AAYMA
RU
Войти

EventMatch

Чат на Streamlit. Собирает условия мероприятия (локация, дата, формат, категория, бюджет) за несколько реплик, после подтверждения выдаёт до трёх…

Рабочий MVP
Команда «Nova» 9💬 0 комментариевс 23 сентября 2026 г.

О проекте

Команда: Nova
Кейс HackAlem.ai: Креативные индустрии — Firebird
Стадия: Рабочий MVP

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

Суть

Чат на Streamlit. Собирает условия мероприятия (локация, дата, формат, категория, бюджет) за несколько реплик, после подтверждения выдаёт до трёх подрядчиков из каталога с объяснениями на основе фактов.

Как устроено

LLM (OpenAI Responses API + Pydantic Structured Outputs) ведёт диалог и извлекает параметры, но не решает, кого рекомендовать. Детерминированный matcher применяет жёсткие фильтры (город, категория, busy_dates, формат, price_from ≤ бюджет, часы) и ранжирует через локальный TF-IDF с косинусным сходством. Исходы: SUCCESS / CATEGORY_NOT_FOUND / NO_MATCH; если генерация не удалась, объяснения детерминированные. Архитектура слоистая: UI → application → domain.

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

  • Structured Outputs
  • жёсткая фильтрация
  • TF-IDF
  • косинусное сходство
  • проверка доступности по датам

Особенности

Чёткое правило «LLM понимает, код решает». Синтетические профили и оценочные цены/локации честно помечены. Тесты проверяют архитектурные зависимости между слоями.

Стек

Python 3.10+ · Streamlit 1.64 · OpenAI SDK · Pydantic 2 · pytest

Модели

gpt-4.1-mini (OpenAI)


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

README

README.md

EventMatch — подбор подрядчиков для мероприятий

EventMatch помогает найти подрядчика для мероприятия: пользователь описывает задачу на русском, а помощник уточняет детали и собирает условия поиска. После подтверждения система возвращает до трёх рекомендаций с индивидуальными объяснениями.

LLM понимает запрос; детерминированный код решает, кого рекомендовать. Модель ведёт диалог и извлекает структурированные параметры; допуск подрядчиков и порядок результатов определяет Python.

Что реализовано

  • Сбор локации, даты, формата мероприятия, категории и бюджета за несколько сообщений; язык и длительность необязательны.
  • Уточнение недостающих и неоднозначных условий, исправления и продолжение поиска в том же разговоре, подтверждение перед подбором.
  • Обработка посторонних и небезопасных сообщений без запуска подбора.
  • Жёсткие фильтры, детерминированное ранжирование и максимум три карточки с объяснениями на основе фактов каталога.
  • Исходы SUCCESS, CATEGORY_NOT_FOUND, NO_MATCH и фактические причины отказа.
  • Проверка периода доступности, обозначение синтетических профилей и ориентировочных данных.
  • Безопасное отображение ошибок API и детерминированные объяснения при сбое генерации текста.

Текущий интерфейс — только чат, без переключения режимов. Кнопка «Новый поиск» сбрасывает разговор; «О сервисе» и «Как работает подбор» находятся под чатом. Поддерживаются светлая и тёмная темы. Состояние разговора хранится только в сессии.

Технологии

Python 3.10+, Streamlit 1.64.0, OpenAI SDK 2.x / Responses API, Pydantic 2.x / Structured Outputs, python-dotenv 1.x, pytest 8–9. Детерминированный движок, включая локальный TF-IDF и косинусное сходство, использует стандартную библиотеку Python. База данных не нужна.

Архитектура

app.py                            # запуск и композиция Streamlit
src/eventmatch/
├── paths.py                      # пути каталога и .env
├── domain/matcher.py              # Request, Contractor, загрузка, фильтры, рейтинг
├── application/
│   ├── conversation.py           # диалог, уточнения, подтверждение, поиск
│   ├── availability.py           # проверка периода доступности
│   └── presentation.py           # резюме, исходы, резервные объяснения
├── ai/
│   ├── models.py                 # независимые от провайдера схемы
│   └── prompts.py                # инструкции извлечения и объяснений
├── infrastructure/openai_client.py # OpenAI SDK, настройки, обработка ошибок
└── ui/
    ├── chat.py                   # текущий разговорный интерфейс
    ├── components.py             # карточки, оформление, справка
    ├── state.py                  # интеграция состояния со Streamlit
    ├── manual.py                 # сохранённый модуль; не подключён к app.py
    └── demo_scenarios.py          # фиксированные запросы для CLI-проверки
