ToiTap
Веб-сервис подбора до 3 подрядчиков для мероприятия по свободному текстовому запросу, с объяснением выбора к каждой карточке и избранным.
О проекте
Команда: Qadam Tech
Кейс HackAlem.ai: Креативные индустрии — Firebird
Стадия: Доведённый продукт
Задача кейса: «#79-lite»: подобрать до 3 подрядчиков для мероприятия и объяснить выбор
Суть
Веб-сервис подбора до 3 подрядчиков для мероприятия по свободному текстовому запросу, с объяснением выбора к каждой карточке и избранным.
Как устроено
/api/parse через OpenAI (structured output) извлекает город, дату, формат, категорию, бюджет; если AI не справился, недостающее дополняет разбор по правилам. Backend на pandas фильтрует 66 профилей из CSV по городу, категории, занятости, цене, формату, языку и длительности и детерминированно ранжирует их (упоминание формата, полнота данных, запас бюджета, при равенстве по ID). LLM получает только отобранных кандидатов и пишет объяснения на фактах, порядок не меняет. При сбое срабатывают шаблоны, повторные запросы кэшируются.
Ключевые решения
- LLM query parsing
- rule-based fallback parser
- deterministic scoring
- hard-filter eligibility
- structured outputs
- in-memory cache
Особенности
Жёсткое разделение: LLM только разбирает запрос и формулирует объяснения, а отбор и порядок задаёт воспроизводимый алгоритм. Флаги source/explanation_source показывают, что сделал AI. Сервис различает «категории нет в городе» и «нет подходящих кандидатов». Есть живой деплой и Docker-first запуск.
Стек
Python · FastAPI · Pydantic · pandas · React 19 · TypeScript · Vite · Tailwind CSS · Docker Compose · Nginx · pytest
Модели
gpt-4.1-mini
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-c1535a6c-qadam-tech.
README
README.mdToiTap
Подрядчики для вашего мероприятия.
Сайт: toi-tap.aribzhan.kz
React 19 · TypeScript · FastAPI · OpenAI
Хакатон-проект команды Qadam Tech.
Быстрый старт · Как работает AI · Демо-запросы · Backend API
ToiTap помогает найти подрядчиков для мероприятия: расскажите о планах обычным текстом, получите до трёх подходящих вариантов и узнайте, почему каждый из них оказался в подборке.
Вместо длинного каталога — короткий список с конкретными фактами: стоимость, доступность, язык, длительность и детали из описания профиля.
Хакатон-проект Qadam Tech, задача #79-lite. Каталог содержит 66 анонимизированных профилей, включая 13 синтетических. Это демонстрационный подбор, без бронирования и оплаты. Фотографии иллюстрируют категории услуг и не изображают конкретных подрядчиков.
Preview

