AAYMA
RU
Войти

NurSolution

Веб-сервис подбора до трёх event-подрядчиков с объяснениями, подсказками при пустом результате, каталогом в CSV или PostgreSQL и админ-редактором.

Доведённый продукт
Команда «NurSolution» 1💬 0 комментариевс 23 сентября 2026 г.

О проекте

Команда: NurSolution
Кейс HackAlem.ai: Креативные индустрии — Firebird
Стадия: Доведённый продукт

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

Суть

Веб-сервис подбора до трёх event-подрядчиков с объяснениями, подсказками при пустом результате, каталогом в CSV или PostgreSQL и админ-редактором.

Как устроено

Python фильтрует 66 профилей по городу, категории, бюджету, занятости и формату (опционально язык, длительность), сортирует по близости цены к бюджету, совпадению языка и id, берёт первые три детерминированно. Если AI включён, модель OpenAI получает только выбранные профили и выбирает дословные цитаты из описаний, Python проверяет цитаты и собирает объяснения; при ошибке/таймауте — объяснения от Python. При пустой выдаче считает причины отказов и предлагает проверенное изменение одного условия; кеш AI-ответов, дедупликация одновременных запросов и лимит LLM-конкуренции.

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

  • детерминированная фильтрация и сортировка
  • проверка дословных цитат
  • кеш с TTL
  • ограничение конкуренции LLM
  • подсказки ослабления условий

Особенности

LLM не выбирает подрядчиков, а только извлекает проверяемые цитаты; подсказки при пустой выдаче сначала проверяются повторным поиском. Есть /metrics с числом вызовов провайдера и таблица jury-сценариев с ожидаемыми результатами.

Стек

Python · FastAPI · PostgreSQL · Docker Compose · JavaScript · HTML · CSS · pytest

Модели

OpenAI


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

README

README.md

NurSolution

Умный подбор event-подрядчиков в Казахстане — до трёх карточек с объяснением каждого выбора.

Решение хакатон-задачи #79-lite для заказчиков свадеб, тоев, корпоративов и других мероприятий. Когда каталог уже выбран, NurSolution помогает сравнить подходящих подрядчиков: проверяет условия заказа и показывает конкретные факты из профилей. Если никто не подходит, объясняет причины и предлагает проверенные изменения условий.

66 исходных профилей · CSV / PostgreSQL · OpenAI · Docker

Быстрый запуск · Проверка для жюри · Архитектура · Документация

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

  • Строгая фильтрация по городу, категории, бюджету, занятости на дату и формату мероприятия; дополнительно — по языку и длительности.
  • До трёх карточек: факт из профиля, цена «от», сравнение с бюджетом и отметки о совпадении условий. Дата, подробности и источник раскрываются отдельно.
  • Детерминированный состав и порядок карточек при неизменных запросе и каталоге.
  • Различимые состояния: подрядчики подобраны, категории в городе нет, условия не прошёл никто.
  • Счётчики причин отказа и подсказки при пустом подборе: изменить одно условие и проверить результат.
  • Работа без ключа OpenAI; при ошибке или таймауте модели объяснения формирует Python.
  • Кеш проверенных AI-ответов, объединение одинаковых одновременных запросов и ограничение числа LLM-вызовов.
  • Отдельная главная страница и страница подбора с формой и результатами; адаптивное оформление, HTTP API, Swagger UI и автоматические тесты.
  • Два источника каталога: CSV или PostgreSQL; защищённый редактор профилей для базы данных.
  • Диагностика режима через /health, агрегаты запросов и OpenAI через /metrics.

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

  1. На главной странице заказчик нажимает «Подобрать подрядчиков» и переходит на /selection. Там указывает город, дату, формат мероприятия, категорию и бюджет в тенге и нажимает «Подобрать людей». Язык и длительность в часах необязательны. Страница формы загружает варианты полей из каталога; открытие любой страницы не запускает подбор.
  2. Python проверяет профили из выбранного источника — CSV или PostgreSQL. Занятые на указанную дату и не соответствующие условиям подрядчики исключаются. Каталог не расширяется.
  3. Подходящие профили сортируются по близости цены к бюджету, затем по совпадению языка и строковому id. Выбираются первые три. При строгом фильтре по языку все оставшиеся ему соответствуют.
  4. Если AI включён, модель получает только выбранные профили и выбирает дословные цитаты из описаний. Python проверяет цитаты и собирает итоговые объяснения с ценой и условиями.
  5. Рядом с формой появляются карточки либо аналитика пустого результата; на мобильном экране результаты располагаются ниже формы. Подсказка применяет одно изменение к форме и запускает новый поиск. При пустой выдаче обращения к LLM нет.