scripts/
├── demo.py                       # проверка четырёх сценариев без API
└── eval_live.py                  # отдельные проверки с настоящим API
tests/
├── support.py
├── unit/
└── integration/
data/hackathon dataset anonymized .csv
.streamlit/config.toml
.env.example
pyproject.toml
requirements.txt

Направление зависимостей: UI → application → domain. Application использует схемы ai и внедрённый backend, но не импортирует Streamlit или OpenAI SDK. infrastructure реализует вызовы провайдера по AI-контракту; app.py связывает компоненты. Domain не зависит от UI, API или инфраструктуры.

Пользователь → Streamlit UI → conversation
                                 ├── OpenAI → структурированные изменения запроса
                                 └── валидация → подтверждение → matcher
                                                                  ↓
                                             Top-3 / CATEGORY_NOT_FOUND / NO_MATCH
                                                                  ↓
                                    объяснения по фактам → карточки Streamlit

LLM не решает, соответствует ли подрядчик локации, категории, дате, бюджету, формату или длительности, и не определяет порядок выдачи. Текст объяснений хранится отдельно от результатов matcher; при ошибке генерации используются детерминированные формулировки.

Установка и запуск

Нужны Git, Python 3.10+ и интернет для установки зависимостей. Для разговора также нужен действующий ключ OpenAI с доступом к выбранной модели. Все команды после cd выполняются из корня репозитория.

macOS / Linux

git clone https://github.com/BAITC-Hacks/hack-0bb708a1-nova.git
cd hack-0bb708a1-nova
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
python -c "import shutil; shutil.copyfile('.env.example', '.env')"

Откройте созданный .env в редакторе и заполните OPENAI_API_KEY по инструкции ниже. Затем:

streamlit run app.py

Windows PowerShell

git clone https://github.com/BAITC-Hacks/hack-0bb708a1-nova.git
cd hack-0bb708a1-nova
py -m venv .venv
.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
python -c "import shutil; shutil.copyfile('.env.example', '.env')"

Откройте .env в редакторе и заполните OPENAI_API_KEY. Затем:

streamlit run app.py

Если PowerShell запрещает активацию, выполните Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass и повторите .venv\Scripts\Activate.ps1. Изменение действует только в текущем процессе PowerShell.

Streamlit обычно открывает http://localhost:8501, но при занятом порте может выбрать другой свободный. Используйте URL, напечатанный в терминале; при необходимости явно задайте порт: streamlit run app.py --server.port 8502. Остановка сервера — Ctrl+C.

Параметры окружения

Создайте в корне проекта копию .env.example с именем .env — через файловый менеджер или команду копирования выше. В новом файле укажите свой ключ; не перезаписывайте уже настроенный .env при повторной установке.

OPENAI_API_KEY=ваш_ключ_OpenAI
OPENAI_MODEL=gpt-4.1-mini
ПеременнаяОбязательностьНазначениеПо умолчанию
OPENAI_API_KEYДля чатаДоступ к OpenAI APIНе задана
OPENAI_MODELНетМодель с поддержкой используемых Structured Outputsgpt-4.1-mini
NOVA_DATASETНетПуть к другому CSV или JSON той же схемыdata/hackathon dataset anonymized .csv из checkout

Настройки OpenAI читаются из окружения и корневого .env; уже заданные переменные окружения имеют приоритет. После изменения настроек перезапустите сервер. .env исключён через .gitignore, не отслеживается Git и никогда не должен попадать в коммит. .env.example содержит только шаблон.

Для комплектного каталога NOVA_DATASET не нужен. Переопределение читается до загрузки .env, поэтому задавайте его именно в окружении до запуска:

# macOS / Linux
export NOVA_DATASET="/path/to/catalog.csv"
# Windows PowerShell
$env:NOVA_DATASET="C:\path\to\catalog.csv"

