diff --git a/backend/scripts/exam-textbook-sections.json b/backend/scripts/exam-textbook-sections.json
new file mode 100644
index 0000000..7664c11
--- /dev/null
+++ b/backend/scripts/exam-textbook-sections.json
@@ -0,0 +1,1559 @@
+[
+ {
+ "book": "math-5",
+ "chapter_slug": "math-5-ch1",
+ "subject": "math",
+ "grade": 5,
+ "engine": "math6",
+ "note": "paras in window.M6 config; anchors sec-
"
+ },
+ {
+ "book": "math-5",
+ "chapter_slug": "math-5-ch2",
+ "subject": "math",
+ "grade": 5,
+ "engine": "math6",
+ "note": "paras in window.M6 config; anchors sec-"
+ },
+ {
+ "book": "math-5",
+ "chapter_slug": "math-5-ch3",
+ "subject": "math",
+ "grade": 5,
+ "engine": "math6",
+ "note": "paras in window.M6 config; anchors sec-"
+ },
+ {
+ "book": "math-6",
+ "chapter_slug": "math-6-ch1",
+ "subject": "math",
+ "grade": 6,
+ "engine": "math6",
+ "note": "paras in window.M6 config; anchors sec-"
+ },
+ {
+ "book": "math-6",
+ "chapter_slug": "math-6-ch2",
+ "subject": "math",
+ "grade": 6,
+ "engine": "math6",
+ "note": "paras in window.M6 config; anchors sec-"
+ },
+ {
+ "book": "math-6",
+ "chapter_slug": "math-6-ch3",
+ "subject": "math",
+ "grade": 6,
+ "engine": "math6",
+ "note": "paras in window.M6 config; anchors sec-"
+ },
+ {
+ "book": "math-6",
+ "chapter_slug": "math-6-ch4",
+ "subject": "math",
+ "grade": 6,
+ "engine": "math6",
+ "note": "paras in window.M6 config; anchors sec-"
+ },
+ {
+ "book": "math-6",
+ "chapter_slug": "math-6-ch5",
+ "subject": "math",
+ "grade": 6,
+ "engine": "math6",
+ "note": "paras in window.M6 config; anchors sec-"
+ },
+ {
+ "book": "math-6",
+ "chapter_slug": "math-6-ch6",
+ "subject": "math",
+ "grade": 6,
+ "engine": "math6",
+ "note": "paras in window.M6 config; anchors sec-"
+ },
+ {
+ "book": "algebra-7",
+ "chapter_slug": "algebra-7-ch1",
+ "subject": "algebra",
+ "grade": 7,
+ "para_id": "p1",
+ "num": "§ 1",
+ "title": "Степень с натуральным показателем и её свойства"
+ },
+ {
+ "book": "algebra-7",
+ "chapter_slug": "algebra-7-ch1",
+ "subject": "algebra",
+ "grade": 7,
+ "para_id": "p2",
+ "num": "§ 2",
+ "title": "Степень с целым показателем и её свойства"
+ },
+ {
+ "book": "algebra-7",
+ "chapter_slug": "algebra-7-ch1",
+ "subject": "algebra",
+ "grade": 7,
+ "para_id": "p3",
+ "num": "§ 3",
+ "title": "Стандартный вид числа"
+ },
+ {
+ "book": "algebra-7",
+ "chapter_slug": "algebra-7-ch2",
+ "subject": "algebra",
+ "grade": 7,
+ "para_id": "p4",
+ "num": "§ 4",
+ "title": "Числовые выражения и выражения с переменными"
+ },
+ {
+ "book": "algebra-7",
+ "chapter_slug": "algebra-7-ch2",
+ "subject": "algebra",
+ "grade": 7,
+ "para_id": "p5",
+ "num": "§ 5",
+ "title": "Тождество. Тождественные преобразования"
+ },
+ {
+ "book": "algebra-7",
+ "chapter_slug": "algebra-7-ch2",
+ "subject": "algebra",
+ "grade": 7,
+ "para_id": "p6",
+ "num": "§ 6",
+ "title": "Одночлен. Стандартный вид. Степень"
+ },
+ {
+ "book": "algebra-7",
+ "chapter_slug": "algebra-7-ch2",
+ "subject": "algebra",
+ "grade": 7,
+ "para_id": "p7",
+ "num": "§ 7",
+ "title": "Действия с одночленами"
+ },
+ {
+ "book": "algebra-7",
+ "chapter_slug": "algebra-7-ch2",
+ "subject": "algebra",
+ "grade": 7,
+ "para_id": "p8",
+ "num": "§ 8",
+ "title": "Многочлен. Стандартный вид. Степень"
+ },
+ {
+ "book": "algebra-7",
+ "chapter_slug": "algebra-7-ch2",
+ "subject": "algebra",
+ "grade": 7,
+ "para_id": "p9",
+ "num": "§ 9",
+ "title": "Сложение и вычитание многочленов"
+ },
+ {
+ "book": "algebra-7",
+ "chapter_slug": "algebra-7-ch2",
+ "subject": "algebra",
+ "grade": 7,
+ "para_id": "p10",
+ "num": "§ 10",
+ "title": "Умножение и деление многочлена на одночлен"
+ },
+ {
+ "book": "algebra-7",
+ "chapter_slug": "algebra-7-ch2",
+ "subject": "algebra",
+ "grade": 7,
+ "para_id": "p11",
+ "num": "§ 11",
+ "title": "Умножение многочленов"
+ },
+ {
+ "book": "algebra-7",
+ "chapter_slug": "algebra-7-ch2",
+ "subject": "algebra",
+ "grade": 7,
+ "para_id": "p12",
+ "num": "§ 12",
+ "title": "Квадрат суммы и квадрат разности"
+ },
+ {
+ "book": "algebra-7",
+ "chapter_slug": "algebra-7-ch2",
+ "subject": "algebra",
+ "grade": 7,
+ "para_id": "p13",
+ "num": "§ 13",
+ "title": "Разность квадратов: (a−b)(a+b) = a\\xB2−b\\xB2"
+ },
+ {
+ "book": "algebra-7",
+ "chapter_slug": "algebra-7-ch2",
+ "subject": "algebra",
+ "grade": 7,
+ "para_id": "p14",
+ "num": "§ 14",
+ "title": "Разложение многочлена на множители"
+ },
+ {
+ "book": "algebra-7",
+ "chapter_slug": "algebra-7-ch3",
+ "subject": "algebra",
+ "grade": 7,
+ "para_id": "p15",
+ "num": "§ 15",
+ "title": "Линейные уравнения с одной переменной"
+ },
+ {
+ "book": "algebra-7",
+ "chapter_slug": "algebra-7-ch3",
+ "subject": "algebra",
+ "grade": 7,
+ "para_id": "p16",
+ "num": "§ 16",
+ "title": "Решение текстовых задач с помощью уравнений"
+ },
+ {
+ "book": "algebra-7",
+ "chapter_slug": "algebra-7-ch3",
+ "subject": "algebra",
+ "grade": 7,
+ "para_id": "p17",
+ "num": "§ 17",
+ "title": "Числовые неравенства и их свойства"
+ },
+ {
+ "book": "algebra-7",
+ "chapter_slug": "algebra-7-ch3",
+ "subject": "algebra",
+ "grade": 7,
+ "para_id": "p18",
+ "num": "§ 18",
+ "title": "Линейные неравенства с одной переменной"
+ },
+ {
+ "book": "algebra-7",
+ "chapter_slug": "algebra-7-ch3",
+ "subject": "algebra",
+ "grade": 7,
+ "para_id": "p19",
+ "num": "§ 19",
+ "title": "Функция"
+ },
+ {
+ "book": "algebra-7",
+ "chapter_slug": "algebra-7-ch3",
+ "subject": "algebra",
+ "grade": 7,
+ "para_id": "p20",
+ "num": "§ 20",
+ "title": "Линейная функция и её свойства"
+ },
+ {
+ "book": "algebra-7",
+ "chapter_slug": "algebra-7-ch4",
+ "subject": "algebra",
+ "grade": 7,
+ "para_id": "p21",
+ "num": "§ 21",
+ "title": "Линейное уравнение с двумя переменными"
+ },
+ {
+ "book": "algebra-7",
+ "chapter_slug": "algebra-7-ch4",
+ "subject": "algebra",
+ "grade": 7,
+ "para_id": "p22",
+ "num": "§ 22",
+ "title": "График линейного уравнения $ax + by = c$"
+ },
+ {
+ "book": "algebra-7",
+ "chapter_slug": "algebra-7-ch4",
+ "subject": "algebra",
+ "grade": 7,
+ "para_id": "p23",
+ "num": "§ 23",
+ "title": "Система линейных уравнений с двумя переменными"
+ },
+ {
+ "book": "algebra-7",
+ "chapter_slug": "algebra-7-ch4",
+ "subject": "algebra",
+ "grade": 7,
+ "para_id": "p24",
+ "num": "§ 24",
+ "title": "Способы решения системы (подстановка, сложение)"
+ },
+ {
+ "book": "algebra-7",
+ "chapter_slug": "algebra-7-ch4",
+ "subject": "algebra",
+ "grade": 7,
+ "para_id": "p25",
+ "num": "§ 25",
+ "title": "Решение текстовых задач с помощью системы"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch1",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p1",
+ "num": "§ 1",
+ "title": "Повторение материала 5-6 классов"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch1",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p2",
+ "num": "§ 2",
+ "title": "Предмет геометрии"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch1",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p3",
+ "num": "§ 3",
+ "title": "Прямая. Луч. Отрезок. Ломаная"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch1",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p4",
+ "num": "§ 4",
+ "title": "Окружность и круг"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch1",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p5",
+ "num": "§ 5",
+ "title": "Угол. Виды углов"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch1",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p6",
+ "num": "§ 6",
+ "title": "Смежные углы. Вертикальные углы"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch1",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p7",
+ "num": "§ 7",
+ "title": "Перпендикулярные прямые"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch2",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p8",
+ "num": "§ 8",
+ "title": "Треугольники"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch2",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p9",
+ "num": "§ 9",
+ "title": "Первый и второй признаки равенства"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch2",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p10",
+ "num": "§ 10",
+ "title": "Высота, медиана и биссектриса треугольника"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch2",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p11",
+ "num": "§ 11",
+ "title": "Равнобедренный треугольник"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch2",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p12",
+ "num": "§ 12",
+ "title": "Признаки равнобедренного треугольника"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch2",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p13",
+ "num": "§ 13",
+ "title": "Третий признак равенства треугольников"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch2",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p14",
+ "num": "§ 14",
+ "title": "Серединный перпендикуляр к отрезку"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch3",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p15",
+ "num": "§ 15",
+ "title": "Признаки параллельности прямых"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch3",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p16",
+ "num": "§ 16",
+ "title": "Аксиома параллельных прямых"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch3",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p17",
+ "num": "§ 17",
+ "title": "Свойства параллельных прямых"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch3",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p18",
+ "num": "§ 18",
+ "title": "Углы со сторонами, соответственно параллельными или перпендикулярными"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch4",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p19",
+ "num": "§ 19",
+ "title": "Сумма углов треугольника"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch4",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p20",
+ "num": "§ 20",
+ "title": "Внешний угол треугольника"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch4",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p21",
+ "num": "§ 21",
+ "title": "Соотношения между сторонами и углами"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch4",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p22",
+ "num": "§ 22",
+ "title": "Неравенство треугольника"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch4",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p23",
+ "num": "§ 23",
+ "title": "Прямоугольные треугольники"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch4",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p24",
+ "num": "§ 24",
+ "title": "Признаки равенства прямоугольных треугольников"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch4",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p25",
+ "num": "§ 25",
+ "title": "Биссектриса угла как ГМТ"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch4",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p26",
+ "num": "§ 26",
+ "title": "Свойство катета против угла 30°"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch5",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p27",
+ "num": "§ 27",
+ "title": "Простейшие построения"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch5",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p28",
+ "num": "§ 28",
+ "title": "Построение треугольника по 3 сторонам"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch5",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p29",
+ "num": "§ 29",
+ "title": "Построение биссектрисы угла"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch5",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p30",
+ "num": "§ 30",
+ "title": "Середина и перпендикуляр"
+ },
+ {
+ "book": "geometry-7",
+ "chapter_slug": "geometry-7-ch5",
+ "subject": "geometry",
+ "grade": 7,
+ "para_id": "p31",
+ "num": "§ 31",
+ "title": "Метод геометрических мест точек"
+ },
+ {
+ "book": "algebra-8",
+ "chapter_slug": "algebra-8-ch1",
+ "subject": "algebra",
+ "grade": 8,
+ "para_id": "p1",
+ "num": "§ 1",
+ "title": "Квадратный корень из числа. Арифметический квадратный корень"
+ },
+ {
+ "book": "algebra-8",
+ "chapter_slug": "algebra-8-ch1",
+ "subject": "algebra",
+ "grade": 8,
+ "para_id": "p2",
+ "num": "§ 2",
+ "title": "Множество иррациональных чисел. Множество действительных чисел"
+ },
+ {
+ "book": "algebra-8",
+ "chapter_slug": "algebra-8-ch1",
+ "subject": "algebra",
+ "grade": 8,
+ "para_id": "p3",
+ "num": "§ 3",
+ "title": "Свойства квадратных корней"
+ },
+ {
+ "book": "algebra-8",
+ "chapter_slug": "algebra-8-ch1",
+ "subject": "algebra",
+ "grade": 8,
+ "para_id": "p4",
+ "num": "§ 4",
+ "title": "Применение свойств квадратных корней"
+ },
+ {
+ "book": "algebra-8",
+ "chapter_slug": "algebra-8-ch1",
+ "subject": "algebra",
+ "grade": 8,
+ "para_id": "p5",
+ "num": "§ 5",
+ "title": "Числовые промежутки. Объединение и пересечение"
+ },
+ {
+ "book": "algebra-8",
+ "chapter_slug": "algebra-8-ch1",
+ "subject": "algebra",
+ "grade": 8,
+ "para_id": "p6",
+ "num": "§ 6",
+ "title": "Системы и совокупности линейных неравенств. Двойные неравенства"
+ },
+ {
+ "book": "algebra-8",
+ "chapter_slug": "algebra-8-ch2",
+ "subject": "algebra",
+ "grade": 8,
+ "para_id": "p7",
+ "num": "§ 7",
+ "title": "Квадратные уравнения. Решение неполных квадратных уравнений"
+ },
+ {
+ "book": "algebra-8",
+ "chapter_slug": "algebra-8-ch2",
+ "subject": "algebra",
+ "grade": 8,
+ "para_id": "p8",
+ "num": "§ 8",
+ "title": "Формулы корней квадратного уравнения"
+ },
+ {
+ "book": "algebra-8",
+ "chapter_slug": "algebra-8-ch2",
+ "subject": "algebra",
+ "grade": 8,
+ "para_id": "p9",
+ "num": "§ 9",
+ "title": "Теорема Виета"
+ },
+ {
+ "book": "algebra-8",
+ "chapter_slug": "algebra-8-ch2",
+ "subject": "algebra",
+ "grade": 8,
+ "para_id": "p10",
+ "num": "§ 10",
+ "title": "Квадратный трёхчлен. Разложение на множители"
+ },
+ {
+ "book": "algebra-8",
+ "chapter_slug": "algebra-8-ch2",
+ "subject": "algebra",
+ "grade": 8,
+ "para_id": "p11",
+ "num": "§ 11",
+ "title": "Решение текстовых задач с помощью квадратных уравнений"
+ },
+ {
+ "book": "algebra-8",
+ "chapter_slug": "algebra-8-ch2",
+ "subject": "algebra",
+ "grade": 8,
+ "para_id": "p12",
+ "num": "§ 12",
+ "title": "Целые рациональные уравнения, сводящиеся к квадратным"
+ },
+ {
+ "book": "algebra-8",
+ "chapter_slug": "algebra-8-ch3",
+ "subject": "algebra",
+ "grade": 8,
+ "para_id": "p13",
+ "num": "§ 13",
+ "title": "Числовые неравенства и их свойства"
+ },
+ {
+ "book": "algebra-8",
+ "chapter_slug": "algebra-8-ch3",
+ "subject": "algebra",
+ "grade": 8,
+ "para_id": "p14",
+ "num": "§ 14",
+ "title": "Сложение и умножение числовых неравенств. Оценка значений"
+ },
+ {
+ "book": "algebra-8",
+ "chapter_slug": "algebra-8-ch3",
+ "subject": "algebra",
+ "grade": 8,
+ "para_id": "p15",
+ "num": "§ 15",
+ "title": "Числовые промежутки. Линейные неравенства"
+ },
+ {
+ "book": "algebra-8",
+ "chapter_slug": "algebra-8-ch3",
+ "subject": "algebra",
+ "grade": 8,
+ "para_id": "p16",
+ "num": "§ 16",
+ "title": "Системы и совокупности линейных неравенств"
+ },
+ {
+ "book": "algebra-8",
+ "chapter_slug": "algebra-8-ch3",
+ "subject": "algebra",
+ "grade": 8,
+ "para_id": "p17",
+ "num": "§ 17",
+ "title": "Квадратные неравенства. Метод интервалов"
+ },
+ {
+ "book": "algebra-8",
+ "chapter_slug": "algebra-8-ch3",
+ "subject": "algebra",
+ "grade": 8,
+ "para_id": "p18",
+ "num": "§ 18",
+ "title": "Дробно-рациональные неравенства"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch1",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p1",
+ "num": "§ 1",
+ "title": "Выпуклые многоугольники. Диагональ. Периметр"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch1",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p2",
+ "num": "§ 2",
+ "title": "Сумма углов выпуклого многоугольника"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch1",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p3",
+ "num": "§ 3",
+ "title": "Сумма внешних углов выпуклого многоугольника"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch1",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p4",
+ "num": "§ 4",
+ "title": "Параллелограмм"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch1",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p5",
+ "num": "§ 5",
+ "title": "Свойства параллелограмма"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch1",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p6",
+ "num": "§ 6",
+ "title": "Признаки параллелограмма"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch1",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p7",
+ "num": "§ 7",
+ "title": "Прямоугольник. Свойство диагоналей"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch1",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p8",
+ "num": "§ 8",
+ "title": "Признак прямоугольника"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch1",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p9",
+ "num": "§ 9",
+ "title": "Ромб. Свойство диагоналей ромба. Признаки ромба"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch1",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p10",
+ "num": "§ 10",
+ "title": "Квадрат. Свойства квадрата"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch1",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p11",
+ "num": "§ 11",
+ "title": "Теорема Фалеса. Деление отрезка на равные части"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch1",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p12",
+ "num": "§ 12",
+ "title": "Свойство медиан треугольника"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch1",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p13",
+ "num": "§ 13",
+ "title": "Свойство средней линии треугольника"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch1",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p14",
+ "num": "§ 14",
+ "title": "Трапеция. Свойство средней линии"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch1",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p15",
+ "num": "§ 15",
+ "title": "Свойство углов и диагоналей равнобедренной трапеции"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch1",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p16",
+ "num": "§ 16",
+ "title": "Признаки равнобедренной трапеции"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch2",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p1",
+ "num": "§ 1",
+ "title": "Площадь квадрата"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch2",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p2",
+ "num": "§ 2",
+ "title": "Площадь прямоугольника"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch2",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p3",
+ "num": "§ 3",
+ "title": "Площадь параллелограмма"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch2",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p4",
+ "num": "§ 4",
+ "title": "Площадь треугольника"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch2",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p5",
+ "num": "§ 5",
+ "title": "Площадь трапеции"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch2",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p6",
+ "num": "§ 6",
+ "title": "Площадь ромба"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch2",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p7",
+ "num": "§ 7",
+ "title": "Площадь прямоугольного треугольника"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch2",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p8",
+ "num": "§ 8",
+ "title": "Высота, проведённая к гипотенузе"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch2",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p9",
+ "num": "§ 9",
+ "title": "Треугольники с общей высотой"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch2",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p10",
+ "num": "§ 10",
+ "title": "Свойства медианы для площадей"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch2",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p11",
+ "num": "§ 11",
+ "title": "Теорема Пифагора"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch2",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p12",
+ "num": "§ 12",
+ "title": "S и h равностороннего треугольника"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch2",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p13",
+ "num": "§ 13",
+ "title": "Диагональ квадрата"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch2",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p14",
+ "num": "§ 14",
+ "title": "Обратная теорема Пифагора"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch2",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p15",
+ "num": "§ 15",
+ "title": "Пифагоровы тройки"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch3",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p1",
+ "num": "§ 1",
+ "title": "Теорема Фалеса (обобщённая)"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch3",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p2",
+ "num": "§ 2",
+ "title": "Деление отрезка в отношении m : n"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch3",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p3",
+ "num": "§ 3",
+ "title": "Определение подобных треугольников"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch3",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p4",
+ "num": "§ 4",
+ "title": "Свойство прямой, параллельной стороне треугольника"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch3",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p5",
+ "num": "§ 5",
+ "title": "Первый признак подобия. Следствие"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch3",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p6",
+ "num": "§ 6",
+ "title": "Второй признак подобия"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch3",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p7",
+ "num": "§ 7",
+ "title": "Третий признак подобия"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch3",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p8",
+ "num": "§ 8",
+ "title": "Свойство биссектрисы треугольника"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch3",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p9",
+ "num": "§ 9",
+ "title": "Отношение площадей подобных треугольников"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch4",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p1",
+ "num": "§ 1",
+ "title": "Касательная. Признак касательной"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch4",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p2",
+ "num": "§ 2",
+ "title": "Свойство касательной"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch4",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p3",
+ "num": "§ 3",
+ "title": "Свойство касательных из одной точки"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch4",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p4",
+ "num": "§ 4",
+ "title": "Построение касательной"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch4",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p5",
+ "num": "§ 5",
+ "title": "Свойство окружностей, вписанных в угол"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch4",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p6",
+ "num": "§ 6",
+ "title": "Взаимное расположение окружностей"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch4",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p7",
+ "num": "§ 7",
+ "title": "Длина общей внешней касательной"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch4",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p8",
+ "num": "§ 8",
+ "title": "Центральный угол. Градусная мера дуги. Вписанный угол"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch4",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p9",
+ "num": "§ 9",
+ "title": "Свойство вписанного угла"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch4",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p10",
+ "num": "§ 10",
+ "title": "Вписанные углы, опирающиеся на одну дугу"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch4",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p11",
+ "num": "§ 11",
+ "title": "Вписанный угол, опирающийся на диаметр"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch4",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p12",
+ "num": "§ 12",
+ "title": "Угол между касательной и хордой"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch4",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p13",
+ "num": "§ 13",
+ "title": "Угол между хордами"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch4",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p14",
+ "num": "§ 14",
+ "title": "Угол между секущими"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch4",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p15",
+ "num": "§ 15",
+ "title": "Свойство пересекающихся хорд"
+ },
+ {
+ "book": "geometry-8",
+ "chapter_slug": "geometry-8-ch4",
+ "subject": "geometry",
+ "grade": 8,
+ "para_id": "p16",
+ "num": "§ 16",
+ "title": "Свойство касательной и секущей"
+ },
+ {
+ "book": "algebra-9",
+ "chapter_slug": "algebra-9-ch1",
+ "subject": "algebra",
+ "grade": 9,
+ "para_id": "p1",
+ "num": "§ 1",
+ "title": "Рациональная дробь"
+ },
+ {
+ "book": "algebra-9",
+ "chapter_slug": "algebra-9-ch1",
+ "subject": "algebra",
+ "grade": 9,
+ "para_id": "p2",
+ "num": "§ 2",
+ "title": "Основное свойство дроби"
+ },
+ {
+ "book": "algebra-9",
+ "chapter_slug": "algebra-9-ch1",
+ "subject": "algebra",
+ "grade": 9,
+ "para_id": "p3",
+ "num": "§ 3",
+ "title": "Сложение и вычитание"
+ },
+ {
+ "book": "algebra-9",
+ "chapter_slug": "algebra-9-ch1",
+ "subject": "algebra",
+ "grade": 9,
+ "para_id": "p4",
+ "num": "§ 4",
+ "title": "Умножение и деление"
+ },
+ {
+ "book": "algebra-9",
+ "chapter_slug": "algebra-9-ch1",
+ "subject": "algebra",
+ "grade": 9,
+ "para_id": "p5",
+ "num": "§ 5",
+ "title": "Преобразование выражений"
+ },
+ {
+ "book": "algebra-9",
+ "chapter_slug": "algebra-9-ch2",
+ "subject": "algebra",
+ "grade": 9,
+ "para_id": "p6",
+ "num": "§ 6",
+ "title": "Функция числового аргумента"
+ },
+ {
+ "book": "algebra-9",
+ "chapter_slug": "algebra-9-ch2",
+ "subject": "algebra",
+ "grade": 9,
+ "para_id": "p7",
+ "num": "§ 7",
+ "title": "Свойства функции"
+ },
+ {
+ "book": "algebra-9",
+ "chapter_slug": "algebra-9-ch2",
+ "subject": "algebra",
+ "grade": 9,
+ "para_id": "p8",
+ "num": "§ 8",
+ "title": "Чётные и нечётные функции"
+ },
+ {
+ "book": "algebra-9",
+ "chapter_slug": "algebra-9-ch2",
+ "subject": "algebra",
+ "grade": 9,
+ "para_id": "p9",
+ "num": "§ 9",
+ "title": "Сдвиги графиков"
+ },
+ {
+ "book": "algebra-9",
+ "chapter_slug": "algebra-9-ch3",
+ "subject": "algebra",
+ "grade": 9,
+ "para_id": "p10",
+ "num": "§ 10",
+ "title": "Дробно-рациональные уравнения"
+ },
+ {
+ "book": "algebra-9",
+ "chapter_slug": "algebra-9-ch3",
+ "subject": "algebra",
+ "grade": 9,
+ "para_id": "p11",
+ "num": "§ 11",
+ "title": "Системы нелинейных уравнений"
+ },
+ {
+ "book": "algebra-9",
+ "chapter_slug": "algebra-9-ch3",
+ "subject": "algebra",
+ "grade": 9,
+ "para_id": "p12",
+ "num": "§ 12",
+ "title": "Уравнение окружности"
+ },
+ {
+ "book": "algebra-9",
+ "chapter_slug": "algebra-9-ch3",
+ "subject": "algebra",
+ "grade": 9,
+ "para_id": "p13",
+ "num": "§ 13",
+ "title": "Метод интервалов"
+ },
+ {
+ "book": "algebra-9",
+ "chapter_slug": "algebra-9-ch4",
+ "subject": "algebra",
+ "grade": 9,
+ "para_id": "p14",
+ "num": "§ 14",
+ "title": "Числовая последовательность"
+ },
+ {
+ "book": "algebra-9",
+ "chapter_slug": "algebra-9-ch4",
+ "subject": "algebra",
+ "grade": 9,
+ "para_id": "p15",
+ "num": "§ 15",
+ "title": "Арифметическая прогрессия"
+ },
+ {
+ "book": "algebra-9",
+ "chapter_slug": "algebra-9-ch4",
+ "subject": "algebra",
+ "grade": 9,
+ "para_id": "p16",
+ "num": "§ 16",
+ "title": "Сумма арифм. прогрессии"
+ },
+ {
+ "book": "algebra-9",
+ "chapter_slug": "algebra-9-ch4",
+ "subject": "algebra",
+ "grade": 9,
+ "para_id": "p17",
+ "num": "§ 17",
+ "title": "Геометрическая прогрессия"
+ },
+ {
+ "book": "algebra-9",
+ "chapter_slug": "algebra-9-ch4",
+ "subject": "algebra",
+ "grade": 9,
+ "para_id": "p18",
+ "num": "§ 18",
+ "title": "Сумма геом. прогрессии"
+ },
+ {
+ "book": "algebra-9",
+ "chapter_slug": "algebra-9-ch4",
+ "subject": "algebra",
+ "grade": 9,
+ "para_id": "p19",
+ "num": "§ 19",
+ "title": "Бесконечно убывающая"
+ },
+ {
+ "book": "geometry-9",
+ "chapter_slug": "geometry-9-ch1",
+ "subject": "geometry",
+ "grade": 9,
+ "para_id": "p1",
+ "num": "§ 1",
+ "title": "sin, cos, tg, ctg острого угла"
+ },
+ {
+ "book": "geometry-9",
+ "chapter_slug": "geometry-9-ch1",
+ "subject": "geometry",
+ "grade": 9,
+ "para_id": "p2",
+ "num": "§ 2",
+ "title": "Решение прямоугольного треугольника"
+ },
+ {
+ "book": "geometry-9",
+ "chapter_slug": "geometry-9-ch1",
+ "subject": "geometry",
+ "grade": 9,
+ "para_id": "p3",
+ "num": "§ 3",
+ "title": "Тригонометрические формулы"
+ },
+ {
+ "book": "geometry-9",
+ "chapter_slug": "geometry-9-ch1",
+ "subject": "geometry",
+ "grade": 9,
+ "para_id": "p4",
+ "num": "§ 4",
+ "title": "sin, cos, tg, ctg тупого угла"
+ },
+ {
+ "book": "geometry-9",
+ "chapter_slug": "geometry-9-ch1",
+ "subject": "geometry",
+ "grade": 9,
+ "para_id": "p5",
+ "num": "§ 5",
+ "title": "Формулы площади"
+ },
+ {
+ "book": "geometry-9",
+ "chapter_slug": "geometry-9-ch1",
+ "subject": "geometry",
+ "grade": 9,
+ "para_id": "p6",
+ "num": "§ 6",
+ "title": "Среднее геометрическое"
+ },
+ {
+ "book": "geometry-9",
+ "chapter_slug": "geometry-9-ch2",
+ "subject": "geometry",
+ "grade": 9,
+ "para_id": "p7",
+ "num": "§ 7",
+ "title": "Описанная и вписанная окружности треугольника"
+ },
+ {
+ "book": "geometry-9",
+ "chapter_slug": "geometry-9-ch2",
+ "subject": "geometry",
+ "grade": 9,
+ "para_id": "p8",
+ "num": "§ 8",
+ "title": "Окружности прямоугольного треугольника"
+ },
+ {
+ "book": "geometry-9",
+ "chapter_slug": "geometry-9-ch2",
+ "subject": "geometry",
+ "grade": 9,
+ "para_id": "p9",
+ "num": "§ 9",
+ "title": "Вписанные и описанные четырёхугольники"
+ },
+ {
+ "book": "geometry-9",
+ "chapter_slug": "geometry-9-ch3",
+ "subject": "geometry",
+ "grade": 9,
+ "para_id": "p10",
+ "num": "§ 10",
+ "title": "Теорема синусов"
+ },
+ {
+ "book": "geometry-9",
+ "chapter_slug": "geometry-9-ch3",
+ "subject": "geometry",
+ "grade": 9,
+ "para_id": "p11",
+ "num": "§ 11",
+ "title": "Теорема косинусов"
+ },
+ {
+ "book": "geometry-9",
+ "chapter_slug": "geometry-9-ch3",
+ "subject": "geometry",
+ "grade": 9,
+ "para_id": "p12",
+ "num": "§ 12",
+ "title": "Формула Герона. Решение треугольников"
+ },
+ {
+ "book": "geometry-9",
+ "chapter_slug": "geometry-9-ch4",
+ "subject": "geometry",
+ "grade": 9,
+ "para_id": "p13",
+ "num": "§ 13",
+ "title": "Правильные многоугольники"
+ },
+ {
+ "book": "geometry-9",
+ "chapter_slug": "geometry-9-ch4",
+ "subject": "geometry",
+ "grade": 9,
+ "para_id": "p14",
+ "num": "§ 14",
+ "title": "Формулы радиусов"
+ },
+ {
+ "book": "geometry-9",
+ "chapter_slug": "geometry-9-ch4",
+ "subject": "geometry",
+ "grade": 9,
+ "para_id": "p15",
+ "num": "§ 15",
+ "title": "Треугольник, квадрат, шестиугольник"
+ },
+ {
+ "book": "geometry-9",
+ "chapter_slug": "geometry-9-ch4",
+ "subject": "geometry",
+ "grade": 9,
+ "para_id": "p16",
+ "num": "§ 16",
+ "title": "Длина окружности и площадь круга"
+ }
+]
\ No newline at end of file
diff --git a/backend/scripts/gen-exam-textbook-sections.js b/backend/scripts/gen-exam-textbook-sections.js
new file mode 100644
index 0000000..936a61a
--- /dev/null
+++ b/backend/scripts/gen-exam-textbook-sections.js
@@ -0,0 +1,87 @@
+#!/usr/bin/env node
+/**
+ * gen-exam-textbook-sections.js
+ *
+ * Regenerates the §-section taxonomy of the grades 5-9 math-family textbooks,
+ * used by tag-exam-textbook.js (the exam→textbook classifier).
+ *
+ * Outputs:
+ * backend/scripts/exam-textbook-sections.json — machine-readable (the classifier reads this)
+ * plans/exam-textbook-links/taxonomy.md — human-readable reference
+ *
+ * Re-run whenever a grade 5-9 algebra/geometry/math chapter gains or renames a §.
+ * Note: math-5/6 are engine-rendered (math6_engine.js builds
+ * from window.M6.paras) — their §s are NOT extracted statically here (emitted with
+ * engine:'math6' marker); the classifier links them at chapter level.
+ *
+ * Usage: node backend/scripts/gen-exam-textbook-sections.js
+ */
+'use strict';
+const fs = require('fs');
+const path = require('path');
+
+const DIR = path.join(__dirname, '../../frontend/textbooks');
+const OUT_MD = path.join(__dirname, '../../plans/exam-textbook-links/taxonomy.md');
+const OUT_JSON = path.join(__dirname, 'exam-textbook-sections.json');
+
+// chapter slug -> html file (from the textbooks table). Order = teaching order.
+const CHAPTERS = [
+ ['math-5-ch1', 'math_5_ch1.html'], ['math-5-ch2', 'math_5_ch2.html'], ['math-5-ch3', 'math_5_ch3.html'],
+ ['math-6-ch1', 'math_6_ch1.html'], ['math-6-ch2', 'math_6_ch2.html'], ['math-6-ch3', 'math_6_ch3.html'],
+ ['math-6-ch4', 'math_6_ch4.html'], ['math-6-ch5', 'math_6_ch5.html'], ['math-6-ch6', 'math_6_ch6.html'],
+ ['algebra-7-ch1', 'algebra_7_ch1.html'], ['algebra-7-ch2', 'algebra_7_ch2.html'],
+ ['algebra-7-ch3', 'algebra_7_ch3.html'], ['algebra-7-ch4', 'algebra_7_ch4.html'],
+ ['geometry-7-ch1', 'geometry_7_ch1.html'], ['geometry-7-ch2', 'geometry_7_ch2.html'],
+ ['geometry-7-ch3', 'geometry_7_ch3.html'], ['geometry-7-ch4', 'geometry_7_ch4.html'], ['geometry-7-ch5', 'geometry_7_ch5.html'],
+ ['algebra-8-ch1', 'algebra_8.html'], ['algebra-8-ch2', 'algebra_8_ch2.html'], ['algebra-8-ch3', 'algebra_8_ch3.html'],
+ ['geometry-8-ch1', 'geometry_8_ch1.html'], ['geometry-8-ch2', 'geometry_8_ch2.html'],
+ ['geometry-8-ch3', 'geometry_8_ch3.html'], ['geometry-8-ch4', 'geometry_8_ch4.html'],
+ ['algebra-9-ch1', 'algebra_9_ch1.html'], ['algebra-9-ch2', 'algebra_9_ch2.html'],
+ ['algebra-9-ch3', 'algebra_9_ch3.html'], ['algebra-9-ch4', 'algebra_9_ch4.html'],
+ ['geometry-9-ch1', 'geometry_9_ch1.html'], ['geometry-9-ch2', 'geometry_9_ch2.html'],
+ ['geometry-9-ch3', 'geometry_9_ch3.html'], ['geometry-9-ch4', 'geometry_9_ch4.html'],
+];
+
+function strip(html) { return String(html).replace(/<[^>]+>/g, '').replace(/\s+/g, ' ').trim(); }
+
+const lines = ['# §-таксономия учебников 5–9 (математика) — эталон для классификатора экзамена math9', ''];
+const json = []; // [{book, chapter_slug, subject, grade, para_id, num, title}]
+let prevBook = '';
+for (const [slug, file] of CHAPTERS) {
+ const book = slug.replace(/-ch\d+$/, '');
+ const subject = book.replace(/-\d+$/, ''); // math|algebra|geometry
+ const grade = Number((book.match(/-(\d+)$/) || [])[1]) || null;
+ if (book !== prevBook) { lines.push(`\n## ${book}`); prevBook = book; }
+ const p = path.join(DIR, file);
+ if (!fs.existsSync(p)) { lines.push(`### ${slug} (FILE MISSING: ${file})`); continue; }
+ const html = fs.readFileSync(p, 'utf8');
+ const tm = html.match(/([^<]*)<\/title>/i);
+ lines.push(`### ${slug} — ${tm ? strip(tm[1]) : file}`);
+
+ const secRe = /<(?:section|div)\b[^>]*\sid="(sec-(?:p\d+|final\d*|[a-z0-9-]+))"[^>]*>/gi;
+ let m; const secs = [];
+ while ((m = secRe.exec(html)) !== null) secs.push({ id: m[1], start: m.index });
+ if (!secs.length) {
+ lines.push(` (движок math6: статических sec[id] нет; якоря строятся из window.M6.paras → id="sec-")`);
+ json.push({ book, chapter_slug: slug, subject, grade, engine: 'math6', note: 'paras in window.M6 config; anchors sec-' });
+ continue;
+ }
+ for (let i = 0; i < secs.length; i++) {
+ const seg = html.slice(secs[i].start, secs[i + 1] ? secs[i + 1].start : secs[i].start + 4000);
+ const numM = seg.match(/class="sec-num"[^>]*>([\s\S]*?)<\//i);
+ const hM = seg.match(/class="sec-h"[^>]*>([\s\S]*?)<\//i);
+ const paraId = secs[i].id.replace(/^sec-/, ''); // p10 | final3
+ const num = numM ? strip(numM[1]) : '';
+ const title = hM ? strip(hM[1]) : '';
+ lines.push(` ${secs[i].id.padEnd(12)} ${num ? '['+num+'] ' : ''}${title}`);
+ if (/^p\d+$/.test(paraId)) {
+ json.push({ book, chapter_slug: slug, subject, grade, para_id: paraId, num, title });
+ }
+ }
+}
+
+fs.mkdirSync(path.dirname(OUT_MD), { recursive: true });
+fs.writeFileSync(OUT_MD, lines.join('\n'), 'utf8');
+fs.writeFileSync(OUT_JSON, JSON.stringify(json, null, 2), 'utf8');
+console.log('Wrote', OUT_MD);
+console.log('Wrote', OUT_JSON, '(' + json.length + ' sections)');
diff --git a/backend/scripts/tag-exam-textbook.js b/backend/scripts/tag-exam-textbook.js
index e5288b6..9fe3157 100644
--- a/backend/scripts/tag-exam-textbook.js
+++ b/backend/scripts/tag-exam-textbook.js
@@ -41,7 +41,8 @@ if (examIdx !== -1 && args[examIdx + 1]) {
}
/* ── Taxonomy ─────────────────────────────────────────────────── */
-const taxonomy = require('./data/g9_textbook_sections.json');
+/* Generated by gen-exam-textbook-sections.js (re-run when textbook §s change). */
+const taxonomy = require('./exam-textbook-sections.json');
// Build lookup: book -> [ {chapter_slug, para_id, num, title} ]
// and flat: chapter_slug+para_id -> para number (integer)
@@ -349,8 +350,9 @@ const SUBTOPIC_RULES = {
// Scale/map → math-6-ch2
{ slug: 'math-6-ch2', paragraph: null,
kw: [/масштаб|карт[ае]\s+изображ/i] },
- // fallback: percentages (most common in word problems)
- { slug: 'math-6-ch2', paragraph: null, kw: [] },
+ // fallback: solving text problems via an equation (most generic g9 home,
+ // far better than defaulting every word problem to "percents")
+ { slug: 'algebra-7-ch3', paragraph: 16, kw: [] },
],
/* ── geom-triangles ──────────────────────────────────────── */
@@ -523,6 +525,24 @@ const SUBTOPIC_RULES = {
],
};
+/* Extract plain text of answer options (opts_json) so keyword scoring can see
+ the actual statement contents — crucial for theory-statements, whose question
+ text is just "Какое из утверждений неверно" with the substance in the options. */
+function optsText(task) {
+ if (!task.opts_json) return '';
+ try {
+ const o = JSON.parse(task.opts_json);
+ const arr = Array.isArray(o) ? o : (o && Array.isArray(o.options) ? o.options : []);
+ return arr.map(x => {
+ if (typeof x === 'string') return x;
+ // exam_tasks stores options as [label, html] pairs, e.g. ["а","$b^{-3}=...$"]
+ if (Array.isArray(x)) return x.map(String).join(' ');
+ if (x && typeof x === 'object') return x.html || x.text || x.label || '';
+ return '';
+ }).join(' ');
+ } catch { return ''; }
+}
+
/* ── Classifier ───────────────────────────────────────────── */
function classify(task) {
const subtopic = task.subtopic;
@@ -531,9 +551,15 @@ function classify(task) {
const rules = SUBTOPIC_RULES[subtopic];
if (!rules || !rules.length) return { slug: null, para: null };
- const txt = stripText(task.text_html);
+ const txt = stripText(task.text_html + ' ' + optsText(task));
- let bestScore = -1;
+ // Require at least one keyword match (score > 0) to override the fallback.
+ // Starting bestScore at 0 (NOT -1) means: when NOTHING matches, the explicit
+ // fallback (last entry) wins — not the first rule. Critical for subtopics like
+ // theory-statements / alg-word-problems where the question text often carries
+ // no distinctive keyword (otherwise every unmatched task collapsed onto the
+ // first rule — §15 progressions / math-6 percents).
+ let bestScore = 0;
let bestRule = rules[rules.length - 1]; // last = fallback
for (const rule of rules) {
@@ -548,18 +574,13 @@ function classify(task) {
}
}
- // If nothing matched (bestScore == -1 and bestRule is fallback), use first entry (primary)
- if (bestScore < 0) {
- bestRule = rules[rules.length - 1]; // explicit fallback
- }
-
return { slug: bestRule.slug, para: bestRule.paragraph };
}
/* ── Main ─────────────────────────────────────────────────── */
function main() {
const tasks = db.prepare(`
- SELECT id, variant, task_idx, task_type, subtopic, text_html
+ SELECT id, variant, task_idx, task_type, subtopic, text_html, opts_json
FROM exam_tasks
WHERE exam_key = ?
ORDER BY variant, task_idx
diff --git a/plans/exam-textbook-links/PLAN.md b/plans/exam-textbook-links/PLAN.md
new file mode 100644
index 0000000..b5e93da
--- /dev/null
+++ b/plans/exam-textbook-links/PLAN.md
@@ -0,0 +1,138 @@
+# План: точная привязка задач экзамена math9 к § учебников
+
+> Составлен Opus 2026-06-03 по итогам discovery. Исполнитель — Sonnet, по фазам, с верификацией.
+> Эталон §-таксономии: `plans/exam-textbook-links/taxonomy.md` (читаемая) и
+> `backend/scripts/data/g9_textbook_sections.json` (машиночитаемая: {chapter_slug, subject, grade, para_id, num, title}).
+
+## Контекст (проверено по коду/БД)
+
+- `exam_tasks`: 800 задач `math9`, у ВСЕХ заполнен `subtopic` (16 подтем) и `topic`.
+- Текущая связь — КОСВЕННАЯ и грубая: `exam_tasks.subtopic` → `exam_topics.slug` → один
+ `textbook_slug`+`textbook_paragraph` на всю подтему (миграция 028). Контроллер
+ `backend/src/routes/exam-prep.js` штампует `topic_ref` через `getTopicRefMap()`/`shapeTask()`.
+ Фронт `frontend/js/exam-prep/task-card.js:42` строит ссылку `/textbook/#sec-p`.
+- Покрытие сейчас: 546/800 → конкретный §, 158 → только хаб `algebra-9` (alg-numbers/arithmetic/
+ powers/polynomials/word-problems), 96 (theory-statements) → никуда.
+- Экзамен 9 кл. проверяет программу 5–9, а интерактивный учебник 9 кл. — только материал 9 года.
+ Учебники 5–11 все есть (см. dump в discovery). Для math9 релевантны 5–9.
+
+### Готчи нумерации § (ВАЖНО для классификатора и ссылок)
+- **algebra-7, algebra-9, geometry-7, geometry-9** — СКВОЗНАЯ нумерация `sec-pN` по всему учебнику,
+ но каждый § физически лежит в своём файле-главе. Ключ ссылки = (slug-главы, pN).
+- **geometry-8** — ПОГЛАВНАЯ нумерация (каждая глава заново `sec-p1`). Поэтому ОБЯЗАТЕЛЬНО
+ (slug-главы, pN-внутри-этого-файла). JSON-таксономия уже хранит правильную пару.
+- **algebra-8** — сквозная (`sec-p1..p18` по 3 главам).
+- **math-5 / math-6** — рендерятся движком `frontend/js/math6_engine.js`
+ (``, p.id из `window.M6.paras`). Статических `sec-pN` в файле нет.
+ Якорь = `sec-`. Если нужно вести в math-5/6 — para_id брать из конфига M6 главы.
+
+### КРИТИЧНО: deep-link сейчас НЕ РАБОТАЕТ
+- Статические страницы algebra/geometry (`algebra_9_ch3.html` и т.п.) подключают только
+ katex/api.js/xp.js — **`textbook-tracker.js` там НЕ подключён**. Их `init()` всегда
+ вызывает `goTo('p10')` и `location.hash` ИГНОРИРУЕТ. Используют `.psel-card[data-id]`.
+- `textbook-tracker.js` (есть на math-5/6) `handleHashNav()` матчит только `/^#(p\d+)$/`
+ (т.е. `#pN`, НЕ `#sec-pN`) и кликает `.para-pill[data-para]`.
+- exam-prep строит `#sec-pN`. → ссылка ведёт на главу, но НЕ открывает нужный §.
+- Сервер отдаёт `/textbook/:slug` обычным `sendFile` (инъекция только при `?embed=1`,
+ см. `backend/src/server.js:437`). Есть `_renderEmbed()` + `_embedCache`.
+
+## Цель
+Каждая из 800 задач math9 ведёт на наиболее подходящий § учебника 5–9, и клик реально открывает этот §.
+
+---
+
+## Фаза 1 — Починить навигацию deep-link (prerequisite; без неё связь невидима)
+Централизованный хелпер, внедряемый СЕРВЕРОМ в HTML учебника для всех режимов `/textbook/:slug`.
+- `backend/src/server.js`: обобщить выдачу — всегда читать файл и инжектить
+ `` перед `` (или перед `