AAYMA
RU
Войти

Smart Contractor

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

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

О проекте

Команда: 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.md

Smart Contractor

Умный подбор event-подрядчиков в Казахстане: до трёх подходящих профилей, проверка свободной даты, бюджета, формата, языка и длительности. Интерфейс со стеклянными панелями и вечерним mesh-градиентом подключён к работающему Python API.

В комплекте: все 66 профилей исходного датасета, 13 синтетических профилей с отметками, пять сценариев для демонстрации, матрица доступности на семь дней, слепой режим, аудит отсева, OpenAI-интеграция с резервными объяснениями, Docker и Render Blueprint.

Быстро загрузить на GitHub

  1. Распакуйте архив. Откройте папку, в которой лежат main.py, requirements.txt, Dockerfile и этот README.
  2. Создайте на GitHub пустой репозиторий, например smart-contractor. Для команд ниже не добавляйте README на сайте при создании — он уже есть в проекте.
  3. В терминале в папке проекта выполните:
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_MODELgpt-4o-miniМодель с Chat Completions и Structured Outputs
LLM_TIMEOUT_SECONDS6.5Дедлайн, ограничен максимумом 8 секунд
RATE_LIMIT_PER_MINUTE30Лимит запросов подбора с одного IP
PORT8000Порт приложения
DATASET_PATHJSONL в корнеАльтернативный путь к данным
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.

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

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

gggfgfhhhhhhh 2

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

  • 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 комментариев

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

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