AAYMA
RU
Войти

Tiramisu

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

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

О проекте

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

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

Суть

Сервис объяснимого подбора до 3 event-подрядчиков: фильтрация по дате, бюджету, формату, языку и длительности, ранжирование и карточки с доказательствами (evidence).

Как устроено

Python-сервер на стандартной библиотеке загружает и валидирует JSONL-каталог, строго фильтрует кандидатов и ранжирует их. Используется заранее подготовленный снимок OpenAI-эмбеддингов (text-embedding-3-small), а без него работает детерминированный лексический fallback. Порядок: сходство, затем цена, затем ID. explanations.py подбирает цитату из описания профиля и сравнивает начальные цены. API возвращает outcome (recommended / no_category_in_city / no_eligible_candidates), rejection_counts и evidence по каждой карточке.

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

  • OpenAI embeddings snapshot
  • lexical fallback ranking
  • rule-based filtering
  • quote extraction for explanations
  • deterministic demo catalog generation

Особенности

Эмбеддинги считаются офлайн отдельным скриптом и проверяются verify_embeddings, поэтому обычный HTTP-запрос не обращается к OpenAI и порядок выдачи воспроизводим. Есть манифест демо-сценариев с ожидаемыми ID, по которому сверяются тесты. Каталог (66 профилей) честно помечен как синтетический.

Стек

Python · HTML · CSS · JavaScript · JSONL · unittest · Node.js test

Модели

text-embedding-3-small


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

README

README.md

Tiramisu — объяснимый подбор event-подрядчиков

1. Проблема и назначение

Заказчик уже видит каталог подрядчиков в своём городе, но ему трудно быстро понять, кто свободен и подходит под конкретный формат, бюджет и дату. Tiramisu отбирает до трёх вариантов из этого каталога и для каждого показывает проверяемые основания рекомендации.

Проект предназначен для заказчика event-услуг и для жюри хакатона, которое проверяет подбор вживую.

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

  • Вход: город, дата, формат события, категория, бюджет; язык и длительность необязательны.
  • Исключение профилей, занятых на выбранную дату, а также не проходящих бюджет, формат, язык или длительность.
  • До трёх карточек в фиксированном порядке. В карточке есть цена, город, категория, объяснение и список фактов, на которых оно основано.
  • Три явных исхода: рекомендации найдены; категории нет в городе; кандидаты есть, но ни один не прошёл условия.
  • Отдельное сообщение о числе занятых на выбранную дату и о числе подходящих кандидатов, если их больше трёх.
  • Метка «Синтетический профиль» для каждой искусственно созданной записи.
  • Демонстрационные кнопки используют единый проверяемый манифест сценариев, только когда активен синтетический fallback-каталог.

Бронирование, заявки и уведомления подрядчикам не входят в проект.

3. Как работает пользовательский сценарий

  1. Пользователь заполняет параметры события или выбирает демонстрационный сценарий.
  2. Сервер ищет профили нужной категории в выбранном городе.
  3. Он последовательно проверяет дату, бюджет, формат, а при наличии — язык и длительность.
  4. Оставшиеся профили ранжируются и возвращаются в неизменённом API-порядке.
  5. Интерфейс показывает максимум три карточки, условия отбора и причину, почему вариантов меньше трёх либо почему результат пуст.

Карточки относятся к фактически отправленному запросу. Если пользователь изменил форму после отправки, интерфейс явно показывает параметры запроса, по которому были получены карточки.

Объяснение состоит из 1–2 предложений. Первое подтверждает условия заказа, второе выделяет конкретную деталь программы, съёмки или площадки из описания. Правила выбора фрагмента отдают приоритет деталям услуги и исключают предложения с явным указанием другого формата. Цитата сохраняется целиком; если подходящего фрагмента нет, дополнительное преимущество не выдумывается.

Сравнение цены относится только к показанным карточкам: «самая низкая начальная цена среди 3 показанных вариантов» появляется лишь при строго меньшей цене, без равенства с другими вариантами.

4. Архитектура

browser: frontend/index.html + app.js
             |
             | HTTP
             v
backend/server.py
             |
             v
backend/recommendation_service.py
   ├─ загрузка и валидация JSONL
   ├─ фильтрация условий
   ├─ ranking.py: готовый embedding-снимок или lexical fallback
   └─ explanations.py: выбор цитаты и сравнение начальной цены

Источники каталога:

  • Если есть backend/data/hackathon-dataset-anonymized.jsonl, загружается он и, при наличии, backend/data/synthetic-additions.jsonl.
  • Если исходного файла нет, используется только backend/data/profiles.jsonl — текущий демонстрационный каталог.

Исходные строки не меняются. Дополнения должны содержать synthetic: true; повторяющиеся ID не допускаются.

5. Технологии

  • Python 3.10+ и стандартная библиотека.
  • HTML, CSS и JavaScript без сборки и фронтенд-фреймворков.
  • JSONL-каталог и встроенная в проект валидация записей.
  • OpenAI Embeddings, модель text-embedding-3-small, только для отдельной подготовки локального снимка embeddings.

