NurSolution
Веб-сервис подбора до трёх event-подрядчиков с объяснениями, подсказками при пустом результате, каталогом в CSV или PostgreSQL и админ-редактором.
О проекте
Команда: 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.mdNurSolution
Умный подбор event-подрядчиков в Казахстане — до трёх карточек с объяснением каждого выбора.
Решение хакатон-задачи #79-lite для заказчиков свадеб, тоев, корпоративов и других мероприятий. Когда каталог уже выбран, NurSolution помогает сравнить подходящих подрядчиков: проверяет условия заказа и показывает конкретные факты из профилей. Если никто не подходит, объясняет причины и предлагает проверенные изменения условий.
66 исходных профилей · CSV / PostgreSQL · OpenAI · Docker
Быстрый запуск · Проверка для жюри · Архитектура · Документация
Что реализовано
- Строгая фильтрация по городу, категории, бюджету, занятости на дату и формату мероприятия; дополнительно — по языку и длительности.
- До трёх карточек: факт из профиля, цена «от», сравнение с бюджетом и отметки о совпадении условий. Дата, подробности и источник раскрываются отдельно.
- Детерминированный состав и порядок карточек при неизменных запросе и каталоге.
- Различимые состояния: подрядчики подобраны, категории в городе нет, условия не прошёл никто.
- Счётчики причин отказа и подсказки при пустом подборе: изменить одно условие и проверить результат.
- Работа без ключа OpenAI; при ошибке или таймауте модели объяснения формирует Python.
- Кеш проверенных AI-ответов, объединение одинаковых одновременных запросов и ограничение числа LLM-вызовов.
- Отдельная главная страница и страница подбора с формой и результатами; адаптивное оформление, HTTP API, Swagger UI и автоматические тесты.
- Два источника каталога: CSV или PostgreSQL; защищённый редактор профилей для базы данных.
- Диагностика режима через
/health, агрегаты запросов и OpenAI через/metrics.
Как работает решение
- На главной странице заказчик нажимает «Подобрать подрядчиков» и переходит
на
/selection. Там указывает город, дату, формат мероприятия, категорию и бюджет в тенге и нажимает «Подобрать людей». Язык и длительность в часах необязательны. Страница формы загружает варианты полей из каталога; открытие любой страницы не запускает подбор. - Python проверяет профили из выбранного источника — CSV или PostgreSQL. Занятые на указанную дату и не соответствующие условиям подрядчики исключаются. Каталог не расширяется.
- Подходящие профили сортируются по близости цены к бюджету, затем по совпадению языка
и строковому
id. Выбираются первые три. При строгом фильтре по языку все оставшиеся ему соответствуют. - Если AI включён, модель получает только выбранные профили и выбирает дословные цитаты из описаний. Python проверяет цитаты и собирает итоговые объяснения с ценой и условиями.
- Рядом с формой появляются карточки либо аналитика пустого результата; на мобильном экране результаты располагаются ниже формы. Подсказка применяет одно изменение к форме и запускает новый поиск. При пустой выдаче обращения к 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/docs | Swagger UI: запросы прямо из браузера |
| localhost:8080/health | Готовность API и число загруженных профилей |
Адреса приведены для стандартного порта 8080. Контейнер публикуется только на локальном интерфейсе.
docker compose ps
docker compose logs --tail=100 web
docker compose down
После изменения кода или CSV повторите запуск с --build.
Подключение OpenAI и все переменные окружения описаны в настройках.
Как убедиться, что OpenAI включён
- Откройте
/health: должно быть"llm_enabled": true. Это настройка процесса, а не проверка работоспособности ключа. - Выполните подбор и посмотрите ответ
POST /matchв Network браузера.cards[].explanation_source: "openai"означает проверенный ответ модели, в том числе из кеша. - Если указан
"python", прочитайтеwarning: там причина резервного объяснения. - Чтобы проверить именно новый вызов, перезапустите сервер и выполните один запрос;
кеш после перезапуска пуст. Счётчик
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 |
| Контракты и валидация API | Pydantic v2 |
| Каталог | CSV / PostgreSQL 16, Psycopg 3, модели dataclasses |
| AI | OpenAI 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, агрегаты, время ответов и журнал запросов |
Авторы коммитов
История коммитов
- 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 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.