AAYMA
RU
Войти

Tandau

Сервис подбора до трёх event-подрядчиков по городу, дате, категории, формату, бюджету, языку и длительности, с объяснениями и диагностикой причин…

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

О проекте

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

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

Суть

Сервис подбора до трёх event-подрядчиков по городу, дате, категории, формату, бюджету, языку и длительности, с объяснениями и диагностикой причин исключения.

Как устроено

CSV из 66 профилей загружается в память. Жёсткие фильтры исключают занятых по календарю, дороже бюджета, без нужного формата, языка или часов. Прошедшие ранжируются детерминированно (упоминание формата в описании → цена «от» → id). Для объяснения описание делится на фрагменты, OpenAI возвращает номера подходящих фрагментов, код проверяет их и вставляет исходный текст профиля. При ошибке API объяснение строится по правилам. Различаются исходы matched / category_absent / no_match. Доступ через FastAPI и Streamlit.

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

  • жёсткие фильтры с подсчётом причин исключения
  • стабильное лексикографическое ранжирование
  • экстрактивный выбор фрагментов LLM с валидацией индексов
  • кэш ответов (128)

Особенности

LLM не генерирует текст, а выбирает номер фрагмента исходного описания, поэтому выдуманные биографии исключены. Порядок карточек не зависит от модели.

Стек

Python · FastAPI · Pydantic · Uvicorn · Streamlit · unittest

Модели

gpt-4.1-mini (OpenAI Responses API)


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

README

README.md

Tandau - Умный подбор подрядчиков

Сервис рекомендаций event-подрядчиков для мероприятий в Казахстане. Разработан в рамках HackAlem AI / Vision2026, задача #79-lite.

Пользователь задаёт параметры мероприятия и получает до трёх подходящих подрядчиков с конкретными объяснениями. Сервис учитывает занятость, бюджет, формат, язык и длительность, а при отсутствии результатов сообщает причину.

Возможности

  • Подбор по городу, дате, категории подрядчика и формату мероприятия.
  • Проверка бюджета, языка и длительности.
  • Исключение занятых подрядчиков, включая площадки.
  • До трёх карточек с именем, категорией, городом, ценой «от» и объяснением.
  • Стабильный порядок результатов при одинаковых параметрах и данных.
  • Выбор фактов из описаний с помощью OpenAI API.
  • Работа с объяснениями по правилам при недоступности AI.
  • Отдельная обработка отсутствующей категории и неподходящих условий.
  • Сохранение признаков синтетических и подставленных данных.

Текущая версия включает Streamlit-интерфейс, backend и интерактивную документацию Swagger UI. Бронирование, оплата и уведомления подрядчикам не входят в функциональность.

Технологии

ТехнологияНазначение
PythonОбработка данных и логика подбора
FastAPIHTTP API и интерактивная документация
PydanticПроверка входных параметров
UvicornЗапуск сервера
OpenAI APIВыбор полезных фактов из описаний
python-dotenvЗагрузка конфигурации
unittestАвтоматические тесты

Запуск

Требуется Python 3.10+. Команды ниже приведены для PowerShell.

Установка

git clone https://github.com/BAITC-Hacks/hack-74a0463d-vision2026.git
cd hack-74a0463d-vision2026

python -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.txt

Пакет Streamlit для интерфейса устанавливается вместе с остальными зависимостями из requirements.txt.

Для закрытого репозитория необходим доступ к организации или проекту.

Конфигурация

В корне проекта используется файл .env:

OPENAI_API_KEY=your_api_key
OPENAI_MODEL=gpt-4.1-mini

API-ключ должен иметь разрешение на создание ответов через /v1/responses. Выбранная модель должна быть доступна в проекте OpenAI.

Файл .env исключён из Git. Ключ используется только на стороне backend.

Без ключа или при ошибке API сервис возвращает объяснения по правилам.

Запуск сервера

.\.venv\Scripts\python.exe -m uvicorn backend.app:app --reload

После запуска доступны:

Для исходного датасета /health возвращает:

{
  "status": "ok",
  "profiles": 66
}

В macOS и Linux вместо ..venv\Scripts\python.exe используется .venv/bin/python.

Запуск интерфейса Streamlit

Из корневой папки проекта запустите интерфейс отдельной командой:

