AAYMA
RU
Войти

FSP (Fast Search Project)

Сайт и CLI для организаторов мероприятий: строгая фильтрация подрядчиков по городу, категории, дате, формату и бюджету, смысловое ранжирование по…

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

О проекте

Команда: Larp
Кейс HackAlem.ai: Креативные индустрии — Firebird
Стадия: Рабочий MVP

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

Суть

Сайт и CLI для организаторов мероприятий: строгая фильтрация подрядчиков по городу, категории, дате, формату и бюджету, смысловое ранжирование по пожеланиям локальной нейросетью, до трёх карточек с объяснениями, а при пустой выдаче — подсказки изменить бюджет или дату.

Как устроено

Обязательные фильтры применяются к SQLite-каталогу. Пожелания и фрагменты описаний (до 128 токенов) переводятся в 384-мерные эмбеддинги локальной моделью paraphrase-multilingual-MiniLM-L12-v2 в квантованном ONNX на CPU. Оценка = 0.7×максимальная + 0.3×средняя косинусная близость фрагментов, сортировка сначала по числу противоречий пожеланиям. Объяснения собираются из полей каталога и цитат, генеративная LLM не используется. Отдельный сервер каталога поддерживает ETag/304, кэш и импорт CSV/JSONL с резервными копиями.

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

  • sentence embeddings
  • косинусная близость по фрагментам
  • ONNX Runtime с квантованием
  • проверка противоречий пожеланиям
  • предложения изменить бюджет или дату
  • ETag-кэширование
  • SQLite-каталог с бэкапами

Особенности

Полностью локальный семантический поиск без API-ключей: квантованная мультиязычная MiniLM в ONNX на CPU, ревизия модели закреплена с SHA-256 в config/model.lock.json. Отдельно проверяются русскоязычные пожелания о конкурсах и играх: подтверждение, противоречие или отсутствие подтверждения.

Стек

Python · http.server · SQLite · NumPy · ONNX Runtime · Hugging Face Tokenizers · HTML/CSS/JS · unittest

Модели

paraphrase-multilingual-MiniLM-L12-v2


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

README

README.md

FSP (Fast Search Project) — умный подбор подрядчиков

Проект команды Larp для организаторов мероприятий. Помогает составить короткий список ведущих, площадок и других подрядчиков: проверяет дату, бюджет и обязательные требования, сравнивает описания с пожеланиями и показывает до трёх вариантов с объяснениями.

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

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

  • Веб-сайт с главной страницей, формой подбора и просмотром каталога.
  • Консольный интерфейс, запросы из JSON и машиночитаемый JSON-ответ.
  • Строгая фильтрация по городу, категории, дате, формату мероприятия и бюджету; по языку и длительности, если они указаны.
  • Смысловое ранжирование описаний готовой нейросетью по свободным пожеланиям.
  • До трёх карточек со стартовой ценой, объяснением и фрагментом исходного описания.
  • Предупреждения о синтетических профилях, проставленных при подготовке данных ценах и городах. Синтетические профили можно исключить из поиска и отображения каталога.
  • Проверка отдельных русскоязычных пожеланий о конкурсах и играх: подтверждение в описании, противоречие или отсутствие подтверждения.
  • Отдельные предложения изменения бюджета или даты; дополнительный бюджет для большего выбора; совместное изменение даты и бюджета, если одиночных предложений нет.
  • SQLite-каталог, импорт CSV/JSONL с резервным копированием, восстановление из резервной копии.
  • Кэширование, HTTP API, валидация запросов, ограничение нагрузки и настраиваемый CORS.

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

  1. Пользователь вводит город, дату, формат мероприятия, категорию и бюджет. Необязательно указывает часы, язык и пожелания.
  2. Система нормализует ввод и исключает записи, не соответствующие обязательным полям. Текст описания не переопределяет занятость, цену или другие структурированные ограничения.
  3. Нейросеть переводит пожелания и фрагменты описаний прошедших кандидатов в векторы. Если пожеланий нет, используется контекст категории и формата.
  4. Кандидаты сортируются по числу выявленных противоречий пожеланиям, затем по смысловой близости. При равенстве учитываются цена и ID.
  5. Пользователь видит до трёх карточек, причины выбора и предупреждения. Объяснение составляется из полей каталога и исходного фрагмента описания.
  6. При пустом результате выводятся причины и доступные предложения. Исходные карточки остаются пустыми; изменённый запрос выполняется только после выбора пользователя.

