AAYMA
RU
Войти

Shabyt

Веб-сервис подбора до трёх event-подрядчиков по городу, дате, типу события, категории и бюджету с объяснением каждого выбора.

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

О проекте

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

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

Суть

Веб-сервис подбора до трёх event-подрядчиков по городу, дате, типу события, категории и бюджету с объяснением каждого выбора.

Как устроено

FastAPI загружает JSONL-каталог (66 профилей + 3 синтетических), filters.py отсекает занятых/не укладывающихся в бюджет/формат/язык/длительность, ranking.py детерминированно ранжирует по близости цены к бюджету снизу, бонусам за язык и ключевые слова, tie-break по id. Для топ-3 llm_explain.py генерирует объяснение через OpenAI gpt-4o-mini, иначе локальный шаблонный fallback; различаются состояния ok / no_category / no_match. Есть статический режим для GitHub Pages.

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

  • жёсткие фильтры
  • детерминированное скоринговое ранжирование
  • LLM-объяснения с fallback

Особенности

Разделение пустой выдачи на no_category и no_match с причинами исключения; статический клиентский режим поиска для GitHub Pages; сохранённые QA-артефакты (90 backend-тестов, 203 core-проверки).

Стек

Python · FastAPI · Pydantic · Uvicorn · React 19 · TypeScript · Vite · Vitest · pytest · GitHub Actions

Модели

gpt-4o-mini


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

README

README.md

Shabyt — умный подбор подрядчиков для мероприятий

