SQL Практика тестирующая система курса

локально · пак в git · без общего сервера

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

Автор собирает практику в студии у себя на машине: предметная область, задания, эталонные решения. Движок разворачивает десятки вариантов данных и прогоняет по ним эталон. Студент решает в Docker и коммитит solutions/ — CI преподавателя считает то же самое и тем же кодом.

Python 3.12+ · FastAPI · React Postgres по digest GitLab CI
прогон одного задания ✓ сошлось
53 варианта данных53 сошлось
эталонное решение1
открытых примера3
скрытых варианта50
расхождений0

Столько раз задание проверено до того, как студент его увидел.

из чего состоит

Два приложения и папка между ними

Общего сервера нет. Пак — это каталог в git и одновременно контракт: студия его только пишет, раннер только читает. Обе стороны зависят от одной библиотеки packcore, поэтому «у меня проверка проходит, а в CI нет» не бывает по построению.

студия автора

Собрать практику

FastAPI и SPA на localhost. Три шага подготовки, вызовы LLM, эфемерный Postgres в контейнере на каждый прогон. Тот же CLI, если удобнее из терминала.

машина автора · uv run gen

раннер студента

Решать и проверять

Docker-образ: FastAPI, SPA и Postgres. Живая база для экспериментов, отдельная чистая — для проверок. Тот же образ с --mode=ci проверяет сдачу в пайплайне.

машина студента · GitLab CI

пак практики

Хранить результат

Каталог в git: описание базы, задания, эталоны, спеки данных, миграции вариантов, проверки, отчёт приёмки и оформление курса. Утверждения держатся на хэшах.

packs/<практика>/

путь практики

Четыре шага, каждый со своим артефактом

Первые три проходит автор, и каждый заканчивается утверждением: пока шаг не утверждён, следующий не начинается. Четвёртый идёт у студента и в CI.

шаг 1 · область

Предметная область

Описание базы, темы курса с квотами заданий, DDL. Утверждённая область получает хэш содержимого — задания привязаны именно к этой версии.

domain.json

шаг 2 · задания

Задания

Формулировки, темы, сложность, обязательные и запрещённые конструкции. Покрытие тем считается до утверждения, а не после жалоб студентов.

tasks.json

шаг 3 · данные

Данные и проверки

Эталонное решение, спека данных, варианты, набор проверок и негативные решения — заведомо неверные ответы, на которых проверки обязаны падать.

materializations/
<id>.json

шаг 4 · практика

Решение и сдача

Студент пишет запрос на живой базе, жмёт «Проверить решение» и получает прогон по всем вариантам. Результат ложится в файл и уезжает в CI.

solutions/<задание>.sql

ручной режим Ключ LLM не обязателен: студия печатает промпт, вы приносите ответ модели из чата и вставляете обратно. Шаги те же, гейты те же.

почему это воспроизводимо

Модель отвечает за текст, а не за вердикт

  • данные

    LLM пишет спеку данных, строки генерирует движок по seed. Один seed — один и тот же набор строк на любой машине, в любой день.

  • эталон

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

  • нормализация

    Перед сравнением результат канонизируется: регистр имён колонок, точность чисел, даты в ISO-8601 UTC, место NULL, порядок строк. 40 и 40.00 — одно значение.

  • окружение

    Образ Postgres пришпилен по digest и записан в манифест, база создаётся с LC_COLLATE=C. Иначе сортировка текста разъедется между машинами.

BEGIN накат миграций варианта — данные seed'а выполнение решения студента — по одному стейтменту съём результата — строки / таблицы / схема ROLLBACK нормализация · сравнение с эталоном

когда не сошлось

53 варианта36 сошлось17 нет

Полоса прогона показывает не слово «зачёт», а объём проверки. Упавший скрытый вариант раскрывается — видно его данные, ожидаемый результат и ваш. До падения он закрыт, чтобы ответ нельзя было подогнать под данные.

студия автора · localhost:8765

Всё, что уедет студентам, видно до отправки

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

Отчёт приёмки в студии автора: список заданий, отчёт по выбранному, инспектор справа
шаг 3Отчёт приёмки: список заданий с поиском, разбор выбранного, инспектор прогона справа.
Шаг 1 студии: предметная область, таблицы и темы курса
шаг 1Предметная область: таблицы, документация, темы курса с квотами.
Шаг 2 студии: список заданий, формулировки и покрытие тем
шаг 2Задания: формулировки, сложность, покрытие тем до утверждения.
Вкладка вариантов данных: 53 варианта, квоты классов и отпечатки
варианты53 варианта: seed, квоты классов и отпечаток результата у каждого.
Негативные решения: заведомо неверные ответы и проверки, которые их ловят
негативыНегативные решения: типичные ошибки и проверки, которые их ловят.
Ручной режим: готовый промпт для копирования и поле для ответа модели
ручной режимБез ключа API: студия печатает промпт, вы вставляете ответ модели обратно.

