AAYMA
RU
Войти

Tandau

Сервис подбирает до трёх подрядчиков мероприятия по городу, дате, формату, категории и бюджету.

Доведённый продукт
Команда «apex» 8💬 0 комментариевс 23 сентября 2026 г.

О проекте

Команда: apex
Кейс HackAlem.ai: Креативные индустрии — Firebird
Стадия: Доведённый продукт

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

Суть

Сервис подбирает до трёх подрядчиков мероприятия по городу, дате, формату, категории и бюджету. К каждой карточке даётся объяснение из фактов, отдельно объясняется «почему не больше», выдачу можно сравнить на двух датах. Есть аккаунты, избранное и три языка.

Как устроено

Запрос валидируется по справочникам и календарю, затем отбираются кандидаты по категории и городу. Жёсткие фильтры (занятость, формат, бюджет, язык, часы) сохраняют полный список причин отсева. Прошедшие получают прозрачный балл из шести факторов, сортировка детерминированная (−score, цена, id). Объяснения собираются по шаблонам из «атомов»-фактов, включая контраст с соседними карточками, общие фразы запрещены списком стоп-фраз. Есть пять явных исходов: found/partial/no_category_in_city/none_match/invalid_request. LLM в этой версии не используется.

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

  • фильтры с причинами отсева и воронка
  • прозрачный взвешенный скоринг
  • собственный TF-IDF по символьным n-граммам
  • контрастные объяснения
  • стоп-фразы в тестах
  • hashlib.scrypt
  • CSRF

Особенности

Главное здесь объяснение, а не сортировка. Контрастные фразы («единственный из трёх, кто…»), разбивка балла под каждой карточкой, подсказки ближайших дат, бюджета и города при пустом результате. Сравнение выдачи на двух датах. TF-IDF написан без numpy и sklearn. Размещён на Vercel.

Стек

Python · FastAPI · Uvicorn · Jinja2 · vanilla JS · SQLite · pytest · httpx · Docker


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

README

README.md

