AAYMA
RU
Войти

EventMatch

Локальный сервис подбора до трёх подрядчиков для мероприятия с объяснениями, дословными цитатами из профилей и проверенными альтернативами условий…

Доведённый продукт
Команда «RDЕvibe» 27💬 0 комментариевс 23 сентября 2026 г.

О проекте

Команда: RDЕvibe
Кейс HackAlem.ai: Креативные индустрии — Firebird
Стадия: Доведённый продукт

Задача кейса: «#79-lite»: подобрать до 3 подрядчиков для мероприятия и объяснить выбор

Суть

Локальный сервис подбора до трёх подрядчиков для мероприятия с объяснениями, дословными цитатами из профилей и проверенными альтернативами условий, если никто не подошёл.

Как устроено

Сначала применяются жёсткие фильтры: город, категория, свободная дата, формат, бюджет, язык и длительность. Затем пожелание пользователя и 262 фрагмента из 66 профилей кодируются моделью multilingual-e5-small (ONNX int8, размерность 384), и кандидаты ранжируются фиксированной формулой: 85×clamp((cos−0.65)/0.30) плюс 15×(1−цена/бюджет). Объяснение собирается из полей профиля и точной цитаты без генеративной LLM. Если результат пустой, система предлагает проверенные изменения бюджета или даты и сравнивает выдачу по датам. Бэкенд на FastAPI, фронт на чистом HTML/JS.

Ключевые решения

  • multilingual-e5-small ONNX int8
  • косинусное сходство эмбеддингов
  • жёсткие фильтры + эвристическое ранжирование
  • извлечение цитаты-доказательства
  • генерация альтернатив условий

Особенности

Семантический поиск работает полностью офлайн: веса ONNX-модели лежат в репозитории, внешний API не нужен. Скоринг прозрачный и разложен на смысловой и бюджетный вклад. Есть 31 автотест и пять демо-сценариев, включая «нет категории» и «нет совпадений».

Стек

Python · FastAPI · Uvicorn · Pydantic · ONNX Runtime · HTML/CSS/JS · pytest

Модели

multilingual-e5-small


Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-7f67d212-rd-vibe.

README

README.md

EventMatch — объяснимый ИИ-подбор подрядчиков

Готовый прототип для задачи #79-lite. Пользователь задаёт условия мероприятия и описывает пожелания своими словами. Сервис возвращает до трёх доступных профилей, показывает основания выбора и предлагает проверенные изменения условий, если никто не подошёл.

Это работающий локальный AI-поиск, а не имитация. В комплекте модель multilingual-e5-small в формате ONNX, векторный индекс и исходные 66 профилей. Ключ OpenAI, платный API, GPU и отдельный сервер базы данных не требуются. Данные хранятся в файлах; PostgreSQL здесь не подключён и для этого объёма не нужен.

Быстрый запуск на Windows

Frontend, backend и ИИ запускаются вместе через START.cmd. Отдельно поднимать три программы не нужно.

Что понадобится

  • Обычный 64-битный CPython 3.12 или 3.13. Проверено на Windows / Python 3.12.14. MSYS/MinGW Python не подходит для ONNX.
  • Интернет для первой установки Python-зависимостей. Веса ИИ-модели уже включены в полный архив.
  • Браузер. Node.js, npm, Docker, GPU и платный API-ключ не нужны.

Если Python не установлен, скачайте его с python.org. В установщике отметьте Add Python to PATH. После установки откройте новое окно терминала, если будете запускать проект командой.

Запуск без команд

  1. Нажмите правой кнопкой на EventMatch-final.zip → «Извлечь всё». Распакуйте весь архив, а не только HTML-файл.
  2. Откройте распакованную папку hackathon-contractor-ai. Внутри должны находиться START.cmd, run.py и папки backend, frontend, docs.
  3. Дважды нажмите START.cmd. Откроется окно сервера. При первом запуске дождитесь установки зависимостей; при следующих они не устанавливаются заново, если список зависимостей не изменился.
  4. Дождитесь сообщения Application startup complete. Браузер откроется автоматически. Если не открылся, перейдите на http://127.0.0.1:8000/app/.
  5. Нажмите «01 · Спокойный ведущий» над формой. Должны появиться три карточки с объяснениями и выделенными цитатами.

