Files
Learn_System/frontend/js/exam-prep/answer-check.js

89 lines
3.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
/* ──────────────────────────────────────────────────────────────────
Answer normalization + correctness check.
Same algorithm runs on user input and stored canonical answer
(from /tasks endpoint), then compared.
Canonical answer forms (produced by backend/scripts/import-exam-tasks.js):
MC: single Cyrillic letter: "а" | "б" | "в" | "г" | "д"
open NUM: decimal: "-2" "7500" "1.5" "0.25"
open FRAC: fraction: "9/4" "-104/9" (sign on numerator)
open PAIR: two values, ";" sep: "-2;4"
The user can type the answer in many equivalent forms — the normalizer
accepts any form and decides equivalence numerically.
────────────────────────────────────────────────────────────────── */
(function () {
const EPS = 1e-6;
// Try to coerce a string to a number; returns NaN on failure.
function toNumber(s) {
if (s == null) return NaN;
let t = String(s).trim().replace(/\$/g, '').replace(/\s+/g, '').replace(',', '.');
// Fraction "a/b" → a/b
const f = t.match(/^(-?\d+(?:\.\d+)?)\s*\/\s*(-?\d+(?:\.\d+)?)$/);
if (f) {
const num = Number(f[1]);
const den = Number(f[2]);
if (!Number.isFinite(num) || !Number.isFinite(den) || den === 0) return NaN;
return num / den;
}
const n = Number(t);
return Number.isFinite(n) ? n : NaN;
}
// Parse a "pair" answer "A;B" — accepts ";" or "," or " и " or whitespace
function toPair(s) {
if (s == null) return null;
const t = String(s).trim().replace(/\$/g, '').replace(/\s+и\s+/g, ';');
const parts = t.split(/[;,]/).map(p => p.trim()).filter(Boolean);
if (parts.length !== 2) return null;
const a = toNumber(parts[0]);
const b = toNumber(parts[1]);
if (Number.isNaN(a) || Number.isNaN(b)) return null;
// Order-insensitive comparison: return sorted pair
return a <= b ? [a, b] : [b, a];
}
/* Detect form of the canonical answer.
Returns: 'mc' | 'pair' | 'numeric' */
function detectForm(canonical) {
if (canonical == null) return 'numeric';
const t = String(canonical).trim();
if (/^[а-д]$/.test(t)) return 'mc';
if (/^[^;]+;[^;]+$/.test(t)) return 'pair';
return 'numeric';
}
/* Main check. Returns true iff user input matches canonical answer.
False if either side can't be normalized.
Both inputs are strings as returned by the form or stored in DB. */
function check(userInput, canonical) {
if (userInput == null || canonical == null) return false;
const form = detectForm(canonical);
if (form === 'mc') {
const u = String(userInput).trim().toLowerCase();
return u === String(canonical).trim().toLowerCase();
}
if (form === 'pair') {
const cp = toPair(canonical);
const up = toPair(userInput);
if (!cp || !up) return false;
return Math.abs(cp[0] - up[0]) < EPS && Math.abs(cp[1] - up[1]) < EPS;
}
// Numeric (integer / decimal / fraction)
const c = toNumber(canonical);
const u = toNumber(userInput);
if (Number.isNaN(c) || Number.isNaN(u)) return false;
return Math.abs(c - u) < EPS;
}
window.EP = window.EP || {};
window.EP.answer = { check, detectForm, toNumber, toPair };
})();