AI-ANA
Веб-сервис подбора до трёх подрядчиков для мероприятия из каталога в 66 профилей с объяснением каждого выбора и причин исключения остальных.
О проекте
Команда: AI-ANA
Кейс HackAlem.ai: Креативные индустрии — Firebird
Стадия: Доведённый продукт
Задача кейса: «#79-lite»: подобрать до 3 подрядчиков для мероприятия и объяснить выбор
Суть
Веб-сервис подбора до трёх подрядчиков для мероприятия из каталога в 66 профилей с объяснением каждого выбора и причин исключения остальных.
Как устроено
FastAPI загружает и проверяет каталог CSV/JSONL при старте; запрос (город, дата, формат, категория, бюджет, опционально язык и длительность) фильтрует профили, исключая занятых по busy_dates. Подходящие сортируются по начальной цене и идентификатору, берутся первые три. Объяснения собираются детерминированно из проверенных фрагментов описаний (проверка хеша и наличия цитаты); различаются исходы «подобрали», «нет категории в городе» и «никто не проходит условия». React-фронт показывает карточки и «Основания».
Ключевые решения
- фильтрация по ограничениям
- стабильная сортировка
- объяснения из верифицированных цитат
- причины исключения
- diff при смене даты
Метрики
270 серверных и 39 браузерных проверок пройдено; среднее время выдачи 59.4 мс
Особенности
Сознательно без LLM: объяснения собираются из цитат, привязанных к исходному тексту по хешу, поэтому преимущества не выдумываются. При смене только даты интерфейс показывает, кто стал занят, а кто просто выпал из топ-3.
Стек
Python · FastAPI · Pydantic · React · TypeScript · Vite · Tailwind CSS · SQLite · Docker Compose · Nginx · Playwright · pytest
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-3c47c506-ai-ana.
README
README.mdAI-ANA
Подбор event-подрядчиков в Казахстане с проверяемыми объяснениями. Решение хакатон-задачи #79-lite: 66 исходных профилей, до трёх карточек в ответе, конкретные причины выбора и отсутствия результата.
Запуск · Живая проверка · Алгоритм · Ограничения