Не закрывайте окно сервера, пока пользуетесь сайтом. Для остановки нажмите в этом окне Ctrl+C. Для повторного запуска снова откройте START.cmd.

Не запускайте файлы прямо внутри ZIP. Не открывайте frontend/index.html отдельно для основного сценария: страница сама по себе не запускает backend и ИИ. Правильная точка входа — адрес /app/ работающего сервера.

После установки зависимостей подбор работает без интернета: запросы и описания не отправляются во внешний AI-сервис.

Запуск через PowerShell

Откройте папку проекта в Проводнике, введите powershell в адресную строку и нажмите Enter. В открывшемся окне выполните:

python run.py

Если команда python не найдена, но установлен Python Launcher, можно выполнить:

py -3.12 run.py

Если порт 8000 занят, выберите другой:

python run.py --port 8001

Тогда сайт будет доступен на http://127.0.0.1:8001/app/. Используйте порт, указанный в окне запуска.

Как убедиться, что всё работает

Для проверки смыслового поиска сначала нажмите «01 · Спокойный ведущий», затем замените пожелание на «Без долгих речей, только развлечения и танцы» и повторите подбор. При неизменных остальных условиях порядок должен измениться; первым станет профиль Аня Форджер.

Если не запускается

Что произошлоЧто сделать
python не найден или требуется Python 3.12+Установите обычный 64-битный Python 3.12/3.13 с опцией Add Python to PATH и повторите запуск.
Ошибка установки зависимостейПроверьте интернет и последние строки сообщения об ошибке. После устранения причины повторно откройте START.cmd.
Порт занят / WinError 10048Возможно, проект уже запущен. Откройте его адрес либо выполните python run.py --port 8001.
Сайт сообщает «Нет связи с сервером»Проверьте, что окно сервера не закрыто и запуск завершился без ошибки. Открывайте /app/ на порту из окна запуска.
ИИ не загружен или HTTP 503Восстановите модель по разделу «Повторная загрузка модели» ниже и перезапустите сервер.
Дата не принимаетсяИспользуйте дату с 23 сентября по 31 декабря 2026 года: только этот период есть в датасете.
Нет подходящих подрядчиковЭто не обязательно ошибка. Проверьте условия и предложенные сервисом альтернативы; для контрольного примера используйте демо-кнопки.

Если ошибка осталась, сохраните последние строки или скриншот окна сервера, а не только страницы браузера: там указана причина.

Что отличает решение

  • Смысловой поиск: пожелания вроде «спокойная ненавязчивая подача» влияют на порядок, даже если слова не совпали буквально.
  • Условия важнее AI: город, категория, свободная дата, формат, бюджет, язык и длительность остаются жёсткими ограничениями. Высокая смысловая близость не вернёт занятого или слишком дорогого подрядчика.
  • Доказательства рядом с ответом: цитата выделена дословно; в «Профиль и факты» можно увидеть её в исходном описании.
  • Проверенные альтернативы: при пустом результате показывается конкретное изменение бюджета, даты или других условий и число действительно доступных вариантов. Изменение применяется только по нажатию пользователя.
  • Сравнение дат: при изменении только даты интерфейс объясняет, кто из прошлой выдачи стал занят и кто появился благодаря календарю.
  • Прозрачный порядок: раскрывающийся блок показывает вклад смысла и бюджета. Баллы не выдаются за вероятность успеха.
  • Честные данные: видны флаги синтетического профиля, проставленной цены и города. 13 синтетических профилей уже были в предоставленном датасете; мы не добавляли выдуманных подрядчиков.
  • Проверяемость: 31 автоматическая проверка, воспроизводимый диагностический набор и пять готовых демонстраций в интерфейсе.

Что показать жюри