Относительный путь отсчитывается от рабочего каталога процесса. Затем запускайте обычную команду streamlit run app.py.

Зависимости

Полная установка приложения и тестовых зависимостей:

python -m pip install -r requirements.txt

requirements.txt содержит -e .[test]: устанавливает пакет из src/ в editable-режиме вместе с зависимостями из pyproject.toml. Минимальная версия Python — 3.10. Отдельно устанавливать пакеты или настраивать PYTHONPATH не нужно. Запуск рассчитан на checkout с каталогом data/, а не на автономный wheel без данных. Установка в чистое окружение, CLI-сценарии и запуск Streamlit проверены на macOS с Python 3.14.2; команды PowerShell приведены для Windows, но на этой платформе не выполнялись.

Данные

В репозиторий включён data/hackathon dataset anonymized .csv — 66 профилей: 53 исходных и 13 синтетических. Основные поля: city, categories, price_from_kzt, event_formats, languages, max_hours, busy_dates, description. Поля-списки в CSV разделяются символом |.

  • Локации каталога: Алматы, Астана, Зарубежье. Автоматического отнесения произвольного зарубежного города к «Зарубежью» нет.
  • Доступность известна только за 23.09.2026–31.12.2026 включительно. Внутри периода отсутствие даты в busy_dates означает доступность по каталогу, без внешнего подтверждения. Чат отклоняет даты вне периода и просит уточнение; проверка находится вне matcher.
  • max_hours=null у 9 профилей означает неприменимое ограничение длительности: такой профиль не исключается по часам. Это не обещание неограниченного времени работы.
  • synthetic отмечается как «Синтетический профиль». У 18 профилей price_imputed показывается как «Ориентировочная цена», у 8 city_imputed — «Локация указана ориентировочно».

price_from_kzt — начальная цена «от», не окончательная смета и не почасовой тариф. Условие price_from_kzt <= budget означает только: «Начальная цена подрядчика не превышает указанный бюджет». Итоговая стоимость может быть выше: готовых пакетов и окончательных предложений в каталоге нет. Цена не умножается на длительность.

Внешние сервисы

OpenAI Responses API используется для понимания сообщений, структурированного извлечения условий, уточнений и формулировки объяснений по выбранным профилям. Модель по умолчанию в коде и шаблоне настроек — gpt-4.1-mini. Проверка доступности, обязательных условий и ранжирование выполняются локально, без модели.

Другие внешние сервисы для работы приложения не требуются. Разговор требует сетевого доступа к OpenAI и доступного API-баланса; локальный CLI-подбор работает без ключа.

Алгоритм подбора

1. Жёсткая фильтрация

Совпадение локации и категории после нормализации регистра, пробелов и ё/е; отсутствие даты в busy_dates; поддержка формата; начальная цена не выше бюджета; достаточная длительность, если часы заданы и ограничение применимо. Язык — мягкое предпочтение, а не причина исключения.

2. Детерминированное ранжирование

СоставляющаяБаллыПравило
Формат20Все прошедшие фильтр поддерживают формат
Язык0 или 3030 при совпадении или отсутствии предпочтения
Бюджет0–1515 × max(0, 1 − abs(цена / бюджет − 0.75) / 0.75)
Длительность10–1510 + 5 × min((max_hours − часы) / часы, 1); без запроса или ограничения — 15
Описание0–20Косинусное сходство локальных TF-IDF-векторов запроса и описания × 20

Бюджетная составляющая предпочитает начальную цену около 75% бюджета. TF-IDF использует весь каталог; текст запроса составляется из категории, формата и языка. Оценка показывает соответствие запросу, а не качество подрядчика.

Одинаковый структурированный запрос и каталог дают одинаковый порядок. Сортировка — по полной оценке по убыванию, при равенстве — по строковому ID по возрастанию; округление выполняется после сортировки. Выбираются максимум три профиля.

Основные исходы

ИсходЗначение
SUCCESSНайден хотя бы один подходящий профиль; показано до трёх
CATEGORY_NOT_FOUNDВ выбранной локации нет нужной категории
NO_MATCHКатегория есть, но никто не прошёл все условия

