Smart Contractor
Веб-сервис подбора до 3 event-подрядчиков по городу, категории, дате, бюджету, формату, языку и длительности с объяснениями, матрицей доступности на…
О проекте
Команда: 228
Кейс HackAlem.ai: Креативные индустрии — Firebird
Стадия: Рабочий MVP
Задача кейса: «#79-lite»: подобрать до 3 подрядчиков для мероприятия и объяснить выбор
Суть
Веб-сервис подбора до 3 event-подрядчиков по городу, категории, дате, бюджету, формату, языку и длительности с объяснениями, матрицей доступности на 7 дней и слепым режимом.
Как устроено
FastAPI-сервер последовательно фильтрует 66 профилей (категория → город → дата → бюджет → формат → язык → длительность) и сортирует по (budget − price_from_kzt, id), берёт первые три. Для выбранных карточек AsyncOpenAI через Structured Outputs возвращает индексы 1–2 фрагментов описания, сервер проверяет индексы и собирает объяснение только из существующего текста; при таймауте 6,5 с — резервное объяснение. Фронтенд на vanilla JS/Tailwind показывает воронку отсева и матрицу доступных дат, считаемую теми же фильтрами на клиенте.
Ключевые решения
- последовательные жёсткие фильтры
- детерминированное ранжирование по близости цены к бюджету
- Structured Outputs с выбором индексов фрагментов
- кэш объяснений
- rate limiting
Особенности
LLM не генерирует свободный текст, а лишь выбирает индексы существующих фрагментов описания — простой и надёжный способ избежать галлюцинаций в объяснениях; совпадение клиентских и серверных счётчиков проверяется тестами.
Стек
Python · FastAPI · Pydantic · Uvicorn · OpenAI SDK · vanilla JS · Tailwind CSS · Docker · Caddy · Render · GitHub Actions
Модели
gpt-4o-mini
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-0c90ccb6-228.
README
README.mdSmart Contractor
Умный подбор event-подрядчиков в Казахстане: до трёх подходящих профилей, проверка свободной даты, бюджета, формата, языка и длительности. Интерфейс со стеклянными панелями и вечерним mesh-градиентом подключён к работающему Python API.
В комплекте: все 66 профилей исходного датасета, 13 синтетических профилей с отметками, пять сценариев для демонстрации, матрица доступности на семь дней, слепой режим, аудит отсева, OpenAI-интеграция с резервными объяснениями, Docker и Render Blueprint.
Быстро загрузить на GitHub
- Распакуйте архив. Откройте папку, в которой лежат
main.py,requirements.txt,Dockerfileи этот README. - Создайте на GitHub пустой репозиторий, например
smart-contractor. Для команд ниже не добавляйте README на сайте при создании — он уже есть в проекте. - В терминале в папке проекта выполните:
git init -b main
git add .
git commit -m "Initial Smart Contractor application"
git remote add origin https://github.com/YOUR_USERNAME/smart-contractor.git
git push -u origin main
Замените YOUR_USERNAME своим логином. Если Git просит представиться, задайте свои user.name и user.email. Авторизацию GitHub выполните обычным способом через Git Credential Manager, GitHub CLI или SSH; токены в файлы проекта добавлять не нужно.
Без терминала можно воспользоваться GitHub Desktop: Add local repository → create a repository → Commit → Publish repository. Публикуйте папку проекта целиком, чтобы сохранить скрытые .github, .gitignore, .env.example и .gitattributes.
GitHub хранит исходники. Для работающего сайта нужен Python/Docker-хостинг. GitHub Pages сам по себе не запускает FastAPI и OpenAI-интеграцию. Порядок публикации сайта: DEPLOY.md.
Запуск на компьютере
Нужен Python 3.11+; локальная проверка выполнена на Python 3.12.
Windows PowerShell:
py -3 -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
Copy-Item .env.example .env
.\.venv\Scripts\python.exe main.py
macOS / Linux:
python3 -m venv .venv
.venv/bin/python -m pip install -r requirements.txt
cp .env.example .env
.venv/bin/python main.py
Откройте http://127.0.0.1:8000. Документация API: http://127.0.0.1:8000/docs. Есть также скрипты start.ps1 и start.sh, которые создают окружение, устанавливают зависимости и запускают приложение.
Для объяснений через OpenAI добавьте свой новый ключ в OPENAI_API_KEY файла .env. Ключей в поставке нет. Без ключа приложение полностью выполняет подбор и составляет объяснения из каталога; интерфейс явно сообщает о резервном режиме. Из браузера ключ никогда не отправляется — API вызывается сервером.
Что делает приложение
- Строго исключает подрядчиков, занятых на выбранную дату.
- Применяет фильтры: категория → город → дата → бюджет → формат → язык → длительность.
- Сортирует оставшихся по
(budget - price_from_kzt, id)по возрастанию; берёт первые три. Это близость цены к бюджету, а не оценка качества подрядчика. - Различает три результата:
SUCCESS,CITY_CATEGORY_NOT_FOUND,NO_CANDIDATES_MATCHED. - При выдаче из 1–2 карточек возвращает
partial_reason; при пустой выдаче показывает объяснение и числа отсева. - После поиска показывает семь дат: выбранную по центру и по три дня с каждой стороны. Числа учитывают все условия и весь каталог до ограничения выдачи тремя карточками. Клик по дате меняет поле формы и повторяет поиск.
- Сохраняет исходные
synthetic,city_imputed,price_imputedи отображает соответствующие отметки. - Скрывает имена в слепом режиме, сохраняя порядок и факты. ID остаётся для проверки воспроизводимости.
Счётчики воронки последовательные: один профиль исключается один раз, на первом непройденном условии. Первый этап показывает количество в выбранной категории, а не весь каталог. Окно календаря: 23.09.2026 — 31.12.2026. max_hours = null означает отсутствие лимита часов присутствия. Цена указана «от» и не является подтверждённой сметой.
В матрице красный цвет означает «Нет мест», жёлтый — 1–2 варианта, зелёный — 3 и более. Она остаётся доступна при пустой выдаче. За пределами календаря кнопки отключены и подписаны «Вне календаря»; такие дни не считаются свободными. На узком экране ряд прокручивается горизонтально и центрируется на выбранной дате. При ручном изменении условий матрица скрывается до следующего поиска, чтобы не показывать устаревшие числа.
renderDateMatrix(selectedDate, formParams, dataset) в static/app.js создаёт кнопки и обработчики, а getDateMatrix в static/date-matrix.js применяет те же семь фильтров, что и сервер. Браузер получает только поля для фильтрации в availability_profiles из /api/meta, без имён и описаний. Подсчёт соседних дат не вызывает LLM и не выполняет семь сетевых запросов. Совпадение клиентских счётчиков с matcher.py проверяется автоматическими тестами.
Как используются LLM
Фильтрация и ранжирование реализованы обычным кодом. LLM не меняет состав подборки. Для максимум трёх отобранных профилей AsyncOpenAI конкурентно выбирает 1–2 конкретных фрагмента описания, возвращая индексы через Structured Outputs. Сервер проверяет индексы, берёт только существующие фрагменты и добавляет точные условия заказа. Свободные, непроверенные утверждения модели в карточки не попадают.
Модель по умолчанию — gpt-4o-mini, temperature=0. Дедлайн одного вызова, включая очередь, — 6,5 секунды; повторные попытки SDK отключены. При ошибке или таймауте используется резервное объяснение. Текст профиля считается данными, а не инструкциями. Дообучение не используется.
Порядок карточек детерминирован. Успешные объяснения кэшируются в памяти до 1024 записей и совпадают при переключении слепого режима. После перезапуска выбранные моделью цитаты могут отличаться: temperature=0 не является абсолютной гарантией идентичного текста.
Настройки
| Переменная | Значение по умолчанию | Назначение |
|---|---|---|
OPENAI_API_KEY | пусто | Серверный ключ OpenAI; пустое значение включает резервный режим |
OPENAI_MODEL | gpt-4o-mini | Модель с Chat Completions и Structured Outputs |
LLM_TIMEOUT_SECONDS | 6.5 | Дедлайн, ограничен максимумом 8 секунд |
RATE_LIMIT_PER_MINUTE | 30 | Лимит запросов подбора с одного IP |
PORT | 8000 | Порт приложения |
DATASET_PATH | JSONL в корне | Альтернативный путь к данным |
DOMAIN | пусто | Домен Caddy при деплое через Compose |
Кэш, лимит запросов и очередь находятся в памяти одного процесса. Для нескольких экземпляров сервиса нужно общее хранилище лимитов и кэша. Бюджет расходов OpenAI задаётся отдельно в аккаунте провайдера. Холодный запуск хостинга и задержка сети не входят в целевое время обработки работающего API до 10 секунд.
API и сценарии для защиты
GET /healthz: состояние приложения и число профилей.llm_configuredподтверждает только наличие ключа в настройках.GET /api/meta: значения списков, окно календаря, готовые сценарии,availability_profilesдля матрицы дат (категории, город, цена, форматы, языки, лимит часов и занятые даты).POST /api/match: подбор.explanation_source: openaiв карточке подтверждает фактический ответ модели.
{
"city": "Алматы",
"category": "Ведущий",
"event_date": "2026-09-23",
"event_format": "корпоратив",
"budget": 1500000,
"duration_hours": null,
"language": null,
"blind_mode": false
}
Над результатами доступны пять реальных запросов из demo-queries.json: ведущие осенью (прошли 4, выбраны 3), другая дата, флористы с неполной выдачей, все 7 банкетных залов Алматы заняты 6 декабря, отсутствие категории в Астане. Кнопки работают через API; вымышленной карточки из макета в приложении нет.
Проверки и GitHub Actions
python -m pip install -r requirements-dev.txt
python -m pytest -q
Для проверки матрицы нужен также Node.js в PATH (в CI используется Node 22); можно указать путь переменной NODE_BINARY. Без Node тесты клиентского подсчёта будут пропущены, а тесты сервера выполнятся. Для самого сайта Node не нужен.
В .github/workflows/ci.yml настроены тесты Python 3.11/3.12, сверка JavaScript-матрицы с серверным подбором, проверка синтаксиса JavaScript, сборка Docker-образа и запуск контейнера с PORT=8080. Workflow запускается на push и pull request, не использует API-ключи и ничего не публикует на хостинг. Посмотреть результат после загрузки можно во вкладке Actions.
Подробности фактически выполненной локальной проверки: TEST_REPORT.md.
Файлы
main.py, models.py, matcher.py, llm_explainer.py сервер и бизнес-логика
static/index.html новый интерфейс Smart Contractor
static/styles.css стеклянные панели, mesh-фон, адаптивность
static/tailwind-config.js палитра, шрифты, анимация Tailwind CDN
static/app.js обращения к API и отрисовка результатов
static/date-matrix.js подсчёт доступных подрядчиков на соседние даты
hackathon-dataset-anonymized.jsonl 66 профилей из вашего CSV
demo-queries.json пять проверяемых сценариев
tests/ тесты без расходов API
tools/ конвертер CSV и генератор сценариев
.github/workflows/ci.yml тесты и сборка в GitHub Actions
Dockerfile, compose.yaml, Caddyfile запуск сервера и HTTPS
render.yaml, Procfile настройки облачного запуска
.env.example, .gitignore, .dockerignore шаблон настроек и исключение секретов
README.md, DEPLOY.md, TEST_REPORT.md документация
Стек: Python/FastAPI/Pydantic/Uvicorn, OpenAI SDK, HTML/CSS/vanilla JavaScript, Tailwind CSS через запрошенный CDN, Inter из Google Fonts. Npm-сборки нет. Для загрузки Tailwind CDN и шрифта нужен интернет; перед крупным production-развёртыванием Tailwind можно собрать локально. Официальные инструкции Render и Python CI GitHub.
Авторы коммитов
История коммитов
- 2026-09-23 16:23Add files via uploadgggfgfhhhhhhh
- 2026-09-23 16:21Add files via uploadgggfgfhhhhhhh
Структура репозитория (25 файлов)
.dockerignore .env.example .gitattributes .gitignore .python-version Caddyfile DEPLOY.md Dockerfile Procfile README.md TEST_REPORT.md compose.yaml demo-queries.json hackathon-dataset-anonymized.jsonl llm_explainer.py main.py matcher.py models.py pytest.ini render.yaml requirements-dev.txt requirements.txt smart-contractor-github.zip start.ps1 start.sh
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.