Быстрый запуск

Нужны Git и Docker Engine / Docker Desktop с Linux-контейнерами и Docker Compose. Python и Node.js на хосте для этого способа не требуются. Первая сборка скачивает образ и зависимости.

1. Получите проект

git clone https://github.com/BAITC-Hacks/hack-dbe07283-nursolution.git
cd hack-dbe07283-nursolution

Если репозиторий уже открыт локально, выполняйте следующие команды из его корня.

2. Создайте и заполните .env

Для запуска с OpenAI обязательно откройте .env в корне проекта и укажите свой ключ. Не редактируйте только .env.example: это образец, приложение использует .env.

Создайте файл из образца, если его ещё нет:

Windows / PowerShell:

if (-not (Test-Path .env)) { Copy-Item .env.example .env }

Linux / macOS / Bash:

if [ ! -f .env ]; then cp .env.example .env; fi

Откройте созданный .env. Для старта с исходным каталогом проверьте эти строки:

OPENAI_API_KEY=ваш_настоящий_ключ_OpenAI
MATCHER_USE_LLM=true
CATALOG_BACKEND=csv
APP_PORT=8080
MATCHER_TIMEOUT_SECONDS=8
EXPLANATION_CACHE_TTL_SECONDS=600
EXPLANATION_CACHE_MAX_ENTRIES=256
LLM_MAX_CONCURRENCY=4

Остальные поля уже есть в образце; их назначение описано в настройках. Замените текст ваш_настоящий_ключ_OpenAI действующим ключом. Существующий ключ сохраняйте, .env в Git не добавляйте. Главная страница / не запрашивает каталог или OpenAI. Страница /selection загружает /catalog для полей формы. При включённом AI только отправка запроса подбора может вызвать платный запрос.

3. Запустите с настройками из файла

Удалите переопределение режима из текущей оболочки, если раньше запускали офлайн-демо.

PowerShell:

Remove-Item Env:MATCHER_USE_LLM -ErrorAction SilentlyContinue
docker compose up --build -d --wait web

Bash:

unset MATCHER_USE_LLM
docker compose up --build -d --wait web

Запуск без OpenAI: в том же .env установите MATCHER_USE_LLM=false и выполните ту же команду запуска. Ключ тогда не нужен. Для чистой копии проекта без .env Compose тоже использует офлайн-режим.

4. Откройте приложение

АдресЧто доступно
localhost:8080Главная страница с переходом к подбору
localhost:8080/selectionФорма заказа и карточки результатов
localhost:8080/adminРедактор: вход по ADMIN_TOKEN, запись доступна в PostgreSQL
localhost:8080/docsSwagger UI: запросы прямо из браузера
localhost:8080/healthГотовность API и число загруженных профилей

Адреса приведены для стандартного порта 8080. Контейнер публикуется только на локальном интерфейсе.

docker compose ps
docker compose logs --tail=100 web
docker compose down

После изменения кода или CSV повторите запуск с --build. Подключение OpenAI и все переменные окружения описаны в настройках.

Как убедиться, что OpenAI включён

  1. Откройте /health: должно быть "llm_enabled": true. Это настройка процесса, а не проверка работоспособности ключа.
  2. Выполните подбор и посмотрите ответ POST /match в Network браузера. cards[].explanation_source: "openai" означает проверенный ответ модели, в том числе из кеша.
  3. Если указан "python", прочитайте warning: там причина резервного объяснения.
  4. Чтобы проверить именно новый вызов, перезапустите сервер и выполните один запрос; кеш после перезапуска пуст. Счётчик provider_calls в /metrics покажет вызовы провайдера.

Изменение .env требует перезапуска: docker compose up -d --wait web для Docker; для локального Python остановите Uvicorn через Ctrl+C и запустите заново. Обычное обновление страницы режим сервера не меняет. Другие переменные оболочки, включая OPENAI_API_KEY и CATALOG_BACKEND, также имеют приоритет над .env.

CSV или PostgreSQL

РежимКогда использоватьИзменение данных
CATALOG_BACKEND=csvПростой запуск и исходный каталог хакатонаВ /admin только просмотр; изменить CSV вручную и перезапустить, в Docker пересобрать
CATALOG_BACKEND=postgresПостоянное хранение и редактор подрядчиковСохранить профиль в /admin; новые запросы сразу видят изменения