Откройте приложение и последовательно нажмите пять кнопок над формой. Они заполняют форму и отправляют настоящий запрос к API.

  1. Спокойный ведущий: три профиля с индивидуальными цитатами, соответствующими пожеланию.
  2. Другая дата: календарь меняет выдачу; отдельная плашка объясняет изменения.
  3. Редкая категория: два флориста, честное объяснение «почему не три», отметки качества данных.
  4. Нет совпадений: при бюджете 100 000 ₸ на 31 декабря появляется проверенное предложение увеличить его до 250 000 ₸. Нажмите предложение — будет найден один профиль.
  5. Нет категории: отдельный бизнес-исход, а не ошибка сервера; можно применить доступную альтернативу по городу.

Подробный трёхминутный сценарий — docs/demo.md.

Как устроен поиск

Модель multilingual-e5-small преобразует пожелание и фрагменты описаний в векторы. Используется локальный ONNX int8-экспорт Xenova, закреплённая ревизия 761b726dd34fb83930e26aab4e9ac3899aa1fa78. Индекс содержит 262 дословных фрагмента из 66 профилей, размерность — 384.

После обязательных фильтров профили получают оценку:

смысловой вклад = 85 × clamp((cosine_similarity − 0.65) / 0.30, 0, 1)
вклад бюджета  = 15 × (1 − price_from_kzt / budget)
итог           = смысловой вклад + вклад бюджета

Это фиксированная эвристика ранжирования, а не обученная вероятность. Если пожелание пустое, используется только запас по стартовой цене (100 баллов). Равенство разрешается по цене и затем по id.

Объяснение собирается из полей профиля и точной цитаты, без генеративной LLM. Модель выбирает релевантный фрагмент, но не переписывает его. Подробнее — docs/architecture.md.

API

  • GET /health — состояние, число профилей, semantic_ready, версия модели и календарь.
  • GET /catalog — города, категории, форматы, языки и счётчики происхождения данных.
  • POST /recommend — подбор.
  • /docs — интерактивная документация FastAPI.
  • /app/ — интерфейс.

Пример тела запроса:

{
  "city": "Алматы",
  "date": "2026-10-15",
  "event_format": "корпоратив",
  "category": "Ведущий",
  "budget": 2000000,
  "duration": 6,
  "language": "русский",
  "preferences": "Спокойная ненавязчивая подача, интеллигентный юмор и уютная атмосфера"
}

language, duration, preferences и compare_date опциональны. Последнее поле — предыдущая дата для сравнения при остальных неизменных условиях. Календарь датасета: 23.09.2026–31.12.2026. Бюджет — целое число от 1 до 1 000 000 000 ₸; длительность — целое от 1 до 24 часов; пожелание — до 600 символов.

HTTP 200 содержит один из трёх бизнес-исходов:

  • ok: до трёх элементов results, персональные explanation, точные evidence с позициями в описании, score, флаги данных;
  • category_not_found: в выбранном городе нет такой категории;
  • no_matches: категория есть, но никто не прошёл все условия.

Дополнительно возвращаются stats, rejected, suggestions, ranking, date_comparison. Счётчики причин относятся только к выбранным городу и категории и могут пересекаться: один профиль может нарушить несколько условий.

Ошибочный запрос — HTTP 422. Если модель отсутствует или не загрузилась, поиск с пожеланием возвращает HTTP 503 с понятным сообщением; простой подбор без пожелания остаётся доступен. Сервис не подменяет AI-поиск незаметной имитацией. Время обработки вынесено в заголовок X-Response-Time-Ms, чтобы тело одинаковых запросов оставалось одинаковым.

Технологии

  • Интерфейс: обычные HTML, CSS и JavaScript, без сборки и frontend-фреймворка.
  • Сервер: Python, FastAPI, Uvicorn; проверка запросов — Pydantic.
  • ИИ-поиск: multilingual-e5-small, ONNX Runtime, Tokenizers и NumPy; вычисления на CPU.
  • Хранение: локальный каталог JSON/JSONL и JSON-файл векторного индекса, без отдельной СУБД.
  • Проверки: pytest и FastAPI TestClient. Версии прямых зависимостей закреплены в backend/requirements.txt.

Структура

