AAYMA
RU
Войти

EventDNA

Веб-приложение детерминированного подбора до трёх event-подрядчиков из каталога 66 профилей с объяснениями WHY THIS / WHY NOT, воронкой отбора и API…

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

О проекте

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

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

Суть

Веб-приложение детерминированного подбора до трёх event-подрядчиков из каталога 66 профилей с объяснениями WHY THIS / WHY NOT, воронкой отбора и API сценариев WHAT IF.

Как устроено

FastAPI загружает CSV, последовательно применяет фильтры (категория → город → формат → занятые даты → бюджет → длительность → язык), для каждого исключённого сохраняет первую нарушенную причину. Ранжирование — индекс запаса бюджета (budget − price)/budget с tie-break по ID. /api/what-if пересчитывает соседние сценарии (бюджет +50–200k, дата +1–3 дня, длительность −1–2 ч) и сравнивает появившихся/исчезнувших кандидатов. LLM не используются.

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

  • последовательные hard filters
  • budget-slack scoring
  • WHY NOT причины исключения
  • what-if анализ

Особенности

Воронка отбора с числом профилей на каждом этапе и WHY NOT для каждого исключённого; what-if-пересчёт параметров, показывающий, какие кандидаты появятся при смягчении условий.

Стек

Python · FastAPI · Pydantic · Uvicorn · HTML · CSS · JavaScript · unittest


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

README

README.md

EventDNA

Подбор event-подрядчиков Казахстана с объяснением каждого решения.

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

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

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

ВозможностьЧто делает текущая версия
Поиск через формуГород, дата, формат мероприятия, категория и бюджет; дополнительно — длительность и язык
Значения из каталогаВарианты выбора загружаются из CSV через API
Обязательные фильтрыИсключают профили, не удовлетворяющие условиям; дата из busy_dates всегда исключает подрядчика
Детерминированный TOP-3Одинаковый запрос при неизменном каталоге даёт одинаковый порядок; если подходят два, один или никто, результат не дополняется
WHY THISОбъяснения выбора на основании структурированных полей и расчётов
WHY NOTПервый нарушенный фильтр для каждого исключённого профиля, группировка причин; отдельное объяснение для прошедших фильтры кандидатов ниже TOP-3
Воронка отбораКоличество профилей после каждого этапа
Происхождение данныхОтображение признаков дополненных цены/города и синтетического профиля
WHAT IF через APIПересчёт соседних сценариев тем же алгоритмом, сравнение появившихся и исчезнувших кандидатов
Состояния интерфейсаЗагрузка, пустой результат, ошибки, повтор загрузки каталога и предупреждение об изменённых параметрах
Адаптивная вёрсткаИнтерфейс для широких и мобильных экранов

Как работает решение

  1. Пользователь открывает сайт. Форма получает доступные города, категории, форматы и языки из /api/filters.

  2. Пользователь задаёт условия и нажимает «Найти моих подрядчиков».

  3. Frontend отправляет JSON в /api/recommend; backend проверяет структуру и значения запроса.

  4. Алгоритм последовательно применяет фильтры:

    Все профили → категория → город → формат → занятые даты
                → бюджет → длительность → язык → ranking → до 3 результатов
    
  5. Для каждого исключения сохраняется первая причина. Оставшиеся кандидаты сортируются по индексу запаса бюджета и ID.

  6. Пользователь видит параметры запроса, воронку, карточки, объяснения WHY THIS и WHY NOT.

Текстовые значения сравниваются без учёта регистра, внешних и повторяющихся пробелов. Категория, формат и язык проверяются по отдельным значениям списков, а не по подстроке. Длительность и язык применяются только при заполнении соответствующих полей.

Формула ranking

Для профилей, прошедших все фильтры:

score = (budget − price_from_kzt) / budget

Чем больше запас относительно стартовой цены, тем выше место. При нулевом бюджете прошедшие фильтры профили получают score = 0. При одинаковом score порядок определяется по ID по возрастанию.

В интерфейсе индекс отображается на шкале от 0 до 100. Это индекс запаса бюджета, а не оценка качества услуг или вероятность успешного мероприятия. Отзывы, популярность, текст описания и флаги происхождения данных на ranking не влияют.

WHAT IF