.\.venv\Scripts\python.exe -m streamlit run frontend/streamlit_app.py

Откройте адрес, который выведет Streamlit в терминале; обычно это http://localhost:8501. Для интерфейса отдельный запуск Uvicorn не требуется: Streamlit загружает каталог и вызывает модули backend напрямую.

API

МетодEndpointНазначение
GET/healthСостояние сервера и количество профилей
GET/optionsГорода, категории, форматы, языки и границы календаря
POST/recommendПодбор подрядчиков

Запрос на подбор

Заголовок: Content-Type: application/json.

{
  "city": "Алматы",
  "date": "2026-10-15",
  "event_format": "корпоратив",
  "category": "Ведущий",
  "budget_kzt": 2000000,
  "duration_hours": 5,
  "language": "русский"
}
ПолеОбязательноеОписание
cityДаГород
dateДаДата в формате YYYY-MM-DD
event_formatДаФормат мероприятия
categoryДаКатегория подрядчика
budget_kztДаПоложительный бюджет в тенге
duration_hoursНетПоложительная длительность в часах
languageНетТребуемый язык

Бюджет относится к выбранному подрядчику, а не ко всему мероприятию. Необязательные поля можно пропустить или передать как null.

Ответ

Ответ содержит:

  • status — результат подбора;
  • cards — список до трёх карточек;
  • message — пояснение результата;
  • diagnostics — количество подходящих и исключённых профилей, причины исключения.

Карточка содержит:

id
anon_name
category
city
price_from_kzt
explanation
synthetic
city_imputed
price_imputed
explanation_source

Три исхода подбора

СтатусЗначение
matchedНайдены подходящие подрядчики
category_absentВ каталоге нет выбранной категории в указанном городе
no_matchКатегория есть, но ни один профиль не соответствует условиям

Если подходят один или два кандидата, возвращаются только они. Причина короткой выдачи указывается в message.

Все три исхода возвращаются с HTTP 200. Некорректный ввод получает HTTP 422 с описанием ошибки в поле detail.

Как работает подбор

1. Подготовка данных

CSV загружается в память при запуске сервера.

Загрузчик преобразует:

  • категории, форматы и языки — в списки;
  • занятые даты — в множество дат;
  • цены и длительность — в числа;
  • признаки синтетических данных — в логические значения.

Также проверяются идентификаторы, цены и длительность.

2. Фильтрация

Сначала выбираются профили нужного города и категории. Затем исключаются подрядчики, которые:

  • заняты на указанную дату;
  • имеют цену «от» выше бюджета;
  • не поддерживают выбранный формат;
  • не работают на указанном языке;
  • не могут работать необходимое количество часов.

Площадки проходят такую же проверку календаря, как ведущие и фотографы.

Пустой max_hours означает, что ограничение по присутствию неприменимо. Он не считается нулём.

У профиля может быть несколько причин исключения. Поэтому сумма счётчиков причин может превышать количество исключённых подрядчиков.

3. Ранжирование

Прошедшие фильтры кандидаты сортируются последовательно:

  1. Наличие прямого упоминания формата мероприятия в описании.
  2. Меньшая цена «от».
  3. Идентификатор id при равенстве предыдущих условий.

Возвращаются первые три профиля. Сортировка выполняется кодом и не зависит от ответа модели.

4. Объяснение рекомендации

Объяснение объединяет соответствие условиям запроса и конкретную деталь из описания подрядчика.

AI используется для выбора этой детали. Описание разбивается на фрагменты, после чего модель получает параметры запроса и фрагменты выбранных профилей.

Модель возвращает номера фрагментов, а код проверяет идентификаторы и допустимость номеров. В карточку вставляется исходный текст профиля.

Таким образом, AI не изменяет цены, календарь, состав или порядок выдачи и не добавляет свободно сгенерированную биографию.

Надёжность AI-компонента

  • На выбранные карточки выполняется один запрос к модели.
  • Для пустого результата модель не вызывается.
  • Успешные результаты сохраняются в памяти процесса, до 128 вариантов.
  • При ошибке API или некорректном ответе используются объяснения по правилам.
  • Для обращения к модели задан таймаут 8 секунд без повторных попыток.