hackathon-contractor-ai/
├── START.cmd, run.py, README.md, pytest.ini, .gitignore, .env.example
├── THIRD_PARTY_NOTICES.md
├── backend/
│   ├── main.py, requirements.txt, download_model.py
│   ├── recommender/
│   │   ├── semantic.py, generate_embeddings.py, config.py
│   │   ├── models.py, data_loader.py, filters.py
│   │   └── ranking.py, explanations.py, alternatives.py
│   ├── data/contractors.jsonl, embeddings.json
│   ├── models/multilingual-e5-small/
│   ├── tests/
│   └── evaluation/queries.json, run_evaluation.py
├── frontend/index.html, style.css, app.js
└── docs/architecture.md, demo.md, evaluation.md, evaluation.json

contractors.jsonl сохранён под исходным именем, но его содержимое — JSON-массив; загрузчик поддерживает и этот формат, и настоящий JSONL. При изменении описаний/категорий индекс пересобирается автоматически по отпечатку данных.

Проверки и измерения

После первого запуска, из корня проекта в PowerShell:

.venv\Scripts\python.exe -B -m pytest backend/tests -q -p no:cacheprovider
.venv\Scripts\python.exe backend/evaluation/run_evaluation.py

В этой поставке 31 тест прошёл: фильтры, занятость, три исхода, реальные эмбеддинги, изменение порядка от пожеланий, точность цитат, проверяемость альтернатив, сравнение дат, детерминизм и валидация.

На 12 вручную размеченных диагностических запросах поиск нашёл подходящий профиль в топ-3 в 12 случаях; простое совпадение слов — в 6. Это небольшой подобранный smoke-набор, не независимый бенчмарк и не заявление о 100% качестве. Числа и методика: docs/evaluation.md; все запросы и ожидаемые ID доступны для проверки. В отчёте также измерены 20 повторов пяти API-сценариев; это локальный TestClient, включая кэш, не нагрузочный тест.

Ручная проверка включает пять сценариев с живым API, применение альтернативы, модальное окно профиля и мобильную/настольную вёрстку.

Повторная загрузка модели

Полный ZIP уже содержит веса. Если вы клонировали только код, первый запуск загрузит модель из Hugging Face. Для восстановления файлов нужен интернет. После первого запуска проекта откройте PowerShell в его корневой папке и выполните:

.venv\Scripts\python.exe backend/download_model.py
.venv\Scripts\python.exe backend/recommender/generate_embeddings.py

После восстановления остановите работающий сервер через Ctrl+C и снова откройте START.cmd. Параметр semantic_ready по адресу /health должен стать true.

Версия зафиксирована, SHA-256 файлов перечислены в backend/models/multilingual-e5-small/manifest.json. Загрузчик не отправляет датасет или запросы. Уведомления и лицензия — THIRD_PARTY_NOTICES.md.

Честные ограничения

  • Это хакатон-прототип, не система бронирования. Доступность означает отсутствие даты в календаре датасета, а не подтверждение живого подрядчика.
  • Цены указаны «от», часть проставлена составителями датасета. Попадание в бюджет по стартовой цене не гарантирует итоговую стоимость.
  • Пожелание — мягкий сигнал ранжирования. Отрицания, сложные сочетания и субъективные требования модель может понять неточно. Обязательные требования задаются структурированными полями. Цитата не доказывает все пожелания сразу.
  • Цитаты подтверждают, что так написано в профиле, но не являются независимой проверкой правдивости профиля. Шаблонные объяснения не добавляют новых фактов.
  • Нет новых выдуманных кандидатов, выдуманных рейтингов, отзывов или опыта. Синтетические записи исходного датасета явно помечены.
  • Работает на CPU; на другом компьютере время запуска и ответа может отличаться. Промышленная авторизация, лимиты запросов и нагрузочное масштабирование не включены. Сервер по умолчанию слушает только 127.0.0.1.
Звёзды
⭐ 0
Форки
0
Язык
Python
Последний push
в прошлом месяце
Коммиты
72
Авторы
3
Файлы
48
Ветки
1

Авторы коммитов