POST /api/what-if пересчитывает независимо друг от друга:

  • увеличение бюджета на 50 000, 100 000 и 200 000 ₸;
  • перенос даты на 1, 2 и 3 дня вперёд, если дата остаётся допустимой;
  • сокращение длительности на 1 и 2 часа, если она задана и результат больше нуля.

Ответ содержит исходный результат, результаты сценариев, added_ids, removed_ids и изменение общего числа подходящих кандидатов. Сравниваются все прошедшие фильтры профили, а не только TOP-3. Дополнительно возвращается до пяти предложений с положительным приростом количества кандидатов. В веб-интерфейсе WHAT IF пока не подключён; его можно проверить через /docs.

Технологии

КомпонентТехнологии
BackendPython, FastAPI, Pydantic 2
СерверUvicorn
Данные и расчётыCSV, стандартные модули Python csv, datetime, decimal, dataclasses
FrontendHTML, CSS, обычный JavaScript, браузерный Fetch API
Тестыunittest, FastAPI TestClient, HTTPX

Зависимости backend перечислены в requirements.txt, зависимости тестов — в requirements-dev.txt.

AI/LLM-модели в текущем коде не используются. Выбор, ranking, объяснения и сценарии вычисляются обычным кодом. Внешние API, API-ключи, платные сервисы, база данных и сборка через Node.js для запуска не нужны. Для первоначальной установки Python-зависимостей потребуется доступ к интернету.

Архитектура проекта

Браузер: HTML / CSS / JavaScript
                │ HTTP / JSON
                ▼
        FastAPI: backend/main.py
                │
       ┌────────┴──────────┐
       ▼                   ▼
  engine.py           what_if.py
       ▲                   │
       └── повторный вызов ─┘
       │
       ├── filters.py       обязательные условия
       ├── scoring.py       индекс и стабильная сортировка
       └── explanations.py  причины выбора и исключения

contractors.csv → data_loader.py → профили в памяти приложения

FastAPI также раздаёт frontend: интерфейс и API работают на одном адресе. CSV загружается при старте сервера; база данных не используется.

.
├── backend/
│   ├── __init__.py
│   ├── main.py               # API, загрузка каталога и раздача frontend
│   ├── models.py             # Contractor и проверка запроса
│   ├── data_loader.py        # Чтение и нормализация CSV
│   ├── engine.py             # Общий pipeline, воронка и TOP-3
│   ├── filters.py            # Фильтры и занятые даты
│   ├── scoring.py            # Ranking
│   ├── explanations.py       # WHY THIS / WHY NOT
│   ├── what_if.py            # Пересчёт сценариев
│   └── data/contractors.csv  # Каталог из 66 профилей
├── frontend/
│   ├── index.html
│   ├── style.css
│   └── app.js                # Форма, запросы и отображение результатов
├── tests/
│   ├── __init__.py
│   ├── helpers.py            # Искусственные тестовые профили
│   ├── test_dataset.py
│   ├── test_filters.py
│   ├── test_recommendations.py
│   └── test_api.py
├── .gitignore
├── requirements.txt
├── requirements-dev.txt
├── run.py
└── README.md

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

Понадобятся Git, Python с модулем venv и браузер. Проект проверен на Python 3.9.6. Команды ниже приведены для macOS/Linux.

1. Скачайте репозиторий

git clone https://github.com/BAITC-Hacks/hack-e823438b-stackattack.git
cd hack-e823438b-stackattack

Если репозиторий уже скачан, откройте терминал в его корневой папке — там, где находится run.py.

2. Создайте и активируйте окружение

python3 -m venv .venv
source .venv/bin/activate

В Windows PowerShell вместо этих команд:

py -m venv .venv
.\.venv\Scripts\Activate.ps1

3. Установите зависимости

python -m pip install -r requirements.txt

4. Запустите приложение

python run.py

Оставьте терминал с сервером открытым:

При корректной загрузке текущего CSV /health возвращает:

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

Открывайте сайт через адрес сервера, а не двойным щелчком по index.html. Для остановки сервера нажмите Ctrl+C в его терминале. После изменения CSV остановите сервер и снова выполните python run.py: горячая перезагрузка каталога не реализована.

Если порт 8000 занят предыдущим запуском, сначала остановите тот процесс. Если браузер показывает старую страницу, обновите её без кеша: ⌘ Shift R на macOS или Ctrl Shift R на Windows/Linux.