Для Docker + PostgreSQL задайте в .env:

CATALOG_BACKEND=postgres
DOCKER_DATABASE_URL=
POSTGRES_PASSWORD=nur_local_dev
POSTGRES_PORT=55432
ADMIN_TOKEN=ваш_отдельный_ключ_редактора

DOCKER_DATABASE_URL для локальной базы оставьте пустым: Compose сформирует адрес db:5432 из POSTGRES_PASSWORD. Хостовый DATABASE_URL Compose не использует; он предназначен для Python вне Docker. nur_local_dev — пример пароля для локальной разработки. Если ADMIN_TOKEN уже заполнен в вашем .env, используйте его. В новой копии проекта ключа нет: создайте его командой ниже и вставьте результат вместо заглушки:

python -c "import secrets; print(secrets.token_urlsafe(32))"

Если Python на хосте нет, можно использовать Python из образа проекта:

docker compose run --rm --no-deps web python -c "import secrets; print(secrets.token_urlsafe(32))"

Затем запустите базу, дождитесь готовности и запустите API:

docker compose --profile postgres up -d --wait db
docker compose up --build -d --wait web

Откройте редактор и введите ADMIN_TOKEN из .env. Этот ключ не является ключом OpenAI. Пустой ADMIN_TOKEN отключает API редактора. При первом запуске в PostgreSQL импортируются 66 профилей из CSV; повторный запуск не затирает правки. Данные сохраняются в Docker-томе. Переключение обратно на csv не удаляет базу и не переносит её изменения в CSV.

Для Python вне Docker добавьте в тот же .env:

DATABASE_URL=postgresql://nur:[email protected]:55432/nur

Так локальный Uvicorn на 8000 и контейнер на 8080 работают с одной базой: сохранённые через любой редактор профили доступны новым запросам обоих серверов. При другом пароле или порте измените URL. Для внешней базы в контейнере задайте отдельный DOCKER_DATABASE_URL. Подробнее — в руководстве по каталогу.

Проверка для жюри

Для проверки без расходов установите MATCHER_USE_LLM=false и CATALOG_BACKEND=csv, затем запустите приложение по инструкции выше. Для всех сценариев используйте Алматы, дату 2026-09-26, язык «Любой» и пустую длительность. Это фиксированная дата демонстрации, а не рекомендация выбрать ближайший день.

СценарийКатегорияФорматБюджетОжидаемый результат на текущем CSV
Основной подборВедущийкорпоратив1 000 000 ₸matched, 3 карточки
Редкая категорияФлористсвадьба2 000 000 ₸matched, 1 карточка и причина, почему меньше трёх
Никто не проходит условияВедущийкорпоратив1 ₸no_matches, причины отказов и проверенная подсказка по бюджету
Категории нет в городеНесуществующая категориякорпоратив1 000 000 ₸no_category_in_city, текстовое объяснение

Первые три сценария можно повторить на /selection: заполните поля и нажмите «Подобрать людей». Результат появится в соседнем блоке, на мобильном — ниже формы. Четвёртый выполняется через Swagger UI: список категорий в форме состоит только из значений каталога.

Для проверки порядка повторите основной запрос. Ожидаемые id карточек: HK-44733 → HK-29829 → HK-88430.

Пример тела POST /match для Swagger UI:

{
  "city": "Алматы",
  "date": "2026-09-26",
  "event_type": "корпоратив",
  "category": "Ведущий",
  "budget": 1000000
}

Автоматическая проверка в отдельном тестовом контейнере:

docker compose run --build --rm tests

Основной набор содержит 62 теста: фильтры, три бизнес-исхода, стабильный порядок, подсказки, валидация API, проверка ответов модели, кеш, дедлайн и конкурентные запросы. Тесты используют подставные LLM-клиенты и не требуют ключа OpenAI; они не подтверждают качество живой модели или работу под нагрузкой. Ещё 3 интеграционных теста проверяют реальную PostgreSQL, 14 браузерных сценариев — интерфейс в обоих режимах. Команды запуска описаны в руководстве по проверкам.

Технологии

КомпонентРеализация
БэкендPython 3.10+, FastAPI, Uvicorn
Контракты и валидация APIPydantic v2
КаталогCSV / PostgreSQL 16, Psycopg 3, модели dataclasses
AIOpenAI Python SDK, Chat Completions API, gpt-4o-mini, temperature=0.0
НастройкиПеременные окружения, python-dotenv
ФронтендHTML, CSS, JavaScript без сборщика и UI-фреймворка
Тестыunittest, FastAPI TestClient, HTTPX, Playwright
ЗапускDocker, Docker Compose; Python 3.12 в контейнере

