feat(trainer): ИИ-тренажёр — генераторы задач + SimExpr-верификатор, прогресс, фича-флаг
- движок _trainer_engine.js: instantiate/generateBatch/verifyRoot/checkStudentAnswer/exprToLatex - 5 генераторов уравнений 7 класса (generators.js), приём «корень-вперёд» → целые ответы - страница /trainer: KaTeX-рендер, чипы-темы, мгновенная проверка, подсказка/решение, авто-выбор навыка - прогресс practice_progress (мигр.081) + /api/practice/progress|attempt + LS.practiceProgressList/Submit - фича-флаг trainer: тумблер в админке (Модули), requireFeature, FEATURE_HREFS (скрытие сайдбара+редирект), MODULE_CATALOG - fix: подключён Lucide CDN на странице (иначе иконки сайдбара пустые) - тесты practice.test.js (10/10); план развития plans/ai-trainer/PLAN.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
'use strict';
|
||||
/* ════════════════════════════════════════════════════════════════════════
|
||||
Генераторы уравнений — 7 класс (прототип). Это ДАННЫЕ, не код.
|
||||
|
||||
Приём «корень-вперёд»: выбираем целый корень (или множитель) и коэффициенты,
|
||||
затем ВЫВОДИМ свободный член так, чтобы ответ гарантированно был целым, а
|
||||
уравнение — решаемым. Поэтому самопроверка движка (verifyRoot) всегда
|
||||
проходит. Шаг решения — { note(текст), tex(формула) }; tex рендерится в KaTeX
|
||||
через TrainerEngine.exprToLatex (одно равенство на шаг, без цепочек a=b=c).
|
||||
|
||||
Прогрессия 7 класса: простое линейное → скобки → переменная в обеих частях →
|
||||
уравнение с дробью в знаменателе → дробный коэффициент. Дальше (Уровень 1):
|
||||
текстовые задачи через LLM с той же подстановочной верификацией.
|
||||
════════════════════════════════════════════════════════════════════════ */
|
||||
(function (global) {
|
||||
|
||||
var GENERATORS = [
|
||||
|
||||
/* 1. ax + b = c */
|
||||
{
|
||||
id: 'lin-basic',
|
||||
skill: 'linear-basic',
|
||||
title: 'Линейное: ax + b = c',
|
||||
grade: 7,
|
||||
pick: { a: [2, 9], b: [1, 20], root: [-9, 9] },
|
||||
require: 'root != 0',
|
||||
derive: { c: 'a*root + b', cmb: 'a*root' }, // cmb = c - b
|
||||
lhs: '{a}*x + {b}', rhs: '{c}',
|
||||
display: '{a}x + {b} = {c}',
|
||||
answerVar: 'x', answer: 'root', integerAnswer: true,
|
||||
solution: [
|
||||
{ note: 'Переносим число вправо:', tex: '{a}x = {cmb}' },
|
||||
{ note: 'Делим обе части на {a}:', tex: 'x = {cmb} / {a}' },
|
||||
{ note: 'Ответ:', tex: 'x = {ans}' }
|
||||
]
|
||||
},
|
||||
|
||||
/* 2. a(x + b) = c */
|
||||
{
|
||||
id: 'lin-paren',
|
||||
skill: 'linear-parentheses',
|
||||
title: 'Со скобками: a(x + b) = c',
|
||||
grade: 7,
|
||||
pick: { a: [2, 8], b: [1, 12], root: [-9, 9] },
|
||||
require: 'root != 0',
|
||||
derive: { c: 'a*(root + b)', ca: 'root + b' }, // ca = c / a
|
||||
lhs: '{a}*(x + {b})', rhs: '{c}',
|
||||
display: '{a}(x + {b}) = {c}',
|
||||
answerVar: 'x', answer: 'root', integerAnswer: true,
|
||||
solution: [
|
||||
{ note: 'Делим обе части на {a}:', tex: 'x + {b} = {ca}' },
|
||||
{ note: 'Переносим {b} влево:', tex: 'x = {ca} - {b}' },
|
||||
{ note: 'Ответ:', tex: 'x = {ans}' }
|
||||
]
|
||||
},
|
||||
|
||||
/* 3. ax + b = cx + d */
|
||||
{
|
||||
id: 'lin-both-sides',
|
||||
skill: 'linear-both-sides',
|
||||
title: 'Переменная с двух сторон: ax + b = cx + d',
|
||||
grade: 7,
|
||||
pick: { a: [3, 9], c: [1, 8], b: [1, 20], root: [-9, 9] },
|
||||
constraint: 'c < a', // гарантируем a - c > 0
|
||||
require: 'root != 0',
|
||||
derive: { d: '(a - c)*root + b', amc: 'a - c', dmb: '(a - c)*root' }, // dmb = d - b
|
||||
lhs: '{a}*x + {b}', rhs: '{c}*x + {d}',
|
||||
display: '{a}x + {b} = {c}x + {d}',
|
||||
answerVar: 'x', answer: 'root', integerAnswer: true,
|
||||
solution: [
|
||||
{ note: 'Собираем x слева, числа справа:', tex: '({a} - {c})x = {d} - {b}' },
|
||||
{ note: 'Приводим подобные:', tex: '{amc}x = {dmb}' },
|
||||
{ note: 'Делим на {amc}:', tex: 'x = {dmb} / {amc}' },
|
||||
{ note: 'Ответ:', tex: 'x = {ans}' }
|
||||
]
|
||||
},
|
||||
|
||||
/* 4. x/a + b = c (дробь в знаменателе) */
|
||||
{
|
||||
id: 'lin-frac-denom',
|
||||
skill: 'linear-fraction-denom',
|
||||
title: 'Дробь: x/a + b = c',
|
||||
grade: 7,
|
||||
pick: { a: [2, 6], k: [-6, 6], b: [1, 12] },
|
||||
require: 'k != 0',
|
||||
derive: { root: 'a*k', c: 'k + b', cmb: 'k' }, // root = a·k, cmb = c - b = k
|
||||
lhs: 'x/{a} + {b}', rhs: '{c}',
|
||||
display: 'x/{a} + {b} = {c}',
|
||||
answerVar: 'x', answer: 'root', integerAnswer: true,
|
||||
solution: [
|
||||
{ note: 'Вычитаем {b}:', tex: 'x/{a} = {cmb}' },
|
||||
{ note: 'Умножаем обе части на {a}:', tex: 'x = {cmb} * {a}' },
|
||||
{ note: 'Ответ:', tex: 'x = {ans}' }
|
||||
]
|
||||
},
|
||||
|
||||
/* 5. (a·x)/b = c (дробный коэффициент) */
|
||||
{
|
||||
id: 'lin-coef-frac',
|
||||
skill: 'linear-coef-frac',
|
||||
title: 'Дробный коэффициент: ax/b = c',
|
||||
grade: 7,
|
||||
pick: { a: [2, 5], b: [2, 5], m: [-5, 5] },
|
||||
require: 'm != 0',
|
||||
derive: { root: 'b*m', c: 'a*m', cb: 'a*m*b' }, // root = b·m, c = a·m, cb = c·b
|
||||
lhs: '{a}*x/{b}', rhs: '{c}',
|
||||
display: '{a}x/{b} = {c}',
|
||||
answerVar: 'x', answer: 'root', integerAnswer: true,
|
||||
solution: [
|
||||
{ note: 'Умножаем обе части на {b}:', tex: '{a}x = {cb}' },
|
||||
{ note: 'Делим на {a}:', tex: 'x = {cb} / {a}' },
|
||||
{ note: 'Ответ:', tex: 'x = {ans}' }
|
||||
]
|
||||
}
|
||||
|
||||
];
|
||||
|
||||
function get(id) {
|
||||
for (var i = 0; i < GENERATORS.length; i++) if (GENERATORS[i].id === id) return GENERATORS[i];
|
||||
return null;
|
||||
}
|
||||
|
||||
global.TrainerGenerators = {
|
||||
list: function () { return GENERATORS.slice(); },
|
||||
get: get,
|
||||
GENERATORS: GENERATORS
|
||||
};
|
||||
|
||||
})(typeof window !== 'undefined' ? window : globalThis);
|
||||
Reference in New Issue
Block a user