From 8e80d77babf46784d62a5b11b1575d6a8d433691 Mon Sep 17 00:00:00 2001 From: Patricia Romaniuc Date: Wed, 7 Jan 2026 12:17:21 +0200 Subject: [PATCH 1/2] feat(math-templated): add scoring trace in outcome function PD-5468 --- .../controller/src/__tests__/index.test.jsx | 2 +- .../math-templated/controller/src/index.js | 103 +++++++++++++++++- 2 files changed, 101 insertions(+), 4 deletions(-) diff --git a/packages/math-templated/controller/src/__tests__/index.test.jsx b/packages/math-templated/controller/src/__tests__/index.test.jsx index 2a9abca925..315ebd562b 100644 --- a/packages/math-templated/controller/src/__tests__/index.test.jsx +++ b/packages/math-templated/controller/src/__tests__/index.test.jsx @@ -106,7 +106,7 @@ describe('outcome', () => { { ...sessionValue }, env2, ); - expect(result).toEqual(expected); + expect(result).toEqual(expect.objectContaining(expected)); }); }; diff --git a/packages/math-templated/controller/src/index.js b/packages/math-templated/controller/src/index.js index 78e62be69d..01c77747c2 100644 --- a/packages/math-templated/controller/src/index.js +++ b/packages/math-templated/controller/src/index.js @@ -94,21 +94,118 @@ export const getPartialScore = (question, session) => { return 1; }; +/** + * Generates detailed trace log for math-templated scoring evaluation + * @param {Object} question + * @param {Object} session + * @param {Object} env + * @returns {Array} traceLog + */ +export const getLogTrace = (question, session, env) => { + const traceLog = []; + + if (!session || !session.answers || Object.keys(session.answers).length === 0) { + traceLog.push('Student did not enter a response.'); + return traceLog; + } + + const answers = session.answers; + const responseIds = Object.keys(question.responses || {}); + const totalAreas = responseIds.length; + + traceLog.push(`${totalAreas} response area(s) defined in this question.`); + + let answeredCount = 0; + let correctCount = 0; + + responseIds.forEach((responseId, index) => { + const answerItem = answers[`r${responseId}`]; + const studentValue = answerItem?.value?.trim(); + const correctResponse = question.responses[responseId] || {}; + + const cleanStudentAnswer = studentValue ? studentValue.replace(/\\/g, '') : ''; + + if (studentValue) { + answeredCount++; + traceLog.push(`Response area ${index + 1}: student entered ${cleanStudentAnswer}.`); + } else { + traceLog.push(`Response area ${index + 1}: no response entered.`); + } + + const validation = correctResponse.validation || question.validationDefault; + traceLog.push(`Validation mode: ${validation}.`); + + if (validation === 'literal') { + const allowTrailingZeros = correctResponse.allowTrailingZeros || false; + const ignoreOrder = correctResponse.ignoreOrder || false; + + traceLog.push(`Allow trailing zeros: ${allowTrailingZeros ? 'enabled' : 'disabled'}.`); + traceLog.push(`Ignore order: ${ignoreOrder ? 'enabled' : 'disabled'}.`); + } + + const acceptedValues = [correctResponse.answer].concat( + Object.values(correctResponse.alternates || {}) + ); + + if (acceptedValues.length > 1) { + traceLog.push(`Accepted answers: ${acceptedValues.length} (including alternates).`); + } + + if (studentValue) { + const isCorrect = getIsAnswerCorrect(correctResponse, answerItem); + if (isCorrect) { + correctCount++; + traceLog.push(`Response area ${index + 1} is correct.`); + } else { + traceLog.push(`Response area ${index + 1} is incorrect.`); + } + } + }); + + traceLog.push(`${correctCount} out of ${totalAreas} response area(s) are correct.`); + + const partialScoringEnabled = partialScoring.enabled(question, env); + traceLog.push( + `Scoring method: ${partialScoringEnabled ? 'partial scoring' : 'all-or-nothing scoring'}.` + ); + + if (partialScoringEnabled) { + traceLog.push('Each correct response area contributes to the score.'); + } else { + traceLog.push('Student must answer all response areas correctly to receive full credit.'); + } + + const correctness = getCorrectness(question, env, session); + traceLog.push(`Overall correctness: ${correctness.correctness}.`); + traceLog.push(`Final score: ${correctness.score}.`); + + return traceLog; +}; + export const outcome = (question, session, env) => new Promise((resolve) => { if (!session || isEmpty(session)) { - resolve({ score: 0, empty: true }); + resolve({ + score: 0, + empty: true, + logTrace: ['Student did not enter a response.'] + }); + return; } + const partialScoringEnabled = partialScoring.enabled(question, env); session = normalizeSession(session); if (env.mode !== 'evaluate') { - resolve({ score: undefined, completed: undefined }); + resolve({ score: undefined, completed: undefined, logTrace: [] }); } else { const correctness = getCorrectness(question, env, session, true); const score = correctness.score; - resolve({ score: partialScoringEnabled ? score : score === 1 ? 1 : 0 }); + resolve({ + score: partialScoringEnabled ? score : score === 1 ? 1 : 0, + logTrace: getLogTrace(question, session, env) + }); } }); From 55be82c9ecc2cd00881fcaad732ae8403637486f Mon Sep 17 00:00:00 2001 From: Patricia Romaniuc Date: Wed, 7 Jan 2026 12:19:02 +0200 Subject: [PATCH 2/2] fix: remove extra space --- packages/math-templated/controller/src/__tests__/index.test.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/math-templated/controller/src/__tests__/index.test.jsx b/packages/math-templated/controller/src/__tests__/index.test.jsx index 315ebd562b..6da9f74d91 100644 --- a/packages/math-templated/controller/src/__tests__/index.test.jsx +++ b/packages/math-templated/controller/src/__tests__/index.test.jsx @@ -106,7 +106,7 @@ describe('outcome', () => { { ...sessionValue }, env2, ); - expect(result).toEqual(expect.objectContaining(expected)); + expect(result).toEqual(expect.objectContaining(expected)); }); };