Интерфейс адаптируется к размеру экрана: одна колонка карточек на телефоне, две на планшете, три на широком экране. Проверены ширины 320, 375, 390, 768, 1024 и 1440 px, включая открытый календарь. Это проверка размеров viewport, а не сертификация всех устройств и браузеров.
Возможности
- Поиск обычным текстом. Город, дата, формат, категория и бюджет извлекаются из запроса; язык и длительность можно уточнить дополнительно.
- До трёх рекомендаций. Занятые на дату, превышающие бюджет и не подходящие под условия профили исключаются до формирования подборки.
- Объяснение к каждой карточке. Цена относительно бюджета, особенности предложения и факты из описания помогают сравнивать варианты.
- Понятный пустой результат. Сервис различает отсутствие категории в городе и отсутствие подходящих кандидатов; показывает причины исключения.
- Личный список. Избранное открывается отдельным представлением без баннера, поиска и фильтров. Сохранение — в localStorage этого браузера.
- Карточка профиля. Описание, языки, форматы, стартовая цена и ограничения по времени доступны в модальном окне.
- Светлая и тёмная темы. Адаптивный интерфейс с локальными тематическими фотографиями, календарём и фильтрами.
- Работа без AI-ключа. Разбор по правилам и шаблонные объяснения сохраняют доступность поиска.
Дизайн
Светлый фон, глубокий зелёный акцент, типографика Manrope и крупные фотографии создают спокойный интерфейс для планирования события.
| Элемент | Значение |
|---|---|
| Основной фон | #FAFBF8 |
| Акцент и основные действия | #075B43 |
| Основной текст | #203B31 |
| Мягкие поверхности | #F1F5EF |
| Шрифт | Manrope |
Фотографии хранятся в frontend/public/images и загружаются с самого приложения. На карточках они отмечены как изображения для вдохновения. Шрифт загружается через Google Fonts; при недоступности используется системный sans-serif.
Designed by Qadam Tech.
Как работает AI
flowchart LR
A[Описание события] --> B[AI: извлечение параметров]
B --> C[Backend: фильтры и ранжирование]
C --> D[До 3 кандидатов]
D --> E[AI: объяснения на фактах]
E --> F[Карточки и избранное]
- Понимает запрос.
/api/parseпреобразует текст в структурированные параметры. Значения приводятся к категориям каталога; недостающие распознанные значения дополняются разбором по правилам. - Отбирает по условиям. Backend проверяет город, категорию, занятость, цену, формат, язык и максимальную длительность.
- Ранжирует воспроизводимо. Порядок определяет алгоритм: упоминание формата в описании, полнота исходных данных, запас бюджета, язык и часы. При равном балле используется ID.
- Объясняет выбор. OpenAI получает только отобранные профили и факты для объяснения. AI не выбирает кандидатов и не меняет их порядок.
- Обрабатывает сбои. При ошибке или таймауте AI используются правила и шаблоны. Повторные запросы кэшируются в памяти backend.
Поле source: "llm" подтверждает AI-разбор, а explanation_source: "llm" — AI-объяснение конкретной карточки. В интерфейсе это отдельные индикаторы. /api/health с llm: true означает наличие ключа, но не гарантирует успешный ответ модели.
Модель по умолчанию — gpt-4.1-mini. Изменить её и таймаут можно в локальном .env.
Данные и ограничения
- 66 профилей, 17 категорий, города каталога: Алматы, Астана и «Зарубежье».
- Доступное окно дат: 23 сентября — 31 декабря 2026 года.
- Синтетические профили отмечены; восстановленные цены отображаются как ориентировочные.
- Стоимость — начальная цена услуги, не подтверждённая итоговая смета.
max_hours: nullозначает отсутствие ограничения по часам присутствия.- Если никто не подходит, условия автоматически не смягчаются. Нужно изменить бюджет, дату, формат или категорию.
- Неуказанные в новом тексте параметры сохраняются из текущих фильтров. Перед независимым сценарием проверьте язык и длительность.
- Избранное хранит результат прошлого поиска: доступность нужно проверять повторно.
- Объяснения AI требуют проверки: возможны неточности формулировок и сравнения цен.
Быстрый старт
Основной способ запуска — Docker Compose: frontend с Nginx и backend FastAPI поднимаются одной командой. Нужны Git, Docker Engine / Docker Desktop и Docker Compose 2.24+. Локальные Python, Node.js и npm для запуска не требуются.
1. Получите проект
git clone --branch main [email protected]:BAITC-Hacks/hack-c1535a6c-qadam-tech.git
cd hack-c1535a6c-qadam-tech
Для клонирования нужен доступ к репозиторию и SSH-ключ, добавленный в GitHub. Если проект уже скачан, откройте его папку.
2. Настройте AI
Без ключа поиск работает по правилам и возвращает шаблонные объяснения. Для OpenAI создайте файл, только если его ещё нет:
if [ ! -f backend/.env ]; then
cp backend/.env.example backend/.env
fi
chmod 600 backend/.env
nano backend/.env
Заполните OPENAI_API_KEY. Параметры OPENAI_MODEL и OPENAI_TIMEOUT задают модель и таймаут в секундах. Существующий .env сохраните: ключ не нужно переносить в Dockerfile или frontend.
3. Соберите и запустите
Запустите Docker Desktop, если используете его, затем выполните из корня проекта:
docker compose up -d --build --wait
- Приложение: localhost:8080
- Состояние API: localhost:8080/api/health
Frontend обращается к /api через Nginx в контейнере. Backend доступен внутри сети Compose; его порт не публикуется на хост. Первый запуск скачивает образы и устанавливает зависимости, последующие используют кэш.
Управление
Все команды выполняются из корня проекта:
# Состояние контейнеров
docker compose ps
# Логи в реальном времени; Ctrl+C завершает просмотр логов
docker compose logs -f
# Остановка и удаление контейнеров; исходники и .env сохраняются
docker compose down
# Повторный запуск
docker compose up -d --wait
# Пересборка после изменения кода
docker compose up -d --build --wait
# Применение изменений backend/.env
docker compose up -d --force-recreate --wait backend
Для запуска с логами в текущем терминале используйте docker compose up --build; в этом режиме Ctrl+C останавливает контейнеры. Если порт 8080 занят, задайте другой: TOITAP_PORT=8081 docker compose up -d --build --wait.
Ubuntu и HTTPS
На сервере Docker также слушает 127.0.0.1:8080. Внешний Nginx направляет запросы домена в контейнер, а Certbot продолжает обслуживать HTTPS на хосте.
Инструкция по настройке, переходу с systemd и откату · Пример Nginx для домена.
Для разработки без Docker запустите backend по его инструкции, затем выполните npm ci и npm run dev в папке frontend (Node.js 22.12+). Vite открывается на порту 5173 и направляет /api на backend порта 8765.
Демо-запросы
Скопируйте один запрос в строку поиска. Ожидаемые результаты относятся к текущему датасету без дополнительных ограничений языка и длительности.
| Запрос | Ожидаемый результат |
|---|---|
| Нужен ведущий на свадьбу в Алматы 15 октября 2026 года, бюджет до 1 500 000 тенге. | Кики, Эмилия, Мицури Канроджи — 3 карточки |
| Нужен флорист на свадьбу в Алматы 14 ноября 2026 года, бюджет до 500 000 тенге. | Тихиро Огино — 1 карточка, второй флорист занят |
| Нужен отель для свадьбы в Алматы 30 сентября 2026 года, бюджет до 3 500 000 тенге. | Гохан — 1 карточка, от 3 200 000 ₸ |
| Нужен декоратор на свадьбу в Астане 14 ноября 2026 года, бюджет до 3 000 000 тенге. | Категории нет в городе |
Техническая структура
| Слой | Технологии и ответственность |
|---|---|
| Frontend | React 19, TypeScript, Vite, Tailwind CSS, Lucide |
| API | FastAPI, Pydantic, Uvicorn |
| Подбор | Python, pandas, CSV → JSON-кэш, детерминированный скоринг |
| AI | OpenAI Responses API, структурированный вывод, fallback и кэш |
| Проверки | pytest, Node.js test runner, TypeScript и production build |
compose.yaml Запуск frontend и backend
docker/ Инструкции, smoke-тест и конфигурация хостового Nginx
backend/
Dockerfile Образ FastAPI
app/main.py API и валидация
app/data.py Загрузка и нормализация каталога
app/search.py Фильтрация, причины исключения, скоринг
app/llm.py AI-интеграция, кэш и fallback
app/ai/ Промпты, разбор запроса и объяснения
data/dataset.csv Исходные данные
tests/ Тесты подбора и AI-модуля
frontend/
Dockerfile Сборка React и образ Nginx
nginx.conf Статика и проксирование /api
public/images/ Статические иллюстрации категорий
src/main.tsx Поиск, карточки, избранное и темы
src/api.ts Контракты API и проверка параметров
src/DatePicker.tsx Календарь
src/style.css Визуальная система
Проверки
После запуска контейнеров проверьте API:
curl -fsS http://127.0.0.1:8080/api/health
Полная проверка frontend → Nginx → backend, изображений, подбора и разбора текста требует Python 3 на хосте, без дополнительных пакетов:
python3 docker/smoke.py
Backend-тесты можно выполнить внутри Docker из корня проекта:
docker compose run --rm --no-deps -e OPENAI_API_KEY= \
-v "$PWD/backend/tests:/app/tests:ro" \
backend python -m pytest -q -p no:cacheprovider
Текущий набор: 22 backend-теста и 4 frontend-теста. Frontend-тесты, TypeScript и production build выполняются при сборке frontend image. Unit-тесты не заменяют проверку реального OpenAI-запроса: smoke-тест выводит источники разбора и объяснений, включая переход на шаблоны.
Конфигурация и приватность
backend/.env исключён из Git правилами .gitignore. В репозитории хранится только .env.example без ключа. .dockerignore исключает секреты из контекста сборки; Compose передаёт переменные backend при запуске контейнера. API-ключ используется исключительно backend и не должен попадать в frontend, скриншоты или коммиты.
С включённым AI текст запроса и данные выбранных профилей отправляются в OpenAI; клиент указывает store=False. Избранное и выбранная тема сохраняются локально в браузере. Сервис не реализует регистрацию, бронирование, оплату или отправку сообщений подрядчикам.
Дальнейшее развитие
- Подбор с учётом условий и занятости
- AI-разбор текста и объяснения с fallback
- Избранное, темы и адаптивные карточки
- Улучшение сравнений цен в AI-объяснениях
- Явное уведомление при переходе на шаблон после таймаута
- Предложение альтернативных условий при пустом результате
Команда и материалы
Qadam Tech — backend и данные, frontend и дизайн, AI-интеграция.
Фотографии: Unsplash. Источники локальных изображений перечислены в frontend/public/images/README.md.
Авторы коммитов
История коммитов
- 2026-09-23 17:50Add Docker Compose deployment and Docker-first READMEAribzhan
- 2026-09-23 17:30docs: add live website link to READMEAribzhan
- 2026-09-23 16:55Polish responsive UI and favorites, add project showcase READMEAribzhan
- 2026-09-23 15:35Track generated candidates.jsonjahongirdev1
- 2026-09-23 15:33Replace native date picker and selects with branded controlsjahongirdev1
- 2026-09-23 15:30Improve LLM explanations and query parsingjahongirdev1
- 2026-09-23 15:24Integrate backend, AI engine and frontend into one appjahongirdev1
- 2026-09-23 15:17Merge remote-tracking branch 'origin/frontend' into integrationjahongirdev1
Все коммиты (12)
- 2026-09-23 15:17Merge remote-tracking branch 'origin/feature/ai-engine' into integrationjahongirdev1
- 2026-09-23 15:13feat: add AI query parser and contractor explanationsAkhror
- 2026-09-23 15:04feat: add ToiTap frontend with contractor matching and themesAribzhan
- 2026-09-23 14:34Add FastAPI backend for contractor matchingjahongirdev1
Структура репозитория (49 файлов)
.gitignore README.md backend/.dockerignore backend/.env.example backend/Dockerfile backend/README.md backend/app/__init__.py backend/app/ai/__init__.py backend/app/ai/client.py backend/app/ai/explainer.py backend/app/ai/parser.py backend/app/ai/prompts.py backend/app/data.py backend/app/explain.py backend/app/llm.py backend/app/main.py backend/app/rules_parser.py backend/app/search.py backend/data/candidates.json backend/data/dataset.csv backend/demo.py backend/requirements.txt backend/tests/test_ai.py backend/tests/test_pipeline.py compose.yaml docker/README.md docker/nginx-host-location.conf.example docker/smoke.py docs/images/toitap-banner.png frontend/.dockerignore frontend/Dockerfile frontend/index.html frontend/nginx.conf frontend/package-lock.json frontend/package.json frontend/public/images/README.md frontend/public/images/celebration.jpg frontend/public/images/entertainment.jpg frontend/public/images/flowers.jpg frontend/public/images/photography.jpg frontend/public/images/venue.jpg frontend/src/DatePicker.tsx frontend/src/api.test.ts frontend/src/api.ts frontend/src/main.tsx frontend/src/style.css frontend/src/vite-env.d.ts frontend/tsconfig.json frontend/vite.config.ts
Ветки (4)
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.