При NO_MATCH показаны фактические причины: занятость, неподходящий формат, превышение начальной цены или недостаточная длительность. Причины считаются независимо: один профиль может нарушить несколько условий. Ограничения автоматически не ослабляются.

Проверка основного сценария

  1. Настройте ключ и запустите streamlit run app.py.
  2. Откройте URL из терминала. Если разговор уже начат, нажмите «Новый поиск».
  3. Отправьте: «Нужен ведущий на свадьбу в Алматы 2 ноября 2026 года, бюджет до 2 000 000 ₸, на 6 часов, на русском.»
  4. Помощник должен собрать условия и показать «Проверьте условия». Проверьте локацию, дату, категорию, формат, бюджет, часы и язык. Если модель просит уточнение, ответьте теми же значениями.
  5. Нажмите «Найти подрядчиков». До подтверждения новый подбор не выполняется.
  6. Ожидается SUCCESS: 4 подходящих профиля, Top-3 в порядке Сон Гоку → Хаул → Эмилия, с ценами «от» и индивидуальными объяснениями. Число прошедших фильтры проверяется также CLI; обычный UI показывает три карточки.

Можно продолжить: «нет, бюджет миллион» — бюджет обновится перед новым подтверждением. На «а подешевле?» помощник уточнит предел бюджета, если конкретная сумма не названа. Формулировки модели могут различаться; проверяйте условия подтверждения.

Проверка редкой категории

Начните новый поиск и отправьте: «Нужен флорист на свадьбу в Алматы 14 ноября 2026 года, бюджет до 400 000 ₸, на русском.» Длительность не задавайте. Подтвердите: ожидается одна карточка Тихиро Огино с пометкой «Синтетический профиль».

Проверка пустого результата

Новый поиск: «Нужен ведущий на свадьбу в Алматы 14 ноября 2026 года, бюджет до 1 000 ₸, на 6 часов, на русском.» После подтверждения ожидается NO_MATCH: 5 профилей заняты, 4 не поддерживают формат, у 10 начальная цена выше бюджета. Карточек нет; суммы причин могут пересекаться.

Для CATEGORY_NOT_FOUND: «Нужен декоратор на свадьбу в Астане 14 ноября 2026 года, бюджет до 400 000 ₸.» После подтверждения — сообщение об отсутствии категории в локации.

Проверка детерминизма

Повторите основной сценарий через «Новый поиск», сохранив все семь параметров. Порядок карточек должен совпасть. Текст LLM-объяснений может отличаться; состав и порядок рекомендаций — нет.

Проверка календаря

После основного сценария отправьте «Изменим дату на 14 ноября 2026 года», сохранив остальные условия, и подтвердите. Для 02.11.2026 подходят 4 профиля: Top-3 Сон Гоку, Хаул, Эмилия. Для 14.11.2026 подходят 3: Сон Гоку, Эмилия, Мицури Канроджи. Меняется только дата; различие вызвано календарями занятости.

Затем запросите «Изменим дату на 1 января 2027 года»: помощник должен сообщить о периоде 23.09.2026–31.12.2026 и попросить дату внутри него, не выполняя новый поиск.

Режим проверки для жюри

Откройте http://localhost:8501/?review=1 (при другом порте замените его). Основной продукт остаётся тем же чатом. Дополнительно доступны свёрнутые разделы «Условия этого поиска» и «Подробности подбора» с составляющими оценки карточек.

Режим не меняет фильтры, оценки или порядок и не является механизмом авторизации. Ручного поиска и переключателя сценариев в текущем интерфейсе нет, в том числе в review-режиме. Подготовленные сценарии запускаются через python scripts/demo.py.

Тестирование

Из корня проекта с активированным окружением:

pytest

Текущий результат проверки: 83 теста прошли, 0 падений; дополнительно прошли 15 подтестов. UI-тесты проверяют текущий разговорный интерфейс, расположение справки под чатом и дополнительные детали в review-режиме.

Тесты охватывают разбор каталога, жёсткие фильтры, занятые даты, нормализацию значений, ранжирование, уточнения и исправления в диалоге, продолжение поиска, период доступности, пустые исходы, UI-интеграцию, ошибки API и архитектурные границы. Обычный pytest использует подменённый API и не тратит кредиты OpenAI.