Пожелания — мягкие предпочтения: прохождение обязательных фильтров не означает, что описание подтверждает каждое пожелание.

Где используется ИИ

Модель sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 выполняется локально в квантованном ONNX-формате на CPU. Дообучение в проекте не выполняется.

Длинные описания разбиваются на фрагменты до 128 токенов; модель строит 384-мерные эмбеддинги. Оценка кандидата:

0.7 × максимальная косинусная близость фрагмента
+ 0.3 × средняя косинусная близость фрагментов

Коэффициенты — инженерное правило. semantic_score показывает близость смысла, а не вероятность, качество услуг или гарантию выполнения требований. Генеративная LLM не используется; факты и цитаты берутся из каталога.

Технологии

Часть решенияТехнологии
Сервер и консольPython 3.11–3.13, 64-bit
HTTP-сервисыСтандартная библиотека Python: http.server, urllib, потоки
ИнтерфейсHTML, CSS, JavaScript, Fetch API; отдельная сборка не нужна
ХранениеSQLite через sqlite3, CSV/JSONL для импорта
ВычисленияNumPy 2.3.5
Локальный инференсONNX Runtime 1.23.2, CPU
ТокенизацияHugging Face Tokenizers 0.22.1
AI-модельMultilingual MiniLM L12 v2
Проверкиunittest, тесты HTTP-сервисов и реальной модели

Зависимости: requirements.txt. Ревизия модели и SHA-256: config/model.lock.json.

Hugging Face используется для первоначального скачивания модели. После подготовки подбор работает локально, без API-ключей и внешнего API инференса.

Архитектура проекта

flowchart LR
    Browser[Браузер] --> Web[Сайт и API: порт 8000]
    Web --> Engine[Фильтры и ранжирование]
    Web -->|HTTP| Catalog[Сервер каталога: порт 8001]
    Catalog --> DB[(SQLite)]
    CLI[Консоль main.py] --> DB
    CLI --> Engine
    Engine --> Model[Локальная ONNX-модель]
    Engine --> Cache[Кэш]
    CSV[CSV / JSONL] -->|Инициализация или импорт| DB

run_web.py запускает оба сервиса, проверяет свободные порты и готовность процессов, затем открывает браузер. Веб-сервер предварительно загружает два экземпляра модели. При остановке через Ctrl+C завершаются дочерние процессы.

ПутьНазначение
main.py, run_web.pyЗапуск консоли и сайта
hackalem/catalog.pyСхема данных, нормализация, обязательные фильтры
hackalem/catalog_store.pySQLite, импорт и резервные копии
hackalem/catalog_server.py, hackalem/catalog_client.pyHTTP-доступ к каталогу, ETag и ответы 304
hackalem/recommender.pyРанжирование, карточки, предложения изменения условий
hackalem/ai_model.py, hackalem/preferences.pyМодель, эмбеддинги и проверка пожеланий
hackalem/web_server.py, hackalem/cli.py, hackalem/launcher.pyAPI, консоль и управление процессами
web/Страницы, стили и общие части интерфейса
data/catalog.csvПоставляемый каталог
config/, models/, cache/Конфигурация, загружаемая модель и кэш
examples/, tests/, scripts/Примеры, тесты и служебные команды
docs/, reports/Дополнительная документация и исторические отчёты

Установка и запуск

Нужны Python 3.11–3.13 64-bit, Git, интернет при первой установке и свободные порты 8000 и 8001. GPU не требуется. Целевая конфигурация — Windows/Linux x86-64; работа на ARM отдельно не подтверждена.

Веса и токенизатор занимают около 127 МБ суммарно, библиотеки требуют дополнительного места и трафика. Модель, локальные окружения, база и кэш не включены в Git.

Windows / PowerShell

Клонируйте проект:

