Точка события
Сервис подбора до трёх event-подрядчиков по городу, дате, формату, категории и бюджету с проверкой календаря занятости и объяснением каждого выбора…
О проекте
Команда: USB
Кейс HackAlem.ai: Креативные индустрии — Firebird
Стадия: Рабочий MVP
Задача кейса: «#79-lite»: подобрать до 3 подрядчиков для мероприятия и объяснить выбор
Суть
Сервис подбора до трёх event-подрядчиков по городу, дате, формату, категории и бюджету с проверкой календаря занятости и объяснением каждого выбора фактами профиля.
Как устроено
Импортёр валидирует CSV из 66 профилей (Pydantic) и сохраняет в PostgreSQL; FastEmbed/ONNX с paraphrase-multilingual-MiniLM-L12-v2 строит 384-мерные векторы, которые хранятся в Elasticsearch. Запрос ограничивается городом и категорией, гибридный поиск сочетает BM25 и семантическую близость, затем бизнес-правила отсекают занятых, дорогих и несовместимых по формату, языку и длительности. Ранжирование по запасу бюджета и релевантности со стабильным tie-break по id; объяснение собирается из подтверждённых фактов профиля, без вызова LLM.
Ключевые решения
- гибридный поиск BM25 + dense vectors
- FastEmbed ONNX Runtime
- фильтр по календарю занятости
- детерминированное ранжирование
- шаблонные объяснения из фактов
- Redis-кэш с таймаутами
Особенности
Полностью локальный AI без внешних API: мультиязычные эмбеддинги в Docker, а допуск кандидата решают прозрачные правила. Явные статусы category_not_found и no_eligible_candidates, кнопка «Сравнить с другой датой», показ причин исключения и пометка синтетических профилей.
Стек
Python 3.12 · FastAPI · Pydantic · Elasticsearch 8 · PostgreSQL 16 · Redis 7 · Vue 3 · TypeScript · Vite · Tailwind CSS · Reka UI · Docker Compose · pytest · Vitest
Модели
paraphrase-multilingual-MiniLM-L12-v2
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-65c14aea-usb.
README
README.mdТочка события — умный подбор event-подрядчиков
Точка события помогает заказчику выбрать подрядчиков для мероприятия в Казахстане. Вместо длинного каталога сервис возвращает до трёх доступных вариантов и для каждого объясняет решение фактами из профиля.
Пользователь указывает город, дату, формат события, нужную категорию и бюджет. При необходимости добавляет язык и длительность. Сервис проверяет календарь занятости, обязательные условия и смысл описания профиля, после чего показывает короткую, понятную подборку.
Проект создан для хакатона #79-lite. Данные — 66 анонимизированных профилей, в том числе 13 синтетических, которые заметно помечаются в интерфейсе.
Подробные инструкции по частям проекта находятся в backend/README.md и frontend/README.md. Главный README описывает продукт и полный сценарий запуска.
Какую проблему решаем
Каталог подрядчиков сам по себе не отвечает на главный вопрос заказчика: кто реально подходит на мою дату и почему? Поиск превращается в ручное сравнение десятков профилей, а занятость часто выясняется только после обращения к подрядчику.
Сервис сокращает этот путь:
- Не показывает занятого подрядчика.
- Не выдаёт варианты выше заданного бюджета или с неподходящим форматом, языком либо длительностью.
- Возвращает не более трёх кандидатов в стабильном порядке.
- Объясняет каждый результат: доступность, цену, соответствие формату и конкретный факт из описания профиля.
- Если ответа нет, прямо говорит, что произошло: категории нет в городе или кандидаты не проходят условия.
Наши преимущества
Короткая подборка вместо долгого поиска. Обычный каталог отдаёт десятки профилей для ручного сравнения. Мы показываем до трёх кандидатов, которые уже прошли проверку условий.
Календарь учитывается до рекомендации. Пользователь не тратит время на подрядчика, занятого в нужную дату: занятость — обязательный фильтр, а не информация «после звонка».
Понятно, почему выбран именно этот профиль. У каждой карточки есть проверяемое объяснение: доступность, цена, формат, язык, длительность и факт из конкретного описания.
Смысл запроса, а не только совпадение слов. Семантический поиск находит релевантные описания даже при другой формулировке. BM25 дополняет его точными совпадениями.
Стабильный результат. Один и тот же запрос возвращает один и тот же порядок. При одинаковом балле применяется стабильное правило сортировки по id.
AI работает локально. В проект встроен собственный локальный нейросетевой модуль поиска: multilingual embeddings запускаются через FastEmbed и ONNX Runtime внутри Docker. Внешний AI API, ключи и оплата запросов не нужны.
Что видит пользователь
Интерфейс показывает форму, карточки рекомендаций и три подготовленных сценария для демонстрации:
- Плотная категория — фотографы на осеннюю дату: видно ранжирование нескольких кандидатов.
- Редкая категория — флористы: сервис возвращает столько карточек, сколько действительно есть, и объясняет нехватку.
- Без результата — кандидаты существуют, но бюджет намеренно не позволяет выбрать ни одного.
После успешной выдачи можно нажать «Сравнить с 14 ноября» и увидеть, как календарь меняет состав подборки. Отдельно отображаются причины исключения кандидатов и пометка синтетических профилей.
Карточка подрядчика кликабельна: нажмите «Открыть профиль» или любую область карточки. Справа откроется подробный профиль с категорией, городом, стартовой стоимостью, источником данных, причиной рекомендации и параметрами текущего подбора. Профиль можно открыть мышью, клавишей Enter или пробелом и закрыть клавишей Escape.
Быстрый запуск
Нужны:
- Docker Desktop с Docker Compose;
- Node.js 22.12 или новее;
- интернет только при первом Docker build, чтобы скачать локальную модель. При дальнейших запусках внешний AI API не требуется.
1. Запустить API и хранилища одной командой
В PowerShell из корня репозитория:
cd backend
Copy-Item .env.example .env
docker compose up -d --build
В macOS/Linux замените Copy-Item .env.example .env на cp .env.example .env.
Эта команда запускает всю серверную часть: PostgreSQL, Elasticsearch, Redis, импортёр данных и FastAPI. Compose ждёт healthcheck хранилищ, после чего импортёр проверяет CSV, загружает 66 профилей, строит векторы описаний и только затем разрешает старт API.
Дождитесь завершения импорта и проверьте состояние:
docker compose ps
docker compose logs importer
curl.exe http://localhost:8000/ready
У importer должен быть статус exited (0), у api — healthy, а /ready должен вернуть "status":"ready". Документация API доступна на http://localhost:8000/docs.
2. Запустить веб-интерфейс
Откройте второй терминал:
cd frontend
npm ci
Copy-Item .env.example .env
npm run dev
Откройте http://localhost:5173. В режиме разработки Vite сам направляет запросы /api на FastAPI.
3. Остановить сервисы
cd backend
docker compose down
Данные PostgreSQL и Elasticsearch сохраняются в Docker volumes. Чтобы переиндексировать исходный CSV, выполните:
docker compose run --rm importer
Как проверить решение
Через интерфейс
- Откройте
http://localhost:5173. - Нажмите по очереди три кнопки демо-сценариев: «Плотная категория», «Редкая категория», «Без результата».
- В успешной подборке нажмите «Сравнить с 14 ноября».
- Нажмите «Открыть профиль» на любой карточке и проверьте детали подрядчика и параметры текущего подбора.
- Убедитесь, что карточек максимум три, занятые исполнители не показаны, а причины на карточках различаются.
Через API
Пример плотной категории:
curl.exe -X POST http://localhost:8000/api/v1/recommendations `
-H "Content-Type: application/json" `
-d '{"city":"Алматы","event_date":"2026-10-10","event_format":"свадьба","category":"Фотограф","budget_kzt":6000000}'
В ответе важны:
status: "matched";- не более трёх элементов в
items; explanationу каждой карточки;metaс количеством подходящих кандидатов и причинами исключений.
Проверьте и два граничных случая, изменив параметры запроса:
- Категория отсутствует в городе: укажите
category: "Отель"иcity: "Алматы". Ожидаемый статус —category_not_found. - Кандидаты есть, но не подходят: укажите
budget_kzt: 1. Ожидаемый статус —no_eligible_candidates.
Как работает подбор
Форма пользователя
│
▼
FastAPI ──► Redis-кэш ──► Elasticsearch
│
город + категория + BM25 + семантическая близость
▼
фильтры: дата, бюджет, формат, язык, длительность
│
▼
ранжирование → до 3 карточек → персональные объяснения
- Импортёр валидирует исходный CSV через Pydantic и сохраняет каталог в PostgreSQL.
- Встроенный локальный нейросетевой модуль использует
paraphrase-multilingual-MiniLM-L12-v2: FastEmbed запускает ONNX-модель и создаёт для каждого профиля 384-мерный вектор. Вектор хранится в Elasticsearch рядом со структурированными данными. - По запросу система ограничивает поиск городом и категорией, сочетает семантическую близость описания с BM25 и получает кандидатов.
- Бизнес-правила исключают занятые, дорогие и несовместимые профили.
- Оставшиеся кандидаты ранжируются по запасу бюджета и релевантности описания. Порядок фиксируется по
id, если баллы равны. - Объяснение собирается только из подтверждённых фактов конкретного профиля: дата, цена, формат, язык, длительность и релевантный фрагмент описания.
Чтобы не задерживать пользователя при сбоях инфраструктуры, Redis ограничен 250 мс на операцию, Elasticsearch — 4 секундами без повторных попыток, а веб-интерфейс прекращает ожидание через 10 секунд.
Локальная нейросетевая часть
Мы не вызываем LLM или внешний AI API во время подбора. Docker-образ заранее получает open-source multilingual MiniLM, а FastEmbed выполняет инференс через ONNX Runtime внутри контейнера. Модель превращает текст запроса и описания подрядчиков в числовые векторы, чтобы находить смысловую близость на русском, казахском и английском. Решение о допуске кандидата всё равно принимают прозрачные бизнес-правила: дата, бюджет, формат, язык и длительность.
Технологии
- Веб-интерфейс: Vue 3, TypeScript, Vite, Tailwind CSS и Reka UI.
- API: Python 3.12+, FastAPI, Pydantic и Uvicorn.
- Поиск и AI: Elasticsearch 8, BM25, FastEmbed, ONNX Runtime и multilingual MiniLM.
- Данные: PostgreSQL 16, Redis 7 и CSV-импортёр.
- Инфраструктура: Docker Compose.
- Проверки: pytest, Vitest, TypeScript type-check и Vite production build.
Автоматические проверки
Backend:
cd backend
py -3.12 -m venv .venv
.\.venv\Scripts\python -m pip install -r requirements-dev.txt
.\.venv\Scripts\python -m pytest
Frontend:
cd frontend
npm ci
npm test
npm run build
Тесты покрывают занятость, все три бизнес-исхода, детерминированный порядок, влияние другой даты, работу услуг без ограничения по времени, уникальность объяснений, разбор датасета и отображение результатов во frontend.
Структура репозитория
.
├── backend/
│ ├── app/ # FastAPI, поиск, ранжирование, объяснения и embeddings
│ ├── data/ # исходный датасет из 66 профилей
│ ├── scripts/ # импорт и индексация каталога
│ ├── tests/ # backend-проверки
│ └── docker-compose.yml
└── frontend/
├── src/ # Vue-интерфейс и API-клиент
└── tests/ # frontend-проверки
Детали API и серверной части описаны в backend/README.md, детали интерфейса — в frontend/README.md.
Авторы коммитов
История коммитов
- 2026-09-23 17:51complete projectEvgeniy Stepanov
- 2026-09-23 17:40complete work, and write full READMEEnt_Front
- 2026-09-23 17:34add worker's profileEvgeniy Stepanov
- 2026-09-23 17:24changed designEvgeniy Stepanov
- 2026-09-23 15:56add llm and new emb, test service and all backendOdintsov
- 2026-09-23 15:14optimize responseEnt_Front
- 2026-09-23 14:49first commit, frontendEvgeniy Stepanov
- 2026-09-23 14:40add postgresql, optimize responsesOdintsov
Все коммиты (9)
- 2026-09-23 14:14first commit, backend first logicOdintsov
Структура репозитория (71 файлов)
README.md audit-results.json backend/.dockerignore backend/.env.example backend/.gitignore backend/Dockerfile backend/README.md backend/app/__init__.py backend/app/api/__init__.py backend/app/api/routes.py backend/app/core/__init__.py backend/app/core/config.py backend/app/core/text.py backend/app/database/__init__.py backend/app/database/models.py backend/app/main.py backend/app/repositories/__init__.py backend/app/repositories/base.py backend/app/repositories/elasticsearch.py backend/app/repositories/hybrid.py backend/app/repositories/memory.py backend/app/repositories/postgres.py backend/app/schemas/__init__.py backend/app/schemas/contractor.py backend/app/schemas/recommendation.py backend/app/services/__init__.py backend/app/services/cache.py backend/app/services/embeddings.py backend/app/services/explanations.py backend/app/services/recommendations.py backend/data/.gitkeep backend/data/README.md backend/data/hackathon-dataset-anonymized.csv backend/data/sample-contractors.jsonl backend/docker-compose.yml backend/pyproject.toml backend/requirements-dev.txt backend/requirements.txt backend/scripts/__init__.py backend/scripts/import_dataset.py backend/scripts/index_dataset.py backend/tests/conftest.py backend/tests/test_api.py backend/tests/test_data_pipeline.py backend/tests/test_embeddings.py backend/tests/test_index_dataset.py backend/tests/test_recommendations.py frontend/.env.example frontend/.gitignore frontend/README.md frontend/components.json frontend/index.html frontend/package-lock.json frontend/package.json frontend/src/App.vue frontend/src/api/client.ts frontend/src/assets/main.css frontend/src/components/ContractorCard.vue frontend/src/components/ContractorDetail.vue frontend/src/components/KazakhstanMap.vue frontend/src/components/ui/badge/Badge.vue frontend/src/components/ui/badge/index.ts frontend/src/components/ui/button/Button.vue frontend/src/components/ui/button/index.ts frontend/src/composables/useRecommendations.ts frontend/src/lib/utils.ts frontend/src/main.ts frontend/src/types/api.ts frontend/tests/app.test.ts frontend/tsconfig.json frontend/vite.config.ts
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.