228bd885ed
- 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>
94 lines
5.6 KiB
JavaScript
94 lines
5.6 KiB
JavaScript
'use strict';
|
||
/*
|
||
* Входная диагностика для курса «ЦЭ/ЦТ — Математика».
|
||
* Собирает ОДИН test из РЕАЛЬНЫХ размеченных вопросов ЦТ-11 (banks 2011–2024):
|
||
* по 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 перекрывает выбор).');
|
||
}
|