git clone https://github.com/BAITC-Hacks/hack-ee0863ec-larp.git
cd hack-ee0863ec-larp

Из корня проекта создайте окружение и подготовьте модель:

py -3.13 -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
.\.venv\Scripts\python.exe main.py --prepare

Если py недоступен, используйте python -m venv .venv, предварительно проверив поддерживаемую версию командой python --version. Активировать окружение не обязательно.

Запустите сайт:

.\.venv\Scripts\python.exe run_web.py

Главная страница: http://127.0.0.1:8000. Форма подбора: http://127.0.0.1:8000/search. Остановка — Ctrl+C в терминале запуска. Флаг --no-browser отключает автоматическое открытие браузера.

Для консоли:

.\.venv\Scripts\python.exe main.py --offline

Альтернатива ручной установке для консольного режима — scripts/run_windows.bat: создаёт окружение, устанавливает зависимости, подготавливает модель и запускает консоль.

Linux

После клонирования, из корня проекта с установленным Python поддерживаемой версии:

python3 -m venv .venv
.venv/bin/python -m pip install -r requirements.txt
.venv/bin/python main.py --prepare
.venv/bin/python run_web.py

Частые проблемы

  • Нет модели: выполните main.py --prepare с доступом к Hugging Face. Веб-сервер сам веса не скачивает.
  • Порт занят: остановите ранее запущенный экземпляр проекта.
  • Каталог недоступен: запускайте run_web.py, который поднимает оба сервиса.
  • Изменённый CSV не виден на сайте: импортируйте его в SQLite по инструкции ниже. Перезапуска сайта недостаточно для обновления уже заполненной базы.

Как проверить решение

Сценарий для жюри

  1. Установите зависимости и подготовьте модель.
  2. Для уже существующей базы при необходимости импортируйте поставляемый data/catalog.csv по инструкции ниже. Импорт заменяет каталог с сохранением резервной копии.
  3. Запустите сайт, откройте /search, оставьте включёнными учебные профили.
  4. Заполните форму параметрами из таблицы и нажмите «Подобрать подрядчиков».
  5. Изучите карточки и раскрываемые объяснения, проверьте предупреждения.
ПолеЗначение
ГородАлматы
КатегорияВедущий
Дата14.11.2026
Форматкорпоратив
Бюджет1 200 000 ₸
Длительность6 часов
Языкрусский
ПожеланияИнтеллигентный юмор, импровизация, общение с гостями без банальных конкурсов

Для текущего поставляемого CSV обязательные условия проходят 5 профилей, показываются 3 карточки. Числа зависят от каталога и включения синтетических записей.

Тот же сценарий напрямую по файлу, независимо от состояния SQLite:

.\.venv\Scripts\python.exe main.py --catalog data/catalog.csv --query examples/dense.json --offline --json

Проверка пустого результата

ПримерОжидаемый результат по текущему CSV
examples/budget.jsonИсходный бюджет 100 000 ₸ не даёт совпадений. Минимальное предложение — 800 000 ₸; дополнительное для большего выбора — 1 000 000 ₸
examples/venue_busy.jsonНа 14.11.2026 совпадений нет; изменение только даты на 15.11.2026 допускает HK-90012
examples/venue_free.jsonНа 15.11.2026 проходит один профиль HK-90012
.\.venv\Scripts\python.exe main.py --catalog data/catalog.csv --query examples/budget.json --offline --json
.\.venv\Scripts\python.exe main.py --catalog data/catalog.csv --query examples/venue_busy.json --offline --json
.\.venv\Scripts\python.exe main.py --catalog data/catalog.csv --query examples/venue_free.json --offline --json

У первых двух запросов cards остаётся пустым, а варианты находятся в suggestions. В веб-интерфейсе они применяются кнопкой, в интерактивной консоли — выбором номера. Для JSON-режима нужен отдельный запрос с выбранными изменениями. Предупреждения о синтетическом профиле площадки и неподтверждённой цене сохраняются.

Тесты и повторяемость