Зависимости указаны в requirements.txt и requirements-dev.txt.

Архитектура

Проект организован как модульный монолит: фронтенд, API и каталог поставляются одним приложением. PostgreSQL подключается как опциональный сервис хранения. Redis и отдельный AI-сервис не используются.

flowchart TD
    Home["Главная страница"] --> UI["/selection: форма и карточки"]
    UI --> API["FastAPI: проверка запроса"]
    CSV["CSV: 66 исходных профилей"] --> Catalog["Интерфейс каталога"]
    PG["PostgreSQL: редактируемые профили"] --> Catalog
    Editor["Защищённый редактор"] --> PG
    Catalog --> Match["Python: фильтры и TOP-3"]
    API --> Match
    Match --> Empty["Пустой результат: аналитика Python"]
    Match --> Explain["Объяснения: кеш и дедлайн"]
    Explain --> AI["OpenAI: выбор цитат, опционально"]
    Explain --> Facts["Python: проверка и сборка текста"]
    AI --> Facts
    Empty --> UI
    Facts --> UI
app/
├── api/             # HTTP-маршруты, схемы, ошибки и отсчёт дедлайна
├── domain/          # Модель подрядчика и проверка значений
├── repositories/    # Единый контракт, CSV и PostgreSQL
├── services/        # Подбор, подсказки, объяснения, кеш и OpenAI
├── config.py        # Пути и настройки процесса
└── cli.py           # Три демонстрационных сценария
frontend/
├── index.html       # Главная страница
├── selection.html   # Форма подбора и результаты
├── home.js          # Поведение главной страницы
├── about.js         # Общее окно «Как это работает»
├── app.js           # Форма и запросы к API
├── cards.js         # Карточки результатов
└── admin.html       # Редактор; рядом его JS/CSS, общие стили и утилиты
data/                # Исходный каталог
tests/               # Быстрые тесты без внешних сервисов
integration/         # Тесты реальной PostgreSQL
e2e/                 # Браузерные сценарии
docs/                # API, архитектура и настройки
api.py               # Совместимая точка входа uvicorn api:app
smart_matcher.py     # Совместимый импорт EventMatcher и запуск демо
schemas.py           # Совместимые импорты HTTP-схем
Dockerfile           # Образы приложения и тестов
compose.yaml         # Локальный запуск контейнеров
.env.example         # Образец настроек без рабочего ключа

Корневые Python-файлы сохраняют прежние точки входа. Основная реализация находится в app/. Подробнее о границах модулей, фильтрах и AI — в архитектуре.

Локальная разработка

Нужен Python 3.10 или новее. Команды выполняются из корня проекта. Сначала заполните .env по инструкции выше: true включает OpenAI, false отключает. Для первого локального запуска оставьте CATALOG_BACKEND=csv.

Windows / PowerShell:

python -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements-dev.txt
Remove-Item Env:MATCHER_USE_LLM -ErrorAction SilentlyContinue
.\.venv\Scripts\python.exe -m uvicorn app.api.application:app --reload --port 8000

Linux / macOS / Bash:

python3 -m venv .venv
.venv/bin/python -m pip install -r requirements-dev.txt
unset MATCHER_USE_LLM
.venv/bin/python -m uvicorn app.api.application:app --reload --port 8000

Главная доступна на localhost:8000, форма — на localhost:8000/selection, редактор — на localhost:8000/admin, Swagger — на localhost:8000/docs. Сборка фронтенда не требуется.

В другом терминале запустите тесты и консольное демо:

.\.venv\Scripts\python.exe -m unittest -v
.\.venv\Scripts\python.exe smart_matcher.py --offline

На Linux / macOS замените .\.venv\Scripts\python.exe на .venv/bin/python. CLI демонстрирует плотную категорию, редкую категорию и пустой подбор с аналитикой.

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

Источник — предоставленный для хакатона анонимизированный CSV с 66 профилями и вымышленными именами. Он содержит id, anon_name, categories, city, price_from_kzt, event_formats, languages, max_hours, busy_dates, description. Списковые значения разделены символом |. В CSV-режиме каталог загружается при старте; в PostgreSQL-режиме каждый подбор читает свежий снимок из базы.

