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, // мелочи наружу для билдера/тестов