Решение для кейса HackAlem AI «Умный подбор подрядчиков (#79-lite)».

Shabyt помогает заказчику выбрать подходящего event-подрядчика из каталога по конкретным условиям мероприятия: городу, дате, типу события, категории и бюджету. Дополнительно можно учитывать язык и длительность работы.

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


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

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

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

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


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

Поиск и фильтрация

Система учитывает:

  • город;
  • дату мероприятия;
  • тип мероприятия;
  • категорию подрядчика;
  • бюджет;
  • язык — опционально;
  • длительность — опционально.

Подрядчик исключается из выдачи, если он:

  • занят на выбранную дату;
  • не укладывается в бюджет;
  • не работает с выбранным форматом мероприятия;
  • не поддерживает выбранный язык;
  • не подходит по длительности работы.

Детерминированное ранжирование

После фильтрации кандидаты ранжируются без ML/LLM по воспроизводимым правилам:

  1. чем ближе цена price_from_kzt к бюджету снизу, тем выше кандидат;
  2. добавляется бонус за совпадение языка;
  3. добавляется бонус за релевантные слова запроса в описании профиля;
  4. при одинаковом score используется сортировка по id.

Одинаковый запрос даёт одинаковый порядок карточек.

Три отдельных результата поиска

API и интерфейс различают состояния:

  • ok — найдены подходящие подрядчики;
  • no_category — в выбранном городе нет такой категории;
  • no_match — категория есть, но никто не прошёл условия запроса.

Если подходят только один или два профиля, сервис возвращает их и объясняет, почему результатов меньше трёх.

Объяснения рекомендаций

Для каждой карточки формируется краткое фактическое объяснение.

Есть два режима:

  • локальный детерминированный режим — работает без API-ключа;
  • OpenAI — если задан OPENAI_API_KEY, backend запрашивает LLM-объяснение. Модель по умолчанию задаётся как gpt-4o-mini через OPENAI_EXPLAIN_MODEL.

Если внешний LLM недоступен или не отвечает вовремя, сервис возвращает локальное объяснение вместо падения запроса.

Frontend

Реализован адаптивный интерфейс на React + TypeScript:

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

Проверки

В репозитории есть:

  • backend/tests/ — pytest-тесты backend;
  • Vitest-тесты frontend;
  • frontend/scripts/check-core.cjs — дополнительные проверки логики;
  • CHECK_WINDOWS.bat — единая команда проверки перед демонстрацией;
  • QA_REPORT.md и папка qa/ с сохранёнными результатами предыдущих прогонов.

Сохранённый в репозитории backend-лог содержит 90 passed, а core-проверки frontend — 203 core checks passed. Перед финальной сдачей рекомендуется повторить CHECK_WINDOWS.bat на машине команды.


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

Основной пользовательский сценарий:

  1. Пользователь открывает Shabyt.
  2. Выбирает город, дату, тип мероприятия, категорию и бюджет.
  3. При необходимости указывает язык и длительность.
  4. Frontend отправляет POST /api/search.
  5. Backend загружает каталог подрядчиков.
  6. filters.py отбрасывает неподходящие профили.
  7. ranking.py детерминированно ранжирует прошедших фильтр кандидатов.
  8. Для топ-3 формируется объяснение через OpenAI либо локальный fallback.
  9. Frontend показывает до трёх карточек и объяснение результата.

Если подходящих кандидатов нет, пользователь получает понятную причину, а не пустой экран.


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

Frontend

  • React 19
  • TypeScript
  • Vite 8
  • CSS
  • Manrope (@fontsource/manrope)
  • Vitest
  • Testing Library

Backend

  • Python 3.11+
  • FastAPI
  • Pydantic 2
  • Uvicorn
  • python-dotenv
  • OpenAI Python SDK
  • pytest

AI / внешние сервисы

  • OpenAI API — опциональная генерация объяснений рекомендаций.
  • Модель по умолчанию: gpt-4o-mini.
  • Без OpenAI API проект продолжает работать в локальном rule-based режиме.
  • NVIDIA API в текущей версии репозитория не интегрирован.

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

┌─────────────────────────────┐
│      React / TypeScript     │
│        Vite frontend        │
└──────────────┬──────────────┘
               │ POST /api/search
               ▼
┌─────────────────────────────┐
│          FastAPI            │
│           main.py           │
└──────────────┬──────────────┘
               │
       ┌───────┴────────┐
       ▼                ▼
┌─────────────┐   ┌─────────────┐
│ data_loader │   │ validation  │
└──────┬──────┘   └─────────────┘
       ▼
┌─────────────┐
│  filters.py │
└──────┬──────┘
       ▼
┌─────────────┐
│ ranking.py  │
└──────┬──────┘
       ▼
┌─────────────────────────────┐
│       llm_explain.py        │
│ OpenAI API / local fallback │
└──────────────┬──────────────┘
               ▼
┌─────────────────────────────┐
│    До 3 карточек + reason   │
└─────────────────────────────┘

Основные директории

backend/
  main.py                 FastAPI, схемы запроса/ответа, endpoints
  data_loader.py          загрузка и проверка JSONL
  filters.py              фильтрация кандидатов
  ranking.py              детерминированное ранжирование
  llm_explain.py          объяснения OpenAI / fallback
  data/                    основной датасет
  synthetic_profiles.jsonl дополнительные synthetic-профили
  tests/                   backend-тесты

frontend/
  src/components/         UI-компоненты
  src/services/           API и статический поиск
  src/hooks/              состояние и запросы
  src/styles/             стили
  public/data/            каталог для статического режима

qa/                       сохранённые QA-артефакты

Режим GitHub Pages

В проекте присутствует GitHub Actions workflow .github/workflows/pages.yml.

Для Pages предусмотрен отдельный статический режим каталога: frontend может выполнять поиск по frontend/public/data/contractors.json без запущенного FastAPI. Обычный локальный режим использует backend API.


6. Данные

Используется предоставленный организаторами анонимизированный каталог event-подрядчиков.

Основной файл:

backend/data/hackathon-dataset-anonymized.jsonl

В нём 66 профилей.

Дополнительно проект загружает:

backend/synthetic_profiles.jsonl

В нём 3 дополнительных synthetic-профиля для демонстрации редких категорий.

Итого backend загружает 69 уникальных профилей, из которых 16 имеют synthetic: true с учётом synthetic-профилей, уже присутствующих в исходном датасете.

Каталог содержит 17 категорий и города:

  • Алматы;
  • Астана;
  • Зарубежье.

Календарь занятости ограничен диапазоном:

23.09.2026 — 31.12.2026

Frontend содержит эквивалентный каталог frontend/public/data/contractors.json для статического режима.


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

Требования

  • Python 3.11+
  • Node.js ^20.19.0 или >=22.12.0
  • npm

API-ключ OpenAI не обязателен для запуска.

Самый простой запуск на Windows

Из корня проекта:

.\START_WINDOWS.bat

Скрипт:

  1. создаст backend/.venv;
  2. установит Python-зависимости;
  3. установит frontend-зависимости через npm ci;
  4. выполнит production build frontend;
  5. запустит FastAPI.

После запуска открыть:

http://127.0.0.1:8000/

Swagger:

http://127.0.0.1:8000/docs

Health check:

http://127.0.0.1:8000/health

Режим разработки

Backend

Из корня проекта:

python -m venv backend\.venv
backend\.venv\Scripts\python.exe -m pip install -r backend\requirements.txt
backend\.venv\Scripts\python.exe -m uvicorn main:app --app-dir backend --reload --host 127.0.0.1 --port 8000

Frontend

Во втором терминале:

cd frontend
npm ci --include=dev
npm run dev

Frontend:

http://127.0.0.1:4173/

Backend API:

http://127.0.0.1:8000/

8. Подключение OpenAI

Скопируйте:

backend/.env.example

в:

backend/.env

и укажите:

OPENAI_API_KEY=your_key_here
OPENAI_EXPLAIN_MODEL=gpt-4o-mini

После этого перезапустите backend.

Не публикуйте .env и API-ключи в GitHub.

Если OPENAI_API_KEY отсутствует, используются локальные объяснения.


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

Вариант 1 — через интерфейс

После запуска откройте сайт и используйте демо-пресеты над формой либо заполните форму вручную.

Сценарий A — обычная выдача

Город: Алматы
Дата: 14.11.2026
Тип: Свадьба
Категория: Фотограф
Бюджет: 300 000 ₸

Ожидаемый результат: ok, до 3 карточек. В текущем каталоге этот запрос возвращает три профиля.

Сценарий B — редкая категория

Город: Алматы
Дата: 14.11.2026
Тип: Свадьба
Категория: Флорист
Бюджет: 300 000 ₸

Ожидаемый результат: ok, один подходящий профиль и сообщение, почему карточек меньше трёх. Synthetic-профиль помечается соответствующим флагом.

Сценарий C — кандидаты есть, но не проходят условия

Город: Алматы
Дата: 14.11.2026
Тип: Свадьба
Категория: Фотограф
Бюджет: 1 000 ₸

Ожидаемый результат: no_match с причинами исключения кандидатов.

Сценарий D — категории нет в городе

Город: Астана
Дата: 14.11.2026
Тип: Свадьба
Категория: Лайв-бэнд
Бюджет: 2 000 000 ₸

Ожидаемый результат: no_category.

Вариант 2 — через Swagger

Откройте:

http://127.0.0.1:8000/docs

и выполните POST /search:

{
  "city": "Алматы",
  "event_date": "2026-11-14",
  "event_type": "свадьба",
  "category": "Фотограф",
  "budget_kzt": 300000,
  "duration_hours": null,
  "language": null
}

Автоматические проверки

На Windows:

.\CHECK_WINDOWS.bat

Команда запускает backend-тесты, frontend core checks, Vitest и production build.


10. API

POST /search

Также доступен alias:

POST /api/search

Пример запроса:

{
  "city": "Алматы",
  "event_date": "2026-11-14",
  "event_type": "свадьба",
  "category": "Фотограф",
  "budget_kzt": 300000,
  "duration_hours": null,
  "language": null
}

Пример структуры ответа:

{
  "status": "ok",
  "message": null,
  "results": [
    {
      "id": "...",
      "name": "...",
      "category": "Фотограф",
      "city": "Алматы",
      "price_from_kzt": 300000,
      "explanation": "...",
      "synthetic": false
    }
  ]
}

Дополнительные endpoints

GET /health
GET /catalog/meta

Для frontend также зарегистрированы /api/health и /api/catalog/meta.


11. Ограничения текущей версии

  • Проект не выполняет бронирование подрядчиков.
  • Нет отправки заявок и уведомлений подрядчику.
  • Нет пользовательской регистрации и авторизации.
  • Нет контактов подрядчиков — используется анонимизированный хакатон-датасет.
  • price_from_kzt — цена «от», а не финальная смета.
  • Доступность определяется только календарём датасета, а не реальной системой бронирования.
  • Поиск по датам доступен только для 23.09.2026–31.12.2026.
  • OpenAI используется только для объяснений и является опциональным; без ключа работает rule-based fallback.
  • NVIDIA API в текущем коде не подключён.
  • Статический GitHub Pages-режим работает без backend и поэтому использует локальную логику и локальные объяснения.

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

В репозитории настроен workflow для GitHub Pages, однако конкретный публичный URL развёрнутой версии не зафиксирован в исходниках проекта.

Если Pages уже успешно опубликован, добавьте сюда URL перед сдачей:

Deployed: <вставьте публичную ссылку>

Репозиторий

https://github.com/BAITC-Hacks/hack-e68c7bd2-triple-a

Безопасность

Не коммитьте в GitHub:

  • backend/.env;
  • OPENAI_API_KEY;
  • любые другие секреты и API-токены.

Для примера конфигурации используйте только backend/.env.example.

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

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

mustaf1nx 12ANAYSSA 3poterpish 2

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

  • 2026-09-23 17:54Update README.mdpoterpish
  • 2026-09-23 17:45Add files via uploadmustaf1nx
  • 2026-09-23 17:35Add files via uploadmustaf1nx
  • 2026-09-23 17:34Add files via uploadmustaf1nx
  • 2026-09-23 17:33Add files via uploadmustaf1nx
  • 2026-09-23 17:32Delete shabyt-design-package directorymustaf1nx
  • 2026-09-23 17:32Delete frontend directorymustaf1nx
  • 2026-09-23 17:32Delete backend directorymustaf1nx
Все коммиты (16)
  • 2026-09-23 16:33README.mdpoterpish
  • 2026-09-23 16:26frontend: rebrand as Shabyt and add GitHub Pages deploymentANAYSSA
  • 2026-09-23 16:19Add files via uploadmustaf1nx
  • 2026-09-23 16:18Delete backend.zipmustaf1nx
  • 2026-09-23 16:18Add files via uploadmustaf1nx
  • 2026-09-23 15:50frontend: refactor into tested React TypeScript applicationANAYSSA
  • 2026-09-23 15:14frontend: add responsive contractor search and LAN supportANAYSSA
  • 2026-09-23 14:56Add files via uploadmustaf1nx
Структура репозитория (130 файлов)
.github/workflows/pages.yml
CHANGELOG.md
CHECK_WINDOWS.bat
QA_REPORT.md
README.md
START_WINDOWS.bat
backend/README.md
backend/data/hackathon-dataset-anonymized.jsonl
backend/data_loader.py
backend/filters.py
backend/llm_explain.py
backend/main.py
backend/ranking.py
backend/requirements-dev.txt
backend/requirements.txt
backend/synthetic_profiles.jsonl
backend/tests/conftest.py
backend/tests/test_backend.py
docs/DESIGN_V2.md
docs/desktop-dark.png
docs/desktop-light.png
docs/desktop-results.png
docs/mobile-light.png
frontend/README.md
frontend/index.html
frontend/package-lock.json
frontend/package.json
frontend/public/data/contractors.json
frontend/scripts/check-core.cjs
frontend/src/App.tsx
frontend/src/assets/brand/icon-dark.svg
frontend/src/assets/brand/icon-light.svg
frontend/src/assets/brand/logo-dark.svg
frontend/src/assets/brand/logo-light.svg
frontend/src/assets/chevron-down.svg
frontend/src/components/layout/Footer.tsx
frontend/src/components/layout/Header.tsx
frontend/src/components/search/CategoryBar.tsx
frontend/src/components/search/ContractorCard.tsx
frontend/src/components/search/DemoPresets.tsx
frontend/src/components/search/Hero.tsx
frontend/src/components/search/HowItWorks.tsx
frontend/src/components/search/MatchIllustration.tsx
frontend/src/components/search/SearchForm.tsx
frontend/src/components/search/SearchResults.tsx
frontend/src/components/search/SearchSummary.tsx
frontend/src/components/search/StatusPanel.tsx
frontend/src/components/ui/BrandLogo.tsx
frontend/src/components/ui/Button.tsx
frontend/src/components/ui/ErrorBoundary.tsx
frontend/src/components/ui/Field.tsx
frontend/src/components/ui/Icon.tsx
frontend/src/components/ui/InfoDialog.tsx
frontend/src/components/ui/SectionHeading.tsx
frontend/src/components/ui/ThemePicker.tsx
frontend/src/config/runtime.ts
frontend/src/config/search.ts
frontend/src/hooks/useSearch.ts
frontend/src/hooks/useSearchDraft.ts
frontend/src/hooks/useSectionNavigation.ts
frontend/src/hooks/useTheme.ts
frontend/src/main.tsx
frontend/src/pages/SearchPage.tsx
frontend/src/services/searchApi.test.ts
frontend/src/services/searchApi.ts
frontend/src/services/staticSearch.test.ts
frontend/src/services/staticSearch.ts
frontend/src/styles/base.css
frontend/src/styles/form.css
frontend/src/styles/layout.css
frontend/src/styles/redesign.css
frontend/src/styles/responsive.css
frontend/src/styles/results.css
frontend/src/styles/tokens.css
frontend/src/tests/App.test.tsx
frontend/src/tests/InfoDialog.test.tsx
frontend/src/tests/ThemePicker.test.tsx
frontend/src/tests/setup.ts
frontend/src/types/search.ts
frontend/src/utils/format.ts
frontend/src/utils/storage.test.ts
frontend/src/utils/storage.ts
frontend/src/utils/theme.test.ts
frontend/src/utils/theme.ts
frontend/src/utils/validation.test.ts
frontend/src/utils/validation.ts
frontend/tsconfig.json
frontend/vite.config.ts
frontend/vitest.config.ts
qa/backend-tests.log
qa/backend-tests.xml
qa/browser-checks.json
qa/browser-checks.log
qa/frontend-core-tests.log
qa/manifest-sha256.json
qa/parity_cases.json
qa/transpile-result.json
shabyt-design-package/design-spec.md
shabyt-design-package/desktop-dark.png
shabyt-design-package/desktop-dark.svg
shabyt-design-package/desktop-light.png
shabyt-design-package/desktop-light.svg
shabyt-design-package/icon-dark.png
shabyt-design-package/icon-dark.svg
shabyt-design-package/icon-light.png
shabyt-design-package/icon-light.svg
shabyt-design-package/icon-mono-black.png
shabyt-design-package/icon-mono-black.svg
shabyt-design-package/icon-mono-white.png
shabyt-design-package/icon-mono-white.svg
shabyt-design-package/logo-cyrillic-dark.png
shabyt-design-package/logo-cyrillic-dark.svg
shabyt-design-package/logo-cyrillic-light.png
shabyt-design-package/logo-cyrillic-light.svg
shabyt-design-package/logo-cyrillic-mono-black.png
shabyt-design-package/logo-cyrillic-mono-black.svg
shabyt-design-package/logo-cyrillic-mono-white.png
shabyt-design-package/logo-cyrillic-mono-white.svg
shabyt-design-package/logo-dark.png
shabyt-design-package/logo-dark.svg
shabyt-design-package/logo-light.png
shabyt-design-package/logo-light.svg
shabyt-design-package/logo-mono-black.png
shabyt-design-package/logo-mono-black.svg
shabyt-design-package/logo-mono-white.png
shabyt-design-package/logo-mono-white.svg
shabyt-design-package/mobile-dark.png
shabyt-design-package/mobile-dark.svg
shabyt-design-package/mobile-light.png
shabyt-design-package/mobile-light.svg
Ветки (3)

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

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

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