From f4d20ff10f6fb414c4395f2669fa5ad770dd1104 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Fri, 19 Jun 2026 12:32:53 +0300 Subject: [PATCH] =?UTF-8?q?feat(ctmath):=20=D0=BF=D1=80=D0=BE=D0=B1=D0=BD?= =?UTF-8?q?=D0=B8=D0=BA=20=D0=A6=D0=A2-2014=20=D0=92=D0=B0=D1=80=D0=B8?= =?UTF-8?q?=D0=B0=D0=BD=D1=82=201=20(=D0=B2=D0=B0=D1=80=D0=B8=D0=B0=D0=BD?= =?UTF-8?q?=D1=82=20110)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Первый из ЦТ-годовых. Формат ЦТ: А1–А18 (18 mc) + В1–В12 (12 open) = 30. Перенабор по PDF; решений в источнике НЕТ — решено вручную, ответы сверены с официальным ключом (стр.34 сборника). Адаптации картинок: А2 (симметричные фигуры, неразборчивы) → MC о симметрии точки; А6 (параллелограмм на сетке) → координаты вершин; А10/А15 — текстом. Метка 110 (ЦТ-2014) в VARIANT_LABEL. Идемпотентный seed, --apply — пользователь. Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/scripts/seed_ctmath_ct2014_v1.js | 380 +++++++++++++++++++++++ backend/src/routes/exam-prep.js | 1 + 2 files changed, 381 insertions(+) create mode 100644 backend/scripts/seed_ctmath_ct2014_v1.js diff --git a/backend/scripts/seed_ctmath_ct2014_v1.js b/backend/scripts/seed_ctmath_ct2014_v1.js new file mode 100644 index 0000000..e5f7508 --- /dev/null +++ b/backend/scripts/seed_ctmath_ct2014_v1.js @@ -0,0 +1,380 @@ +'use strict'; +/* ─────────────────────────────────────────────────────────────────────────── + seed_ctmath_ct2014_v1.js + Чистый вариант-пробник для трека exam-prep `ctmath`. + + Источник: Централизованное тестирование (ЦТ) по математике, 2014, Вариант 1. + Формат ЦТ тех лет: Часть А = А1–А18 (закрытые, 5 вариантов), Часть В = В1–В12 + (открытые). Всего 30 заданий. Перенабрано вручную в KaTeX по PDF: + F:\!Рабочие\ЦТ\Математика\Математика\ЦТ-ЦЭ\ЦТ 2014.pdf + + ⚠️ В PDF РЕШЕНИЙ НЕТ (только задания). Решения и ответы получены вручную и + СВЕРЕНЫ с официальной таблицей ответов в конце сборника (стр. 34, столбец + «Вариант 1»). variant=110 (после РТ-вариантов 101–109). + + Адаптации заданий-«с-картинкой» (исходный ответ/идея сохранены, авто-проверка): + • А2 (выбор рисунка с симметричными фигурами — неразборчиво в скане) → + эквивалентный MC о симметрии точки относительно оси; + • А6 (параллелограмм на координатной сетке) → координаты вершин заданы + в тексте (та же длина диагонали $AC=4\sqrt2$); + • А10 (касательные/секущая) и А15 (таблица поставщиков) — закодированы + текстом (данные с рисунка/таблицы перенесены в условие). + + Идемпотентность: upsert по UNIQUE(exam_key, variant, task_idx). + Запуск: + node backend/scripts/seed_ctmath_ct2014_v1.js # DRY-RUN (по умолчанию) + node backend/scripts/seed_ctmath_ct2014_v1.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 = 110; +const PROV = 'ЦТ–2014, Вариант 1'; +const R = String.raw; + +const L = ['а', 'б', 'в', 'г', 'д']; +const mc = (...html) => html.map((h, i) => [L[i], h]); + +/* ── 30 заданий ─────────────────────────────────────────────────────────── */ +const TASKS = [ + // ── Часть A: А1–А18 ────────────────────────────────────────────────────── + { idx: 1, type: 'mc', topic: 'numbers', subtopic: 'num-real', diff: 1, + text: R`Даны дроби: $1\frac67$, $\ 1\frac17$, $\ 6\frac67$, $\ 7\frac17$, $\ 6\frac17$. Укажите дробь, которая равна дроби $\dfrac{43}{7}$.`, + opts: mc('$1\frac67$', '$1\frac17$', '$6\frac67$', '$7\frac17$', '$6\frac17$'), + answer: 'д', + sol: R`$\dfrac{43}{7}=6\frac17$, так как $43=6\cdot7+1$.`, + ref: 'Герасимов «Математика, 5 кл.», ч. 1, гл. 2' }, + + { idx: 2, type: 'mc', topic: 'functions', subtopic: 'fn-graphs', diff: 1, + text: R`Точка $M(-3;4)$ симметрична точке $N$ относительно оси абсцисс. Укажите координаты точки $N$.`, + opts: mc('$(3;4)$', '$(-3;-4)$', '$(3;-4)$', '$(-4;-3)$', '$(-3;4)$'), + answer: 'б', + sol: R`При симметрии относительно оси абсцисс абсцисса точки сохраняется, а ордината меняет знак: $N(-3;-4)$.`, + ref: 'Латотин «Математика, 6 кл.», гл. 5' }, + + { idx: 3, type: 'mc', topic: 'planimetry', subtopic: 'plan-triangles', diff: 1, + text: R`Прямые $a$ и $b$ пересекаются, образуя четыре угла. Известно, что сумма трёх из этих углов равна $210^\circ$. Найдите градусную меру меньшего угла.`, + opts: mc('$150^\circ$', '$15^\circ$', '$30^\circ$', '$10^\circ$', '$105^\circ$'), + answer: 'в', + sol: R`При пересечении двух прямых вертикальные углы равны, а смежные дают $180^\circ$. Сумма трёх углов равна $180^\circ+x$, где $x$ — один из углов; $180^\circ+x=210^\circ$, $x=30^\circ$ — меньший угол.`, + ref: 'Казаков «Геометрия, 7 кл.», гл. 2' }, + + { idx: 4, type: 'mc', topic: 'expressions', subtopic: 'expr-polynomials', diff: 1, + text: R`Результат разложения многочлена $x(6a-b)+b-6a$ на множители имеет вид:`, + opts: mc('$x$', '$x+1$', '$(6a-b)(x+1)$', '$(6a-b)(x+b)$', '$(6a-b)(x-1)$'), + answer: 'д', + sol: R`$x(6a-b)+b-6a=x(6a-b)-(6a-b)=(6a-b)(x-1)$.`, + ref: 'Арефьева «Алгебра, 7 кл.», гл. 2, § 14' }, + + { idx: 5, type: 'mc', topic: 'numbers', subtopic: 'num-real', diff: 1, + text: R`Вычислите $\dfrac{7{,}3^{2}-2{,}4^{2}+9{,}7\cdot1{,}1}{6}$.`, + opts: mc('$\dfrac97$', '$\dfrac32$', '$9$', '$9{,}7$', '$3{,}41$'), + answer: 'г', + sol: R`$\dfrac{53{,}29-5{,}76+10{,}67}{6}=\dfrac{58{,}2}{6}=9{,}7$.`, + ref: 'Герасимов «Математика, 6 кл.», гл. 4' }, + + { idx: 6, type: 'mc', topic: 'functions', subtopic: 'fn-graphs', diff: 2, + text: R`Вершины параллелограмма $ABCD$ имеют координаты $A(-2;-1)$, $B(-3;2)$, $C(2;3)$, $D(3;0)$. Найдите длину диагонали $AC$.`, + opts: mc('$4$', '$5$', '$4\sqrt2$', '$5\sqrt2$', '$9\sqrt2$'), + answer: 'в', + sol: R`$AC=\sqrt{(2-(-2))^{2}+(3-(-1))^{2}}=\sqrt{16+16}=4\sqrt2$.`, + ref: 'Латотин «Математика, 6 кл.», гл. 7' }, + + { idx: 7, type: 'mc', topic: 'planimetry', subtopic: 'plan-triangles', diff: 2, + text: R`Длины катетов прямоугольного треугольника являются корнями уравнения $x^{2}-9x+12=0$. Найдите площадь треугольника.`, + opts: mc('$6$', '$9$', '$10{,}5$', '$12$', '$4{,}5$'), + answer: 'а', + sol: R`По теореме Виета произведение корней (катетов) равно $12$. Площадь прямоугольного треугольника $=\dfrac12\cdot12=6$.`, + ref: 'Арефьева «Алгебра, 8 кл.», гл. 2; Казаков «Геометрия, 8 кл.», гл. 2' }, + + { idx: 8, type: 'mc', topic: 'numbers', subtopic: 'num-real', diff: 1, + text: R`Пусть $a=5{,}4$, $b=3{,}2\cdot10^{1}$. Найдите произведение $ab$ и запишите его в стандартном виде.`, + opts: mc('$0{,}1728\cdot10^{3}$', '$1728\cdot10^{-1}$', '$1{,}728\cdot10^{2}$', '$1{,}728$', '$172{,}8$'), + answer: 'в', + sol: R`$ab=5{,}4\cdot32=172{,}8=1{,}728\cdot10^{2}$.`, + ref: 'Арефьева «Алгебра, 7 кл.», гл. 1, § 3' }, + + { idx: 9, type: 'mc', topic: 'expressions', subtopic: 'expr-fractions', diff: 2, + text: R`Выразите $x$ из равенства $\dfrac{2+y}{5}=\dfrac{x-y}{15}$.`, + opts: mc('$x=4y-6$', '$x=4y+6$', '$x=20y+30$', '$x=20y-30$', '$x=2y+2$'), + answer: 'б', + sol: R`$15(2+y)=5(x-y)$, $3(2+y)=x-y$, $6+3y=x-y$, $x=4y+6$.`, + ref: 'Арефьева «Алгебра, 7 кл.», гл. 2' }, + + { idx: 10, type: 'mc', topic: 'planimetry', subtopic: 'plan-circle', diff: 2, + text: R`Из точки $A$ к окружности проведены касательные $AB$ и $AC$ и секущая $AM$, проходящая через центр окружности $O$ (точки $B$, $C$, $M$ лежат на окружности). Отрезок $BK$ перпендикулярен $AM$ ($K$ на $AM$), $BK=4$, $AC=9$. Найдите длину отрезка $AK$.`, + opts: mc('$4$', '$\sqrt{97}$', '$65$', '$5$', '$\sqrt{65}$'), + answer: 'д', + sol: R`Касательные, проведённые из одной точки, равны: $AB=AC=9$. В прямоугольном треугольнике $ABK$ ($\angle K=90^\circ$): $AK=\sqrt{AB^{2}-BK^{2}}=\sqrt{81-16}=\sqrt{65}$.`, + ref: 'Казаков «Геометрия, 8 кл.», гл. 4' }, + + { idx: 11, type: 'mc', topic: 'equations', subtopic: 'eq-rational', diff: 2, + text: R`Даны два числа. Известно, что одно из них меньше другого на $6$. Какому условию удовлетворяет меньшее число $x$, если его удвоенный квадрат не больше суммы квадратов этих чисел?`, + opts: mc('$x\le3$', '$x\le-3$', '$x\ge-3$', '$x\ge3$', '$x\le12$'), + answer: 'в', + sol: R`Числа $x$ и $x+6$. Условие $2x^{2}\le x^{2}+(x+6)^{2}$, то есть $2x^{2}\le2x^{2}+12x+36$, $0\le12x+36$, $x\ge-3$.`, + ref: 'Арефьева «Алгебра, 8 кл.», гл. 3' }, + + { idx: 12, type: 'mc', topic: 'expressions', subtopic: 'expr-fractions', diff: 2, + text: R`Свежие фрукты при сушке теряют $a\%$ своей массы. Укажите выражение, определяющее массу сухих фруктов (в килограммах), полученных из $20$ кг свежих.`, + opts: mc('$\dfrac{2000}{a}$', '$\dfrac{20(100-a)}{100}$', '$\dfrac{2000}{100-a}$', '$\dfrac{20(100+a)}{100}$', '$\dfrac{2000}{100+a}$'), + answer: 'б', + sol: R`Сухие фрукты составляют $(100-a)\%$ массы свежих: $20\cdot\dfrac{100-a}{100}=\dfrac{20(100-a)}{100}$.`, + ref: 'Герасимов «Математика, 6 кл.», гл. 2' }, + + { idx: 13, type: 'mc', topic: 'stereometry', subtopic: 'ster-rotation', diff: 2, + text: R`Объём конуса равен $5$, а его высота равна $\dfrac12$. Найдите площадь основания конуса.`, + opts: mc('$\dfrac56$', '$\dfrac{10}{3}$', '$10$', '$30$', '$\dfrac{15}{2}$'), + answer: 'г', + sol: R`$V=\dfrac13 S h$, поэтому $5=\dfrac13 S\cdot\dfrac12=\dfrac{S}{6}$, откуда $S=30$.`, + ref: 'Латотин «Геометрия, 11 кл.», разд. 2' }, + + { idx: 14, type: 'mc', topic: 'functions', subtopic: 'fn-properties', diff: 2, + text: R`Известно, что наименьшее значение функции, заданной формулой $y=x^{2}+8x+c$, равно $-3$. Тогда значение $c$ равно:`, + opts: mc('$13$', '$16$', '$-51$', '$-19$', '$19$'), + answer: 'а', + sol: R`Наименьшее значение квадратичной функции равно $c-\dfrac{8^{2}}{4}=c-16$. Из $c-16=-3$ получаем $c=13$.`, + ref: 'Арефьева «Алгебра, 8 кл.», гл. 3' }, + + { idx: 15, type: 'mc', topic: 'word-sequences', subtopic: 'word-problems', diff: 2, + text: R`Строительная бригада планирует заказать фундаментные блоки у одного из трёх поставщиков. Поставщик 1: стоимость блока $335$ тыс. руб., доставка $1850$ тыс. руб.; поставщик 2: блок $365$, доставка $970$; поставщик 3: блок $420$, доставка бесплатно. При покупке какого количества блоков самыми выгодными будут условия второго поставщика?`, + opts: mc('от $18$ до $29$', 'более $17$', 'от $30$ до $55$', 'менее $30$', 'от $17$ до $30$'), + answer: 'а', + sol: R`Стоимость заказа $n$ блоков: $P_1=335n+1850$, $P_2=365n+970$, $P_3=420n$. Условие $P_2970$, $n\ge18$. Значит, второй поставщик выгоднее при $18\le n\le29$.`, + ref: 'Арефьева «Алгебра, 7 кл.», гл. 3' }, + + { idx: 16, type: 'mc', topic: 'expressions', subtopic: 'expr-powers-roots', diff: 2, + text: R`Расположите числа $8^{10}$, $3^{18}$, $31^{6}$ в порядке возрастания.`, + opts: mc('$3^{18};\ 8^{10};\ 31^{6}$', '$8^{10};\ 3^{18};\ 31^{6}$', '$31^{6};\ 3^{18};\ 8^{10}$', '$3^{18};\ 31^{6};\ 8^{10}$', '$31^{6};\ 8^{10};\ 3^{18}$'), + answer: 'г', + sol: R`$8^{10}=2^{30}\approx1{,}07\cdot10^{9}$; $\ 3^{18}=9^{9}\approx3{,}87\cdot10^{8}$; $\ 31^{6}\approx8{,}9\cdot10^{8}$. Порядок возрастания: $3^{18};\ 31^{6};\ 8^{10}$.`, + ref: 'Арефьева «Алгебра, 10 кл.», гл. 1' }, + + { idx: 17, type: 'mc', topic: 'stereometry', subtopic: 'ster-angles-distances', diff: 3, + text: R`Через вершину $A$ прямоугольного треугольника $ABC$ ($\angle C=90^\circ$) проведён перпендикуляр $AK$ к его плоскости. Найдите расстояние от точки $K$ до прямой $BC$, если $AK=2$, $AB=4$, $BC=\sqrt{11}$.`, + opts: mc('$3$', '$2\sqrt5$', '$\sqrt5$', '$\sqrt{15}$', '$6$'), + answer: 'а', + sol: R`$AC=\sqrt{AB^{2}-BC^{2}}=\sqrt{16-11}=\sqrt5$. Так как $BC\perp AC$ и $BC\perp AK$, то $BC$ перпендикулярна плоскости $ACK$, поэтому расстояние от $K$ до $BC$ равно $CK=\sqrt{AK^{2}+AC^{2}}=\sqrt{4+5}=3$.`, + ref: 'Латотин «Геометрия, 10 кл.», разд. 3' }, + + { idx: 18, type: 'mc', topic: 'equations', subtopic: 'eq-irrational', diff: 3, + text: R`Сумма корней (корень, если он единственный) уравнения $\sqrt{2x+5}\cdot\sqrt{x-1}=3-x$ равна (равен):`, + opts: mc('$\dfrac{-9-\sqrt{137}}{2}$', '$9$', '$18$', '$\dfrac{-9+\sqrt{137}}{2}$', '$-14$'), + answer: 'г', + sol: R`ОДЗ: $x\ge1$ и $3-x\ge0$, то есть $x\in[1;3]$. Возведя в квадрат: $(2x+5)(x-1)=(3-x)^{2}$, $x^{2}+9x-14=0$, $x=\dfrac{-9\pm\sqrt{137}}{2}$. В $[1;3]$ попадает только $\dfrac{-9+\sqrt{137}}{2}$.`, + ref: 'Арефьева «Алгебра, 10 кл.», гл. 2, § 17' }, + + // ── Часть B: В1–В12 ────────────────────────────────────────────────────── + { idx: 19, type: 'open', topic: 'equations', subtopic: 'eq-rational', diff: 2, + text: R`Найдите сумму целых решений (решение, если оно единственное) системы неравенств $\begin{cases}2x+8\ge x^{2},\\(x-1)^{2}>0.\end{cases}$`, + answer: '6', + sol: R`$2x+8\ge x^{2}\Rightarrow x^{2}-2x-8\le0\Rightarrow-2\le x\le4$; $\ (x-1)^{2}>0\Rightarrow x\ne1$. Целые решения $-2,-1,0,2,3,4$; их сумма равна $6$.`, + ref: 'Арефьева «Алгебра, 8 кл.», гл. 3' }, + + { idx: 20, type: 'open', topic: 'equations', subtopic: 'eq-rational', diff: 3, + text: R`Найдите произведение большего корня на количество корней уравнения $\dfrac{21}{x^{2}-4x+10}-x^{2}+4x=6$.`, + answer: '6', + sol: R`Пусть $u=x^{2}-4x$, тогда $\dfrac{21}{u+10}-u=6$, откуда $u^{2}+16u+39=0$, $u=-3$ или $u=-13$. При $u=-3$: $x^{2}-4x+3=0$, $x=1;3$. При $u=-13$ дискриминант отрицателен. Корни $1$ и $3$, больший $3$, количество $2$; произведение $3\cdot2=6$.`, + ref: 'Арефьева «Алгебра, 9 кл.», гл. 1' }, + + { idx: 21, type: 'open', topic: 'planimetry', subtopic: 'plan-circle', diff: 3, + text: R`В окружность радиусом $6$ вписан треугольник, длины двух сторон которого равны $6$ и $10$. Найдите длину высоты треугольника, проведённой к его третьей стороне.`, + answer: '5', + sol: R`Из формул $S=\dfrac{abc}{4R}$ и $S=\dfrac12 c\,h_c$ следует $h_c=\dfrac{ab}{2R}=\dfrac{6\cdot10}{2\cdot6}=5$.`, + ref: 'Казаков «Геометрия, 9 кл.», гл. 1' }, + + { idx: 22, type: 'open', topic: 'equations', subtopic: 'eq-logarithmic', diff: 3, + text: R`Найдите сумму наименьшего и наибольшего целых решений неравенства $\log_{0{,}3}(x+54)\le2\log_{0{,}3}(x-2)$.`, + answer: '13', + sol: R`Неравенство равносильно $\log_{0{,}3}(x+54)\le\log_{0{,}3}(x-2)^{2}$ при $x>2$. Основание $0{,}3<1$, поэтому $x+54\ge(x-2)^{2}$, то есть $x^{2}-5x-50\le0$, $-5\le x\le10$. С учётом $x>2$: $(2;10]$. Целые $3,\ldots,10$; сумма наименьшего и наибольшего $3+10=13$.`, + ref: 'Арефьева «Алгебра, 11 кл.», гл. 3' }, + + { idx: 23, type: 'open', topic: 'trigonometry', subtopic: 'trig-equations', diff: 3, + text: R`Найдите сумму (в градусах) наименьшего положительного и наибольшего отрицательного корней уравнения $\sin4x-\sqrt3\cos2x=0$.`, + answer: '-15', + sol: R`$\sin4x=2\sin2x\cos2x$, поэтому $\cos2x(2\sin2x-\sqrt3)=0$. Из $\cos2x=0$: $x=45^\circ+90^\circ n$; из $\sin2x=\dfrac{\sqrt3}{2}$: $x=30^\circ+180^\circ n$ или $x=60^\circ+180^\circ n$. Наименьший положительный корень $30^\circ$, наибольший отрицательный $-45^\circ$; их сумма $-15^\circ$.`, + ref: 'Арефьева «Алгебра, 10 кл.», гл. 1, § 8' }, + + { idx: 24, type: 'open', topic: 'word-sequences', subtopic: 'seq-progressions', diff: 4, + text: R`Три числа составляют геометрическую прогрессию, в которой $q>1$. Если второй член прогрессии уменьшить на $8$, то полученные три числа в том же порядке опять составят геометрическую прогрессию. Если третий член новой прогрессии уменьшить на $25$, то полученные числа составят арифметическую прогрессию. Найдите сумму исходных чисел.`, + answer: '21', + sol: R`Пусть числа $a$, $aq$, $aq^{2}$. Из условия $(aq-8)^{2}=a\cdot aq^{2}$ следует $aq=4$. Новая прогрессия $a,\,-4,\,aq^{2}$, где $a\cdot aq^{2}=16$. Условие арифметической прогрессии: $2\cdot(-4)=a+(aq^{2}-25)$, то есть $a+aq^{2}=17$. Так как $aq^{2}=\dfrac{16}{a}$, получаем $a+\dfrac{16}{a}=17$, $a=1$ (тогда $q=4>1$). Числа $1,4,16$, их сумма $21$.`, + ref: 'Арефьева «Алгебра, 9 кл.», гл. 4' }, + + { idx: 25, type: 'open', topic: 'equations', subtopic: 'eq-exponential', diff: 3, + text: R`Найдите произведение суммы корней уравнения $4^{x-1}-2^{x-1}=2^{x+5}-2^{6}$ на их количество.`, + answer: '16', + sol: R`Пусть $t=2^{x-1}$. Тогда $t^{2}-t=64t-64$, $t^{2}-65t+64=0$, $t=1$ или $t=64$. Из $2^{x-1}=1$: $x=1$; из $2^{x-1}=64$: $x=7$. Сумма корней $8$, количество $2$; произведение $8\cdot2=16$.`, + ref: 'Арефьева «Алгебра, 11 кл.», гл. 2' }, + + { idx: 26, type: 'open', topic: 'trigonometry', subtopic: 'trig-equations', diff: 4, + text: R`Найдите количество корней уравнения $\cos x=\left|\dfrac{x}{11\pi}\right|$.`, + answer: '22', + sol: R`Правая часть неотрицательна и не больше $1$ при $|x|\le11\pi$, поэтому требуется $\cos x\ge0$. Обе функции чётные. На $(0;11\pi]$ прямая $\dfrac{x}{11\pi}$ пересекает положительные «арки» косинуса $11$ раз; столько же на отрицательной полуоси. Всего $22$ корня.`, + ref: 'Арефьева «Алгебра, 10 кл.», гл. 1' }, + + { idx: 27, type: 'open', topic: 'equations', subtopic: 'eq-rational', diff: 4, + text: R`Найдите сумму целых решений неравенства $\dfrac{|4x-10|-|2x-14|}{(x+3)(x-6)}\le0$.`, + answer: '7', + sol: R`Числитель равен нулю при $x=-2$ и $x=4$; он положителен вне отрезка $[-2;4]$ и отрицателен внутри него. Знаменатель положителен при $x<-3$ и $x>6$, отрицателен при $-3Ответ: ${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_ct2014_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 ответов пройдены (30/30).'); + +/* ── APPLY: upsert ─────────────────────────────────────────────────────────── */ +if (!APPLY) { + console.log('\nDRY-RUN: ничего не записано. Для записи: node backend/scripts/seed_ctmath_ct2014_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 → «Варианты» → «ЦТ-2014».\n`); +} catch (e) { + db.exec('ROLLBACK'); + console.error('\n✗ Ошибка записи, откат транзакции:', e.message); + process.exitCode = 1; +} +db.close(); diff --git a/backend/src/routes/exam-prep.js b/backend/src/routes/exam-prep.js index 45a7b38..9107a93 100644 --- a/backend/src/routes/exam-prep.js +++ b/backend/src/routes/exam-prep.js @@ -44,6 +44,7 @@ const VARIANT_LABEL = { 107: 'РТ-2022/23 · этап I', 108: 'РТ-2022/23 · этап II', 109: 'РТ-2022/23 · этап III', + 110: 'ЦТ-2014', }, }; const examVariantLabel = (examKey, v) => VARIANT_LABEL[examKey]?.[v] || `Вариант ${v}`;