Как проверить решение: сценарий для жюри

Поиск через сайт

  1. Откройте http://127.0.0.1:8000.

  2. Заполните форму:

    ПолеЗначение
    ГородАлматы
    Дата14 ноября 2026 года
    Форматсвадьба
    КатегорияФотограф
    Бюджет400000
    Длительность в дополнительных пожеланиях8 часов
    Языкрусский
  3. Нажмите «Найти моих подрядчиков».

Для CSV, включённого в репозиторий, ожидается следующий порядок:

МестоIDИмяЦена отИндекс запаса бюджета
1HK-68220Рок Ли190 000 ₸52,5 / 100
2HK-76268Сацуки Кусакабэ200 000 ₸50 / 100
3HK-91112Мэгуми Фушигуро300 000 ₸25 / 100

Воронка: 66 всего → 12 по категории → 8 по городу → 6 по формату → 3 по дате → 3 по бюджету → 3 по длительности → 3 по языку.

  1. Раскройте WHY THIS у карточки и WHY NOT под результатами: проверьте факты и причины исключения.
  2. Повторите тот же запрос — порядок должен сохраниться.
  3. Измените дату на 15 ноября 2026 года и повторите поиск. Останутся Рок Ли и Мэгуми Фушигуро. У Сацуки Кусакабэ эта дата присутствует в busy_dates; в WHY NOT появится соответствующая причина.
  4. Верните 14 ноября и установите бюджет 0. Выдача станет пустой, с объяснением и причинами отказа.

Результаты примера относятся к текущему каталогу и могут измениться после его редактирования.

Проверка API и WHAT IF

В /docs выберите POST /api/recommend, нажмите Try it out и отправьте:

{
  "city": "Алматы",
  "date": "2026-11-14",
  "event_format": "свадьба",
  "category": "Фотограф",
  "budget": 400000,
  "duration_hours": 8,
  "language": "русский"
}

Отправьте тот же JSON в POST /api/what-if. В scenarios найдите дату 2026-11-15: для неё removed_ids содержит HK-76268, а eligible_delta равен -1.

Запрос из терминала macOS/Linux:

curl -X POST http://127.0.0.1:8000/api/recommend \
  -H 'Content-Type: application/json' \
  -d '{"city":"Алматы","date":"2026-11-14","event_format":"свадьба","category":"Фотограф","budget":400000,"duration_hours":8,"language":"русский"}'

Автоматические тесты

В активированном окружении:

python -m pip install -r requirements-dev.txt
python -m unittest discover -s tests -v

В репозитории 21 тест: загрузка и валидация CSV, фильтры, граничные значения, занятые даты, стабильность ranking, выдача от нуля до трёх профилей, объяснения, WHAT IF, валидация API, пустой каталог и доступность frontend через сервер. Тестовые профили отделены от основного каталога.

Данные и интеграции

Источник подбора — backend/data/contractors.csv, включённый в репозиторий. В нём 66 профилей и 17 категорий. Значения города: Алматы — 50 профилей, Астана — 15, «Зарубежье» — 1. Названия профилей хранятся в поле anon_name.

ПоляНазначение
id, anon_nameИдентификатор и имя профиля
categories, city, event_formats, languagesУсловия подбора
price_from_kztСтартовая цена в тенге
max_hoursМаксимальная длительность работы
busy_datesСписок занятых дат
city_imputed, price_imputedПризнаки дополненных значений
syntheticПризнак синтетического профиля
descriptionТекст описания; не участвует в ranking

Формат файла: UTF-8, разделитель колонок — запятая; списки внутри ячеек разделяются |. Даты записываются как YYYY-MM-DD, флаги — true/false без учёта регистра или 1/0.

Загрузчик проверяет обязательные колонки, уникальность ID, числа, даты и флаги. Пропуски не заменяются выдуманными значениями. Неизвестная цена исключает профиль; неизвестная длительность исключает его только при заданном ограничении длительности. В текущем каталоге max_hours отсутствует у 9 профилей.

Объект data_confidence содержит флаги происхождения, а не числовую оценку надёжности. Они не штрафуют профиль при сортировке.

HTTP API приложения

