Files
Learn_System/plans/ai-trainer/PLAN.md
T
Maxim Dolgolyov cd7c75ff08 feat(trainer): P4 — авторинг задач учителем + раздача классу
- POST /api/practice/author: учитель пишет story/lhs/rhs/answer → та же проверка подстановкой (validateAndVerify) → пул; не сходится → 422
- POST /api/practice/assign: выдать тему классу → durable pushNotif каждому ученику (ссылка /trainer); владелец/админ, чужой → 403
- клиент: LS.practiceAuthor/Assign; в теме «Текстовые задачи» учителю кнопки «Своя задача» (модалка-форма) и «Выдать классу» (пикер классов)
- тесты: author (валид→пул, неверный→422, ученик→403), assign (владелец уведомляет, чужой→403) — practice 19/19 + practice-gen 16/16
- смоук страницы 27/27; план P4 → DONE (lean: ручной авторинг + раздача, без полного DSL-конструктора)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 14:30:02 +03:00

175 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ИИ-Тренажёр — план развития модуля
Модуль `/trainer`: бесконечные задачи, которые рождаются из **данных-генераторов**, а
математика считается и проверяется **детерминированно** через `SimExpr` (без `eval`).
LLM в ядре не участвует — его роль (Уровень 1+) сочинять генераторы и текстовые задачи,
которые тот же слой верифицирует подстановкой. Тема-пилот: **уравнения, 7 класс**.
## Инвариант корректности (не нарушать)
> Любой источник задачи (генератор, шаблон, LLM) обязан пройти `TrainerEngine.verifyRoot`:
> заявленный корень подставляется в уравнение, расходится → задача отбрасывается.
> Та же подстановка проверяет ответ ученика (принимает `5`, `x=5`, `10/2`, `2+3`).
> Выражения — ТОЛЬКО `SimExpr` (whitelist, без `eval`/`new Function`). Цвета/текст от
> пользователя — только в безопасные стоки или с escape.
## Состояние: Phase 0 — DONE (прототип, в этом коммите)
- **Движок** `frontend/js/trainer/_trainer_engine.js`: `instantiate` / `generateBatch` /
`verifyRoot` / `checkStudentAnswer` / `exprToLatex` (AST→LaTeX, reusable) / `makeRng`.
- **Генераторы** `frontend/js/trainer/generators.js`: 5 типов (линейное `ax+b=c`, скобки,
переменная с двух сторон, дробь `x/a+b=c`, дробный коэффициент `ax/b=c`). Приём
«корень-вперёд» → гарантированно целые ответы, самопроверка всегда проходит.
- **Страница** `frontend/trainer.html`: KaTeX-рендер уравнений и шагов, чипы-темы,
мгновенная проверка, подсказка/решение, авто-выбор первого неосвоенного навыка.
- **Прогресс на сервере**: `practice_progress` (мигр.081), `practiceController` +
`routes/practice` (`/api/practice/progress|attempt`), клиент `LS.practiceProgressList/Submit`.
Мастерство = серия 5 верных подряд (липкое).
- **Фича-флаг** `trainer`: тумблер в админке (Модули), `requireFeature('trainer')`,
скрытие из сайдбара + редирект страницы (`FEATURE_HREFS`), запись в `MODULE_CATALOG`.
- Тесты: `practice.test.js` (10/10), headless-смоуки движка/страницы. lint:routes 0.
---
## Phase 1 — Ширина контента (генераторы) — DONE
**Сделано:** таксономия `тема → навык` с метаданными (`topic/order/subject/grade`),
`TrainerGenerators.topics()/byTopic()`. **13 генераторов в 3 темах**: Уравнения (7:
`ax+b=c`, `a(x+b)=c`, `ax+b=cx+d`, `a(x+b)=c(x+d)`, `x/a+b=c`, `ax/b=c`, `(ax+b)/c=d`),
Пропорции (3), Проценты (3, `kind:'compute'` — текстовый prompt + проверка подстановкой).
UI: выбор темы (вкладки) → навыки (чипы) с бейджами мастерства, авто-выбор первой
неосвоенной темы/навыка. **Подробные объяснения**: каждый шаг расписан словами + шаг
«Проверка» (подстановка корня). Движок: `exprToLatex` чинит отрицательные множители
(`7·(5)`), `kind:'compute'`. Смоуки 238/238 (движок) + 19/19 (страница).
**Цель (исходная):** перестать быть «демкой одной темы». Структура `класс → предмет → тема → навык`.
- Реестр генераторов: вынести в данные с метаданными `{ grade, subject, topic, skill, order, difficulty }`.
Группировка чипов по темам/классам; выбор класса/предмета вверху.
- Новые генераторы 7 кл: пропорции, раскрытие скобок с обеих сторон `a(x+b)=c(x+d)`,
уравнения с дробью-уравнением `(ax+b)/c = d`, простые буквенные преобразования.
- Соседние темы (параметрические, без LLM): упрощение выражений, степени, проценты,
линейные неравенства (расширить `checkStudentAnswer` под интервалы — см. P5).
- **Acceptance:** ≥3 темы × ≥3 навыка, у каждого generateBatch(50) даёт 50 разных корректных
задач; solvability-смоук на сетке параметров.
## Phase 2 — Адаптивность и интервальное повторение — DONE
**Сделано:** `frontend/js/trainer/adaptive.js` (`window.TrainerAdaptive`, чистая логика) —
`nextSkill` (приоритет: in-session повтор → серверный due → прогрессия → удержание по
box), `onWrong/onCorrect` (in-session очередь повторения), `sessionStats`. **Умная
тренировка** на странице (по умолчанию вкл, тумблер): авто-подбор навыка, ведёт от
простого к сложному, возвращает ошибки; сессия из 10 задач с **итогом** (верно/точность/
навыки/«стоит повторить»). Неверный ответ авто-показывает решение. Сервер: SR-поля
`box`+`due_at` на `practice_progress` (мигр.**082**, Leitner-интервалы 0/1/3/7/16/30 дней),
`listProgress` отдаёт `box/due_at/due`. Смоуки: adaptive 12/12, страница 23/23,
practice.test.js 11/11 (+SR box/due).
**Цель (исходная):** вести ученика, а не давать случайное.
- Диагностика на входе (по 1–2 задачи на навык) → стартовый уровень.
- Подбор следующего навыка по мастерству (escalate при серии, откат при ошибках).
- Ошибки уходят в очередь повторения (свой лёгкий SR или reuse flashcards Tier-1).
- «Продолжить тренировку», дневная норма/цель, сводка сессии (что освоено, над чем работать).
- Сервер: расширить `practice_progress` (или новая `practice_review_queue`); агрегаты для аналитики.
- **Acceptance:** сессия из N задач сама ведёт от простого к сложному; промахнутый навык
всплывает повторно; прогресс переживает перезаход.
## Phase 3 — Уровень 1: LLM-задачи с верификацией — DONE
**Сделано:** серверная проверка `backend/src/utils/practiceVerify.js` (грузит `SimExpr`
в Node через require, `verifyRoot` подстановкой). Сервис `practiceGenService.js`:
`buildMessages`→LLM→`parseProblem``validateAndVerify` (компиляция SimExpr + подстановка
корня + санитизация story/шагов) с **авторетраем по фидбэку**; LLM-вызов инъектируется
(`opts.ask`, дефолт — `assistantController.callLLMFailover`). Пул `practice_problems`
(мигр.**083**, status approved/draft). Эндпоинты: `POST /api/practice/generate`
(учитель/админ) + `GET /api/practice/pool` (ученикам). Клиент: `LS.practicePool/Generate`,
тема **«Текстовые задачи»** на странице (берёт из пула; учителю — кнопка «Сгенерировать»).
Гарантия: невалидная/неверная задача в БД НЕ пишется → ученику не попадёт.
Тесты `practice-gen.test.js` 13/13 (verify, ретраи, off→503, 403 ученику, пул).
**Цель (исходная):** текстовые/контекстные задачи, которых не даёт параметрика.
- LLM (через провайдеров админки) генерирует `{ lhs, rhs, answer, story }`; сервер прогоняет
`verifyRoot`; расхождение → авторетрай с фидбэком («корень не удовлетворяет, исправь»).
- Кэш-пул `practice_problems` (предгенерация, ревью учителем) — не платить за каждый показ.
- Генерация «по теме урока» (связка с theory/exam-prep).
- **Acceptance:** доля задач, прошедших верификацию с 1–2 ретраев, ≥95%; пул кэшируется;
ни одна неверная задача не доходит до ученика (гарантирует инвариант).
## Phase 4 — Авторинг учителем — DONE (lean)
**Сделано (переиспользуя P3-проверку):** ручной авторинг — `POST /api/practice/author`
(учитель пишет story/lhs/rhs/answer → та же `validateAndVerify` подстановкой → пул;
не сходится → 422). Раздача классу — `POST /api/practice/assign` (владелец/админ →
durable `pushNotif` каждому ученику класса, ссылка `/trainer`). Клиент:
`LS.practiceAuthor/Assign`; в теме «Текстовые задачи» учителю — кнопки «Своя задача»
(модалка-форма с серверной проверкой) и «Выдать классу» (пикер классов → уведомление).
Тесты: author (валид→пул, неверный→422, ученик→403), assign (владелец уведомляет,
чужой→403). **Не делалось (осознанно):** полноценный визуальный конструктор
ПАРАМЕТРИЧЕСКИХ генераторов (pick/derive/lhs/rhs DSL) — крупный отдельный билдер;
текущий авторинг закрывает «учитель создаёт задачи + раздаёт классу» переиспользованием
пула и инварианта проверки.
**Цель (исходная):** учитель создаёт свои наборы и раздаёт классу (как sim-builder/Quantik Ф5).
- Конструктор генераторов: шаблон `lhs/rhs`, диапазоны параметров, формула ответа, шаги
решения; превью + клиентская валидация через `SimExpr.compile`.
- Хранение (таблица по образцу `custom_sims`), серверная `validateSpec` без исполнения,
раздача классу + уведомление, привязка к ДЗ/уроку.
- **Acceptance:** учитель собирает рабочий генератор без кода; ученик решает; права/видимость
как у custom-sim (own + раздано).
## Phase 5 — Типы ответов и проверки — DONE (частично)
**Сделано:** движок получил **несколько корней** (`gen.answers``problem.answers`;
`_checkMultiRoot` — ввод всех корней через «;», сверка мультимножеством) и
**эквивалентность выражений** (`kind:'simplify'`, `gen.srcExpr`/`answerExpr`;
`_sampleEquiv` — численный сэмплинг в фикс. точках, без Math.random; `_checkEquiv`).
`exprToLatex` чинит знаковые коэффициенты (`-5x`, `x²−5x+6`, `a(b)→a+b`). Новые
темы: **Упрощение** (привести подобные, раскрыть скобки) и **Квадратные** (Виета
`x²+bx+c=0`, разность квадратов — 2 корня). Страница: префикс «x=» и подсказка ввода
по типу, ответ-лейбл (корни/выражение). Смоук движка 291/291 (T11 roots, T12 simplify,
T13 latex). **Осталось (стретч):** неравенства (нужен парсер отношений) — не вошло.
**Цель (исходная):** не только «корень-число».
- Множество корней (квадратные/факторизация), интервалы (неравенства), упрощение выражений
(эквивалентность через численный сэмплинг по диапазону, а не строковое равенство).
- Пошаговый ввод (проверять каждый шаг подстановкой), несколько форматов ответа.
- **Acceptance:** квадратное уравнение принимает оба корня в любом порядке; `(x+1)^2`
`x^2+2x+1` через сэмплинг; неравенство принимает `x>3` и эквивалент.
## Phase 6 — Геймификация, аналитика, UX — DONE (частично)
**Сделано:** **учительская аналитика**`GET /api/practice/class-stats?class_id=`
(`classStats`, владелец класса/админ): агрегаты по навыкам (attempted/mastered/accuracy)
+ матрица ученик×навык. Клиент: кнопка «Аналитика класса» (учителю) → модалка с
**тепловой картой** (ученики × навыки, цвет по точности, ✓ освоено) + пикер классов.
`LS.practiceClassStats`. **Лёгкая геймификация**: строка общего прогресса «Освоено
навыков M из N · решено всего K» (из агрегатов `practice_progress`), бейджи мастерства
на чипах (P2). Тесты practice.test.js +4 (владелец видит, чужой/ученик→403, без id→400).
Смоук страницы 27/27. **Осталось (стретч):** XP в общую геймификацию, виртуальная
клавиатура, сократические подсказки — не вошло (отдельные крупные направления).
Изначальный список:
- XP/энергия/стрики (reuse инфраструктуры Квантика), бейджи мастерства на чипах (есть основа).
- Учительская аналитика: кто на каком навыке застрял, тепловая карта класса, отчёты.
- UX: виртуальная клавиатура для дробей/степеней, «почему неверно» (разбор ошибки),
сократические подсказки через Квантик-ассистента, мобильная раскладка, доступность.
---
## Сквозное
- **Безопасность:** только `SimExpr`; авторские генераторы — серверная `validateSpec` без
исполнения (длины/лимиты, escape текста), как `custom_sims`.
- **Тесты:** на каждый генератор — solvability-смоук (сетка параметров → есть корректные
задачи + достижим целевой ответ); бэкенд-тесты на новые роуты; headless-смоук страницы.
- **Контент = данные:** генераторы и темы — JS-данные/таблицы, не код.
## Рекомендуемый следующий шаг
**Phase 1** (ширина контента) — даёт наибольшую пользу при минимальном риске и переиспользует
готовый движок/проверку/страницу. Затем **Phase 2** (адаптивность) для удержания.