Files
Learn_System/backend/scripts/seed_ctmath_diagnostic.js
T
Maxim Dolgolyov 228bd885ed feat(ct-math): диагностический тест из реальных вопросов банка (tests.id=164)
- backend/scripts/seed_ctmath_diagnostic.js — идемпотентный сбор ОДНОГО test
  «Диагностика ЦЭ/ЦТ — Математика» из размеченных вопросов ЦТ-11 (в осн. 2024):
  5 single (базовые) + 10 fill-blank (средние/сложные), по 1 на ключевую тему.
  Новых вопросов не авторит. Применён: test id=164, 15 вопросов, лимит 40 мин.
  Выдать = assignment с test_id=164.
- BUILD_ON_QUESTIONS.md / README: отметка о готовой диагностике, статус.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 22:16:27 +03:00

94 lines
5.6 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';
/*
* Входная диагностика для курса «ЦЭ/ЦТ — Математика».
* Собирает ОДИН test из РЕАЛЬНЫХ размеченных вопросов ЦТ-11 (banks 20112024):
* по 1 заданию на ключевую тему, смесь уровней (single 🟢 → fill-blank 🔴).
* Новых вопросов НЕ авторит — только группирует существующие.
* ИДЕМПОТЕНТЕН: если test с таким title есть — не дублирует.
* Запуск: node backend/scripts/seed_ctmath_diagnostic.js (применить)
* node backend/scripts/seed_ctmath_diagnostic.js --dry (показать выбор)
*/
const db = require('../src/db/db');
const DRY = process.argv.includes('--dry');
const MATH_ID = 3;
const TITLE = 'Диагностика ЦЭ/ЦТ — Математика';
const DESC = 'Входная диагностика: задания по ключевым темам (от базовых до сложных) для определения уровня и приоритетных тем подготовки к ЦЭ/ЦТ.';
// Слоты: тема (по имени) + предпочтительный тип + уровень-зонд.
// Исключаем набор year=2025 («Экзамен 9»): берём только размеченные ЦТ-11 (year<=2024).
const SLOTS = [
['Теория чисел', 'single', 'base'],
['Арифметика и степени', 'single', 'base'],
['Квадратные уравнения', 'single', 'base'],
['Тригонометрия', 'single', 'base'],
['Числовые промежутки', 'single', 'base'],
['Словесные задачи', 'fill-blank', 'mid'],
['Прогрессии', 'fill-blank', 'mid'],
['Функции', 'fill-blank', 'mid'],
['Геометрия', 'fill-blank', 'mid'],
['Окружность и круг', 'single', 'mid'],
['Стереометрия', 'fill-blank', 'mid'],
['Логарифмы', 'fill-blank', 'hard'],
['Неравенства', 'fill-blank', 'hard'],
['Уравнения', 'fill-blank', 'hard'],
['Показательные неравенства','fill-blank', 'hard'],
];
function topicId(name) {
const r = db.prepare('SELECT id FROM topics WHERE subject_id=? AND LOWER(name)=LOWER(?)').get(MATH_ID, name);
return r && r.id;
}
function adminId() {
const u = db.prepare("SELECT id FROM users WHERE role='admin' ORDER BY id LIMIT 1").get()
|| db.prepare('SELECT id FROM users ORDER BY id LIMIT 1').get();
return u && u.id;
}
// Кандидаты по теме: сперва предпочт. тип, потом любой; только размеченные ЦТ-11 (year<=2024 или not null),
// исключая набор «Экзамен 9» (source_type='экзамен 9'); 2024 в приоритете, затем свежие.
function candidates(tid, type) {
const order = "ORDER BY (year=2024) DESC, year DESC, id";
const base = `SELECT id, type, year, substr(text,1,70) AS t FROM questions
WHERE subject_id=${MATH_ID} AND topic_id=${tid}
AND (source_type IS NULL OR source_type <> 'экзамен 9')`;
const pref = db.prepare(`${base} AND type=? ${order} LIMIT 8`).all(type);
const any = db.prepare(`${base} ${order} LIMIT 8`).all();
// предпочт. тип впереди, затем остальные (для фолбэка)
const seen = new Set(pref.map(r => r.id));
return [...pref, ...any.filter(r => !seen.has(r.id))];
}
const used = new Set();
const picks = [];
for (const [name, type, level] of SLOTS) {
const tid = topicId(name);
if (!tid) { console.log(` [skip] нет темы: ${name}`); continue; }
const cand = candidates(tid, type).find(r => !used.has(r.id));
if (!cand) { console.log(` [skip] нет вопросов: ${name}`); continue; }
used.add(cand.id);
picks.push({ name, level, ...cand });
}
console.log(DRY ? '[DRY-RUN] выбранные вопросы диагностики:' : '[APPLY] диагностика:');
const mark = { base: 'базовый', mid: 'средний', hard: 'сложный' };
picks.forEach((p, i) => console.log(
` ${String(i + 1).padStart(2)}. [${mark[p.level]}] ${p.name} | qid ${p.id} (${p.type}, ${p.year || '—'}) — ${p.t.replace(/\s+/g, ' ')}…`
));
console.log(`\nВсего отобрано: ${picks.length} заданий.`);
const existing = db.prepare("SELECT id FROM tests WHERE subject_slug='math' AND title=?").get(TITLE);
if (existing) {
console.log(`\nТест «${TITLE}» уже существует (id ${existing.id}) — не дублирую.`);
} else if (DRY) {
console.log(`\nDRY-RUN: тест НЕ создан. Будет создан с ${picks.length} вопросами.`);
} else {
const by = adminId();
const testId = db.prepare(
'INSERT INTO tests (title, subject_slug, description, show_answers, time_limit, created_by) VALUES (?,?,?,?,?,?)'
).run(TITLE, 'math', DESC, 1, 40, by).lastInsertRowid;
const ins = db.prepare('INSERT INTO test_questions (test_id, question_id, order_index) VALUES (?,?,?)');
picks.forEach((p, i) => ins.run(testId, p.id, i));
console.log(`\nСоздан тест «${TITLE}» (id ${testId}, ${picks.length} вопросов, лимит 40 мин).`);
console.log('Выдать классу/ученику: assignment с test_id=' + testId + ' (mode неважен, test_id перекрывает выбор).');
}