From fb81beca3961d9a1c06887e2b813ecbac988d5cc Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Thu, 25 Jun 2026 16:52:00 +0300 Subject: [PATCH] =?UTF-8?q?feat(trainer):=20=D1=80=D0=B0=D0=B7=D0=B1=D0=BE?= =?UTF-8?q?=D1=80=20=D1=82=D0=B8=D0=BF=D0=BE=D0=B2=D1=8B=D1=85=20=D0=BE?= =?UTF-8?q?=D1=88=D0=B8=D0=B1=D0=BE=D0=BA=20(=D1=80=D0=B5=D0=BF=D0=B5?= =?UTF-8?q?=D1=82=D0=B8=D1=82=D0=BE=D1=80=20C1,=20=D0=B4=D0=B2=D0=B8=D0=B6?= =?UTF-8?q?=D0=BE=D0=BA)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TrainerEngine.analyzeMistake(problem, value) -> {type, hint} | null: по неверному числовому ответу распознаёт типовую ошибку и даёт адресную подсказку, НЕ выдавая ответ - solve: уравнение восстанавливается как линейное f(x)=A·x+B по двум точкам (без структуры генератора) -> ловит «забыл разделить на коэффициент» - общие эвристики: перепутан знак (value≈-correct), близкая арифметическая ошибка (|Δ|≤20%), иначе generic - работает для solve/compute; пара/корни/неравенство пропускаются - смоук движка 825/825 (T20: nodivide/sign/arith/generic/null) - страница НЕ тронута (редизайн в параллельной сессии); показ подсказки на неверном ответе подключу на странице вместе с полировкой ввода систем после редизайна Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend/js/trainer/_trainer_engine.js | 42 ++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/frontend/js/trainer/_trainer_engine.js b/frontend/js/trainer/_trainer_engine.js index 6c6475f..4276dec 100644 --- a/frontend/js/trainer/_trainer_engine.js +++ b/frontend/js/trainer/_trainer_engine.js @@ -452,6 +452,47 @@ }; } + /* ── Разбор типовой ошибки ученика (репетитор, направление C) ── + По неверному ЧИСЛОВОМУ ответу пытается распознать типовую ошибку и дать + адресную подсказку, НЕ выдавая правильный ответ. Работает для solve/compute. + Для solve уравнение восстанавливается как линейное f(x)=A·x+B по двум точкам + (без структуры генератора) → ловим «забыл разделить на коэффициент». Плюс + общие эвристики: перепутан знак, близкая арифметическая ошибка. + Возвращает { type, hint } или null (ошибка не распознана / ответ верный). */ + function _linAB(problem) { + var av = problem.answerVar || 'x'; + var e0 = {}, e1 = {}; e0[av] = 0; e1[av] = 1; + var g0 = evalExpr(problem.lhsExpr, e0) - evalExpr(problem.rhsExpr, e0); + var g1 = evalExpr(problem.lhsExpr, e1) - evalExpr(problem.rhsExpr, e1); + if (!isFinite(g0) || !isFinite(g1)) return null; + return { A: g1 - g0, B: g0 }; // f(x) = A·x + B, корень = -B/A + } + function analyzeMistake(problem, value) { + if (!problem || !isFinite(value)) return null; + var kind = problem.kind || 'solve'; + if (kind !== 'solve' && kind !== 'compute') return null; // пара/корни/неравенство — отдельно + var correct = problem.answer; + var tol = 1e-6 * Math.max(1, Math.abs(correct)); + if (Math.abs(value - correct) <= tol) return null; // на самом деле верно + + // структурно: линейное уравнение → «забыл разделить на коэффициент» + if (kind === 'solve') { + var ab = _linAB(problem); + if (ab && Math.abs(ab.A) > 1.5) { + var noDivide = -ab.B; // значение на шаге «A·x = -B», ещё не делённое на A (= A·correct) + if (Math.abs(value - noDivide) <= Math.max(tol, 1e-6 * Math.abs(noDivide))) + return { type: 'nodivide', hint: 'Похоже, ты не разделил обе части на коэффициент при переменной (' + fmtNum(ab.A) + '). Раздели — и получишь ответ.' }; + } + } + // перепутан знак ответа + if (correct !== 0 && Math.abs(value + correct) <= Math.max(tol, 1e-6 * Math.abs(correct))) + return { type: 'sign', hint: 'Кажется, перепутан знак. Проверь знаки при переносе слагаемых через знак «=».' }; + // близкая арифметическая ошибка + if (Math.abs(value - correct) <= Math.max(1, Math.abs(correct) * 0.2)) + return { type: 'arith', hint: 'Очень близко — похоже на арифметическую ошибку в вычислениях. Пересчитай аккуратно.' }; + return { type: 'generic', hint: 'Разбери решение по шагам и попробуй похожую задачу.' }; + } + /* Система: ученик вводит пару «x = 2; y = 3» (или «2; 3»). Проверяем подстановкой в ОБА уравнения. Метки переменных опциональны; без меток — по порядку answerVars. */ function _checkSystem(problem, raw) { @@ -609,6 +650,7 @@ generateBatch: generateBatch, verifyRoot: verifyRoot, checkStudentAnswer: checkStudentAnswer, + analyzeMistake: analyzeMistake, checkStep: checkStep, makeRng: makeRng, // мелочи наружу для билдера/тестов