HackAlem AI #79-lite
API и веб-интерфейс для подбора до 3 event-подрядчиков из датасета на 66 профилей по городу, дате, формату, категории, бюджету, языку и…
О проекте
Команда: Где мой API
Кейс HackAlem.ai: Креативные индустрии — Firebird
Стадия: Рабочий MVP
Задача кейса: «#79-lite»: подобрать до 3 подрядчиков для мероприятия и объяснить выбор
Суть
API и веб-интерфейс для подбора до 3 event-подрядчиков из датасета на 66 профилей по городу, дате, формату, категории, бюджету, языку и длительности, с объяснением выбора и прозрачными альтернативами.
Как устроено
Next.js-форма или свободный текст (intake-парсер) через proxy уходят в NestJS; DTO валидируются, CSV-профили нормализуются. Модуль matching применяет жёсткие фильтры (город, категория, занятость на дату, бюджет, формат, язык, длительность). Допустимые профили передаются в OpenAI Responses API для смыслового ранжирования и объяснений с проверкой через Zod Structured Outputs; при отсутствии ключа или невалидном ответе работает детерминированный fallback. Возвращаются максимум 3 карточки с таблицей сверки и явной причиной, если точных совпадений меньше трёх (matched / no_category_in_city / no_candidates_after_filters).
Ключевые решения
- hard-constraint filtering
- LLM reranking
- Structured Outputs with Zod
- deterministic fallback scoring
- transparent alternatives with differences
- result caching
Особенности
Три явно различимых исхода подбора и альтернативы, у которых перечислены отличия от запроса (дата/бюджет/язык/длительность), вместо молчаливой подмены. Учитываются флаги качества данных (synthetic, city_imputed, price_imputed). Дополнительно сделан Telegram-бот.
Стек
Node.js 22 · TypeScript · NestJS · Next.js 15 · React 19 · Tailwind CSS · shadcn/ui · TanStack Query · Zod · Jest · Vitest · Playwright · Docker Compose · Python (Telegram bot)
Модели
gpt-4o-mini
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-ea63b56a-api.
README
README.mdHackAlem AI #79-lite
API и веб-интерфейс для хакатон-задачи «умный подбор подрядчиков». Проект помогает организатору мероприятия быстро подобрать event-подрядчиков по городу, дате, формату, категории, бюджету, языку и длительности. На выходе пользователь получает до трёх карточек с конкретным объяснением, почему подрядчик подходит или почему точных вариантов меньше трёх.
Публичной deployed-версии пока нет. Проект запускается локально или через Docker Compose.
Что реализовано
- Подбор подрядчиков из анонимизированного CSV-датасета на 66 профилей.
- Жёсткие фильтры по городу, категории, занятости на дату, бюджету, формату, языку и длительности.
- Поддержка составных категорий в CSV через
|: запрос по одной категории находит часть составной. - Стабильный порядок карточек для одинакового запроса.
- Максимум 3 карточки в ответе.
- Три различимых исхода для пользователя:
matched— найдены точные или близкие карточки;no_category_in_city— в выбранном городе нет такой категории;no_candidates_after_filters— категория есть, но условия отсеяли кандидатов.
- Прозрачные альтернативы, если точных совпадений меньше трёх: карточка сохраняет город,
категорию и формат, а отличия по дате, бюджету, языку или длительности перечисляются в
differences. - Персональные объяснения по бюджету, формату, языку, длительности, дате и смыслу описания.
- Таблица сверки в каждой карточке: запрос заказчика сравнивается с данными исполнителя, а отличия подсвечиваются отдельно и не заменяют человеческое объяснение.
- Шесть быстрых сценариев с точными результатами, редкой категорией и прозрачными альтернативами.
- Fallback без OpenAI API: сервис продолжает работать детерминированно без ключа.
- Веб-интерфейс с ручной формой, вкладкой свободного текста и страницей просмотра датасета.
- Отображение флагов качества данных:
synthetic,city_imputed,price_imputed. - Swagger-документация backend API.
Бронирования, платежей, уведомлений и личных кабинетов в текущей версии нет.
Как работает решение
- Пользователь открывает Next.js-интерфейс и вводит параметры мероприятия вручную или свободным текстом.
- Frontend отправляет запрос через same-origin proxy на NestJS backend.
- Backend валидирует DTO и нормализует входные значения.
- Модуль
contractorsчитает нормализованные профили из CSV. - Модуль
matchingприменяет обязательные фильтры: город, категория, дата занятости, бюджет, формат, язык и длительность. - Если есть подходящие кандидаты и настроен
OPENAI_API_KEY, backend передаёт только допустимые профили в OpenAI Responses API для смыслового ранжирования и генерации объяснений. - Ответ модели проверяется через Zod Structured Outputs и сверяется с исходными профилями.
- При отсутствии ключа, таймауте или невалидном ответе включается детерминированный fallback.
- Сервис сортирует результат, возвращает максимум три карточки и явно сообщает, почему карточек меньше трёх, если это произошло.
ИИ не имеет права возвращать занятого подрядчика, менять цену или придумывать отсутствующие поля: жёсткие бизнес-правила проверяются обычным кодом до и после вызова модели.
Технологии
Backend:
- Node.js 22, TypeScript, NestJS;
- REST API с префиксом
/api/v1; @nestjs/config,class-validator,class-transformer;@nestjs/swaggerдля OpenAPI/Swagger;csv-parseдля чтения датасета;- официальный пакет
openai, Responses API и Structured Outputs; - Zod для проверки структурированных ответов модели;
nestjs-pino,pino-pretty,helmet, CORS,@nestjs/throttler;- Jest и Supertest для тестов.
Frontend:
- Next.js 15 App Router, React 19, TypeScript;
- Tailwind CSS;
- shadcn/ui, Radix UI, Lucide React;
- React Hook Form, Zod,
@hookform/resolvers; - TanStack Query;
- Sonner;
- Vitest, Testing Library и Playwright.
Инфраструктура:
- Docker Compose для запуска backend и frontend;
- named volume
recommendationsдля кэша повторяемых результатов; - локальные smoke-скрипты в
scripts/.
Архитектура проекта
Репозиторий — монорепозиторий с двумя приложениями:
backend/ NestJS API, подбор подрядчиков, фильтры, OpenAI-интеграция, fallback
frontend/ Next.js web-интерфейс, форма, чат-ввод, просмотр датасета
docs/ архитектура и API-контракт
scripts/ smoke-проверки
tests/ дополнительные тестовые артефакты
Основной поток:
Next.js form/chat
-> /api/v1/* proxy
-> NestJS DTO validation
-> CSV repository
-> matching filters
-> OpenAI Responses API + Zod validation или fallback scoring
-> stable sort
-> response with exact cards and transparent alternatives
Ключ OpenAI хранится только в backend-переменной OPENAI_API_KEY. Frontend не обращается к
OpenAI напрямую и не получает секреты. Подробности архитектуры описаны в
docs/ARCHITECTURE.md, контракт API — в
docs/API-CONTRACT.md.
Основная ветка
main — единственная основная ветка проекта. Актуальная версия решения, изменения для демонстрации
и итоговая версия для сдачи хранятся в main; в рамках текущего процесса отдельные рабочие ветки
не используются.
Установка и запуск локально
Нужны Node.js 22, npm и два терминала. Команды выполняются из корня репозитория.
Backend:
npm --prefix backend ci
npm --prefix backend run start:dev
Frontend:
npm --prefix frontend ci
npm --prefix frontend run dev
После запуска:
- сайт: http://localhost:3000;
- страница датасета: http://localhost:3000/dataset;
- API: http://localhost:3001/api/v1;
- Swagger: http://localhost:3001/api/docs;
- healthcheck: http://localhost:3001/api/v1/health.
Без API-ключа приложение работает в fallback-режиме. Для включения ИИ создайте backend/.env
по примеру backend/.env.example и задайте OPENAI_API_KEY:
PORT=3001
OPENAI_API_KEY=
OPENAI_MODEL=gpt-4o-mini
OPENAI_TIMEOUT_MS=7000
DATASET_PATH=./data/contractors.csv
CACHE_DIR=./cache
FRONTEND_ORIGIN=http://localhost:3000
Frontend использует серверную переменную BACKEND_URL из frontend/.env.example.
Не кладите API-ключ в NEXT_PUBLIC_*.
Запуск через Docker
Docker Desktop должен быть запущен в режиме Linux containers.
docker compose config --quiet
docker compose build
docker compose up -d --wait
node scripts/smoke.mjs
Открыть интерфейс: http://localhost:3000. Swagger: http://localhost:3001/api/docs.
backend/.env подключается при наличии, но запуск без него поддерживается. В Docker frontend
обращается к backend по http://backend:3001, а исходный CSV копируется в backend-образ без
изменения. Кэш рекомендаций хранится в named volume recommendations, поэтому переживает
restart/rebuild.
Для остановки:
docker compose down
Для других портов используйте FRONTEND_PORT и BACKEND_PORT; для smoke-проверки —
FRONTEND_URL и API_URL.
Как проверить решение
Самый простой повторяемый сценарий для жюри:
- Запустить проект через Docker Compose.
- Открыть http://localhost:3000.
- Выбрать демо-сценарий или вручную ввести:
- город:
Алматы; - дата:
2026-10-15; - формат:
корпоратив; - категория:
Ведущий; - бюджет:
900000; - язык:
русский.
- город:
- Убедиться, что возвращаются до трёх карточек, у каждой есть конкретное объяснение и таблица критериев.
- Повторить запрос с датой
2026-12-20: выдача должна измениться из-за декабрьской занятости. - Проверить редкий сценарий:
Астана,2026-11-14,Флорист, бюджет900000. - Проверить пустой сценарий: плотный запрос с бюджетом
1илиАстана+Инструменталист.
Быстрые сценарии на главной странице подобраны по текущему датасету и всегда показывают рекомендации:
| Сценарий | Что демонстрирует | Ожидаемый результат |
|---|---|---|
| Корпоратив · ведущий | Плотная точная выдача в Алматы | 3 точных |
| Корпоратив · флорист | Редкая категория в Астане | 1 точный |
| Корпоратив · новая дата | Занятость на выбранную дату | 3 близкие альтернативы |
| Свадьба · фотограф | Длительность работы 6 часов | 3 точных |
| Той · ведущий | Казахский язык и длительность | 2 точных + 1 альтернатива |
| Свадьба · банкетный зал | Комбинированная выдача | 1 точный + 2 альтернативы |
Нулевая выдача остаётся частью корректного поведения: система не подменяет категорию и не рекомендует профиль, если безопасной альтернативы в заданных пределах нет.
Те же проверки можно выполнить автоматически:
node scripts/smoke.mjs
Smoke проверяет healthcheck, catalog, intake parser, рекомендации, альтернативы, повторяемость, датасет, валидацию, frontend и proxy.
Пример прямого API-запроса:
curl -s http://localhost:3001/api/v1/recommendations \
-H 'Content-Type: application/json' \
-d '{"city":"Алматы","date":"2026-10-15","eventFormat":"корпоратив","category":"Ведущий","budgetKzt":900000,"language":"русский"}'
Браузерная проверка всей связки:
npm --prefix frontend exec -- playwright install chromium
node scripts/browser-smoke.mjs
Проверки качества:
npm --prefix backend run lint
npm --prefix backend test
npm --prefix backend run build
npm --prefix frontend run lint
npm --prefix frontend test
npm --prefix frontend run build
Данные и интеграции
Используемые данные:
hackathon dataset anonymized.csv— основной источник профилей подрядчиков;HackAlem AI_ Хакатон-задача_ умный подбор подрядчиков.docx— исходное ТЗ;hackathon dataset preview.html— локальный preview исходных данных.
Характеристики датасета:
- 66 строк, 13 колонок;
- города: Алматы — 50, Астана — 15, Зарубежье — 1;
synthetic— 13 профилей;city_imputed— 8 профилей;price_imputed— 18 профилей;max_hours = nullозначает, что работа не привязана к присутствию на площадке.
Внешние сервисы:
- OpenAI API используется опционально для смыслового ранжирования и генерации объяснений;
- без OpenAI API работает локальный детерминированный fallback;
- других внешних сервисов, баз данных, очередей или векторных хранилищ в MVP нет.
Ограничения текущей версии
- Публичной deployed-версии пока нет.
- Поддерживается MVP-топология с одним backend-процессом.
- База данных и Redis не используются; CSV остаётся единственным источником профилей.
- Календарь доступности ограничен диапазоном
2026-09-23—2026-12-31; даты вне окна отклоняются с HTTP 400. - Результаты кэшируются как JSON-снимки. Изменение CSV, модели, версии промпта/пайплайна или удаление кэша создаёт новый набор результатов.
- Первый сохранённый результат для запроса может быть fallback, если в момент первого запроса OpenAI был недоступен.
- Нет дообучения модели на 66 строках и нет embeddings/vector search.
- Нет реального бронирования подрядчиков, оплаты, календарной синхронизации и уведомлений.
- Синтетические профили допустимы только с флагом
synthetic: true; интерфейс показывает этот флаг пользователю.
Полезные ссылки
- Архитектура: docs/ARCHITECTURE.md
- API-контракт: docs/API-CONTRACT.md
- Swagger после запуска: http://localhost:3001/api/docs
- Frontend после запуска: http://localhost:3000
- Deployed-версия: отсутствует на момент подготовки README
Авторы коммитов
История коммитов
- 2026-09-23 17:52update: READMEMatrasos
- 2026-09-23 17:52update: UIMatrasos
- 2026-09-23 17:47test: verify seasonal availability for dense demorisest
- 2026-09-23 17:47update: README testsMatrasos
- 2026-09-23 17:47feat: add Python Telegram bot for contractor recommendationsKerajo
- 2026-09-23 17:43updateMatrasos
- 2026-09-23 17:39Merge branch 'main' of https://github.com/BAITC-Hacks/hack-ea63b56a-apiMatrasos
- 2026-09-23 17:39updateMatrasos
Все коммиты (28)
- 2026-09-23 17:31test: add application audit scenarios and reportsrisest
- 2026-09-23 17:31test: add recommendation scenarios and requirements auditrisest
- 2026-09-23 17:30updateMatrasos
- 2026-09-23 17:20add: features and UIMatrasos
- 2026-09-23 17:03docs: add design reference for form-first redesignKerajo
- 2026-09-23 16:45feat: improve grounded contractor explanations and result messagesKerajo
- 2026-09-23 16:45update: UIMatrasos
- 2026-09-23 16:38test: reject empty matching field values in datasetrisest
- 2026-09-23 16:35feat: redesign recommender with shadcnMatrasos
- 2026-09-23 16:31test: add root matching scenarios and full verification reportrisest
- 2026-09-23 16:14feat: add OpenAI integration and harden recommendationsKerajo
- 2026-09-23 15:46feat: complete AI contractor recommenderMatrasos
- 2026-09-23 15:35test: validate dataset prices and hour limitsrisest
- 2026-09-23 15:26feat: add contractor recommendation appKerajo
- 2026-09-23 15:22test: validate contractor quality flagsrisest
- 2026-09-23 14:58add: back & frontMatrasos
- 2026-09-23 14:31test: add two dataset integrity checksrisest
- 2026-09-23 14:15chore: test commitKerajo
- 2026-09-23 13:44Configure hackathon contractor recommender projectMatrasos
- 2026-09-23 13:37Add Codex wrapperMatrasos
Структура репозитория (127 файлов)
.21st/DESIGN.md .21st/design.json .codex/config.toml .codex/rules/core.md .codex/rules/memory.md .codex/rules/nest.md .dockerignore .gitignore AGENTS.md HackAlem AI_ Хакатон-задача_ умный подбор подрядчиков.docx README.md backend/.dockerignore backend/.env.example backend/.gitignore backend/.gitkeep backend/Dockerfile backend/Dockerfile.dockerignore backend/README.md backend/data/contractors.csv backend/eslint.config.mjs backend/jest.config.cjs backend/nest-cli.json backend/package-lock.json backend/package.json backend/src/ai/ai.module.ts backend/src/ai/ai.service.ts backend/src/app.module.ts backend/src/common/configure-app.ts backend/src/common/domain.ts backend/src/common/evidence.ts backend/src/config/environment.ts backend/src/config/runtime-config.module.ts backend/src/contractors/contractors.controller.ts backend/src/contractors/contractors.dto.ts backend/src/contractors/contractors.module.ts backend/src/contractors/contractors.service.ts backend/src/contractors/csv-contractors.loader.ts backend/src/health/health.controller.ts backend/src/health/health.module.ts backend/src/intake/intake-ai.service.ts backend/src/intake/intake.controller.ts backend/src/intake/intake.dto.ts backend/src/intake/intake.module.ts backend/src/intake/intake.service.ts backend/src/main.ts backend/src/matching/matching.module.ts backend/src/matching/matching.service.ts backend/src/recommendations/explainer.service.ts backend/src/recommendations/recommendation.dto.ts backend/src/recommendations/recommendations.controller.ts backend/src/recommendations/recommendations.module.ts backend/src/recommendations/recommendations.service.ts backend/src/recommendations/snapshot.service.ts backend/test/ai.spec.ts backend/test/backend.spec.ts backend/test/config.spec.ts backend/test/contractors.spec.ts backend/test/intake.spec.ts backend/tsconfig.build.json backend/tsconfig.json docker-compose.yml docs/ACCEPTANCE.md docs/API-CONTRACT.md docs/ARCHITECTURE.md frontend/.dockerignore frontend/.env.example frontend/.gitignore frontend/.gitkeep frontend/Dockerfile frontend/components.json frontend/e2e/recommender.spec.ts frontend/eslint.config.mjs frontend/next-env.d.ts frontend/next.config.ts frontend/package-lock.json frontend/package.json frontend/playwright.config.ts frontend/postcss.config.js frontend/public/brand-mark.svg frontend/src/app/api/v1/[...path]/route.ts frontend/src/app/dataset/api/route.ts frontend/src/app/dataset/page.tsx frontend/src/app/globals.css frontend/src/app/layout.tsx frontend/src/app/page.tsx frontend/src/components/app-header.tsx frontend/src/components/dataset-explorer.test.tsx frontend/src/components/dataset-explorer.tsx frontend/src/components/intent-assistant.tsx frontend/src/components/providers.tsx frontend/src/components/recommender.test.tsx frontend/src/components/recommender.tsx frontend/src/components/ui/alert.tsx frontend/src/components/ui/badge.tsx frontend/src/components/ui/button.tsx frontend/src/components/ui/card.tsx frontend/src/components/ui/input.tsx frontend/src/components/ui/label.tsx frontend/src/components/ui/select.tsx frontend/src/components/ui/separator.tsx frontend/src/components/ui/skeleton.tsx frontend/src/components/ui/table.tsx frontend/src/components/ui/tabs.tsx frontend/src/components/ui/textarea.tsx frontend/src/components/ui/tooltip.tsx frontend/src/lib/api.test.ts frontend/src/lib/api.ts frontend/src/lib/dataset-api.test.ts frontend/src/lib/dataset-api.ts frontend/src/lib/form.test.ts frontend/src/lib/form.ts frontend/src/lib/utils.ts frontend/tailwind.config.ts frontend/tsconfig.json frontend/vitest.config.ts frontend/vitest.setup.ts hackathon dataset anonymized.csv hackathon dataset preview.html scripts/browser-smoke.mjs scripts/smoke.mjs tests/AI-SCENARIOS-REPORT.md tests/README.md tests/REQUIREMENTS-REVIEW.md tests/TEST-REPORT.md tests/matching.spec.cjs tests/recommendation-scenarios.spec.cjs tests/site-scenarios.mjs
Ветки (5)
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.