"use strict"; /** * Grades a submission against the stored correct answers. * * The breakdown returned here is what gets sent back to the client, so it * deliberately never includes which option(s) were correct — only whether * the user's own answer for each question was right or wrong. Explanation * text is included only when the question was answered correctly, since * showing it for a wrong answer would effectively reveal the correct one. * * @param {Array} questions - QuizQuestion rows w/ .options (incl. is_correct), already fetched * @param {Object} answers - { [question_id]: optionId | optionId[] } submitted by the client */ function gradeSubmission(questions, answers = {}) { let totalPoints = 0; let earnedPoints = 0; const breakdown = questions.map((q) => { const points = q.points ?? 1; totalPoints += points; const correctIds = (q.options ?? []) .filter((o) => o.is_correct) .map((o) => o.option_id); const submitted = answers[q.question_id]; const submittedIds = Array.isArray(submitted) ? submitted : (submitted !== undefined && submitted !== null ? [submitted] : []); const isCorrect = submittedIds.length === correctIds.length && correctIds.every((id) => submittedIds.includes(id)); if (isCorrect) earnedPoints += points; return { question_id: q.question_id, type: q.type, question: q.question, points, is_correct: isCorrect, selected_option_ids: submittedIds, explanation: isCorrect ? (q.explanation ?? null) : null, options: (q.options ?? []).map((o) => ({ option_id: o.option_id, text: o.text, })), }; }); const score = totalPoints > 0 ? Math.round((earnedPoints / totalPoints) * 100) : 0; return { totalPoints, earnedPoints, score, breakdown }; } module.exports = { gradeSubmission };