Обычный HTTP-запрос рекомендаций не обращается к OpenAI. При старте сервер либо принимает уже проверенный снимок backend/data/embeddings.json, либо выбирает локальный детерминированный lexical fallback.

Порядок: оценка сходства по убыванию, затем цена по возрастанию, затем ID. При неизменных каталоге и снимке embeddings повторный запрос сохраняет порядок, в том числе после перезапуска.

6. API

GET /api/options возвращает значения для формы, границы дат, metadata каталога и текущий режим ранжирования.

GET /api/demo-scenarios возвращает манифест демонстрационных сценариев только для синтетического fallback-каталога.

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

POST /api/recommendations принимает:

{
  "city": "Алматы",
  "date": "2026-10-02",
  "event_type": "день рождения",
  "category": "Ведущий",
  "budget_kzt": 300000,
  "duration_hours": 5,
  "language": "русский"
}

Поля duration_hours и language необязательны. Дата — в формате YYYY-MM-DD, с 2026-09-23 по 2026-12-31 включительно; бюджет — неотрицательное число; длительность — больше 0 и не больше 24 часов. Запрос отправляется с Content-Type: application/json.

В ответе присутствуют:

  • outcome: recommended, no_category_in_city или no_eligible_candidates;
  • message: текстовое объяснение результата, включая пустую выдачу;
  • degraded: true при локальном ранжировании без подготовленных embeddings;
  • results: до трёх карточек;
  • candidate_count и total_eligible;
  • rejection_counts и availability_summary;
  • catalog: источник, версия, количество профилей и число synthetic-профилей;
  • ranking: режим, версия, модель при наличии и статус artifact;
  • evidence у каждой карточки: факты о цене, формате, языке, длительности, доступности и описании.

Факт description добавляется, когда найден подходящий фрагмент описания; comparison — когда начальная цена строго ниже остальных показанных вариантов. Оба используют тот же формат {kind, label, value, source}.

7. Данные и происхождение

В текущем репозитории fallback-каталог содержит 66 профилей, и все они отмечены synthetic: true. Это не выдаётся за исходные данные организаторов.

Каталог воспроизводит нужную плотность для демо: 15 ведущих, 12 фотографов, 8 банкетных залов и редкие категории. Календари созданы детерминированным скриптом scripts/generate_demo_catalog.py: в сентябре–ноябре доля занятости ниже, а в декабре выше, с повышенной занятостью выходных. Скрипт работает только с synthetic fallback и отказывается читать или изменять исходный файл организаторов.

Цена в каталоге интерпретируется как цена «от» за мероприятие. Для двух записей кейтеринга исправлены единицы цены: теперь указана начальная стоимость пакета обслуживания мероприятия. Проверка бюджета сравнивает его с начальной ценой; окончательная смета в каталоге не представлена.

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

Нужен Python 3.10 или новее. Внешние пакеты не требуются.

git clone https://github.com/BAITC-Hacks/hack-d6fe790b-tiramisu.git
cd hack-d6fe790b-tiramisu
python backend/server.py

Откройте локальную страницу. Сервер по умолчанию слушает только локальный адрес и порт 8000. Если порт занят, в PowerShell можно запустить на 8001:

$env:PORT = "8001"
python backend/server.py

В этом случае адрес страницы — http://127.0.0.1:8001. После изменения каталога или подготовки embeddings перезапустите сервер.

Публичная deployed-версия для этого репозитория не заявлена.

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

Запустите автоматические проверки из корня репозитория:

python -m unittest discover -s tests -v

Дополнительно, при установленном Node.js 18+, можно проверить регрессии интерфейса без сторонних пакетов:

node --test tests/frontend.test.cjs

Эти проверки используют упрощённое представление DOM и дополняют проверку в браузере.

HTTP-тесты прогоняют все 8 сценариев в текущем режиме. Точные ID из манифеста сверяются только для локального ранжирования. Для семантического режима отдельно проверяются известный порядок на неодинаковых контрольных векторах, неизменность фильтров и повторяемость после создания нового экземпляра сервиса. Проверки API-контракта OpenAI используют подменённые ответы и не расходуют средства.

Для live-демо после запуска сервера используйте кнопки «Демо-запросы» на странице. Их данные и ожидаемые результаты хранятся в backend/data/demo-scenarios.json.

Три основных сценария из текущего synthetic fallback-манифеста. Указанный порядок ID относится к локальному ранжированию; после подготовки embeddings порядок может измениться, а фильтры и число подходящих остаются теми же:

IDЗапросОжидаемый результат
dense-autumnАлматы, 2026-10-02, день рождения, Ведущий, 500 000 ₸3 карточки: demo-021, demo-026, demo-001; всего подходит 4, заняты 4.
rare-under-threeАлматы, 2026-09-23, свадьба, Декоратор, 500 000 ₸1 карточка: demo-005; всего подходит 1.
no-eligibleАлматы, 2026-09-23, свадьба, Ведущий, 0 ₸Пустой результат; 2 профиля исключены как занятые, остальные не укладываются в бюджет.

