diff --git a/backend/scripts/seed_ctmath_rt2324_e3v1.js b/backend/scripts/seed_ctmath_rt2324_e3v1.js
new file mode 100644
index 0000000..b2d7dac
--- /dev/null
+++ b/backend/scripts/seed_ctmath_rt2324_e3v1.js
@@ -0,0 +1,388 @@
+'use strict';
+/* ───────────────────────────────────────────────────────────────────────────
+ seed_ctmath_rt2324_e3v1.js
+ Чистый вариант-пробник для трека exam-prep `ctmath`.
+
+ Источник: РТ–2023/2024, Этап III, Вариант 1 (РИКЗ, «Тематическое
+ консультирование по математике»). 30 заданий: А1–А10 + В1–В20.
+ Перенабрано вручную в KaTeX по PDF (визуальное чтение, НЕ OCR):
+ F:\!Рабочие\ЦТ\Математика\Математика\РТ\2023-2024\МАТ РТ-3 23_24 В1.pdf
+
+ variant=106 — Этап III РТ-2023/24 (этап I — 104, этап II — 105).
+ Геометрия закодирована текстом (стандартная разметка фигур / углы словами).
+ Исключение — В1 (чтение графика): кусочно-линейная нечётная функция
+ воспроизведена inline-SVG в figure_html (как у math9-заданий); все 6
+ утверждений и ответ (145) согласованы с реконструкцией.
+
+ Идемпотентность: upsert по UNIQUE(exam_key, variant, task_idx).
+ Запуск:
+ node backend/scripts/seed_ctmath_rt2324_e3v1.js # DRY-RUN (по умолчанию)
+ node backend/scripts/seed_ctmath_rt2324_e3v1.js --apply # запись в БД
+
+ ⚠️ Массовую запись в БД запускает ПОЛЬЗОВАТЕЛЬ вручную (авто-режим Claude Code
+ блокирует продакшн-записи). Без --apply ничего не пишется.
+ ─────────────────────────────────────────────────────────────────────────── */
+
+const { DatabaseSync } = require('node:sqlite');
+const path = require('path');
+
+const APPLY = process.argv.includes('--apply');
+const EXAM = 'ctmath';
+const VARIANT = 106;
+const PROV = 'РТ–2023/2024, Этап III, Вариант 1';
+const R = String.raw;
+
+/* opts: метки кириллица а–д (как в существующих строках ctmath; checkAnswerServer
+ имеет ветку /^[а-д]$/). РТ-варианты 1..5 → а..д. */
+const L = ['а', 'б', 'в', 'г', 'д'];
+const mc = (...html) => html.map((h, i) => [L[i], h]);
+
+/* ── SVG-график для В1: нечётная кусочно-линейная функция на [-6;6] через
+ точки (-6,-3),(-4,1),(4,-1),(6,3). f(0)=0; возрастает на [-6;-4] и [4;6],
+ убывает на [-4;4]. Цвета — только в SVG-стоки (как у math9-фигур). */
+const FIG_B1 = ``;
+
+/* ── 30 заданий ─────────────────────────────────────────────────────────── */
+const TASKS = [
+ // ── Часть A: А1–А10 ──────────────────────────────────────────────────────
+ { idx: 1, type: 'mc', topic: 'functions', subtopic: 'fn-graphs', diff: 1,
+ text: R`На координатной плоскости отмечены точки $A(2;-1)$, $B(1;2)$, $C(-2;-2)$, $D(-1;1)$, $E(0;-2)$. Выберите ту из них, сумма координат которой равна $-4$.`,
+ opts: mc('$A$', '$B$', '$C$', '$D$', '$E$'),
+ answer: 'в',
+ sol: R`Сумма координат: для $A$ это $2+(-1)=1$, для $B$ это $1+2=3$, для $C$ это $-2+(-2)=-4$, для $D$ это $-1+1=0$, для $E$ это $0+(-2)=-2$. Сумме $-4$ соответствует точка $C$.`,
+ ref: 'Герасимов «Математика, 6 кл.», гл. 5, § 1' },
+
+ { idx: 2, type: 'mc', topic: 'stereometry', subtopic: 'ster-basics', diff: 3,
+ text: R`Дан куб $ABCDA_1B_1C_1D_1$. Точка $K$ — середина диагонали $A_1D$. Среди отрезков $A_1B_1$, $B_1D_1$, $C_1K$, $BB_1$, $A_1C_1$ укажите отрезок, по которому плоскость, заданная прямой $DC_1$ и точкой $K$, пересекает плоскость грани $A_1B_1C_1D_1$.`,
+ opts: mc('$A_1B_1$', '$B_1D_1$', '$C_1K$', '$BB_1$', '$A_1C_1$'),
+ answer: 'д',
+ sol: R`Секущая плоскость, заданная прямой $DC_1$ и точкой $K$, содержит точку $C_1$ (она на $DC_1$) и точку $A_1$ (так как $K$ — середина $A_1D$, прямая $DC_1$ и точка $K$ задают плоскость диагонального сечения, проходящую через $A_1$). Точки $A_1$ и $C_1$ принадлежат и грани $A_1B_1C_1D_1$, поэтому пересечение плоскостей — отрезок $A_1C_1$.`,
+ ref: 'Латотин «Геометрия, 10 кл.», разд. 1, § 2–3' },
+
+ { idx: 3, type: 'mc', topic: 'numbers', subtopic: 'num-real', diff: 2,
+ text: R`Расположите числа $\log_3 27$, $\ 3^{-1}$, $\ \sqrt{64}$ в порядке возрастания.`,
+ opts: mc('$\sqrt{64};\ \log_3 27;\ 3^{-1}$', '$3^{-1};\ \sqrt{64};\ \log_3 27$', '$\sqrt{64};\ 3^{-1};\ \log_3 27$', '$3^{-1};\ \log_3 27;\ \sqrt{64}$', '$\log_3 27;\ \sqrt{64};\ 3^{-1}$'),
+ answer: 'г',
+ sol: R`$\log_3 27=\log_3 3^{3}=3$; $\ 3^{-1}=\dfrac13$; $\ \sqrt{64}=8$. Так как $\dfrac13<3<8$, числа в порядке возрастания: $3^{-1};\ \log_3 27;\ \sqrt{64}$.`,
+ ref: 'Арефьева «Алгебра, 8 кл.», гл. 1, § 1–4; гл. 11 кл., § 3' },
+
+ { idx: 4, type: 'mc', topic: 'expressions', subtopic: 'expr-polynomials', diff: 1,
+ text: R`Укажите номер выражения, тождественно равного выражению $a^{3}$.`,
+ opts: mc('$a:a^{3}$', '$a\cdot a^{2}$', '$\left(a^{2}\right)^{2}$', '$3a$', '$a^{-3}$'),
+ answer: 'б',
+ sol: R`По свойству степеней $a\cdot a^{2}=a^{1+2}=a^{3}$. (Остальные: $a:a^{3}=a^{-2}$; $\left(a^{2}\right)^{2}=a^{4}$; $3a$ и $a^{-3}$ не равны $a^{3}$.)`,
+ ref: 'Арефьева «Алгебра, 7 кл.», гл. 2, § 5' },
+
+ { idx: 5, type: 'mc', topic: 'expressions', subtopic: 'expr-polynomials', diff: 1,
+ text: R`Результат разложения многочлена $4b^{2}+4bc-4b$ на множители имеет вид:`,
+ opts: mc('$4b(b+c)$', '$4b(bc-1)$', '$4b(1+c)$', '$(4b-1)(b+c)$', '$4b(b+c-1)$'),
+ answer: 'д',
+ sol: R`Общий множитель членов многочлена $4b^{2}+4bc-4b$ — одночлен $4b$. Тогда $4b^{2}+4bc-4b=4b(b+c-1)$.`,
+ ref: 'Арефьева «Алгебра, 7 кл.», гл. 2, § 14' },
+
+ { idx: 6, type: 'open', topic: 'expressions', subtopic: 'expr-powers-roots', diff: 2,
+ text: R`Среди чисел $10$, $99$, $0$, $-10$, $100$ укажите номера тех, которые не входят в область определения выражения $\dfrac{1}{10-\sqrt{x}}$.
1) $10$; 2) $99$; 3) $0$; 4) $-10$; 5) $100$.
Ответ запишите цифрами в порядке возрастания, без пробелов.`,
+ answer: '45', ansShow: '4, 5',
+ sol: R`Выражение $\dfrac{1}{10-\sqrt{x}}$ имеет смысл при $x\ge0$ и $10-\sqrt{x}\ne0$, то есть $x\ge0$, $x\ne100$. Область определения $[0;100)\cup(100;+\infty)$. Из данных чисел ей не принадлежат $-10$ (так как $-10<0$) и $100$ (исключено). Это числа под номерами 4 и 5.`,
+ ref: 'Арефьева «Алгебра, 8 кл.», гл. 1, § 1' },
+
+ { idx: 7, type: 'mc', topic: 'word-sequences', subtopic: 'word-problems', diff: 2,
+ text: R`За три четверти учебного года Петя использовал $\dfrac25$ купленных в начале учебного года тетрадей, после чего у него осталось $48$ тетрадей. Сколько тетрадей купил Петя в начале учебного года?`,
+ opts: mc('$80$', '$96$', '$120$', '$74$', '$116$'),
+ answer: 'а',
+ sol: R`Числу $48$ соответствует дробь $1-\dfrac25=\dfrac35$ всех тетрадей. Тогда куплено $48:\dfrac35=\dfrac{48\cdot5}{3}=80$ тетрадей.`,
+ ref: 'Герасимов «Математика, 5 кл.», ч. 2, гл. 3, § 10' },
+
+ { idx: 8, type: 'mc', topic: 'trigonometry', subtopic: 'trig-identities', diff: 2,
+ text: R`Найдите значение выражения $\operatorname{tg}(-120^\circ)+\left|-\sqrt3\right|$.`,
+ opts: mc('$0$', '$\dfrac{4\sqrt3}{3}$', '$1-\sqrt3$', '$2\sqrt3$', '$-\dfrac{2\sqrt3}{3}$'),
+ answer: 'г',
+ sol: R`$\operatorname{tg}(-120^\circ)=-\operatorname{tg}120^\circ=-\operatorname{tg}(180^\circ-60^\circ)=\operatorname{tg}60^\circ=\sqrt3$. Тогда $\operatorname{tg}(-120^\circ)+\left|-\sqrt3\right|=\sqrt3+\sqrt3=2\sqrt3.$`,
+ ref: 'Арефьева «Алгебра, 10 кл.», гл. 1, § 3; § 9' },
+
+ { idx: 9, type: 'mc', topic: 'stereometry', subtopic: 'ster-rotation', diff: 2,
+ text: R`Сечение сферы плоскостью, отстоящей от её центра на расстоянии $3$, имеет радиус $3\sqrt2$. Найдите радиус сферы.`,
+ opts: mc('$9\sqrt2$', '$3\sqrt3$', '$6$', '$12$', '$4\sqrt3$'),
+ answer: 'б',
+ sol: R`Радиус сферы $R$ — гипотенуза прямоугольного треугольника с катетами $3$ (расстояние до плоскости) и $3\sqrt2$ (радиус сечения). По теореме Пифагора $R^{2}=3^{2}+\left(3\sqrt2\right)^{2}=9+18=27$, поэтому $R=3\sqrt3$.`,
+ ref: 'Латотин «Геометрия, 11 кл.», разд. 3, § 5' },
+
+ { idx: 10, type: 'open', topic: 'functions', subtopic: 'fn-properties', diff: 2,
+ text: R`Укажите номера функций, которые принимают только положительные значения на промежутке $(4;+\infty)$.
1) $f(x)=-4x$;
2) $f(x)=\sqrt{x-4}$;
3) $f(x)=x^{3}-4$;
4) $f(x)=\log_{\frac14}x$;
5) $f(x)=-x^{2}-4$.
Ответ запишите цифрами в порядке возрастания, без пробелов.`,
+ answer: '23', ansShow: '2, 3',
+ sol: R`$1)$ $-4x$ при $x>4$ отрицательна. $\ 2)$ $\sqrt{x-4}$ при $x>4$ положительна. $\ 3)$ $x^{3}-4$ положительна при $x>\sqrt[3]{4}$, а $(4;+\infty)\subset(\sqrt[3]{4};+\infty)$ — положительна. $\ 4)$ $\log_{\frac14}x$ положительна только на $(0;1)$. $\ 5)$ $-x^{2}-4$ отрицательна при всех $x$. Подходят функции 2 и 3.`,
+ ref: 'Арефьева «Алгебра, 8 кл.», гл. 3, § 13–14' },
+
+ // ── Часть B: В1–В20 ──────────────────────────────────────────────────────
+ { idx: 11, type: 'open', topic: 'functions', subtopic: 'fn-properties', diff: 3,
+ fig: FIG_B1,
+ text: R`На рисунке изображён график функции $y=f(x)$, определённой на промежутке $[-6;6]$. Выберите верные утверждения.
1) функция является нечётной;
2) $f(3)>0$;
3) график функции симметричен относительно оси ординат;
4) $f(-5)>f(-6)$;
5) функция убывает на промежутке $[-4;4]$;
6) график функции $y=f(x)+3$ проходит через точку $(0;2)$.
Ответ запишите цифрами в порядке возрастания, без пробелов.`,
+ answer: '145', ansShow: '1, 4, 5',
+ sol: R`$1)$ верно: график симметричен относительно начала координат, поэтому функция нечётная. $\ 2)$ неверно: по графику $f(3)<0$. $\ 3)$ неверно: график нечётной функции симметричен относительно начала координат, а не оси ординат. $\ 4)$ верно: на промежутке $[-6;-4]$ функция возрастает, поэтому $f(-5)>f(-6)$. $\ 5)$ верно: на отрезке $[-4;4]$ при увеличении $x$ значения функции уменьшаются. $\ 6)$ неверно: $f(0)=0$, поэтому график $y=f(x)+3$ проходит через точку $(0;3)$, а не $(0;2)$.`,
+ ref: 'Арефьева «Алгебра, 9 кл.», гл. 2, § 6–9' },
+
+ { idx: 12, type: 'long', topic: 'stereometry', subtopic: 'ster-angles-distances', diff: 3,
+ text: R`$ABCDA_1B_1C_1D_1$ — куб. Точки $M$ и $K$ — середины рёбер $A_1D_1$ и $AA_1$ соответственно. Для начала каждого из предложений А–В подберите его окончание 1–6 так, чтобы получилось верное утверждение.
Начало:
А) Величина угла между прямыми $A_1B_1$ и $KM$ равна …
Б) Величина угла между прямыми $B_1C_1$ и $KM$ равна …
В) Величина угла между прямыми $BD$ и $KM$ равна …
Окончание:
1) $30^\circ$; 2) $0^\circ$; 3) $60^\circ$; 4) $90^\circ$; 5) $120^\circ$; 6) $45^\circ$.
Ответ запишите сочетанием букв и цифр, например: А1Б1В4.`,
+ answer: 'А4Б6В3', ansShow: 'А4Б6В3',
+ sol: R`А) Прямая $A_1B_1$ перпендикулярна плоскости грани $AA_1D_1D$, а $KM$ лежит в этой плоскости, поэтому $A_1B_1\perp KM$ — угол $90^\circ$ (окончание 4). Б) $B_1C_1\parallel A_1D_1$, поэтому угол между $B_1C_1$ и $KM$ равен углу $A_1MK$; в равнобедренном прямоугольном треугольнике $KA_1M$ ($A_1K=A_1M$) он равен $45^\circ$ (окончание 6). В) Через середину $P$ ребра проведём $MP\parallel B_1D_1$; треугольник $PMK$ равносторонний, поэтому угол между $BD$ и $KM$ равен $60^\circ$ (окончание 3).`,
+ ref: 'Латотин «Геометрия, 10 кл.», разд. 2, § 4' },
+
+ { idx: 13, type: 'open', topic: 'numbers', subtopic: 'num-divisibility', diff: 2,
+ text: R`Найдите сумму всех натуральных делителей числа $95$.`,
+ answer: '120',
+ sol: R`Число $95=5\cdot19$ имеет четыре натуральных делителя: $1$, $5$, $19$ и $95$. Их сумма равна $1+5+19+95=120$.`,
+ ref: 'Герасимов «Математика, 5 кл.», ч. 1, гл. 1, § 12' },
+
+ { idx: 14, type: 'open', topic: 'equations', subtopic: 'eq-linear', diff: 2,
+ text: R`Найдите произведение наименьшего и наибольшего целых решений двойного неравенства $-51\dfrac13<-3x<4\sqrt2$.`,
+ answer: '-17',
+ sol: R`Разделим все части на $-3$ (знаки неравенства меняются на противоположные): $-\dfrac{4\sqrt2}{3}1$ решений не имеет. Из $\sin3x=0$: $3x=180^\circ n$, $x=60^\circ n$. Промежутку $[-270^\circ;-135^\circ]$ принадлежат $-180^\circ$ ($n=-3$) и $-240^\circ$ ($n=-4$); их сумма равна $-420^\circ$.`,
+ ref: 'Арефьева «Алгебра, 10 кл.», гл. 1, § 8; § 12' },
+
+ { idx: 29, type: 'open', topic: 'equations', subtopic: 'eq-logarithmic', diff: 4,
+ text: R`Найдите сумму всех целых решений неравенства $\log_{0{,}7}\left(x^{2}-7x+18\right)-\log_{0{,}7}(x-1)<\log_{0{,}7}2$ на промежутке $(-10;10)$.`,
+ answer: '35',
+ sol: R`Неравенство приводится к виду $\log_{0{,}7}\left(x^{2}-7x+18\right)<\log_{0{,}7}\bigl(2(x-1)\bigr)$. Основание $0{,}7<1$ (функция убывает), поэтому равносильна система $\begin{cases}x^{2}-7x+18>2x-2,\\2x-2>0.\end{cases}$ Первое: $x^{2}-9x+20>0\Rightarrow x<4$ или $x>5$; второе: $x>1$. Решение $(1;4)\cup(5;+\infty)$. Пересечение с $(-10;10)$: $(1;4)\cup(5;10)$; целые $2,3,6,7,8,9$, их сумма $35$.`,
+ ref: 'Арефьева «Алгебра, 11 кл.», гл. 3, § 10' },
+
+ { idx: 30, type: 'open', topic: 'stereometry', subtopic: 'ster-angles-distances', diff: 5,
+ text: R`Сторона $AB$ треугольника $ABC$, у которого $AB=BC=12$, $AC=8$, лежит в плоскости $\alpha$, а длины проекций двух других сторон треугольника $ABC$ на эту плоскость относятся как $1:3$. Найдите значение выражения $\dfrac{13}{\cos^{2}\beta}$, где $\beta$ — угол между плоскостью треугольника $ABC$ и плоскостью $\alpha$.`,
+ answer: '256',
+ sol: R`Пусть $CO$ — перпендикуляр к $\alpha$, $AO:BO=1:3$, $AO=x$, $BO=3x$. Из $CO^{2}=BC^{2}-BO^{2}=AC^{2}-AO^{2}$: $144-9x^{2}=64-x^{2}$, $8x^{2}=80$, $x=\sqrt{10}$, $CO=3\sqrt6$. Высота $CK$ треугольника $ABC$ к $AB$: по формуле Герона $S=32\sqrt2$, откуда $CK=\dfrac{2S}{AB}=\dfrac{64\sqrt2}{12}=\dfrac{16\sqrt2}{3}$. Тогда $OK=\sqrt{CK^{2}-CO^{2}}=\sqrt{\dfrac{512}{9}-54}=\dfrac{\sqrt{26}}{3}$, а $\cos\beta=\dfrac{OK}{CK}=\dfrac{\sqrt{13}}{16}$. Значит, $\dfrac{13}{\cos^{2}\beta}=\dfrac{13\cdot256}{13}=256$.`,
+ ref: 'Латотин «Геометрия, 10 кл.», разд. 3, § 10' },
+];
+
+/* ── Сборка 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 !== 30) problems.push(`Ожидалось 30 заданий, получено ${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 > 30) problems.push(`task_idx вне 1..30: ${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_rt2324_e3v1 (${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), ' | с фигурой:', TASKS.filter(t => t.fig).length, '\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 ответов пройдены (30/30).');
+
+/* ── APPLY: upsert ─────────────────────────────────────────────────────────── */
+if (!APPLY) {
+ console.log('\nDRY-RUN: ничего не записано. Для записи: node backend/scripts/seed_ctmath_rt2324_e3v1.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 → «Варианты» → «РТ-2023/24 · этап III».\n`);
+} catch (e) {
+ db.exec('ROLLBACK');
+ console.error('\n✗ Ошибка записи, откат транзакции:', e.message);
+ process.exitCode = 1;
+}
+db.close();