Files
Learn_System/backend/scripts/fix_ctmath_inline_opts.js
T
Maxim Dolgolyov 9b1abb83f8 fix(ct-math): варианты ответа из текста → нормальный opts_json (mc ctmath)
У части mc-задач ЦТ (формат РИКЗ «укажите номер») список ответов был вшит
в текст («1) 44; 2) 22; …»), а opts содержали лишь цифры-указатели — рисовалось
«а) 1, б) 2…» + значения строкой. Скрипт fix_ctmath_inline_opts.js вытаскивает
список из текста в opts_json (метка=цифра, текст=значение), пересчитывает answer,
очищает текст. Последовательный парсер сохраняет ';' внутри значений (интервалы).
Dry: 281 кандидат → 213 чинятся чисто, 68 нестандартных пропущены (без порчи).

Запись (UPDATE 213) — запускает пользователь (--apply), как и прочие записи в БД.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 11:06:42 +03:00

86 lines
4.3 KiB
JavaScript
Raw 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';
/*
* Фикс mc-задач ctmath, где варианты ответа вшиты в текст («1) 44; 2) 22; …»),
* а opts_json содержит лишь цифры-указатели. Вытаскивает список из текста в
* нормальный opts_json (метка=цифра, текст=значение), пересчитывает answer,
* очищает текст. Только для чисто распознанных случаев (иначе пропуск).
* node backend/scripts/fix_ctmath_inline_opts.js # dry: статистика+выборка
* node backend/scripts/fix_ctmath_inline_opts.js --apply # запись (UPDATE)
*/
const db = require('../src/db/db');
const APPLY = process.argv.includes('--apply');
// Разбор инлайн-списка "1) v1; 2) v2; … N) vN."
// Последовательный: режем значение только по " ; (n+1)) " следующего номера,
// поэтому ';' внутри значений (интервалы вида (-6;9)) сохраняются.
function parseInline(text) {
const m1 = text.match(/(^|[\s:>(])1\)\s/);
if (!m1) return null;
const start = m1.index + m1[1].length; // позиция "1)"
const stem = text.slice(0, start).replace(/[\s:]+$/, '').trim();
if (!stem) return null;
let rest = text.slice(start);
const h1 = /^1\)\s*/;
if (!h1.test(rest)) return null;
rest = rest.replace(h1, ''); // "1)" снимаем один раз
const pairs = [];
let n = 1;
while (true) {
const nextRe = new RegExp('\\s*;?\\s*' + (n + 1) + '\\)\\s');
const nm = rest.match(nextRe);
let val;
if (nm) { val = rest.slice(0, nm.index); rest = rest.slice(nm.index + nm[0].length); }
else { val = rest; rest = ''; } // последний пункт
val = val.replace(/[;.\s]+$/, '').trim();
if (!val) return null;
pairs.push([String(n), val]);
if (!nm) break;
n++;
}
if (pairs.length < 2) return null;
return { stem, pairs };
}
const rows = db.prepare("SELECT id, text_html, opts_json, answer FROM exam_tasks WHERE exam_key='ctmath' AND task_type='mc'").all();
const stat = { total: rows.length, candidate: 0, fixed: 0, skip_notdigit: 0, skip_parse: 0, skip_count: 0, skip_answer: 0 };
const updates = [];
for (const r of rows) {
let opts; try { opts = JSON.parse(r.opts_json); } catch { continue; }
const texts = opts.map(p => String(p[1]).replace(/\$/g, '').trim());
const isDigitPtr = texts.length >= 2 && texts.every(x => /^[1-9][0-9]?$/.test(x));
if (!isDigitPtr) { stat.skip_notdigit++; continue; }
stat.candidate++;
const parsed = parseInline(r.text_html);
if (!parsed) { stat.skip_parse++; continue; }
if (parsed.pairs.length !== opts.length) { stat.skip_count++; continue; }
// correctDigit = указатель, на который ссылается текущий answer
const ai = opts.findIndex(p => String(p[0]).toLowerCase() === String(r.answer).toLowerCase());
const correctDigit = ai >= 0 ? String(opts[ai][1]).replace(/\$/g, '').trim() : null;
if (!correctDigit || !/^[1-9][0-9]?$/.test(correctDigit) || Number(correctDigit) > parsed.pairs.length) { stat.skip_answer++; continue; }
const newOpts = JSON.stringify(parsed.pairs); // [["1","44"],...]
updates.push({ id: r.id, text: parsed.stem, opts: newOpts, answer: correctDigit, _old: r.text_html, _newpairs: parsed.pairs });
stat.fixed++;
}
console.log(APPLY ? '[APPLY]' : '[DRY-RUN]', 'mc всего', stat.total);
console.log('Статистика:', JSON.stringify(stat));
console.log('\n— Выборка (3) —');
for (const u of updates.slice(0, 3)) {
console.log(`\n id=${u.id}`);
console.log(' было text:', u._old.replace(/\s+/g, ' ').slice(0, 120));
console.log(' стало text:', u.text.replace(/\s+/g, ' ').slice(0, 90));
console.log(' стало opts:', u.opts.slice(0, 160), '| answer:', u.answer);
}
if (!APPLY) { console.log('\nDRY-RUN: запись НЕ выполнялась. Запись: --apply'); process.exit(0); }
const upd = db.prepare('UPDATE exam_tasks SET text_html=@text, opts_json=@opts, answer=@answer WHERE id=@id');
let n = 0;
for (const u of updates) { upd.run({ id: u.id, text: u.text, opts: u.opts, answer: u.answer }); n++; }
console.log(`\nОбновлено ${n} задач.`);