В манифесте также есть отсутствие категории в городе, две даты для сравнения доступности, банкетный зал и декабрьская сезонность. Это позволяет проверить:

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

Для ручной проверки API в PowerShell используйте фактический порт сервера. Явная кодировка UTF-8 сохраняет кириллицу и в Windows PowerShell 5.1:

$port = if ($env:PORT) { $env:PORT } else { "8000" }
$body = @{ city = "Алматы"; date = "2026-10-02"; event_type = "день рождения"; category = "Ведущий"; budget_kzt = 500000 } | ConvertTo-Json
Invoke-RestMethod -Method Post -Uri "http://127.0.0.1:$port/api/recommendations" -ContentType "application/json; charset=utf-8" -Body ([System.Text.Encoding]::UTF8.GetBytes($body))

При изменении каталога нужно пересчитать manifest и сверить параметры с тестами до публикации README.

10. Подготовка embeddings

Embedding-снимок создаётся отдельно и не во время запроса пользователя. Перед использованием можно посмотреть объём будущей операции без сети и без записи файлов:

python scripts/prepare_embeddings.py --dry-run

Чтобы ключ был введён скрыто в интерактивном терминале:

python scripts/prepare_embeddings.py --prompt-key

Также скрипт поддерживает ключ из переменной OPENAI_API_KEY текущего процесса. Ключ не передаётся в аргументах командной строки и не записывается в artifact, логи или репозиторий.

Скрипт формирует полный снимок в backend/data/embeddings.json и проверяет модель, версию схемы, векторы и fingerprint текущего каталога. После подготовки нужен перезапуск сервера. Если снимок отсутствует, устарел или невалиден, сервис остаётся детерминированным в lexical-режиме.

Проверить сохранённый снимок без новых запросов к OpenAI:

python scripts/verify_embeddings.py

Команда завершается с кодом 0 только при рабочем семантическом режиме: проверяет все 8 сценариев, одинаковые условия отбора в обоих режимах и повторяемость порядка после перезапуска сервиса. При отсутствии или повреждении снимка возвращается код 2. Качество рекомендаций для людей эта техническая проверка не измеряет.

Формат запроса и ответа интеграции соответствует официальному руководству OpenAI по embeddings.

В репозитории нет готового embedding-снимка, и обращение к реальному OpenAI API в этой версии не подтверждено тестовым прогоном.

11. Ограничения

  • Исходный датасет организаторов пока отсутствует; при его добавлении необходимо заново выбрать и проверить demo-сценарии.
  • Публичного deployment нет.
  • Нет бронирования, заявок, уведомлений и подтверждения доступности после рекомендации.
  • Semantic ranking возможен только с заранее подготовленным и валидным artifact; по умолчанию используется lexical fallback.
  • Продуктовая оценка качества рекомендаций на реальных пользователях не проводилась.

Результаты автоматических проверок и проверки сценариев в браузере описаны в отчёте об отладке. Там же приведены локальные замеры времени ответа и ограничения проверки.

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

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

Beknur 12

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

  • 2026-09-23 17:41Delete ux-researcher.mdBeknur
  • 2026-09-23 17:41Delete debugger.mdBeknur
  • 2026-09-23 17:41Delete code-reviewer.mdBeknur
  • 2026-09-23 17:41Delete directoryBeknur
  • 2026-09-23 17:40Delete docs directoryBeknur
  • 2026-09-23 17:39Improve recommendation explanations and semantic ranking verificationADSKI-KIADS
  • 2026-09-23 17:03Fix recommendation determinism, demo data and regression coverageADSKI-KIADS
  • 2026-09-23 16:26Add complete synthetic vendor catalogADSKI-KIADS
Все коммиты (12)
  • 2026-09-23 16:01Harden catalog recommendations and add testsADSKI-KIADS
  • 2026-09-23 15:30Strengthen recommendation demo scenariosADSKI-KIADS
  • 2026-09-23 14:49Build event vendor recommendation demoADSKI-KIADS
  • 2026-09-23 13:50Add note about GPT Pro application issueBeknur
Структура репозитория (21 файлов)
.gitignore
README.md
backend/data/demo-scenarios.json
backend/data/profiles.jsonl
backend/explanations.py
backend/ranking.py
backend/recommendation_service.py
backend/server.py
frontend/app.js
frontend/index.html
frontend/styles.css
scripts/generate_demo_catalog.py
scripts/prepare_embeddings.py
scripts/verify_embeddings.py
tests/fixtures/invalid_synthetic_additions/hackathon-dataset-anonymized.jsonl
tests/fixtures/invalid_synthetic_additions/synthetic-additions.jsonl
tests/frontend.test.cjs
tests/test_explanations.py
tests/test_http_api.py
tests/test_prepare_embeddings.py
tests/test_recommendation_service.py

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

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

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