Для кого и какую проблему решает
Организатор уже знает город, формат мероприятия и нужную категорию, но должен вручную сравнить цены, занятость и условия подрядчиков. AI-ANA помогает выбрать из существующего каталога: сокращает список до трёх подходящих вариантов и объясняет каждый выбор. Сервис рассчитан на организаторов мероприятий, HR- и офис-менеджеров, а также людей, которые готовят собственное событие.
Что реализовано
| Требование | Поведение приложения |
|---|---|
| Входные данные | Обязательны город, дата, формат мероприятия, категория и бюджет в тенге; язык и длительность опциональны |
| До трёх карточек | Имя, категория, город, начальная цена и объяснение из 1–2 предложений; раскрываемые «Основания» позволяют проверить факты |
| Занятость | Подрядчики с выбранной датой в busy_dates исключаются до формирования карточек; площадки проверяются так же, как люди |
| Меньше трёх результатов | Показываются только подходящие профили, их количество и причины исключения остальных |
| Три разных исхода | «Подобрали», «В городе нет такой категории» и «Категория есть, но условия не проходит никто» явно различаются |
| Повторяемость | При одинаковых данных и параметрах порядок одинаков: начальная цена, затем идентификатор |
| Смена даты | При изменении только даты интерфейс объясняет, кто стал занят, а кто просто вышел из первых трёх |
| Честность данных | Синтетические профили, дополненные города и цены отмечены; отсутствие сведений не заменяется выдуманными преимуществами |
Дополнительно работают примеры запросов, автоматический подбор через 400 мс после изменения формы, немедленный запуск кнопкой, отмена устаревших запросов, ошибки у полей и повтор после технического сбоя. Интерфейс адаптивный, цветовой акцент настраивается и сохраняется в браузере.
Есть регистрация, вход и редактирование имени и города; аккаунты сохраняются в SQLite. Для подбора регистрация не нужна. Эти дополнительные функции не меняют правила отбора.
Как работает решение
- Пользователь задаёт условия или выбирает готовый пример. Браузер проверяет поля и отправляет
POST /api/recommendations. - FastAPI повторно проверяет запрос. Каталог заранее загружен и проверен при запуске сервера.
- Сначала выбираются город и категория. Затем проверяются свободная дата, формат и бюджет; язык и длительность проверяются, если они заданы.
- Подходящие профили сортируются по начальной цене и нормализованному идентификатору; в ответ попадают первые три. Это воспроизводимый порядок, а не оценка качества подрядчика.
- Сервер составляет объяснения из проверенных фрагментов описаний и полей профиля, добавляет основания, предупреждения и причины исключения. Браузер показывает карточки либо объяснённый пустой результат.
Разметка описаний привязана к исходному тексту и контексту профиля: проверяются хэш и наличие цитаты. При недостаточном описании используются доступные структурированные сведения. Текст описания не исполняется как инструкция. Правила подробно описаны в документации подбора.
Технологии
| Компонент | Используется |
|---|---|
| Интерфейс | TypeScript 7.0.2, React 19.3.0, Vite 8.3.0, Tailwind CSS 4.3.3, React Hook Form 7.88.0, Zod 4.6.5 |
| Сервер | Python 3.13, FastAPI 0.141.1, Pydantic 2.13.5, Uvicorn 0.53.0 |
| Данные | CSV/JSONL для каталога, JSON для разметки объяснений, SQLite для аккаунтов и сессий |
| Запуск | Docker Compose, Nginx; для разработки — Uvicorn и Vite |
| Проверки | pytest 9.1.1, Playwright 1.63.0 и Chromium |
| AI и внешние API | Внешние модели, эмбеддинги и LLM API не используются; объяснения формируются детерминированно |
Зависимости закреплены в requirements.txt и package-lock.json. NVIDIA/Brev, Supabase и облачное хранилище к приложению сейчас не подключены; ключи внешних сервисов для запуска не нужны.
Архитектура проекта
flowchart LR
U[Пользователь: условия события] --> F[React: форма и карточки]
F --> N[Nginx или Vite proxy]
N --> A[FastAPI: валидация и API]
C[CSV / JSONL: 66 профилей] --> V[Проверка и загрузка в память]
V --> M[Фильтры и стабильный порядок]
A --> M
M --> E[Объяснения и причины исключения]
Q[Проверенная разметка описаний] --> E
E --> F
A --> S[SQLite: аккаунты и сессии]
Основные части: frontend/src/ — интерфейс; backend/app/catalog.py — импорт; matching.py — отбор; explanations.py — объяснения; main.py — API; auth.py — аккаунты. Каталог подрядчиков и база аккаунтов хранятся отдельно. Браузер работает через API, исходный CSV ему не передаётся.
Установка и запуск
Через Docker
Нужны Git и Docker Desktop либо Docker Engine с Compose. Python и Node.js на компьютере не требуются. Первый запуск скачивает образы и зависимости.
git clone https://github.com/BAITC-Hacks/hack-3c47c506-ai-ana.git
cd hack-3c47c506-ai-ana
docker compose up --build -d --wait
| Адрес | Назначение |
|---|---|
| http://localhost:8080 | Сайт |
| http://localhost:8080/docs | Интерактивная документация API |
| http://localhost:8080/health | Готовность API: ожидаются ready и 66 профилей |
Если репозиторий уже клонирован, выполните последнюю команду из его корня. Состояние и журналы:
docker compose ps
docker compose logs --tail=100
docker compose down
На новом компьютере база аккаунтов создаётся пустой; все 66 подрядчиков доступны сразу. Аккаунты хранятся в volume auth-data и переживают обычный docker compose down. Флаг --volumes удаляет эту базу. Настройки порта, HTTPS, независимых копий и переноса базы — в инструкции Docker; шаблон — .env.example.
Без Docker
Нужны Python 3.13, venv и Node.js 20.19+ в ветке 20 либо >=22.12.0. Проверенная локальная среда: Linux, Python 3.13.5, Node.js 20.19.2, npm 9.2.0. Из корня репозитория:
python3 -m venv backend/.venv
backend/.venv/bin/python -m pip install -r backend/requirements.txt
cd frontend
npm ci
cd ..
В первом терминале, из корня проекта:
backend/.venv/bin/python -m uvicorn app.main:app --app-dir backend --host 127.0.0.1 --port 8000
Во втором терминале:
cd frontend
npm run dev -- --port 5173 --strictPort
Откройте http://localhost:5173, API-документация — http://127.0.0.1:8000/docs. Оба процесса должны работать; остановка — Ctrl+C. Для просмотра сборки из frontend/: npm run build, затем npm run preview -- --port 4173 --strictPort; API должен оставаться запущенным.
Vite проксирует /api на порт 8000. CATALOG_PATH позволяет выбрать локальный CSV/JSONL; после смены данных нужно перезапустить API. Без Docker аккаунты хранятся в data/auth/accounts.sqlite3. Подробнее: каталог, API, аккаунты.
Как проверить решение
Проверяйте на работающем сайте с исходным каталогом из 66 профилей. Для первых шести строк ниже формат — корпоратив, язык — Любой, длительность пустая, кроме явно указанных 5 часов.
| Сценарий | Действие и параметры | Ожидаемый результат |
|---|---|---|
| Плотная категория | «Ведущие в Алматы»: Алматы, Ведущий, 30.09.2026, 1 000 000 ₸ | Три из шести подходящих: Куррапика → Мицури Канроджи → Кики (HK-88430, HK-44923, HK-35215) |
| Другая дата | В предыдущем запросе изменить только дату на 01.10.2026 | Две карточки: Куррапика и Мицури; интерфейс указывает, что Кики занят на новую дату |
| Повторяемость | Нажать «Подобрать подрядчиков» ещё раз с теми же условиями | Те же две карточки в том же порядке; после возврата на 30.09.2026 — исходные три |
| Редкая категория | «Редкая категория»: Алматы, Декоратор, 23.09.2026, 2 500 000 ₸, 5 ч | Уинри Рокбелл и Франки; два подходящих из двух, Франки помечен синтетическим; max_hours = null не исключает их |
| Нет подходящих | «Небольшой бюджет»: Алматы, Ведущий, 30.09.2026, 100 000 ₸ | Карточек нет, но категория есть; показаны причины исключения кандидатов |
| Нет категории | «Декораторы в Астане»: Астана, Декоратор, 23.09.2026, 2 500 000 ₸ | Отдельное сообщение: такой категории в каталоге города нет |
| Площадка и занятость | Вручную: Астана, Отель, корпоратив, 23.09.2026, 3 000 000 ₸, 5 ч; затем дата 25.09.2026 | Сначала Веджита (HK-90012), затем пустой результат с причиной занятости |
Раскройте «Основания» и сравните объяснения трёх ведущих без имён: они должны опираться на конкретные факты профилей. Дополнительно проверьте запрос национальных ансамблей: Алматы, Национальный ансамбль, свадьба, 24.09.2026, 500 000 ₸, без языка и длительности. Этот сценарий проверяет различимость объяснений при бедных описаниях и одинаковых цене, языке и лимите часов.
Чтобы повторить запрос напрямую к Docker-версии API, выполните из корня проекта:
curl --fail-with-body --silent --show-error \
-H 'Content-Type: application/json' \
--data-binary @docs/queries/corporate-host.json \
http://localhost:8080/api/recommendations
При запуске без Docker замените адрес на http://127.0.0.1:8000/api/recommendations. Остальные запросы — в docs/queries. Сценарий защиты содержит пояснение алгоритма и ответы на вопросы жюри.
Автоматическая проверка и время ответа
Итог проверки требований 23.09.2026: 270 серверных и 39 браузерных проверок прошли, production-сборка успешна. Пять локальных повторов дали среднее 59.4 мс от отправки формы до готовой выдачи; начальная загрузка и ввод параметров не входят в замер. Проверены в том числе различимость объяснений ансамблей и все потенциально совместимые пары исходного каталога без имён/ID. Результаты и границы проверки.
После установки локальных зависимостей выполните из корня репозитория:
cd backend
.venv/bin/python -m pytest -q
cd ../frontend
npx playwright install chromium
npm run test:requirements
Браузерный прогон собирает сайт и запускает отдельные API и preview на портах 8002 и 4174; они должны быть свободны. Аккаунты создаются только во временной тестовой базе. На Linux Chromium может потребовать системные зависимости: npx playwright install-deps chromium.
Результаты сверки с требованиями, замеры скорости и границы проверки собраны в отчёте по требованиям. Отчёты этапа 7, аккаунтов и Docker описывают предыдущие прогоны. Их показатели относятся к соответствующим версиям и локальному окружению, а не к скорости будущего облачного размещения.
Данные и интеграции
- Основной источник: предоставленный
hackathon dataset anonymized .csv— 66 анонимизированных профилей, 17 категорий. Исходный файл сохранён; команда не добавляла профили в рабочий каталог. - В исходном наборе 13 синтетических профилей, 8 дополненных городов и 18 дополненных цен. Флаг
syntheticне означает, что запись добавила команда: происхождение показывается отдельно. - CSV содержит полные описания и занятые даты. HTML-превью служит справочным материалом; сокращённые описания и сводные числа из него не подменяют исходные данные.
- Импорт поддерживает CSV и JSONL. Производный
data/derived/catalog.jsonl— альтернативное представление того же каталога; одновременно загружать оба файла не нужно. backend/app/data/profile_facts.jsonсодержит разметку оснований и предупреждений, проверяемую относительно исходных описаний. Подробности происхождения — в описании каталога.- Собственный HTTP API связывает сайт и FastAPI. Supabase, NVIDIA API и другие внешние сервисы не вызываются; отправки профилей в модель нет. SQLite используется только для аккаунтов и сессий.
Ограничения
- Календарь фиксирован: 23.09.2026–31.12.2026 включительно. Доступность известна только по датасету и не подтверждается подрядчиком в реальном времени.
- Цена «от» не равна окончательной стоимости.
max_hours = nullозначает, что услуга не привязана к присутствию и фильтр часов не применяется; это не обещание неограниченной работы. - Объяснения ограничены фактами набора. Для нового или изменённого описания может потребоваться обновить разметку; если данных для отличия профилей недостаточно, сервис сообщает об этом, а не придумывает преимущество.
- Каталог загружается в память при старте API. Нет автоматической синхронизации календарей, редактирования каталога через сайт или подключения к облачной базе.
- Нет бронирования, заявок, оплаты и уведомлений подрядчикам. Аккаунты не сохраняют историю подборов; город профиля не подставляется в форму. Подтверждение email, восстановление/смена пароля и удаление аккаунта не реализованы.
- Проверки выполняются локально на небольшом каталоге; высокая параллельная нагрузка, публичная сеть и все семейства браузеров не проверены. Нативный запуск на Windows не проверялся.
Deployed-версия
Публичная deployed-ссылка не настроена. Для живой проверки используйте локальный запуск через Docker: http://localhost:8080. Этот адрес доступен на вашем компьютере; текущий Compose публикует порт только на 127.0.0.1. Порядок настройки HTTPS и внешнего доступа описан в docs/docker.md.
Дополнительная документация
Алгоритм · Каталог · API · OpenAPI · Docker · Аккаунты · Демонстрация · Проверка требований · История этапов
Авторы коммитов
История коммитов
- 2026-09-23 17:53the endAkira-vs96
- 2026-09-23 17:38Enable automatic filters and fix interface controlsAASSAA17
- 2026-09-23 17:38Present AI-ANA problem, solution, and setup in READMEAASSAA17
- 2026-09-23 17:02Document fresh-clone SQLite setup and verify Docker persistenceAASSAA17
- 2026-09-23 16:53Add account authentication and Docker Compose deploymentAASSAA17
- 2026-09-23 16:266Akira-vs96
- 2026-09-23 16:26Finalize project documentation and handoffAkira-vs96
- 2026-09-23 16:07Add original task materials and workspace configurationArsen Aitkazinov
Все коммиты (13)
- 2026-09-23 16:066Akira-vs96
- 2026-09-23 16:00q1Akira-vs96
- 2026-09-23 15:58dAkira-vs96
- 2026-09-23 14:34filesAkira-vs96
- 2026-09-23 14:331commitAkira-vs96
Структура репозитория (186 файлов)
.dockerignore .env.example .gitignore HackAlem AI_ Хакатон-задача_ умный подбор подрядчиков.docx HackAlem AI_ Хакатон-задача_ умный подбор подрядчиков.pdf README.md backend/.env.example backend/Dockerfile backend/app/__init__.py backend/app/api_models.py backend/app/auth.py backend/app/catalog.py backend/app/cli.py backend/app/data/profile_facts.json backend/app/explanations.py backend/app/export_contract.py backend/app/export_examples.py backend/app/main.py backend/app/match_cli.py backend/app/matching.py backend/app/matching_models.py backend/app/models.py backend/pyproject.toml backend/requirements.txt backend/tests/fixtures/README.md backend/tests/fixtures/catalog_valid.csv backend/tests/fixtures/catalog_valid.jsonl backend/tests/test_auth.py backend/tests/test_catalog.py backend/tests/test_explanations.py backend/tests/test_match_cli.py backend/tests/test_matching.py backend/tests/test_matching_determinism.py backend/tests/test_recommendations_api.py backend/tests/test_server.py compose.yaml data/derived/catalog.jsonl data/derived/catalog.jsonl.meta.json docs/account-check-2026-09-23/ai-ana-account-320-submit.png docs/account-check-2026-09-23/ai-ana-account-320.png docs/account-check-2026-09-23/ai-ana-account-profile-desktop.png docs/account-check-2026-09-23/ai-ana-account-register-desktop.png docs/account-check-2026-09-23/matching-browser-report.json docs/accounts.md docs/api-examples/corporate-host-next-date.json docs/api-examples/corporate-host.json docs/api-examples/minimum-duration.json docs/api-examples/national-ensemble.json docs/api-examples/no-category.json docs/api-examples/no-matches.json docs/api-examples/rare-category.json docs/api-examples/venue-busy.json docs/api-examples/venue-free.json docs/api.md docs/audit.md docs/catalog-conversion.json docs/catalog-report.json docs/catalog.md docs/cloud-csv-advice.md docs/demo-guide.md docs/design.md docs/docker.md docs/handoff.md docs/interface-check.md docs/matching-report.json docs/matching.md docs/openapi.json docs/progress.md docs/queries/corporate-host-next-date.json docs/queries/corporate-host.json docs/queries/national-ensemble.json docs/queries/no-category.json docs/queries/no-matches.json docs/queries/rare-category.json docs/queries/venue-busy.json docs/queries/venue-free.json docs/recheck-2026-09-23/README.md docs/recheck-2026-09-23/stage7-browser-report.json docs/recheck-2026-09-23/stage7-corporate-host-next-date.png docs/recheck-2026-09-23/stage7-corporate-host.png docs/recheck-2026-09-23/stage7-no-matches.png docs/recheck-2026-09-23/stage7-rare-category.png docs/recheck-2026-09-23/stage7-venue-busy.png docs/recheck-2026-09-23/stage7-venue-free.png docs/requirements-browser-report.json docs/screenshots/appearance-green.png docs/screenshots/filters-1440.png docs/screenshots/filters-390.png docs/screenshots/prototype-1440.png docs/screenshots/prototype-320.png docs/screenshots/prototype-390.png docs/screenshots/prototype-768.png docs/screenshots/requirements-1440.png docs/screenshots/requirements-320.png docs/screenshots/requirements-390.png docs/screenshots/requirements-768.png docs/screenshots/requirements-category_unavailable-320.png docs/screenshots/requirements-category_unavailable-390.png docs/screenshots/requirements-corporate-host-next-date.png docs/screenshots/requirements-corporate-host.png docs/screenshots/requirements-initial-390.png docs/screenshots/requirements-loading-390.png docs/screenshots/requirements-national-ensemble.png docs/screenshots/requirements-no-category.png docs/screenshots/requirements-no-matches.png docs/screenshots/requirements-no_matches-320.png docs/screenshots/requirements-no_matches-390.png docs/screenshots/requirements-rare-category.png docs/screenshots/requirements-timeout-390.png docs/screenshots/requirements-venue-busy.png docs/screenshots/requirements-venue-free.png docs/screenshots/stage6-1440.png docs/screenshots/stage6-320.png docs/screenshots/stage6-390.png docs/screenshots/stage6-768.png docs/screenshots/stage7-1440.png docs/screenshots/stage7-320.png docs/screenshots/stage7-390.png docs/screenshots/stage7-768.png docs/screenshots/stage7-category_unavailable-320.png docs/screenshots/stage7-category_unavailable-390.png docs/screenshots/stage7-corporate-host-next-date.png docs/screenshots/stage7-corporate-host.png docs/screenshots/stage7-initial-390.png docs/screenshots/stage7-loading-390.png docs/screenshots/stage7-no-category.png docs/screenshots/stage7-no-matches.png docs/screenshots/stage7-no_matches-320.png docs/screenshots/stage7-no_matches-390.png docs/screenshots/stage7-rare-category.png docs/screenshots/stage7-timeout-390.png docs/screenshots/stage7-venue-busy.png docs/screenshots/stage7-venue-free.png docs/screenshots/stage8-restart.png docs/stage7-browser-report.json docs/stage7-visual-report.json docs/stage8-links-report.json docs/stage8-run-report.json docs/verification-requirements.md docs/verification-stage7.md frontend/Dockerfile frontend/components.json frontend/index.html frontend/nginx.conf frontend/package-lock.json frontend/package.json frontend/playwright.config.ts frontend/public/brand/favicon.svg frontend/public/brand/logo.jpeg frontend/src/App.tsx frontend/src/components/Account.tsx frontend/src/components/AppearanceSettings.tsx frontend/src/components/RecommendationCard.tsx frontend/src/components/brand/BrandLogo.tsx frontend/src/components/ui/button.tsx frontend/src/config/brand.ts frontend/src/lib/api/auth.ts frontend/src/lib/api/client.ts frontend/src/lib/api/generated.ts frontend/src/lib/api/response-schema.ts frontend/src/lib/matching-form.ts frontend/src/lib/use-matching.ts frontend/src/lib/utils.ts frontend/src/main.tsx frontend/src/styles/account.css frontend/src/styles/tokens.css frontend/src/vite-env.d.ts frontend/tests/artifacts.ts frontend/tests/auth.spec.ts frontend/tests/controls.spec.ts frontend/tests/demonstration.spec.ts frontend/tests/filters.spec.ts frontend/tests/prototype.spec.ts frontend/tests/requirements.spec.ts frontend/tests/resilience.spec.ts frontend/tsconfig.json frontend/vite.config.ts hack-3c47c506-ai-ana.code-workspace hackathon dataset anonymized .csv hackathon dataset preview.html logo.jpeg outputs/contractor-agent-brief.zip outputs/dataset-review-ru.md outputs/prompt-contractor-matching-agent-ru.md outputs/stage-1-plan-ru.md prompt-contractor-matching-agent-ru.md
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.