При проверке 23.09.2026 на Windows с Python 3.13.7 прошли 94 теста, включая проверки реальной модели (RUN_AI_TESTS=1). Скрипт проверки повторяемости вернул PASS на локальной SQLite-базе. Это проверка корректности и повторяемости, а не оценка качества рекомендаций людьми.

Базовый набор; интеграционные проверки реальной модели по умолчанию пропускаются:

.\.venv\Scripts\python.exe -m unittest discover -s tests -v

После подготовки модели включите её интеграционные проверки:

$env:RUN_AI_TESTS="1"
.\.venv\Scripts\python.exe -m unittest discover -s tests -v
Remove-Item Env:RUN_AI_TESTS

На Linux эквивалент: RUN_AI_TESTS=1 .venv/bin/python -m unittest discover -s tests -v.

Проверка двух независимых запусков с настоящей моделью без постоянного кэша:

.\.venv\Scripts\python.exe scripts/check_determinism.py

Скрипт использует общую SQLite-базу. Результат PASS означает совпадение состава, порядка, оценок и объяснений между двумя процессами в текущем окружении.

Часть регрессионных тестов использует фиксированный исходный каталог из 66 записей в tests/fixtures/original_catalog.csv. Расширенный каталог из 166 профилей проверяется отдельно в tests/test_merged_catalog.py. Исторические логи в reports/ не следует считать результатом проверки текущей версии: для этого выполняйте команды выше.

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

Каталог и хранение

Источник подбора — data/catalog.csv. Текущий файл содержит 166 профилей, 17 категорий, значения города Алматы, Астана, Зарубежье. В него входят 100 добавленных профилей с ID SYN-2026-*.

ФлагКоличество записейЗначение
synthetic113Синтетический учебный профиль
price_imputed118Цена проставлена при подготовке данных
city_imputed108Город проставлен при подготовке данных

Флаги пересекаются. Синтетические записи не подтверждают существование реального подрядчика. Текстовые описания — сведения из профилей, а не независимая проверка услуг.

Календарь известен только на 23.09.2026–31.12.2026. Цена price_from_kzt — стартовая цена за мероприятие в тенге, не почасовая ставка. max_hours = null означает неприменимость ограничения длительности присутствия.

При первом запуске CSV заполняет data/catalog.sqlite3. Сайт и консоль по умолчанию используют эту базу. Для обновления:

.\.venv\Scripts\python.exe -m scripts.import_catalog data/catalog.csv

Импорт поддерживает CSV/JSONL с той же схемой, проверяет записи и полностью заменяет каталог. Перед заменой непустой базы сохраняется резервная копия в data/backups/. Сайт увидит обновление при следующем запросе, консоль — при следующем запуске.

Для восстановления укажите существующий файл резервной копии вместо имени в примере:

.\.venv\Scripts\python.exe -m scripts.restore_catalog data/backups/catalog-TIMESTAMP.sqlite3

Восстановление также сохраняет предыдущую непустую базу. Резервные копии исключены из Git; их хранение и очистка выполняются вручную.

API

СервисМетод и путьНазначение
Сайт, порт 8000GET /api/optionsЗначения полей формы
Сайт, порт 8000GET /api/catalogКаталог профилей
Сайт, порт 8000POST /api/recommendПодбор
Сайт, порт 8000GET /healthГотовность сервиса
Каталог, порт 8001GET /contractorsКаталог с ETag
Каталог, порт 8001GET /healthСостояние сервера каталога

POST /api/recommend принимает JSON с заголовком Content-Type: application/json. Обязательные поля: city, event_date, event_format, category, budget_kzt. Необязательные: hours, language, preferences, include_synthetic (по умолчанию true). Образец — examples/dense.json.

Ответ содержит статус found, no_matches или no_category_in_city, исходный нормализованный запрос, карточки, причины исключения, предложения и сведения о модели. Для одиночного предложения изменение задано через field и suggested_value, для совместного — через объект changes.

Можно разрешить конкретный адрес отдельного фронтенда:

.\.venv\Scripts\python.exe run_web.py --allow-origin http://localhost:5173

Подробности: сайт и API. Источники модели описаны в docs/SOURCES.md; сведения там об исходном CSV относятся к историческому набору, а не к текущему расширенному файлу.

