KaiBridge AI
Платформа Challenge Hub: бизнес через AI-интервью превращает черновик проблемы в карточку задачи с Readiness Score и публикует её, студенты…
О проекте
Команда: Kai Education
Кейс HackAlem.ai: Образование — AI Sana
Стадия: Рабочий MVP
Задача кейса: Challenge Hub: превратить задачу бизнеса в задание для студенческих команд
Суть
Платформа Challenge Hub: бизнес через AI-интервью превращает черновик проблемы в карточку задачи с Readiness Score и публикует её, студенты откликаются, бизнес вручную выбирает команду.
Как устроено
React+Vite обращается к FastAPI, данные хранятся в SQLite. Общий AIWorkflow ведёт цепочку draft → analysis → questions → answers → card proposal и проверяет схемы и точные цитаты с source_id. Карточка применяется только после подтверждения человеком. Провайдер подключается через единый интерфейс: детерминированный DemoAIProvider без сети либо OpenAIProvider (Responses API + Pydantic Structured Outputs). Readiness Score считается Python-кодом. Есть роли, cookie-сессии и demo-верификация аккаунтов.
Ключевые решения
- structured outputs
- проверка цитат-источников (source_id)
- human-in-the-loop confirmation
- детерминированный readiness scoring
- provider abstraction
Особенности
Явный и честно подписанный DEMO-провайдер (баннер «не является LLM inference»), который проходит тот же workflow и валидацию цитат, что и OpenAI. Скрытого fallback с OpenAI на demo нет. Заявлено 94 backend-теста.
Стек
Python · FastAPI · SQLite · React · Vite · unittest
Модели
gpt-5.4-mini
Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-20246846-kai-education.
README
README.mdKaiBridge AI
MVP для HackAlem AI / AI Sana Hackathon: бизнес описывает проблему, уточняет её через интервью, проверяет предложенную карточку и публикует задачу. Студенты отправляют предложения, бизнес вручную выбирает команду. Readiness Score показывает полноту карточки и считается Python-кодом, а не моделью.
Архитектура: React + Vite → FastAPI → SQLite. Общий AIWorkflow выполняет проверку схем и цитат, сохраняет анализ/предложение отдельно и применяет карточку только после подтверждения человека. Через один интерфейс подключается либо локальный DemoAIProvider, либо настоящий OpenAIProvider (Responses API + Structured Outputs). Аккаунты, роли и серверные cookie-сессии относятся к этапу 5A; demo AI не изменяет права доступа. backend/agent.py и backend/tools.py остаются неиспользуемыми заготовками.
Актуальный путь независимой проверки — quick-start и сценарий DEMO ниже. Разделы этапов 1–4 дальше в README сохраняют историю: их старые Swagger/PowerShell-примеры без cookie и Origin не являются актуальной инструкцией запуска.
Подготовка (Windows PowerShell)
Нужны Python 3.11 и Node.js 24 LTS (проверено на 24.18.0), Git.
git clone https://github.com/BAITC-Hacks/hack-20246846-kai-education.git
cd hack-20246846-kai-education
py -3.11 -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
cd frontend
npm ci
cd ..
if (!(Test-Path -LiteralPath .env)) { Copy-Item -LiteralPath .env.example -Destination .env }
В свежем клоне .env.example включает AI_PROVIDER=demo, а OPENAI_API_KEY пуст. OpenAI account, подписка и API-ключ для этого сценария не нужны. Если корневой .env уже существует, не перезаписывайте его: установите в нём AI_PROVIDER=demo вручную. Переменные окружения процесса имеют приоритет над .env; убедитесь, что они не задают другой provider.
Два режима AI
1. DEMO MODE — независимая техническая проверка
AI_PROVIDER=demo
OPENAI_API_KEY=
Это локальный deterministic provider: одни и те же входные данные дают одинаковые вопросы и содержимое предложения. Он не делает сетевых AI-запросов, не требует OpenAI account/API key и не является реальным LLM inference. Позволяет проверить весь workflow: draft → analysis → questions → answers → card proposal → source validation → human confirmation → readiness. UUID записей анализа/предложения создаются сервером заново и не обязаны совпадать.
Provider использует текст draft только как исходный контекст, а не пытается понимать его смысл. Вопросы построены по фиксированным категориям и содержат фрагмент текущего описания. Ответ бизнеса переносится целиком в соответствующее поле с точной цитатой и source_id. Неотвеченные поля остаются неизвестными; название и отрасль заполняются человеком. Текст длиннее лимита поля не обрезается с потерей смысла: контекст можно уточнить ответом на соответствующий вопрос.
На странице виден баннер «Демонстрационный AI-режим» с пояснением об отсутствии LLM. Это симуляция для проверки интеграции и человеческого подтверждения, а не доказательство качества настоящего AI или автоматическая гарантия допуска организаторами.
2. OPENAI MODE — настоящая интеграция
AI_PROVIDER=openai
OPENAI_API_KEY=your_key_here
OPENAI_MODEL=gpt-5.4-mini
Замените your_key_here своим действующим серверным ключом только в корневом .env. Здесь выполняются настоящие запросы через OpenAI Responses API с Pydantic Structured Outputs; требуется доступ к модели и API-биллинг. Ключ не помещается во frontend или Git. Реальные вызовы могут тарифицироваться.
Режим выбирается явно. Если AI_PROVIDER нигде не задан, сохраняется прежний режим openai, а не demo. При отсутствии ключа/модели или ошибке OpenAI остаётся прежний ручной fallback; скрытого переключения OpenAI → demo нет. Неизвестный AI_PROVIDER останавливает запуск с понятной ошибкой. После изменения режима перезапустите backend и обновите страницу. Для незавершённого интервью заново выполните анализ: demo не переинтерпретирует вопросы, сохранённые другим provider.
Запуск
В первом терминале из корня проекта:
.\.venv\Scripts\python.exe -m uvicorn backend.main:app --reload --host 127.0.0.1 --port 8000
Во втором терминале из корня проекта:
cd frontend
npm run dev -- --host localhost --port 5173 --strictPort
Открыть http://localhost:5173. Каталог доступен без входа. Для создания задачи зарегистрируйте бизнес-аккаунт; для отклика — студенческий. Публикация, решения по откликам и отправка откликов требуют demo-подтверждения по инструкции 5A ниже.
Backend: http://localhost:8000/health возвращает {"status":"ok","ai_provider":"demo"} для quick-start либо ai_provider="openai" в реальном режиме. Ключей и секретов в диагностике нет; status=ok означает доступность backend, а не проверку доступа к OpenAI. Документация API: http://localhost:8000/docs. Используйте localhost для обоих приложений: смешивание localhost и 127.0.0.1 мешает cookie-сессии. Swagger показывает схемы, но его POST/PATCH из backend-origin блокируются защитой Origin; ручной сценарий выполняйте через frontend.
Используйте именно localhost:5173 для frontend: этот адрес разрешён настройкой CORS. Если порт занят, проверьте уже запущенные терминалы; повторный запуск не нужен. Для остановки нажмите Ctrl+C в соответствующем терминале.
Полная независимая проверка в DEMO MODE
Все действия ниже выполняются на http://localhost:5173. В новом клоне SQLite создаётся автоматически; заранее подготовленная база и сохранённая сессия не нужны.
- Проверьте баннер «Демонстрационный AI-режим» и ai_provider="demo" в /health. Если баннера нет, проверьте AI_PROVIDER и перезапустите backend. Demo-провайдер не следует путать с demo-verification аккаунта: первое воспроизводит AI-интервью, второе только разрешает защищённые действия.
- Нажмите «Регистрация», выберите «Бизнес», укажите имя, email
[email protected]и придумайте тестовый пароль от 8 символов. Доставка писем не требуется. После регистрации интерфейс выполнит вход. - Нажмите «Запросить проверку». При статусе «На проверке» из корня проекта выполните:
.\.venv\Scripts\python.exe -m backend.demo_identity verify --email [email protected] --demo
- В интерфейсе нажмите «Обновить статус»: ожидается «Подтверждён». Это только явно обозначенное demo-подтверждение, не проверка документов или компании.
- Введите draft:
У нас небольшой магазин. Менеджеры тратят время на одинаковые вопросы клиентов. Хотим упростить обработку обращений.Нажмите «Проанализировать с AI». Ожидается интервью с минимум тремя вопросами, в текущем demo — девятью. Не переходите на «Заполнить вручную»: проверяется именно маршрут анализа и предложения. - Ответьте на вопросы по их категориям. Можно использовать следующие тестовые сведения:
| Категория | Пример ответа бизнеса |
|---|---|
| Контекст | Небольшой магазин бытовых товаров; обращения приходят по электронной почте. |
| Потребность | Сократить время менеджеров на повторяющиеся вопросы клиентов. |
| Пользователи | Менеджеры магазина. |
| Данные и материалы | Предоставим FAQ и 30 обезличенных примеров переписки. |
| Ограничения | Русский язык; без CRM и персональных данных клиентов. |
| Ожидаемый результат | Веб-прототип, предлагающий менеджеру ответ по FAQ. |
| Критерии успеха | Минимум 16 правильных ответов из 20 по утверждённому FAQ. |
| Контакт | [email protected] |
| Взаимодействие | Переписка по почте и один созвон в неделю. |
- Нажмите «Сформировать карточку». Ожидается предложенная карточка с источниками из ответов. Пока она не подтверждена, сохранённая задача не изменяется. Вручную укажите название, например «Помощник менеджера магазина», и отрасль «Розничная торговля». Проверьте сведения и нажмите «Подтвердить карточку» — этот путь использует AI card-proposal confirm, а не прямое ручное сохранение. При всех заполненных оцениваемых полях ожидается 100 / Priority, published=false.
- Нажмите «Опубликовать задачу», установите отдельный чекбокс согласия и подтвердите. Задача появляется в каталоге.
- Выйдите. Зарегистрируйте «Студент» с другим email
[email protected]и собственным тестовым паролем. Запросите проверку и выполните:
.\.venv\Scripts\python.exe -m backend.demo_identity verify --email [email protected] --demo
- Обновите статус, откройте задачу в каталоге, нажмите «Предложить решение». Заполните название команды, навыки, идею, план и сроки; ссылка необязательна. Отправьте предложение — ожидается «Ожидает решения».
- Выйдите, войдите как
[email protected]. Откройте свою карточку в каталоге → «Управлять задачей» → «Принять» → «Подтвердить принятие». Ожидается «Принято». Решение принимает человек; AI не выбирает команду.
Тестовые ответы выше — данные, которые вводит проверяющий. Они не встроены как факты в provider. Demo использует те же модели, проверку source_id/точных цитат, хранение предложения, подтверждение и Python scoring, что и OpenAI. Отдельного обходного workflow нет.
Проверки из корня проекта:
.\.venv\Scripts\python.exe -m pip install -r requirements-dev.txt
.\.venv\Scripts\python.exe -m unittest discover -s backend/tests -v
npm --prefix frontend run lint
npm --prefix frontend run build
Автотесты не требуют реального API-ключа. Проверка настоящего качества LLM относится к OPENAI MODE и не подтверждается результатом DEMO MODE.
Проверено для изменения DEMO: 94 backend tests (79 прежних + 15 новых), frontend lint и production build проходят. В отдельной копии исходников с заново установленными зависимостями, пустой SQLite, новым браузерным контекстом и без OPENAI_API_KEY пройден весь сценарий выше, включая AI card-proposal confirm, 100 / Priority, публикацию, отклик и принятие бизнесом. В режиме openai без ключа отдельно проверены отсутствие demo-баннера и явный not_configured, без скрытого переключения. Живые платные OpenAI-запросы этой проверкой не выполнялись.
Проверки frontend
Из папки frontend:
npm run lint
npm run build
Локальные файлы
.env, .venv/, node_modules/, dist/, SQLite и кеши Python исключены из Git. Для полного независимого workflow задайте AI_PROVIDER=demo по инструкции выше; API-ключ не нужен. Не добавляйте секреты в исходный код.
Этап 1: API задач
Работает без OpenAI и API-ключа. Сервис сохраняет только введённые сведения, ничего не извлекает и не придумывает из черновика. Поле draft хранит исходное описание; структурированные поля заполняются явно через API.
| Метод | Адрес | Назначение |
|---|---|---|
| POST | /challenges | Создать черновик, HTTP 201 |
| GET | /challenges/{id} | Получить карточку и актуальный рейтинг |
| PATCH | /challenges/{id} | Частично изменить карточку и пересчитать рейтинг |
| POST | /challenges/{id}/readiness | Пересчитать рейтинг сохранённой карточки |
| GET | /health | Прежний health check |
Ответ карточки содержит id, draft, все структурированные поля, published=false, readiness_score, readiness_level, breakdown, missing_information и recommendations. Ответ пересчёта содержит только рейтинг, уровень, разбивку, недостающие поля и рекомендации.
При создании обязателен непустой draft (до 20 000 символов). Остальные текстовые поля необязательны и ограничены 10 000 символами каждое. Пробелы по краям удаляются. PATCH меняет только переданные поля; пустая строка очищает структурированное поле. Пустой PATCH, null, неверные типы, неизвестные поля и попытки задать id/published/рейтинг возвращают 422. Некорректный UUID — 422, отсутствующая задача — 404, ошибка SQLite — 503. Валидация сообщает проблемное поле в detail.
Создание и редактирование не публикуют задачу: даже 100 баллов не меняют published. Для публикации добавлен отдельный endpoint этапа 3. Рейтинг всегда вычисляется из сохранённых полей и не хранится отдельным устаревающим значением.
Правила рейтинга
| Критерий | Баллы |
|---|---|
| context + need | 10 + 10 |
| data_and_materials | 20 |
| expected_result | 15 |
| success_criteria | 15 |
| constraints | 10 |
| users | 10 |
| contact + interaction_format | 5 + 5 |
Каждое непустое поле получает указанный вес целиком. Пустое поле получает 0. title, industry и исходный draft на баллы не влияют. Явное описание отсутствия данных или ограничений считается предоставленной информацией. Это оценка заполненности, а не качества, истинности или реализуемости задачи: произвольный непустой текст тоже получает баллы.
Уровни: 0–39 Draft; 40–69 Working; 70–89 Ready; 90–100 Priority. breakdown содержит score и max_score для семи критериев. missing_information перечисляет пустые оцениваемые поля, recommendations объясняет, что добавить и сколько баллов это даст.
Ручная проверка
Запустите backend командой выше и откройте http://127.0.0.1:8000/docs. Либо выполните в PowerShell:
$base = 'http://127.0.0.1:8000'
Invoke-RestMethod "$base/health"
$body = @{draft='We want to improve customer support'} | ConvertTo-Json
$challenge = Invoke-RestMethod "$base/challenges" -Method Post -ContentType 'application/json' -Body $body
$challenge # score=0, level=Draft, published=False
$edit = @{context='Online shop'; need='Reduce repetitive questions'; data_and_materials='FAQ and anonymized support tickets'} | ConvertTo-Json
Invoke-RestMethod "$base/challenges/$($challenge.id)" -Method Patch -ContentType 'application/json' -Body $edit
# score=40, level=Working
Invoke-RestMethod "$base/challenges/$($challenge.id)/readiness" -Method Post
Invoke-RestMethod "$base/challenges/$($challenge.id)"
После перезапуска backend GET с тем же id должен вернуть сохранённую карточку. Для проверки снижения баллов отправьте PATCH с {"context":""}: результат — 30, Draft.
SQLite и тесты
SQLite создаётся при запуске в backend/data/challenges.sqlite3 (путь не зависит от текущей папки терминала). Одна таблица challenges хранит id, JSON полей и published=0. База и служебные файлы SQLite исключены из Git. Частичные обновления выполняются в транзакции; подключения закрываются после запроса. ORM и миграции пока не нужны.
.\.venv\Scripts\python.exe -m pip install -r requirements-dev.txt
.\.venv\Scripts\python.exe -m unittest discover -s backend/tests -v
Тесты используют отдельные временные базы, не трогают рабочие данные и не обращаются к OpenAI. httpx нужен только для FastAPI TestClient; unittest и sqlite3 входят в Python.
Границы этапа
Авторизации и проверки владения задачами пока нет; запуск рассчитан на локальную разработку. Публикация, каталог и предложения студенческих команд добавлены на этапе 3 (см. ниже). AI workflow этапа 2 описан ниже. Scoring остаётся независимым от AI.
Этап 2: AI-анализ и предложенная карточка
Весь workflow доступен через Swagger; frontend не менялся. Слово card-proposals означает предложения полей карточки от AI, а не заявки студенческих команд.
Настройки OpenAI
Проверенный установленный SDK: openai==3.19.0. Используется официальный Responses API, client.responses.parse(text_format=PydanticModel), строгий Structured Output. Документация: https://developers.openai.com/api/docs/guides/structured-outputs
Установите зависимости:
.\.venv\Scripts\python.exe -m pip install -r requirements-dev.txt
Для настоящего OpenAI в корневом .env задайте AI_PROVIDER=openai, OPENAI_API_KEY и OPENAI_MODEL. Актуальный .env.example включает demo и пример модели gpt-5.4-mini, но не содержит настоящего ключа. Не перезаписывайте существующий .env командой копирования, если в нём есть ваши настройки.
AI_PROVIDER=openai
OPENAI_API_KEY=your_key_here
OPENAI_MODEL=gpt-5.4-mini
Замените your_key_here своим ключом. OpenAIProvider не подставляет модель в коде: она берётся из настройки OPENAI_MODEL. Без ключа ИЛИ модели в режиме openai AI endpoint вернёт fallback/not_configured. Переменные окружения процесса имеют приоритет над .env. Секрет читается только backend; префикс VITE_ использовать нельзя. /health возвращает только статус и имя provider, без секретов. .env не изменялся при реализации.
Один запрос анализа или построения карточки делает один запрос OpenAI: timeout 30 секунд, автоматические повторы выключены, store=false. SDK-клиент закрывается после запроса. Реальные вызовы могут тарифицироваться; automated tests используют mock и не вызывают OpenAI.
Новые endpoints
| Метод | Путь | Назначение |
|---|---|---|
| POST | /challenges/{id}/analysis | Анализ draft, известные сведения, недостающие категории, минимум 3 вопроса |
| POST | /challenges/{id}/card-proposals | Ответы на вопросы → отдельное AI-предложение карточки |
| GET | /challenges/{id}/card-proposals/{proposal_id} | Просмотр предложения и исходной карточки |
| POST | /challenges/{id}/card-proposals/{proposal_id}/confirm | Подтверждение с необязательными ручными правками |
Анализ и построение возвращают HTTP 200 со status=ok либо status=fallback. При fallback доступны code, message и manual_edit_available=true; фиктивных AI-карточек или вопросов не генерируется. Коды: not_configured, unavailable, invalid_output. При ошибке остаются доступны PATCH и readiness этапа 1. Нет анализа/предложения для этой задачи — 404, неверный запрос — 422, устаревшее или повторное подтверждение — 409.
Анализ содержит known_information, evidence, missing_information и questions с id (q1, q2, …), field, kind и text. Для неизвестной категории kind=missing; когда категорий не хватает меньше трёх, модель должна спрашивать только ещё не описанные детали (kind=detail), не повторять известные факты. Дубликаты вопросов и вопросы kind=missing о найденных полях отклоняются. missing_information вычисляется backend по извлечённым полям девяти обязательных категорий.
Ручная проверка с настоящим ключом
- Запустите backend и откройте http://127.0.0.1:8000/docs.
- Создайте Challenge через POST /challenges с непустым draft и сохраните id.
- Выполните POST /challenges/{id}/analysis (без body). При status=ok сохраните analysis_id и прочитайте вопросы.
- Ответьте на вопросы через POST /challenges/{id}/card-proposals. Подставьте настоящий analysis_id, соответствующие question_id и ваши ответы:
{
"analysis_id": "UUID из ответа анализа",
"answers": [
{"question_id": "q1", "text": "Ответ бизнеса на первый вопрос"},
{"question_id": "q2", "text": "Ответ бизнеса на второй вопрос"},
{"question_id": "q3", "text": "Ответ бизнеса на третий вопрос"}
]
}
Можно ответить на часть вопросов, но минимум на один. Неизвестные поля остаются пустыми. Суммарный размер ответов — до 30 000 символов.
- Получите proposal_id. Просмотрите proposed_card, current_card и evidence. GET Challenge до подтверждения должен вернуть прежние данные и рейтинг. Предложение хранится отдельно и доступно после перезапуска.
- Для принятия отправьте POST /challenges/{id}/card-proposals/{proposal_id}/confirm:
{
"confirmed": true,
"edits": {
"title": "Название, подтверждённое бизнесом"
}
}
edits можно опустить или передать пустой объект. В него входят только поля карточки; пустая строка очищает поле. Ручные правки — новые сведения самого пользователя, поэтому они не ограничены цитатами AI. Значения null, draft, published и readiness_score в edits запрещены.
- Confirm заменяет ВСЕ структурированные поля на просмотренную proposed_card с вашими edits. Пустые поля предложения тоже применяются: сравните с current_card перед подтверждением, чтобы сохранить нужные прежние сведения через edits. Исходный draft сохраняется. Backend пересчитывает readiness прежним scoring engine; published не меняется (для неопубликованной задачи остаётся false). GET Challenge показывает итог.
- Если Challenge изменился после анализа (включая ручной PATCH), повторите анализ и построение предложения: устаревшее предложение не перезапишет новые данные. Повторный confirm возвращает 409.
Защита от выдуманных сведений и ограничения
- Каждое непустое AI-поле — точная цитата с source_id из draft или конкретного answer:qN. Backend проверяет наличие цитаты именно в указанном источнике. Текст вопроса не является источником фактов.
- Неизвестные поля возвращаются как null в ответе модели и как пустые строки в proposed_card. Произвольный текст не разбирается регулярными выражениями.
- Некорректная схема, выдуманная цитата, отказ модели или незавершённый ответ дают явный fallback. Challenge не изменяется.
- AI не получает инструментов записи в БД, публикации или выбора команды. Scoring не передаётся модели.
- Цитирование не доказывает правильность смыслового отнесения фразы к полю. Ошибки интерпретации, неполное извлечение и неудачные вопросы возможны: просмотр и ручное подтверждение обязательны. В MVP нет свободного перефразирования и придуманного заголовка, поэтому title/industry часто остаются пустыми.
- При очень полном draft требование трёх вопросов реализуется вопросами о дополнительных неуказанных деталях. Их полезность зависит от модели.
- Используются две таблицы SQLite: исходные challenges и отдельная ai_workflows с анализами, ответами и предложениями. Добавление таблицы не меняет существующие Challenge. Подтверждение и отметка confirmed выполняются одной транзакцией. Автоматического удаления старых workflow пока нет.
- Авторизации, rate limiting и UI workflow пока нет. Реальные вызовы OpenAI проверены отдельно при ручном сценарии с gpt-5.4-mini. API-ключ хранится только локально в .env.
Тесты
.\.venv\Scripts\python.exe -m unittest discover -s backend/tests -v
Старые 14 тестов сохранены. Новые тесты проверяют анализ, минимум три вопроса, строгие схемы, источники цитат, отсутствие сохранения до confirm, ручные правки, пересчёт, fallback, устаревшие предложения, повторное подтверждение, сохранение workflow между запусками и параметры SDK. Все AI-вызовы замоканы.
Этап 3: публикация, каталог и студенческие предложения
Frontend и AI workflow не изменены. Регистрации, ролей и проверки прав нет: Business/Student — логические сценарии. Использовать локально для MVP; любой клиент API технически может выполнить ручное действие.
Публикация и каталог
- POST /challenges/{id}/publish с JSON {"confirmed":true} публикует существующую карточку. Подтверждение обязательно; false, строка "true" и отсутствующий body дают 422.
- Любой score, включая 0, допустим. Публикация не меняет поля или рейтинг. Повторная публикация идемпотентна: 200 и та же карточка.
- AI analysis, card-proposals, confirm и обычный PATCH не меняют флаг публикации. Уже опубликованная карточка остаётся опубликованной после редактирования; изменения сразу видны в каталоге. Снятие с публикации в этом этапе не реализовано.
- GET /challenges возвращает массив только опубликованных карточек, включая низкорейтинговые. Сортировка: score по убыванию, затем id по возрастанию при равенстве.
- Необязательные query-параметры industry и readiness_level комбинируются через AND. Industry — полное совпадение без учёта регистра и внешних пробелов; readiness_level — Draft, Working, Ready или Priority (неизвестное значение даёт 422).
- Рейтинг вычисляется текущим scoring engine при чтении. Для небольшого MVP фильтрация и сортировка выполняются в Python; пагинации и полнотекстового поиска нет.
Student Proposal
POST /challenges/{id}/proposals создаёт заявку команды (201). В отличие от AI /card-proposals, это отдельная сущность и таблица student_proposals.
{
"team_name": "Student Team",
"skills": "Python, React",
"solution_idea": "Помощник для подготовки ответов по FAQ",
"plan": "Изучить FAQ, сделать прототип, проверить на тестовых вопросах",
"estimated_time": "5 часов",
"prototype_url": "https://example.com/demo"
}
Все текстовые поля, кроме prototype_url, обязательны и не могут состоять из пробелов. skills — строка. prototype_url можно опустить или передать null; разрешены HTTP/HTTPS URL. Ссылка не открывается и не загружается сервером. id, challenge_id и status задаёт backend, при создании status=pending. Попытка передать status в запросе даёт 422.
Для unpublished Challenge отправка запрещена (409). Отсутствующая задача — 404. GET /challenges/{id}/proposals возвращает все её заявки в порядке поступления; пустой список допустим. Заявка связана с Challenge внешним ключом SQLite.
Ручные решения бизнеса
- POST /challenges/{challenge_id}/proposals/{proposal_id}/accept (без body): pending → accepted.
- POST /challenges/{challenge_id}/proposals/{proposal_id}/reject (без body): pending → rejected.
- Повтор того же решения возвращает 200 и текущее состояние.
- accepted → rejected и rejected → accepted запрещены (409).
- Предложение от другой задачи или неизвестный proposal_id — 404.
- Только одна accepted-заявка на Challenge. Попытка принять другую — 409. Ограничение защищено транзакцией BEGIN IMMEDIATE и уникальным частичным индексом SQLite, включая конкурентные запросы.
- Остальные pending остаются pending, их можно вручную отклонить. Новые заявки после выбора команды разрешены и остаются pending; принимать их нельзя, пока уже есть accepted. Отмена выбора не реализована.
- AI не вызывается при публикации, отправке студенческой заявки или решении бизнеса.
SQLite migration
При старте старая таблица challenges с CHECK (published = 0) атомарно перестраивается в таблицу с CHECK (published IN (0, 1)). Сохраняются id, payload и published, таблица ai_workflows не удаляется. Добавляется student_proposals. Повторный запуск не перестраивает уже обновлённую схему. На каждом подключении включён foreign_keys. Новых зависимостей нет.
Ручной сценарий через Swagger
- Создайте draft и пройдите AI analysis → card-proposals → confirm по инструкции этапа 2. Проверьте score и published=false.
- Вызовите POST /challenges/{id}/publish с {"confirmed":true}. Ожидайте published=true и прежний score.
- GET /challenges: карточка видна в каталоге. Проверьте фильтры industry и readiness_level.
- Отправьте заявку командой POST /challenges/{id}/proposals с JSON выше. Сохраните её id.
- Получите список GET /challenges/{id}/proposals: заявка pending.
- POST /challenges/{id}/proposals/{proposal_id}/accept: accepted. Альтернативный сценарий — reject для другой pending-заявки.
- Проверьте запрет принятия второй команды (409) и повтор того же accept (200).
Отдельный ручной прогон выполнен на временном Uvicorn-сервере и отдельной SQLite с настоящим OpenAI: draft → анализ (10 вопросов) → AI-предложение → ручные правки/confirm → score 100 → publish → отфильтрованный каталог → student proposal → accept. До confirm Challenge оставался неизменным. /health возвращал ok. Рабочая карточка пользователя не использовалась; временный сервер остановлен.
Полный automated suite: 55 тестов (40 прежних + 15 новых), включая миграцию с сохранением данных и конкурентный accept. Автотесты не делают платных AI-вызовов.
Backend-ограничения этапа 3: нет авторизации, пагинации, снятия публикации, отмены принятия команды, уведомлений и UI этих сценариев. Смысловые неточности вопросов AI из этапа 2 требуют ручной проверки; новый AI workflow не добавлялся.
Этап 4: пользовательский frontend
Приложение: http://localhost:5173. Запуск frontend/backend — командами из начала README. Существующие endpoints и backend не менялись. Новых npm-зависимостей нет.
Business
- Введите бизнес-черновик и нажмите «Проанализировать с AI». Frontend создаст Challenge и вызовет /analysis.
- Ответьте на вопросы. Справа показан рейтинг именно сохранённого Challenge; неподтверждённые ответы пока не меняют его. Достаточно одного ответа; неизвестное можно оставить пустым.
- «Сформировать карточку» вызывает /card-proposals. Проверьте и отредактируйте все 11 полей. Для сравнения доступна сохранённая карточка; источники AI-полей обозначены.
- «Подтвердить карточку» вызывает /card-proposals/{id}/confirm с явным confirmed=true и правками. Рейтинг, breakdown и рекомендации берутся из ответа backend, формулы на frontend нет.
- «Опубликовать задачу» открывает диалог с отдельным чекбоксом согласия. Только после подтверждения вызывается /publish. Низкий score не блокирует публикацию.
- Для опубликованной карточки доступны предложения команд, обновление списка и ручной Accept/Reject. Каждый выбор требует подтверждения; принять вторую команду нельзя.
При fallback AI видна понятная ошибка, кнопка повтора и «Заполнить вручную». Ручная карточка сохраняется через существующий PATCH и тоже может быть опубликована.
Student
- Переключите header в Student: откроется GET /challenges. Сортировку выполняет backend.
- При необходимости примените фильтры отрасли (точное совпадение без учёта регистра) и уровня готовности. Откройте задачу.
- «Предложить решение» раскрывает форму команды: название, навыки, идея, план, сроки и необязательная HTTP/HTTPS-ссылка на прототип.
- «Отправить предложение» вызывает POST /challenges/{id}/proposals. После успеха показано подтверждение, повторная отправка из этой формы заблокирована.
- Переключитесь в Business: вернётся карточка бизнеса и обновится список предложений. Выберите Accept или Reject.
Из каталога в Business можно открыть опубликованную задачу через «Управлять задачей». Это демонстрационные режимы без авторизации и проверки владельца.
Настройка и состояние
Адрес API по умолчанию http://localhost:8000. При необходимости задайте VITE_API_BASE_URL в frontend/.env.local и перезапустите Vite; это только публичный URL, не API-ключ. OPENAI_API_KEY остаётся только в корневом backend .env и никогда не попадает в frontend. В 5A backend разрешает только FRONTEND_ORIGIN (по умолчанию http://localhost:5173), а frontend отправляет cookie через credentials: include.
В localStorage хранится только ID последнего Business Challenge (ai-sana:challenge:v1). После перезагрузки загружается сохранённая карточка. Незавершённые ответы интервью и неподтверждённые правки существуют только в памяти страницы: при перезагрузке их нужно ввести заново. Переключение Business/Student сохраняет незавершённый Business workflow в текущей вкладке. «Создать другую задачу» начинает новый черновик после предупреждения.
Во время запросов кнопки действий и переключение режимов заблокированы. Ошибки показаны обычным текстом, без JSON и stack traces. Обновление каталога/заявок ручное. До создания Challenge рейтинг отображается как «не рассчитан», а не как выдуманный балл.
Проверки frontend
npm --prefix frontend run lint
npm --prefix frontend run build
.\.venv\Scripts\python.exe -m unittest discover -s backend/tests -q
Browser QA включает полный demo flow с реальным OpenAI, отдельные состояния ошибки и адаптивный экран. Рабочая область рассчитана прежде всего на ноутбук; на телефоне рейтинг расположен под формой. UI работает без маршрутизатора: смена состояний не меняет адрес страницы, браузерная кнопка «Назад» не заменяет кнопки приложения. Полноценные профили, уведомления, чат и AI matching не добавлялись.
Результат проверки этапа 4: 55 backend-тестов проходят, frontend lint и production build успешны. Через интерфейс в Edge (Playwright) пройден реальный сценарий: draft → AI-вопросы → ответы → редактирование → подтверждение → 100/Priority → публикация → фильтры каталога → предложение Sana Demo Team → ручное принятие. Сохранение результата и восстановление карточки после перезагрузки проверены через backend. Демонстрационная карточка «Демо: помощник магазина» оставлена в локальной БД. Дополнительно на подставных API-ответах проверены fallback, ручное заполнение и публикация с 0 баллов только после отдельного согласия. На ширине 390 px горизонтального переполнения нет; заключительная проверка не обнаружила ошибок консоли. Встроенный браузер потерял сессию, поэтому проверка выполнена в headless Edge.
Этап 5A: аккаунты, роли и демонстрационный статус проверки
Новых Python/npm-зависимостей нет. User содержит id, name, уникальный нормализованный email, password_hash, role (student/business), verification_status, created_at и необязательные university / company_name / position / company_industry / company_website. Поля другой роли не сохраняются. Роль задаётся при регистрации; статус всегда unverified. Передать owner_id, чужую роль в действии или verification_status для повышения прав нельзя.
Authentication
| Метод | Путь | Поведение |
|---|---|---|
| POST | /auth/register | Создать аккаунт, 201; отдельный login обязателен |
| POST | /auth/login | Проверить пароль, выдать новую cookie-сессию |
| GET | /auth/me | Текущий профиль без password_hash либо 401 |
| POST | /auth/logout | Удалить серверную сессию и cookie, 204 |
| POST | /auth/request-verification | unverified → pending, без автоматического подтверждения |
Пароли хешируются PBKDF2-HMAC-SHA256: 600 000 итераций и индивидуальная случайная соль. Сессия — случайный 256-битный токен; в SQLite хранится только SHA-256 токена, user_id и срок действия 12 часов. Cookie kaibridge_session имеет HttpOnly, SameSite=Lax, Path=/, без Domain. Повторный login меняет токен и отзывает предыдущую сессию данного браузера; logout отзывает её сразу. Права и статус читаются из БД на каждый запрос.
Все POST/PUT/PATCH/DELETE требуют точный Origin из FRONTEND_ORIGIN, включая login/logout; отсутствие Origin тоже даёт 403. Для API-клиента явно передавайте Origin: http://localhost:5173 и сохраняйте полученную cookie. Это защита от браузерного CSRF, а не замена авторизации. CORS разрешает credentials только этому origin. Auth-ответы имеют Cache-Control: no-store; ошибки валидации не возвращают входные значения, включая пароль.
Секрет подписи не требуется: токены непрозрачные и серверные. Секреты и токены не хранятся в frontend/localStorage. Там остаётся только ID последней задачи с отдельным ключом для каждого пользователя и несекретная отметка незавершённого расчёта. Выход очищает текущий ID и приватные формы.
Verification и права
unverified → pending выполняет пользователь. Повтор запроса при pending/verified не меняет статус; rejected остаётся rejected. В 5A rejected предусмотрен моделью, но отдельного процесса отказа и апелляции нет. pending → verified доступен только локальной demo-команде ниже; публичного endpoint для назначения verified нет. Это не проверка личности, документов, email или компании.
| Действие | Условия на backend |
|---|---|
| Каталог и опубликованная карточка | Публично |
| Создать черновик | Вход с ролью business, подтверждение пока не нужно |
| Читать черновик, редактировать, AI-анализ/предложение/confirm, читать отклики | Только business-владелец |
| Публиковать, принять/отклонить отклик | verified business-владелец |
| Изменять уже опубликованную карточку | verified business-владелец |
| Отправить отклик | verified student; задача опубликована и имеет владельца |
Новый Challenge получает owner_id из сессии, Student Proposal — student_id из сессии. Business A не может редактировать задачи или решать по откликам Business B. Ручное подтверждение AI-карточки и отдельное согласие на публикацию сохранены; скоринг не менялся.
Миграция и старые задачи
При старте добавляются users/auth_sessions и nullable-ссылки challenges.owner_id, student_proposals.student_id. Старые payload, id, published, AI history и статусы предложений сохраняются. Повторная инициализация безопасна. Старым записям никто автоматически не назначается.
Опубликованные задачи без owner остаются в каталоге только для чтения; новые отклики и управление запрещены (409). Старые черновики и AI history не становятся публичными. Для восстановления управления оператор локально назначает конкретную старую задачу существующему бизнес-аккаунту:
.\.venv\Scripts\python.exe -m backend.demo_identity assign-legacy --challenge-id ID --business-email [email protected] --demo
Команда не может заменить уже назначенного владельца и не принимает student. Сначала убедитесь, что выбраны нужные задача и email. Для другой БД укажите --database PATH перед assign-legacy или verify. Назначение владельца не изменяет содержание и предложения. Старые student_id остаются NULL, поскольку достоверно связать прежние отклики с новыми аккаунтами нельзя.
Ручная проверка 5A
- Запустите backend/frontend. Каталог должен открыться без входа. Зарегистрируйте business; интерфейс выполняет register, затем login. Видны имя, роль и «Неподтверждён».
- Подготовьте карточку через существующий AI flow или «Заполнить вручную» при fallback. Черновик разрешён, публикация пока запрещена понятным сообщением.
- Нажмите «Запросить проверку»: статус «На проверке». Только для тестового аккаунта выполните из корня:
.\.venv\Scripts\python.exe -m backend.demo_identity verify --email [email protected] --demo
- Нажмите «Обновить статус», проверьте «Подтверждён». Опубликуйте задачу с отдельным согласием.
- Выйдите, зарегистрируйте student с другим email. Откройте задачу: отправка до подтверждения запрещена. Запросите проверку, выполните ту же demo-команду для студенческого email, обновите статус. Отправьте предложение.
- Выйдите, войдите прежним business, найдите свою задачу в каталоге и нажмите «Управлять задачей». Примите/отклоните отклик вручную. Другой business не получает эту кнопку и получает 403 при прямом запросе к API.
- После logout /auth/me возвращает 401. Обновление страницы восстанавливает действующую cookie-сессию. Не переключайте бизнес и студента в одном аккаунте: это разные роли и разные аккаунты.
В локальном HTTP demo AUTH_COOKIE_SECURE=false. Для HTTPS-развёртывания нужен AUTH_COOKIE_SECURE=true и корректный FRONTEND_ORIGIN; frontend/API должны иметь совместимые same-site адреса. До публичного запуска нужны HTTPS, ограничение частоты login/register/AI, реальное управление verification, восстановление доступа и эксплуатационная защита БД/резервных копий. Email при регистрации не подтверждается, поэтому он не доказывает личность. Большой профиль, email delivery, OAuth, Student ID/OCR/Vision, внешняя проверка компаний, MFA и admin panel не реализованы в 5A.
Backend suite 5A: 79 тестов (55 прежних + 24 новых). Auth-fixtures минимально адаптированы для 43 прежних тестов: 10 API, 18 AI workflow, 1 fallback provider, 14 publication; исходные проверки поведения сохранены. Остальные 12 тестов не менялись. Добавлены проверки ролей, владельца, session expiry/logout, Origin, паролей/валидации и legacy migration. AI-вызовы в тестах подменены, реальный ключ не используется.
Frontend lint и production build проходят. Browser QA 5A выполнен в headless Edge на экранах 1366×900 и 390×844, с отдельной SQLite и детерминированной подменой только AI-provider: регистрация Business/Student → запрет до verification → запрос pending → локальный demo verify → публикация с согласием → два студенческих отклика → ручные Accept/Reject. Проверены logout, восстановление сессии и своей задачи после reload, HttpOnly cookie, отсутствие токена в localStorage, прокрутка мобильной формы и переключение роли. Ошибок JavaScript/CORS не обнаружено; 401 от /auth/me для гостя ожидаем. Реальные AI-запросы в проверке 5A не выполнялись.
Сравнение рабочей БД до/после миграции подтвердило сохранность всех прежних 9 Challenges, 13 AI workflow и 5 Student Proposals без изменения содержимого или статусов; foreign_key_check не выявил ошибок. Тестовые аккаунты браузерной проверки находятся только в отдельной временной БД, а не в рабочем каталоге.
Авторы коммитов
История коммитов
- 2026-09-23 17:20Add explicit deterministic demo AI mode for independent reviewРашид
- 2026-09-23 16:30Add Stage 5A identity, roles, and demo verificationРашид
- 2026-09-23 15:48Polish KaiBridge AI branding and frontend experienceРашид
- 2026-09-23 15:04Complete stage 4: business and student frontend workflowsРашид
- 2026-09-23 14:21Complete stage 3: publication, catalog and manual team decisionsРашид
- 2026-09-23 14:07Complete stage 2: grounded AI card proposals and human confirmationРашид
- 2026-09-23 13:24Complete stage 1: SQLite challenges API and deterministic readiness scoringРашид
- 2026-09-23 12:21Prepare runnable hackathon starter and setup instructionsРашид
Структура репозитория (55 файлов)
.env.example .gitignore README.md backend/agent.py backend/ai_models.py backend/ai_provider.py backend/ai_workflow.py backend/auth.py backend/auth_models.py backend/database.py backend/demo_identity.py backend/demo_provider.py backend/main.py backend/models.py backend/proposal_models.py backend/scoring.py backend/tests/auth_helpers.py backend/tests/test_ai.py backend/tests/test_api.py backend/tests/test_auth.py backend/tests/test_demo_ai.py backend/tests/test_publication.py backend/tests/test_scoring.py backend/tools.py frontend/.gitignore frontend/README.md frontend/eslint.config.js frontend/index.html frontend/package-lock.json frontend/package.json frontend/public/favicon.svg frontend/public/fonts/Inter-LICENSE.txt frontend/public/fonts/InterVariable.woff2 frontend/public/fonts/README.txt frontend/public/icons.svg frontend/public/sana.svg frontend/src/App.css frontend/src/App.jsx frontend/src/AuthPanel.jsx frontend/src/Business.jsx frontend/src/Catalog.jsx frontend/src/TeamProposals.jsx frontend/src/api.js frontend/src/assets/hero.png frontend/src/assets/react.svg frontend/src/assets/vite.svg frontend/src/components.jsx frontend/src/fields.js frontend/src/index.css frontend/src/main.jsx frontend/src/motion.js frontend/src/readinessState.js frontend/vite.config.js requirements-dev.txt requirements.txt
Обсуждение 0 комментариев
Войдите, чтобы комментироватьПока нет комментариев — будьте первым.