Поле explanation_source показывает происхождение объяснения:

  • ai — факт выбрала модель;
  • rules — использовано объяснение по правилам.

Кэш очищается при перезапуске. После перезапуска выбранный AI-фрагмент может измениться, но порядок карточек остаётся стабильным.

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

Данные

Используется анонимизированный датасет организаторов:

  • 66 профилей;
  • 13 синтетических профилей;
  • города: Алматы, Астана, Зарубежье;
  • календарь: 23 сентября — 31 декабря 2026 года.

Файл расположен в backend/data/contactors.csv.

Особенности:

  • price_from_kzt — цена «от» за мероприятие, а не окончательная стоимость или почасовой тариф;
  • synthetic отмечает синтетические профили;
  • city_imputed и price_imputed показывают, что значения были проставлены при подготовке датасета;
  • запросы вне доступного календаря отклоняются;
  • высокая занятость в декабре является особенностью исходных данных.

Демонстрационные сценарии

Сценарии выполняются через POST /recommend на странице /docs. Ожидаемые результаты соответствуют исходному CSV и текущим правилам ранжирования.

Массовая категория

Для запроса из раздела API подходят 7 ведущих, возвращаются:

  1. Мицури Канроджи — HK-44923.
  2. Аня Форджер — HK-29829.
  3. Кики — HK-35215.

Повторный запрос сохраняет порядок.

Изменение даты

В том же запросе дата меняется на 2026-10-16.

Подходят 5 профилей, возвращаются:

  1. Куррапика — HK-88430.
  2. Мицури Канроджи — HK-44923.
  3. Буллма — HK-44733.

Куррапика занят 15 октября и свободен по календарю 16 октября. Аня Форджер и Кики, напротив, заняты 16 октября. Причины исключения отражаются в ответе.

Редкая категория

{
  "city": "Алматы",
  "date": "2026-10-15",
  "event_format": "корпоратив",
  "category": "Флорист",
  "budget_kzt": 300000
}

Возвращается одна карточка: Тони Тони Чоппер — HK-39372.

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

Кандидаты есть, но условия не подходят

В базовом запросе ведущего бюджет меняется на 1.

Ожидаемый результат: no_match, пустой список карточек и сообщение о превышении бюджета.

Категория отсутствует в городе

В базовом запросе город меняется на Астана, категория — на Декоратор.

Ожидаемый результат: category_absent и сообщение об отсутствии категории в городе.

Синтетические профили

{
  "city": "Алматы",
  "date": "2026-10-15",
  "event_format": "корпоратив",
  "category": "Фото и видеобудки",
  "budget_kzt": 500000
}

Возвращаются:

  • HK-90009 — synthetic: true;
  • HK-35846 — synthetic: false.

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

.\.venv\Scripts\python.exe -m unittest backend.test_recommender -v

Набор содержит 11 тестов, проверяющих:

  • граничные значения бюджета и длительности;
  • занятость людей и площадок;
  • исключение по каждому обязательному условию;
  • отсутствие категории в городе;
  • короткую выдачу и ограничение в три карточки;
  • стабильность порядка;
  • влияние даты;
  • необязательные поля и пустую длительность;
  • некорректный ввод;
  • использование исходных фактов;
  • обработку ошибок AI.

Тесты используют отдельные тестовые профили и имитацию API. Они не меняют исходный CSV и не расходуют квоту OpenAI.

Ожидаемое завершение:

Ran 11 tests in ...

OK

Структура проекта

.
├── backend/
│   ├── data/
│   │   └── contactors.csv
│   ├── loader.py
│   ├── recommender.py
│   ├── ai_client.py
│   ├── ai_explanations.py
│   ├── app.py
│   └── test_recommender.py
├── frontend/
│   ├── demo_queries.py
│   ├── streamlit_app.py
│   └── ui_components.py
├── requirements.txt
├── .gitignore
└── README.md

Фронтенд Streamlit

Интерфейс написан на Streamlit. Точка входа — frontend/streamlit_app.py; оформление и карточки вынесены в frontend/ui_components.py, а демонстрационные запросы — в frontend/demo_queries.py.

