'use strict';
/* ───────────────────────────────────────────────────────────────────────────
seed_ctmath_ct2020_v1.js
Чистый вариант-пробник для трека exam-prep `ctmath`.
Источник: Централизованное тестирование (ЦТ) по математике, 2020, Вариант 1.
Формат 2020: Часть А = А1–А20 (закрытые), Часть В = В1–В12 (открытые; В1 — на
соответствие, В2 — множественный выбор). Всего **32 задания** (не 30!).
Перенабрано вручную в KaTeX по PDF:
F:\!Рабочие\ЦТ\Математика\Математика\ЦТ-ЦЭ\ЦТ 2020.pdf (10 вариантов, табл. ответов стр.44).
⚠️ Ответы решены самостоятельно и СВЕРЕНЫ с официальной таблицей (стр.44, столбец
«Вариант 1»): ВСЕ 32 совпали, включая A20=37√13/3, B5=-335, B8=-320, B9=160, B10=577,
B11=-16, B12=336. variant=116 (после ЦТ-2019 = 115).
Реконструкции заданий-«с-картинкой» (смысл/ответ сохранены, авто-проверка):
• А9 (точка и прямая на сетке) → A(-1;2), прямая l: y=-x; симметрия → (-2;1);
• А11 (графики плот/катер) → скорость плота и расстояние даны числами (→ 960 мин);
• А2/А7 — добавлены явные условия (точки на одной дуге; M,N — середины сторон);
• В1/В2 — данные предложений/утверждений приведены текстом (как в оригинале).
Идемпотентность: upsert по UNIQUE(exam_key, variant, task_idx). Без авторских ссылок
(политика «все учебники наши»).
Запуск:
node backend/scripts/seed_ctmath_ct2020_v1.js # DRY-RUN (по умолчанию)
node backend/scripts/seed_ctmath_ct2020_v1.js --apply # запись в БД
⚠️ Массовую запись в БД запускает ПОЛЬЗОВАТЕЛЬ вручную. Без --apply ничего не пишется.
─────────────────────────────────────────────────────────────────────────── */
const { DatabaseSync } = require('node:sqlite');
const path = require('path');
const APPLY = process.argv.includes('--apply');
const EXAM = 'ctmath';
const VARIANT = 116;
const N_TASKS = 32;
const PROV = 'ЦТ–2020, Вариант 1';
const R = String.raw;
const L = ['а', 'б', 'в', 'г', 'д'];
const mc = (...html) => html.map((h, i) => [L[i], h]);
/* ── 32 задания ─────────────────────────────────────────────────────────── */
const TASKS = [
// ── Часть A: А1–А20 ──────────────────────────────────────────────────────
{ idx: 1, type: 'mc', topic: 'functions', subtopic: 'fn-graphs', diff: 1,
text: R`Укажите номер точки, которая принадлежит графику функции $y=5^{x}$.`,
opts: mc('$(25;2)$', '$(2;10)$', '$(5;25)$', '$(2;25)$', '$(1;0)$'),
answer: 'г',
sol: R`При $x=2$ имеем $y=5^{2}=25$, поэтому точка $(2;25)$ лежит на графике.` },
{ idx: 2, type: 'mc', topic: 'planimetry', subtopic: 'plan-circle', diff: 2,
text: R`Вписанный угол $KML$ равен $38^\circ$. Точки $M$ и $N$ лежат на одной дуге окружности (по одну сторону от хорды $KL$). Найдите вписанный угол $KNL$.`,
opts: mc('$46^\circ$', '$38^\circ$', '$19^\circ$', '$52^\circ$', '$76^\circ$'),
answer: 'б',
sol: R`Вписанные углы, опирающиеся на одну и ту же дугу $KL$ с одной стороны, равны: $\angle KNL=\angle KML=38^\circ$.` },
{ idx: 3, type: 'mc', topic: 'numbers', subtopic: 'num-real', diff: 1,
text: R`Укажите номер выражения для натурального числа, содержащего $c$ десятков и $3$ единицы ($c$ — цифра).`,
opts: mc('$c+3$', '$3c$', '$3c+10$', '$10c+3$', '$30+c$'),
answer: 'г',
sol: R`$c$ десятков и $3$ единицы — это $10c+3$.` },
{ idx: 4, type: 'mc', topic: 'numbers', subtopic: 'num-real', diff: 1,
text: R`Определите, на сколько неизвестное слагаемое меньше суммы, если $x+20=80$.`,
opts: mc('$80$', '$20$', '$60$', '$40$', '$100$'),
answer: 'б',
sol: R`Неизвестное слагаемое $x=80-20=60$, сумма равна $80$. Разность $80-60=20$.` },
{ idx: 5, type: 'mc', topic: 'numbers', subtopic: 'num-real', diff: 1,
text: R`Среди точек $C(33)$, $D(24)$, $E(28)$, $F(43)$, $K(12)$ координатной прямой укажите точку, симметричную точке $A(5)$ относительно точки $B(19)$.`,
opts: mc('$C(33)$', '$D(24)$', '$E(28)$', '$F(43)$', '$K(12)$'),
answer: 'а',
sol: R`Симметричная точка имеет координату $2\cdot19-5=33$ — это точка $C(33)$.` },
{ idx: 6, type: 'mc', topic: 'numbers', subtopic: 'num-real', diff: 2,
text: R`Найдите значение выражения $\left(3\tfrac17-2\right)\cdot\left(1+\tfrac34\right):9$.`,
opts: mc('$1\tfrac{41}{63}$', '$\tfrac{3}{28}$', '$1\tfrac{19}{252}$', '$-\tfrac{11}{36}$', '$\tfrac29$'),
answer: 'д',
sol: R`$\left(\tfrac{22}{7}-2\right)\cdot\tfrac74:9=\tfrac87\cdot\tfrac74:9=2:9=\tfrac29$.` },
{ idx: 7, type: 'mc', topic: 'planimetry', subtopic: 'plan-triangles', diff: 2,
text: R`В треугольнике $ABC$ $\angle ABC=104^\circ$, $\angle ACB=29^\circ$. Точки $M$ и $N$ — середины сторон $BC$ и $AC$ соответственно. Найдите градусную меру угла $ANM$ четырёхугольника $ABMN$.`,
opts: mc('$151^\circ$', '$128^\circ$', '$119^\circ$', '$133^\circ$', '$104^\circ$'),
answer: 'г',
sol: R`$MN$ — средняя линия, поэтому $MN\parallel AB$ и $\angle MNC=\angle BAC=180^\circ-104^\circ-29^\circ=47^\circ$. Так как $A,N,C$ лежат на одной прямой, $\angle ANM=180^\circ-47^\circ=133^\circ$.` },
{ idx: 8, type: 'mc', topic: 'numbers', subtopic: 'num-divisibility', diff: 1,
text: R`У Юры некоторое количество марок, а у Яна — в $2$ раза больше. Все марки поместили в один альбом. Среди чисел $26$, $38$, $20$, $37$, $39$ выберите то, которое может выражать количество марок в альбоме.`,
opts: mc('$26$', '$38$', '$20$', '$37$', '$39$'),
answer: 'д',
sol: R`Всего марок $x+2x=3x$ — число, кратное $3$. Из данных чисел кратно $3$ только $39$.` },
{ idx: 9, type: 'mc', topic: 'functions', subtopic: 'fn-graphs', diff: 2,
text: R`Даны точка $A(-1;2)$ и прямая $l$, заданная уравнением $y=-x$. Найдите координаты точки, симметричной точке $A$ относительно прямой $l$.`,
opts: mc('$(1;1)$', '$(-1;0)$', '$(-2;1)$', '$(0;2)$', '$(-2;4)$'),
answer: 'в',
sol: R`Симметрия относительно прямой $y=-x$ переводит точку $(x;y)$ в $(-y;-x)$, поэтому $(-1;2)\to(-2;1)$.` },
{ idx: 10, type: 'mc', topic: 'functions', subtopic: 'fn-graphs', diff: 2,
text: R`График уравнения $1{,}8x-0{,}6y=a$ проходит через точку $A(-2;9)$. Найдите число $a$.`,
opts: mc('$-9$', '$9$', '$7$', '$-18$', '$-2{,}4$'),
answer: 'а',
sol: R`$a=1{,}8\cdot(-2)-0{,}6\cdot9=-3{,}6-5{,}4=-9$.` },
{ idx: 11, type: 'mc', topic: 'word-sequences', subtopic: 'word-problems', diff: 2,
text: R`Из двух пунктов навстречу друг другу одновременно отправляются плот (по течению) и катер (против течения). По графику движения скорость плота (равная скорости течения) составляет $0{,}5$ км/ч, а расстояние между пунктами — $8$ км. За сколько минут плот придёт в пункт, из которого отправился катер?`,
opts: mc('$1020$ мин', '$960$ мин', '$510$ мин', '$900$ мин', '$480$ мин'),
answer: 'б',
sol: R`Плоту нужно пройти $8$ км со скоростью $0{,}5$ км/ч: $\dfrac{8}{0{,}5}=16$ ч $=960$ мин.` },
{ idx: 12, type: 'mc', topic: 'expressions', subtopic: 'expr-powers-roots', diff: 2,
text: R`Внесите множитель под знак корня в выражении $-x\cdot\sqrt[5]{2x^{2}}$.`,
opts: mc('$\sqrt[5]{2x^{3}}$', '$\sqrt[5]{2x^{7}}$', '$\sqrt[5]{-2x^{7}}$', '$\sqrt[5]{-2x^{3}}$', '$\sqrt[5]{-2x^{10}}$'),
answer: 'в',
sol: R`$-x\cdot\sqrt[5]{2x^{2}}=\sqrt[5]{(-x)^{5}\cdot2x^{2}}=\sqrt[5]{-2x^{7}}$.` },
{ idx: 13, type: 'mc', topic: 'planimetry', subtopic: 'plan-circle', diff: 2,
text: R`В окружности радиуса $13$ проведена хорда $AB$. Точка $M$ делит хорду $AB$ на отрезки длиной $10$ и $12$. Найдите расстояние от точки $M$ до центра окружности.`,
opts: mc('$11$', '$7$', '$5$', '$6$', '$8$'),
answer: 'б',
sol: R`По свойству хорд $AM\cdot MB=R^{2}-OM^{2}$: $10\cdot12=169-OM^{2}$, откуда $OM^{2}=49$, $OM=7$.` },
{ idx: 14, type: 'mc', topic: 'equations', subtopic: 'eq-rational', diff: 3,
text: R`Для неравенства $(8-x)(x+3)\ge0$ укажите номера верных утверждений.
$1)$ число $0$ не является решением неравенства;
$2)$ неравенство равносильно неравенству $|x|\le8$;
$3)$ количество всех целых решений неравенства равно $12$;
$4)$ неравенство верно при $x\in[-2;3]$;
$5)$ решением неравенства является промежуток $[-8;3]$.`,
opts: mc('$2$ и $4$', '$3$ и $5$', '$3$ и $4$', '$1$ и $2$', '$1$ и $5$'),
answer: 'в',
sol: R`Решение неравенства — отрезок $[-3;8]$. Тогда: $0$ — решение (1 неверно); $|x|\le8$ даёт $[-8;8]$ (2 неверно); целых решений от $-3$ до $8$ ровно $12$ (3 верно); на $[-2;3]$ неравенство выполнено (4 верно); промежуток $[-3;8]$, не $[-8;3]$ (5 неверно). Верны $3$ и $4$.` },
{ idx: 15, type: 'mc', topic: 'planimetry', subtopic: 'plan-quadrilaterals', diff: 2,
text: R`Длины диагоналей ромба являются корнями уравнения $0{,}1x^{2}-2{,}2x+7{,}4=0$. Найдите площадь ромба.`,
opts: mc('$22$', '$48$', '$74$', '$11$', '$37$'),
answer: 'д',
sol: R`Уравнение равносильно $x^{2}-22x+74=0$; по теореме Виета произведение корней-диагоналей $d_1d_2=74$. Площадь ромба $\tfrac12 d_1d_2=37$.` },
{ idx: 16, type: 'mc', topic: 'planimetry', subtopic: 'plan-circle', diff: 3,
text: R`На одной стороне прямого угла с вершиной $O$ отмечены точки $A$ и $B$ так, что $OA=1{,}7$, $OB=a$, $OAА) Разность этой прогрессии равна …
Б) Первый член этой прогрессии равен …
В) Сумма первых восьми членов этой прогрессии равна …
Окончания: $1)\;2$; $\ 2)\;-13$; $\ 3)\;4$; $\ 4)\;-26$; $\ 5)\;-20$; $\ 6)\;3$.`,
answer: 'А6Б2В5',
ansShow: 'А6Б2В5',
sol: R`$a_9-a_5=4d=12$, $d=3$ (окончание 6). $a_{10}=a_1+9d=14$, $a_1=-13$ (окончание 2). $S_8=4(2a_1+7d)=4(-26+21)=-20$ (окончание 5). Ответ: А6Б2В5.` },
{ idx: 22, type: 'open', topic: 'trigonometry', subtopic: 'trig-identities', diff: 3,
text: R`Выберите номера трёх верных утверждений, если известно, что $\sin\alpha=\sin23^\circ$ и $\cos\alpha=-\cos23^\circ$ (запишите цифрами в порядке возрастания).
$1)$ $\sin(\alpha+23^\circ)=0$;
$2)$ $\operatorname{tg}\alpha>0$;
$3)$ $\operatorname{ctg}\alpha<0$;
$4)$ $\alpha$ — угол первой четверти;
$5)$ $\sin^{2}\alpha+\cos^{2}\alpha=1$;
$6)$ $\alpha=-23^\circ$.`,
answer: '135',
sol: R`Из условий $\alpha=157^\circ$ (вторая четверть). Тогда $\sin(157^\circ+23^\circ)=\sin180^\circ=0$ (1 верно); $\operatorname{tg}157^\circ<0$ (2 неверно); $\operatorname{ctg}157^\circ<0$ (3 верно); это вторая четверть (4 неверно); основное тождество всегда верно (5 верно); $\alpha\ne-23^\circ$ (6 неверно). Верны $1,3,5$.` },
{ idx: 23, type: 'open', topic: 'word-sequences', subtopic: 'word-problems', diff: 3,
text: R`В каждую из трёх корзин положили одинаковое количество яблок. Если в одну из корзин добавить $19$ яблок, то в ней окажется меньше, чем в двух других корзинах вместе. Если же в эту корзину положить ещё $23$ яблока, то в ней их станет больше, чем было первоначально в трёх корзинах вместе. Сколько яблок было в каждой корзине первоначально?`,
answer: '20',
sol: R`Пусть в корзине $x$ яблок. Тогда $x+19<2x$, то есть $x>19$; и $x+19+23>3x$, то есть $x<21$. Значит $x=20$.` },
{ idx: 24, type: 'open', topic: 'planimetry', subtopic: 'plan-quadrilaterals', diff: 3,
text: R`В равнобедренную трапецию, площадь которой равна $115$, вписана окружность радиуса $5$. Найдите периметр трапеции.`,
answer: '46',
sol: R`Высота $h=2r=10$. Площадь $\tfrac12(a+b)h=5(a+b)=115$, откуда $a+b=23$. Для описанной около окружности трапеции сумма оснований равна сумме боковых сторон, поэтому периметр $=2(a+b)=46$.` },
{ idx: 25, type: 'open', topic: 'trigonometry', subtopic: 'trig-equations', diff: 4,
text: R`Найдите произведение наименьшего корня (в градусах) на количество различных корней уравнения $\sin5x=\cos65^\circ$ на промежутке $(-90^\circ;90^\circ)$.`,
answer: '-335',
sol: R`$\cos65^\circ=\sin25^\circ$, поэтому $5x=25^\circ+360^\circ k$ или $5x=155^\circ+360^\circ k$, то есть $x=5^\circ+72^\circ k$ или $x=31^\circ+72^\circ k$. На $(-90^\circ;90^\circ)$ корни $-67^\circ,-41^\circ,5^\circ,31^\circ,77^\circ$ — всего $5$; наименьший $-67^\circ$. Произведение $-67\cdot5=-335$.` },
{ idx: 26, type: 'open', topic: 'planimetry', subtopic: 'plan-quadrilaterals', diff: 4,
text: R`Точки $N$ и $M$ лежат на сторонах $AB$ и $AD$ параллелограмма $ABCD$ так, что $AN:NB=1:2$ и $AM:MD=1:2$. Площадь треугольника $CMN$ равна $45$. Найдите площадь параллелограмма $ABCD$.`,
answer: '162',
sol: R`Пусть площадь параллелограмма равна $S$. Через векторы $\vec{AB}$ и $\vec{AD}$ площадь треугольника $CMN$ равна $\tfrac{5}{18}S$. Из $\tfrac{5}{18}S=45$ получаем $S=162$.` },
{ idx: 27, type: 'open', topic: 'equations', subtopic: 'eq-exponential', diff: 5,
text: R`Найдите произведение наибольшего целого отрицательного и наибольшего целого положительного решений неравенства $3\cdot16^{\frac{x^{2}-29}{-3x}}-10\cdot16^{\frac{x^{2}-29}{-6x}}>8$.`,
answer: '-32',
sol: R`Пусть $t=16^{\frac{x^{2}-29}{-6x}}>0$. Тогда $3t^{2}-10t-8>0$, $(3t+2)(t-4)>0$, значит $t>4$, то есть $\frac{x^{2}-29}{-6x}>\tfrac12$, что приводит к $\frac{x^{2}+3x-29}{x}<0$. Решение: $x<\frac{-3-5\sqrt5}{2}$ или $00$, $x\ne17$. Уравнение приводится к $\log_{18}\bigl(x\,|17-x|\bigr)=1$, то есть $x\,|17-x|=18$. При $x<17$: $x^{2}-17x+18=0$ (корни $p,q$ с $p+q=17$, $pq=18$); при $x>17$: $x=18$. Сумма квадратов $(17^{2}-2\cdot18)+18^{2}=253+324=577$.` },
{ idx: 31, type: 'open', topic: 'numbers', subtopic: 'num-divisibility', diff: 5,
text: R`Найдите все пары $(m,n)$ целых чисел, связанных соотношением $m^{2}+2m=n^{2}-6n+13$. Пусть $k$ — количество таких пар, $m_0$ — наименьшее из значений $m$. Найдите значение выражения $k\cdot m_0$.`,
answer: '-16',
sol: R`Равенство приводится к $(m+1)^{2}-(n-3)^{2}=5$. Полагая $a=m+1$, $b=n-3$, имеем $(a-b)(a+b)=5$; целые решения дают пары $(m;n)$: $(2;5),(2;1),(-4;1),(-4;5)$. Значит $k=4$, $m_0=-4$, и $k\cdot m_0=-16$.` },
{ idx: 32, type: 'open', topic: 'stereometry', subtopic: 'ster-rotation', diff: 5,
text: R`$ABCDA_1B_1C_1D_1$ — куб, длина ребра которого равна $4\sqrt6$. Сфера проходит через его вершины $B$ и $D_1$ и середины рёбер $BB_1$ и $CC_1$. Найдите площадь сферы $S$ и в ответ запишите значение выражения $\dfrac{S}{\pi}$.`,
answer: '336',
sol: R`В координатах с ребром $a$ центр сферы — $\left(\tfrac{a}{4};\tfrac{a}{2};\tfrac{a}{4}\right)$, а $R^{2}=\tfrac{7a^{2}}{8}$. При $a=4\sqrt6$ ($a^{2}=96$) получаем $R^{2}=84$, $S=4\pi R^{2}=336\pi$ и $\dfrac{S}{\pi}=336$.` },
];
/* ── Сборка solution_html ────────────────────────────────────────────────── */
function ansShowOf(t) {
if (t.ansShow != null) return t.ansShow;
if (t.type === 'mc') return `${t.answer})`;
return `$${t.answer}$`;
}
function buildSolution(t) {
const ans = ansShowOf(t);
let html = `${t.sol}Ответ: ${ans}
`;
if (t.ref) html += `Учебник: ${t.ref}
`;
return html;
}
/* ── Самопроверка (повтор логики checkAnswerServer из exam-prep.js) ────────── */
const EPS = 1e-6;
function srvToNumber(s) {
if (s == null) return NaN;
let t = String(s).trim().replace(/\$/g, '').replace(/\s+/g, '').replace(',', '.');
const f = t.match(/^(-?\d+(?:\.\d+)?)\s*\/\s*(-?\d+(?:\.\d+)?)$/);
if (f) { const n = Number(f[1]), d = Number(f[2]); return d === 0 ? NaN : n / d; }
const n = Number(t); return Number.isFinite(n) ? n : NaN;
}
function checkAnswerServer(userInput, canonical) {
if (userInput == null || canonical == null) return false;
const c = String(canonical).trim();
if (/^[а-д]$/.test(c)) return String(userInput).trim().toLowerCase() === c.toLowerCase();
if (/^[^;]+;[^;]+$/.test(c)) return false;
const cn = srvToNumber(c), un = srvToNumber(userInput);
if (Number.isNaN(cn) || Number.isNaN(un)) return false;
return Math.abs(cn - un) < EPS;
}
/* ── Валидация набора ──────────────────────────────────────────────────────── */
const problems = [];
if (TASKS.length !== N_TASKS) problems.push(`Ожидалось ${N_TASKS} заданий, получено ${TASKS.length}`);
const seen = new Set();
for (const t of TASKS) {
if (seen.has(t.idx)) problems.push(`Дубль task_idx=${t.idx}`); seen.add(t.idx);
if (t.idx < 1 || t.idx > N_TASKS) problems.push(`task_idx вне 1..${N_TASKS}: ${t.idx}`);
if (!['mc', 'open', 'long'].includes(t.type)) problems.push(`#${t.idx}: тип ${t.type}`);
if (t.type === 'mc') {
if (!Array.isArray(t.opts) || t.opts.length !== 5) problems.push(`#${t.idx}: mc должен иметь 5 вариантов`);
if (!t.opts.some(o => o[0] === t.answer)) problems.push(`#${t.idx}: answer "${t.answer}" не среди меток`);
}
if (!t.text || !t.sol) problems.push(`#${t.idx}: пустой text/sol`);
if (t.type !== 'long' && !checkAnswerServer(t.answer, t.answer))
problems.push(`#${t.idx}: answer "${t.answer}" не проходит self-check (Unicode-минус? пробел?)`);
if (/−/.test(String(t.answer))) problems.push(`#${t.idx}: Unicode-минус в answer`);
}
/* ── Экспорт для тестов/тиража (без запуска main при require) ──────────────── */
module.exports = { TASKS, buildSolution, ansShowOf, checkAnswerServer, EXAM, VARIANT, PROV };
if (require.main !== module) return;
/* ── Открытие БД ───────────────────────────────────────────────────────────── */
const DB = path.join(__dirname, '..', 'data', 'learnspace.db');
const db = new DatabaseSync(DB);
const track = db.prepare(`SELECT exam_key, variants_count FROM exam_tracks WHERE exam_key=?`).get(EXAM);
if (!track) { console.error(`✗ Трек '${EXAM}' не найден в exam_tracks. Прерывание.`); process.exit(1); }
/* ── DRY-RUN сводка ────────────────────────────────────────────────────────── */
console.log(`\n=== seed_ctmath_ct2020_v1 (${PROV}) variant=${VARIANT} ===`);
console.log(`Режим: ${APPLY ? 'APPLY (запись)' : 'DRY-RUN (только проверка)'}\n`);
const byType = TASKS.reduce((a, t) => (a[t.type] = (a[t.type] || 0) + 1, a), {});
console.log('Типы:', JSON.stringify(byType), '\n');
console.log('idx | type | subtopic | d | answer');
console.log('----+------+-----------------------+---+----------');
for (const t of TASKS) {
console.log(`${String(t.idx).padStart(3)} | ${t.type.padEnd(4)} | ${String(t.subtopic).padEnd(21)} | ${t.diff} | ${String(t.answer)}`);
}
if (problems.length) {
console.error(`\n✗ ПРОБЛЕМЫ (${problems.length}):`);
problems.forEach(p => console.error(' - ' + p));
console.error('\nЗапись отменена из-за ошибок валидации.');
db.close();
process.exit(1);
}
console.log(`\n✓ Валидация и self-check ответов пройдены (${N_TASKS}/${N_TASKS}).`);
/* ── APPLY: upsert ─────────────────────────────────────────────────────────── */
if (!APPLY) {
console.log('\nDRY-RUN: ничего не записано. Для записи: node backend/scripts/seed_ctmath_ct2020_v1.js --apply\n');
db.close();
process.exit(0);
}
const upsert = db.prepare(`
INSERT INTO exam_tasks
(exam_key, variant, task_idx, task_type, text_html, figure_html,
opts_json, answer, solution_html, topic, subtopic, difficulty)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(exam_key, variant, task_idx) DO UPDATE SET
task_type = excluded.task_type,
text_html = excluded.text_html,
figure_html = excluded.figure_html,
opts_json = excluded.opts_json,
answer = excluded.answer,
solution_html = excluded.solution_html,
topic = excluded.topic,
subtopic = excluded.subtopic,
difficulty = excluded.difficulty
`);
let n = 0;
db.exec('BEGIN');
try {
for (const t of TASKS) {
upsert.run(
EXAM, VARIANT, t.idx, t.type,
t.text,
t.fig || null,
t.type === 'mc' ? JSON.stringify(t.opts) : null,
t.answer,
buildSolution(t),
t.topic, t.subtopic, t.diff
);
n++;
}
const distinct = db.prepare(`SELECT COUNT(DISTINCT variant) c FROM exam_tasks WHERE exam_key=? AND variant BETWEEN 101 AND 1999`).get(EXAM).c;
db.prepare(`UPDATE exam_tracks SET variants_count=? WHERE exam_key=?`).run(distinct, EXAM);
db.exec('COMMIT');
console.log(`\n✓ Записано/обновлено ${n} заданий (variant=${VARIANT}).`);
console.log(`✓ exam_tracks.variants_count = ${distinct} (различных вариантов).`);
console.log(`\nПробник доступен: /exam-prep/ctmath → «Варианты» → «ЦТ-2020».\n`);
} catch (e) {
db.exec('ROLLBACK');
console.error('\n✗ Ошибка записи, откат транзакции:', e.message);
process.exitCode = 1;
}
db.close();