d3meshka1 46Rassul 16TheBest-Goals 12

История коммитов

  • 2026-09-23 17:44Add files via uploadTheBest-Goals
  • 2026-09-23 17:32Delete FrontendTheBest-Goals
  • 2026-09-23 17:01Refactor generate_embeddings.py for better clarityTheBest-Goals
  • 2026-09-23 17:01Refactor generate_explanation with _get_field helperTheBest-Goals
  • 2026-09-23 17:01Refactor semantic.py for better directory and query handlingTheBest-Goals
  • 2026-09-23 16:57Update README.mdTheBest-Goals
  • 2026-09-23 17:42Add files via uploadd3meshka1
  • 2026-09-23 17:41Add files via uploadd3meshka1
Все коммиты (72)
  • 2026-09-23 17:29Add files via uploadd3meshka1
  • 2026-09-23 16:23Delete semantic.pyTheBest-Goals
  • 2026-09-23 16:21Refactor explanation generation and add utilitiesTheBest-Goals
  • 2026-09-23 16:21Refactor semantic.py for better readability and safetyTheBest-Goals
  • 2026-09-23 17:07Refactor CSS variables and styles for consistencyRassul
  • 2026-09-23 17:07Add files via uploadd3meshka1
  • 2026-09-23 17:07Update event description and form fields in HTMLRassul
  • 2026-09-23 17:06Update print statement from 'Hello' to 'Goodbye'Rassul
  • 2026-09-23 17:06Add files via uploadd3meshka1
  • 2026-09-23 17:05Refactor ranking tests and contractor creationd3meshka1
  • 2026-09-23 17:04Update test_filters.pyd3meshka1
  • 2026-09-23 17:04Revise README for EventMatch projectRassul
  • 2026-09-23 17:04Add files via uploadd3meshka1
  • 2026-09-23 17:03Delete hackalem-backend/backend/scripts directoryd3meshka1
  • 2026-09-23 17:03Add local E5 embeddings and semantic indexingd3meshka1
  • 2026-09-23 17:03Update ranking.pyd3meshka1
  • 2026-09-23 17:03Create generate_embeddings.pyd3meshka1
  • 2026-09-23 17:02Enhance RecommendationRequest model validationsd3meshka1
  • 2026-09-23 17:02Update explanations.pyd3meshka1
  • 2026-09-23 17:01Add configuration settings for recommender moduled3meshka1
  • 2026-09-23 17:01Create alternatives.pyd3meshka1
  • 2026-09-23 17:00Update embeddings.jsond3meshka1
  • 2026-09-23 17:00Update contractors.jsonld3meshka1
  • 2026-09-23 15:42Add files via uploadTheBest-Goals
  • 2026-09-23 16:20Update ranking.pyd3meshka1
  • 2026-09-23 16:15Add architecture documentation for EventMatchRassul
  • 2026-09-23 16:13Update print statement from 'Hello' to 'Goodbye'Rassul
  • 2026-09-23 16:11Add files via uploadd3meshka1
  • 2026-09-23 16:09Refactor explanation generation logicd3meshka1
  • 2026-09-23 16:09Update ranking.pyd3meshka1
  • 2026-09-23 16:07Update semantic.pyd3meshka1
  • 2026-09-23 16:07Update generate_embeddings.pyd3meshka1
  • 2026-09-23 16:07Update test_ranking.pyd3meshka1
  • 2026-09-23 16:07Update test_filters.pyd3meshka1
  • 2026-09-23 15:46Refactor index.html for event contractor selectionRassul
  • 2026-09-23 15:45Refactor CSS styles and add CSS variablesRassul
  • 2026-09-23 15:45Create app.jsRassul
  • 2026-09-23 15:44Create .envd3meshka1
  • 2026-09-23 14:43Create semantic.pyTheBest-Goals
  • 2026-09-23 15:43Update requirements.txtd3meshka1
  • 2026-09-23 15:43Add files via uploadd3meshka1
  • 2026-09-23 15:41Add files via uploadd3meshka1
  • 2026-09-23 15:40Delete hackalem-backend/tests directoryd3meshka1
  • 2026-09-23 15:40Delete hackalem-backend/scripts directoryd3meshka1
  • 2026-09-23 15:39Delete hackalem-backend/recommender directoryd3meshka1
  • 2026-09-23 15:37Create generate_embeddings.pyd3meshka1
  • 2026-09-23 15:11Add custom styles for user interfaceRassul
  • 2026-09-23 15:11Update index.htmlRassul
  • 2026-09-23 15:05Add initial HTML structure for event contractor appRassul
  • 2026-09-23 15:05Delete frontRassul
  • 2026-09-23 14:59Create frontRassul
  • 2026-09-23 14:46Add FastAPI and related dependencies to requirementsRassul
  • 2026-09-23 13:43Add .gitignore to exclude environment files and cacheTheBest-Goals
  • 2026-09-23 14:42Add contractor ranking after filteringd3meshka1
  • 2026-09-23 14:41Update ranking.pyd3meshka1
  • 2026-09-23 14:33Update main.pyd3meshka1
  • 2026-09-23 14:33Update filters.pyd3meshka1
  • 2026-09-23 14:33Update models.pyd3meshka1
  • 2026-09-23 14:19Update main.pyd3meshka1
  • 2026-09-23 14:18Add files via uploadd3meshka1
  • 2026-09-23 14:18Delete hackalem-backend/recomender directoryd3meshka1
  • 2026-09-23 13:52Update contractors.jsonld3meshka1
  • 2026-09-23 13:39Add files via uploadd3meshka1
  • 2026-09-23 13:23Create FrontendRassul
