fix(ctmath): чистка банка — год-пачки убраны из пикера пробников

- exam-prep.js: MOCK_VARIANT_RANGE — для ctmath показываем как пробники
  только чистые 30-задачные варианты [101;1999]; год-пачки (variant=год
  2011-2024 и 0, до 114 задач) остаются пулом для тренажёра по темам,
  но скрыты из пикера/mock-start/просмотра вариантов. math9 (1..80) не затронут
  (диапазон только для ctmath).
- mock.js: пикер «По варианту» — выпадающий список реальных вариантов
  (через listVariants) вместо number-input 1..N; раньше для ctmath он
  предлагал 1..18 и не доходил до 101 → пробник по варианту не запускался.
- cleanup_ctmath_bank.js: идемпотентный скрипт — ретайр битого id=1419
  (mc с противоречивым ответом → long), variants_count → 3 (чистых вариантов).
- seed_*: variants_count считается по диапазону [101;1999] (консистентно с роутом).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-06-18 22:22:32 +03:00
parent 6cd0a81d88
commit 68817cc612
6 changed files with 121 additions and 12 deletions
+18 -2
View File
@@ -15,6 +15,21 @@ router.param('examKey', (req, res, next, examKey) => {
next();
});
/* ── Mock/variant picker: какие variant считаются «пробниками» ──────
ctmath: год-пачки (variant=год 20112024 и 0) — это тематический ПУЛ для
тренажёра по темам, а НЕ чистые 30-задачные варианты (у части до 114 задач).
Чистые варианты-пробники нумеруются 3-значно (101, 102, …), а год-пачки —
4-значными годами (≥2011) и 0, поэтому фильтр — ДИАПАЗОН [101;1999], а не
просто порог (год 2024 > 101 и иначе бы прошёл!). В пикере пробников,
mock/start и просмотре вариантов показываем только чистые. Тренажёр по темам
отбирает по subtopic и этот фильтр НЕ использует — пул задач не теряется.
Для остальных треков (math9: варианты 1..80) диапазона нет — показываются все. */
const MOCK_VARIANT_RANGE = { ctmath: [101, 1999] };
const isMockVariant = (examKey, v) => {
const r = MOCK_VARIANT_RANGE[examKey];
return r ? (v >= r[0] && v <= r[1]) : (v >= 1);
};
/* ── Statements (prepared once) ────────────────────────────────── */
const SQL = {
listTracks: db.prepare(`
@@ -478,7 +493,7 @@ router.get('/:examKey/info', (req, res) => {
router.get('/:examKey/variants', (req, res) => {
const { examKey } = req.params;
if (!SQL.getTrack.get(examKey)) return res.status(404).json({ error: 'Unknown exam track' });
const rows = SQL.listVariants.all(req.user.id, examKey);
const rows = SQL.listVariants.all(req.user.id, examKey).filter(r => isMockVariant(examKey, r.variant));
const variants = rows.map(r => ({
n: r.variant,
label: `Вариант ${r.variant}`,
@@ -498,6 +513,7 @@ router.get('/:examKey/variants/:n/tasks', (req, res) => {
const { examKey } = req.params;
const n = parseInt(req.params.n, 10);
if (!Number.isFinite(n) || n < 1) return res.status(400).json({ error: 'Bad variant number' });
if (!isMockVariant(examKey, n)) return res.status(404).json({ error: 'Variant not found or empty' });
const rows = SQL.getVariantTasks.all(examKey, n);
if (!rows.length) return res.status(404).json({ error: 'Variant not found or empty' });
@@ -1180,7 +1196,7 @@ router.post('/:examKey/mock/start', (req, res) => {
if (source === 'variant') {
variant = Number(req.body?.variant);
if (!Number.isInteger(variant) || variant < 1) {
if (!Number.isInteger(variant) || !isMockVariant(examKey, variant)) {
return res.status(400).json({ error: 'Variant number required' });
}
const rows = SQL.getTasksByVariant.all(examKey, variant);