EVENT-MATCH
Диалоговый подбор до 3 подрядчиков для мероприятия по городу, категории, дате, бюджету, формату, языку и длительности, с объяснением выбора цитатой…
О проекте
Команда: Фанаты Нурика
Кейс HackAlem.ai: Креативные индустрии — Firebird
Стадия: Рабочий MVP
Задача кейса: «#79-lite»: подобрать до 3 подрядчиков для мероприятия и объяснить выбор
Суть
Диалоговый подбор до 3 подрядчиков для мероприятия по городу, категории, дате, бюджету, формату, языку и длительности, с объяснением выбора цитатой из описания и причинами пустого результата.
Как устроено
Чат извлекает условия локальным разбором (синонимы, сокращения, опечатки), при сложных сообщениях подключается OpenAI Chat Completions со структурированным ответом и проверкой по тексту. Python-код жёстко фильтрует каталог (город, категория, занятость по календарю, цена «от», формат, язык, длительность) и сортирует по цене, затем по id; берутся первые три. Для каждой карточки цитата из description подбирается через эмбеддинги text-embedding-3-small (256) либо лексический поиск; есть загрузка новых CSV с версионированием и фоновой индексацией.
Ключевые решения
- rule-based filtering
- deterministic price ranking
- OpenAI embeddings semantic snippet search
- slot filling dialogue
- fuzzy text matching
Особенности
LLM используется только для извлечения полей и поиска цитат, а допуск и порядок подрядчиков определяет код; сервис различает «нет категории в городе» и «кандидаты есть, но отсеяны» с подсчётом причин, и объясняет изменения выдачи при смене даты.
Стек
Python · http.server (stdlib) · JavaScript · HTML/CSS · unittest
Модели
GPT-4.1-mini (gpt-4.1-mini-2025-04-14) · text-embedding-3-small
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-69e35190-team.
README
README.mdEVENT-MATCH — подбор подрядчиков для мероприятий
Краткое описание: проблема и аудитория
При подготовке мероприятия приходится вручную сопоставлять город, дату, бюджет и специализацию подрядчиков. Подходящий по описанию исполнитель может оказаться занят, не работать с нужным форматом или превышать бюджет.
EVENT-MATCH предназначен для организаторов мероприятий, event-агентств и частных заказчиков в Казахстане. Он собирает условия в диалоге, проверяет их по каталогу и объясняет как подходящие варианты, так и отсутствие результата. Итог работы — короткий список для дальнейшего выбора и уточнения условий у подрядчика.
Что реализовано
- Диалоговый поиск. Чат извлекает город, категорию подрядчика, формат мероприятия, дату и бюджет; при необходимости спрашивает недостающие поля. Язык работы и длительность задаются дополнительно. Условия можно менять следующим сообщением.
- Разбор русского текста. Поддержаны распространённые сокращения, формы слов и небольшие однозначные опечатки:
алм,вед,свадба,22 окт,1,5 млн. Для сложных сообщений предусмотрен дополнительный AI-разбор. - Строгая проверка условий. Город, категория, занятость, цена «от», формат, язык и длительность проверяются кодом. Календарный фильтр применяется также к площадкам.
- До трёх карточек с объяснениями. Каждая содержит код, имя из анонимизированного каталога, категорию, город, цену и проверенные факты с конкретным фрагментом описания. Видны отметки синтетического профиля и полей, заполненных при подготовке данных.
- Понятные пустые результаты. Сервис различает отсутствие категории в городе и ситуацию, когда кандидаты есть, но не проходят условия. Во втором случае показывает причины исключения и их количество.
- Повторяемость и сравнение дат. При неизменных условиях и каталоге порядок карточек совпадает. При изменении только даты сервис объясняет изменения состава по календарю и правилу сортировки.
- Загрузка новых CSV. Есть выбор каталога, проверка данных, отдельные версии и календарные периоды, сохранение после перезапуска и фоновая индексация описаний.
- Демонстрация в интерфейсе. Готовые примеры, сравнение 10 и 11 октября, повтор запроса и измерение времени ответа доступны кнопками над чатом.
Как работает решение
- Пользователь выбирает каталог и пишет, например: «Корпоратив в Алматы 10 октября 2026, ведущий, бюджет 1500000».
- Локальный разбор выделяет условия. Если их не удалось собрать полностью или распознать изменение и настроен API-ключ, подключается LLM. Все полученные значения проходят проверку; недостающие условия чат уточняет по одному.
- Backend выбирает профили нужной категории в городе и исключает занятых на дату, превышающих бюджет или не соответствующих формату, языку и длительности.
- Подходящие профили сортируются по возрастанию
price_from_kzt, при равной цене — поid. В ответ попадают первые три. Это порядок по стоимости, а не оценка качества исполнителя. - Для каждой карточки выбирается содержательный фрагмент её собственного
description: с помощью эмбеддингов при готовом AI-индексе либо локального поиска по словам. Текст объяснения собирается из проверенных полей и цитаты. - Пользователь получает карточки или объяснение отсутствия результата. Сообщение «11 октября» пересчитывает подбор с сохранением остальных условий.
Роль AI: извлечение полей из сложного сообщения и семантический поиск фрагментов описаний. Допуск подрядчика, статус ответа и порядок карточек определяет Python-код. Загрузка CSV пополняет базу знаний; веса моделей не дообучаются.
Технологии
| Часть | Технологии и назначение |
|---|---|
| Backend | Python 3.9+, стандартная библиотека: http.server, csv, json, datetime, pathlib, threading, concurrent.futures. |
| Frontend | HTML5, CSS, JavaScript и Fetch API; без фреймворка и сборки. Интерфейс и API обслуживаются одним Python-сервером. |
| AI-разбор | OpenAI Chat Completions API; модель по умолчанию — gpt-4.1-mini-2025-04-14. Структурированный ответ проверяется по схеме и текстовым подтверждениям из сообщения. |
| Семантический поиск | OpenAI Embeddings API; по умолчанию text-embedding-3-small, размерность 256. Поиск фрагментов и сравнение векторов выполняются локально. |
| Хранение | CSV-каталоги, JSON-метаданные и JSON-индексы на локальном диске; отдельная СУБД не требуется. |
| Тестирование | unittest, подмены внешних сервисов и локальные HTTP-серверы для интеграционных проверок. |
Сторонние Python-пакеты, Node.js и npm для запуска не нужны. Переменная NVIDIA_API_KEY присутствует в образце настроек, но код её не использует.
Архитектура проекта
flowchart TD
UI["Браузер: чат и каталоги"] --> API["backend/api.py: HTTP и JSON API"]
API --> CHAT["backend/chat.py: разбор и уточнение условий"]
CHAT --> RULES["backend/service.py: фильтры и сортировка"]
API --> RULES
API --> DATA["backend/datasets.py: версии и импорт CSV"]
DATA --> CSV["CSV, метаданные и локальные индексы"]
CSV --> RULES
RULES --> EXPLAIN["ai_layer: выбор цитаты и объяснение"]
CHAT -. "сложные сообщения" .-> LLM["OpenAI Chat Completions"]
EXPLAIN -. "при настроенном ключе" .-> EMB["OpenAI Embeddings"]
EXPLAIN --> RESULT["JSON: статус, причины, до 3 карточек"]
RESULT --> UI
Основные файлы и каталоги:
backend/
api.py # HTTP-сервер, frontend и маршруты API
chat.py # Диалог и извлечение условий запроса
text_matching.py # Синонимы, сокращения и небольшие опечатки
service.py # Валидация, фильтры, ранжирование, сравнение дат
catalog.py # Чтение CSV и проверка профилей
datasets.py # Импорт, версии, сохранение и индексация каталогов
ai_layer/ # LLM-разбор, эмбеддинги, поиск цитат, объяснения
frontend/ # index.html и app.js
datasets/ # Исходный CSV и его Markdown-представление
examples/ # Шесть запросов с ожидаемыми результатами
tests/ # Автоматические проверки
docs/ # Контракт, AI-пайплайн и сценарии демонстрации
.env.example # Образец необязательных AI-настроек
Состояние текущего диалога frontend передаёт в API как slots и pending_fields — условия, которые ещё нужно уточнить. Нераспознанный язык или длительность не теряются при следующем сообщении; подбор продолжается только после уточнения или явной отмены необязательного ограничения. Каталоги изолированы: одинаковые id в разных CSV не смешиваются. Индекс исходного каталога хранится в .cache/, импортированные данные, метаданные и индексы — в .local/datasets/. Эти каталоги и локальный .env исключены из Git.
Установка и запуск
Локальный запуск без AI-ключа
Потребуются Python 3.9 или новее и браузер. Git нужен, если проект скачивается командой git clone.
-
Скачайте репозиторий и перейдите в его корень. Если проект уже открыт локально, используйте существующую папку:
git clone https://github.com/BAITC-Hacks/hack-69e35190-team.git cd hack-69e35190-team -
Проверьте Python:
python --versionВ системах, где Python 3 вызывается как
python3, заменитеpythonнаpython3во всех командах ниже. -
Запустите сервер из корня проекта:
python -m backend.api -
Откройте http://127.0.0.1:8000. Оставьте терминал с сервером открытым; для остановки нажмите
Ctrl+C.
Проверка доступности: GET /health должен вернуть {"status": "ok", "catalog_size": 66} для исходного каталога. Открывайте интерфейс через Python-сервер: файл frontend/index.html, открытый напрямую, не подключит API.
Если порт занят, запустите python -m backend.api --port 8001 и откройте http://127.0.0.1:8001. После изменения Python-кода или .env перезапустите сервер.
Подключение AI — необязательно
Создайте в корне файл .env по образцу .env.example. Если файл уже существует, обновите нужные значения в нём:
OPENAI_API_KEY=ваш_ключ
AI_EMBEDDING_MODEL=text-embedding-3-small
AI_EMBEDDING_DIMENSIONS=256
AI_CHAT_MODEL=gpt-4.1-mini-2025-04-14
Нужны доступ к OpenAI API и интернет. Настройки также можно задавать переменными окружения; они имеют приоритет над .env. Ключ остаётся на сервере и не передаётся в браузер.
После перезапуска недостающие индексы строятся в фоне. В разделе «Каталоги и загрузка CSV» доступны статус и кнопка «Построить AI-индекс» для повторной попытки. Состояния: lexical — локальный поиск, indexing — построение индекса, ready — AI-индекс готов, degraded — ошибка индексации с сохранением локального подбора.
Индекс исходного каталога можно подготовить отдельно:
python -m ai_layer prepare-index
Эта команда требует API-ключ. Без ключа или при отказе провайдера продолжают работать локальный разбор, фильтры, ранжирование и объяснения по CSV.
Как проверить решение
Сценарий для жюри
-
Запустите сервер и откройте интерфейс. В разделе «Каталоги и загрузка CSV» выберите исходный каталог
default. -
Нажмите «Показать три примера». В диалоге последовательно появятся:
Пример Ожидаемый результат Ведущий на корпоратив в Алматы, 10.10.2026, бюджет 1 500 000 ₸ matched: 10 кандидатов, 5 подходят; три карточки в порядкеHK-88430,HK-29829,HK-27222.Флорист на свадьбу в Алматы, 10.10.2026, бюджет 500 000 ₸ matched: 2 кандидата, подходит один —HK-39372; второй занят.Фотограф на корпоратив в Алматы, 25.10.2026, 3 часа, бюджет 50 000 ₸ no_matches: 8 кандидатов, у всех цена выше бюджета; дополнительно 3 заняты и 4 не берут формат. -
Нажмите «Сравнить даты · 10 и 11 октября». Для ведущих на 11.10.2026 проходят 4 профиля, а первые три —
HK-44923,HK-27222,HK-44733. Интерфейс объяснит, какие профили сменились и почему. -
Нажмите «Повторить запрос». Порядок кодов карточек должен совпасть с предыдущим результатом. Под ответом отображается измеренное время запроса.
-
Нажмите «Нет категории в городе». Поиск флориста за рубежом вернёт
category_not_in_cityи пустой список карточек. -
Для проверки уточнений нажмите «Новый поиск», введите «Корпоратив в Алматы 10 октября 2026, бюджет 1500000», затем ответьте «ведущий». Чат сохранит уже распознанные условия и покажет результат первого примера.
Причины исключения могут пересекаться: один профиль бывает одновременно занят и дороже бюджета. Поэтому их сумма не обязана совпадать с количеством исключённых кандидатов. Для времени 15:00 в примере с фотографом появляется предупреждение: календарь учитывает весь день.
Проверка API
Пример для PowerShell при работающем сервере:
$body = @{
city = 'Алматы'
event_date = '2026-10-10'
event_type = 'корпоратив'
category = 'Ведущий'
budget_kzt = 1500000
} | ConvertTo-Json
$response = Invoke-RestMethod -Uri 'http://127.0.0.1:8000/recommendations' `
-Method Post -ContentType 'application/json; charset=utf-8' `
-Body ([System.Text.Encoding]::UTF8.GetBytes($body))
$response | ConvertTo-Json -Depth 6
Ожидаются status: matched, candidate_count: 10, eligible_count: 5 и те же три id, что в первом примере. Необязательные поля запроса: language, duration_hours, dataset_id (по умолчанию default).
| Маршрут | Назначение |
|---|---|
GET /health | Проверка работы сервера и размер исходного каталога. |
POST /recommendations | Подбор по структурированным условиям. |
POST /chat | Диалог: {message, slots?, pending_fields?, dataset_id?}. Возвращает распознанные поля, уточнения и result; до заполнения и уточнения условий result равен null. |
GET /datasets | Каталоги, их словари, период календаря и статус индекса. |
POST /datasets | Импорт: {name, csv, calendar_start, calendar_end}; csv передаётся строкой. |
POST /datasets/index | Фоновая индексация выбранного {dataset_id}. |
Все три бизнес-статуса (matched, category_not_in_city, no_matches) возвращаются с HTTP 200. Ошибки валидации — HTTP 400; непредвиденная ошибка — JSON с HTTP 500 и request_id. При работе через /chat передавайте полученные slots и pending_fields в следующем запросе и сбрасывайте их при смене каталога. Примеры исправлений: «не Алматы, а Астана», «бюджет максимум 500000», «на 6 часов 30 минут», «язык любой, длительность не важна».
Автоматические тесты
Из корня проекта выполните:
python -m unittest discover -s tests -v
После повторного аудита на Python 3.9.6 проверено 69 тестов — все прошли. Тесты не требуют API-ключа и не обращаются к внешним сервисам. Они проверяют шесть эталонных сценариев, диалог и опечатки, исправления и накопленные уточнения, фильтры, порядок выдачи, принадлежность цитат профилям, импорт и регистр меток, изоляцию каталогов, перезапуск, ошибочные данные, отказ провайдера, сетевые тайм-ауты и обработку HTTP 500. Запись ERROR в проверке аварийного ответа ожидаема; итог успешного запуска — OK. Исправленные случаи и результаты живой проверки — в docs/regressions.md.
Дополнительное демо объяснений: python -m ai_layer demo. Оно использует заранее выбранные карточки из эталонных сценариев и проверяет только AI-слой; полный подбор проверяется через интерфейс, API и тесты выше.
Данные и интеграции
Исходный каталог
Источник — предоставленный для хакатона анонимизированный CSV-каталог. Markdown-файл рядом — представление тех же данных; приложение читает CSV.
| Характеристика | Значение |
|---|---|
| Профили и категории | 66 профилей, 17 категорий; один профиль может относиться к нескольким категориям. |
| Города | Алматы — 50 профилей, Астана — 15, «Зарубежье» — 1. |
| Примеры категорий | Ведущие — 15, фотографы — 12, банкетные залы — 8, флористы — 3 по всему каталогу. |
| Цены «от» | От 100 000 до 6 000 000 ₸. |
| Период известного календаря | 23.09.2026–31.12.2026 включительно. |
| Происхождение полей | 13 синтетических профилей; у 8 профилей город, у 18 цена заполнены при подготовке набора. |
Поля synthetic, city_imputed, price_imputed сохраняются в карточках. synthetic=false означает несинтетическую запись исходного набора, а не подтверждённую личность подрядчика.
Загрузка своего CSV
В разделе «Каталоги и загрузка CSV» укажите название, файл и полный известный период календаря, затем нажмите «Загрузить CSV». Новый каталог выбирается автоматически, диалог сбрасывается. Начало и конец периода задаются явно: первая и последняя занятые даты не определяют границы известной доступности.
Формат: UTF-8 (BOM допустим), разделитель столбцов — запятая, списков — |, даты — YYYY-MM-DD. Нужны столбцы:
id,anon_name,categories,city,price_from_kzt,event_formats,languages,max_hours,busy_dates,description,synthetic,city_imputed,price_imputed
Цена — неотрицательное целое число тенге; флаги — true/false без учёта регистра. Пустой max_hours или null означает отсутствие ограничения по часам присутствия. Пустой busy_dates допустим внутри заявленного периода. Выдуманные профили следует отмечать synthetic=true.
Импорт отклоняет дубли id, пустые имена и описания, некорректные числа, даты и флаги. Лимиты: 2 000 000 байт на CSV, 5000 профилей в каталоге, 50 каталогов вместе с исходным. Повторная загрузка идентичного содержимого с тем же периодом использует существующую версию; изменение содержимого или периода создаёт новую. Исходный датасет сохраняется без изменений.
Внешние сервисы
Единственная реализованная внешняя интеграция — необязательный OpenAI API. При включённом AI в сервис передаются фрагменты описаний и поисковый текст для эмбеддингов, а для сложных сообщений — текст пользователя, текущие условия и словарь каталога. Учитывайте это при выборе данных для загрузки.
Подключений к реальным календарям подрядчиков, CRM, оплате и сервисам бронирования нет. Актуальность подбора определяется выбранным CSV.
Ограничения текущей версии
- Локальный прототип. Нет учётных записей, разграничения доступа, квот и TLS; используется стандартный HTTP-сервер Python. Для публичной эксплуатации требуется отдельная подготовка инфраструктуры и защиты API.
- Календарь ограничен данными. Для исходного набора разрешён только период 23.09–31.12.2026. Даты вне известного периода отклоняются. Время начала и пересечения по часам не проверяются.
- Цена и доступность предварительные. Фильтр сравнивает бюджет с ценой «от», включая проставленные при подготовке цены. Отсутствие даты в списке занятости не гарантирует возможность заказа. Бронирование и подтверждение стоимости не реализованы.
- Ранжирование простое. Учитываются цена и
id; рейтинги, отзывы, качество работы и персонализация в формулу не входят. - Разбор текста не универсален. Сложную или неоднозначную формулировку может потребоваться уточнить. Распознанные условия отображаются в интерфейсе и доступны для исправления. История диалога после перезагрузки страницы не сохраняется.
- Импорт требует заданную схему. Таблицы с другими колонками или из другой предметной области требуют адаптера. Fine-tuning моделей не выполняется.
- Время ответа зависит от окружения. Ориентир демо — до 10 секунд. Каждый пользовательский AI-вызов ограничен 3 секундами ожидания с переходом на локальную обработку; это не гарантия сетевого SLA или производительности под нагрузкой.
Развёрнутая версия
Публичная ссылка на deployed-версию в репозитории не указана. Для демонстрации используйте локальный адрес http://127.0.0.1:8000 после запуска сервера.
Дополнительные материалы: контракт и правила подбора, устройство AI-слоя, демонстрационные запросы, эталонные JSON-сценарии, история проверки требований.
Авторы коммитов
История коммитов
- 2026-09-23 17:43mvpNurislam
- 2026-09-23 17:13Изменения readmeAba
- 2026-09-23 16:54Improve contractor search, demos, and AI reliabilityPas1fon
- 2026-09-23 15:38test mvpNurislam
- 2026-09-23 14:31Merge branch 'main' of https://github.com/BAITC-Hacks/hack-69e35190-teamPas1fon
- 2026-09-23 14:30добавил визуалPas1fon
- 2026-09-23 14:21Добавление логики AINurislam
- 2026-09-23 13:35upload datasetsNurislam
Структура репозитория (40 файлов)
.DS_Store .env.example .gitignore README.md ai_layer/__init__.py ai_layer/__main__.py ai_layer/catalog.py ai_layer/config.py ai_layer/embeddings.py ai_layer/evidence.py ai_layer/explainer.py ai_layer/http_json.py ai_layer/intent.py backend/__init__.py backend/api.py backend/catalog.py backend/chat.py backend/datasets.py backend/service.py backend/text_matching.py datasets/.DS_Store datasets/hackathon dataset anonymized .csv datasets/hackathon dataset anonymized.md docs/ai-pipeline.md docs/audit.md docs/demo-queries.md docs/regressions.md docs/spec.md examples/demo-cases.json frontend/app.js frontend/index.html tests/test_acceptance.py tests/test_ai_layer.py tests/test_backend.py tests/test_catalog_regressions.py tests/test_chat_language.py tests/test_chat_regressions.py tests/test_explanation_regressions.py tests/test_latency.py tests/test_product.py
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.