- 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>
17 KiB
ИИ-Тренажёр — план развития модуля
Модуль /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 (адаптивность) для удержания.