Структура репозитория (48 файлов)
.gitignore
README.md
START.cmd
THIRD_PARTY_NOTICES.md
architecture.md
docs/architecture.md
docs/demo.md
docs/evaluation.json
docs/evaluation.md
front/app.js
front/index.html
front/style.css
hackalem-backend/backend/.env
hackalem-backend/backend/data/contractors.jsonl
hackalem-backend/backend/data/embeddings.json
hackalem-backend/backend/download_model.py
hackalem-backend/backend/evaluation/queries.json
hackalem-backend/backend/evaluation/run_evaluation.py
hackalem-backend/backend/main.py
hackalem-backend/backend/model_bundle.py
hackalem-backend/backend/models/multilingual-e5-small/LICENSE
hackalem-backend/backend/models/multilingual-e5-small/config.json
hackalem-backend/backend/models/multilingual-e5-small/manifest.json
hackalem-backend/backend/models/multilingual-e5-small/model_quantized.onnx.xz (1).part004
hackalem-backend/backend/models/multilingual-e5-small/model_quantized.onnx.xz.part001
hackalem-backend/backend/models/multilingual-e5-small/model_quantized.onnx.xz.part002
hackalem-backend/backend/models/multilingual-e5-small/model_quantized.onnx.xz.part003
hackalem-backend/backend/models/multilingual-e5-small/special_tokens_map.json
hackalem-backend/backend/models/multilingual-e5-small/tokenizer.json
hackalem-backend/backend/models/multilingual-e5-small/tokenizer_config.json
hackalem-backend/backend/recommender/__init__.py
hackalem-backend/backend/recommender/alternatives.py
hackalem-backend/backend/recommender/config.py
hackalem-backend/backend/recommender/data_loader.py
hackalem-backend/backend/recommender/explanations.py
hackalem-backend/backend/recommender/filters.py
hackalem-backend/backend/recommender/generate_embeddings.py
hackalem-backend/backend/recommender/models.py
hackalem-backend/backend/recommender/ranking.py
hackalem-backend/backend/recommender/semantic.py
hackalem-backend/backend/requirements.txt
hackalem-backend/backend/tests/test_api.py
hackalem-backend/backend/tests/test_explanations.py
hackalem-backend/backend/tests/test_filters.py
hackalem-backend/backend/tests/test_ranking.py
hackalem-backend/backend/tests/test_semantic_workflow.py
pytest.ini
run.py

Обсуждение 0 комментариев

Войдите, чтобы комментировать

Пока нет комментариев — будьте первым.