Форма позволяет выбрать город, категорию, формат и дату мероприятия, задать бюджет, а также при желании указать длительность и язык общения. Списки городов и категорий формируются из загруженного CSV-каталога.

  • Три готовых демонстрационных сценария заполняют форму для популярной категории, редкой категории и запроса без подходящих вариантов.
  • После отправки формы приложение вызывает backend.recommender.recommend напрямую; каталог загружается через backend.loader.load_contractors и кэшируется средствами Streamlit.
  • Интерфейс показывает до трёх карточек с именем профиля, категорией, городом, ценой «от» и индивидуальным объяснением рекомендации.
  • Синтетические профили и восстановленные значения города или цены отмечаются на карточке отдельными бейджами.
  • Для пустой выдачи показываются сообщение backend и счётчики основных причин, мешавших подбору; при отсутствии категории в городе отображается отдельное сообщение. Интерфейс оформлен в светлой палитре с лиловыми акцентами, округлыми карточками и фиолетовой основной кнопкой без декоративного эмодзи. Предусмотрены состояния загрузки и ошибки.

Текущая страница Streamlit не отправляет HTTP-запросы на GET /options или POST /recommend и не использует CORS: она вызывает Python-функции backend напрямую. Эти HTTP-маршруты остаются доступными для отдельной проверки API. При раздельном размещении интерфейса и backend потребуется настроить адрес сервера и CORS.

Ограничения

  • Каталог и календари статичны.
  • Ранжирование основано на правилах; отзывы и подтверждённые оценки качества не используются.
  • AI может выбрать слишком общий фрагмент. Конкретность, различимость и длина объяснений требуют ручной оценки.
  • Целевое время ответа — до 10 секунд. Измерения полного времени ответа пока не опубликованы; таймаут API не является гарантией общей задержки.
  • Версии большинства зависимостей в requirements.txt не закреплены.
  • Текущие инструкции описывают локальную демонстрацию Streamlit-интерфейса и backend. Публичная deployed-версия в проекте не указана.

Развитие

  • Оценка качества рекомендаций на примерах, размеченных экспертами.
  • Улучшение выбора фактов и ранжирования.
  • Учёт пожеланий к стилю мероприятия.
  • Подключение обновляемых каталогов и календарей.
  • Измерение времени выбора и полезности рекомендаций для пользователей.
  • Переход к базе данных и оценка семантического поиска при расширении каталога.
Звёзды
⭐ 0
Форки
0
Язык
Python
Последний push
в прошлом месяце
Коммиты
13
Авторы
2
Файлы
13
Ветки
1

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

akmaralabilhayir-lab 10wassuprizu 3

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

  • 2026-09-23 17:57Add README.md with service details and setup instructionsakmaralabilhayir-lab
  • 2026-09-23 17:20(feat):изменение дизайнаwassuprizu
  • 2026-09-23 17:17(feat):изменение дзиайнwassuprizu
  • 2026-09-23 16:14Add Streamlit frontend for contractor recommendationswassuprizu
  • 2026-09-23 15:29test: проверить фильтры, порядок и обработку ошибок AIakmaralabilhayir-lab
  • 2026-09-23 15:16feat: добавить API подбора подрядчиков и параметры формыakmaralabilhayir-lab
  • 2026-09-23 15:03feat: подключить AI к выбору фактов для объясненийakmaralabilhayir-lab
  • 2026-09-23 14:53feat: подключить OpenAI API и добавить проверкуakmaralabilhayir-lab
Все коммиты (13)
  • 2026-09-23 14:05feat: добавить стабильное ранжирование и базовые объясненияakmaralabilhayir-lab
  • 2026-09-23 13:55feat: добавить фильтрацию подрядчиков и причины отказаakmaralabilhayir-lab
  • 2026-09-23 13:39feat: добавить загрузку и проверку данных подрядчиковakmaralabilhayir-lab
  • 2026-09-23 13:37data: добавить исходный датасет подрядчиковakmaralabilhayir-lab
  • 2026-09-23 13:29chore: добавить структуру проекта и датасетakmaralabilhayir-lab
Структура репозитория (13 файлов)
.gitignore
README.md
backend/ai_client.py
backend/ai_explanations.py
backend/app.py
backend/data/contactors.csv
backend/loader.py
backend/recommender.py
backend/test_recommender.py
frontend/demo_queries.py
frontend/streamlit_app.py
frontend/ui_components.py
requirements.txt

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

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

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