Tandau (https://tandau-jade.vercel.app/) — подбор event-подрядчиков с объяснениями

https://tandau-jade.vercel.app/

Прототип для HackAlem AI, трек 06 «Креативные индустрии», кейс Firebird «Умный подбор подрядчиков».

Tandau (таңдау — «выбор») по параметрам заказа выдаёт до трёх подрядчиков и честно объясняет, почему именно они. Если подобрать нельзя — говорит почему и что изменить.

Коротко
Запускpip install -r requirements.txt → python run.py → http://127.0.0.1:8000
НужноPython 3.10+ и pip. Не нужно: .env, ключи API, Node.js, Docker, сервер БД
ДоступПодбор работает без регистрации; демо-аккаунт [email protected] / Demo2026!
ПроверкаСемь сценариев с ожидаемым результатом — раздел «Как проверить решение»
Развёрнутая версияНет — проект запускается локально за пару минут

Содержание

  1. Что делает проект и для кого
  2. Что реализовано
  3. Как это работает
  4. Технологии
  5. Архитектура
  6. Установка и запуск
  7. Демо-доступ
  8. Как проверить решение
  9. API
  10. Данные
  11. Формула скоринга
  12. Переменные окружения
  13. Тесты
  14. LLM-режим
  15. Ограничения
  16. Использованные компоненты, лицензии и AI-инструменты
  17. Языки интерфейса
  18. Команда
  19. English summary

Что делает проект и для кого

Заказчику мероприятия. Он уже выбрал город, дату и тип события и смотрит каталог агрегатора. Tandau сокращает каталог до трёх подрядчиков и к каждому пишет 1–2 предложения: чем именно он подходит этому заказу — цена и запас бюджета, формат, язык, часы, факт или цитата из описания.

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

Главный принцип — ценность в объяснении, а не в сортировке. Объяснения собираются из проверяемых фактов профиля, без общих фраз вроде «отличный выбор для вашего мероприятия». Бронирование, заявки и уведомления подрядчикам — вне задачи: сервис только рекомендует.

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

  • Лендинг с кратким брифом: пять условий события (город, дата, формат, категория, бюджет) переносятся в форму подбора /app.
  • Подбор до трёх подрядчиков по городу, дате, типу мероприятия, категории и бюджету; опционально — длительность (ч), язык и свободные пожелания.
  • Пять различимых исходов — у каждого свой баннер с цветом, иконкой и текстом:
    • found — подобрали;
    • partial — подобрали 1–2 и объяснили, почему не больше;
    • no_category_in_city — в этом городе такой категории нет (и где она есть);
    • none_match — кандидаты есть, но ни один не проходит (разбивка причин и подсказки);
    • invalid_request — запрос вне данных, например дата вне календаря.
  • Объяснение у каждой карточки — 1–2 предложения из фактов: цена «от» и запас бюджета, форматы, языки, часы, стаж и объём работ из описания, цитата с пометкой «по описанию», контраст с соседними карточками («единственный из трёх, кто…»).
  • «Почему не больше»: отсеянные кандидаты с именами и причинами (занят на дату, не берёт формат, дороже бюджета, не работает на языке, не работает так долго), воронка фильтров, подсказки — ближайшие даты, бюджет, другой город.
  • «Как посчитан балл» под каждой карточкой: шесть факторов, значение, вес и вклад в итог.
  • Сравнение двух дат (/app/compare): тот же запрос на две даты — кто вошёл, кто выпал и что дело в занятости.
  • Детерминизм: тот же запрос — тот же порядок карточек и тот же JSON; ссылкой на выдачу можно поделиться.
  • Честность данных: бейджи «синтетический профиль», «цена ориентировочная», «город уточняется», «цена «от» = бюджет».
  • Профиль подрядчика (/contractors/{id}) с календарём занятости на 100 дней и счётчиками свободных дней.
  • Аккаунты: регистрация, вход, выход, демо-аккаунт. Для вошедших — история запросов (без дублей подряд, до 50 записей, «Повторить» в один клик) и избранное (звезда на карточке, страница «Избранное»). Подбор никогда не требует входа.
  • Три языка интерфейса: русский, казахский, английский.
  • JSON API со Swagger-документацией (/docs).
  • Автотесты (pytest): данные, настройки, бриф, страницы, авторизация, сценарии Definition of Done, качество объяснений, кабинет, гигиена релиза.

Как это работает

flowchart TD
    A["Запрос: город, дата, тип, категория, бюджет<br/>+ длительность, язык, пожелания"] --> B{"Валидация<br/>дата в окне 23.09–31.12.2026,<br/>бюджет больше нуля, значения из справочников"}
    B -- "нет" --> X["invalid_request<br/>что именно не так"]
    B -- "да" --> C["Кандидаты: категория + город"]
    C -- "0 профилей" --> N["no_category_in_city<br/>подсказка: где эта категория есть"]
    C -- "есть" --> D["Фильтры с причинами<br/>занят · формат · бюджет · язык · часы"]
    D -- "прошли 0" --> M["none_match<br/>разбивка причин + ближайшие даты и бюджет"]
    D -- "прошли 1 и больше" --> E["Скоринг<br/>прозрачный балл с разбивкой по компонентам"]
    E --> F["Топ-3 по (−score, цена, id)<br/>+ контраст: чем карточка отличается от соседних"]
    F --> G["Объяснения из фактов<br/>числа, цитата «по описанию», флаги данных"]
    G --> H["Ответ: found (3+) или partial (1–2)<br/>+ отсеянные с причинами + подсказки"]
  1. Валидация. Дата в окне календаря 23.09–31.12.2026, бюджет больше нуля, город, категория, формат и язык — из справочников (/api/meta). Иначе — invalid_request с объяснением, что не так.
  2. Кандидаты. Профили нужной категории в нужном городе. Категория сравнивается по элементу списка: «Ведущий» ≠ «Ведущий церемонии». Кандидатов нет — no_category_in_city и подсказка, в каком городе категория есть.
  3. Фильтры с причинами. Занят на дату · не берёт этот формат · цена «от» выше бюджета · не работает на выбранном языке · максимум часов меньше длительности. Проверяются все условия; у каждого отсеянного сохраняется полный список причин — из него строятся воронка, разбивка причин и подсказки.
  4. Скоринг. Прозрачный балл для прошедших (см. «Формула скоринга»); сортировка по (−score, цена, id) — без случайности.
  5. Топ-3 и контраст. До трёх лучших карточек и число остальных подходящих (more_count); для каждой карточки ищется то, чем она отличается от двух других.
  6. Объяснения. Из «атомов» — проверяемых фактов (бюджет, формат, язык, часы, стаж, цитата, флаг данных, контраст) — по шаблонам на языке интерфейса собираются 1–2 предложения. Общие фразы запрещены списком стоп-фраз и тестами.
  7. Ответ. Статус, строка-итог, карточки, отсеянные с причинами, группы «почему не больше», воронка, подсказки и служебное поле meta (версия движка и объяснений, флаг llm_used — в этой версии всегда false, время ответа).

Формат объяснений на примере запроса S2 на 17 октября (фактический текст собирает движок, цитаты зависят от описаний):

Эмилия. Берёт только свадьбы и тои — самый узкий профиль из трёх; 13 лет, 356 свадеб — по описанию. От 900 000 ₸, запас 100 000 ₸; до 10 ч — запас 2 ч к вашим 8 ч; цена проставлена при подготовке данных — уточните.

Кики. Единственный из трёх, кто работает ещё и на английском; в описании прямо названы проводы невесты и обряд первых шагов. От 900 000 ₸, запас 100 000 ₸; до 10 ч — запас 2 ч к вашим 8 ч.

Технологии

СлойЧто используемВерсия
ЯзыкPython3.10+ (проверено на 3.12)
Веб и APIFastAPI + UvicornFastAPI >=0.110,<1.0 · Uvicorn >=0.27,<1.0
СтраницыJinja2 (серверный рендер) + ванильный JavaScriptJinja2 >=3.1,<4.0
СтилиСобственные CSS-файлы в app/static/css/ на CSS-переменных: белые поверхности, чёрная типографика, синий акцент #2B5FE3; одна светлая тема (тёмной темы нет)—
ШрифтGeist, файл лежит в репозитории (app/static/fonts/), системный fallbackGeist 1.7.2
Формы и сессииpython-multipart; подписанная cookie (Starlette SessionMiddleware + itsdangerous)python-multipart >=0.0.9 · itsdangerous >=2.1,<3.0
База данныхSQLite через стандартный sqlite3, файл var/tandau.dbвходит в Python
Пароли и формыhashlib.scrypt с солью; CSRF-токен в каждой POST-формевходит в Python
Поиск по смыслуСобственный TF-IDF по символьным n-граммамбез numpy и scikit-learn
Тестыpytest + httpx (TestClient)pytest >=8.0,<9.0 · httpx >=0.27,<0.28

Обязательных зависимостей семь — все в requirements.txt. Node.js, сборка фронтенда, сервер БД и Docker не нужны.

Архитектура

tandau/
├── run.py                       # точка входа: python run.py → http://127.0.0.1:8000 (HOST, PORT из окружения)
├── requirements.txt             # 7 обязательных зависимостей
├── requirements-llm.txt         # зарезервировано для будущего LLM-слоя; устанавливать не нужно
├── .env.example                 # образец переменных окружения — всё необязательно
├── data/
│   ├── hackathon-dataset-anonymized.csv   # 66 профилей от организаторов, без изменений
├── matcher/                     # движок подбора: чистый Python, без веб-зависимостей
│   ├── models.py                # Contractor, SearchRequest, Card, Rejection, SearchResponse
│   ├── data.py                  # загрузка CSV → Catalog: индексы, статистика, справочники
│   ├── validation.py            # проверка запроса → список ошибок для invalid_request
│   ├── filters.py               # жёсткие условия; каждая причина отсева — код busy/format/budget/language/duration
│   ├── scoring.py               # прозрачный балл и его разбивка (веса WEIGHTS — здесь)
│   ├── facts.py                 # факты из описаний: стаж, объём, масштаб, традиции
│   ├── evidence.py              # TF-IDF по символьным n-граммам, выбор лучшей цитаты
│   ├── explain.py               # атомы объяснения, контраст, сборка 1–2 предложений, стоп-фразы
│   ├── hints.py                 # подсказки: ближайшие даты, бюджет, другой город
│   ├── compare.py               # один запрос на две даты: вошли, выпали, остались
│   └── engine.py                # recommend(): весь пайплайн от запроса до ответа
├── app/                         # веб-слой FastAPI
│   ├── main.py                  # фабрика приложения: middleware, роутеры, статика, старт
│   ├── config.py                # настройки из окружения и .env, автогенерация SECRET_KEY
│   ├── db.py                    # SQLite: схема, пользователи, история, избранное, демо-пользователь
│   ├── security.py              # scrypt-хэш паролей, CSRF, безопасный next, ограничение попыток входа
│   ├── deps.py                  # текущий пользователь, защита страниц кабинета
│   ├── brief.py                 # нормализация и проверка пяти условий брифа с лендинга
│   ├── profiles.py              # профиль подрядчика: календарь, сводка занятости, JSON
│   ├── i18n.py                  # словари RU/KZ/EN, выбор языка
│   ├── web.py                   # render(): общий контекст и фильтры шаблонов
│   ├── routes/pages.py          # /, /app, /app/compare, /contractors/{id}, /lang/{code}, /designbook
│   ├── routes/auth.py           # /register, /login, /login/demo, /logout
│   ├── routes/account.py        # /account/history, /account/shortlist, /account/shortlist/{id}
│   ├── routes/api.py            # /healthz, /api/meta, /api/recommend, /api/compare, /api/contractors/{id}
│   ├── i18n/                    # ru.json, kk.json, en.json — тексты интерфейса
│   ├── templates/               # Jinja2-шаблоны страниц и общих блоков
│   └── static/                  # CSS, JS, шрифт Geist, SVG-иконки — без CDN
├── scripts/
│   ├── find_demo_queries.py     # печатает сильные демо-запросы по данным
│   ├── check_i18n.py            # полнота словарей ru/kk/en и переводов значений из CSV
├── tests/                       # pytest: данные, настройки, бриф, страницы, авторизация, DoD, объяснения, кабинет, релиз
├── docs/                        # мастер-план, справочник по данным, дизайнбук, задачи локализации
└── var/                         # создаётся при старте: tandau.db, secret_key (не в git)
  • matcher/ не зависит от веба: движок можно вызвать из скрипта или встроить в другой сервис; app/ — только веб-слой поверх него.
  • Каталог загружается из CSV один раз при старте, подбор идёт в памяти.
  • SQLite хранит только пользователей, историю и избранное; подбор от базы не зависит.
  • Внешних сервисов нет: подбор и объяснения работают полностью локально.
  • Справочник по данным и проверенные сценарии — docs/01_REFERENCE.md, общий план — docs/00_MASTER_PLAN.md, дизайн-система — docs/TANDAU_DESIGNBOOK.md.

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

Требования: Python 3.10 или новее, pip, git (или ZIP-архив репозитория). Интернет нужен только для pip install; сам сервис работает офлайн — без CDN, внешних шрифтов и API. Единственное исключение — страница Swagger UI /docs: FastAPI по умолчанию загружает её JS и CSS с CDN.

macOS / Linux

git clone https://github.com/BAITC-Hacks/hack-ce6ddbc4-apex.git tandau
cd tandau
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python run.py

Windows (PowerShell)

git clone https://github.com/BAITC-Hacks/hack-ce6ddbc4-apex.git tandau
cd tandau
python -m venv .venv
.venv\Scripts\Activate.ps1
pip install -r requirements.txt
python run.py
  • Команда python не найдена — используйте py -3 -m venv .venv.
  • PowerShell запрещает Activate.ps1 — выполните в этом же окне Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass и повторите активацию, либо в cmd выполните .venv\Scripts\activate.bat.
  • Без активации тоже можно: .venv\Scripts\python.exe -m pip install -r requirements.txt, затем .venv\Scripts\python.exe run.py.

Без git

GitHub → Code → Download ZIP, распакуйте в папку, путь к которой без кириллицы и пробелов (например, C:\work\tandau), откройте в ней терминал и выполните команды, начиная с создания .venv.

Что вы увидите

INFO:     Started server process [...]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
АдресЧто там
http://127.0.0.1:8000Лендинг
http://127.0.0.1:8000/appПодбор — без регистрации
http://127.0.0.1:8000/docsAPI (Swagger)
http://127.0.0.1:8000/healthz{"status":"ok","profiles":66}

При первом запуске в папке var/ создаются база SQLite tandau.db (с демо-аккаунтом) и ключ подписи сессий secret_key. Остановить сервер — Ctrl+C. Папку var/ можно удалить в любой момент: при следующем запуске всё создастся заново.

Если порт 8000 занят

PORT=8001 python run.py              # macOS / Linux
$env:PORT = "8001"; python run.py    # Windows PowerShell

Затем откройте http://127.0.0.1:8001 и замените порт в ссылках раздела «Как проверить решение».

Вариант: Docker (необязательно)

docker compose up --build

Затем http://127.0.0.1:8000. Основной и проверенный способ — без Docker (выше).

Демо-доступ

Проверка не требует личных аккаунтов участников команды и ключей внешних сервисов.

РежимКак войтиЧто доступно
ГостьНичего не нужно — откройте /appПодбор, сравнение дат, профили подрядчиков, весь JSON API
Демо-аккаунт/login → кнопка «Войти как демо» (без ввода пароля) или email [скрыто], пароль [скрыто]Всё то же + история запросов и избранное
Свой аккаунт/register: имя, email и пароль от 8 символов с буквой и цифройТо же, что у демо-аккаунта

Демо-аккаунт создаётся автоматически при первом запуске; пароль хранится только как scrypt-хэш в var/tandau.db. Аккаунт общий: историю и избранное в нём видят все, кто в него входит. После 5 неудачных попыток входа для одного email вход по паролю на 5 минут приостанавливается; кнопку «Войти как демо» это не блокирует.

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

За 5 минут

  1. python run.py, затем http://127.0.0.1:8000/healthz → {"status":"ok","profiles":66}.
  2. S1 — плотная категория: ранжирование реально работает.
  3. S2 — один запрос на две даты: выдача меняется из-за занятости.
  4. S4 и S5 — «в городе нет такой категории» и «кандидаты есть, но никто не проходит»: объяснено словами, с подсказками.
  5. Обновите страницу S1 или откройте ссылку ещё раз — порядок карточек тот же.
  6. python -m pytest -q — все тесты зелёные.

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

S1 · Плотная категория — ранжирование работает

Алматы · 2026-10-10 · корпоратив · Ведущий · 1 500 000 ₸ · 6 ч — открыть S1

Ожидается баннер «Подобрали» (found). Условиям соответствуют 5 ведущих — Куррапика, Аня Форджер, Хаул, Сон Гоку, Джинбей; показаны три лучших и «ещё 2». Воронка: 15 ведущих → 10 в Алматы → 6 свободны 10.10 → 6 берут корпоратив → 5 в бюджете → 5 по часам. В отсеве с причинами: Буллма, Мицури Канроджи, Кики — заняты; Эмилия — занята и не берёт корпоративы; Софи Хаттер — дороже бюджета.

S2 · Один запрос, две даты — причина в занятости

Алматы · свадьба · Ведущий · 1 000 000 ₸ · казахский · 8 ч

  • 10 октября → partial: Хаул, Сон Гоку. Отсев: Эмилия и Кики заняты 10 октября; Мицури Канроджи занят и не ведёт на казахском; Софи Хаттер дороже бюджета; Куррапика, Джинбей, Аня Форджер, Буллма не берут свадьбы. Подсказка: 3 ведущих подходят 9 октября (Эмилия, Кики, Хаул) и 11 октября (Эмилия, Хаул, Сон Гоку).
  • 17 октября → found: Эмилия (бейдж «цена ориентировочная» — цена проставлена при подготовке данных), Кики, Хаул. Отсев: Сон Гоку занят 17 октября.
  • Сравнить две даты → вошли Эмилия и Кики (были заняты 10.10), выпал Сон Гоку (занят 17.10), остался Хаул.

S3 · Редкая категория

Алматы · 2026-10-10 · той · Ведущий церемонии · 300 000 ₸ · казахский — открыть S3

Ожидается partial: Спайк Спигел (200 000 ₸, свадьба и той, казахский и русский, до 3 ч). Второй профиль категории в Алматы — Субару Нацуки (синтетический) — занят 10.10 и не ведёт тои. Ближайших дат с двумя вариантами нет: вторая карточка возможна только для свадьбы.

Синтетический профиль в выдаче: Алматы · 2026-11-14 · свадьба · Флорист · 300 000 ₸ — открыть → partial: единственный подходящий — Тихиро Огино с бейджем «синтетический профиль»; реальный Тони Тони Чоппер занят 14.11.

S4 · В этом городе такой категории нет

Астана · 2026-11-14 · свадьба · Декоратор · 2 500 000 ₸ — открыть S4

Ожидается баннер «В этом городе такой категории нет» (no_category_in_city) и подсказка: в Алматы — 3 профиля в категории «Декоратор», из них 2 синтетических.

S5 · Кандидаты есть, но никто не проходит

Алматы · 2026-12-26 · той · Ведущий · 700 000 ₸ — открыть S5

Ожидается баннер «Кандидаты есть, но ни один не проходит по условиям» (none_match). Из 10 ведущих Алматы 9 заняты 26.12; свободен только Мицури Канроджи (650 000 ₸), но он не ведёт тои. Тои ведут только Эмилия и Кики (от 900 000 ₸), Сон Гоку (1 000 000 ₸) и Софи Хаттер (2 000 000 ₸) — все дороже 700 000 ₸. Подсказка: «С бюджетом от 900 000 ₸ 27 декабря свободны Эмилия и Кики». В пределах ±14 дней при 700 000 ₸ вариантов нет — это тоже сказано прямо.

S6 · Площадка: «подобрать зал на 14 ноября»

Алматы · 2026-11-14 · свадьба · Банкетный зал · 3 500 000 ₸ — открыть S6

Ожидается partial: Иноскэ Хашибира (2 500 000 ₸; цена и город проставлены при подготовке данных — бейджи) и Гохан (3 200 000 ₸, синтетический профиль). Отсев: Шинобу Кочо, Хината Хьюга, Дарквнесс — заняты; Асуна Юки — занята и дороже; Сосукэ — занят, не берёт свадьбы и дороже.

S7 · Некорректный запрос

Дата вне календаря, например Алматы · 2027-01-15 · корпоратив · Ведущий · 1 500 000 ₸ — открыть S7

Ожидается серый баннер invalid_request: «Календарь есть только на 23.09–31.12.2026».

Кабинет и профиль подрядчика (2 минуты)

  1. Без входа откройте профиль Эмилии: 10 октября занято, 17 октября свободно. Поэтому в сценарии S2 Эмилия выпадает 10.10 и входит 17.10.
  2. Войдите демо-аккаунтом (/login → «Войти как демо» или [email protected] / [скрыто]) и выполните S2.
  3. Нажмите звезду у Эмилии — она появится в «Кабинет → Избранное».
  4. «Кабинет → История» → «Повторить»: та же выдача в том же порядке.

Детерминизм

  • В браузере: откройте ссылку S1 дважды или обновите страницу — тот же порядок карточек и те же тексты.
  • Через API: два одинаковых запроса дают одинаковый JSON; отличается только meta.latency_ms — время ответа.
  • Тестом: python -m pytest -q -k determinism.
# macOS / Linux: два одинаковых запроса, сравнение без поля meta
for i in 1 2; do
  curl -s -X POST http://127.0.0.1:8000/api/recommend -H "Content-Type: application/json" \
    -d '{"city":"Алматы","date":"2026-10-10","event_type":"корпоратив","category":"Ведущий","budget_kzt":1500000,"duration_h":6}' \
  | python -c "import json,sys; d=json.load(sys.stdin); d.pop('meta',None); print(json.dumps(d,sort_keys=True,ensure_ascii=False))" > /tmp/tandau_run$i.json
done
diff /tmp/tandau_run1.json /tmp/tandau_run2.json && echo identical

Проверка через API

curl -s -X POST http://127.0.0.1:8000/api/recommend \
  -H "Content-Type: application/json" \
  -d '{"city":"Алматы","date":"2026-10-10","event_type":"корпоратив","category":"Ведущий","budget_kzt":1500000,"duration_h":6}' \
  | python -m json.tool --no-ensure-ascii

Ожидается: "status": "found"; в cards — три карточки из пяти прошедших (HK-88430, HK-29829, HK-77838, HK-27222, HK-75012), у каждой explanation, badges, score и score_breakdown; "more_count": 2; в rejected — пять отсеянных с кодами причин (busy, format, budget).

Сравнение двух дат (S2):

curl -s -X POST http://127.0.0.1:8000/api/compare \
  -H "Content-Type: application/json" \
  -d '{"request":{"city":"Алматы","event_type":"свадьба","category":"Ведущий","budget_kzt":1000000,"language":"казахский","duration_h":8},"date_a":"2026-10-10","date_b":"2026-10-17"}' \
  | python -m json.tool --no-ensure-ascii

Ожидается: в a — partial с HK-77838 и HK-27222; в b — found с HK-42352, HK-35215, HK-77838; в diff — entered: HK-42352 и HK-35215 (причина busy: были заняты 10.10), left: HK-27222 (причина busy: занят 17.10), stayed: HK-77838.

Windows. Проще всего — http://127.0.0.1:8000/docs → POST /api/recommend → Try it out → вставить JSON → Execute. Или в PowerShell:

$body = '{"city":"Алматы","date":"2026-10-10","event_type":"корпоратив","category":"Ведущий","budget_kzt":1500000,"duration_h":6}'
$r = Invoke-RestMethod -Method Post -Uri http://127.0.0.1:8000/api/recommend -ContentType "application/json; charset=utf-8" -Body ([Text.Encoding]::UTF8.GetBytes($body))
$r.status; $r.cards.id; $r.more_count

В Windows PowerShell 5.1 кириллица в выводе может отображаться неверно — это особенность консоли; статус и id латиницей читаются.

API

Интерактивная документация — http://127.0.0.1:8000/docs (Swagger UI, запросы можно отправлять из браузера; сама страница загружает JS и CSS с CDN). Схема — http://127.0.0.1:8000/openapi.json, работает офлайн. Для JSON API не нужны вход и CSRF-токен.

МетодПутьЧто делает
GET/healthzПроверка живости: {"status":"ok","profiles":66}
GET/api/metaСправочники для формы: города, категории с количеством по городам, форматы, языки, окно дат
POST/api/recommendПодбор: SearchRequest → SearchResponse
POST/api/compareОдин запрос на две даты: {request, date_a, date_b} → {date_a, date_b, a, b, diff}; 422, если даты совпадают
GET/api/contractors/{id}Профиль подрядчика и его занятость; неизвестный id → 404

Коды ответа /api/recommend: 200 — запрос прочитан, в том числе если он невыполним: status: invalid_request с перечнем ошибок в summary и hints (дата вне окна, неизвестный город, бюджет не больше нуля); 422 — запрос нельзя прочитать (битый JSON, нет обязательного поля, неверный тип).

SearchRequest — значения на русском, как в данных:

ПолеТипОбязательноЗначения
cityстрокадаАлматы, Астана, Зарубежье
dateстрока YYYY-MM-DDда2026-09-23 … 2026-12-31
event_typeстрокадасвадьба, той, корпоратив, конференция, юбилей, день рождения
categoryстрокадаодна из 17 категорий (/api/meta)
budget_kztцелоедабольше нуля, ₸
duration_hцелоенетдлительность, 1–24 ч
languageстроканетрусский, казахский, английский
wishesстроканетсвободный текст до 500 символов: влияет на смысловую релевантность и выбор цитаты
langстроканетязык текстов объяснений: ru (по умолчанию), kk, en

SearchResponse:

ПолеЧто внутри
statusfound · partial · no_category_in_city · none_match · invalid_request
summaryОдна строка для баннера
cardsДо 3 карточек: id, name, category, city, price_from_kzt, badges, explanation, atoms, score, score_breakdown
more_countСколько ещё подходят, но ниже в рейтинге
rejectedОтсеянные: id, name, reasons (busy, format, budget, language, duration), details
reason_countsСколько раз встречается каждая причина отсева
funnelВоронка фильтров: шаг и сколько кандидатов осталось
hintsПодсказки: ближайшие даты, бюджет, другой город, что исправить в запросе
metaengine (версия движка), explain (версия объяснений), llm_used (всегда false), latency_ms
why_not«Почему не больше»: группы по первичной причине — reason, ids, готовый text

Страницы: / — лендинг · /app — подбор (параметры запроса — в адресной строке: city, date, event_type, category, budget, duration, language, wishes; ссылкой можно поделиться) · /app/compare — две даты (те же параметры + date_a, date_b) · /contractors/{id} — профиль · /register, /login, /login/demo (только POST — кнопка «Войти как демо» на /login), /logout (только POST) · /account/history (+ POST /account/history/clear), /account/shortlist (+ POST /account/shortlist/{id} — звезда) — для вошедших · /lang/{ru|kk|en} — язык · /designbook, /designbook/landing — служебный дизайнбук интерфейса. HTML-страницы входа и кабинета скрыты из Swagger: формы с CSRF оттуда не отправить.

Данные

Файл data/hackathon-dataset-anonymized.csv — анонимизированный каталог event-подрядчиков от организаторов (кейс Firebird): 66 профилей, 13 колонок, UTF-8. Загружается один раз при старте; в репозитории лежит без изменений, своих профилей мы не добавляли.

ПолеПримерКак читаем
idHK-39372Уникальный идентификатор
anon_nameТони Тони ЧопперАнонимизированное имя
categoriesБанкетный зал|Загородная площадка|РесторанСписок через |; сравнение по элементу: «Ведущий» ≠ «Ведущий церемонии»
cityАлматыТочное совпадение: Алматы, Астана, Зарубежье
price_from_kzt200000Цена «от» за мероприятие, ₸
event_formatsсвадьба|корпоративСвадьба, той, корпоратив, конференция, юбилей, день рождения
languagesрусский|английскийРусский, казахский, английский
max_hours6 или пустоПусто — работа не привязана к присутствию (флорист, декоратор, сувениры)
busy_dates2026-09-25|2026-09-30Занятые даты в окне 23.09–31.12.2026
descriptionсвободный текстИсточник фактов и цитат; жёстких фильтров по описанию нет
synthetic, price_imputed, city_imputedTrue / FalseФлаги качества данных

Каталог в цифрах: Алматы 50 · Астана 15 · Зарубежье 1. 17 категорий: плотные — Ведущий 15, Фотограф 12, Банкетный зал 8; редкие, по 3 профиля, — Флорист, Декоратор, Подарки и сувениры, Ведущий церемонии, Фото и видеобудки, Отель, Инструменталист. Занятость — в среднем 50.6 дня из 100: сентябрь–ноябрь 30–50 %, декабрь 70–80 %.

Календарь в профиле подрядчика строится из busy_dates, окно 23.09–31.12.2026 (100 дней). Счётчики («ближайшие 30 дней», по месяцам) считаются от 23.09, а не от текущей даты — так ответы детерминированы.

Флаги и как они показаны в интерфейсе:

ФлагСколькоЧто значитКак видно в интерфейсе
synthetic13 (HK-90001…HK-90013)Синтетический профиль из датасета организаторов, не реальный подрядчикБейдж «синтетический профиль» на карточке и в профиле; в подсказках синтетические считаются отдельно («из них 2 синтетических»)
price_imputed18Цена «от» проставлена при подготовке данныхБейдж «цена ориентировочная»; в объяснении — просьба уточнить цену
city_imputed8 (6 реальных банкетных залов, фотобудка HK-35846, ансамбль HK-19103)Город проставлен при подготовке данныхБейдж «город уточняется»
вычисляется—Цена «от» равна бюджетуБейдж «цена «от» = бюджет»: итог может оказаться выше

Внешние сервисы: для работы не нужны; ключи API не требуются.

Формула скоринга

Жёсткие условия — дата, формат, бюджет, язык, часы — в балл не входят: они отсекают. Балл считается только для прошедших и отвечает на вопрос «кто из подходящих подходит лучше»:

score = 0.30·budget_fit + 0.25·relevance + 0.20·focus + 0.15·experience + 0.10·extras − 0.05·flags

Каждый компонент — от 0 до 1. Положительные веса в сумме дают 1.00, а штраф flags вычитает не больше 0.05, поэтому итоговый балл лежит в диапазоне от −0.05 до 1.00.

КомпонентКлюч в score_breakdownКак считаетсяВес
Запас бюджетаbudget_fit1 − цена «от» / бюджет, обрезано до [0, 1]: доля бюджета, которая остаётся. 900 000 ₸ при бюджете 1 000 000 ₸ → 0.10; цена «от» = бюджет → 00.30
Смысловая близостьrelevanceКосинус TF-IDF (символьные n-граммы 3–5) между описанием и запросом (синонимы формата, традиции, категория, язык, пожелания wishes), делённый на максимум среди прошедших: 1.0 у самого близкого0.25
СпециализацияfocusДоля форматов профиля из «семейства» запроса: {свадьба, той}, {корпоратив, конференция}, {юбилей, день рождения}. Берёт только свадьбы и тои при запросе свадьбы → 1.00.20
Опытexperience½·min(стаж / 15, 1) + ½·min(ln(1 + число мероприятий) / ln 1001, 1) — стаж и объём работ из описания0.15
ДополнительноextrasСреднее трёх: дополнительные языки min(n, 2) / 2; запас часов min(max(запас, 0), 4) / 4; 1, если в описании названы традиции или деловые форматы запроса, у площадки 2+ типа (например, зал и отель) или масштаб от 500 гостей0.10
Флаги данных (штраф)flagsmin(1, 0.6·синтетический + 0.2·цена проставлена + 0.2·город проставлен)−0.05
  • Список компонентов и веса заданы в одном месте — WEIGHTS в matcher/scoring.py.
  • Разбивка каждого балла — поле score_breakdown карточки: шесть значений компонентов до весов. В интерфейсе под карточкой раскрывается «Как посчитан балл»: значение, вес и вклад каждого фактора.
  • Компоненты и балл округляются до 4 знаков, поэтому вклады в раскрывашке сходятся с итогом. Порядок — по (−score, цена «от», id): при равном балле выше тот, кто дешевле, затем по id. Случайности нет.
  • Веса подобраны вручную и не обучались: обучение на 66 записях — вне задачи.

Переменные окружения

Все переменные необязательны: без .env приложение запускается с безопасными значениями по умолчанию.

ПеременнаяПо умолчаниюНазначение
HOST127.0.0.1Адрес сервера (в Docker — 0.0.0.0)
PORT8000Порт сервера
SECRET_KEYпусто → генерируется и сохраняется в var/secret_keyПодпись cookie сессии
DATABASE_PATHvar/tandau.dbФайл SQLite: пользователи, история, избранное; относительный путь считается от корня проекта
LLM_MODEoffЗарезервировано для будущего LLM-слоя; в этой версии ни на что не влияет
OPENAI_API_KEYпустоЗарезервировано; в этой версии не используется
OPENAI_MODELпустоЗарезервировано; в этой версии не используется
OPENAI_BASE_URLпустоЗарезервировано; в этой версии не используется

Как задать: cp .env.example .env (Windows: Copy-Item .env.example .env) и отредактировать .env. Переменные окружения процесса важнее значений из файла. .env в .gitignore и в репозиторий не попадает. При смене SECRET_KEY или удалении var/secret_key ранее выданные сессии становятся недействительными.

Тесты

python -m pytest -q

Ожидается строка N passed без failed и error, несколько секунд.

ФайлЧто проверяет
tests/test_data.pyЗагрузка CSV: 66 профилей, флаги 13 / 18 / 8, 17 категорий, пустой max_hours, даты в окне, категории по элементу списка, отказ на битых данных
tests/test_config.pyЧтение .env, приоритет окружения, сохранение и приватность SECRET_KEY, запуск из другой папки
tests/test_i18n.pyПеревод с запасным русским, подстановки, порядок выбора языка
tests/test_brief.pyБриф лендинга: нормализация пяти условий, адрес /app, ошибки ввода
tests/test_pages.pyЛендинг на разных языках, переключатель языка и безопасные редиректы, /healthz, /api/meta, /docs, /app
tests/test_auth.pyРегистрация, вход, выход, демо-аккаунт, CSRF, ограничение попыток, защищённые страницы
tests/test_engine_dod.pyДвижок: пять статусов, причины отсева, сценарии, детерминизм
tests/test_explanations.pyФакты и цитаты из описаний, объяснения без стоп-фраз, различимые без имён, с числами или цитатой; подсказки и сравнение дат
tests/test_account.pyИстория запросов, избранное, профиль подрядчика и его API
tests/test_release.pyСценарии S1–S7 через HTTP API, сравнение дат, бейджи, детерминизм, время ответа, полнота README, отсутствие ключей, работа без LLM и секретов

Проверка словарей интерфейса: python scripts/check_i18n.py.

LLM-режим

LLM-режим в эту версию не входит: переменные LLM_MODE и OPENAI_* из .env.example зарезервированы и ни на что не влияют; подбор и объяснения полностью детерминированы.

Ограничения

  • Каталог — 66 анонимизированных профилей, календарь занятости только на 23.09–31.12.2026 (100 дней). Запросы на другие даты честно отклоняются (invalid_request).
  • 13 профилей синтетические (HK-90001…HK-90013) — они пришли в датасете организаторов, своих мы не добавляли. В Астане единственные банкетный зал, отель, флорист, сувениры, ведущий церемонии и фотобудка — синтетические. Все помечены бейджем.
  • Часть значений проставлена при подготовке данных: цена у 18 профилей, город у 8. Они помечены бейджами, в объяснении предлагаем уточнить.
  • Цена в данных — «от»: итоговая стоимость может быть выше, бюджет сравнивается с ценой «от». Если они равны — предупреждаем бейджем.
  • Факты из описаний извлекаются правилами (числа, в том числе словами, стаж, объёмы) и могут пропускать нестандартные формулировки. Маркетинговые и личные детали в объяснения намеренно не попадают.
  • Профили с одинаковыми полями (например, ансамбли с близкой ценой, одинаковыми языками и часами) различаются в объяснениях только описанием — цитатой и фактами из него.
  • Цитаты из описаний — на русском, как в оригинале, даже в казахском и английском интерфейсе; помечены «по описанию».
  • Длительность не проверяется, если у профиля нет ограничения по часам (флористы, декораторы, сувениры — 9 профилей).
  • Веса скоринга подобраны вручную и не обучались на поведении пользователей — таких данных нет.
  • Прототип для одного процесса: SQLite и подписанные cookie-сессии, без HTTPS. Бронирования, заявок и уведомлений подрядчикам нет — это вне задачи.
  • История и избранное хранятся в локальной SQLite (var/, не в репозитории); на чистом клоне база пустая. Демо-аккаунт общий для всех, кто в него входит. Время в истории — UTC+5 (Астана).
  • Лимита регистраций с одного IP нет: массовую регистрацию замедляет только scrypt. Вход по паролю ограничен 5 неудачами на email за 5 минут.
  • Swagger UI (/docs) загружает JS и CSS с CDN (поведение FastAPI по умолчанию) и без интернета не откроется; JSON API и /openapi.json работают офлайн.
  • Развёрнутой версии нет — запуск локально по инструкции выше.
  • Казахские термины, которых нет в официальной KZ-версии ТЗ (например, «Лайв-бэнд», «Загородная площадка»), переведены командой.

Использованные компоненты, лицензии и AI-инструменты

Раскрытие по п. 5.4.4 Положения.

КомпонентВерсия (requirements)ЛицензияЗачем
FastAPI>=0.110,<1.0MITВеб-фреймворк, JSON API, Swagger /docs
Starlette (зависимость FastAPI)ставится с FastAPIBSD-3-ClauseМаршрутизация, SessionMiddleware, статика
Pydantic (зависимость FastAPI)ставится с FastAPIMITВалидация JSON API
Uvicorn>=0.27,<1.0BSD-3-ClauseASGI-сервер (python run.py)
Jinja2>=3.1,<4.0BSD-3-ClauseСерверные шаблоны страниц
itsdangerous>=2.1,<3.0BSD-3-ClauseПодпись cookie сессии
python-multipart>=0.0.9Apache-2.0Разбор HTML-форм
pytest>=8.0,<9.0MITАвтотесты
httpx>=0.27,<0.28BSD-3-ClauseТестовый HTTP-клиент (TestClient)
Шрифт Geist (app/static/fonts/)1.7.2, из npm-пакета geist, без измененийSIL Open Font License 1.1 (Geist-OFL.txt)Основной шрифт интерфейса; лежит в репозитории, npm-зависимостей нет
Стандартная библиотека Python: sqlite3, hashlib, csv, json3.10+PSFБаза данных, хэш паролей, чтение данных

Данные: data/hackathon-dataset-anonymized.csv — анонимизированный каталог 66 event-подрядчиков, предоставлен организаторами хакатона (кейс Firebird); используется без изменений.

Модели: в основном режиме никакие ML-модели и эмбеддинги не используются — TF-IDF по символьным n-граммам реализован в matcher/evidence.py.

Внешние ресурсы интерфейса: CSS, JavaScript, SVG-иконки и шрифт Geist лежат в репозитории; CDN и внешние шрифты страницы сервиса не подключают. Исключение — стандартная страница Swagger UI /docs: её JS и CSS FastAPI загружает с CDN.

AI-инструменты разработки: в разработке использовались два AI-агента для программирования — Codex (OpenAI) и Claude Code (Anthropic); использование AI-агента — условие организаторов. С их помощью по поэтапным спецификациям генерировались код, тесты, переводы и документация, включая этот README. Каждый шаг команда проверяла автотестами и ручным прогоном сценариев S1–S7. Сам сервис во время работы не требует никакой AI-модели: подбор и объяснения полностью детерминированы.

Языки интерфейса

Русский (ru, по умолчанию), казахский (kk) и английский (en). Плоские JSON-словари находятся в app/i18n/. Язык выбирается по ?lang=, затем cookie lang, затем предпочтению пользователя и наконец ru. Переключатель сохраняет cookie на год.

Отсутствующий перевод берётся из русского словаря; если ключ отсутствует и там, показывается сам ключ. Значения каталога внутри модели и API остаются на русском, как в CSV; для интерфейса используются ключи data.*. Переводы основаны на глоссарии из docs/01_REFERENCE.md; участник 2 может дополнительно проверить казахскую редактуру.

Команда

apex — HackAlem AI, трек 06 «Креативные индустрии». Репозиторий: https://github.com/BAITC-Hacks/hack-ce6ddbc4-apex

УчастникРольGitHub
Kassymzhomart ShubayГлавный разработчик: движок подбора, веб-приложение, тесты, READMEk4ssymzhomart
YasminЛокализация RU/KZ/EN, тексты интерфейса, логотип, скриншоты, проверка воспроизводимостиyassmn126

English summary

Tandau is a prototype for HackAlem AI (track 06 Creative Industries, Firebird case "Smart contractor matching"). Given a city, date, event type, contractor category and budget (optionally duration and language), it returns up to three event contractors from a catalog of 66 anonymised profiles. Each card comes with a 1–2 sentence explanation built from verifiable facts: price vs budget, formats, languages, hours, experience and a quote from the profile. When fewer than three match, Tandau says why (booked on the date, over budget, format not offered, no such category in the city) and suggests nearby dates, a budget or another city.

  • Run: Python 3.10+ → python -m venv .venv → activate → pip install -r requirements.txt → python run.py → http://127.0.0.1:8000
  • Access: no sign-up needed; demo account [email protected] / Demo2026!; no API keys required — no LLM is used at runtime.
  • Verify: scenarios S1–S7 above (S1 found, S2 two dates, S4 no_category_in_city, S5 none_match, S7 invalid_request), the JSON API at /docs, tests with python -m pytest -q.
  • Deterministic: the same request always returns the same order and the same JSON (except meta.latency_ms).
  • Stack: FastAPI, Jinja2, SQLite (standard library), a pure-Python matching engine with its own TF-IDF; local Geist font, no CDN in the product pages; UI in Russian, Kazakh and English.
  • AI disclosure: built with the help of the Codex and Claude Code coding agents; the service itself needs no AI model at runtime.
Звёзды
⭐ 0
Форки
0
Язык
Python
Последний push
в прошлом месяце
Коммиты
37
Авторы
2
Файлы
167
Ветки
1

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

k4ssymzhomart 35yasmnn 2

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

  • 2026-09-23 17:49Update README.mdyasmnn
  • 2026-09-23 17:54Merge remote-tracking branch 'origin/main' into integrate-4567k4ssymzhomart
  • 2026-09-23 17:53Merge branch 'claude/phase-7-implementation-614457' into integrate-4567k4ssymzhomart
  • 2026-09-23 17:53Merge branch 'claude/phase-6-implementation-bc7217' into integrate-4567k4ssymzhomart
  • 2026-09-23 17:52Merge branch 'claude/phase-5-implementation-b288fb' into integrate-4567k4ssymzhomart
  • 2026-09-23 17:47Merge branch 'claude/phase-3-implementation-a1bf11'k4ssymzhomart
  • 2026-09-23 17:47Merge branch 'claude/phase-2-implementation-11e9f4'k4ssymzhomart
  • 2026-09-23 17:47feat: landing, guest brief and /app summary (phase 1)k4ssymzhomart
Все коммиты (37)
  • 2026-09-23 17:47feat: registrationk4ssymzhomart
  • 2026-09-23 17:38fix: reject lone surrogates in login/logout forms instead of 500k4ssymzhomart
  • 2026-09-23 17:22fix: keep wishes out of phase 5 links; whynot marker for phase 4 page testsk4ssymzhomart
  • 2026-09-23 17:22merge: phase 4 into phase 5 (engine + explanations integrated)k4ssymzhomart
  • 2026-09-23 17:19fix: use phase 3 current_user name in /app fallbackk4ssymzhomart
  • 2026-09-23 17:18docs: readme fact-check fixesk4ssymzhomart
  • 2026-09-23 17:18feat: return guests to the same /app results after logink4ssymzhomart
  • 2026-09-23 17:17feat: phase 6 hooks on /app (history, shortlist toggle, profile links)k4ssymzhomart
  • 2026-09-23 17:16feat: account history, shortlist, contractor profilesk4ssymzhomart
  • 2026-09-23 17:15feat: matching engine, /api/recommend and /app results (phase 4)k4ssymzhomart
  • 2026-09-23 17:14chore: docker and release hygienek4ssymzhomart
  • 2026-09-23 17:14docs: readmek4ssymzhomart
  • 2026-09-23 17:14test: dod suitek4ssymzhomart
  • 2026-09-23 16:54feat: login & sessionsk4ssymzhomart
  • 2026-09-23 16:53chore: vendor Phase 2 db/security/web snapshot for Phase 3 developmentk4ssymzhomart
  • 2026-09-23 16:52feat: contrastive explanations, hints, date comparek4ssymzhomart
  • 2026-09-23 16:51chore: vendor Phase 2 auth styles, flash partial and test DB fixturesk4ssymzhomart
  • 2026-09-23 16:49scaffold: phase 4 matcher core from spec (base for phase 5)k4ssymzhomart
  • 2026-09-23 16:48chore: vendor Phase 2 db/security/web snapshot for Phase 3 developmentk4ssymzhomart
  • 2026-09-23 16:19docs: reconcile Phase 1 plan with concurrent design integrationk4ssymzhomart
  • 2026-09-23 16:17docs: define Phase 1 landing and guest brief after Phase 0k4ssymzhomart
  • 2026-09-23 16:17design: apply Tandau designbook to live product pagesk4ssymzhomart
  • 2026-09-23 15:59Redesign Tandau inputs and recommendations with full landing samplek4ssymzhomart
  • 2026-09-23 15:33Refine Tandau designbook and define ten branded asset briefsk4ssymzhomart
  • 2026-09-23 15:19design: use selected vivid blue wave backgroundk4ssymzhomart
  • 2026-09-23 15:15feat: add Tandau designbook and team implementation issuesk4ssymzhomart
  • 2026-09-23 14:51feat: landing page with live catalog stats and language switchk4ssymzhomart
  • 2026-09-23 14:51feat: skeleton, catalog loader and configurationk4ssymzhomart
  • 2026-09-23 13:47Add entry 'Zhyl bil Yasmin i Kassym' to databaseyasmnn
Структура репозитория (167 файлов)
.dockerignore
.env.example
.gitattributes
.gitignore
Dockerfile
README.md
app/__init__.py
app/brief.py
app/config.py
app/db.py
app/deps.py
app/i18n.py
app/i18n/en.json
app/i18n/kk.json
app/i18n/ru.json
app/main.py
app/profiles.py
app/routes/__init__.py
app/routes/account.py
app/routes/api.py
app/routes/auth.py
app/routes/contractors.py
app/routes/login.py
app/routes/pages.py
app/search.py
app/security.py
app/static/assets/tandau/archive/chain-3d.png
app/static/assets/tandau/archive/cobalt-grid-strip.png
app/static/assets/tandau/archive/documents-3d.png
app/static/assets/tandau/archive/fluid-card.png
app/static/assets/tandau/archive/glass-wave-blue-edge.png
app/static/assets/tandau/archive/glass-wave-wide.png
app/static/assets/tandau/archive/grid-3d.png
app/static/assets/tandau/archive/restricted-cube-3d.png
app/static/assets/tandau/archive/stack-3d.png
app/static/assets/tandau/archive/timer-3d.png
app/static/assets/tandau/asset-briefs.json
app/static/assets/tandau/img/tandau-brief-1200.jpg
app/static/assets/tandau/img/tandau-brief-640.jpg
app/static/assets/tandau/img/tandau-budget-1200.jpg
app/static/assets/tandau/img/tandau-budget-640.jpg
app/static/assets/tandau/img/tandau-calendar-1200.jpg
app/static/assets/tandau/img/tandau-calendar-640.jpg
app/static/assets/tandau/img/tandau-camera-1200.jpg
app/static/assets/tandau/img/tandau-camera-640.jpg
app/static/assets/tandau/img/tandau-choice-tray-1200.jpg
app/static/assets/tandau/img/tandau-choice-tray-640.jpg
app/static/assets/tandau/img/tandau-complete-1200.jpg
app/static/assets/tandau/img/tandau-complete-640.jpg
app/static/assets/tandau/img/tandau-floral-arrangement-1200.jpg
app/static/assets/tandau/img/tandau-floral-arrangement-640.jpg
app/static/assets/tandau/img/tandau-microphone-1200.jpg
app/static/assets/tandau/img/tandau-microphone-640.jpg
app/static/assets/tandau/img/tandau-open-folder-1200.jpg
app/static/assets/tandau/img/tandau-open-folder-640.jpg
app/static/assets/tandau/img/tandau-shortlist-1200.jpg
app/static/assets/tandau/img/tandau-shortlist-640.jpg
app/static/assets/tandau/img/tandau-venue-1200.jpg
app/static/assets/tandau/img/tandau-venue-640.jpg
app/static/assets/tandau/manifest.json
app/static/assets/tandau/tandau-icons.svg
app/static/css/account.css
app/static/css/app.css
app/static/css/auth.css
app/static/css/components.css
app/static/css/designbook-product.css
app/static/css/designbook.css
app/static/css/explain.css
app/static/css/landing_sample.css
app/static/css/search.css
app/static/fonts/Geist-OFL.txt
app/static/fonts/Geist-Variable.woff2
app/static/fonts/README.md
app/static/fonts/geist-provenance.json
app/static/img/favicon.svg
app/static/js/app.js
app/static/js/auth.js
app/static/js/brief.js
app/static/js/designbook-product.js
app/static/js/designbook.js
app/static/js/landing_sample.js
app/static/js/search.js
app/templates/account/_nav.html
app/templates/account/history.html
app/templates/account/shortlist.html
app/templates/app/compare.html
app/templates/app/contractor.html
app/templates/app/placeholder.html
app/templates/app/request.html
app/templates/app/search.html
app/templates/auth/login.html
app/templates/auth/register.html
app/templates/base.html
app/templates/designbook.html
app/templates/designbook_assets.html
app/templates/designbook_inputs.html
app/templates/designbook_recommendations.html
app/templates/landing.html
app/templates/landing_sample.html
app/templates/partials/auth_nav.html
app/templates/partials/brief_form.html
app/templates/partials/card.html
app/templates/partials/event_card.html
app/templates/partials/fav_button.html
app/templates/partials/flash.html
app/templates/partials/footer.html
app/templates/partials/funnel.html
app/templates/partials/guest_banner.html
app/templates/partials/header.html
app/templates/partials/hints.html
app/templates/partials/icons.html
app/templates/partials/lang_switch.html
app/templates/partials/result_card.html
app/templates/partials/result_card_v2.html
app/templates/partials/search_extras.html
app/templates/partials/search_form.html
app/templates/partials/search_results.html
app/templates/partials/status_banner.html
app/templates/partials/why_not.html
app/templates/partials/whynot.html
app/web.py
data/hackathon-dataset-anonymized.csv
database.json
docker-compose.yml
docs/00_MASTER_PLAN.md
docs/01_REFERENCE.md
docs/ISSUE_i18n_and_minor_tasks.md
docs/PHASE_1_FOUNDATION_AND_LANDING.md
docs/PHASE_4_INTEGRATION.md
docs/TANDAU_ASSET_BRIEFS.md
docs/TANDAU_ASSET_INVENTORY.md
docs/TANDAU_DESIGNBOOK.md
docs/issues/00-design-system-rollout.md
docs/issues/01-i18n-audit.md
docs/issues/02-responsive-accessibility-qa.md
docs/issues/03-tandau-assets.md
docs/issues/04-demo-readme-check.md
matcher/__init__.py
matcher/compare.py
matcher/data.py
matcher/engine.py
matcher/evidence.py
matcher/explain.py
matcher/facts.py
matcher/filters.py
matcher/hints.py
matcher/models.py
matcher/scoring.py
matcher/validation.py
requirements-llm.txt
requirements.txt
run.py
scripts/find_demo_queries.py
tests/conftest.py
tests/test_account.py
tests/test_auth.py
tests/test_brief.py
tests/test_config.py
tests/test_data.py
tests/test_engine_dod.py
tests/test_explanations.py
tests/test_i18n.py
tests/test_landing.py
tests/test_login.py
tests/test_pages.py
tests/test_release.py
tests/test_search_page.py

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

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

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