Единственная внешняя AI-интеграция — OpenAI. При включённой LLM ей передаются параметры заказа, до трёх выбранных профилей и варианты цитат. Ключ хранится на сервере, .env исключён из Git и Docker-образа. Без OpenAI подбор и объяснения на Python доступны полностью.

Ограничения

  • Цена «от» — нижняя граница, а не окончательная смета. Отсутствие занятой даты в каталоге не подтверждает бронь.
  • Нет бронирования, оплаты, пользовательских аккаунтов и синхронизации с календарями подрядчиков. Редактор защищён общим ключом; отдельных ролей и журнала изменений пока нет.
  • CSV статический: после его изменения нужен перезапуск, для Docker — пересборка. Редактор PostgreSQL позволяет добавлять и изменять профили; удаления и автоматического обратного экспорта в CSV нет.
  • Ранжирование учитывает заданные условия и цену; семантического поиска по всему каталогу нет. Цитаты проверяются по выбранному каталогу, достоверность заявлений подрядчика независимо не проверяется.
  • Состав и порядок карточек стабильны; выбор цитаты живой моделью может меняться после истечения кеша.
  • По умолчанию бюджет обработки — 8 секунд, SDK-таймаут — до 7 секунд. При истечении ожидания используются объяснения Python. Это не гарантия сетевого времени ответа или SLA под нагрузкой.
  • Кеш и лимит LLM-вызовов локальны для процесса. Общего распределённого кеша и долговременного хранения метрик нет.
  • Подсказки проверяют изменение одного условия; поиск новой даты ограничен следующими 14 днями.

Развёрнутая версия

Публичный адрес развёрнутой версии в репозитории не указан. Проверить решение можно локально через Docker по инструкции выше; localhost не является публичным демо.

Документация

ДокументСодержание
APIМаршруты, запросы, поля ответа, статусы и ошибки
АрхитектураЧетыре этапа, детерминизм, проверка цитат, дедлайн и кеш
Настройки и запуск.env, подключение OpenAI, Docker и устранение проблем запуска
Каталог и редакторВыбор CSV / PostgreSQL, первоначальная загрузка, редактор и версии записей
Локальные тестыПроверки Python, PostgreSQL, браузера и Docker
НаблюдаемостьРежим OpenAI, агрегаты, время ответов и журнал запросов
Звёзды
⭐ 0
Форки
0
Язык
Python
Последний push
в прошлом месяце
Коммиты
8
Авторы
1
Файлы
64
Ветки
1

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

paind 8

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

  • 2026-09-23 17:00deleted CIpaind
  • 2026-09-23 16:56final version v1.0paind
  • 2026-09-23 16:50upd readmepaind
  • 2026-09-23 15:26first demo versionpaind
  • 2026-09-23 14:46upd readmepaind
  • 2026-09-23 14:42upd. readme.md filepaind
  • 2026-09-23 13:55initpaind
  • 2026-09-23 13:53initpaind
Структура репозитория (64 файлов)
.dockerignore
.env.example
.gitignore
Dockerfile
README.md
api.py
app/__init__.py
app/api/__init__.py
app/api/admin.py
app/api/application.py
app/api/deadline.py
app/api/errors.py
app/api/schemas.py
app/cli.py
app/config.py
app/domain/__init__.py
app/domain/models.py
app/domain/validation.py
app/observability.py
app/repositories/__init__.py
app/repositories/catalog.py
app/repositories/postgres.py
app/repositories/source.py
app/services/__init__.py
app/services/cache.py
app/services/cards.py
app/services/evidence.py
app/services/explanations.py
app/services/matcher.py
app/services/prompts.py
app/services/provider.py
compose.yaml
data/hackathon dataset anonymized .csv
docs/api.md
docs/architecture.md
docs/catalog.md
docs/configuration.md
docs/observability.md
docs/testing.md
e2e/test_browser.py
frontend/about.js
frontend/admin.css
frontend/admin.html
frontend/admin.js
frontend/app.js
frontend/cards.js
frontend/favicon.svg
frontend/home.js
frontend/index.html
frontend/selection.html
frontend/styles.css
frontend/ui.js
integration/test_postgres.py
requirements-dev.txt
requirements-e2e.txt
requirements.txt
schemas.py
smart_matcher.py
tests/__init__.py
tests/support.py
tests/test_api.py
tests/test_explanations.py
tests/test_observability.py
tests/test_smart_matcher.py

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

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

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