приложение студента · localhost:8766

Редактор, живая база и разбор падения

docker compose up — и практика открыта в браузере. Слева задания, сверху условие и редактор с подсветкой и автодополнением по схеме пака, снизу результат, проверка, схема базы, примеры данных и история попыток.

Рабочее место студента: список заданий, условие, редактор SQL и таблица результата
рабочее местоЗадания слева, условие и редактор сверху, результат запроса снизу.
Вкладка примеров данных с кнопкой загрузки в живую базу
примерыТри открытых варианта данных грузятся в живую базу одной кнопкой.
Схема базы: карточки таблиц с колонками, типами и связями
схемаКарточки таблиц: колонки, типы, ключи и связи — без внешнего клиента.
Успешная проверка: зелёная полоса прогона на 53 варианта
зачётРешение прошло все варианты и записано в solutions/.
Упавшая проверка: красная полоса и список вариантов с расхождением
падениеПод полосой — сами варианты, на которых результат разошёлся.
Построчное сравнение ожидаемого и полученного результата
разборПострочное сравнение: помечены те ячейки, где есть расхождение.
История попыток по заданию
историяИстория попыток по заданию: что запускали и чем закончилось.

что умеет

Семь типов проверок вместо сравнения текста запроса

Задание набирает нужные ему проверки. Одни сравнивают результат, другие — состояние таблиц после записи, третьи — сам текст решения через AST, а не регулярками.

ТипЧто сравнивает
schemaНабор и порядок колонок результата, имена псевдонимов.
cardinalityСколько строк ожидается: ровно одна, много, допустим ли пустой ответ.
result_exactРезультат построчно — на открытых примерах данных.
result_hashХэш нормализованного результата — на скрытых вариантах.
table_stateСостояние таблиц после INSERT, UPDATE, DELETE.
db_schemaСтруктура схемы после CREATE и ALTER.
sql_constraintsОбязательные и запрещённые конструкции в решении — разбором AST.

Десятки вариантов на задание

Систематические крайности — каждый класс строк пуст, каждый единственный — плюс случайные комбинации квот.

Негативные решения

Типичные ошибки записаны как SQL. Если проверки их не ловят, задание не проходит приёмку.

Ручной режим

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

CLI рядом со студией

gen domain, tasks, materialize, status, build — те же этапы из терминала.

Проверка в CI

Тот же образ раннера с --mode=ci читает solutions/ из merge request и считает то же самое.

Оформление под курс

Тема пака задаёт цвета, радиусы, логотип и свой CSS. Семь готовых тем в комплекте.

Устаревание по хэшам

Поменяли формулировку — материализация помечается устаревшей до того, как пак уедет студентам.

Закрытая часть пака

Эталоны и скрытые варианты лежат зашифрованными: студент видит вариант только после падения на нём.

Две раскладки результата

Строками — как в консоли; по записям — когда колонок больше, чем помещается в экран.

контракт

Между генератором и раннером — папка, а не API

Пак читают студия, раннер и CI. Версия формата и версия движка данных лежат в манифесте: несовместимый мажор раннер не откроет, а не откроет «как-нибудь». Утверждения держатся на хэшах — content_hash области, source_hash задания.

packs/<практика>/ domain.json описание базы, темы курса, DDL tasks.json задания materializations/<id>.json эталон, спека, варианты, проверки migrations/common/ создание схемы migrations/tasks/<id>/ INSERT-ы вариантов report/index.md отчёт приёмки theme/ оформление курса state.json что утверждено, хэши

быстрый старт · 15 минут

Пак-пример уже пройден до третьего шага

Ключ LLM не нужен: оба сценария работают на заготовленных ответах модели. Нужны Docker и uv; Node — только если собираете интерфейсы из исходников.

01 · зависимости

Поставить

Один uv-воркспейс на весь монорепозиторий: ядро, генератор, раннер.

uv sync --all-packages

02 · автор

Открыть студию

Ответы модели берутся из mock_llm/. На macOS сторож testcontainers отключается флагом.

# localhost:8765
TESTCONTAINERS_RYUK_DISABLED=true \
uv run gen studio \
  --pack packs/spike --mock

03 · студент

Поднять практику

Первый запуск собирает образ и накатывает схему пака, дальше старт занимает секунды.

# deploy/student · localhost:8766
docker compose up

оформление курса

Тема лежит в паке рядом с заданиями

Одна папка theme/ задаёт цвета, радиусы, логотип и, если нужно, свой CSS — приложение студента открывается уже в фирменном виде. Семь готовых тем в комплекте, включая полную брендовую пару.

paper светлая plum светлая blueprint тёмная forest тёмная amber-dark тёмная high-contrast для проектора t-bank бренд целиком

дальше

Забрать репозиторий или сначала почитать

В документации — руководства автора и студента, контракт темы, формат пака, справочник API и разбор частых ошибок.