Ограничения

  • Локальный учебный прототип: нет бронирования, оплаты, переписки, личных кабинетов и редактирования каталога через сайт.
  • Нет интеграции с внешними календарями и подтверждёнными контактами. Занятость, окончательную стоимость и услуги нужно согласовывать отдельно.
  • Проверка отрицаний ограничена отдельными формулировками о конкурсах и играх. Смысловая близость не доказывает выполнение пожелания; семантический порог отсечения не применяется.
  • Предложения меняют бюджет, дату или оба поля; остальные обязательные требования сохраняются. Поиск даты ограничен известным календарём, при равном расстоянии выбирается более поздний день.
  • При пустом результате ранжирование моделью не выполняется, но веб-сервер всё равно требует подготовленных весов для старта.
  • По умолчанию обрабатываются два подбора одновременно, лимит — 30 запросов подбора в минуту с одного IP. При перегрузке API возвращает HTTP 429. Две копии модели требуют дополнительной памяти.
  • Повторяемость рассчитана на неизменные данные, код, модель и окружение. После смены CPU или библиотек численная идентичность не гарантируется.
  • Подтверждённой метрики качества по человеческой разметке пока нет. Есть методика nDCG@3 и scripts/evaluate_ranking.py, но технические тесты не доказывают полезность рекомендаций для пользователей.

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

Публичная deployed-ссылка в репозитории не указана. Для демонстрации используйте локальный запуск: http://127.0.0.1:8000. Адрес доступен на компьютере, где работает проект. Публикация в интернете требует отдельного развёртывания.

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

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

TheTohackNow 6Islambek 5Akyl 1

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

  • 2026-09-23 17:17mmTheTohackNow
  • 2026-09-23 17:13readmeIslambek
  • 2026-09-23 16:48rIslambek
  • 2026-09-23 16:47readme changesIslambek
  • 2026-09-23 16:46mTheTohackNow
  • 2026-09-23 16:22rTheTohackNow
  • 2026-09-23 15:49Add verified budget and date suggestions for empty resultsIslambek
  • 2026-09-23 15:30Fix preference ranking and add validated frontend CORS supportAkyl
Все коммиты (12)
  • 2026-09-23 15:01Organize project, add homepage, and improve service reliabilityTheTohackNow
  • 2026-09-23 14:22Merge remote main and preserve team and project documentationTheTohackNow
  • 2026-09-23 14:19Add HackAlem AI recommendations with catalog server and web UITheTohackNow
  • 2026-09-23 13:01islam 1Islambek
Структура репозитория (60 файлов)
.gitignore
README.md
cache/README.txt
config/model.lock.json
data/catalog.csv
docs/ARCHIVE_MERGE.md
docs/EVALUATION.md
docs/SOURCES.md
docs/WEB_README.md
examples/budget.json
examples/busy.json
examples/dense.json
examples/no_category.json
examples/other_date.json
examples/rare.json
examples/venue_busy.json
examples/venue_free.json
hackalem/__init__.py
hackalem/ai_model.py
hackalem/cache_store.py
hackalem/catalog.py
hackalem/catalog_client.py
hackalem/catalog_server.py
hackalem/catalog_store.py
hackalem/cli.py
hackalem/cors.py
hackalem/http_common.py
hackalem/launcher.py
hackalem/preferences.py
hackalem/recommender.py
hackalem/web_server.py
main.py
models/README.txt
reports/test_results.txt
reports/validation_report.json
requirements.txt
run_web.py
scripts/__init__.py
scripts/check_determinism.py
scripts/evaluate_ranking.py
scripts/import_catalog.py
scripts/restore_catalog.py
scripts/run_windows.bat
tests/__init__.py
tests/fixtures/original_catalog.csv
tests/test_ai_integration.py
tests/test_core.py
tests/test_cors.py
tests/test_merged_catalog.py
tests/test_openai_integration.py
tests/test_preferences.py
tests/test_reliability.py
tests/test_review_fixes.py
tests/test_suggestions.py
tests/test_web.py
web/home.html
web/index.html
web/partials/footer.html
web/partials/header.html
web/site.css

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

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

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