МетодПутьНазначение
GET/healthСтатус и число загруженных профилей
GET/api/filtersДоступные значения формы и диапазон цен
POST/api/recommendЗапрос, воронка, рекомендации, причины исключения и их статистика
POST/api/what-ifИсходный результат и пересчитанные сценарии

Также сохранены алиасы /filters, /recommendations и /what-if. Ошибки валидации запроса возвращают HTTP 422. При пустом каталоге /health сообщает dataset_empty, а фильтры, поиск и WHAT IF возвращают HTTP 503. Некорректный CSV останавливает запуск с диагностикой.

Интеграций с внешними календарями, CRM, сервисами бронирования или AI-провайдерами в текущей версии нет.

Ограничения текущей версии

  • Данные статичны. Нет синхронизации календарей и подтверждения доступности в реальном времени. Отсутствие даты в busy_dates не гарантирует возможность бронирования.
  • Учитывается стартовая цена «от», а не итоговая смета, стоимость выезда или почасовой тариф. После выбора условия нужно уточнять у подрядчика.
  • Ranking оценивает только запас бюджета. Он не измеряет опыт, качество работы, отзывы или репутацию.
  • Подбор выполняется для одной категории за запрос. Совместная оптимизация команды подрядчиков и общего бюджета события отсутствует.
  • WHY NOT сохраняет первую причину отказа по порядку фильтров, а не все возможные нарушения сразу.
  • WHAT IF реализован в API, но отдельного пользовательского экрана для него нет.
  • Свободный текст и AI/LLM-разбор запроса не реализованы.
  • Нет регистрации, личного кабинета, бронирования, оплаты и административного интерфейса редактирования каталога.
  • Данные загружаются в память при запуске; после изменения CSV сервер нужно перезапустить.
  • Это локальный MVP. В репозитории нет конфигурации production-развёртывания или нагрузочных тестов.

Deployed-версия

Подтверждённой ссылки на публично развёрнутую версию в репозитории нет. Для демонстрации используйте локальный запуск по инструкции выше. Адрес 127.0.0.1:8000 доступен на компьютере, где запущен сервер, и не является публичной ссылкой.

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

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

wazukeed 8AkmaralZhumabay 8sayazhan555 1

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

  • 2026-09-23 16:27Merge remote-tracking branch 'origin/main'wazukeed
  • 2026-09-23 16:27docs: add Russian project guide and hackathon demo instructionswazukeed
  • 2026-09-23 16:25Update index.htmlsayazhan555
  • 2026-09-23 16:16merge: reconcile identical contractor catalogue importswazukeed
  • 2026-09-23 16:15feat: connect contractor search UI and import 66-profile cataloguewazukeed
  • 2026-09-23 15:56UpdateAkmaralZhumabay
  • 2026-09-23 15:37merge: reconcile team backend with validated recommendation enginewazukeed
  • 2026-09-23 15:35fix: complete deterministic recommendation backend and API testswazukeed
Все коммиты (17)
  • 2026-09-23 15:27Implement FastAPI for contractor recommendation APIAkmaralZhumabay
  • 2026-09-23 15:26Implement what-if analysis for contractor eligibilityAkmaralZhumabay
  • 2026-09-23 15:26Add rejection labels and explanation building logicAkmaralZhumabay
  • 2026-09-23 15:24Implement contractor scoring and ranking functionsAkmaralZhumabay
  • 2026-09-23 15:23Add contractor evaluation and normalization functionsAkmaralZhumabay
  • 2026-09-23 15:23Refactor contractor data loading with pandasAkmaralZhumabay
  • 2026-09-23 15:22Refactor contractor model to Pydantic and add requestsAkmaralZhumabay
  • 2026-09-23 14:54feat: add responsive EventDNA landing interfacewazukeed
  • 2026-09-23 14:18feat: scaffold EventDNA and add CSV loader with testswazukeed
Структура репозитория (24 файлов)
.gitignore
README.md
backend/__init__.py
backend/data/contractors.csv
backend/data_loader.py
backend/engine.py
backend/explanations.py
backend/filters.py
backend/main.py
backend/models.py
backend/scoring.py
backend/what_if.py
frontend/app.js
frontend/index.html
frontend/style.css
requirements-dev.txt
requirements.txt
run.py
tests/__init__.py
tests/helpers.py
tests/test_api.py
tests/test_dataset.py
tests/test_filters.py
tests/test_recommendations.py

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

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

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