HTTP API
Оба приложения — FastAPI на localhost, каждое говорит только со своей SPA. Общего сервера и межприложенческого API нет: генератор и раннер связаны форматом пака.
Раннер студента
Порт 8766.
Практика и задания
| Метод | Путь | Отдаёт |
|---|---|---|
GET | /api/pack | описание базы, DDL, таблицы, число заданий, сколько решено |
GET | /api/tasks | задания с формулировками, темами, сложностью, открытыми примерами, статусом и текущим решением |
GET /api/tasks отдаёт и public_checks — открытые проверки задания. Скрытые в ответ не попадают.
Вместе с формулировками едет контракт результата: result (columns, ordered, cardinality, numeric_precision), state_tables, require_constructs, forbid_constructs. Это часть условия, а не закрытые данные: без состава колонок и признака порядка задание нерешаемо.
Работа с базой
| Метод | Путь | Делает |
|---|---|---|
POST | /api/query | выполняет SQL в живой базе, возвращает колонки, строки и время |
POST | /api/load_example | наливает в живую базу открытый вариант данных |
POST | /api/reset | возвращает живую базу к пустой схеме |
Проверка
| Метод | Путь | Делает |
|---|---|---|
POST | /api/tasks/{id}/check | прогоняет решение по всем вариантам в проверочной базе, сохраняет solutions/<id>.sql |
GET | /api/tasks/{id}/variant/{variant} | данные варианта и ожидаемый результат |
POST | /api/tasks/{id}/variant/{variant} | то же плюс результат решения студента на этих данных — для построчного сравнения |
Каждая запись в failures ответа check несёт машинный диагноз: code (row_order, columns_mismatch, cardinality, rows_differ, exec_error, constructs, …) и details с числами — сколько строк ожидалось и получено, с какой позиции разошёлся порядок, каких колонок не хватает. Английский diagnostic остаётся для отчёта автора и CI; текст для студента интерфейс собирает из code и details, а не разбирает прозу.
Эти два эндпоинта отдают ожидаемый результат только для открытых примеров и для вариантов, на которых решение студента уже упало. Всё остальное остаётся в закрытой части пака и наружу не уходит.
Черновики
| Метод | Путь | Делает |
|---|---|---|
GET POST | /api/history/{id} | история запросов по заданию; кольцо из ста записей |
GET PUT | /api/bookmarks | закладки |
Хранятся в solutions/.history/, который раннер дописывает в solutions/.gitignore: черновики не попадают в сдачу.
Оформление
| Метод | Путь | Отдаёт |
|---|---|---|
GET | /api/theme | разобранную тему: brand, scheme, fonts, tokens, hasCss |
GET | /theme/theme.css | содержимое theme.css как есть |
GET | /theme/assets/{path} | файлы темы; расширения по белому списку |
Значения токенов подставляются в <head> при отдаче index.html, поэтому приложение стартует уже в цветах курса. Параметр ?theme=off отключает тему.
Заголовки
Content-Security-Policy: default-src 'self'; img-src 'self' data:; font-src 'self';
style-src 'self' 'unsafe-inline'; script-src 'self'
X-Content-Type-Options: nosniff
unsafe-inline для стилей необходим: редактор кода вставляет свои <style> в рантайме, и токены темы приходят инлайн-блоком. Внешние запросы при этом запрещены — образ автономен.
Студия автора
Порт 8765. Доступна только с локального адреса.
| Метод | Путь | Делает |
|---|---|---|
GET | /api/practice | состояние всех шагов и хэши |
GET | /api/catalog | каталог предметных областей и тем SQL для форм |
GET POST | /api/brief, /api/brief/generate, /api/brief/apply | шаг 0: разбор лекции и перенос в форму шага 1 |
GET POST | /api/domain, /api/domain/generate, /api/domain/approve | шаг 1 |
GET POST | /api/tasks, /api/tasks/generate, /api/tasks/approve | шаг 2 |
POST | /api/materialize?task_id=… | шаг 3 для одного задания; итог сливается в report/outcomes.json, итоги остальных заданий сохраняются. Без task_id — все задания разом, отчёт пишется заново |
GET | /api/outcomes | результаты прогона: статусы, материализации, матрица негативов |
GET | /api/tasks/{id}/variant/{variant} | данные любого варианта, включая скрытые |
Ручной режим — параллельная пара эндпоинтов у каждого шага: /prompt отдаёт готовый промт, /manual принимает ответ модели и валидирует его так же, как ответ API. У шага 0 /api/brief/prompt — POST, а не GET: текст лекции в query-строку не помещается. Лекция длиннее 60 000 знаков отклоняется с 400 — усечения нет.
Ответ нейросети не той формы
Если JSON от модели не прошёл проверку формы — на генерации после одного повтора, в ручном режиме сразу, — эндпоинт отвечает 422 с телом:
{
"detail": "ответ нейросети не прошёл проверку формы: …",
"errors": [
"`dataspec.class_columns[0]`: нужен объект {table, column, values}, получено: строка `\"shops.city\"`"
],
"fix_request": "Твой JSON не прошёл проверку формы:\n- …\n\nПришли исправленный JSON целиком…"
}
detail — сводная строка для любого клиента. errors — по одной ошибке на поле, в обратных кавычках пути и значения. fix_request — готовое сообщение для нейросети; тот же текст студия отправляет модели при повторе. Прочие 422 (пустая лекция, не выбраны темы) приходят с одним detail.
Прогон одного задания на шаге 3 не отвечает 422 на ошибку формы: такое задание получает статус bad_response в outcomes.json, а очередь в UI идёт дальше.
Студии автора закрытых данных не существует: она показывает всё содержимое пака, включая скрытые проверки и ожидаемые результаты всех вариантов.