Четыре детерминированных сценария без API:

python scripts/demo.py

Ожидаются два SUCCESS, один NO_MATCH, один CATEGORY_NOT_FOUND; значения совпадают со сценариями выше. Отдельная необязательная проверка настоящего OpenAI может расходовать API-кредиты:

python scripts/eval_live.py --help
python scripts/eval_live.py --suite conversation

Также доступны наборы safety, multiturn и all. Запуск без --suite выполняет полный набор; для обычной установки и тестов он не нужен.

Известные ограничения

  • Подбор ограничен комплектным каталогом; доступность известна только за 23.09.2026–31.12.2026.
  • Начальная цена не гарантирует окончательную стоимость. Бронирование, оплата и связь с подрядчиком не реализованы.
  • TF-IDF измеряет лексическое сходство, не понимает все синонимы и не оценивает качество услуги.
  • Разговор зависит от сети и OpenAI API. При ошибке понимания запроса новый поиск не выполняется: отображается сообщение, после восстановления доступа запрос можно повторить. Если сбоит только генерация объяснений, выбранные карточки сохраняются с детерминированным текстом.
  • Резервной ручной формы в текущем UI нет. Без API можно проверить matcher через scripts/demo.py, но не провести полноценный новый разговор.
  • LLM может ошибиться при извлечении или формулировке; проверка условий пользователем обязательна, а проверка текста моделью не даёт абсолютной гарантии. Модель не может изменить факты выбора и ранжирование.
  • Зависимости, кроме Streamlit, заданы диапазонами, поэтому установка не фиксирует все транзитивные версии.

Развёрнутая версия

Публичная развёрнутая версия отсутствует; проект запускается локально по инструкции выше.

Звёзды
⭐ 0
Форки
0
Язык
Python
Последний push
в прошлом месяце
Коммиты
7
Авторы
2
Файлы
43
Ветки
1

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

abekpaye 5quralai-baqytnur 2

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

  • 2026-09-23 17:58Finalize EventMatch for submissionabekpaye
  • 2026-09-23 17:37Finalize EventMatch user experienceabekpaye
  • 2026-09-23 16:50Restructure project into layered architecturequralai-baqytnur
  • 2026-09-23 16:22Polish conversational AI experiencequralai-baqytnur
  • 2026-09-23 15:51Add conversational AI agent with OpenAI integrationabekpaye
  • 2026-09-23 15:30Configure OpenAI API environmentabekpaye
  • 2026-09-23 14:44Implement contractor recommendation MVPabekpaye
Структура репозитория (43 файлов)
.env.example
.gitignore
.streamlit/config.toml
README.md
app.py
data/hackathon dataset anonymized .csv
pyproject.toml
requirements.txt
scripts/demo.py
scripts/eval_live.py
src/eventmatch/__init__.py
src/eventmatch/ai/__init__.py
src/eventmatch/ai/models.py
src/eventmatch/ai/prompts.py
src/eventmatch/application/__init__.py
src/eventmatch/application/availability.py
src/eventmatch/application/conversation.py
src/eventmatch/application/presentation.py
src/eventmatch/domain/__init__.py
src/eventmatch/domain/matcher.py
src/eventmatch/infrastructure/__init__.py
src/eventmatch/infrastructure/openai_client.py
src/eventmatch/paths.py
src/eventmatch/ui/__init__.py
src/eventmatch/ui/chat.py
src/eventmatch/ui/components.py
src/eventmatch/ui/demo_scenarios.py
src/eventmatch/ui/manual.py
src/eventmatch/ui/state.py
tests/__init__.py
tests/integration/__init__.py
tests/integration/test_app.py
tests/integration/test_architecture.py
tests/integration/test_chat_ui.py
tests/integration/test_llm_client.py
tests/integration/test_package.py
tests/integration/test_ux.py
tests/support.py
tests/unit/__init__.py
tests/unit/test_availability.py
tests/unit/test_conversation.py
tests/unit/test_matcher.py
tests/unit/test_presentation.py

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

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

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