29 Commits

Author SHA1 Message Date
Maxim Dolgolyov e9fe4dabb9 fix(stereo): прямой угол (90°) рисуется квадратиком, а не дугой
В инструменте «∠ рёбер» общий рисовальщик _drawAngleArc всегда чертил дугу,
включая случай 90° — должен быть квадратный маркер прямого угла.

- _drawAngleArc: при |angle−90|<0.5° рисует угловой «квадратик» (p1=center+
  n1·r, p3=center+(n1+n2)·r, p2=center+n2·r, r=radius·0.7) вместо дуги.
  Подпись «∠ABC = 90.0°» и лучи угла рисуются отдельно в обработчике —
  не затронуты. Для не-прямых углов поведение прежнее (дуга).

Верификация: node --check OK; headless-смоук 10/10 (90° → 3-точечный квадрат
с верной геометрией в любой плоскости; 89.6° в допуске → квадрат; 60/88/130°
→ дуга; полный поток _onEdgeAngleClick на угле куба → квадрат); эмодзи/eval/
new Function — 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 18:20:09 +03:00
Maxim Dolgolyov ce99c15895 feat(stereo): мастер-тумблер «Фигура» — скрыть тело с поля
Не было способа убрать само тело со сцены. Добавил тумблер «Фигура» в
начале секции «Отображение»: скрывает грани, рёбра, вершины и подписи тела,
оставляя сетку/оси и ВСЕ построения, точки, сечения и выделения — удобно
работать с конструкциями на «пустом» поле.

- StereoSim: флаг showFigure (деф. true) + toggleFigure(v) — переключает
  _figGroup.visible/_labelGroup.visible (флаг переживает _clearGroup, поэтому
  фигура остаётся скрытой и после перестроения при смене параметров). При
  смене типа фигуры (setFigure) тело снова показывается.
- Панель: st-toggle-row #stg-figure; диспетчер stereoToggleSt('figure');
  setStereoFigure возвращает тумблер в «вкл» для новой фигуры.

Верификация: node --check OK; headless-смоук 13/13 (деф. видна; скрытие
прячет fig+labels, но grid/construct/poly/point-группы остаются; перестроение
сохраняет скрытие; обратное включение; setFigure ре-показывает; dispose);
эмодзи/eval/new Function — 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 18:14:53 +03:00
Maxim Dolgolyov 1f461e96fd feat(stereo): выделение цветом — многоугольник по точкам (с палитрой)
Новый инструмент «Многоугольник по точкам» (секция «Выделение цветом»):
кликаешь точки/вершины по контуру → «Замкнуть» (или клик по первой точке)
→ область заливается полупрозрачным цветом + контур + вершины. Палитра из
6 цветов (свотчи), переключается. Можно выделить треугольник/грань/сечение
из выбранных точек, чтобы подсветить «фигуру по точкам».

- StereoSim: _polyMode/_polyPicks/_polyHighlights/_polyColor + _polyGroup;
  setPolyMode (взаимоисключение с другими инструментами), setPolyColor,
  closePoly (≥3 точек), removeLastPolyPick, clearPoly, _onPolyClick
  (авто-замыкание кликом по первой вершине), _rebuildPoly/_drawPolyHighlight/
  _drawPolyPreview (превью: пунктир + крупная 1-я точка-подсказка). Пикинг
  вершин/точек через _pickConstructPoint. Сброс в setFigure, очистка в dispose.
- Панель: секция «Выделение цветом» (кнопка, палитра .st-sw, Замкнуть/
  Отменить точку/Очистить, #poly-hint); glue stereoPolyMode/Color/Close/
  Undo/Clear; интеграция в _stereoDeactivateTools. CSS палитры в lab.css.

Верификация: node --check OK; headless-смоук 21/21 (режим+взаимоисключение,
пик→замыкание, дефолт/выбранный цвет, авто-замыкание по 1-й точке, требование
≥3, undo точки/выделения, clear, setFigure-сброс, dispose, счётчики
fill+контур+вершины); эмодзи/eval/new Function — 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 18:02:06 +03:00
Maxim Dolgolyov 5e6effa8cd feat(stereo): тумблер показа длин соединённых отрезков
В инструменте «Соединить» подпись длины у каждого отрезка рисовалась всегда.
Добавил переключатель «Длины отрезков» (секция «Инструменты»): прячет только
подписи длин, сами отрезки и точки остаются.

- StereoSim: флаг showConnectionLengths (деф. true), гард в
  _rebuildPointVisuals, метод toggleConnectionLengths(on). Предпочтение
  переживает смену фигуры (не сбрасывается в setFigure).
- Панель: st-toggle-row #stg-connlen + glue stereoToggleConnLen.

Верификация: node --check OK; headless-смоук 8/8 (деф. вкл, подпись
гейтится флагом, линия/маркеры сохраняются, предпочтение переживает
setFigure); эмодзи/eval/new Function — 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 17:52:26 +03:00
Maxim Dolgolyov 601f584181 feat(stereo): сворачиваемый аккордеон панели управления (UX)
Панель за фазы A–C разрослась до ~14 всегда-раскрытых секций (длинный
скролл, тяжело ориентироваться). Сделал её удобнее:

- _stereoInitPanel() (вызов из _openStereo, идемпотентно) оборачивает
  контролы каждой секции в .st-acc-body; заголовки .gp-section-title →
  кликабельные .st-acc-hdr с шевроном; состояние секций в localStorage.
- Тройку фигурных секций (Многогранники/Правильные/Тела вращения) слил в
  одну «Фигуры» (под-метки .st-sublabel). По умолчанию открыты «Фигуры» и
  «Параметры», остальное свёрнуто.
- Кнопки «Развернуть всё / Свернуть всё» (stereoAccAll), клавиатура
  (Enter/Space на заголовке), role=button/tabindex.
- Только раскладка: ни один контрол/обработчик не изменён (узлы лишь
  перемещены в тело секции). Затронуты stereo.js + lab.css.

Верификация: node --check OK; headless DOM-смоук (мини-DOM + реальный
stereo.js в vm) 22/22: 12 сворачиваемых секций, тройка фигур слита (2
под-метки внутри «Фигуры»), пары заголовок→тело, дефолт-открытие,
тоггл+персист, развернуть/свернуть всё, идемпотентная переинициализация,
ни одна строка контролов не потеряна. Эмодзи/eval/new Function — 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 17:48:08 +03:00
Maxim Dolgolyov 9547a20875 feat(stereo): B — умные точки (деление m:n, координаты, перетаскивание)
Фаза B раунда «Конструктор» (умные точки для построений).

B1 — деление отрезка m:n: задаёшь m,n, кликаешь 2 точки A,B → точка делит
AB как AM:MB = m:n (t=m/(m+n)), создаётся как точка-построение M,N,K…
B2 — точка по координатам: поля x/y/z + кнопка → addPointAt.
B3 — перетаскивание построенных точек мышью: drag в плоскости, обращённой
к камере (нормаль фиксируется на старте), приоритет над орбитой; снапшот
истории на старте → undo откатывает весь drag. Непараметрично: downstream-
объекты за перетаскиванием не следуют (параметрический граф — бэклог).

- StereoSim: setDivideMode/setDivideRatio (+ ветка в _onConstructClick),
  addPointAt; setDragPointMode/_pickCPointAt/_beginCPointDrag/_rayPlaneHit/
  _dragCPointWithRay/_dragCPointAt/_endCPointDrag; pointer-хендлеры
  (down=начать drag, move=тащить, up=завершить); сброс в setFigure;
  интеграция в _stereoDeactivateTools.
- Панель: блок «Точки» (кнопки Деление/Тащить, поля m:n, поля x,y,z +
  «Точка (x,y,z)»); glue stereoDivideMode/DivideRatio/AddCoordPoint/
  DragPointMode.

Верификация: node --check OK; headless-смоук 25/25 (деление 1:1/1:2/3:1,
координатная точка + отказ NaN, ray∩plane вкл. parallel/behind, drag begin→
move→end с проверкой позиции и снапшота истории + undo, взаимоисключение
режимов, setFigure-сброс, dispose); эмодзи/eval/new Function — 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 17:28:22 +03:00
Maxim Dolgolyov 24403718bf feat(stereo): C1+C3 — плоскость как сечение + «натуральная величина»
Фаза C раунда «Конструктор» (C2 покрыта Фазой A, C4 отложена).

C1 — любую построенную плоскость можно показать сечением тела: клик по
плоскости в дереве (нормальный режим) → setSectionPlane: заливка
многоугольника + подписи вершин K,L,M… + площадь и периметр в readout-
панели. Удаление плоскости / очистка / смена фигуры сбрасывают сечение.

C3 — «Натуральная величина» сечения (getTrueShape): многоугольник сечения
разворачивается в свою плоскость (ортонормированный базис от нормали) с
сохранением истинных длин → 2D-SVG мини-панель со штриховкой (pattern),
подписями вершин, длинами сторон и S/P. Появляется автоматически при
активном сечении.

- StereoSim: _sectionPlaneId, setSectionPlane, _activeSectionPolygon,
  _sectionVertexLabel, getTrueShape; _drawPlaneObject заливает+подписывает
  активное сечение; getReadout добавляет S/P; getConstructions отдаёт
  sectionId + per-plane section; pickConstructObject в нормальном режиме
  тогглит сечение по плоскости.
- Панель: контейнер #construct-trueshape + подсказка; glue
  _stereoUpdateTrueShape (SVG-рендер) вызывается из _stereoUpdateUI; строки
  плоскостей в дереве всегда кликабельны, тег «(сечение)».

Верификация: node --check OK; headless-смоук 26/26 (квадрат y=2: S=16,P=16;
readout/дерево/тоггл; true-shape длины K,L,M,N=4, площадь=16; сохранение
длин и площади для прямого И наклонного сечения; 2D-shoelace=S; удаление/
очистка/setFigure сбрасывают сечение; dispose); эмодзи/eval/new Function — 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 17:15:22 +03:00
Maxim Dolgolyov 9382b063aa feat(stereo): A3 — параллели/перпендикуляры + общий undo/redo построений
Фаза A3 раунда «Конструктор». Построения через точку, опираясь на объект:
- lpar: прямая ∥ выбранной прямой;
- lperp: прямая ⟂ выбранной плоскости (вдоль нормали);
- ppar: плоскость ∥ выбранной плоскости;
- pperp: плоскость ⟂ выбранной прямой (= плоскость по точке+нормали,
  через _createPlaneFromPointNormal — мост к Фазе C).
Поток: кнопка op → выбор опоры в дереве → клик точки.

Общий undo/redo конструкторного слоя: JSON-снапшоты _undoStack/_redoStack
(кап 60), хуки _pushHistory в create/remove/clear; Ctrl+Z / Ctrl+Shift+Z /
Ctrl+Y + кнопки «Отменить»/«Вернуть». Видимость объекта — не шаг истории.

- StereoSim: setRelMode/_pickRelRef/_onRelClick/_createPlaneFromPointNormal;
  _snapshot/_pushHistory/_restoreSnapshot/undo/redo/canUndo/canRedo;
  pickConstructObject диспатчит rel/intersect; getConstructions отдаёт
  relMode + selected по опоре; _lastConstructMsg → flash в подсказку.
  Сброс rel/истории в setFigure, очистка в clearConstructions.
- Панель: 4 кнопки (∥/⟂ прямая/плоск.) + «Отменить»/«Вернуть»; интеграция в
  _stereoDeactivateTools; glue stereoRelMode/HistUndo/HistRedo; дерево —
  строки выбираемы и в rel-режиме.

Верификация: node --check OK; headless-смоук 30/30 (4 rel-операции с
проверкой параллельности направлений/нормалей, гард типа опоры, undo/redo
одиночный/многошаговый/redo-сброс/clear-undoable/vis-не-шаг/кап, setFigure-
сброс истории, dispose); эмодзи/eval/new Function — 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 17:07:43 +03:00
Maxim Dolgolyov abd1af2653 feat(stereo): A2 — пересечения построений + интерактивное дерево объектов
Фаза A2 раунда «Конструктор». Пересечения как list-based операция:
- прямая ∩ плоскость → точка (_cpoints, имена M,N,K…);
- плоскость ∩ плоскость → прямая;
- прямая ∩ прямая → точка либо «скрещиваются»/«параллельны».
Точки-пересечения пикабельны — по ним строятся новые прямые/плоскости.

- StereoSim: setIntersectMode/pickConstructObject (выбор 2 объектов),
  _computeIntersection + _intersectLinePlane/_intersectPlanePlane/
  _intersectLineLine, _createCPoint/_drawCPointObject/_cpointLabel,
  removeConstruction(id)/toggleConstructionVis(id), getConstructions
  переписан в дерево (id/type/hidden/selected/info), _pickConstructPoint
  теперь учитывает точки-пересечения. Сброс в setFigure, очистка/clear.
- Панель: кнопка «Пересечение»; список — интерактивные строки (выбор для
  пересечения, глаз=видимость, ×=удаление) через glue stereoIntersectMode/
  ConstructSelect/ConstructVis/ConstructDelete; интеграция в _stereoDeactivateTools.

Верификация: node --check OK; headless-смоук 34/34 (точная геометрия
line∩plane / plane∩plane / line∩line, параллельные/скрещ. без объекта,
list-pick поток, гард точки, дерево, видимость/удаление/remove-last,
setFigure-сброс, dispose); эмодзи/eval/new Function — 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 16:59:20 +03:00
Maxim Dolgolyov 53ac45bccd feat(stereo): конструкторное ядро A1 — прямые и плоскости как объекты
Фаза A раунда «Конструктор» (под ученика-самоучку). Прямая по 2 точкам
(имена a,b,c…) и плоскость по 3 точкам (имена α,β,γ…) как именованные
объекты сцены. Плоскость рисует полупрозрачный квад + пунктирную рамку +
сечение тела этой плоскостью (через _sliceByPlane) — сразу осмысленна.

- StereoSim: _lines/_planes (сериализуемые {x,y,z}), _constructGroup,
  setLineMode/setPlaneMode, _onConstructClick, _createLine/_createPlane,
  _rebuildConstructions/_drawLineObject/_drawPlaneObject, removeLast/clear,
  getConstructions (с уравнением плоскости). Сброс в setFigure, очистка в
  dispose, перерисовка подписей в toggleLabels, счётчик в info().
- Панель «Построения» в labs-bodies.html + glue (stereoLineMode/PlaneMode/
  ConstructUndo/Clear, _stereoUpdateConstructList); интеграция в
  _stereoDeactivateTools и _stereoUpdateUI.
- План: Фазы A и C в plans/STEREO_3D_IMPROVEMENT.md.

Верификация: node --check OK; headless-смоук 35/35 (создание/имена/нормаль/
коллинеарность/rebuild/summary/remove-last/clear/click-путь/setFigure-сброс/
dispose); эмодзи/eval/new Function — 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 16:27:27 +03:00
Maxim Dolgolyov 477d47e9e6 feat(admin): тумблер фичи для «Квантик» (паритет с другими играми)
У Квантика не было фиче-флага — его нельзя было выключить, и он всегда висел
в сайдбаре (даже у учеников без класса). Добавлено по образцу остальных игр:
- adminController.updateFeatures: 'quantik' в whitelist (PATCH принимает флаг).
- games.js: пункт «Квантик: Законы Мира» в GAME_FEATURES и FS_FEATURES
  (тумблер в админке → Игры; пишет feature_quantik_enabled).
- api.js hideDisabledFeatures: quantik -> ['/quantik','/quantik.html'] (скрытие
  из сайдбара при выключении) + '/quantik' в classOnlyHrefs/classOnlyPaths
  (скрыт у учеников без класса, как прочие игры).

Миграция не нужна: флаг «неявно включён», пока админ не выключит (features[key]
!== false => включено). Требует Ctrl+F5 (фронт).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 16:00:23 +03:00
Maxim Dolgolyov 56fc15418e feat(sidebar): скрывать ссылки exam-prep при выключенном/недоступном треке 2026-06-15 14:19:38 +03:00
Maxim Dolgolyov 6fed18f819 feat(admin): тумблер вкл/выкл для экзамен-модулей (exam-prep)
Не было UI для управления exam_tracks.enabled (только флаг в БД, ставился
миграцией). Добавлена админ-секция «Экзамен-модули»:
- backend exam-prep.js: GET /admin/tracks (все треки, вкл. выключенные, + число
  заданий) и PATCH /admin/track (exam_key, enabled), обе requireRole('admin').
  Пути без :examKey, чтобы не задеть гейт content_access.
- frontend: секция sections/exams.js (список треков + переключатель enabled),
  вкладка в admin.html (admin-only через ADMIN_ONLY_TABS, locked для не-админов),
  регистрация в admin.js (ROUTE_TO_SECTION).

Выключенный трек скрыт у учеников и пропадает из каталога прав доступа (тот
берёт exam_tracks WHERE enabled=1). Доступ ученикам по-прежнему в «Доступ · контент».
Требует перезапуска бэкенда + Ctrl+F5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 12:32:01 +03:00
Maxim Dolgolyov 1cf8083c0e docs(ct-math): IDEAS.md - идеи по улучшению модуля по всем направлениям 2026-06-15 12:15:04 +03:00
Maxim Dolgolyov 8091b48e1c fix(ct-math): практика возвращала меньше count + перенос заголовков в навигации урока
1) exam-prep practice (strategy=random) возвращал около 0.6 от count: функция
   distributeByDifficulty раскладывает count по 5 уровням сложности, а у трека
   ctmath задания только уровней 1-3 (уровни 4-5 пустые) -> часть выборки терялась
   (20 -> 12, 15 -> 10, 10 -> 6). В pickRandomByDifficulty добавлен добор до count
   из доступных уровней. Трек math9 не затронут (там добор не требуется).
2) lesson.html: .lesson-nav-btn-title был inline-span, поэтому max-width и ellipsis
   игнорировались и длинные заголовки вылезали за кнопку. Добавлен display:block.

Бэкенд-правка требует перезапуска сервера; фронт-правка видна после Ctrl+F5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 12:09:50 +03:00
Maxim Dolgolyov 4b23d768f2 fix(ct-math): литеральные угловые скобки в формулах уроков ломали KaTeX
Блок formula вставляет tex в HTML без экранирования, поэтому литеральная
"меньше"-скобка (напр. в "0 le r lt d") принималась браузером за HTML-тег и
формула не рендерилась (показывался сырой $$...$$). Заменено на \lt и \gt
(KaTeX рендерит их как отношения).

- seed_ctmath_lessons_rest.js: исправлены 4 формулы в исходнике (числа,
  модуль, показ/лог равносильности, производная-монотонность).
- fix_ctmath_formula_lt.js: фикс уже залитых блоков курса 13 (dry/--apply).
  Флешкарты не затронуты (mathHtmlFC через textContent экранирует сам).

Запись (UPDATE 4 блоков) запускает пользователь.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 12:05:47 +03:00
Maxim Dolgolyov a982628d04 feat(ct-math): уроки всех остальных блоков (48-55) + 4 колоды флешкарт формул
- seed_ctmath_lessons_rest.js — 8 уроков по PLAN: числа, преобразования,
  уравнения (квадратные/рацион/модуль + показ/лог/иррац+рационализация),
  функции+производная, прогрессии/текстовые, планиметрия, параметры.
  Курс 13 теперь покрывает все 9 секций (15 уроков, lessons.id=41-55).
- seed_ctmath_flashcards.js — 4 колоды формул (тригонометрия/стереометрия/
  логарифмы-степени/производная, 49 карт, flashcard_decks.id=11-14, владелец admin).
- Форматы блоков/карт сверены с рендером (lesson.html $…$/$$; flashcards $…$/\(\)/\[\]).
  Применены seed-скриптами; JSON валиден (0 битых).
- README: статус контента.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 11:48:39 +03:00
Maxim Dolgolyov 623fbde38b feat(ct-math): уроки стереометрии (44-47) + скрипт мини-фикса 866/1248
- backend/scripts/seed_ctmath_lessons_stereo.js — 4 урока блока «Стереометрия»
  по PILOT_STEREOMETRY (расположение/сечения, многогранники, тела вращения,
  координатный метод В20) в курс 13; применён (lessons.id=44-47, 60 блоков).
- backend/scripts/fix_ctmath_misc.js — точечный фикс exam_tasks id=866
  (варианты-прямые в норму) и id=1248 (битый источник → long); dry/--apply,
  идемпотентен. Запись блокируется авто-режимом — запускает пользователь.
- README: статус (уроки стерео, сайдбар, остаток).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 11:36:56 +03:00
Maxim Dolgolyov 1bc0cc247a docs(ct-math): постфикс инлайн-вариантов применён (213 задач, осталось ~3) 2026-06-15 11:11:57 +03:00
Maxim Dolgolyov 9b1abb83f8 fix(ct-math): варианты ответа из текста → нормальный opts_json (mc ctmath)
У части mc-задач ЦТ (формат РИКЗ «укажите номер») список ответов был вшит
в текст («1) 44; 2) 22; …»), а opts содержали лишь цифры-указатели — рисовалось
«а) 1, б) 2…» + значения строкой. Скрипт fix_ctmath_inline_opts.js вытаскивает
список из текста в opts_json (метка=цифра, текст=значение), пересчитывает answer,
очищает текст. Последовательный парсер сохраняет ';' внутри значений (интервалы).
Dry: 281 кандидат → 213 чинятся чисто, 68 нестандартных пропущены (без порчи).

Запись (UPDATE 213) — запускает пользователь (--apply), как и прочие записи в БД.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 11:06:42 +03:00
Maxim Dolgolyov c79effa16a feat(ct-math): пункт сайдбара «Подготовка к ЦЭ/ЦТ» → /exam-prep/ctmath
Навигация exam-prep не динамическая — пункты прописываются вручную. Добавил
ссылку на модуль ctmath рядом с «Экзамен 9» (группа «Контент»). Поэтому ранее
модуль не появлялся в панели, хотя открывался по прямому адресу.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 08:32:52 +03:00
Maxim Dolgolyov 3a20ac8a6e docs(ct-math): модуль ctmath поднят — 723 задания в exam_tasks (/exam-prep/ctmath)
Миграция 077 применена (пользователем вручную) + конвертер залил 723 задания
ЦТ-11 из банка questions в exam_tasks (exam_key='ctmath'): 525 mc + 191 open +
7 long, дерево тем 41 (9+32), variants_count=15. Проверка: осиротевших
subtopic 0, неконвертированных делимитеров 0. Модуль на /exam-prep/ctmath.

- BUILD_ON_QUESTIONS.md §0a / README: статус «применено», что осталось
  (content_access, сайдбар, фикс id=1248).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 08:27:08 +03:00
Maxim Dolgolyov fd26efca53 feat(ct-math): конвертер questions→exam_tasks для отдельного модуля ctmath (dry-готов)
- backend/scripts/seed_ctmath_exam_tasks.js — переносит размеченные вопросы
  ЦТ-11 из банка questions в exam_tasks (exam_key='ctmath') для отдельного
  модуля exam-prep. Dry по умолчанию, запись только с --apply.
  Правила сверены с exam-prep: MC-метки кириллица а..д (answer=метка);
  open числовой/дробь/пара иначе long; делимитеры \( \)→$, \[ \]→$$;
  subtopic=slug из 077; variant=год; multi/multiple пропуск.
  Dry-run: 733 вопроса → 723 (525 mc + 191 open + 7 long), выборка корректна.
- BUILD_ON_QUESTIONS.md: решение «ЦТ = отдельный модуль» + план + dry-результат.

Запись в БД (применение 077 + вставка 723) — ожидает явной санкции пользователя.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 07:56:43 +03:00
Maxim Dolgolyov 31719b2e79 feat(ct-math): уроки блока «Тригонометрия» (3 урока в курсе ЦЭ/ЦТ)
- backend/scripts/seed_ctmath_lessons_trig.js — идемпотентный seed 3 уроков по
  PILOT_TRIGONOMETRY в секцию «Тригонометрия» курса 13:
  круг и значения (lessons.id=41, 18 блоков, А3), тождества и формулы (id=42,
  19 блоков, А8/В4), уравнения и отбор корней (id=43, 15 блоков, В15).
  Форматы блоков сверены с рендером frontend/lesson.html (heading/text/formula/
  callout/sim trigcircle/flashcard/quiz/matching/ordering/accordion/table;
  math $…$/$$…$$; data JSON валиден). Уроки — в DRAFT-курсе (ученикам не видны).
- BUILD_ON_QUESTIONS.md / README: статус (блок «Тригонометрия» готов).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 07:41:15 +03:00
Maxim Dolgolyov 228bd885ed feat(ct-math): диагностический тест из реальных вопросов банка (tests.id=164)
- backend/scripts/seed_ctmath_diagnostic.js — идемпотентный сбор ОДНОГО test
  «Диагностика ЦЭ/ЦТ — Математика» из размеченных вопросов ЦТ-11 (в осн. 2024):
  5 single (базовые) + 10 fill-blank (средние/сложные), по 1 на ключевую тему.
  Новых вопросов не авторит. Применён: test id=164, 15 вопросов, лимит 40 мин.
  Выдать = assignment с test_id=164.
- BUILD_ON_QUESTIONS.md / README: отметка о готовой диагностике, статус.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 22:16:27 +03:00
Maxim Dolgolyov c3816baf99 feat(ct-math): каркас курса ЦЭ/ЦТ на банке questions (темы + draft-курс + секции)
- backend/scripts/seed_ctmath_course.js — идемпотентный аддитивный seed:
  +6 тем (Преобразование выражений/Модуль/Иррациональные ур./Показательные ур./
  Производная/Параметры), DRAFT-курс «ЦЭ/ЦТ — Математика» + 9 секций.
  Применён на живой БД: course id=13 (is_published=0), topics 72-77, sections 27-35.
  Существующие данные не тронуты; повторный запуск ничего не дублирует.
- BUILD_ON_QUESTIONS.md: уточнения инспекции банка (year=2025 = «Экзамен 9»,
  без тем; реальный ЦТ-11 = ~733 размеч., Часть B = fill-blank → гоча mode='ct')
  + блок «Состояние реализации».
- README: статус каркаса.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 22:10:22 +03:00
Maxim Dolgolyov 055a6cd1a4 docs(ct-math): пивот плана на существующий банк questions (1753 задания ЦЭ/ЦТ)
Контент ЦЭ/ЦТ по математике уже в БД (questions, subject_id=3, 1753 задания
2011–2025, seed_math_ct*.js) — курс строим на нём через tests/assignments
(готовый mode='ct') и courses, а не через exam-prep/exam_tasks.

- plans/ct-math/BUILD_ON_QUESTIONS.md — новый основной тех-документ: схема
  questions/topics/tests/assignments, режимы ct/topic, таксономия и её доведение,
  каркас курса, диагностика из реальных вопросов, прогресс, порядок работ
- примечания-пивот в PLAN (§6/§8), TOPICS_SEED, DIGITIZATION_SPEC (помечены
  вторичными: exam-prep — опция, оцифровка уже сделана), пилотах, README
- difficulty приведён к шкале банка 1–3

Миграция 077 оставлена как опция exam-prep, в БД не применяется.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 21:56:33 +03:00
Maxim Dolgolyov 7eb6cb2da0 docs(ct-math): план подготовки к ЦЭ/ЦТ по математике + миграция дерева тем
- plans/ct-math: модульная программа (карта теста А1–А10/В1–В20, 9 блоков
  и ~32 модуля, 3 уровня, маппинг на exam-prep платформы), 2 пилота
  (тригонометрия, стереометрия), seed дерева тем, спецификация оцифровки
  заданий РТ/ЦТ, инвентарь материалов
- backend: миграция 077 — трек ctmath + exam_topics (9 разделов, 32 подтемы),
  валидирована in-memory node:sqlite; на живую БД НЕ применялась

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 21:26:43 +03:00
Maxim Dolgolyov c9a00d105e @
merge: SimForge + Квантик — Законы Мира → master

Вливает конструктор симуляций (SimForge) и игру «Квантик: Законы Мира»
(фазы 0–5) в master. master был прямым предком feature/sim-builder —
мерж чистый, без конфликтов.
@
2026-06-14 20:21:19 +03:00
33 changed files with 4253 additions and 20 deletions
+29
View File
@@ -0,0 +1,29 @@
'use strict';
/*
* Фикс: блок formula вставляет tex в HTML БЕЗ экранирования ($$...$$), поэтому
* литеральные '<' / '>' в формуле браузер принимает за HTML-тег → KaTeX не рендерит.
* Заменяем литеральные '<' → '\lt', '>' → '\gt' в tex всех formula-блоков курса 13
* (KaTeX их рендерит как отношения). Идемпотентно. dry по умолчанию, запись --apply.
* node backend/scripts/fix_ctmath_formula_lt.js [--apply]
*/
const db = require('../src/db/db');
const APPLY = process.argv.includes('--apply');
const rows = db.prepare(`SELECT lb.id, lb.lesson_id, lb.data FROM lesson_blocks lb
JOIN lessons l ON l.id=lb.lesson_id WHERE l.course_id=13 AND lb.type='formula'`).all();
const upd = db.prepare('UPDATE lesson_blocks SET data=? WHERE id=?');
let changed = 0;
for (const r of rows) {
let d; try { d = JSON.parse(r.data); } catch { continue; }
if (!d.tex || !/[<>]/.test(d.tex)) continue;
const before = d.tex;
d.tex = d.tex.replace(/</g, '\\lt ').replace(/>/g, '\\gt ');
changed++;
console.log(`block ${r.id} (lesson ${r.lesson_id}):`);
console.log(' было:', before);
console.log(' стало:', d.tex);
if (APPLY) upd.run(JSON.stringify(d), r.id);
}
console.log(`\n${APPLY ? 'Обновлено' : '(dry) к обновлению'}: ${changed} формул.`);
if (!APPLY) console.log('Запись: --apply');
+85
View File
@@ -0,0 +1,85 @@
'use strict';
/*
* Фикс mc-задач ctmath, где варианты ответа вшиты в текст («1) 44; 2) 22; …»),
* а opts_json содержит лишь цифры-указатели. Вытаскивает список из текста в
* нормальный opts_json (метка=цифра, текст=значение), пересчитывает answer,
* очищает текст. Только для чисто распознанных случаев (иначе пропуск).
* node backend/scripts/fix_ctmath_inline_opts.js # dry: статистика+выборка
* node backend/scripts/fix_ctmath_inline_opts.js --apply # запись (UPDATE)
*/
const db = require('../src/db/db');
const APPLY = process.argv.includes('--apply');
// Разбор инлайн-списка "1) v1; 2) v2; … N) vN."
// Последовательный: режем значение только по " ; (n+1)) " следующего номера,
// поэтому ';' внутри значений (интервалы вида (-6;9)) сохраняются.
function parseInline(text) {
const m1 = text.match(/(^|[\s:>(])1\)\s/);
if (!m1) return null;
const start = m1.index + m1[1].length; // позиция "1)"
const stem = text.slice(0, start).replace(/[\s:]+$/, '').trim();
if (!stem) return null;
let rest = text.slice(start);
const h1 = /^1\)\s*/;
if (!h1.test(rest)) return null;
rest = rest.replace(h1, ''); // "1)" снимаем один раз
const pairs = [];
let n = 1;
while (true) {
const nextRe = new RegExp('\\s*;?\\s*' + (n + 1) + '\\)\\s');
const nm = rest.match(nextRe);
let val;
if (nm) { val = rest.slice(0, nm.index); rest = rest.slice(nm.index + nm[0].length); }
else { val = rest; rest = ''; } // последний пункт
val = val.replace(/[;.\s]+$/, '').trim();
if (!val) return null;
pairs.push([String(n), val]);
if (!nm) break;
n++;
}
if (pairs.length < 2) return null;
return { stem, pairs };
}
const rows = db.prepare("SELECT id, text_html, opts_json, answer FROM exam_tasks WHERE exam_key='ctmath' AND task_type='mc'").all();
const stat = { total: rows.length, candidate: 0, fixed: 0, skip_notdigit: 0, skip_parse: 0, skip_count: 0, skip_answer: 0 };
const updates = [];
for (const r of rows) {
let opts; try { opts = JSON.parse(r.opts_json); } catch { continue; }
const texts = opts.map(p => String(p[1]).replace(/\$/g, '').trim());
const isDigitPtr = texts.length >= 2 && texts.every(x => /^[1-9][0-9]?$/.test(x));
if (!isDigitPtr) { stat.skip_notdigit++; continue; }
stat.candidate++;
const parsed = parseInline(r.text_html);
if (!parsed) { stat.skip_parse++; continue; }
if (parsed.pairs.length !== opts.length) { stat.skip_count++; continue; }
// correctDigit = указатель, на который ссылается текущий answer
const ai = opts.findIndex(p => String(p[0]).toLowerCase() === String(r.answer).toLowerCase());
const correctDigit = ai >= 0 ? String(opts[ai][1]).replace(/\$/g, '').trim() : null;
if (!correctDigit || !/^[1-9][0-9]?$/.test(correctDigit) || Number(correctDigit) > parsed.pairs.length) { stat.skip_answer++; continue; }
const newOpts = JSON.stringify(parsed.pairs); // [["1","44"],...]
updates.push({ id: r.id, text: parsed.stem, opts: newOpts, answer: correctDigit, _old: r.text_html, _newpairs: parsed.pairs });
stat.fixed++;
}
console.log(APPLY ? '[APPLY]' : '[DRY-RUN]', 'mc всего', stat.total);
console.log('Статистика:', JSON.stringify(stat));
console.log('\n— Выборка (3) —');
for (const u of updates.slice(0, 3)) {
console.log(`\n id=${u.id}`);
console.log(' было text:', u._old.replace(/\s+/g, ' ').slice(0, 120));
console.log(' стало text:', u.text.replace(/\s+/g, ' ').slice(0, 90));
console.log(' стало opts:', u.opts.slice(0, 160), '| answer:', u.answer);
}
if (!APPLY) { console.log('\nDRY-RUN: запись НЕ выполнялась. Запись: --apply'); process.exit(0); }
const upd = db.prepare('UPDATE exam_tasks SET text_html=@text, opts_json=@opts, answer=@answer WHERE id=@id');
let n = 0;
for (const u of updates) { upd.run({ id: u.id, text: u.text, opts: u.opts, answer: u.answer }); n++; }
console.log(`\nОбновлено ${n} задач.`);
+45
View File
@@ -0,0 +1,45 @@
'use strict';
/*
* Точечная полировка 2 mc-задач ctmath:
* - id=866: варианты-прямые вшиты в середину текста, opts = цифры-указатели →
* нормальный opts_json + чистый текст (answer сохраняем = 4).
* - id=1248: битый источник (нет верного варианта, опции не сходятся) → 'long'.
* Идемпотентно (проверяет текущее состояние). dry по умолчанию, запись --apply.
*/
const db = require('../src/db/db');
const APPLY = process.argv.includes('--apply');
const t866 = db.prepare('SELECT id,task_type,answer,opts_json FROM exam_tasks WHERE id=866').get();
const t1248 = db.prepare('SELECT id,task_type FROM exam_tasks WHERE id=1248').get();
const plan = [];
if (t866 && t866.task_type === 'mc') {
// opts уже нормальные? (значения не цифры-указатели)
let o = []; try { o = JSON.parse(t866.opts_json); } catch {}
const isDigit = o.length && o.every(p => /^[1-9]$/.test(String(p[1]).trim()));
if (isDigit) {
plan.push({
id: 866,
set: {
text_html: 'A16. Какая из прямых пересекает график функции $y=x^4-3x^2+11x$ в 11 добавочных точках?',
opts_json: JSON.stringify([['1', '$y=-3$'], ['2', '$y=-1{,}5$'], ['3', '$y=0$'], ['4', '$y=4k$'], ['5', '$y=2$']]),
answer: '4',
},
});
} else console.log('id=866 уже не цифровой — пропуск');
} else console.log('id=866 нет или уже не mc — пропуск');
if (t1248 && t1248.task_type === 'mc') {
plan.push({ id: 1248, set: { task_type: 'long', answer: null } });
} else console.log('id=1248 нет или уже не mc — пропуск');
console.log(APPLY ? '[APPLY]' : '[DRY-RUN]', 'к изменению:', plan.map(p => p.id).join(', ') || '(нет)');
for (const p of plan) console.log(' id', p.id, '→', JSON.stringify(p.set).slice(0, 160));
if (!APPLY) { console.log('DRY-RUN: запись НЕ выполнялась. Запись: --apply'); process.exit(0); }
for (const p of plan) {
const cols = Object.keys(p.set);
const sql = `UPDATE exam_tasks SET ${cols.map(c => c + '=@' + c).join(', ')} WHERE id=@id`;
db.prepare(sql).run({ ...p.set, id: p.id });
}
console.log('Обновлено:', plan.length);
+93
View File
@@ -0,0 +1,93 @@
'use strict';
/*
* Каркас курса «ЦЭ/ЦТ — Математика» на существующем банке questions.
* План: plans/ct-math/ (BUILD_ON_QUESTIONS.md).
* ИДЕМПОТЕНТЕН и АДДИТИВЕН: добавляет недостающие темы (topics),
* создаёт DRAFT-курс (is_published=0) + 9 секций. Существующие данные не трогает.
* Запуск: node backend/scripts/seed_ctmath_course.js (применить)
* node backend/scripts/seed_ctmath_course.js --dry (только показать план)
*/
const db = require('../src/db/db');
const DRY = process.argv.includes('--dry');
const MATH_ID = 3;
// 1) Недостающие темы под модульную карту (см. BUILD_ON_QUESTIONS §3)
const NEW_TOPICS = [
'Преобразование выражений',
'Модуль',
'Иррациональные уравнения',
'Показательные уравнения',
'Производная',
'Параметры',
];
// 2) Секции курса = 9 блоков (PLAN §3)
const SECTIONS = [
'Числа и вычисления',
'Алгебраические преобразования',
'Уравнения и неравенства',
'Функции и производная',
'Тригонометрия',
'Прогрессии и текстовые задачи',
'Планиметрия',
'Стереометрия',
'Продвинутое и комбинированное',
];
const COURSE_TITLE = 'ЦЭ/ЦТ — Математика';
const COURSE_DESC = 'Подготовка к ЦЭ/ЦТ по математике: 30 заданий (часть А — А1–А10, часть В — В1–В20). Теория по темам, тренажёр на банке заданий прошлых лет, карточки формул, пробные варианты.';
function topicExists(name) {
return db.prepare('SELECT id FROM topics WHERE subject_id=? AND LOWER(name)=LOWER(?)').get(MATH_ID, name);
}
function adminId() {
const u = db.prepare("SELECT id FROM users WHERE role='admin' ORDER BY id LIMIT 1").get()
|| db.prepare('SELECT id FROM users ORDER BY id LIMIT 1').get();
return u && u.id;
}
let addedTopics = 0, skippedTopics = 0;
console.log(DRY ? '[DRY-RUN] план изменений:' : '[APPLY] вношу изменения:');
console.log('\n— Темы (topics) —');
for (const name of NEW_TOPICS) {
const ex = topicExists(name);
if (ex) { console.log(` есть: ${name} (id ${ex.id})`); skippedTopics++; continue; }
if (DRY) { console.log(` + добавить: ${name}`); addedTopics++; continue; }
const id = db.prepare('INSERT INTO topics (subject_id,name) VALUES (?,?)').run(MATH_ID, name).lastInsertRowid;
console.log(` + добавлено: ${name} (id ${id})`);
addedTopics++;
}
console.log('\n— Курс (courses) —');
let course = db.prepare("SELECT id,is_published FROM courses WHERE subject_slug='math' AND title=?").get(COURSE_TITLE);
let courseId;
if (course) {
courseId = course.id;
console.log(` есть курс «${COURSE_TITLE}» (id ${courseId}, ${course.is_published ? 'published' : 'draft'})`);
} else if (DRY) {
console.log(` + создать DRAFT-курс «${COURSE_TITLE}» (created_by=${adminId()})`);
} else {
const by = adminId();
// cover_emoji не указываем — применится дефолт схемы; в коде эмодзи не вводим
courseId = db.prepare(
'INSERT INTO courses (subject_slug,title,description,is_published,created_by) VALUES (?,?,?,0,?)'
).run('math', COURSE_TITLE, COURSE_DESC, by).lastInsertRowid;
console.log(` + создан DRAFT-курс «${COURSE_TITLE}» (id ${courseId}, created_by=${by})`);
}
console.log('\n— Секции (course_sections) —');
if (!courseId && DRY) {
SECTIONS.forEach((t, i) => console.log(` + секция [${i + 1}] ${t}`));
} else if (courseId) {
SECTIONS.forEach((title, i) => {
const ex = db.prepare('SELECT id FROM course_sections WHERE course_id=? AND title=?').get(courseId, title);
if (ex) { console.log(` есть: [${i + 1}] ${title} (id ${ex.id})`); return; }
if (DRY) { console.log(` + секция [${i + 1}] ${title}`); return; }
const id = db.prepare('INSERT INTO course_sections (course_id,title,order_index) VALUES (?,?,?)').run(courseId, title, i + 1).lastInsertRowid;
console.log(` + секция [${i + 1}] ${title} (id ${id})`);
});
}
console.log(`\nИтог: темы +${addedTopics} (есть ${skippedTopics}); курс id=${courseId || '(dry)'}; секций ${SECTIONS.length}.`);
console.log(DRY ? 'DRY-RUN: ничего не записано.' : 'Готово. Курс создан как ЧЕРНОВИК (is_published=0) — ученикам не виден до публикации.');
+93
View File
@@ -0,0 +1,93 @@
'use strict';
/*
* Входная диагностика для курса «ЦЭ/ЦТ — Математика».
* Собирает ОДИН test из РЕАЛЬНЫХ размеченных вопросов ЦТ-11 (banks 20112024):
* по 1 заданию на ключевую тему, смесь уровней (single 🟢 → fill-blank 🔴).
* Новых вопросов НЕ авторит — только группирует существующие.
* ИДЕМПОТЕНТЕН: если test с таким title есть — не дублирует.
* Запуск: node backend/scripts/seed_ctmath_diagnostic.js (применить)
* node backend/scripts/seed_ctmath_diagnostic.js --dry (показать выбор)
*/
const db = require('../src/db/db');
const DRY = process.argv.includes('--dry');
const MATH_ID = 3;
const TITLE = 'Диагностика ЦЭ/ЦТ — Математика';
const DESC = 'Входная диагностика: задания по ключевым темам (от базовых до сложных) для определения уровня и приоритетных тем подготовки к ЦЭ/ЦТ.';
// Слоты: тема (по имени) + предпочтительный тип + уровень-зонд.
// Исключаем набор year=2025 («Экзамен 9»): берём только размеченные ЦТ-11 (year<=2024).
const SLOTS = [
['Теория чисел', 'single', 'base'],
['Арифметика и степени', 'single', 'base'],
['Квадратные уравнения', 'single', 'base'],
['Тригонометрия', 'single', 'base'],
['Числовые промежутки', 'single', 'base'],
['Словесные задачи', 'fill-blank', 'mid'],
['Прогрессии', 'fill-blank', 'mid'],
['Функции', 'fill-blank', 'mid'],
['Геометрия', 'fill-blank', 'mid'],
['Окружность и круг', 'single', 'mid'],
['Стереометрия', 'fill-blank', 'mid'],
['Логарифмы', 'fill-blank', 'hard'],
['Неравенства', 'fill-blank', 'hard'],
['Уравнения', 'fill-blank', 'hard'],
['Показательные неравенства','fill-blank', 'hard'],
];
function topicId(name) {
const r = db.prepare('SELECT id FROM topics WHERE subject_id=? AND LOWER(name)=LOWER(?)').get(MATH_ID, name);
return r && r.id;
}
function adminId() {
const u = db.prepare("SELECT id FROM users WHERE role='admin' ORDER BY id LIMIT 1").get()
|| db.prepare('SELECT id FROM users ORDER BY id LIMIT 1').get();
return u && u.id;
}
// Кандидаты по теме: сперва предпочт. тип, потом любой; только размеченные ЦТ-11 (year<=2024 или not null),
// исключая набор «Экзамен 9» (source_type='экзамен 9'); 2024 в приоритете, затем свежие.
function candidates(tid, type) {
const order = "ORDER BY (year=2024) DESC, year DESC, id";
const base = `SELECT id, type, year, substr(text,1,70) AS t FROM questions
WHERE subject_id=${MATH_ID} AND topic_id=${tid}
AND (source_type IS NULL OR source_type <> 'экзамен 9')`;
const pref = db.prepare(`${base} AND type=? ${order} LIMIT 8`).all(type);
const any = db.prepare(`${base} ${order} LIMIT 8`).all();
// предпочт. тип впереди, затем остальные (для фолбэка)
const seen = new Set(pref.map(r => r.id));
return [...pref, ...any.filter(r => !seen.has(r.id))];
}
const used = new Set();
const picks = [];
for (const [name, type, level] of SLOTS) {
const tid = topicId(name);
if (!tid) { console.log(` [skip] нет темы: ${name}`); continue; }
const cand = candidates(tid, type).find(r => !used.has(r.id));
if (!cand) { console.log(` [skip] нет вопросов: ${name}`); continue; }
used.add(cand.id);
picks.push({ name, level, ...cand });
}
console.log(DRY ? '[DRY-RUN] выбранные вопросы диагностики:' : '[APPLY] диагностика:');
const mark = { base: 'базовый', mid: 'средний', hard: 'сложный' };
picks.forEach((p, i) => console.log(
` ${String(i + 1).padStart(2)}. [${mark[p.level]}] ${p.name} | qid ${p.id} (${p.type}, ${p.year || '—'}) — ${p.t.replace(/\s+/g, ' ')}`
));
console.log(`\nВсего отобрано: ${picks.length} заданий.`);
const existing = db.prepare("SELECT id FROM tests WHERE subject_slug='math' AND title=?").get(TITLE);
if (existing) {
console.log(`\nТест «${TITLE}» уже существует (id ${existing.id}) — не дублирую.`);
} else if (DRY) {
console.log(`\nDRY-RUN: тест НЕ создан. Будет создан с ${picks.length} вопросами.`);
} else {
const by = adminId();
const testId = db.prepare(
'INSERT INTO tests (title, subject_slug, description, show_answers, time_limit, created_by) VALUES (?,?,?,?,?,?)'
).run(TITLE, 'math', DESC, 1, 40, by).lastInsertRowid;
const ins = db.prepare('INSERT INTO test_questions (test_id, question_id, order_index) VALUES (?,?,?)');
picks.forEach((p, i) => ins.run(testId, p.id, i));
console.log(`\nСоздан тест «${TITLE}» (id ${testId}, ${picks.length} вопросов, лимит 40 мин).`);
console.log('Выдать классу/ученику: assignment с test_id=' + testId + ' (mode неважен, test_id перекрывает выбор).');
}
+149
View File
@@ -0,0 +1,149 @@
'use strict';
/*
* Конвертер: размеченные вопросы ЦТ-11 из банка `questions` (subject_id=3)
* → `exam_tasks` для отдельного модуля exam-prep (exam_key='ctmath').
*
* По умолчанию DRY (только чтение, печать выборки и статистики).
* Запись ТОЛЬКО с флагом --apply (и только если применён трек 077).
* node backend/scripts/seed_ctmath_exam_tasks.js # dry: выборка+статистика
* node backend/scripts/seed_ctmath_exam_tasks.js --apply # запись
*
* Правила (сверены с exam-prep, см. plans/ct-math/BUILD_ON_QUESTIONS.md):
* - тип: single/true_false → 'mc'; fill-blank/short_answer → 'open' (если ответ
* числовой/дробь/пара) иначе 'long'; multi/multiple → пропуск (exam-prep mc = radio).
* - opts_json: [["а","html"],...] кириллические метки; answer(mc)=метка верного.
* - answer(open): очищенный числовой/дробь/пара; проверка на клиенте численная.
* - математика: \( \) → $ , \[ \] → $$ (exam-prep KaTeX знает только $/$$).
* - subtopic = slug из exam_topics(077); difficulty 1..3; variant=year.
*/
const db = require('../src/db/db');
const APPLY = process.argv.includes('--apply');
const MATH_ID = 3, EXAM_KEY = 'ctmath';
const LABELS = ['а', 'б', 'в', 'г', 'д', 'е', 'ж', 'з'];
// flat topic name → [section slug, subtopic slug] (slugs из миграции 077)
const TOPIC_MAP = {
'Теория чисел': ['numbers', 'num-divisibility'],
'Арифметика и степени': ['expressions', 'expr-powers-roots'],
'Квадратные уравнения': ['equations', 'eq-quadratic'],
'Тригонометрия': ['trigonometry', 'trig-identities'],
'Тригонометрические уравнения':['trigonometry', 'trig-equations'],
'Прогрессии': ['word-sequences', 'seq-progressions'],
'Словесные задачи': ['word-sequences', 'word-problems'],
'Неравенства': ['equations', 'eq-rational'],
'Уравнения': ['equations', 'eq-rational'],
'Функции': ['functions', 'fn-properties'],
'Логарифмы': ['equations', 'eq-logarithmic'],
'Показательные неравенства': ['equations', 'eq-exponential'],
'Геометрия': ['planimetry', 'plan-triangles'],
'Стереометрия': ['stereometry', 'ster-basics'],
'Окружность и круг': ['planimetry', 'plan-circle'],
'Числовые промежутки': ['equations', 'eq-linear'],
'Подобные фигуры': ['planimetry', 'plan-quadrilaterals'],
'Парабола': ['functions', 'fn-graphs'],
'Статистика и диаграммы': ['advanced', 'adv-combined'],
};
// \( \) → $ ; \[ \] → $$ (replacement-функции, чтобы $ не интерпретировался)
function conv(s) {
return String(s || '')
.replace(/\\\(/g, () => '$').replace(/\\\)/g, () => '$')
.replace(/\\\[/g, () => '$$').replace(/\\\]/g, () => '$$');
}
// численная проверяемость ответа (зеркало answer-check.js exam-prep)
function isNumericAnswer(s) {
if (s == null) return false;
const t = String(s).trim().replace(/\$/g, '').replace(/\s+/g, '').replace(',', '.');
if (/^-?\d+(?:\.\d+)?$/.test(t)) return true; // число
if (/^-?\d+(?:\.\d+)?\/-?\d+(?:\.\d+)?$/.test(t)) return true; // дробь
const parts = String(s).replace(/\$/g, '').split(/[;]|\sи\s/).map(x => x.trim()).filter(Boolean);
if (parts.length === 2 && parts.every(p => /^-?\d+(?:[.,]\d+)?(?:\/-?\d+)?$/.test(p.replace(/\s/g, '')))) return true; // пара
return false;
}
function cleanAnswer(s) { return String(s || '').trim().replace(/\$/g, '').replace(/\s+/g, ' ').trim(); }
const rows = db.prepare(`
SELECT q.id, q.text, q.type, q.difficulty, q.year, q.explanation, q.image, q.correct_text,
COALESCE(t.name,'') AS topic_name
FROM questions q LEFT JOIN topics t ON t.id = q.topic_id
WHERE q.subject_id = ? AND q.topic_id IS NOT NULL
AND (q.source_type IS NULL OR q.source_type <> 'экзамен 9')
ORDER BY q.year, q.id
`).all(MATH_ID);
const optStmt = db.prepare('SELECT text, is_correct, order_index FROM options WHERE question_id=? ORDER BY order_index, id');
const out = [];
const stat = { mc: 0, open: 0, long: 0, skip_multi: 0, skip_notopic: 0, skip_noopts: 0, open_demoted_long: 0 };
const perVariant = {};
for (const q of rows) {
const map = TOPIC_MAP[q.topic_name];
if (!map) { stat.skip_notopic++; continue; }
const [topic, subtopic] = map;
const opts = optStmt.all(q.id);
const variant = q.year || 0;
let task_type, opts_json = null, answer = null;
if (q.type === 'single' || q.type === 'true_false') {
if (!opts.length) { stat.skip_noopts++; continue; }
task_type = 'mc';
opts_json = JSON.stringify(opts.map((o, i) => [LABELS[i] || String(i + 1), conv(o.text)]));
const ci = opts.findIndex(o => o.is_correct);
answer = ci >= 0 ? (LABELS[ci] || String(ci + 1)) : null;
stat.mc++;
} else if (q.type === 'fill-blank' || q.type === 'short_answer') {
const corr = (opts.find(o => o.is_correct) || {}).text || q.correct_text || '';
if (isNumericAnswer(corr)) { task_type = 'open'; answer = cleanAnswer(corr); stat.open++; }
else { task_type = 'long'; answer = null; stat.long++; stat.open_demoted_long++; }
} else { // multi / multiple
stat.skip_multi++; continue;
}
// solution_html (NOT NULL): объяснение + строка ответа
let sol = conv(q.explanation || '');
if (answer && task_type !== 'long') sol += `<div class="sol-ans">Ответ: ${task_type === 'mc' ? answer + ')' : '$' + answer + '$'}</div>`;
if (!sol.trim()) sol = '<div class="sol-ans">См. решение в источнике.</div>';
const figure = q.image ? `<img src="${String(q.image)}" alt="" loading="lazy" style="max-width:100%">` : null;
perVariant[variant] = (perVariant[variant] || 0) + 1;
out.push({
exam_key: EXAM_KEY, variant, task_idx: perVariant[variant],
task_type, text_html: conv(q.text), figure_html: figure, opts_json, answer,
solution_html: sol, topic, subtopic, difficulty: q.difficulty || 1, _qid: q.id, _tn: q.topic_name,
});
}
console.log(APPLY ? '[APPLY]' : '[DRY-RUN]', `вход: ${rows.length} размеченных вопросов; к вставке: ${out.length}`);
console.log('Статистика типов:', JSON.stringify(stat));
console.log('Заданий по годам (variant):', JSON.stringify(perVariant));
const bySub = {};
out.forEach(o => { bySub[o.subtopic] = (bySub[o.subtopic] || 0) + 1; });
console.log('По подтемам:', JSON.stringify(bySub));
console.log('\n— Выборка (по одному mc / open / long) —');
for (const tp of ['mc', 'open', 'long']) {
const s = out.find(o => o.task_type === tp);
if (!s) continue;
console.log(`\n[${tp}] qid=${s._qid} тема="${s._tn}" → ${s.topic}/${s.subtopic} variant=${s.variant} diff=${s.difficulty}`);
console.log(' text :', s.text_html.replace(/\s+/g, ' ').slice(0, 160));
if (s.opts_json) console.log(' opts :', s.opts_json.slice(0, 200));
console.log(' answ :', s.answer);
console.log(' sol :', s.solution_html.replace(/\s+/g, ' ').slice(0, 140));
}
if (!APPLY) { console.log('\nDRY-RUN: запись НЕ выполнялась. Для записи: --apply (после применения миграции 077).'); process.exit(0); }
// ── APPLY ──
const track = db.prepare("SELECT exam_key FROM exam_tracks WHERE exam_key=?").get(EXAM_KEY);
if (!track) { console.error('Нет трека ctmath (миграция 077 не применена). Сначала примените 077.'); process.exit(1); }
const already = db.prepare("SELECT COUNT(*) c FROM exam_tasks WHERE exam_key=?").get(EXAM_KEY).c;
if (already > 0) { console.error(`В exam_tasks уже есть ${already} задач ctmath — повторная вставка отменена (избегаем дублей).`); process.exit(1); }
const ins = 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 (@exam_key,@variant,@task_idx,@task_type,@text_html,@figure_html,@opts_json,@answer,@solution_html,@topic,@subtopic,@difficulty)`);
let n = 0;
for (const o of out) { ins.run({ exam_key:o.exam_key, variant:o.variant, task_idx:o.task_idx, task_type:o.task_type, text_html:o.text_html, figure_html:o.figure_html, opts_json:o.opts_json, answer:o.answer, solution_html:o.solution_html, topic:o.topic, subtopic:o.subtopic, difficulty:o.difficulty }); n++; }
// обновим метаданные трека
const variants = Object.keys(perVariant).length;
db.prepare("UPDATE exam_tracks SET variants_count=? WHERE exam_key=?").run(variants, EXAM_KEY);
console.log(`\nВставлено ${n} задач в exam_tasks (ctmath). variants_count=${variants}.`);
+87
View File
@@ -0,0 +1,87 @@
'use strict';
/*
* Колоды карточек формул для подготовки к ЦЭ/ЦТ (интервальное повторение).
* flashcard_decks(user_id,title,description,color) + flashcard_cards(deck_id,front,back,order_idx).
* Математика — KaTeX inline $…$ (страница флешкарт рендерит \( \), \[ \], $ $; НЕ $$).
* Идемпотентно: колода с таким title у владельца не создаётся повторно.
* node backend/scripts/seed_ctmath_flashcards.js [--dry]
*/
const db = require('../src/db/db');
const DRY = process.argv.includes('--dry');
const owner = (db.prepare("SELECT id FROM users WHERE role='admin' ORDER BY id LIMIT 1").get()
|| db.prepare('SELECT id FROM users ORDER BY id LIMIT 1').get()).id;
const DECKS = [
{ title: 'ЦТ · Тригонометрия — формулы', color: '#9B5DE5', cards: [
['Определения через единичную окружность', '$\\cos\\alpha=x,\\ \\sin\\alpha=y$ (координаты точки)'],
['Основное тригонометрическое тождество', '$\\sin^2\\alpha+\\cos^2\\alpha=1$'],
['$1+\\operatorname{tg}^2\\alpha$', '$\\dfrac{1}{\\cos^2\\alpha}$'],
['$1+\\operatorname{ctg}^2\\alpha$', '$\\dfrac{1}{\\sin^2\\alpha}$'],
['$\\sin(\\alpha\\pm\\beta)$', '$\\sin\\alpha\\cos\\beta\\pm\\cos\\alpha\\sin\\beta$'],
['$\\cos(\\alpha\\pm\\beta)$', '$\\cos\\alpha\\cos\\beta\\mp\\sin\\alpha\\sin\\beta$'],
['$\\sin 2\\alpha$', '$2\\sin\\alpha\\cos\\alpha$'],
['$\\cos 2\\alpha$', '$\\cos^2\\alpha-\\sin^2\\alpha=1-2\\sin^2\\alpha=2\\cos^2\\alpha-1$'],
['Понижение степени: $\\sin^2\\alpha$', '$\\dfrac{1-\\cos 2\\alpha}{2}$'],
['Область значений $\\arcsin x$', '$\\left[-\\tfrac{\\pi}{2};\\tfrac{\\pi}{2}\\right]$'],
['Область значений $\\arccos x$', '$[0;\\ \\pi]$'],
['$\\sin x=a$ — корни', '$x=(-1)^n\\arcsin a+\\pi n$'],
['$\\cos x=a$ — корни', '$x=\\pm\\arccos a+2\\pi n$'],
['$\\operatorname{tg} x=a$ — корни', '$x=\\operatorname{arctg} a+\\pi n$'],
['$\\sin x=0$', '$x=\\pi n$'],
['$\\cos x=0$', '$x=\\tfrac{\\pi}{2}+\\pi n$'],
]},
{ title: 'ЦТ · Стереометрия — формулы', color: '#00BBF9', cards: [
['$V$ призмы', '$S_{\\text{осн}}\\cdot h$'],
['$V$ пирамиды', '$\\tfrac{1}{3}S_{\\text{осн}}\\cdot h$'],
['$V$ цилиндра', '$\\pi R^2 h$'],
['$V$ конуса', '$\\tfrac{1}{3}\\pi R^2 h$'],
['$V$ шара', '$\\tfrac{4}{3}\\pi R^3$'],
['$S$ сферы', '$4\\pi R^2$'],
['$S_{\\text{бок}}$ цилиндра', '$2\\pi R h$'],
['$S_{\\text{бок}}$ конуса', '$\\pi R l$'],
['Сечение $\\parallel$ основанию: отношение площадей', '$k^2$, где $k$ — отношение высот от вершины'],
['Угол между прямыми (векторы)', '$\\cos\\varphi=\\dfrac{|\\vec a\\cdot\\vec b|}{|\\vec a|\\,|\\vec b|}$'],
['Скалярное произведение', '$a_xb_x+a_yb_y+a_zb_z$'],
['Длина вектора', '$\\sqrt{a_x^2+a_y^2+a_z^2}$'],
['Сфера касается плоскости', 'Радиус в точку касания $\\perp$ плоскости (далее Пифагор)'],
['Расстояние между скрещивающимися прямыми', 'Длина их общего перпендикуляра'],
]},
{ title: 'ЦТ · Логарифмы и степени — формулы', color: '#F15BB5', cards: [
['$\\log_a(xy)$', '$\\log_a x+\\log_a y$'],
['$\\log_a\\dfrac{x}{y}$', '$\\log_a x-\\log_a y$'],
['$\\log_a x^p$', '$p\\log_a x$'],
['Переход к новому основанию', '$\\log_a x=\\dfrac{\\log_b x}{\\log_b a}$'],
['$a^{\\log_a x}$', '$x$'],
['$\\log_a a$ и $\\log_a 1$', '$1$ и $0$'],
['$a^m\\cdot a^n$', '$a^{m+n}$'],
['$(a^m)^n$', '$a^{mn}$'],
['$a^{-n}$', '$\\dfrac{1}{a^n}$'],
['$a^{m/n}$', '$\\sqrt[n]{a^m}$'],
]},
{ title: 'ЦТ · Производная — формулы', color: '#00F5D4', cards: [
['$(x^n)\'$', '$n x^{n-1}$'],
['$(\\sin x)\'$', '$\\cos x$'],
['$(\\cos x)\'$', '$-\\sin x$'],
['$(e^x)\'$', '$e^x$'],
['$(\\ln x)\'$', '$\\dfrac{1}{x}$'],
['$(uv)\'$', '$u\'v+uv\'$'],
['$\\left(\\dfrac{u}{v}\\right)\'$', '$\\dfrac{u\'v-uv\'}{v^2}$'],
['Монотонность по производной', '$f\'>0$ — возрастает; $f\'<0$ — убывает'],
['Точка экстремума', '$f\'=0$ и меняет знак'],
]},
];
const findDeck = db.prepare('SELECT id FROM flashcard_decks WHERE user_id=? AND title=?');
const insDeck = db.prepare('INSERT INTO flashcard_decks (user_id,title,description,color) VALUES (?,?,?,?)');
const insCard = db.prepare('INSERT INTO flashcard_cards (deck_id,front,back,order_idx) VALUES (?,?,?,?)');
console.log(DRY ? '[DRY-RUN]' : '[APPLY]', 'владелец user_id=', owner);
for (const d of DECKS) {
const ex = findDeck.get(owner, d.title);
if (ex) { console.log(` есть колода: «${d.title}» (id ${ex.id}) — пропуск`); continue; }
if (DRY) { console.log(` + колода «${d.title}» (${d.cards.length} карт)`); continue; }
const did = insDeck.run(owner, d.title, 'Формулы для подготовки к ЦЭ/ЦТ. Интервальное повторение.', d.color).lastInsertRowid;
d.cards.forEach(([f, b], i) => insCard.run(did, f, b, i));
console.log(` + колода «${d.title}» (id ${did}, ${d.cards.length} карт)`);
}
console.log(DRY ? 'DRY-RUN: ничего не записано.' : 'Готово. Колоды формул добавлены (владелец — admin; раздать классу можно через доступ к колоде).');
+176
View File
@@ -0,0 +1,176 @@
'use strict';
/*
* Уроки остальных блоков курса «ЦЭ/ЦТ — Математика» (по PLAN.md, шаблон пилотов).
* Числа · Преобразования · Уравнения(×2) · Функции · Прогрессии/текстовые ·
* Планиметрия · Продвинутое. Форматы блоков — под рендер lesson.html
* (text/heading/callout esc-only; математика $…$/$$…$$; callout.style). Идемпотентно.
* node backend/scripts/seed_ctmath_lessons_rest.js [--dry]
*/
const db = require('../src/db/db');
const DRY = process.argv.includes('--dry');
const COURSE_TITLE = 'ЦЭ/ЦТ — Математика';
const course = db.prepare("SELECT id FROM courses WHERE subject_slug='math' AND title=?").get(COURSE_TITLE);
if (!course) { console.error('Нет курса. Сначала seed_ctmath_course.js'); process.exit(1); }
const H = (text, level = 2) => ['heading', { text, level }];
const P = (text) => ['text', { text }];
const F = (tex, label) => ['formula', label ? { label, tex } : { tex }];
const CI = (text) => ['callout', { style: 'info', text }];
const CW = (text) => ['callout', { style: 'warning', text }];
const CS = (text) => ['callout', { style: 'success', text }];
const SIM = (simId, caption) => ['sim', { simId, caption }];
const FC = (front, back) => ['flashcard', { front, back }];
const QZ = (question, options, correctIndex) => ['quiz', { question, options, correctIndex }];
const PR = () => CI('Тренажёр по теме — в модуле /exam-prep/ctmath (реальные задания ЦТ прошлых лет) и в практике по теме.');
const LESSONS = [
{ section: 'Числа и вычисления', title: 'Числа, делимость и проценты', read: 8, blocks: [
H('Числа, делимость и проценты'),
P('Действительные числа на координатной прямой нужно уметь оценивать и сравнивать. Деление с остатком записывается формулой ниже.'),
F('n = d\\cdot q + r,\\qquad 0\\le r\\lt d', 'Деление с остатком'),
P('Проценты: $p\\%$ числа $a$ равно $\\dfrac{p}{100}\\cdot a$. Увеличение на $p\\%$ — умножение на $\\left(1+\\dfrac{p}{100}\\right)$, уменьшение — на $\\left(1-\\dfrac{p}{100}\\right)$.'),
F('\\text{НОД}(a,b)\\cdot\\text{НОК}(a,b)=a\\cdot b', 'Связь НОД и НОК'),
H('Разбор А4', 3),
P('Делитель $15$, неполное частное $k$, остаток $7$. Тогда делимое $n=15k+7$.'),
CS('Ответ: $n=15k+7$.'),
H('Разбор (проценты)', 3),
P('$15\\%$ числа равны $33$. Число $=33:0{,}15=220$, а $20\\%$ от него $=44$.'),
CS('Ответ: $44$.'),
FC('Деление с остатком', '$n=dq+r,\\ 0\\le r<d$'),
FC('$\\text{НОД}\\cdot\\text{НОК}$', '$a\\cdot b$'),
FC('$p\\%$ от $a$', '$\\dfrac{p}{100}\\,a$'),
QZ('20% некоторого числа равны 40. Само число равно:', ['200', '80', '160', '20'], 0),
PR(),
]},
{ section: 'Алгебраические преобразования', title: 'Степени, корни, дроби', read: 9, blocks: [
H('Преобразования выражений: степени, корни, дроби'),
F('a^m\\cdot a^n=a^{m+n},\\quad (a^m)^n=a^{mn},\\quad a^{-n}=\\dfrac{1}{a^n},\\quad a^{m/n}=\\sqrt[n]{a^m}', 'Степени'),
F('\\sqrt[n]{ab}=\\sqrt[n]{a}\\,\\sqrt[n]{b},\\qquad \\sqrt[n]{a^n}=|a|\\ (n\\text{ — чётное})', 'Корни'),
F('(a\\pm b)^2=a^2\\pm2ab+b^2,\\qquad a^2-b^2=(a-b)(a+b)', 'Формулы сокращённого умножения'),
P('ОДЗ выражения: под корнем чётной степени — неотрицательное число; знаменатель не равен нулю; аргумент логарифма положителен.'),
CW('В задании А10 проверяют именно ОДЗ: при каком значении выражение имеет смысл.'),
H('Разбор А10', 3),
P('При $a=-4$ из $\\sqrt{a}$, $\\sqrt[3]{a}$, $\\dfrac{1}{a+4}$ смысл имеет только $\\sqrt[3]{a}$: корень нечётной степени из отрицательного определён; $\\sqrt{-4}$ — нет; $\\dfrac{1}{0}$ — деление на ноль.'),
CS('Ответ: $\\sqrt[3]{a}$.'),
FC('$a^m\\cdot a^n$', '$a^{m+n}$'),
FC('$a^{m/n}$', '$\\sqrt[n]{a^m}$'),
FC('$a^2-b^2$', '$(a-b)(a+b)$'),
QZ('Значение $a^{1/2}$ при $a=9$:', ['3', '4,5', '81', '18'], 0),
PR(),
]},
{ section: 'Уравнения и неравенства', title: 'Квадратные, рациональные, модуль', read: 11, blocks: [
H('Квадратные и рациональные уравнения и неравенства. Модуль'),
F('x_{1,2}=\\dfrac{-b\\pm\\sqrt{D}}{2a},\\ \\ D=b^2-4ac;\\qquad x_1x_2=\\dfrac{c}{a},\\ \\ x_1+x_2=-\\dfrac{b}{a}', 'Квадратное уравнение и теорема Виета'),
SIM('quadratic', 'Корни квадратного уравнения и дискриминант'),
P('Метод интервалов: разложить на множители, отметить нули, расставить знаки по промежуткам. Учитывать кратность корня (при чётной кратности знак не меняется).'),
F('|x|=a\\Rightarrow x=\\pm a\\ (a\\ge0);\\qquad |f(x)|\\lt a\\Leftrightarrow -a\\lt f(x)\\lt a', 'Модуль'),
CI('Двойное неравенство $a\\le f(x)<b$ решают как систему; целые решения отбирают на полученном промежутке.'),
H('Разбор А5', 3),
P('Произведение действительных корней уравнения $x^2-5x+6=0$ по теореме Виета равно $6$ (корни $2$ и $3$).'),
CS('Ответ: $6$.'),
H('Разбор (целые решения)', 3),
P('Сколько целых решений у неравенства $-4<2x-1\\le5$? Имеем $-1{,}5<x\\le3$, то есть $x\\in\\{-1,0,1,2,3\\}$ — пять решений.'),
CS('Ответ: $5$.'),
FC('Дискриминант', '$D=b^2-4ac$'),
FC('Виет: сумма и произведение корней', '$x_1+x_2=-\\dfrac{b}{a},\\ x_1x_2=\\dfrac{c}{a}$'),
FC('$|f(x)|<a$', '$-a<f(x)<a$'),
QZ('Сумма корней уравнения $x^2-7x+12=0$:', ['7', '12', '-7', '3'], 0),
PR(),
]},
{ section: 'Уравнения и неравенства', title: 'Показательные, логарифмические, иррациональные', read: 12, blocks: [
H('Показательные, логарифмические, иррациональные уравнения и неравенства'),
F('a^{f}=a^{g}\\Leftrightarrow f=g;\\qquad \\log_a f=\\log_a g\\Leftrightarrow f=g\\gt 0', 'Равносильные переходы'),
F('\\sqrt{f}=g\\ \\Leftrightarrow\\ \\begin{cases}g\\ge0\\\\ f=g^2\\end{cases}', 'Иррациональное уравнение'),
CI('Метод рационализации (для неравенств): знак $\\log_a f-\\log_a g$ совпадает со знаком $(a-1)(f-g)$; знак $a^{f}-a^{g}$ — со знаком $(a-1)(f-g)$. Экономит время на сложных неравенствах.'),
CW('В логарифмических всегда выписывайте ОДЗ: аргумент $>0$, основание $>0$ и $\\ne1$.'),
H('Разбор В11', 3),
P('$\\log_2^2 x-3\\log_2 x+2=0$. Замена $t=\\log_2 x$: $t^2-3t+2=0$, $t=1$ или $t=2$, откуда $x=2$ или $x=4$; их произведение $8$.'),
CS('Ответ: $8$.'),
H('Разбор В14', 3),
P('Наименьшее целое решение неравенства $3^{x}>9$: так как основание $>1$, получаем $x>2$, наименьшее целое $x=3$.'),
CS('Ответ: $3$.'),
FC('$a^{f}=a^{g}$', '$f=g$'),
FC('$\\log_a f=\\log_a g$', '$f=g>0$'),
FC('Знак $\\log_a f-\\log_a g$ (рационализация)', 'как у $(a-1)(f-g)$'),
QZ('$\\log_3 81$ равно:', ['4', '3', '27', '9'], 0),
PR(),
]},
{ section: 'Функции и производная', title: 'Функции, графики, производная', read: 11, blocks: [
H('Функции: свойства, графики, производная'),
P('Ключевые свойства: ОДЗ, чётность (если $f(-x)=f(x)$ — чётная, график симметричен относительно $Oy$; если $f(-x)=-f(x)$ — нечётная), монотонность, нули.'),
SIM('graphtransform', 'Преобразования графиков: сдвиги и растяжения'),
F('f\'\\gt 0\\Rightarrow\\text{возрастает};\\quad f\'\\lt 0\\Rightarrow\\text{убывает};\\quad f\'=0\\ \\text{со сменой знака}\\Rightarrow\\text{экстремум}', 'Производная и поведение функции'),
H('Разбор В2 (квадратичная)', 3),
P('$f(x)=x^2-6x+5$: нули $1$ и $5$ (их сумма $6$); $f(0)=5$; вершина при $x=3$, наименьшее значение $f(3)=-4$.'),
CS('Сумма нулей $=6$; наименьшее значение $=-4$.'),
H('Разбор В19 (производная)', 3),
P('$f(x)=x^3-3x^2+5$: $f\'(x)=3x^2-6x=3x(x-2)$; функция возрастает на $(-\\infty;0]$ и $[2;+\\infty)$, убывает на $[0;2]$.'),
CS('Промежутки возрастания: $(-\\infty;0]$ и $[2;+\\infty)$.'),
FC('Чётная функция', '$f(-x)=f(x)$, симметрия относительно $Oy$'),
FC('$(x^n)\'$', '$n x^{n-1}$'),
FC('Признак возрастания', '$f\'(x)>0$'),
QZ('Функция $y=x^2$ является:', ['чётной', 'нечётной', 'ни чётной, ни нечётной', 'периодической'], 0),
PR(),
]},
{ section: 'Прогрессии и текстовые задачи', title: 'Прогрессии и текстовые задачи', read: 10, blocks: [
H('Прогрессии и текстовые задачи'),
F('a_n=a_1+(n-1)d,\\qquad S_n=\\dfrac{a_1+a_n}{2}\\,n', 'Арифметическая прогрессия'),
F('b_n=b_1 q^{\\,n-1},\\qquad S_n=\\dfrac{b_1(q^{n}-1)}{q-1}\\ (q\\ne1)', 'Геометрическая прогрессия'),
P('Текстовые задачи: проценты; движение ($s=vt$); работа (производительность $=\\dfrac{1}{t}$); смеси и сплавы (масса вещества $=$ доля $\\times$ масса смеси).'),
H('Разбор В6', 3),
P('$b_3=12$, $b_5=48$ (знаменатель положителен): $q^2=\\dfrac{48}{12}=4$, $q=2$, $b_1=\\dfrac{12}{4}=3$. Сумма первых четырёх членов $3+6+12+24=45$.'),
CS('Ответ: $45$.'),
H('Разбор (сплавы)', 3),
P('Сплав массой $200$ г содержит $30\\%$ меди. Масса меди $=0{,}3\\cdot200=60$ г. На таких долях строятся уравнения смесей.'),
FC('$n$-й член арифм. прогрессии', '$a_n=a_1+(n-1)d$'),
FC('Сумма геом. прогрессии', '$S_n=\\dfrac{b_1(q^n-1)}{q-1}$'),
FC('Путь', '$s=v\\cdot t$'),
QZ('В арифметической прогрессии $a_1=2$, $d=3$. Тогда $a_4$ равно:', ['11', '14', '8', '9'], 0),
PR(),
]},
{ section: 'Планиметрия', title: 'Треугольники, четырёхугольники, окружность', read: 11, blocks: [
H('Планиметрия: треугольники, четырёхугольники, окружность'),
F('S_\\triangle=\\tfrac12 a h_a=\\tfrac12 ab\\sin C;\\qquad \\dfrac{a}{\\sin A}=2R;\\qquad c^2=a^2+b^2-2ab\\cos C', 'Треугольник'),
SIM('triangle', 'Геометрия треугольника'),
P('Прямоугольный треугольник: гипотенуза $=2R$ (радиус описанной окружности). Правильный $n$-угольник связывает сторону, радиус описанной $R$ и вписанной $r$ окружностей.'),
CI('Вписанный угол равен половине центрального, опирающегося на ту же дугу.'),
H('Разбор В5', 3),
P('В прямоугольном треугольнике радиус описанной окружности $R=13$, один катет $10$. Гипотенуза $=2R=26$, второй катет $=\\sqrt{26^2-10^2}=\\sqrt{576}=24$.'),
CS('Ответ: $24$.'),
H('Разбор В10 (правильный шестиугольник)', 3),
P('У правильного шестиугольника со стороной $a$: $R=a$, $r=\\dfrac{\\sqrt3}{2}a$, площадь $S=\\dfrac{3\\sqrt3}{2}a^2$.'),
FC('Площадь треугольника', '$\\tfrac12 ab\\sin C$'),
FC('Теорема синусов', '$\\dfrac{a}{\\sin A}=2R$'),
FC('Вписанный угол', 'половина центрального на ту же дугу'),
QZ('Гипотенуза прямоугольного треугольника, вписанного в окружность радиуса 5, равна:', ['10', '5', '2,5', '25'], 0),
PR(),
]},
{ section: 'Продвинутое и комбинированное', title: 'Параметры и комбинированные задачи', read: 10, blocks: [
H('Задачи с параметрами и комбинированные задачи'),
P('Параметр — буква, от которой зависит ответ. Два подхода: аналитический (исследовать решение по параметру) и графический (семейство графиков и их пересечения).'),
CI('Частый приём: выразить параметр $a=\\varphi(x)$ и смотреть, сколько решений даёт горизонтальная прямая $y=a$ (число пересечений с графиком $\\varphi$).'),
P('Комбинированные задачи смешивают темы (алгебра и геометрия, прогрессии и проценты). Стратегия: разбить на подзадачи, аккуратно следя за ОДЗ и единицами.'),
CI('Продвинутый уровень подробно — в плане курса (Сканави, Высоцкий «Параметры», Прасолов). Здесь — общая стратегия и ориентиры.'),
FC('Графический метод для параметра', '$a=\\varphi(x)$; число решений = число пересечений $y=a$ с графиком'),
FC('Уравнение $x^2=a$: число решений', '$a>0$ — два, $a=0$ — одно, $a<0$ — нет'),
QZ('При каком $a$ уравнение $x^2=a$ имеет ровно одно решение?', ['a=0', 'a>0', 'a<0', 'при любом'], 0),
PR(),
]},
];
console.log(DRY ? '[DRY-RUN]' : '[APPLY]', 'курс id=', course.id);
const insLesson = db.prepare('INSERT INTO lessons (course_id, title, order_index, is_published, section_id, read_time) VALUES (?,?,?,1,?,?)');
const insBlock = db.prepare('INSERT INTO lesson_blocks (lesson_id, type, order_index, data) VALUES (?,?,?,?)');
const secOrder = {};
for (const L of LESSONS) {
const sec = db.prepare('SELECT id FROM course_sections WHERE course_id=? AND title=?').get(course.id, L.section);
if (!sec) { console.log(` [skip] нет секции «${L.section}»`); continue; }
const ex = db.prepare('SELECT id FROM lessons WHERE course_id=? AND title=?').get(course.id, L.title);
if (ex) { console.log(` есть урок: «${L.title}» (id ${ex.id}) — пропуск`); continue; }
secOrder[sec.id] = (secOrder[sec.id] || 0) + 1;
if (DRY) { console.log(` + [${L.section}] «${L.title}» (${L.blocks.length} блоков)`); continue; }
const lid = insLesson.run(course.id, L.title, secOrder[sec.id], sec.id, L.read).lastInsertRowid;
L.blocks.forEach(([type, data], bi) => insBlock.run(lid, type, bi, JSON.stringify(data)));
console.log(` + [${L.section}] «${L.title}» (id ${lid}, ${L.blocks.length} блоков)`);
}
console.log(DRY ? 'DRY-RUN: ничего не записано.' : 'Готово. Уроки остальных блоков добавлены (черновик курса).');
@@ -0,0 +1,128 @@
'use strict';
/*
* Уроки блока «Стереометрия» курса «ЦЭ/ЦТ — Математика» (по PILOT_STEREOMETRY.md).
* 4 урока: расположение/сечения → многогранники → тела вращения → углы/расстояния.
* Форматы блоков — под рендер frontend/lesson.html (text/heading/callout esc-only;
* математика $…$/$$…$$; callout.style=info|warning|success|error). Идемпотентно.
* node backend/scripts/seed_ctmath_lessons_stereo.js [--dry]
*/
const db = require('../src/db/db');
const DRY = process.argv.includes('--dry');
const COURSE_TITLE = 'ЦЭ/ЦТ — Математика', SECTION_TITLE = 'Стереометрия';
const course = db.prepare("SELECT id FROM courses WHERE subject_slug='math' AND title=?").get(COURSE_TITLE);
if (!course) { console.error('Нет курса. Сначала seed_ctmath_course.js'); process.exit(1); }
const section = db.prepare('SELECT id FROM course_sections WHERE course_id=? AND title=?').get(course.id, SECTION_TITLE);
if (!section) { console.error('Нет секции «' + SECTION_TITLE + '»'); process.exit(1); }
const H = (text, level = 2) => ['heading', { text, level }];
const P = (text) => ['text', { text }];
const F = (tex, label) => ['formula', label ? { label, tex } : { tex }];
const CI = (text) => ['callout', { style: 'info', text }];
const CW = (text) => ['callout', { style: 'warning', text }];
const CS = (text) => ['callout', { style: 'success', text }];
const SIM = (caption) => ['sim', { simId: 'stereo', caption }];
const FC = (front, back) => ['flashcard', { front, back }];
const ORD = (question, items) => ['ordering', { question, items }];
const ACC = (title, content) => ['accordion', { title, content }];
// M26 — расположение, сечения (А2, В1)
const L1 = [
H('Прямые и плоскости в пространстве'),
P('Две прямые в пространстве: пересекаются, параллельны или скрещиваются. Прямая и плоскость: прямая лежит в плоскости, параллельна ей или пересекает её. Две плоскости: параллельны или пересекаются по прямой.'),
SIM('Покрутите фигуру: найдите линию пересечения двух плоскостей и пары скрещивающихся прямых'),
CI('Линия пересечения двух плоскостей проходит через их общие точки. В правильной пирамиде плоскости, проходящие через вершину и центр основания, пересекаются по прямой через вершину (например, $SO$).'),
F('a\\parallel b,\\ b\\subset\\alpha,\\ a\\not\\subset\\alpha \\Rightarrow a\\parallel\\alpha', 'Признак параллельности прямой и плоскости'),
CW('В задании В1 (выбор верных утверждений о расстояниях) проверяйте каждое утверждение отдельно: расстояние между скрещивающимися прямыми — это длина их общего перпендикуляра, а не любого отрезка.'),
H('Разбор А2', 3),
P('Пример. В правильной четырёхугольной пирамиде $SABCD$ ($O$ — центр основания) найдите прямую пересечения плоскостей $DSO$ и $SCB$.'),
P('Решение. Обе плоскости проходят через вершину $S$, значит линия их пересечения проходит через $S$; анализом общих точек получаем прямую $SO$.'),
CS('Метод: ищем общие точки двух плоскостей — через них проходит линия пересечения.'),
FC('Расстояние между скрещивающимися прямыми', 'Длина их общего перпендикуляра'),
FC('Линия пересечения двух плоскостей', 'Проходит через все их общие точки'),
CI('Тренажёр: задания А2 и В1 по теме «Стереометрия» в практике модуля /exam-prep/ctmath. Цель: не менее 80%.'),
];
// M27 — многогранники (В13, В17)
const L2 = [
H('Многогранники: объёмы, площади, подобие'),
F('V_{\\text{призмы}}=S_{\\text{осн}}\\cdot h,\\qquad V_{\\text{пирамиды}}=\\tfrac{1}{3}S_{\\text{осн}}\\cdot h', 'Объёмы'),
P('Сечение, параллельное основанию пирамиды, отсекает подобную фигуру. Если высота делится от вершины в отношении $k$, то линейные размеры сечения относятся к основанию как $k$, а площади — как $k^2$.'),
F('\\dfrac{S_{\\text{сеч}}}{S_{\\text{осн}}}=k^2,\\quad k=\\dfrac{\\text{высота до сечения}}{\\text{вся высота}}', 'Сечение, параллельное основанию'),
SIM('Сечение пирамиды плоскостью, параллельной основанию'),
CW('В задании В17 ловят на том, что как $k^2$ относятся именно площади, а не длины. Сначала найдите $k$ из отношения высот, затем возводите в квадрат.'),
H('Разбор В17', 3),
P('Пример. Плоскость, параллельная основанию треугольной пирамиды, делит высоту в отношении $5:3$ от вершины. Площадь сечения меньше площади основания на $39$. Найдите площадь сечения.'),
P('Решение. $k=\\dfrac{5}{5+3}=\\dfrac{5}{8}$, поэтому $\\dfrac{S_{\\text{сеч}}}{S_{\\text{осн}}}=\\dfrac{25}{64}$. Пусть $S_{\\text{осн}}=x$: $x-\\dfrac{25}{64}x=39\\Rightarrow\\dfrac{39}{64}x=39\\Rightarrow x=64$. Тогда $S_{\\text{сеч}}=25$.'),
CS('Ответ: $25$.'),
FC('$V$ пирамиды', '$\\tfrac{1}{3}S_{\\text{осн}}\\cdot h$'),
FC('Отношение площадей сечения и основания (сечение $\\parallel$ основанию)', '$k^2$, где $k$ — отношение высот от вершины'),
CI('Тренажёр: В13 и В17 по теме «Стереометрия». Цель: не менее 75%.'),
];
// M28 — тела вращения (А9, В13)
const L3 = [
H('Тела вращения: цилиндр, конус, шар'),
F('S_{\\text{сферы}}=4\\pi R^2,\\qquad V_{\\text{шара}}=\\tfrac{4}{3}\\pi R^3', 'Шар и сфера'),
F('S_{\\text{бок}}=2\\pi R h,\\qquad V=\\pi R^2 h', 'Цилиндр'),
F('S_{\\text{бок}}=\\pi R l,\\qquad V=\\tfrac{1}{3}\\pi R^2 h', 'Конус'),
SIM('Сечение цилиндра плоскостью, параллельной оси'),
CI('Сфера, касающаяся плоскости: радиус в точку касания перпендикулярен плоскости. Расстояние от центра до точки плоскости и радиус образуют прямоугольный треугольник — работает теорема Пифагора.'),
H('Разбор А9', 3),
P('Пример. Квадрат с диагональю $8$ лежит в плоскости $\\alpha$; сфера касается $\\alpha$ в точке пересечения диагоналей; расстояние от центра сферы до вершины квадрата равно $4\\sqrt2$. Найдите площадь сферы.'),
P('Решение. Полудиагональ $=4$. $R^2=(4\\sqrt2)^2-4^2=32-16=16$, $R=4$. Площадь $=4\\pi R^2=64\\pi$.'),
CS('Ответ: $64\\pi$.'),
H('Разбор В13', 3),
P('Пример. Цилиндр рассечён плоскостью, параллельной оси; в сечении квадрат площади $100$; расстояние от оси до плоскости $\\sqrt{39}$. Найдите $\\dfrac{S_{\\text{бок}}}{\\pi}$.'),
P('Решение. Сторона квадрата $=10$ (это и высота, и хорда). $R^2=(\\sqrt{39})^2+5^2=39+25=64$, $R=8$. $S_{\\text{бок}}=2\\pi\\cdot8\\cdot10=160\\pi$.'),
CS('Ответ: $160$.'),
FC('$S$ сферы', '$4\\pi R^2$'),
FC('$V$ шара', '$\\tfrac{4}{3}\\pi R^3$'),
FC('$S_{\\text{бок}}$ конуса', '$\\pi R l$'),
CI('Тренажёр: А9 и В13 по теме «Стереометрия». Цель: не менее 80% (А9) и 70% (В13).'),
];
// M29 — углы и расстояния, координатный метод (В20)
const L4 = [
H('Координатный метод: угол между прямыми'),
P('Универсальный приём для В20: ввести удобную систему координат (вершину фигуры в начало), выписать координаты нужных точек, составить направляющие векторы прямых и найти угол через косинус скалярного произведения. Если геометрия «не идёт» — считайте координатами.'),
F('\\cos\\varphi=\\dfrac{|\\vec a\\cdot\\vec b|}{|\\vec a|\\,|\\vec b|}', 'Угол между прямыми через векторы'),
F('\\vec a\\cdot\\vec b=a_xb_x+a_yb_y+a_zb_z,\\qquad |\\vec a|=\\sqrt{a_x^2+a_y^2+a_z^2}', 'Скалярное произведение и длина'),
SIM('Угол между скрещивающимися прямыми'),
ORD('Расставьте шаги решения В20 координатным методом', [
'Ввести систему координат',
'Выписать координаты точек (учесть отношения деления рёбер)',
'Составить направляющие векторы прямых',
'Найти cos φ через скалярное произведение и длины',
]),
CW('В числителе — модуль скалярного произведения (угол между прямыми не превосходит $90^\\circ$). Частые ошибки В20 — потеря модуля и неверные координаты точек деления рёбер.'),
ACC('Альтернативы (раскрыть)', 'Угол между прямой и плоскостью считают через нормаль плоскости; есть также теорема о трёх синусах. Но координатный метод универсален и почти всегда быстрее в задачах ЦТ.'),
H('Разбор В20', 3),
P('Пример. В кубе $ABCDA_1B_1C_1D_1$ с ребром $1$ найдите $8\\cos^2\\varphi$, где $\\varphi$ — угол между прямыми $AB_1$ и $BC_1$.'),
P('Решение. Координаты: $A(0;0;0)$, $B(1;0;0)$, $B_1(1;0;1)$, $C_1(1;1;1)$. Векторы $\\vec{AB_1}=(1;0;1)$, $\\vec{BC_1}=(0;1;1)$. $\\cos\\varphi=\\dfrac{|1|}{\\sqrt2\\cdot\\sqrt2}=\\dfrac{1}{2}$, поэтому $8\\cos^2\\varphi=8\\cdot\\dfrac14=2$.'),
CS('Ответ: $2$.'),
FC('Угол между прямыми (векторы)', '$\\cos\\varphi=\\dfrac{|\\vec a\\cdot\\vec b|}{|\\vec a||\\vec b|}$'),
FC('Скалярное произведение', '$a_xb_x+a_yb_y+a_zb_z$'),
FC('Длина вектора', '$\\sqrt{a_x^2+a_y^2+a_z^2}$'),
CI('Тренажёр: В20 по теме «Стереометрия» (координатный метод). Цель: не менее 60% — это самые «дорогие» баллы.'),
];
const LESSONS = [
{ title: 'Расположение прямых и плоскостей. Сечения', read: 9, blocks: L1 },
{ title: 'Многогранники: объёмы, площади, подобие', read: 11, blocks: L2 },
{ title: 'Тела вращения: цилиндр, конус, шар', read: 11, blocks: L3 },
{ title: 'Углы и расстояния: координатный метод', read: 12, blocks: L4 },
];
console.log(DRY ? '[DRY-RUN]' : '[APPLY]', `курс id=${course.id}, секция «${SECTION_TITLE}» id=${section.id}`);
const insLesson = db.prepare('INSERT INTO lessons (course_id, title, order_index, is_published, section_id, read_time) VALUES (?,?,?,1,?,?)');
const insBlock = db.prepare('INSERT INTO lesson_blocks (lesson_id, type, order_index, data) VALUES (?,?,?,?)');
LESSONS.forEach((L, i) => {
const ex = db.prepare('SELECT id FROM lessons WHERE course_id=? AND title=?').get(course.id, L.title);
if (ex) { console.log(` есть урок: «${L.title}» (id ${ex.id}) — пропуск`); return; }
if (DRY) { console.log(` + урок «${L.title}» (${L.blocks.length} блоков)`); return; }
const lid = insLesson.run(course.id, L.title, 10 + i + 1, section.id, L.read).lastInsertRowid;
L.blocks.forEach(([type, data], bi) => insBlock.run(lid, type, bi, JSON.stringify(data)));
console.log(` + урок «${L.title}» (id ${lid}, ${L.blocks.length} блоков)`);
});
console.log(DRY ? 'DRY-RUN: ничего не записано.' : 'Готово. Уроки стереометрии добавлены (черновик курса).');
+136
View File
@@ -0,0 +1,136 @@
'use strict';
/*
* Уроки блока «Тригонометрия» курса «ЦЭ/ЦТ — Математика» (по PILOT_TRIGONOMETRY.md).
* Создаёт 3 урока (круг → тождества → уравнения) в секции «Тригонометрия» курса.
* Форматы блоков — РОВНО под рендер frontend/lesson.html (text/heading/callout
* экранируются → только текст; математика через $...$ / $$...$$; callout.style
* = info|warning|success|error). data хранится JSON-строкой (API её парсит).
* ИДЕМПОТЕНТЕН: урок с тем же title в курсе не создаётся повторно.
* Запуск: node backend/scripts/seed_ctmath_lessons_trig.js [--dry]
*/
const db = require('../src/db/db');
const DRY = process.argv.includes('--dry');
const COURSE_TITLE = 'ЦЭ/ЦТ — Математика';
const SECTION_TITLE = 'Тригонометрия';
const course = db.prepare("SELECT id FROM courses WHERE subject_slug='math' AND title=?").get(COURSE_TITLE);
if (!course) { console.error('Нет курса «' + COURSE_TITLE + '». Сначала: node backend/scripts/seed_ctmath_course.js'); process.exit(1); }
const section = db.prepare('SELECT id FROM course_sections WHERE course_id=? AND title=?').get(course.id, SECTION_TITLE);
if (!section) { console.error('Нет секции «' + SECTION_TITLE + '» в курсе ' + course.id); process.exit(1); }
// helpers для краткости описания блоков
const H = (text, level = 2) => ['heading', { text, level }];
const P = (text) => ['text', { text }];
const F = (tex, label) => ['formula', label ? { label, tex } : { tex }];
const CI = (text) => ['callout', { style: 'info', text }];
const CW = (text) => ['callout', { style: 'warning', text }];
const CS = (text) => ['callout', { style: 'success', text }];
const SIM= (caption) => ['sim', { simId: 'trigcircle', caption }];
const FC = (front, back) => ['flashcard', { front, back }];
const QZ = (question, options, correctIndex) => ['quiz', { question, options, correctIndex }];
const ORD= (question, items) => ['ordering', { question, items }];
const MAT= (question, pairs) => ['matching', { question, pairs }];
const ACC= (title, content) => ['accordion', { title, content }];
const TBL= (header, rows) => ['table', { header, rows }];
// ── Урок 1: Тригонометрический круг и значения (А3, базовый) ──
const L1 = [
H('Тригонометрический круг: смысл синуса и косинуса'),
P('Возьмём окружность радиуса 1 с центром в начале координат. При повороте на угол $\\alpha$ точка на этой окружности получает координаты $(\\cos\\alpha;\\ \\sin\\alpha)$. Это определение, из которого выводится вся тригонометрия: заучивать таблицы наизусть не нужно — нужно уметь «читать» круг.'),
F('\\cos\\alpha = x,\\quad \\sin\\alpha = y,\\quad \\operatorname{tg}\\alpha=\\dfrac{y}{x},\\quad \\operatorname{ctg}\\alpha=\\dfrac{x}{y}', 'Определения через единичную окружность'),
SIM('Покрутите угол и следите за координатами точки — это и есть $\\cos\\alpha$ и $\\sin\\alpha$'),
CI('Знаки по четвертям: I (+,+), II (,+), III (,), IV (+,−). Косинус — это абсцисса, синус — ордината.'),
H('Значения для основных углов', 3),
TBL(
['$\\alpha$', '$0$', '$\\tfrac{\\pi}{6}$', '$\\tfrac{\\pi}{4}$', '$\\tfrac{\\pi}{3}$', '$\\tfrac{\\pi}{2}$'],
[
['$\\sin\\alpha$', '$0$', '$\\tfrac{1}{2}$', '$\\tfrac{\\sqrt2}{2}$', '$\\tfrac{\\sqrt3}{2}$', '$1$'],
['$\\cos\\alpha$', '$1$', '$\\tfrac{\\sqrt3}{2}$', '$\\tfrac{\\sqrt2}{2}$', '$\\tfrac{1}{2}$', '$0$'],
['$\\operatorname{tg}\\alpha$', '$0$', '$\\tfrac{\\sqrt3}{3}$', '$1$', '$\\sqrt3$', '—'],
]
),
F('\\sin x = 0 \\iff x=\\pi k;\\qquad \\cos x = 0 \\iff x=\\tfrac{\\pi}{2}+\\pi k', 'Когда функция равна нулю'),
CW('Типичная ошибка — путать, где ноль у синуса (при $0,\\ \\pi,\\ 2\\pi,\\dots$) и у косинуса (при $\\tfrac{\\pi}{2},\\ \\tfrac{3\\pi}{2},\\dots$). На круге это видно сразу: синус — высота, косинус — горизонталь.'),
FC('$\\sin x = 0$ при каких $x$?', '$x = \\pi k,\\ k\\in\\mathbb{Z}$'),
FC('$\\cos x = 0$ при каких $x$?', '$x = \\tfrac{\\pi}{2}+\\pi k$'),
H('Разбор задания А3', 3),
P('Типичное А3: среди нескольких значений аргумента указать то, при котором функция равна нулю.'),
P('Пример. Среди $-\\tfrac{\\pi}{6};\\ \\tfrac{\\pi}{4};\\ \\tfrac{\\pi}{3};\\ -\\tfrac{3\\pi}{2};\\ -6\\pi$ укажите то, при котором $\\sin x = 0$.'),
P('Решение. $\\sin x=0$ только когда $x$ кратно $\\pi$. Из списка кратно $\\pi$ лишь $-6\\pi$.'),
CS('Ответ: $-6\\pi$.'),
QZ('При каком значении аргумента cos x = 1?', ['π/2', 'π', '0', '3π/2'], 2),
CI('Тренажёр по теме «Тригонометрия» (реальные задания А3 прошлых лет) — в практике курса. Цель освоения: не менее 90% на заданиях А3.'),
];
// ── Урок 2: Тождества и формулы (А8, В4, средний) ──
const L2 = [
H('Тождества: как не учить 30 формул'),
F('\\sin^2\\alpha+\\cos^2\\alpha=1', 'Основное тригонометрическое тождество'),
P('Это теорема Пифагора для точки $(\\cos\\alpha;\\ \\sin\\alpha)$ на единичной окружности. Разделив его на $\\cos^2\\alpha$ и на $\\sin^2\\alpha$, получаем связи с тангенсом и котангенсом — их выводят на месте, а не заучивают.'),
F('1+\\operatorname{tg}^2\\alpha=\\dfrac{1}{\\cos^2\\alpha},\\qquad 1+\\operatorname{ctg}^2\\alpha=\\dfrac{1}{\\sin^2\\alpha}'),
ACC('Формулы сложения и двойного угла (раскрыть)', 'Сложение: $\\sin(\\alpha\\pm\\beta)=\\sin\\alpha\\cos\\beta\\pm\\cos\\alpha\\sin\\beta$; $\\cos(\\alpha\\pm\\beta)=\\cos\\alpha\\cos\\beta\\mp\\sin\\alpha\\sin\\beta$. Двойной угол: $\\sin 2\\alpha=2\\sin\\alpha\\cos\\alpha$; $\\cos 2\\alpha=\\cos^2\\alpha-\\sin^2\\alpha$. Все они следуют из формул сложения.'),
CI('Обратные функции и их области значений (на них ловят в А8): $\\arcsin x\\in[-\\tfrac{\\pi}{2};\\tfrac{\\pi}{2}]$, $\\arccos x\\in[0;\\pi]$, $\\operatorname{arctg} x\\in(-\\tfrac{\\pi}{2};\\tfrac{\\pi}{2})$.'),
MAT('Сопоставьте выражение и тождественно равное ему', [
{ left: '$\\sin 2\\alpha$', right: '$2\\sin\\alpha\\cos\\alpha$' },
{ left: '$\\cos 2\\alpha$', right: '$\\cos^2\\alpha-\\sin^2\\alpha$' },
{ left: '$1-\\cos 2\\alpha$', right: '$2\\sin^2\\alpha$' },
]),
H('Разбор А8 (обратные функции и модуль)', 3),
P('Пример. Найдите значение $\\dfrac{38}{\\pi}\\cdot\\arcsin(-1)-|-7|$.'),
P('Решение. $\\arcsin(-1)=-\\tfrac{\\pi}{2}$, поэтому $\\dfrac{38}{\\pi}\\cdot\\left(-\\tfrac{\\pi}{2}\\right)=-19$; далее $-19-7=-26$.'),
CS('Ответ: $-26$.'),
H('Разбор В4 (тождество)', 3),
P('Пример. Найдите $\\operatorname{ctg}^2\\alpha$, если $\\sin\\alpha=\\tfrac{1}{5}$.'),
P('Решение. $\\cos^2\\alpha=1-\\tfrac{1}{25}=\\tfrac{24}{25}$, поэтому $\\operatorname{ctg}^2\\alpha=\\dfrac{\\cos^2\\alpha}{\\sin^2\\alpha}=\\dfrac{24/25}{1/25}=24$.'),
CS('Ответ: $24$.'),
FC('$1+\\operatorname{tg}^2\\alpha$', '$\\dfrac{1}{\\cos^2\\alpha}$'),
FC('$\\cos 2\\alpha$', '$\\cos^2\\alpha-\\sin^2\\alpha=1-2\\sin^2\\alpha=2\\cos^2\\alpha-1$'),
FC('Область значений $\\arccos x$', '$[0;\\ \\pi]$'),
CI('Тренажёр: реальные задания А8 и В4 в практике курса. Цель освоения: не менее 85%.'),
];
// ── Урок 3: Уравнения и отбор корней (В15, продвинутый) ──
const L3 = [
H('Тригонометрические уравнения и отбор корней'),
F('\\sin x=a\\Rightarrow x=(-1)^n\\arcsin a+\\pi n;\\quad \\cos x=a\\Rightarrow x=\\pm\\arccos a+2\\pi n;\\quad \\operatorname{tg} x=a\\Rightarrow x=\\operatorname{arctg} a+\\pi n', 'Формулы корней простейших уравнений'),
P('Стратегия В15: сначала свести уравнение к произведению или простейшему виду формулами преобразования; затем выписать общие формулы корней; затем отобрать корни, попадающие в заданный промежуток; и наконец выполнить требуемое (например, найти сумму корней).'),
ORD('Расставьте шаги решения В15 по порядку', [
'Преобразовать уравнение к произведению или простейшему виду',
'Выписать общие формулы корней',
'Подставить целые n и отобрать корни на заданном промежутке',
'Сложить (или иначе обработать) отобранные корни',
]),
SIM('Отбор корней: отметьте промежуток и проверьте, какие корни в него попадают'),
CW('Самая частая потеря баллов в В15 — неполный отбор корней и потеря ОДЗ (для $\\operatorname{tg}$ и $\\operatorname{ctg}$). Проверяйте оба семейства корней.'),
H('Разбор простого примера', 3),
P('Найдите (в градусах) сумму корней уравнения $\\cos 2x=0$ на промежутке $(0^\\circ;\\ 180^\\circ)$.'),
P('Решение. $\\cos 2x=0\\Rightarrow 2x=90^\\circ+180^\\circ k\\Rightarrow x=45^\\circ+90^\\circ k$. На промежутке лежат $45^\\circ$ и $135^\\circ$. Их сумма равна $180$.'),
CS('Ответ: $180$.'),
ACC('Более сложный пример (В15 из ЦЭ-2024) — раскрыть', 'Найдите сумму различных корней уравнения $2\\sin 3x\\cos 3x-\\sin 6x\\sin 10x=0$ на промежутке $(-150^\\circ;-55^\\circ)$. Идея: $2\\sin 3x\\cos 3x=\\sin 6x$, выносим общий множитель: $\\sin 6x\\,(1-\\sin 10x)=0$. Дальше решаем $\\sin 6x=0$ или $\\sin 10x=1$ и отбираем корни на промежутке.'),
FC('$\\sin x=a$ (корни)', '$x=(-1)^n\\arcsin a+\\pi n$'),
FC('$\\cos x=a$ (корни)', '$x=\\pm\\arccos a+2\\pi n$'),
FC('$\\operatorname{tg} x=a$ (корни)', '$x=\\operatorname{arctg} a+\\pi n$'),
CI('Тренажёр: тема «Тригонометрические уравнения» (В15) в практике курса. Цель освоения: не менее 70%, отбор корней без потерь.'),
];
const LESSONS = [
{ title: 'Тригонометрический круг и значения', read: 9, blocks: L1 },
{ title: 'Тождества и формулы', read: 10, blocks: L2 },
{ title: 'Уравнения и отбор корней', read: 11, blocks: L3 },
];
console.log(DRY ? '[DRY-RUN]' : '[APPLY]', `курс id=${course.id}, секция «${SECTION_TITLE}» id=${section.id}`);
const insLesson = db.prepare('INSERT INTO lessons (course_id, title, order_index, is_published, section_id, read_time) VALUES (?,?,?,1,?,?)');
const insBlock = db.prepare('INSERT INTO lesson_blocks (lesson_id, type, order_index, data) VALUES (?,?,?,?)');
LESSONS.forEach((L, i) => {
const ex = db.prepare('SELECT id FROM lessons WHERE course_id=? AND title=?').get(course.id, L.title);
if (ex) { console.log(` есть урок: «${L.title}» (id ${ex.id}) — пропуск`); return; }
if (DRY) { console.log(` + урок «${L.title}» (${L.blocks.length} блоков)`); return; }
const lid = insLesson.run(course.id, L.title, i + 1, section.id, L.read).lastInsertRowid;
L.blocks.forEach(([type, data], bi) => insBlock.run(lid, type, bi, JSON.stringify(data)));
console.log(` + урок «${L.title}» (id ${lid}, ${L.blocks.length} блоков)`);
});
console.log(DRY ? 'DRY-RUN: ничего не записано.' : 'Готово. Уроки тригонометрии добавлены в курс (черновик; ученикам видны после публикации курса).');
+1 -1
View File
@@ -525,7 +525,7 @@ function getFeatures(_req, res) {
function updateFeatures(req, res) {
const allowed = ['crossword', 'hangman', 'pet', 'red_book', 'collection',
'flashcards', 'knowledge_map', 'board', 'biochem', 'live_quiz', 'classroom',
'gamification', 'assistant', 'sim_builder'];
'gamification', 'assistant', 'sim_builder', 'quantik'];
const updates = req.body;
const stmt = db.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES (?, ?)");
const getOld = db.prepare("SELECT value FROM app_settings WHERE key = ?");
@@ -0,0 +1,90 @@
-- ═══════════════════════════════════════════════════════════════
-- 077: ЦЭ/ЦТ по математике — трек 'ctmath' + дерево тем exam_topics
--
-- План: plans/ct-math/ (PLAN.md, TOPICS_SEED.md).
-- Формат экзамена: часть А (А1–А10, выбор из 5) + часть В (В1–В20,
-- открытый ответ) = 30 заданий, ~180 мин, до 100 тестовых баллов.
--
-- Двухуровневая иерархия (как 024_exam_topics_seed): раздел (parent=NULL)
-- → подтема. textbook_slug ссылается на существующие учебники платформы
-- (algebra-7..11, geometry-8..11, math-5/6); textbook_paragraph пока NULL
-- (точные § проставляются при маппинге контента).
--
-- variants_count=0 — варианты ещё не оцифрованы (см. DIGITIZATION_SPEC.md).
-- scoring_json — иллюстративный placeholder; заменить официальной
-- таблицей РИКЗ (первичный→тестовый) при наличии вариантов.
-- ═══════════════════════════════════════════════════════════════
-- ── Трек ─────────────────────────────────────────────────────────
INSERT INTO exam_tracks (
exam_key, title, subject_slug, grade, duration_min,
tasks_per_variant, variants_count, scoring_json, intro_html, enabled, sort_order
) VALUES (
'ctmath',
'ЦЭ/ЦТ — Математика',
'math',
11,
180,
30,
0,
'[{"correct":30,"score":100},{"correct":28,"score":92},{"correct":26,"score":85},{"correct":24,"score":78},{"correct":22,"score":71},{"correct":20,"score":64},{"correct":18,"score":57},{"correct":16,"score":50},{"correct":14,"score":43},{"correct":12,"score":36},{"correct":10,"score":30},{"correct":8,"score":24},{"correct":6,"score":17},{"correct":4,"score":11},{"correct":2,"score":5},{"correct":0,"score":0}]',
'<p><b>Подготовка к ЦЭ/ЦТ по математике.</b> Формат: часть А — 10 заданий с выбором ответа (А1–А10), часть В — 20 заданий с открытым ответом (В1–В20), всего 30 заданий, ~180 минут, без калькулятора.</p><p>Курс устроен по темам с входной диагностикой и тремя уровнями сложности (База / Ядро / Продвинутый): теория с выводом формул, разбор эталонных задач, тренажёр по темам, карточки формул с интервальным повторением и пробные экзамены с таймером на реальных вариантах РТ/ЦТ прошлых лет.</p>',
1,
20
);
-- ── Разделы (parent_slug = NULL) ─────────────────────────────────
INSERT INTO exam_topics (slug, exam_key, parent_slug, title, description, sort_order, textbook_slug) VALUES
('numbers', 'ctmath', NULL, 'Числа и вычисления', 'Действительные числа, делимость, проценты, преобразование числовых выражений', 10, 'math-6'),
('expressions', 'ctmath', NULL, 'Алгебраические преобразования','Многочлены, степени и корни, рациональные дроби, ОДЗ', 20, 'algebra-7'),
('equations', 'ctmath', NULL, 'Уравнения и неравенства', 'Линейные, квадратные, рациональные, модуль, иррациональные, показательные, логарифмические; метод рационализации', 30, 'algebra-9'),
('functions', 'ctmath', NULL, 'Функции и производная', 'Свойства функций, графики, исследование с производной', 40, 'algebra-9-ch2'),
('trigonometry', 'ctmath', NULL, 'Тригонометрия', 'Круг, тождества, уравнения и отбор корней', 50, 'algebra-10-ch1'),
('word-sequences', 'ctmath', NULL, 'Прогрессии и текстовые задачи','Арифметическая/геометрическая прогрессии; проценты, движение, работа, смеси', 60, 'algebra-9-ch4'),
('planimetry', 'ctmath', NULL, 'Планиметрия', 'Треугольники, четырёхугольники, окружность; координатный метод', 70, 'geometry-8'),
('stereometry', 'ctmath', NULL, 'Стереометрия', 'Расположение, многогранники, тела вращения, углы и расстояния', 80, 'geometry-10'),
('advanced', 'ctmath', NULL, 'Продвинутое и комбинированное','Параметры, комбинированные задачи, функциональные методы', 90, NULL);
-- ── Подтемы (parent_slug = раздел) ───────────────────────────────
INSERT INTO exam_topics (slug, exam_key, parent_slug, title, sort_order, textbook_slug) VALUES
-- Числа и вычисления
('num-real', 'ctmath', 'numbers', 'Действительные числа, координатная прямая', 11, 'math-6'),
('num-divisibility', 'ctmath', 'numbers', 'Делимость, дроби, НОД/НОК', 12, 'math-5-ch1'),
('num-expressions', 'ctmath', 'numbers', 'Преобразование числовых выражений', 13, 'algebra-7-ch2'),
-- Алгебраические преобразования
('expr-polynomials', 'ctmath', 'expressions', 'Многочлены, ФСУ, разложение на множители', 21, 'algebra-7-ch2'),
('expr-powers-roots', 'ctmath', 'expressions', 'Степени и корни, ОДЗ выражений', 22, 'algebra-10-ch2'),
('expr-fractions', 'ctmath', 'expressions', 'Рациональные (алгебраические) дроби', 23, 'algebra-9-ch1'),
-- Уравнения и неравенства
('eq-linear', 'ctmath', 'equations', 'Линейные уравнения/неравенства, системы', 31, 'algebra-7-ch3'),
('eq-quadratic', 'ctmath', 'equations', 'Квадратные уравнения/неравенства, Виет', 32, 'algebra-8'),
('eq-rational', 'ctmath', 'equations', 'Рациональные уравнения/неравенства, метод интервалов', 33, 'algebra-9-ch3'),
('eq-modulus', 'ctmath', 'equations', 'Уравнения и неравенства с модулем', 34, 'algebra-9'),
('eq-irrational', 'ctmath', 'equations', 'Иррациональные уравнения/неравенства', 35, 'algebra-10-ch2'),
('eq-exponential', 'ctmath', 'equations', 'Показательные уравнения/неравенства', 36, 'algebra-11-ch2'),
('eq-logarithmic', 'ctmath', 'equations', 'Логарифмические уравнения/неравенства', 37, 'algebra-11-ch3'),
('eq-rationalization','ctmath', 'equations', 'Метод рационализации (замена множителей)', 38, 'algebra-11'),
-- Функции и производная
('fn-properties', 'ctmath', 'functions', 'Свойства функций: ОДЗ, чётность, монотонность', 41, 'algebra-9-ch2'),
('fn-graphs', 'ctmath', 'functions', 'Графики и их преобразования, чтение графиков', 42, 'algebra-9-ch2'),
('fn-derivative', 'ctmath', 'functions', 'Производная: монотонность, экстремумы, исследование', 43, 'algebra-10-ch3'),
-- Тригонометрия
('trig-circle', 'ctmath', 'trigonometry', 'Тригонометрический круг, значения, простейшие уравнения', 51, 'algebra-10-ch1'),
('trig-identities', 'ctmath', 'trigonometry', 'Тождества и формулы (вывод), обратные функции', 52, 'algebra-10-ch1'),
('trig-equations', 'ctmath', 'trigonometry', 'Тригонометрические уравнения, отбор корней', 53, 'algebra-10-ch1'),
-- Прогрессии и текстовые задачи
('seq-progressions', 'ctmath', 'word-sequences', 'Арифметическая и геометрическая прогрессии', 61, 'algebra-9-ch4'),
('word-problems', 'ctmath', 'word-sequences', 'Текстовые: проценты, движение, работа, смеси', 62, 'math-6-ch2'),
-- Планиметрия
('plan-triangles', 'ctmath', 'planimetry', 'Треугольники, площади, теоремы синусов/косинусов, окружности', 71, 'geometry-8'),
('plan-quadrilaterals','ctmath','planimetry', 'Четырёхугольники и правильные многоугольники', 72, 'geometry-8-ch1'),
('plan-circle', 'ctmath', 'planimetry', 'Окружность: углы, касательные; координатный метод', 73, 'geometry-8-ch4'),
-- Стереометрия
('ster-basics', 'ctmath', 'stereometry', 'Расположение прямых/плоскостей, сечения', 81, 'geometry-10'),
('ster-polyhedra', 'ctmath', 'stereometry', 'Многогранники: объёмы, площади, сечения, подобие', 82, 'geometry-10'),
('ster-rotation', 'ctmath', 'stereometry', 'Тела вращения: цилиндр, конус, шар/сфера', 83, 'geometry-11'),
('ster-angles-distances','ctmath','stereometry', 'Углы и расстояния; координатно-векторный метод', 84, 'geometry-11'),
-- Продвинутое
('adv-parameters', 'ctmath', 'advanced', 'Задачи с параметрами', 91, NULL),
('adv-combined', 'ctmath', 'advanced', 'Комбинированные задачи, нестандартные приёмы', 92, NULL),
('adv-functional', 'ctmath', 'advanced', 'Функциональные методы, целые числа (бонус)', 93, NULL);
+44 -3
View File
@@ -1,7 +1,7 @@
'use strict';
const router = require('express').Router();
const db = require('../db/db');
const { authMiddleware } = require('../middleware/auth');
const { authMiddleware, requireRole } = require('../middleware/auth');
const access = require('../services/contentAccess');
router.use(authMiddleware);
@@ -416,6 +416,28 @@ router.get('/tracks', (req, res) => {
res.json({ tracks });
});
/* ── Админ: управление экзамен-модулями (вкл/выкл) ──
Отдельные пути (без :examKey, чтобы не задеть гейт content_access). */
router.get('/admin/tracks', requireRole('admin'), (_req, res) => {
const tracks = db.prepare(`
SELECT exam_key, title, subject_slug, grade, enabled, variants_count, sort_order,
(SELECT COUNT(*) FROM exam_tasks t WHERE t.exam_key = exam_tracks.exam_key) AS task_count
FROM exam_tracks
ORDER BY sort_order, exam_key
`).all();
res.json({ tracks });
});
router.patch('/admin/track', requireRole('admin'), (req, res) => {
const key = String(req.body && req.body.exam_key || '').trim();
if (!key) return res.status(400).json({ error: 'exam_key required' });
if (!db.prepare('SELECT 1 FROM exam_tracks WHERE exam_key = ?').get(key))
return res.status(404).json({ error: 'Unknown exam track' });
const enabled = req.body && req.body.enabled ? 1 : 0;
db.prepare('UPDATE exam_tracks SET enabled = ? WHERE exam_key = ?').run(enabled, key);
res.json({ ok: true, exam_key: key, enabled });
});
/* ── GET /api/exam-prep/:examKey/info ──
Track metadata + global counts + this user's aggregate progress. */
// @public-by-design: router-level authMiddleware (line 6) covers this route
@@ -630,13 +652,15 @@ function pickRandomByDifficulty(examKey, count, excludeSlugs) {
? `AND (subtopic IS NULL OR subtopic NOT IN (${exParams.map(() => '?').join(',')}))`
: '';
const COLS = `id, task_idx, variant, task_type, text_html, figure_html, opts_json,
answer, solution_html, topic, subtopic, difficulty, textbook_slug, textbook_paragraph`;
const out = [];
const seen = new Set();
for (let d = 1; d <= 5; d++) {
const limit = dist[d - 1];
if (limit === 0) continue;
const sql = `
SELECT id, task_idx, variant, task_type, text_html, figure_html, opts_json,
answer, solution_html, topic, subtopic, difficulty, textbook_slug, textbook_paragraph
SELECT ${COLS}
FROM exam_tasks
WHERE exam_key = ? AND task_type IN ('mc','open')
AND difficulty = ?
@@ -646,8 +670,25 @@ function pickRandomByDifficulty(examKey, count, excludeSlugs) {
const args = exParams
? [examKey, d, ...exParams, limit]
: [examKey, d, limit];
for (const r of db.prepare(sql).all(...args)) { if (!seen.has(r.id)) { seen.add(r.id); out.push(r); } }
}
// Backfill to `count` from any difficulty — covers tracks whose tasks don't
// span all 5 difficulty levels (otherwise empty levels would shrink the batch).
if (out.length < count) {
const ids = [...seen];
const notIn = ids.length ? `AND id NOT IN (${ids.map(() => '?').join(',')})` : '';
const sql = `
SELECT ${COLS}
FROM exam_tasks
WHERE exam_key = ? AND task_type IN ('mc','open')
${exClause}
${notIn}
ORDER BY RANDOM()
LIMIT ?`;
const args = [examKey, ...(exParams || []), ...ids, count - out.length];
out.push(...db.prepare(sql).all(...args));
}
out.sort((a, b) => (a.difficulty || 0) - (b.difficulty || 0));
return out;
}
+14
View File
@@ -1067,6 +1067,9 @@
<button class="admin-nav-item" data-tab="sims" onclick="switchTab(this)" id="btn-tab-sims" style="display:none">
<i data-lucide="atom" style="width:15px;height:15px"></i> Симуляции
</button>
<button class="admin-nav-item" data-tab="exams" onclick="switchTab(this)" id="btn-tab-exams" style="display:none">
<i data-lucide="clipboard-check" style="width:15px;height:15px"></i> Экзамен-модули
</button>
<button class="admin-nav-item" data-tab="games" onclick="switchTab(this)" id="btn-tab-games" style="display:none">
<i data-lucide="gamepad-2" style="width:15px;height:15px"></i> Игры
</button>
@@ -1616,6 +1619,16 @@
<div id="topics-list"></div>
</div>
<!-- ── Экзамен-модули (вкл/выкл) ── -->
<div class="tab-pane" id="tab-exams">
<div class="section-title">Экзамен-модули</div>
<p style="color:var(--muted);font-size:13px;margin:4px 0 16px;max-width:760px">
Включение/выключение модулей подготовки к экзамену (<code>/exam-prep</code>). Выключенный модуль
скрыт у учеников и не показывается в каталоге прав доступа. Доступ ученикам открывается отдельно
в разделе «Доступ · контент» → «Экзамены».</p>
<div class="perm-grid" id="exams-grid"></div>
</div>
<!-- ── Доступ к учебникам / экзаменам ── -->
<div class="tab-pane" id="tab-access">
<div class="section-title">Доступ к учебникам и экзаменам</div>
@@ -2136,6 +2149,7 @@
<script src="/js/admin/sections/overview.js"></script>
<script src="/js/admin/sections/sublog.js"></script>
<script src="/js/admin/sections/sims.js"></script>
<script src="/js/admin/sections/exams.js"></script>
<script src="/js/admin/sections/games.js"></script>
<script src="/js/admin/sections/assistant.js"></script>
<script src="/js/admin/sections/imggen.js"></script>
+34
View File
@@ -388,6 +388,40 @@
border-color: var(--violet) !important; color: var(--violet) !important; background: rgba(155,93,229,.12) !important;
}
/* ── stereo panel: collapsible accordion (UX) ── */
.stereo-panel .st-acc-toolbar { display: flex; gap: 6px; margin: 0 0 8px; }
.stereo-panel .st-acc-toolbar button {
flex: 1; padding: 5px 6px; border-radius: 8px; border: 1px solid var(--border);
background: transparent; color: var(--text-3);
font-family: 'Manrope', sans-serif; font-size: .62rem; font-weight: 700;
cursor: pointer; transition: all .12s;
}
.stereo-panel .st-acc-toolbar button:hover {
color: var(--violet); border-color: rgba(155,93,229,.4); background: rgba(155,93,229,.06);
}
.stereo-panel .st-acc-hdr {
cursor: pointer; justify-content: space-between; user-select: none;
margin: 3px 0; padding: 8px 8px; border-radius: 9px;
background: rgba(255,255,255,.025); transition: background .12s, color .12s;
}
.stereo-panel .st-acc-hdr::after { display: none; } /* drop the divider line */
.stereo-panel .st-acc-hdr:hover { background: rgba(155,93,229,.09); color: var(--violet); }
.stereo-panel .st-acc-hdr.open { color: var(--text-2); background: rgba(155,93,229,.06); }
.stereo-panel .st-acc-chev { display: flex; align-items: center; opacity: .6; transition: transform .18s; }
.stereo-panel .st-acc-chev svg { width: 13px; height: 13px; stroke: currentColor; stroke-width: 2.5; fill: none; }
.stereo-panel .st-acc-hdr.open .st-acc-chev { transform: rotate(180deg); }
.stereo-panel .st-acc-body { margin: 0 0 8px; padding: 0 1px; }
.stereo-panel .st-sublabel { opacity: .8; margin: 8px 0 6px; }
/* highlight-polygon colour palette */
.stereo-panel .st-poly-palette { display: flex; gap: 6px; margin: 4px 0 2px; flex-wrap: wrap; }
.stereo-panel .st-sw {
width: 20px; height: 20px; border-radius: 50%; cursor: pointer; padding: 0;
border: 2px solid rgba(255,255,255,.25); transition: transform .1s, border-color .12s, box-shadow .12s;
}
.stereo-panel .st-sw:hover { transform: scale(1.12); }
.stereo-panel .st-sw.active { border-color: #fff; box-shadow: 0 0 0 2px rgba(255,255,255,.25); }
.gp-preset-group { margin-bottom: 8px; }
.gp-preset-label {
font-size: 0.68rem; font-weight: 700; text-transform: uppercase;
+2 -1
View File
@@ -15,7 +15,7 @@
AdminCtx.isAdmin = isAdmin;
/* Admin-only tabs: show to everyone for discoverability, but lock for non-admins */
const ADMIN_ONLY_TABS = ['btn-tab-subjects','btn-tab-permissions','btn-tab-shop','btn-tab-gam','btn-tab-tpl','btn-tab-sims','btn-tab-games','btn-tab-assistant','btn-tab-imggen'];
const ADMIN_ONLY_TABS = ['btn-tab-subjects','btn-tab-permissions','btn-tab-shop','btn-tab-gam','btn-tab-tpl','btn-tab-sims','btn-tab-exams','btn-tab-games','btn-tab-assistant','btn-tab-imggen'];
const lockSvg = '<svg class="adm-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>';
ADMIN_ONLY_TABS.forEach(id => {
const el = document.getElementById(id);
@@ -64,6 +64,7 @@
gam: 'gam',
tpl: 'tpl',
sims: 'sims',
exams: 'exams',
games: 'games',
assistant: 'assistant',
imggen: 'imggen',
+72
View File
@@ -0,0 +1,72 @@
'use strict';
/* admin exams (exam-prep modules) section.
* Список ВСЕХ экзамен-треков (вкл. выключенные) + тумблер enabled.
* Источник: GET /api/exam-prep/admin/tracks; переключение: PATCH /api/exam-prep/admin/track.
* Влияет на видимость модуля в /exam-prep и в каталоге прав доступа (Экзамены). */
(function () {
'use strict';
let inited = false;
let _tracks = [];
const SUBJ = { math: 'Математика', physics: 'Физика', phys: 'Физика', chemistry: 'Химия',
chem: 'Химия', biology: 'Биология', bio: 'Биология' };
function esc(s) {
return String(s == null ? '' : s).replace(/[&<>"']/g, c =>
({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
}
async function load() {
try {
const data = await LS.api('/api/exam-prep/admin/tracks');
_tracks = Array.isArray(data.tracks) ? data.tracks : [];
_render();
} catch (e) { LS.toast('Ошибка загрузки экзамен-модулей: ' + e.message, 'error'); }
}
function _render() {
const grid = document.getElementById('exams-grid');
if (!grid) return;
if (!_tracks.length) { grid.innerHTML = '<p style="color:var(--muted);font-size:13px">Нет экзамен-модулей.</p>'; return; }
grid.innerHTML = _tracks.map(t => {
const subj = SUBJ[t.subject_slug] || t.subject_slug || '';
const meta = [subj, t.grade ? (t.grade + ' кл.') : '', (t.task_count || 0) + ' заданий']
.filter(Boolean).join(' · ');
return `<div class="perm-card${t.enabled ? ' enabled' : ''}" id="examcard-${esc(t.exam_key)}" style="flex-wrap:wrap">
<div class="perm-info">
<div class="perm-label">${esc(t.title)}</div>
<div class="perm-desc" style="font-size:11px;margin-top:2px;opacity:.7">${esc(t.exam_key)}${meta ? ' · ' + esc(meta) : ''}</div>
</div>
<div style="display:flex;align-items:center;gap:10px">
<a href="/exam-prep/${esc(t.exam_key)}" target="_blank" title="Открыть модуль"
style="font-size:.72rem;color:var(--text-2);text-decoration:none;border:1px solid var(--border,rgba(255,255,255,.14));border-radius:8px;padding:4px 8px">Открыть</a>
<label class="perm-toggle" title="${t.enabled ? 'Выключить модуль' : 'Включить модуль'}">
<input type="checkbox" ${t.enabled ? 'checked' : ''} onchange="examToggle('${esc(t.exam_key)}', this.checked)" />
<span class="perm-track"></span>
<span class="perm-thumb"></span>
</label>
</div>
</div>`;
}).join('');
if (window.lucide) lucide.createIcons();
}
async function examToggle(examKey, enabled) {
try {
await LS.api('/api/exam-prep/admin/track', {
method: 'PATCH', body: JSON.stringify({ exam_key: examKey, enabled }),
});
const t = _tracks.find(x => x.exam_key === examKey);
if (t) t.enabled = enabled ? 1 : 0;
const card = document.getElementById('examcard-' + examKey);
if (card) card.classList.toggle('enabled', !!enabled);
LS.toast(enabled ? `«${examKey}» включён` : `«${examKey}» выключен`, enabled ? 'success' : 'warning');
} catch (e) { LS.toast('Ошибка: ' + e.message, 'error'); }
}
window.examToggle = examToggle;
window.AdminSections = window.AdminSections || {};
window.AdminSections.exams = {
init: async () => { if (inited) return; inited = true; await load(); },
reload: load,
};
})();
+2
View File
@@ -18,6 +18,7 @@
{ key: 'live_quiz', label: 'Живая викторина', desc: 'Синхронная викторина в реальном времени для всего класса', icon: 'radio' },
{ key: 'classroom', label: 'Онлайн-уроки (classroom)', desc: 'Синхронные онлайн-уроки с доской и видео', icon: 'video' },
{ key: 'sim_builder', label: 'Конструктор симуляций', desc: 'Создание учителем своих интерактивных симуляций (рабочее поле, формулы, физика, графики)', icon: 'pencil-ruler' },
{ key: 'quantik', label: 'Квантик: Законы Мира', desc: '2D физика-головоломка: уровни на движке симуляций, прогресс, скины', icon: 'rocket' },
];
const FS_FEATURES = [
@@ -28,6 +29,7 @@
{ key: 'red_book', label: 'Красная книга', desc: 'Интерактивная Красная книга РБ: виды, биомы, квесты', icon: 'leaf' },
{ key: 'collection', label: 'Коллекция', desc: 'Коллекция карточек и игровой прогресс ученика', icon: 'layers' },
{ key: 'lab', label: 'Лаборатория', desc: 'Виртуальные симуляции и интерактивные опыты', icon: 'flask-conical' },
{ key: 'quantik', label: 'Квантик: Законы Мира', desc: '2D физика-головоломка на движке симуляций', icon: 'rocket' },
{ key: 'knowledge_map',label: 'Карта знаний', desc: 'Визуальная карта тем и связей между понятиями', icon: 'map' },
{ key: 'flashcards', label: 'Флеш-карточки', desc: 'Карточки для повторения терминов и понятий', icon: 'square-stack' },
{ key: 'imggen', label: 'Генерация картинок (ИИ)', desc: 'ИИ-генерация изображений в ассистенте, флэшкартах, уроках, питомце', icon: 'image' },
+1270 -11
View File
File diff suppressed because it is too large Load Diff
+93
View File
@@ -3569,6 +3569,10 @@
<!-- ── Отображение ── -->
<div class="gp-section-title" style="margin-top:8px;margin-bottom:4px">Отображение</div>
<div onclick="stereoToggleSt('figure',this.querySelector('.st-toggle'))" class="st-toggle-row" title="Показать/скрыть само тело (грани, рёбра, вершины, подписи) — построения и сетка остаются">
<span class="st-toggle-label"><svg viewBox="0 0 24 24"><rect x="3" y="7" width="12" height="12"/><path d="M3 7l4-4h12v12l-4 4M15 3v12"/></svg>Фигура</span>
<div class="st-toggle on" id="stg-figure"></div>
</div>
<div onclick="stereoToggleSt('edges',this.querySelector('.st-toggle'))" class="st-toggle-row">
<span class="st-toggle-label"><svg viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18"/></svg>Рёбра</span>
<div class="st-toggle on" id="stg-edges"></div>
@@ -3670,8 +3674,97 @@
<button class="st-action-btn" onclick="stereoMeasureUndo()">Удалить изм.</button>
<button class="st-action-btn" onclick="stereoMeasureClear()">Очист. изм.</button>
</div>
<div onclick="stereoToggleConnLen(this.querySelector('.st-toggle'))" class="st-toggle-row" title="Показывать длины соединённых отрезков">
<span class="st-toggle-label"><svg viewBox="0 0 24 24"><line x1="4" y1="12" x2="20" y2="12" stroke-dasharray="4,2"/><line x1="4" y1="9" x2="4" y2="15"/><line x1="20" y1="9" x2="20" y2="15"/></svg>Длины отрезков</span>
<div class="st-toggle on" id="stg-connlen"></div>
</div>
<div id="points-info" style="font-size:0.65rem;color:rgba(255,255,255,0.4);margin-top:2px"></div>
<!-- ── Выделение цветом (многоугольник по точкам) ── -->
<div class="gp-section-title" style="margin-top:8px;margin-bottom:6px">Выделение цветом</div>
<div class="st-tool-grid">
<button class="st-tool-btn st-tool-btn-wide" id="stereo-poly-btn" onclick="stereoPolyMode(this)" title="Кликайте точки/вершины по контуру → область заливается выбранным цветом">
<svg viewBox="0 0 24 24"><polygon points="4,20 9,5 19,9 16,20" fill="currentColor" fill-opacity="0.25"/><circle cx="4" cy="20" r="1.8" fill="currentColor"/><circle cx="9" cy="5" r="1.8" fill="currentColor"/><circle cx="19" cy="9" r="1.8" fill="currentColor"/><circle cx="16" cy="20" r="1.8" fill="currentColor"/></svg>Многоугольник по точкам
</button>
</div>
<div class="st-poly-palette">
<button class="st-sw active" style="background:#F59E0B" onclick="stereoPolyColor('F59E0B',this)" title="Янтарный"></button>
<button class="st-sw" style="background:#06D6E0" onclick="stereoPolyColor('06D6E0',this)" title="Бирюзовый"></button>
<button class="st-sw" style="background:#EF476F" onclick="stereoPolyColor('EF476F',this)" title="Розовый"></button>
<button class="st-sw" style="background:#7BF5A4" onclick="stereoPolyColor('7BF5A4',this)" title="Зелёный"></button>
<button class="st-sw" style="background:#C4B5FD" onclick="stereoPolyColor('C4B5FD',this)" title="Фиолетовый"></button>
<button class="st-sw" style="background:#60A5FA" onclick="stereoPolyColor('60A5FA',this)" title="Синий"></button>
</div>
<div class="st-action-grid" style="margin-top:4px">
<button class="st-action-btn" onclick="stereoPolyClose()">Замкнуть</button>
<button class="st-action-btn" onclick="stereoPolyUndo()">Отменить точку</button>
</div>
<div class="st-action-grid" style="margin-top:3px">
<button class="st-action-btn st-tool-btn-wide" onclick="stereoPolyClear()" style="grid-column:span 2">Очистить выделения</button>
</div>
<div id="poly-hint" style="font-size:0.63rem;color:rgba(255,255,255,0.38);margin-top:3px;line-height:1.4"></div>
<!-- ── Построения (прямые / плоскости) ── -->
<div class="gp-section-title" style="margin-top:8px;margin-bottom:6px">Построения</div>
<div class="st-tool-grid">
<button class="st-tool-btn" id="stereo-line-btn" onclick="stereoLineMode(this)" title="Прямая через 2 точки — кликните две вершины или точки">
<svg viewBox="0 0 24 24"><line x1="3" y1="21" x2="21" y2="3"/><circle cx="6" cy="18" r="2" fill="currentColor"/><circle cx="18" cy="6" r="2" fill="currentColor"/></svg>Прямая
</button>
<button class="st-tool-btn" id="stereo-plane-btn" onclick="stereoPlaneMode(this)" title="Плоскость через 3 точки — кликните три вершины или точки">
<svg viewBox="0 0 24 24"><polygon points="3,16 13,20 21,8 11,4" fill="none"/><circle cx="3" cy="16" r="1.8" fill="currentColor"/><circle cx="21" cy="8" r="1.8" fill="currentColor"/><circle cx="11" cy="4" r="1.8" fill="currentColor"/></svg>Плоскость
</button>
<button class="st-tool-btn st-tool-btn-wide" id="stereo-intersect-btn" onclick="stereoIntersectMode(this)" title="Пересечение: выберите 2 объекта в списке (прямая∩плоскость → точка, плоскость∩плоскость → прямая)">
<svg viewBox="0 0 24 24"><line x1="3" y1="7" x2="21" y2="17"/><line x1="3" y1="17" x2="21" y2="7"/><circle cx="12" cy="12" r="2.4" fill="currentColor"/></svg>Пересечение
</button>
</div>
<div class="st-tool-grid" style="margin-top:3px">
<button class="st-tool-btn" id="stereo-rel-lpar-btn" onclick="stereoRelMode('lpar',this)" title="Прямая, параллельная выбранной прямой, через точку">
<svg viewBox="0 0 24 24"><line x1="4" y1="8" x2="20" y2="6"/><line x1="4" y1="18" x2="20" y2="16"/></svg>∥ прямая
</button>
<button class="st-tool-btn" id="stereo-rel-lperp-btn" onclick="stereoRelMode('lperp',this)" title="Прямая, перпендикулярная выбранной плоскости, через точку">
<svg viewBox="0 0 24 24"><ellipse cx="12" cy="17" rx="9" ry="3" fill="none"/><line x1="12" y1="3" x2="12" y2="17"/><path d="M12 14 L15 14 L15 17" fill="none"/></svg>⟂ прямая
</button>
<button class="st-tool-btn" id="stereo-rel-ppar-btn" onclick="stereoRelMode('ppar',this)" title="Плоскость, параллельная выбранной плоскости, через точку">
<svg viewBox="0 0 24 24"><polygon points="3,11 12,14 21,6 12,3" fill="none"/><polygon points="3,18 12,21 21,13 12,10" fill="none"/></svg>∥ плоск.
</button>
<button class="st-tool-btn" id="stereo-rel-pperp-btn" onclick="stereoRelMode('pperp',this)" title="Плоскость, перпендикулярная выбранной прямой, через точку">
<svg viewBox="0 0 24 24"><polygon points="3,14 12,17 21,9 12,6" fill="none"/><line x1="12" y1="2" x2="12" y2="21"/></svg>⟂ плоск.
</button>
</div>
<div class="st-tool-grid" style="margin-top:3px">
<button class="st-tool-btn" id="stereo-divide-btn" onclick="stereoDivideMode(this)" title="Точка, делящая отрезок AB в отношении m:n — задайте m,n и кликните 2 точки">
<svg viewBox="0 0 24 24"><line x1="3" y1="12" x2="21" y2="12"/><circle cx="3" cy="12" r="1.8" fill="currentColor"/><circle cx="21" cy="12" r="1.8" fill="currentColor"/><circle cx="11" cy="12" r="2.6" fill="currentColor"/></svg>Деление
</button>
<button class="st-tool-btn" id="stereo-dragpt-btn" onclick="stereoDragPointMode(this)" title="Перетаскивать построенные точки мышью (в плоскости экрана)">
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3" fill="currentColor"/><path d="M12 3v3M12 18v3M3 12h3M18 12h3"/></svg>Тащить
</button>
</div>
<div style="display:flex;align-items:center;gap:5px;margin-top:4px;font-size:.72rem;color:rgba(255,255,255,.55)">
<span>m</span>
<input id="st-div-m" type="number" min="0" step="1" value="1" oninput="stereoDivideRatio()" style="width:40px;background:#1a1030;color:#f0e8ff;border:1px solid rgba(255,255,255,.12);border-radius:6px;padding:3px 4px;font-size:.72rem">
<span>:</span>
<input id="st-div-n" type="number" min="0" step="1" value="1" oninput="stereoDivideRatio()" style="width:40px;background:#1a1030;color:#f0e8ff;border:1px solid rgba(255,255,255,.12);border-radius:6px;padding:3px 4px;font-size:.72rem">
<span>n</span><span style="opacity:.55">(AM:MB)</span>
</div>
<div style="display:flex;align-items:center;gap:4px;margin-top:4px">
<input id="st-pt-x" type="number" step="0.5" value="0" title="x" style="width:42px;background:#1a1030;color:#f0e8ff;border:1px solid rgba(255,255,255,.12);border-radius:6px;padding:3px 4px;font-size:.72rem">
<input id="st-pt-y" type="number" step="0.5" value="0" title="y" style="width:42px;background:#1a1030;color:#f0e8ff;border:1px solid rgba(255,255,255,.12);border-radius:6px;padding:3px 4px;font-size:.72rem">
<input id="st-pt-z" type="number" step="0.5" value="0" title="z" style="width:42px;background:#1a1030;color:#f0e8ff;border:1px solid rgba(255,255,255,.12);border-radius:6px;padding:3px 4px;font-size:.72rem">
<button class="st-action-btn" onclick="stereoAddCoordPoint()" style="flex:1" title="Поставить точку по координатам">Точка (x,y,z)</button>
</div>
<div class="st-action-grid" style="margin-top:3px">
<button class="st-action-btn" onclick="stereoConstructHistUndo()" title="Отменить (Ctrl+Z)">Отменить</button>
<button class="st-action-btn" onclick="stereoConstructHistRedo()" title="Вернуть (Ctrl+Shift+Z)">Вернуть</button>
</div>
<div class="st-action-grid" style="margin-top:3px">
<button class="st-action-btn" onclick="stereoConstructUndo()">Удалить последнее</button>
<button class="st-action-btn" onclick="stereoConstructClear()">Очистить</button>
</div>
<div id="construct-hint" style="font-size:0.63rem;color:rgba(255,255,255,0.38);margin-top:3px;line-height:1.4"></div>
<div style="font-size:0.6rem;color:rgba(255,255,255,0.3);margin-top:2px;line-height:1.35">Клик по плоскости в списке — показать её сечением (заливка, площадь, периметр, натуральная величина).</div>
<div id="construct-list" style="font-size:0.7rem;margin-top:4px;line-height:1.6"></div>
<div id="construct-trueshape" style="display:none;margin-top:6px;background:rgba(6,214,224,0.05);border:1px solid rgba(6,214,224,0.18);border-radius:8px;padding:6px"></div>
<!-- ── Метки рёбер ── -->
<div class="gp-section-title" style="margin-top:8px;margin-bottom:6px">Метки рёбер</div>
<div class="st-tool-grid">
+1 -1
View File
@@ -276,7 +276,7 @@
.lesson-nav-btn-prev { justify-content: flex-start; }
.lesson-nav-btn-next { justify-content: flex-end; margin-left: auto; }
.lesson-nav-btn-label { font-size: 0.7rem; font-weight: 600; color: var(--text-3); display: block; }
.lesson-nav-btn-title { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 160px; }
.lesson-nav-btn-title { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 160px; }
/* ── complete button ── */
.lesson-complete-wrap {
+20 -2
View File
@@ -863,6 +863,7 @@ async function hideDisabledFeatures() {
sim_builder: ['/sim-builder', '/sim-builder.html'],
exam9: ['/exam9', '/exam9.html'],
textbooks: ['/textbooks', '/textbooks.html', '/textbook'],
quantik: ['/quantik', '/quantik.html'],
};
for (const [key, hrefs] of Object.entries(map)) {
if (feats[key] === false) {
@@ -885,13 +886,30 @@ async function hideDisabledFeatures() {
}
}
// Exam-prep track links (/exam-prep/<key>): показываем только включённые
// (exam_tracks.enabled) и доступные пользователю треки. /api/exam-prep/tracks
// уже отдаёт enabled-треки, отфильтрованные по правам доступа.
const examLinks = document.querySelectorAll('[href^="/exam-prep/"]');
if (examLinks.length) {
try {
const data = await apiFetch('/api/exam-prep/tracks');
const allowed = new Set((data.tracks || []).map(t => t.exam_key));
examLinks.forEach(el => {
const m = (el.getAttribute('href') || '').match(/^\/exam-prep\/([^/?#]+)/);
if (m && !allowed.has(m[1])) el.style.display = 'none';
});
const cur = window.location.pathname.match(/^\/exam-prep\/([^/?#]+)/);
if (cur && !allowed.has(cur[1])) window.location.href = '/dashboard.html';
} catch { /* сеть/доступ недоступны — ссылки оставляем как есть */ }
}
// Student with no class — restrict to dashboard, homework, library, theory
if (feats._no_class) {
const classOnlyHrefs = [
'/board', '/lab', '/hangman', '/crossword', '/pet',
'/collection', '/collection.html', '/knowledge-map',
'/red-book', '/red-book.html', '/red-book-ecosystem.html', '/red-book-biomes.html',
'/flashcards', '/live-quiz',
'/flashcards', '/live-quiz', '/quantik',
];
classOnlyHrefs.forEach(href => {
document.querySelectorAll(`[href="${href}"]`).forEach(el => el.style.display = 'none');
@@ -902,7 +920,7 @@ async function hideDisabledFeatures() {
'/board', '/lab', '/hangman', '/crossword', '/pet',
'/collection', '/collection-rb', '/knowledge-map',
'/red-book', '/red-book-ecosystem', '/red-book-biomes', '/red-book-games',
'/flashcards', '/live-quiz',
'/flashcards', '/live-quiz', '/quantik',
];
if (classOnlyPaths.some(h => cur === h || cur === h + '.html')) {
window.location.href = '/dashboard';
+1
View File
@@ -83,6 +83,7 @@
${L('/flashcards', 'copy', 'Флэшкарты')}
${L('/question-bank', 'database', 'Банк вопросов', { cls: 'sb-teacher-only', hidden: !isTch })}
${L('/exam-prep/math9', 'clipboard-check', 'Подготовка к экзамену 9')}
${L('/exam-prep/ctmath', 'clipboard-check', 'Подготовка к ЦЭ/ЦТ')}
`)}
${G('practice', 'Практика и игры', `
+67 -1
View File
@@ -64,4 +64,70 @@
---
История: создан 2026-05-30. Фаза 6 добавлена 2026-05-30.
## Раунд «Конструктор» (2026-06-17) — упор на ученика-самоучку (песочница)
Цель: превратить отличный **визуализатор** в полноценный **конструктор** для самостоятельных
построений. Приоритеты, выбранные пользователем: **Фаза A (конструкторное ядро)** и
**Фаза C (сечения+)**. A — фундамент C (сечение через прямую+точку, параллельно прямой/плоскости
опираются на объекты-прямые/плоскости).
### Фаза A — Конструкторное ядро
Прямые и плоскости как объекты первого класса + пересечения + параллели/перпендикуляры +
общий undo/redo + дерево именованных объектов.
- [x] A1 — **Объектная модель + базовые построения.** `_lines[]` (имена a,b,c…), `_planes[]`
(имена α,β,γ…), группа `_constructGroup`, сериализуемое хранение `{x,y,z}`. Инструменты
«Прямая по 2 точкам» и «Плоскость по 3 точкам» (пикинг вершин/точек). Плоскость рисует
полупрозрачный квад + пунктирную рамку + **сечение тела этой плоскостью** (через `_sliceByPlane`,
делает плоскость осмысленной сразу). Панель «Построения», список объектов с уравнением плоскости.
- [x] A2 — **Пересечения** (прямая∩плоскость → точка `_cpoints` имена M,N,K…; плоскость∩плоскость
→ прямая; прямая∩прямая → точка или «скрещиваются») — выбор 2 объектов в дереве (`setIntersectMode`/
`pickConstructObject`). **Интерактивное дерево**: видимость (глаз)/удаление (×) по объекту, выбор
для пересечения. Точки-пересечения пикабельны → по ним строятся новые прямые/плоскости.
- [x] A3 — **Параллели/перпендикуляры** через точку (`setRelMode`/`_onRelClick`): `lpar` прямая ∥
прямой; `lperp` прямая ⟂ плоскости; `ppar` плоскость ∥ плоскости; `pperp` плоскость ⟂ прямой
(= «плоскость по точке и нормали» через `_createPlaneFromPointNormal` — мост к Фазе C). Поток:
кнопка op → выбор опоры в дереве → клик точки. **Общий undo/redo** конструкторного слоя (JSON-
снапшот `_undoStack`/`_redoStack`, кап 60; хуки в create/remove/clear; Ctrl+Z / Ctrl+Shift+Z /
Ctrl+Y + кнопки «Отменить»/«Вернуть»). Видимость — не шаг истории (намеренно).
### UX панели управления (2026-06-17)
- [x] Панель `.stereo-panel` разрослась за фазы A–C до ~14 всегда-раскрытых секций (длинный скролл) →
**сворачиваемый аккордеон**: `_stereoInitPanel()` (вызывается из `_openStereo`, идемпотентно) оборачивает
контролы каждой секции в `.st-acc-body`, заголовки `.gp-section-title` → кликабельные `.st-acc-hdr`
с шевроном; состояние каждой секции в localStorage (`stereo-acc-<имя>`). Тройка фигурных секций
(Многогранники/Правильные/Тела вращения) слита в одну «Фигуры» (под-метки `.st-sublabel`). По
умолчанию открыты «Фигуры» и «Параметры». Кнопки «Развернуть всё / Свернуть всё» (`stereoAccAll`).
Сами контролы/обработчики не тронуты — только раскладка. Только `stereo.js` + `lab.css`.
### Фаза B — Умные точки
- [x] B1 — **Деление отрезка m:n** (`setDivideMode`/`setDivideRatio`): задаёшь m,n → кликаешь 2 точки A,B
→ точка делит AB как AM:MB = m:n (`t = m/(m+n)`), создаётся как `_cpoints` (M,N,K…).
- [x] B2 — **Точка по координатам** (`addPointAt(x,y,z)`): поля x/y/z + кнопка → точка-построение.
- [x] B3 — **Перетаскивание точек** (`setDragPointMode`): pointerdown по точке-построению (приоритет над
орбитой) → drag в плоскости, обращённой к камере (нормаль = направление камеры, фикс. на старте);
`_beginCPointDrag`/`_dragCPointWithRay`/`_rayPlaneHit`/`_endCPointDrag`; снапшот истории на старте
(undo откатывает весь drag). Точки-пересечения/деления непараметрические (downstream-объекты
копируют позицию при создании и за перетаскиванием НЕ следуют — параметрический граф = бэклог).
### Фаза C — Сечения+
- [x] C1 — Сечение **плоскостью-объектом** (из Фазы A): клик по плоскости в дереве (нормальный режим)
`setSectionPlane` показывает её заливкой + подписи вершин K,L,M… + площадь/периметр в readout
(`_activeSectionPolygon`, `getReadout`). Удаление/очистка/смена фигуры сбрасывают сечение.
- [x] C2 — **Покрыто Фазой A** (отдельный код не нужен): сечение через прямую+точку = плоскость по
3 точкам (2 с прямой + 1); сечение ∥ плоскости через точку = rel-операция `ppar` → затем клик
как сечение. Дополнительный UI признан избыточным.
- [x] C3 — **«Натуральная величина» сечения** (`getTrueShape`): разворот многоугольника в его
плоскость (ортонормированный базис от нормали) с сохранением истинных длин → 2D-мини-панель
(SVG, штриховка `<pattern>`, подписи вершин, длины сторон, S/P). Проверено сохранение длин и
площади для прямого и наклонного сечений.
- [ ] C4 — Честный конструктивный алгоритм следов с анимацией (из бэклога Ф6) — **отложено**
(крупная отдельная фича; текущий гибрид Ф6 + сечения-объекты Фазы A/C закрывают практику).
---
История: создан 2026-05-30. Фаза 6 добавлена 2026-05-30. Раунд «Конструктор» (Фазы A,C) — 2026-06-17.
+240
View File
@@ -0,0 +1,240 @@
# Сборка курса ЦЭ/ЦТ на СУЩЕСТВУЮЩЕМ банке `questions` (основной путь)
> ⚠️ ПИВОТ (2026-06-14): контент ЦЭ/ЦТ по математике **уже в базе** — таблица `questions`
> (`subject_id=3`), **1753 задания** за 2011–2025 (включая ЦЭ-2024 = 117, набор 2025 = 1020),
> размечены по темам (`topics`) и годам (`year`), залиты скриптами `backend/scripts/seed_math_ct*.js`.
> Поэтому курс строим **поверх этого банка** через `tests`/`assignments`/`courses`, а НЕ через
> exam-prep (`exam_tasks`). Миграция 077 (пустой exam-prep-скелет `ctmath`) оставлена как опция,
> но это НЕ основной путь и в БД не применяется.
>
> Этот документ заменяет (по части «куда складывать») разделы маппинга в [PLAN.md](PLAN.md) §6
> и спецификацию оцифровки [DIGITIZATION_SPEC.md](DIGITIZATION_SPEC.md). Карта экзамена (§1 PLAN),
> методика (§2), модульная программа (§3), уровни (§4), пилоты ([тригонометрия](PILOT_TRIGONOMETRY.md)/[стереометрия](PILOT_STEREOMETRY.md)) и инвентарь ([RESOURCES.md](RESOURCES.md)) — остаются в силе.
---
## 0. Состояние реализации
**Сделано (скрипт `backend/scripts/seed_ctmath_course.js`, идемпотентный, применён на живой БД 2026-06-14):**
- ✅ Добавлены 6 тем (`topics`, subject_id=3): Преобразование выражений (72), Модуль (73), Иррациональные уравнения (74), Показательные уравнения (75), Производная (76), Параметры (77).
- ✅ Создан **DRAFT-курс** «ЦЭ/ЦТ — Математика» (`courses.id=13`, `is_published=0`, created_by=2) — ученикам НЕ виден до публикации.
- ✅ 9 секций (`course_sections.id=27..35`) = блоки IIX.
**Сделано (скрипт `backend/scripts/seed_ctmath_diagnostic.js`, применён 2026-06-14):**
- ✅ **Диагностический `test`** «Диагностика ЦЭ/ЦТ — Математика» (`tests.id=164`, 15 вопросов, лимит 40 мин, `show_answers=1`) — собран из РЕАЛЬНЫХ размеченных вопросов ЦТ-11 (в осн. 2024): 5 `single` (базовые, по 1 на тему Теория чисел/Арифметика/Квадратные/Тригонометрия/Промежутки) + 10 `fill-blank` (средние/сложные: Словесные/Прогрессии/Функции/Геометрия/Окружность/Стереометрия/Логарифмы/Неравенства/Уравнения/Показательные). Новых вопросов не авторили. Выдать классу/ученику: assignment с `test_id=164`.
**Сделано (скрипт `backend/scripts/seed_ctmath_lessons_trig.js`, применён 2026-06-15):**
- ✅ **Блок «Тригонометрия» — 3 урока** в секции 31 курса 13 (по [PILOT_TRIGONOMETRY.md](PILOT_TRIGONOMETRY.md)):
«Тригонометрический круг и значения» (`lessons.id=41`, 18 блоков, А3 🟢), «Тождества и формулы»
(`id=42`, 19 блоков, А8/В4 🟡), «Уравнения и отбор корней» (`id=43`, 15 блоков, В15 🔴).
Блоки в формате рендера lesson.html (heading/text/formula/callout/sim `trigcircle`/flashcard/quiz/
matching/ordering/accordion/table); математика `$…$`/`$$…$$`; JSON валиден; идемпотентно.
**Дальше (не сделано):** уроки остальных 8 блоков по пилотам (тиражирование, начать со стереометрии);
assignment-практика `mode='topic'`; колоды формул (`flashcard_decks`); публикация курса; выдача
диагностики классу (assignment `test_id=164`). См. §8.
---
## 0a. Решение: ЦТ как ОТДЕЛЬНЫЙ модуль exam-prep (2026-06-15)
По решению пользователя ЦТ оформляется как **отдельный модуль** (как «Экзамен 9»): свой раздел
`/exam-prep/ctmath` с дашбордом, тренажёром по темам, пробниками на таймер, детектором слабых тем.
Это значит: применить трек+дерево тем (миграция **077**) и **перенести размеченные вопросы ЦТ-11 из
банка `questions` в `exam_tasks`** (exam_key='ctmath').
Конвертер: **`backend/scripts/seed_ctmath_exam_tasks.js`** (dry по умолчанию, запись `--apply`).
Правила сверены с exam-prep (агент-разведка): MC-метки кириллица `а,б,в,г,д`, `answer`=метка; open —
числовой/дробь/пара (иначе → `long` self-check); математика `\( \)``$`, `\[ \]``$$` (exam-prep KaTeX
знает только `$`/`$$`!); `subtopic`=slug из 077; `variant`=год; multi/multiple (radio несовместимо) — пропуск.
**Dry-run (2026-06-15):** 733 размеч. вопроса → **723 к вставке** (525 mc + 191 open + 7 long;
10 multi пропущено; 7 не-числовых → long). Делимитеры/метки/ответы корректны (проверено на выборке).
**Статус записи: ПРИМЕНЕНО (2026-06-15).** Миграцию 077 применил пользователь вручную (авто-режим
блокирует продакшн-миграции); конвертер `--apply` — тоже запускал пользователь (объём 723 был
заблокирован авто-режимом). Итог в БД: трек `ctmath` (enabled=1), дерево тем 41 (9+32), **723 задачи
в `exam_tasks`** (525 mc + 191 open + 7 long), variants_count=15. Проверка: осиротевших subtopic 0,
неконвертированных `\(` 0. Модуль доступен на **`/exam-prep/ctmath`** (учителю/админу сразу; ученику —
после `content_access`).
Постфикс (2026-06-15): варианты ответа у части mc были вшиты в текст («1) 44; 2) 22; …»), а opts —
лишь цифры-указатели. Скрипт `fix_ctmath_inline_opts.js --apply` (запускал пользователь) вытащил список
в нормальный `opts_json` и пересчитал answer — **исправлено 213 задач**; инлайн-списков в тексте
осталось **3** (нестандартный формат, не сломаны). Битых opts 0.
Известный мелкий дефект источника: 1 mc-задача `exam_tasks.id=1248` (var 2020) без верного варианта
(дубль опций) — всегда «неверно»; фикс: перевести в `long` или проставить ответ. Плюс ~3 mc с
неразобранным инлайн-списком — при желании вторая итерация парсера.
После applied — осталось: `content_access` (exam/ctmath классу) + пункт сайдбара на `/exam-prep/ctmath`.
> ⚠️ Гоча: рендер exam-prep — ТОЛЬКО `$…$`/`$$…$$` (НЕ `\(…\)`). Конвертер это учитывает.
---
## 1. Что уже есть (проверено чтением БД)
| Таблица | Роль | Факт |
|---|---|---|
| `questions` | банк заданий | 1753 матем. (subject_id=3); `topic_id`, `year`, `difficulty` 13, `type`, `explanation`, `image`, `source_type` |
| `options` | варианты ответов | `question_id`, `text`, `is_correct`, `order_index`, `match_pair` |
| `topics` | темы (ПЛОСКИЕ) | 19 тем по математике (без иерархии): id+subject_id+name+order_index |
| `tests` / `test_questions` | фикс. наборы | тест = упорядоченный список вопросов |
| `test_sessions` / `user_answers` | прохождение + баллы | score = число верных |
| `assignments` | выдача | режимы `exam/practice/topic/repeat/`**`ct`** |
| `courses`/`course_sections`/`lessons`/`lesson_blocks` | теория | общий слой контента |
| `flashcard_*` | карточки + SR | для формул |
### 1.1. Уточнение по инспекции (2026-06-14) — ВАЖНО
Из 1753 матем. заданий:
- **~733 — реальный банк ЦЭ/ЦТ-11** (20112024), **размечены по темам**. Часть A = `single`, **Часть B = `fill-blank`** (198 шт.), `short_answer` у них НЕТ. **Это база курса.**
- **1020 — набор `year=2025`, `source_type='экзамен 9'` = «Экзамен 9 класс», БЕЗ тем** (`topic_id` пустой), все `difficulty=1`, типы `single`+`short_answer`. Это контент 9-класса (из него собраны тесты «Экзамен 9 — Вариант N»). **Для курса ЦЭ/ЦТ-11 — НЕ основа** (другой экзамен/класс; не размечен).
**Типы заданий (`questions.type`)**: `single`, `multi`, `true_false`, `short_answer`, `matching`, `fill-blank`.
**Сложность**: `difficulty` 13 (CHECK), 1=базовый/2=средний/3=продвинутый. (В [PLAN.md](PLAN.md) шкала 1–5 — привести к 1–3: А-часть и лёгкая В = 1, средняя В = 2, сложная В14–В20 = 3.)
**Год**: `year` (2011…2025) — фильтр по году/варианту.
### Текущая таксономия тем (math, `subject_id=3`)
`16 Арифметика и степени` · `17 Словесные задачи` · `18 Теория чисел` · `19 Тригонометрия` · `20 Квадратные уравнения` · `21 Прогрессии` · `22 Неравенства` · `23 Геометрия` · `24 Функции` · `25 Логарифмы` · `26 Показательные неравенства` · `27 Уравнения` · `28 Статистика и диаграммы` · `61 Стереометрия` · `62 Окружность и круг` · `63 Числовые промежутки` · `64 Подобные фигуры` · `67 Парабола` · `69 Тригонометрические уравнения`
---
## 2. Ключевой механизм: режимы `assignments` (выбор вопросов)
Логика `assignmentController.startAssignment` (подтверждено по коду):
- **`mode='ct'`** — собирает ЦТ-вариант: ~половина из `type IN ('single','true_false')` (Часть A) + остаток из `type IN ('multi','short_answer')` (Часть B), добор при нехватке. ⚠️ **Гоча:** реальный банк ЦТ-11 имеет Часть B типа **`fill-blank`**, который `mode='ct'` НЕ выбирает (он ждёт `short_answer`) → для ЦТ-11 Часть B соберётся только «добором». `mode='ct'` хорошо ложится на набор «Экзамен 9» (там Часть B = `short_answer`). **Для ЦТ-11 надёжнее `mode='topic'` или ручная сборка `test` (single + fill-blank).**
- **`mode='topic'`** — `SELECT id FROM questions WHERE subject_id=3 AND topic_id=? ORDER BY RANDOM() LIMIT count`. **Тренажёр по теме модуля.**
- **`mode='exam'|'practice'|'repeat'`** — случайные `count` по предмету (или теме).
- **`test_id` задан** — берутся ИМЕННО вопросы теста в его порядке (перекрывает режим). **Так делается фиксированная диагностика.**
Выдача: `class_id` (классу) или `user_id` (ученику); `count` 1200; `deadline`; `max_attempts` 010.
---
## 3. Таксономия: довести темы под модульную карту
Текущие 19 тем покрывают большинство модулей, но грубее карты §3 [PLAN.md](PLAN.md). Два варианта (рекомендуется А):
**A. Принять текущую гранулярность + добавить недостающее (мин. усилий).**
Добавить новые `topics` (для будущей разметки и точечной практики), существующие вопросы НЕ перетегировать массово:
- `Преобразование выражений` (M3M6)
- `Модуль` (M10)
- `Иррациональные уравнения` (M11)
- `Показательные уравнения` (M12; сейчас есть только «Показательные неравенства»)
- `Производная` (M17)
- `Параметры` (M30)
**B. Полная иерархия** — перетегировать 1753 вопроса под все 32 подтемы. Дорого; не оправдано на старте.
### Маппинг модуль → тема банка (для практики `mode='topic'`)
| Модуль(и) | topic |
|---|---|
| M1M3 Числа | `18 Теория чисел`, `16 Арифметика и степени` |
| M4–M6 Преобразования | `16` (+ новый `Преобразование выражений`) |
| M7 Линейные/системы | `27 Уравнения`, `22 Неравенства`, `63 Числовые промежутки` |
| M8 Квадратные | `20 Квадратные уравнения` |
| M9 Рациональные | `27 Уравнения`, `22 Неравенства` |
| M10 Модуль | новый `Модуль` |
| M11 Иррациональные | новый `Иррациональные уравнения` |
| M12 Показательные | новый `Показательные уравнения`, `26 Показательные неравенства` |
| M13 Логарифмы | `25 Логарифмы` |
| M15–M16 Функции/графики | `24 Функции`, `67 Парабола` |
| M17 Производная | новый `Производная` |
| M18–M19 Тригонометрия | `19 Тригонометрия` |
| M20 Триг. уравнения | `69 Тригонометрические уравнения` |
| M21 Прогрессии | `21 Прогрессии` |
| M22 Текстовые | `17 Словесные задачи` |
| M23–M24 Планиметрия | `23 Геометрия`, `64 Подобные фигуры` |
| M25 Окружность | `62 Окружность и круг` |
| M26–M29 Стереометрия | `61 Стереометрия` |
| M30 Параметры | новый `Параметры` |
| (стат.) | `28 Статистика и диаграммы` |
> Реализация добавления тем — обычным `INSERT INTO topics(subject_id,name)` (как делают seed-скрипты через `getTopic`). Можно отдельной миграцией или скриптом. Перетегировать существующие вопросы под новые темы — опционально и точечно (по тексту/году).
---
## 4. Структура курса (теория) — `courses`/`sections`/`lessons`
Создаётся через API (teacher/admin), наполняется по пилотам:
1. **`courses`**: `subject_slug='math'`, `title='ЦЭ/ЦТ — Математика'`, `is_published=1`, обложка.
2. **`course_sections`**: 9 секций = блоки I–IX из [PLAN.md](PLAN.md) §3.
3. **`lessons`** + **`lesson_blocks`** по шаблону пилотов (heading → теория/формулы → sim/диаграмма → callout «ошибки» → flashcards → quiz). Типы блоков: `text/formula/callout/quiz/sim/geogebra/flashcard/image/table/accordion/ordering/matching`.
4. В уроке кнопка «тренироваться» → assignment `mode='topic'` по теме модуля (или ссылка на практику банка).
5. Доступ ученикам/классам — `content_access`/`course_access` (миграция 052) + `class_courses`.
> Связь урок↔§учебника — через ссылки в `lesson_blocks` (`text` с гиперссылкой на `/textbook/...`), т.к. в `questions`/`topics` нет поля textbook (в отличие от exam-prep). Учебники реальны (algebra-7..11, geometry-8..11) — см. [TOPICS_SEED.md](TOPICS_SEED.md).
---
## 5. Практика и пробники (на банке)
- **Тренажёр модуля** → assignment `mode='topic'`, `topic_id` = тема модуля, `count` 1025, `max_attempts=0`.
- **Пробный вариант ЦТ** → assignment `mode='ct'`, `count=30` (без `topic_id` = по всем темам). Даёт Часть A + Часть B автоматически. Для «на время» — задать через связанный `test.time_limit` (или прохождение сессии; полноценный таймер-пробник как в exam-prep здесь проще через `test` с `time_limit`).
- **Фиксированный набор** (диагностика, тематический срез) → `test` + `test_questions`, затем assignment с `test_id`.
- Сложность регулируется выбором тем + `difficulty` (1–3) при ручной сборке `test`.
---
## 6. Входная диагностика (из реальных вопросов банка, БЕЗ авторинга)
Собрать `test` «Диагностика ЦЭ/ЦТ» из ~14 существующих вопросов — по одному на ключевую тему, желательно `year=2024`:
| # | topic (bank) | критерий выбора | уровень-зонд |
|---|---|---|---|
| 1 | 18 Теория чисел | `single`, diff 1 | 🟢 |
| 2 | 20 Квадратные уравнения | `single`, diff 1 | 🟢 |
| 3 | 19 Тригонометрия | `single`, diff 1 | 🟢 |
| 4 | 16 Арифметика и степени | `single`, diff 12 | 🟢 |
| 5 | 17 Словесные задачи | `short_answer`, diff 2 | 🟡 |
| 6 | 21 Прогрессии | `short_answer`, diff 2 | 🟡 |
| 7 | 24 Функции | diff 2 | 🟡 |
| 8 | 19 Тригонометрия | `short_answer`, diff 2 (тождества) | 🟡 |
| 9 | 23 Геометрия | `short_answer`, diff 2 | 🟡 |
| 10 | 61 Стереометрия | diff 2 | 🟡 |
| 11 | 25 Логарифмы | `short_answer`, diff 3 | 🔴 |
| 12 | 69 Триг. уравнения | diff 3 | 🔴 |
| 13 | 24 Функции / Производная | diff 3 | 🔴 |
| 14 | 61 Стереометрия | diff 3 (углы/расстояния) | 🔴 |
Реализация: выбрать конкретные `question.id` по критериям (`subject_id=3 AND topic_id=? AND difficulty=? [AND year=2024]`), создать `test` + `test_questions` в нужном порядке, выдать как assignment `test_id`. По результату — назначить трек (правила §4 [PLAN.md](PLAN.md)). **Никакого нового авторинга — берём готовые проверенные вопросы.**
---
## 7. Прогресс и аналитика
- Есть: `test_sessions.score/total`, `user_answers.is_correct`, `lesson_progress.completed`.
- **Нет** (в этой подсистеме): автодетектора слабых тем и per-topic mastery (это только в exam-prep). Варианты:
- считать точность по теме join'ом `user_answers`+`questions.topic_id` (есть `assignmentQuestionStats` по вопросам — расширить до тем);
- или (доработка) добавить агрегат «точность по теме» для рекомендаций «что подтянуть».
- Формулы — `flashcard_decks` по блокам + `flashcard_deck_access` классу (SR встроен).
---
## 8. Порядок реализации (на банке `questions`)
1. **Таксономия**: добавить недостающие темы (§3, вариант A) — миграция/скрипт `INSERT INTO topics`.
2. **Курс-каркас**: `courses` + 9 `course_sections`.
3. **Диагностика**: собрать `test` из 14 реальных вопросов (§6), выдать.
4. **Уроки по приоритету** (стерео, тригонометрия — §8 PLAN): теория в `lesson_blocks` по пилотам + кнопка практики `mode='topic'`.
5. **Пробники**: assignment `mode='ct'` (вариант 30 заданий) + тематические `mode='topic'`.
6. **Карточки формул**: `flashcard_decks` по блокам.
7. (Опц.) **Аналитика по темам**: агрегат точности для рекомендаций.
8. (Опц.) Точечно дотегировать вопросы под новые темы (Производная, Иррациональные, Модуль, Показательные ур., Параметры).
---
## 9. Открытые вопросы
| Вопрос | Заметка / дефолт |
|---|---|
| Что за набор `year=2025` (1020 вопросов)? | Уточнить происхождение (свежие ЦЭ/ЦТ-2025?); вероятно главный современный банк — проверить разметку по темам/типам |
| Перетегировать ли под тонкие темы | По умолчанию — нет (вариант A); добавить только новые темы, дотегировать точечно |
| Per-topic mastery / слабые темы | Пока считать join'ом; полноценный детектор — отдельная доработка |
| Нужен ли отдельный признак «ЦТ/ЦЭ» | Уже есть `year` и `source_type`; при необходимости фильтровать по ним |
| Таймер-пробник на 180 мин | Через `test.time_limit` (есть); полноценный mock как в exam-prep — опционально |
| Судьба миграции 077 (exam-prep ctmath) | Оставлена (по решению), в БД не применяется; основной путь — этот документ |
+197
View File
@@ -0,0 +1,197 @@
# Спецификация оцифровки заданий РТ/ЦТ → `exam_tasks` + диагностика
> ⚠️ **БОЛЬШЕЙ ЧАСТЬЮ УЖЕ СДЕЛАНО / ВТОРИЧНО.** Оцифровка ЦЭ/ЦТ по математике **уже выполнена**
> 1753 задания в банке `questions` (скрипты `backend/scripts/seed_math_ct*.js`, 20112025). Поэтому
> этот документ (перенос в `exam_tasks` exam-prep) **не основной**. Сборка курса и диагностики — на
> существующем банке: **[BUILD_ON_QUESTIONS.md](BUILD_ON_QUESTIONS.md)**. Текст ниже актуален лишь
> для (а) будущей добивки годов в формате `seed_math_ct*` (правило: 1 вариант из сборника, без
> повторов — см. память `project_ct_seeded`), либо (б) если перейдём на exam-prep.
> Как переносить задания из PDF (РТ 20062025, ЦТ/ЦЭ 20042024) в банк `exam_tasks` трека `ctmath`,
> как их классифицировать (тема/сложность/тип) и как собрать входной диагностический тест.
> Опирается на реальные конвейеры платформы: `backend/scripts/import-exam-tasks.js` (импорт),
> `tag-exam-tasks.js` (классификатор темы), формат вариантов `frontend/js/exam9/variants/v*.js`.
---
## 1. Схема целевой таблицы (напоминание)
```sql
exam_tasks(
id, exam_key, variant, task_idx,
task_type TEXT CHECK (task_type IN ('mc','open','long')),
text_html, figure_html, opts_json, answer, solution_html,
topic, subtopic, difficulty,
UNIQUE(exam_key, variant, task_idx)
)
```
Для трека: `exam_key='ctmath'`, `variant` = номер варианта (сквозная нумерация по источникам, см. §6), `task_idx` = 1..30.
---
## 2. Маппинг номера задания → тип и подтема
Позиция в ЦЭ/ЦТ почти жёстко задаёт тему (карта §1.2 PLAN.md). Это **дефолт классификатора** — экономит ручную разметку; правится точечно, если конкретный вариант отклонился.
| task_idx | task_type | topic (раздел) | subtopic (по умолчанию) | difficulty |
|---|---|---|---|---|
| 1 (А1) | mc | numbers | `num-real` | 1 |
| 2 (А2) | mc | stereometry | `ster-basics` | 2 |
| 3 (А3) | mc | trigonometry | `trig-circle` | 1 |
| 4 (А4) | mc | numbers | `num-divisibility` | 1 |
| 5 (А5) | mc | equations | `eq-quadratic` | 1 |
| 6 (А6) | mc | equations | `eq-linear` | 2 |
| 7 (А7) | mc | word-sequences | `word-problems` | 2 |
| 8 (А8) | mc | trigonometry | `trig-identities` | 2 |
| 9 (А9) | mc | stereometry | `ster-rotation` | 3 |
| 10 (А10) | mc | expressions | `expr-powers-roots` | 2 |
| 11 (В1) | open | stereometry | `ster-angles-distances` | 3 |
| 12 (В2) | open | functions | `fn-properties` | 2 |
| 13 (В3) | open | word-sequences | `seq-progressions` | 2 |
| 14 (В4) | open | trigonometry | `trig-identities` | 3 |
| 15 (В5) | open | planimetry | `plan-triangles` | 3 |
| 16 (В6) | open | word-sequences | `seq-progressions` | 3 |
| 17 (В7) | open | word-sequences | `word-problems` | 3 |
| 18 (В8) | open | equations | `eq-linear` | 2 |
| 19 (В9) | open | functions | `fn-properties` | 3 |
| 20 (В10) | open | planimetry | `plan-quadrilaterals` | 3 |
| 21 (В11) | open | equations | `eq-logarithmic` | 3 |
| 22 (В12) | open | numbers | `num-divisibility` | 4 |
| 23 (В13) | open | stereometry | `ster-rotation` | 4 |
| 24 (В14) | open | equations | `eq-exponential` | 4 |
| 25 (В15) | open | trigonometry | `trig-equations` | 5 |
| 26 (В16) | open | equations | `eq-logarithmic` | 5 |
| 27 (В17) | open | stereometry | `ster-polyhedra` | 5 |
| 28 (В18) | open | equations | `eq-irrational` | 4 |
| 29 (В19) | open | functions | `fn-derivative` | 5 |
| 30 (В20) | open | stereometry | `ster-angles-distances` | 5 |
> Часть А = `mc` (всегда 5 вариантов ответа). Часть В = `open` (число/слово/комбинация цифр).
> `long` использовать только если ответ не авто-проверяем (в ЦЭ/ЦТ почти не встречается — там всё с коротким ответом).
> ⚠️ Маппинг — стартовый; при оцифровке КАЖДОГО варианта проверять реальную тему задания и при отклонении менять `subtopic`/`difficulty` вручную.
---
## 3. Форматы полей
### 3.1. `text_html`
Условие задания с формулами в KaTeX-разметке (`$...$` инлайн). HTML допустим (списки, `<br>`). Картинки/чертежи — в `figure_html`.
### 3.2. `figure_html`
Чертёж: предпочтительно inline `<svg>` (масштабируемо, тема). Допустимо `<img src="/img/ct/math/...">` (как уже практикуется в репозитории — см. `frontend/img/ct/math/`). NULL, если рисунка нет.
### 3.3. `opts_json` (только `mc`)
Массив пар `[метка, html]`, как в `exam9`:
```json
[["1","$-16$"],["2","$-12$"],["3","$12$"],["4","$26$"],["5","$-26$"]]
```
(В ЦЭ/ЦТ метки — цифры 1–5.)
### 3.4. `answer`
- `mc`: метка верного варианта, напр. `"5"`.
- `open`: строка-эталон ответа. Форматы части В:
- число: `"-26"`, `"24"`, `"153"`;
- комбинация цифр (В1-тип «выберите верные», порядок не важен): хранить нормализованно, напр. отсортированные цифры `"124"`; проверку делать как множество цифр;
- комбинация буква-цифра (В2-тип сопоставление): `"А5Б1В4"` (как в реальном бланке).
- ⚠️ Договориться о нормализации ответа на клиенте (тримминг, запятая/точка в дробях, регистр) — это логика проверки, не данные. В части В реального ЦТ ответ — целое/конечная десятичная дробь; условие часто просит «увеличьте в N раз», чтобы ответ стал целым (учитывать при вводе эталона).
### 3.5. `solution_html`
Полное решение в HTML+KaTeX. Рекомендуется завершать блоком ответа в стиле exam9:
```html
<div class="sol-ans">Ответ: $-26$</div>
```
(`import-exam-tasks.js` умеет парсить `answer` из такого блока — переиспользовать конвейер.)
### 3.6. `topic` / `subtopic` / `difficulty`
- `topic` = slug раздела, `subtopic` = slug подтемы (из [TOPICS_SEED.md](TOPICS_SEED.md)).
- Дефолт — по таблице §2; правка вручную при отклонении.
- `difficulty` 1–5: 1–2 = часть А и лёгкая В; 3 = средняя В; 45 = В12+, В14–В20. Рубрика — §4.
---
## 4. Рубрика сложности (difficulty 15)
| Балл | Признак | Где |
|---|---|---|
| 1 | одно действие/определение, устно | А1, А3, А4, А5 |
| 2 | 2–3 шага, базовая формула | А-часть, В2, В3, В8 |
| 3 | несколько шагов, выбор метода | В4–В11, А9 |
| 4 | многошаговое + ОДЗ/отбор/подобие | В12–В14, В18 |
| 5 | сложный метод (рационализация, отбор корней, координатный метод в 3D) | В15–В17, В19, В20 |
---
## 5. Конвейер оцифровки (рекомендуемый порядок шагов)
1. **Источник → структура.** Один вариант = 30 задач. Удобный промежуточный формат — JS-объект как в `frontend/js/exam9/variants/vNN.js` (`text`, `opts`, `sol` с `sol-ans`).
2. **Импорт.** Прогнать через `backend/scripts/import-exam-tasks.js` (автоопределение `mc`/`open` по наличию `opts`, парс `answer` из `sol-ans`).
3. **Классификация.** Проставить `topic`/`subtopic`/`difficulty` по §2 (можно скриптом по `task_idx`), затем выборочно проверить отклонения (по аналогии с `tag-exam-tasks.js`).
4. **Чертежи.** Для задач с рисунком — добавить `figure_html` (SVG/`<img>`); часть А2/А9/В1/В13/В17/В20 почти всегда с чертежом.
5. **Верификация.** Сверить `answer` с официальными ответами (папки `…\Ответы…`, DJVU-исходники) — критично для авто-проверки.
6. **Сборка вариантов.** Полные варианты доступны как `exam_mock_sessions` (пробники на 180 мин) автоматически — нужна только заполненность 30 задач варианта.
> OCR кириллицы+формул из PDF/DJVU ненадёжен на математике — формулы почти всегда перенабираются вручную в KaTeX. Это основной объём работы; приоритет источников — §6.
---
## 6. Приоритет источников для оцифровки
| Очередь | Источник | Почему |
|---|---|---|
| 1 | `ЦТ-ЦЭ\ЦЭ-ЦТ-2024 МАТ.pdf` | эталон текущего формата, есть ответы |
| 2 | `РТ\2022-2023 … 2024-2025` | свежие, формат совпадает, 3 этапа × 2 варианта |
| 3 | `ЦТ-ЦЭ\20172021` + DJVU-ответы | большой банк реальных заданий с ответами |
| 4 | `РТ\20162021` | расширение банка |
| 5 | `ДРТ\` | доп. варианты + разборы консультаций |
| 6 | старые `РТ/ЦТ 20042015` | архив, по мере необходимости |
Нумерация `variant`: сквозная, с префиксом-меткой источника в `solution_html`/комментарии (напр. «ЦЭ-2024», «РТ-2024 этап 2 в1»), чтобы не терять происхождение.
---
## 7. Входной диагностический тест
Цель: за ~30–40 минут определить уровень по каждому разделу → назначить трек и приоритетные модули.
### Состав (1 задание на ключевую подтему, смесь А и В, 12–15 задач)
| # | Подтема | Уровень-зонд | Источник-позиция |
|---|---|---|---|
| 1 | `num-real` | 🟢 | А1 |
| 2 | `eq-quadratic` | 🟢 | А5 |
| 3 | `trig-circle` | 🟢 | А3 |
| 4 | `expr-powers-roots` | 🟢 | А10 |
| 5 | `word-problems` | 🟡 | А7 / В7 |
| 6 | `seq-progressions` | 🟡 | В3 / В6 |
| 7 | `fn-properties` | 🟡 | В2 / В9 |
| 8 | `trig-identities` | 🟡 | В4 |
| 9 | `plan-triangles` | 🟡 | В5 |
| 10 | `ster-basics` | 🟡 | А2 / В1 |
| 11 | `eq-logarithmic` | 🔴 | В11 → В16 |
| 12 | `trig-equations` | 🔴 | В15 |
| 13 | `fn-derivative` | 🔴 | В19 |
| 14 | `ster-angles-distances` | 🔴 | В20 |
### Логика назначения трека (по результату)
- Доля верных среди 🟢-зондов < 75% **или** «проваленные» базовые разделы → стартовый трек **База**; провальные разделы проходятся с нуля.
- 🟢 уверенно, 🟡 ≥ ~50% → трек **Ядро**.
- 🟢+🟡 уверенно и хотя бы часть 🔴 решена → трек **Продвинутый**.
- Любой раздел с диагностикой < 50% → этот раздел всегда с уровня База, независимо от общего трека (правило ветвления §4.3 PLAN.md).
### Реализация на платформе
- Диагностика = `exam_mock_sessions` с `source='random'`/спец-набор `task_ids_json` из перечисленных подтем, либо practice-набор `strategy=weak`.
- Результаты пишутся в `exam_attempts` (по подтемам) → дашборд/детектор слабых тем сразу строит heatmap и список приоритетов.
- `exam_user_plan.weak_focus=1` — включить фокус на слабых темах (опционально).
---
## 8. Чек-лист «задание готово»
- [ ] `text_html` набран, формулы в KaTeX, читается;
- [ ] `figure_html` добавлен (если есть чертёж);
- [ ] `opts_json` для `mc` (5 вариантов) / отсутствует для `open`;
- [ ] `answer` сверен с официальным ключом, нормализован;
- [ ] `solution_html` с `sol-ans`;
- [ ] `topic`/`subtopic`/`difficulty` проставлены и проверены против реальной темы;
- [ ] `UNIQUE(exam_key, variant, task_idx)` не нарушен.
+132
View File
@@ -0,0 +1,132 @@
# Идеи по улучшению модуля ЦЭ/ЦТ — по всем направлениям
> Источники идей: текущее состояние модуля (`/exam-prep/ctmath` + курс + флешкарты + диагностика);
> педагогика из папки `F:\!Рабочие\ЦТ\Математика\` (roadmap-док «К прочтению», «Кедр»-отработки,
> «100 баллов», шпоры-формулы, видеоразборы); практики из интернета (разбор ошибок > подсчёт баллов,
> тайм-менеджмент/ловушки; UWorld — аналитика слабых мест + реалистичные пробники; Anki/Quizlet —
> spaced repetition + mastery, до +80% удержания; ИИ-тьютор). Возможности платформы (из памяти):
> ассистент «Квантик», геймификация (XP/ачивки/магазин/питомец), карта знаний, imggen, доска, классы.
>
> Метки готовности: ✅ уже есть · 🔧 расширить существующее · 🆕 новое. Приоритет: P1 (быстрая победа) · P2 · P3 (крупная ставка).
---
## 1. Контент и покрытие
| Идея | Польза | Готовность · Приоритет |
|---|---|---|
| **Полные варианты-пробники по годам** (собрать `exam_mock_sessions` из реальных вариантов РТ/ЦТ, а не только случайные наборы) | Реалистичная репетиция всего теста на время | 🔧 (mock есть) · P2 |
| **Улучшить `solution_html`** — у части задач сейчас заглушка «См. решение». Добавить пошаговые разборы | Разбор > ответ; учит технике | 🔧 · P1 |
| **Видео-разборы в решениях** (ссылки на Трушина / Wild Mathing / П. Маслова из roadmap-папки, по темам В15/В16/В20) | Сильные внешние объяснения сложных тем | 🆕 · P2 |
| **«Ловушки» и типичные ошибки** — поле/блок «частая ошибка» у задач и тем (из «Кедр» и опыта) | Закрывает «обидные» потери баллов | 🆕 · P2 |
| **Дотегировать 68 mc** с неразобранным инлайн-списком + фикс id=866/1248 | Чистая отрисовка вариантов | 🔧 · P1 |
| **Углубить уроки** «лёгких» секций (2-й урок, больше эталонов и тренажёра) | Полнота теории | 🔧 · P2 |
| **Справочник формул на тему** (шпаргалка-страница из папки `формулы триги ВСЕ`, `ШПОРА по СТЕОМЕ`) | Быстрый доступ к формулам в практике | 🆕 · P2 |
| **Чертежи к геом-задачам** (генерация SVG/`imggen` где фигуры нет) | Геометрия без рисунка нерешаема | 🔧 · P3 |
## 2. Методика / как учить (из roadmap-папки)
| Идея | Польза | Готовность · Приоритет |
|---|---|---|
| **Трек «Приёмы и техники»**: метод рационализации (В16/В14), отбор корней (В15), координатный метод (В20), функциональные методы | Непропорционально много баллов за приём; ядро roadmap-папки | 🆕 (часть в пилотах) · P2 |
| **«Вывод вместо зубрёжки»** — в уроках тригонометрии вывод формул из 2–3 базовых (уже заложено) распространить на логарифмы/производные | Понимание устойчивее памяти | 🔧 · P2 |
| **Спираль сложности**: один тип задания от базы к В-уровню в одном потоке | Плавный рост, виден прогресс | 🔧 · P2 |
| **Тайм-менеджмент теста**: рекомендации по распределению 180 мин (часть А быстро → В по возрастанию), тренировка «на скорость» части А | Частая потеря баллов — спешка/хаос | 🆕 · P1 |
## 3. Адаптивность и аналитика (UWorld-стиль)
| Идея | Польза | Готовность · Приоритет |
|---|---|---|
| **Детектор слабых тем** (точность <60% на 3+ попытках) | уже работает в exam-prep | ✅ |
| **Spaced repetition для ЗАДАНИЙ** (а не только флешкарт): повторно показывать ОШИБОЧНЫЕ задачи по нарастающим интервалам | Удержание +80% (исследования) | 🆕 · P2 |
| **Работа над ошибками — отдельный режим**: авто-набор из недавно проваленных задач + ссылка на урок/§ | Разбор ошибок — ключ к росту | 🔧 (данные в `exam_attempts`) · P1 |
| **Прогноз тестового балла** к цели (через `scoring_json`) + «до цели N баллов» | Мотивация, фокус | 🔧 · P2 |
| **Тепловая карта по темам/позициям** (А1–А10, В1–В20): где сильно/слабо | Виден приоритет | 🔧 (дашборд есть) · P2 |
| **Перетегировать difficulty в 15** (сейчас 1–3) | Точнее адаптивная выборка и прогрессия | 🔧 · P2 |
## 4. Режимы практики и симуляция экзамена
| Идея | Польза | Готовность · Приоритет |
|---|---|---|
| **Полный пробник на таймер** (30 заданий, 180 мин, как реальный) с итоговым тестовым баллом | Реалистичность = меньше стресса на экзамене | 🔧 (mock есть) · P1 |
| **Учёт времени на задание** + обратная связь по темпу («тратишь >X с») | Тренирует скорость | 🔧 (`time_ms` есть) · P2 |
| **«До мастерства»**: гонять тему, пока не ≥X% | Доведение до автоматизма | 🆕 · P2 |
| **Часть А «на скорость»** — блиц 10 заданий с жёстким таймером | А-часть = гарантированные баллы | 🆕 · P2 |
| **Ежедневная норма + серия** (`exam_user_plan.daily_target` уже есть) | Привычка заниматься | 🔧 · P1 |
## 5. Память (флешкарты / интервальное повторение)
| Идея | Польза | Готовность · Приоритет |
|---|---|---|
| **Колоды формул** (тригонометрия/стерео/логарифмы/производная) | ✅ созданы (49 карт) | ✅ |
| **Раздать колоды классу** (`flashcard_deck_access`) + кнопка «учить формулы» прямо из урока/практики | Доступность SR | 🔧 · P1 |
| **Карты «факт→где применить»** (не только формула, но и типовой ход) | Перенос знания в задачу | 🆕 · P2 |
| **Авто-карта из ошибки**: проваленную формулу/факт — в колоду на повторение | Замыкает цикл память↔практика | 🆕 · P3 |
## 6. Мотивация / геймификация (платформа уже умеет)
| Идея | Польза | Готовность · Приоритет |
|---|---|---|
| **XP/ачивки за ЦТ-практику** («Закрыл часть А», «10 пробников», «Неделя без пропусков», «Мастер стереометрии») | Платформа имеет XP/achievements/shop | 🔧 · P2 |
| **Серии (streak) и дневная цель** на дашборде модуля | Удержание | 🔧 · P1 |
| **Реакция питомца «Квантик»** на успехи/прогресс в ЦТ | Эмоц. вовлечение (как в игре) | 🔧 · P3 |
| **Рейтинг класса / соревнование** по решённым/точности | Соц. мотивация | 🆕 · P2 |
## 7. ИИ-ассистент (платформа имеет «Квантик-ассистент»)
| Идея | Польза | Готовность · Приоритет |
|---|---|---|
| **«Объясни это задание»** — ассистент даёт подсказку/разбор проваленной задачи | Персональный тьютор (тренд 2025) | 🔧 · P2 |
| **«Дай похожую задачу»** — генерация тренировки по слабой теме | Бесконечная практика | 🆕 · P3 |
| **Подсказки-ступени** (hint 1 → hint 2 → решение) вместо сразу ответа | Учит думать, не списывать | 🆕 · P2 |
## 8. UX / UI
| Идея | Польза | Готовность · Приоритет |
|---|---|---|
| **Ввод ответа части В**: подсказка формата (число/дробь/через `;`), нормализация запятая/точка | Меньше «ложных» ошибок ввода | 🔧 · P1 |
| **Палитра/калькулятор-блокнот** (черновик в интерфейсе) | Организует черновик (частая беда) | 🆕 · P3 |
| **Закладки «вернуться к задаче»** в пробнике | Стратегия теста (пропустить-вернуться) | 🆕 · P2 |
| **Адаптив под мобильные** (карточка задачи, формулы) | Учатся с телефона | 🔧 · P2 |
| **Прогресс-бар по темам/позициям** на входе в модуль | Сразу видно, что качать | 🔧 · P2 |
## 9. Учительские инструменты
| Идея | Польза | Готовность · Приоритет |
|---|---|---|
| **Выдать модуль классу** (`content_access` exam/ctmath) + назначить пробник/тему (`assignments`) | Базовый go-live | 🔧 · P1 |
| **Аналитика по классу**: слабые темы класса, кто отстаёт | Учитель видит, где помочь | 🆕 · P2 |
| **Конструктор пробника** (выбрать темы/сложность → собрать вариант) | Гибкие ДЗ | 🔧 · P3 |
## 10. Качество данных / техническое
| Идея | Польза | Готовность · Приоритет |
|---|---|---|
| **Авто-проверка контента**: скрипт-линтер (битые opts, нечисловые open-ответы, `<`/`>` в формулах, пустые решения) в CI | Не повторять найденные баги | 🆕 · P1 |
| **Тонкая таксономия**: дотегировать вопросы под 32 подтемы (сейчас грубое flat→subtopic) | Точнее практика по теме | 🔧 · P3 |
| **Связь exam-prep ↔ урок**: из проваленной темы — прямая ссылка на урок курса (не только §учебника) | Бесшовно «практика→теория» | 🆕 · P2 |
---
## Топ-рекомендации (если делать по очереди)
**Сразу (P1, быстрые победы):**
1. Go-live ученикам: `content_access` + публикация курса + раздача колод.
2. Режим **«работа над ошибками»** (реюз `exam_attempts`) + ссылка на урок.
3. **Полный пробник на таймер** с прогнозом балла.
4. **Дневная цель + серия** на дашборде.
5. Контент-линтер в CI + добить 866/1248/68 mc.
6. Подсказка формата ответа части В.
**Потом (P2):**
- Spaced-repetition для заданий; трек «Приёмы»; XP/ачивки за ЦТ; аналитика класса; видео-разборы; difficulty 1–5; перетегирование тем.
**Крупные ставки (P3):**
- ИИ-тьютор «объясни/дай похожую»; авто-карты из ошибок; конструктор пробников; чертежи через imggen; черновик-блокнот.
---
## Источники (веб)
- Подготовка к ЦЭ/ЦТ по математике — гайды и разбор ошибок: [centr-lazurkina.by](https://centr-lazurkina.by/podgotovka-k-cze-i-czt-po-matematike/), [formula-ct.by](https://formula-ct.by/math), [turboct.by](https://turboct.by/podgotovka-k-ct-po-matematike/programma), [РЕШУ ЦТ](https://math_ct.reshu.by/methodist)
- Фичи exam-prep / SR / аналитика: [AI study tools 2026](https://fast.io/resources/best-ai-study-tools-2026/), [Spaced repetition guide](https://makeheadway.com/blog/spaced-repetition-app/), [Test-prep platforms 2025](https://studyguides.com/articles/best-study-platforms-for-test-prep-popular-study-platforms)
- Папка материалов: roadmap-док «К прочтению» (методы, видеоканалы, приоритетные слабые темы), «Кедр»-отработки (целевые слабые темы), «100 баллов» (тематическая структура), шпоры-формулы.
+151
View File
@@ -0,0 +1,151 @@
# Пилот: блок «Стереометрия» до уровня занятий (второй эталон)
> Развёртка самого «дорогого» блока (раздел `stereometry`, модули M26–M29) в контент платформы.
> Стереометрия встречается в тесте ~6 раз — А2, А9, В1, В13, В17, В20 — и содержит сложнейшие задания (В17 подобие, В20 угол в пространстве).
> Структура совпадает с [PILOT_TRIGONOMETRY.md](PILOT_TRIGONOMETRY.md); специфика блока — sim `stereo`, **координатно-векторный метод** как универсальный «запасной» подход, и готовые «Кедр»-отработки слабых тем.
>
> ⚠️ После пивота (см. [BUILD_ON_QUESTIONS.md](BUILD_ON_QUESTIONS.md)): «тренажёр» — практика на банке
> `questions` через assignment `mode='topic'` (тема `61 Стереометрия`), а НЕ новые `exam_tasks`.
> Сложность в банке — 1–3. Уроки/карточки ниже — в силе.
---
## 0. Карта блока
| Модуль | Подтема (slug) | Позиции теста | Уровень | Sim | Учебник |
|---|---|---|---|---|---|
| M26 Расположение, сечения | `ster-basics` | А2, В1 | 🟡 | `stereo` | `geometry-10` |
| M27 Многогранники | `ster-polyhedra` | В13, В17 | 🟡🔴 | `stereo` | `geometry-10` |
| M28 Тела вращения | `ster-rotation` | А9, В13 | 🟡🔴 | `stereo` | `geometry-11` |
| M29 Углы и расстояния (коорд.-вект.) | `ster-angles-distances` | В20, В1 | 🔴 | `stereo` | `geometry-11` |
Курсовая структура: `course_section` «Стереометрия» → 4 `lessons` + колода карточек (формулы объёмов/площадей + координатный метод) + наборы `exam_tasks` по подтемам.
---
## M26. Расположение прямых и плоскостей, сечения 🟡 (А2, В1)
### Урок («Аксиоматика и взаимное расположение»)
1. `heading``{ "text": "Прямые и плоскости в пространстве: параллельность, пересечение, скрещивание" }`
2. `text``{ "html": "Три случая для двух прямых: пересекаются, параллельны, скрещиваются. Прямая и плоскость: лежит в ней, параллельна, пересекает. Две плоскости: параллельны или пересекаются по прямой." }`
3. `sim``{ "simId": "stereo", "caption": "Покрутите фигуру: найдите линию пересечения двух плоскостей и пары скрещивающихся прямых" }`
4. `callout``{ "variant": "info", "html": "Линия пересечения двух плоскостей проходит через их общие точки. В правильной пирамиде плоскости через апекс и центр основания пересекаются по прямой через апекс (например SO)." }`
5. `formula``{ "label": "Признак параллельности прямой и плоскости", "tex": "a\\parallel b,\\ b\\subset\\alpha,\\ a\\not\\subset\\alpha \\Rightarrow a\\parallel\\alpha" }`
6. `callout``{ "variant": "warn", "html": "В В1 (выбор верных утверждений о расстояниях) проверяйте КАЖДОЕ утверждение отдельно: расстояние между скрещивающимися — длина общего перпендикуляра, а не любого отрезка." }`
7. `flashcard` ×N (колода ниже).
### Разборы эталонов
- **А2** (РИКЗ-2024): правильная четырёхугольная пирамида SABCD, O — центр основания; найти прямую пересечения плоскостей DSO и SCB. Обе плоскости проходят через S → линия пересечения проходит через S; анализом получаем **SO**. Метод: общие точки двух плоскостей.
- **В1**: прямая треугольная призма, выбрать верные утверждения о расстояниях/равенстве отрезков (ответ — комбинация цифр). Метод: перевести каждое утверждение в проверяемый факт.
### Тренажёр (`exam_tasks`, subtopic=`ster-basics`)
- 🟡 difficulty 23: А2/В1-тип. Источник: «ШПОРА по СТЕОМЕ» (Кедр), Калинин-Терёшин, А2/В1 из РТ/ЦТ.
- **Критерий освоения**: ≥80% на А2+В1.
---
## M27. Многогранники: объёмы, площади, сечения, подобие 🟡🔴 (В13, В17)
### Урок («Призма, пирамида, параллелепипед»)
1. `heading``{ "text": "Объёмы и площади многогранников. Подобие в сечениях" }`
2. `formula``{ "label": "Объёмы", "tex": "V_{\\text{призмы}}=S_{\\text{осн}}\\cdot h,\\qquad V_{\\text{пирамиды}}=\\tfrac{1}{3}S_{\\text{осн}}\\cdot h" }`
3. `text``{ "html": "Сечение, параллельное основанию пирамиды, отсекает подобную фигуру. Если высота делится от вершины в отношении k, то линейные размеры сечения относятся к основанию как k, а ПЛОЩАДИ — как k²." }`
4. `formula``{ "label": "Подобие сечения ∥ основанию", "tex": "\\frac{S_{\\text{сеч}}}{S_{\\text{осн}}}=k^2,\\quad k=\\frac{\\text{высота до сечения}}{\\text{вся высота}}" }`
5. `sim``{ "simId": "stereo", "caption": "Сечение пирамиды плоскостью ∥ основанию" }`
6. `callout``{ "variant": "warn", "html": "В17 ловит на том, что относятся как k² именно ПЛОЩАДИ, а не длины. Сначала найдите k из отношения высот, потом возводите в квадрат." }`
7. `flashcard` ×N.
### Разбор эталона (В17, РИКЗ-2024)
> Плоскость ∥ основанию треуг. пирамиды делит высоту в отношении 5:3 от вершины. Площадь сечения меньше площади основания на 39. Найти площадь сечения.
> k = 5/(5+3) = 5/8 → S_сеч/S_осн = 25/64. Пусть S_осн = x → S_сеч = (25/64)x; x (25/64)x = 39 → (39/64)x = 39 → x = 64 → **S_сеч = 25**.
### Тренажёр (`exam_tasks`, subtopic=`ster-polyhedra`)
- 🟡 difficulty 3 (объёмы/площади) → 🔴 difficulty 5 (подобие В17). Источник: «Метод Кавальери», «100 баллов» стерео, В13/В17 из РТ/ЦТ.
- **Критерий освоения**: ≥75% (В17-тип уверенно).
---
## M28. Тела вращения: цилиндр, конус, шар/сфера 🟡🔴 (А9, В13)
### Урок («Цилиндр, конус, шар»)
1. `heading``{ "text": "Тела вращения: площади поверхностей и объёмы" }`
2. `formula``{ "label": "Шар и сфера", "tex": "S_{\\text{сферы}}=4\\pi R^2,\\qquad V_{\\text{шара}}=\\tfrac{4}{3}\\pi R^3" }`
3. `formula``{ "label": "Цилиндр", "tex": "S_{\\text{бок}}=2\\pi R h,\\qquad V=\\pi R^2 h" }`
4. `formula``{ "label": "Конус", "tex": "S_{\\text{бок}}=\\pi R l,\\qquad V=\\tfrac{1}{3}\\pi R^2 h" }`
5. `sim``{ "simId": "stereo", "caption": "Сечение цилиндра плоскостью, параллельной оси" }`
6. `callout``{ "variant": "info", "html": "Сфера, касающаяся плоскости: радиус в точку касания ⊥ плоскости. Расстояние от центра до точки плоскости и радиус образуют прямоугольный треугольник — теорема Пифагора." }`
7. `flashcard` ×N.
### Разборы эталонов (РИКЗ-2024)
- **А9**: квадрат с диагональю 8 в плоскости α; сфера касается α в точке пересечения диагоналей; расстояние от центра сферы до вершины квадрата 4√2. Найти площадь сферы. Полудиагональ = 4; R² = (4√2)² 4² = 32 16 = 16 → R = 4 → S = 4π·16 = **64π**.
- **В13**: цилиндр рассечён плоскостью ∥ оси, в сечении квадрат площади 100; расстояние от оси до плоскости √39. Найти S_бок/π. Сторона квадрата = 10 = высота = хорда; R² = (√39)² + 5² = 39+25 = 64 → R = 8; S_бок = 2π·8·10 = 160π → **160**.
### Тренажёр (`exam_tasks`, subtopic=`ster-rotation`)
- 🟡 difficulty 3 → 🔴 4. Источник: «Отработка по Шару» (Кедр), Калинин-Терёшин, А9/В13 из РТ/ЦТ.
- **Критерий освоения**: ≥80% А9, ≥70% В13.
---
## M29. Углы и расстояния в пространстве — координатно-векторный метод 🔴 (В20, В1)
> Ключевой модуль трека на 90–100. Универсальный приём: ввести координаты → выразить векторы → угол через скалярное произведение. «Если геометрия не идёт — считай координатами» (roadmap-документ).
### Урок («Координатный метод: угол между прямыми/плоскостями»)
1. `heading``{ "text": "Координаты в пространстве — универсальный способ найти угол и расстояние" }`
2. `text``{ "html": "Алгоритм В20: (1) ввести удобную систему координат (вершину фигуры в начало), (2) выписать координаты нужных точек, (3) составить направляющие векторы прямых, (4) угол — через косинус скалярного произведения." }`
3. `formula``{ "label": "Угол между прямыми через векторы", "tex": "\\cos\\varphi=\\frac{|\\vec a\\cdot\\vec b|}{|\\vec a|\\,|\\vec b|}" }`
4. `formula``{ "label": "Скалярное произведение и длина", "tex": "\\vec a\\cdot\\vec b=a_xb_x+a_yb_y+a_zb_z,\\quad |\\vec a|=\\sqrt{a_x^2+a_y^2+a_z^2}" }`
5. `sim``{ "simId": "stereo", "caption": "Угол между скрещивающимися прямыми" }`
6. `accordion` → альтернативы: угол между прямой и плоскостью (через нормаль), теорема о трёх синусах (раскрывается по желанию).
7. `callout``{ "variant": "warn", "html": "В числителе — МОДУЛЬ скалярного произведения (угол между прямыми ≤ 90°). Самая частая ошибка В20 — знак/потеря модуля и неверные координаты точек деления рёбер." }`
8. `ordering``{ "question": "Порядок решения В20 координатным методом", "items": ["Ввести систему координат","Выписать координаты точек (учесть отношения деления рёбер)","Составить направляющие векторы","cos φ через скалярное произведение и длины"] }`
9. `flashcard` ×N.
### Разбор эталона (В20, РИКЗ-2024)
> Прямой параллелепипед ABCDA₁B₁C₁D₁, объём 5√7/2; AB=√7, BC=√2, cos∠ABC=−√14/8; на рёбрах AA₁ и A₁B₁ точки M, N с AM:MA₁=4:1, A₁N:NB₁=1:4. Найти 8√66·cos φ, φ — угол между MN и BC₁.
> Метод: ввести координаты по основанию (с учётом cos∠ABC найти высоту из объёма), выписать M, N, B, C₁ с учётом отношений, составить MN и BC₁, найти cos φ. (Целевая задача для «Лабораторной по В20» и «Отработки В20 из РЦЭ-2025» — Кедр.)
### Тренажёр (`exam_tasks`, subtopic=`ster-angles-distances`)
- 🔴 difficulty 5. Источник (Кедр): «Лабораторная для отработки В20», «Отработка В20 из РЦЭ-2025», «Отработка скрещивающиеся», «Отработка Угол между прям. и плоск.»; «Векторы на экзаменах» (Шестаков).
- **Критерий освоения**: ≥60% В20 координатным методом (сложнейшая позиция теста).
---
## Колода карточек (`flashcard_decks` «Стереометрия — формулы»)
| front | back |
|---|---|
| V призмы | S_осн · h |
| V пирамиды | (1/3) S_осн · h |
| V цилиндра | π R² h |
| V конуса | (1/3) π R² h |
| V шара | (4/3) π R³ |
| S сферы | 4π R² |
| S_бок цилиндра | 2π R h |
| S_бок конуса | π R l |
| Сечение ∥ основанию: отношение площадей | k² (k — отношение высот от вершины) |
| Угол между прямыми (векторы) | cos φ = \|a·b\| / (\|a\|·\|b\|) |
| Скалярное произведение | aₓbₓ + a_yb_y + a_zb_z |
| Длина вектора | √(aₓ² + a_y² + a_z²) |
| Сфера касается плоскости | радиус в точку касания ⊥ плоскости (→ Пифагор) |
| Расстояние между скрещивающимися | длина общего перпендикуляра |
> Источник: «ШПОРА по СТЕОМЕ» (Кедр), «формулы» из «100 баллов» стерео.
---
## Сводный критерий освоения блока
| Уровень | Условие |
|---|---|
| 🟡 Ядро | А2/В1 ≥80%, А9 ≥80%, объёмы/площади (В13) ≥70% |
| 🔴 Продвинутый | + В17 (подобие) уверенно, В20 ≥60% координатным методом |
Детектор слабых тем вернёт `ster-*` подтему в фокус с предложением урока + sim `stereo` + § учебника (`geometry-10`/`geometry-11`).
---
## Заметки для авторинга
- `stereo` sim — единственная 3D-визуализация; ставить в каждый урок блока (повышает понимание расположения).
- В20 — отдельный мини-тренажёр из «Кедр»-материалов: это самые «дорогие» баллы, и они хорошо алгоритмизируются координатным методом.
- Чертежи задач (А2/А9/В1/В13/В17/В20) почти всегда нужны → `figure_html` (SVG/`<img>`) обязателен при оцифровке (см. [DIGITIZATION_SPEC.md](DIGITIZATION_SPEC.md) §3.2).
+152
View File
@@ -0,0 +1,152 @@
# Пилот: блок «Тригонометрия» до уровня занятий (шаблон тиражирования)
> Эталонная развёртка ОДНОГО блока (раздел `trigonometry`, модули M18–M20) в конкретный контент платформы.
> Демонстрирует полный конвейер: теория (`lesson_blocks`) → разбор → тренажёр (`exam_tasks`) → карточки (`flashcard_*`) → sim (`trigcircle`) → критерий освоения.
> Все data-shape блоков соответствуют реальному рендеру `frontend/lesson.html`. Остальные 8 блоков строятся по этому образцу.
>
> Почему тригонометрия как пилот: охватывает все три уровня (А3 🟢 → А8/В4 🟡 → В15 🔴), задействует sim `trigcircle`,
> карточки формул и философию «вывод вместо зубрёжки» — то есть прогоняет все возможности платформы.
>
> ⚠️ После пивота (см. [BUILD_ON_QUESTIONS.md](BUILD_ON_QUESTIONS.md)): уроки/`lesson_blocks`/карточки
> ниже остаются как есть; «тренажёр» — это НЕ новые `exam_tasks`, а практика на банке `questions`
> через assignment `mode='topic'` (темы `19 Тригонометрия`, `69 Тригонометрические уравнения`).
> Сложность в банке — 1–3.
---
## 0. Карта блока
| Модуль | Подтема (slug) | Позиции теста | Уровень | Sim | Учебник |
|---|---|---|---|---|---|
| M18 Круг и значения | `trig-circle` | А3 | 🟢 | `trigcircle` | `algebra-10-ch1` |
| M19 Тождества и формулы | `trig-identities` | А8, В4 | 🟡 | — | `algebra-10-ch1` |
| M20 Уравнения и отбор корней | `trig-equations` | В15 | 🔴 | `trigcircle` | `algebra-10-ch1` |
Курсовая структура: `course_section` «Тригонометрия» → 3 `lessons` (по модулю) + общая колода карточек + наборы `exam_tasks` по подтемам.
---
## M18. Тригонометрический круг и значения 🟢 (позиция А3)
### Урок (lesson «Тригонометрический круг»)
Последовательность `lesson_blocks` (type → data):
1. `heading``{ "text": "Тригонометрический круг: смысл синуса и косинуса" }`
2. `text``{ "html": "Точка на единичной окружности при повороте на угол α имеет координаты (cos α; sin α). Это определение, из которого выводится всё остальное — запоминать таблицы наизусть не нужно, нужно уметь «прочитать» круг." }`
3. `sim``{ "simId": "trigcircle", "caption": "Покрутите угол и следите за координатами точки — это и есть cos α и sin α" }`
4. `formula``{ "label": "Определения через круг", "tex": "\\cos\\alpha = x,\\quad \\sin\\alpha = y,\\quad \\tan\\alpha=\\frac{y}{x},\\quad \\cot\\alpha=\\frac{x}{y}" }`
5. `callout``{ "variant": "info", "html": "Знаки по четвертям: I (+,+), II (,+), III (,), IV (+,−). «Все Студенты Так Кричат» — sin/all/tan/cos положительны по четвертям." }`
6. `table` → таблица значений 0, π/6, π/4, π/3, π/2 для sin/cos/tan (как `{ "rows": [...] }`, формат table-блока).
7. `callout``{ "variant": "warn", "html": "Типичная ошибка: путать, где нуль у sin (при 0, π, 2π…) и у cos (при π/2, 3π/2…). На круге это видно: sin=ордината, cos=абсцисса." }`
8. `flashcard` ×N → атомы (см. колоду ниже), напр. `{ "front": "sin(π/6)", "back": "1/2" }`
9. `quiz` (само­проверка) → `{ "question": "При каком значении аргумента sin x = 0?", "options": ["π/2", "π", "π/4", "π/3"], "correctIndex": 1 }`
### Разбор эталона (позиция А3, формат реального теста)
> *Среди значений аргумента −π/6, π/4, π/3, −3π/2, −6π укажите то, при котором sin x = 0.*
> Решение: sin x = 0 ⟺ x = πk. Из списка кратно π только −6π. **Ответ: −6π.**
> (Реальное задание А3 из варианта РИКЗ-2024 — годится как эталон.)
### Тренажёр (`exam_tasks`, subtopic=`trig-circle`)
- 🟢 difficulty 12: значения sin/cos/tan по кругу, простейшие «где функция = 0/1/−1».
- Источник заданий: А3 из РТ/ЦТ всех лет + «Все_формулы_по_тригонометрии_для_ЦТ.png».
- **Критерий освоения**: ≥90% на наборе А3 (часть А — гарантированный балл).
---
## M19. Тождества и формулы (вывод!) 🟡 (позиции А8, В4)
### Урок (lesson «Тождества: как не учить 30 формул»)
1. `heading``{ "text": "Главное тождество и что из него следует" }`
2. `formula``{ "label": "Основное тригонометрическое тождество", "tex": "\\sin^2\\alpha + \\cos^2\\alpha = 1" }`
3. `text``{ "html": "Из основного тождества делением на cos²α и sin²α получаем связи с tan и cot — выводим на месте, а не заучиваем:" }`
4. `formula``{ "tex": "1+\\tan^2\\alpha=\\frac{1}{\\cos^2\\alpha},\\qquad 1+\\cot^2\\alpha=\\frac{1}{\\sin^2\\alpha}" }`
5. `accordion` → формулы сложения, двойного/половинного угла, преобразование суммы в произведение — каждая с краткой идеей вывода (раскрывается по желанию; не грузим всё сразу).
6. `callout``{ "variant": "info", "html": "Обратные функции: arcsin x ∈ [−π/2; π/2], arccos x ∈ [0; π], arctan x ∈ (−π/2; π/2). Помните области значений — на них ловят в А8." }`
7. `flashcard` ×N → ключевые формулы (колода ниже).
8. `matching``{ "pairs": [ {"left":"sin 2α","right":"2 sin α cos α"}, {"left":"cos 2α","right":"cos²α sin²α"}, {"left":"1 cos 2α","right":"2 sin²α"} ] }`
### Разборы эталонов
- **А8** (обратные функции + модуль): *Найдите значение (38/π)·arcsin(1) |7|.* arcsin(1)=−π/2 → (38/π)·(−π/2)=19; 197=26. **Ответ: 26.**
- **В4** (тождество): *Найдите ctg²α, если sin α = 1/5.* cos²α=11/25=24/25; ctg²α=cos²α/sin²α=(24/25)/(1/25)=24. **Ответ: 24.**
### Тренажёр (`exam_tasks`, subtopic=`trig-identities`)
- 🟡 difficulty 2–3: вычисление выражений по одному данному (sin→ctg² и т.п.), значения обратных функций, упрощения.
- Источник: А8/В4 из РТ/ЦТ + «формулы триги ВСЕ.pdf», `Trigonometria_2..5.pdf`, «09-11 Тригонометрия.docx».
- **Критерий освоения**: ≥85% на наборе А8+В4.
---
## M20. Тригонометрические уравнения и отбор корней 🔴 (позиция В15)
### Урок (lesson «Уравнения и отбор корней на промежутке»)
1. `heading``{ "text": "Простейшие уравнения и общие формулы корней" }`
2. `formula``{ "label": "Формулы корней", "tex": "\\sin x=a\\Rightarrow x=(-1)^n\\arcsin a+\\pi n;\\quad \\cos x=a\\Rightarrow x=\\pm\\arccos a+2\\pi n;\\quad \\tan x=a\\Rightarrow x=\\arctan a+\\pi n" }`
3. `text``{ "html": "Стратегия В15: (1) свести к произведению/простейшему виду (формулы преобразования), (2) выписать общие корни, (3) ОТОБРАТЬ корни на заданном промежутке — обычно перебором n, удобно на тригонометрическом круге." }`
4. `sim``{ "simId": "trigcircle", "caption": "Отбор корней: отметьте промежуток и проверьте, какие x = πn в него попадают" }`
5. `ordering``{ "question": "Порядок решения В15", "items": ["Преобразовать к произведению / простейшему виду","Выписать общие формулы корней","Подставить n и отобрать корни на промежутке","Сложить отобранные корни"] }`
6. `callout``{ "variant": "warn", "html": "Не теряйте ОДЗ (для tan/cot) и не забывайте оба семейства корней. Самая частая потеря баллов в В15 — неполный отбор." }`
7. `flashcard` ×N → формулы корней + преобразования произведения↔суммы.
### Разбор эталона (В15)
> *Найдите (в градусах) сумму различных корней уравнения 2·sin3x·cos3x sin6x·sin10x = 0 на (150°; 55°).*
> 2 sin3x cos3x = sin6x → sin6x sin6x·sin10x = sin6x(1 sin10x)=0 → sin6x=0 или sin10x=1. Далее отбор корней на промежутке и суммирование. (Эталон из варианта РИКЗ-2024.)
### Тренажёр (`exam_tasks`, subtopic=`trig-equations`)
- 🔴 difficulty 4–5: уравнения с преобразованием + отбор корней на промежутке.
- Источник: В15 из РТ/ЦТ + «Подборка_заданий_триг_уравнений.png», `Trigonometria_3..5.pdf`, разборы (Трушин «13 задача ЕГЭ-2017» — 4 способа, из roadmap-документа).
- **Критерий освоения**: ≥70% на наборе В15 (сложная часть В — целевой уровень).
---
## Колода карточек формул (`flashcard_decks` + `flashcard_cards`)
Одна колода на блок: `flashcard_decks.title = "Тригонометрия — формулы"`. Выдаётся классу через `flashcard_deck_access` (type='class'). Интервальное повторение — встроенный SM-2 (`flashcard_reviews`). Примеры карт (`front` / `back`):
| front | back |
|---|---|
| Определения через круг | cos α = x, sin α = y (на единичной окружности) |
| Основное тождество | sin²α + cos²α = 1 |
| 1 + tan²α | 1/cos²α |
| sin(α±β) | sin α cos β ± cos α sin β |
| cos(α±β) | cos α cos β ∓ sin α sin β |
| sin 2α | 2 sin α cos α |
| cos 2α | cos²α sin²α = 1 2sin²α = 2cos²α 1 |
| Понижение степени sin²α | (1 cos 2α)/2 |
| Область значений arcsin | [−π/2; π/2] |
| Область значений arccos | [0; π] |
| sin x = a (корни) | x = (1)ⁿ arcsin a + πn |
| cos x = a (корни) | x = ± arccos a + 2πn |
| tan x = a (корни) | x = arctan a + πn |
| Произведение в сумму: 2 sinα cosβ | sin(α+β) + sin(α−β) |
> Атомы (таблица значений π/6, π/4, π/3 и т.п.) — отдельными короткими картами для M18.
> Источник формул: «Все_формулы_по_тригонометрии_для_ЦТ.png», «формулы триги ВСЕ.pdf».
---
## Сводный критерий освоения блока
| Уровень | Условие перехода дальше |
|---|---|
| 🟢 База | А3-набор ≥90% |
| 🟡 Ядро | + А8/В4-набор ≥85% |
| 🔴 Продвинутый | + В15-набор ≥70%, отбор корней без потерь |
Детектор слабых тем платформы (точность <60% на 3+ попытках) автоматически вернёт `trig-*` подтему в фокус и предложит урок + § учебника `algebra-10-ch1`.
---
## Как тиражировать на остальные 8 блоков
1. Завести `course_section` = раздел (`numbers`, `equations`, …) из [TOPICS_SEED.md](TOPICS_SEED.md).
2. На каждый модуль — `lesson` по структуре §M18–M20: heading → теория/формулы (с выводом) → sim/диаграмма (где есть: `graph`/`graphtransform`/`geometry`/`stereo`/`trigcircle`/`quadratic`) → callout «ошибки» → flashcards → quiz.
3. Колода карточек на блок (формулы/факты) → `flashcard_deck_access` классу.
4. Наборы `exam_tasks` по подтемам (см. [DIGITIZATION_SPEC.md](DIGITIZATION_SPEC.md)) с difficulty 1–5 и привязкой к позиции теста.
5. Прописать критерий освоения (порог по мини-тесту) в описании модуля.
6. Привязать `textbook_slug` (уже в TOPICS_SEED) для добора теории при ошибках.
Приоритет тиражирования (из §8 PLAN.md): часть А → В1–В10 → стереометрия → сложная часть В.
+318
View File
@@ -0,0 +1,318 @@
# Подготовка к ЦЭ/ЦТ по математике — модульный курс для BQ-System
> Статус: ПЛАН (черновик для согласования). Дата: 2026-06-14.
> Тип: модульная программа по темам, оформленная как курс платформы LearnSpace/BQ-System.
> Универсальность: один курс с входной диагностикой и ветвлением на 3 трека (База / Ядро / Продвинутый).
> Без жёсткой привязки к датам — проходится в своём темпе, контрольные точки по освоению, а не по календарю.
>
> Источник содержания: разбор папки `F:\!Рабочие\ЦТ\Математика\` (РТ 20062025, ЦТ/ЦЭ 20042024,
> «100 баллов all», сборники Сканави/Веременюк/Сиротина/Ларченко/Федорако/Барвенов, «Кедр от Егора»,
> папка ЕГЭ для продвинутого уровня) + реальный сборник РИКЗ «ЦЭ ЦТ Математика 2024» (формат теста).
> Инвентарь источников по модулям/уровням — в [RESOURCES.md](RESOURCES.md).
> ⚠️ **ПИВОТ (2026-06-14):** задания ЦЭ/ЦТ по математике **уже в БД** — таблица `questions`
> (`subject_id=3`, **1753 задания** 2011–2025). Поэтому курс строим на этом банке через
> `tests`/`assignments` (есть готовый `mode='ct'`) и `courses`, а НЕ через exam-prep (`exam_tasks`).
> Актуальный технический маппинг — в **[BUILD_ON_QUESTIONS.md](BUILD_ON_QUESTIONS.md)** (он заменяет
> §6 и §8 ниже в части «куда складывать» и «оцифровка»). Карта теста (§1), методика (§2), модули (§3),
> уровни (§4) и шаблон модуля (§5) — в силе. Сложность в банке — **13** (а не 1–5, как в §3).
---
## 0. Как читать этот документ
Документ описывает **что учить, в каком порядке, по каким материалам и как это ляжет в платформу**
не расписание по дням. Разделы:
1. **Карта экзамена** — точная структура теста и раскладка 30 заданий по темам/сложности → задаёт приоритеты.
2. **Методические принципы** — на чём стоит курс (взято в т.ч. из roadmap-документа автора подборки).
3. **Модульная программа** — 9 блоков, ~30 тематических модулей: цель, содержание, позиции в тесте, источники, критерий освоения.
4. **Уровневые траектории** — диагностика + 3 трека, правила ветвления (универсальность).
5. **Единый шаблон модуля** — как каждый модуль превращается в уроки/тесты/карточки платформы.
6. **Маппинг на BQ-System** — конкретные таблицы/сущности (`exam_tracks`, `exam_tasks`, `exam_topics`, `courses→sections→lessons→blocks`, флешкарты, sims, `content_access`).
7. **Контроль и аналитика** — диагностика, mastery, слабые темы, пробники, прогноз балла.
8. **Порядок наполнения** — что оцифровывать/наполнять первым (по частотности и весу в балле).
9. **Открытые вопросы и решения по умолчанию**.
---
## 1. Карта экзамена (что мы готовим)
### 1.1. Формат (РИКЗ, актуальный)
| Параметр | Значение |
|---|---|
| Часть А | **А1–А10** — закрытые задания, выбор 1 из 5 |
| Часть В | **В1–В20** — открытый ответ (число / слово / комбинация цифр-букв) |
| Всего | **30 заданий** |
| Время | ~**180 минут** (уточнять по спецификации РИКЗ текущего года) |
| Балл | переводится в **100 тестовых**; часть В весит существенно больше части А |
| Калькулятор | запрещён |
> ⚠️ Точная шкала «первичный → тестовый» публикуется РИКЗ ежегодно (таблицы соответствия).
> В платформе хранится в `exam_tracks.scoring_json` — обновляется под актуальный год.
### 1.2. Раскладка заданий по темам (по реальному варианту РИКЗ-2024 + стабильным позициям прошлых лет)
Позиции в ЦЭ/ЦТ из года в год держат тему довольно стабильно. Это **главный инструмент приоритизации**:
видно, какие темы дают «дешёвые» гарантированные баллы (часть А, ранние В) и где «дорогие»/сложные баллы.
**Часть А (А1–А10) — база, цель: закрыть на 100%**
| № | Типовая тема | Раздел | Сложность |
|---|---|---|---|
| А1 | Координатная прямая, действительные числа, оценка значения | Числа | низкая |
| А2 | Стереометрия: взаимное расположение прямых/плоскостей, сечения | Стереометрия | низкая–сред. |
| А3 | Тригонометрия: значения функций, простейшие уравнения | Тригонометрия | низкая |
| А4 | Числа: деление с остатком, проценты, отношения, формула по условию | Числа | низкая |
| А5 | Квадратные уравнения, теорема Виета | Уравнения | низкая |
| А6 | Множества/числовые промежутки, объединение и пересечение | Неравенства | низкая |
| А7 | Простая текстовая задача (стоимость, проценты, остаток) | Текстовые | низкая |
| А8 | Обратные тригонометрические функции + модуль, вычисление выражения | Тригонометрия | сред. |
| А9 | Стереометрия: сфера/шар, касание плоскости, площади/объёмы | Стереометрия | сред. |
| А10 | Область определения: корни, степени с дробным показателем | Функции/выражения | низкая–сред. |
**Часть В (В1–В20) — основной вес балла; растёт по сложности к концу**
| № | Типовая тема | Раздел | Сложность |
|---|---|---|---|
| В1 | Стереометрия: расстояния/углы, выбор верных утверждений | Стереометрия | сред. |
| В2 | Свойства квадратичной функции (нули, вершина, пересечения) — сопоставление | Функции | низкая–сред. |
| В3 | Числа/прогрессии: сумма натуральных по условию (кратность, диапазон) | Прогрессии | низкая |
| В4 | Тригонометрические тождества (по sin найти ctg² и т.п.) | Тригонометрия | сред. |
| В5 | Планиметрия: прямоугольный треугольник, описанная окружность | Планиметрия | сред. |
| В6 | Прогрессии (геометрическая/арифметическая), сумма членов | Прогрессии | сред. |
| В7 | Текстовая задача: проценты/движение/работа/смеси | Текстовые | сред. |
| В8 | Двойные неравенства, целые решения | Неравенства | низкая–сред. |
| В9 | Функция: чётность/симметрия, значения | Функции | сред. |
| В10 | Планиметрия: правильные многоугольники, вписанная/описанная окружность | Планиметрия | сред. |
| В11 | Логарифмические уравнения | Логарифмы | сред. |
| В12 | Числа: дроби, деление с остатком, НОК/НОД, текст | Числа | сред.–выс. |
| В13 | Стереометрия: цилиндр/конус, сечения, площади | Стереометрия | сред.–выс. |
| В14 | Показательные неравенства | Показательные | сред.–выс. |
| В15 | Тригонометрические уравнения, отбор корней на промежутке | Тригонометрия | высокая |
| В16 | Логарифмические неравенства (часто метод рационализации) | Логарифмы | высокая |
| В17 | Стереометрия: подобие, сечение ∥ основанию, отношения площадей/объёмов | Стереометрия | высокая |
| В18 | Иррациональные уравнения | Иррациональные | высокая |
| В19 | Производная: промежутки монотонности/экстремумы, исследование функции | Производная/функции | высокая |
| В20 | Стереометрия: угол между прямыми/плоскостями в координатах/векторах | Стереометрия | очень выс. |
### 1.3. Выводы для стратегии (заложить в курс)
- **«Дешёвые» гарантированные баллы**: вся часть А + В1–В10 — это база и средний уровень. Их закрытие = проходной/средний балл. Приоритет №1 для треков «База» и «Ядро».
- **Стереометрия — сквозная и «дорогая»**: встречается ~5–6 раз (А2, А9, В1, В13, В17, В20), включая самые сложные В17/В20. Отдельный усиленный блок; именно сюда бьют «Кедр»-отработки (B20, шар, скрещивающиеся, угол прямой и плоскости).
- **Тригонометрия — частая и многоуровневая**: А3, А8, В4, В15. От простого к отбору корней. Нужен сильный модуль с выводом формул.
- **«Дорогие» сложные баллы**: В15, В16, В18, В19, В20 — для трека «Продвинутый». Метод рационализации (В16) и техника отбора корней (В15) дают непропорционально много.
- **Производная (В19)** — отдельный модуль; в школьной базе часто провисает.
---
## 2. Методические принципы курса
Сформулированы в т.ч. из roadmap-документа автора подборки (`К прочтению…docx`) и подтверждаются картой теста:
1. **Понимание > зубрёжка.** Формулы выводим, а не заучиваем (особенно тригонометрия: 2–3 факта → всё остальное). Заучивание — только для «атомов» (таблица значений, базовые тождества) и через интервальное повторение.
2. **Метод рационализации (замены множителей)** — стержневая техника для В16/В14/неравенств. Отдельный модуль; экономит баллы и время.
3. **Техника теста ≠ техника олимпиады.** Учим быстро решать закрытую часть (подстановка вариантов, прикидка, отсев), грамотно оформлять открытую часть, управлять временем (180 мин на 30 заданий).
4. **Реальные РТ/ЦТ — основной тренажёр.** Теория → типовые задачи → реальные задания этого номера из прошлых лет. В папке РТ 20062025 и ЦТ 2004–2024 — огромный банк.
5. **Диагностика и адресность.** Входной тест → персональный маршрут; постоянный детектор слабых тем (платформа умеет: точность <60% на 3+ попытках → тема в фокус).
6. **Интервальное повторение формул** через флешкарты со spaced repetition (готовый движок платформы).
7. **Спираль, а не линия.** Базовые темы повторяются на возрастающей сложности; финал — режим пробников (полные варианты на время).
---
## 3. Модульная программа (ядро)
9 блоков. Каждый модуль описан единым форматом:
**Цель · Что входит · Позиции в тесте · Уровень · Ключевые источники · Критерий освоения.**
(Полный список файлов-источников по каждому модулю и уровню — в [RESOURCES.md](RESOURCES.md).)
Обозначение уровня: 🟢 База · 🟡 Ядро · 🔴 Продвинутый (модуль может покрывать несколько уровней с разной глубиной).
### Блок I. Числа и вычисления 🟢
- **M1. Действительные числа, координатная прямая, оценка значений** — позиции А1, А4. Цель: уверенно читать числовую прямую, сравнивать/оценивать, проценты, отношения. Источники: «100 баллов» 01, Ткачук (низы), Вычисления_doc. Критерий: ≥90% на наборе А1/А4.
- **M2. Делимость, остатки, НОД/НОК, обыкновенные/десятичные дроби** — позиции А4, В3, В12. Цель: деление с остатком как формула, признаки делимости, текст на дроби/НОК. Источники: «100 баллов» 01, Сиротина (числа). Критерий: ≥85%, в т.ч. В12-тип.
- **M3. Стандартные преобразования числовых выражений** — сквозное (фундамент всего). Степени, корни, модуль числа, порядок действий. Источники: Вычисления_doc, Ткачук. Критерий: автоматизм.
### Блок II. Алгебраические преобразования 🟢🟡
- **M4. Многочлены, формулы сокращённого умножения, разложение на множители** — фундамент уравнений/неравенств. Источники: «100 баллов» 01–03, Ткачук. Критерий: безошибочное разложение, выделение полного квадрата.
- **M5. Степени и корни (степенная/иррациональная алгебра), ОДЗ выражений** — позиции А10, подготовка к В18. Источники: «100 баллов» 12 (Степенная и иррациональные), Irratsionalnye_Uravnenia.pdf. Критерий: верная ОДЗ, преобразование корней.
- **M6. Рациональные (алгебраические) дроби** — подготовка к рациональным уравнениям/неравенствам. Источники: «100 баллов» 05. Критерий: сокращение, приведение, область определения.
### Блок III. Уравнения и неравенства 🟢🟡🔴
- **M7. Линейные уравнения и неравенства, системы** — позиции А6, В8. Источники: «100 баллов» 03, «Материал по системам» (Кедр), «Операции с двойными неравенствами» (Кедр). Критерий: двойные неравенства, целые решения (В8).
- **M8. Квадратные уравнения и неравенства, теорема Виета** — позиции А5, фундамент. Источники: «100 баллов» 04. Критерий: Виет устно, метод интервалов для квадратичных.
- **M9. Рациональные уравнения и неравенства, метод интервалов** — позиции В-уровня. Источники: «100 баллов» 05, Neravenstva.pdf, «Эффективные пути решения неравенств». Критерий: метод интервалов с кратностями.
- **M10. Уравнения и неравенства с модулем** — Источники: «100 баллов» 06. Критерий: раскрытие модуля по определению и по промежуткам, геометрический смысл.
- **M11. Иррациональные уравнения и неравенства** — позиция В18. Источники: «100 баллов» 12, Irratsionalnye_Uravnenia.pdf, «Функциональные методы решения уравнений». Уровень 🟡🔴. Критерий: равносильные переходы с ОДЗ, В18-тип.
- **M12. Показательные уравнения и неравенства** — позиция В14. Источники: «100 баллов» 13–14. Критерий: В14-тип на время.
- **M13. Логарифмы: уравнения и неравенства** — позиции В11, В16. Источники: «100 баллов» 13–15, «Шпора по свойствам функций». Уровень 🟡🔴. Критерий: В11 уверенно; В16 — через ОДЗ.
- **M14. Метод рационализации (замена множителей)** 🔴 — стержень для В16/В14 и сложных неравенств. Источники: roadmap-ссылки автора + «Эффективные пути решения неравенств», Neravenstva.pdf. Критерий: решать В16 «в три строчки».
### Блок IV. Функции, графики, производная 🟡🔴
- **M15. Функции: ОДЗ, область значений, чётность/симметрия, монотонность** — позиции А10, В2, В9. Источники: «100 баллов» 16, «Шпора по свойствам функций» (Кедр), «Отработка функций» (Кедр). Критерий: В2/В9-тип.
- **M16. Преобразования графиков, чтение графиков** — поддержка В2/В9. Источники: «100 баллов» 16. Привязка sim: `graphtransform`. Критерий: строить/читать сдвиги-растяжения.
- **M17. Производная: смысл, правила, монотонность, экстремумы, исследование** — позиция В19. Источники: Пратусевич (ЕГЭ), Ткачук (анализ). Уровень 🟡🔴. Критерий: В19-тип (промежутки возрастания, наибольшее/наименьшее).
### Блок V. Тригонометрия 🟢🟡🔴
- **M18. Тригонометрический круг, значения, простейшие уравнения** — позиции А3. Источники: «Все формулы по тригонометрии для ЦТ» (Кедр, png), Trigonometrii_1. Привязка sim: `trigcircle`. Критерий: А3 устно.
- **M19. Тождества и формулы (вывод!), обратные функции** — позиции А8, В4. Источники: «формулы триги ВСЕ.pdf», «09-11 Тригонометрия», Trigonometria_2..5. Критерий: вывод формул из 2–3 базовых, В4-тип.
- **M20. Тригонометрические уравнения, отбор корней на промежутке** 🔴 — позиция В15. Источники: «Подборка заданий триг уравнений» (Кедр), Trigonometria_3..5. Критерий: В15-тип (сумма корней на интервале).
### Блок VI. Прогрессии и текстовые задачи 🟢🟡
- **M21. Арифметическая и геометрическая прогрессии** — позиции В3, В6. Источники: Progressii_I_Textovye_Zadachi.pdf, «100 баллов». Критерий: В3/В6-тип.
- **M22. Текстовые задачи: проценты, движение, работа, смеси/сплавы/растворы** — позиции А7, В7. Источники: «СОЧНАЯ подборка текстовых задач», «Текстовые задачи пути решения Инишева», «Отработка на сплавы/растворы» (Кедр), «Решение задач на концентрации». Критерий: А7 устно, В7-тип всех 4 видов.
### Блок VII. Планиметрия 🟡🔴
- **M23. Треугольники: признаки, площади, теоремы синусов/косинусов, окружности (вписанная/описанная)** — позиции В5. Источники: «100 баллов» 1718, 2325, 13_testy_Planimetria.pdf, Gordin_7-9 (для базы геометрии), Прасолов «Планиметрия» (🔴). Критерий: В5-тип.
- **M24. Четырёхугольники и правильные многоугольники** — позиции В10. Источники: «Свойства четырёхугольников» (Кедр), «100 баллов» 19–22. Критерий: В10-тип (правильный шестиугольник и т.п.).
- **M25. Окружность: углы, касательные, степень точки; координатный метод** — поддержка В5/В10. Источники: «Уравнение окружности» (Кедр), Клетеник (аналит. геометрия, 🔴). Критерий: координатный метод как запасной.
### Блок VIII. Стереометрия 🟡🔴 (усиленный — самый «дорогой» блок)
- **M26. Аксиоматика, взаимное расположение прямых и плоскостей, сечения** — позиции А2, В1. Источники: «100 баллов» 26–28, ШПОРА по СТЕОМЕ (Кедр), Калинин-Терёшин «Стереометрия». Критерий: А2/В1-тип.
- **M27. Многогранники: призма, пирамида, параллелепипед — объёмы, площади, сечения, подобие** — позиции В13(частично), В17. Источники: «Метод Кавальери», «Отработка по Шару», «100 баллов» стерео. Критерий: В17-тип (сечение ∥ основанию, отношения).
- **M28. Тела вращения: цилиндр, конус, шар/сфера** — позиции А9, В13. Источники: «Отработка по Шару» (Кедр), Калинин-Терёшин. Критерий: А9/В13-тип.
- **M29. Углы и расстояния в пространстве: угол между прямыми/прямой и плоскостью/плоскостями; координатно-векторный метод** 🔴 — позиция В20 (и В1). Источники: «Лабораторная для отработки В20», «Отработка В20 из РЦЭ-2025», «Отработка скрещивающиеся», «Отработка Угол между прям. и плоск.» (всё Кедр), «Векторы на экзаменах» (Шестаков), теорема о трёх синусах. Привязка sim: `stereo`. Критерий: В20-тип координатным методом.
### Блок IX. Продвинутое и комбинированное 🔴 (для трека на 90–100)
- **M30. Задачи с параметрами** — Источники: Высоцкий «Задачи с параметрами», Прокофьев «Задачи с параметрами». Критерий: графический и аналитический методы.
- **M31. Комбинированные задачи и нестандартные приёмы** — Источники: Сканави_2013, Ларченко_2021, Федорако Практикум, Барвенов/Бахтина «Тренинг ЦТ». Критерий: смешанные варианты без подсказки темы.
- **M32. Функциональные методы, уравнения в целых числах (по желанию)** — Источники: «Функциональные методы решения уравнений», Серпинский (целые числа). Олимпиадный бонус.
---
## 4. Уровневые траектории (универсальность)
### 4.1. Входная диагностика
Короткий адаптивный тест из реальных заданий разных номеров (по 1–2 на каждый раздел, смесь А и В).
Реализация: режим `mode='mock'`/диагностический набор `exam_tasks`. Результат → автоматический трек и список приоритетных модулей (детектор слабых тем платформы).
### 4.2. Три трека (ветвление по результату диагностики)
**🟢 Трек «База» (слабая база, цель — порог/средний балл).**
- Фокус: M1M10, M18M19, M21M24, M26 + часть А целиком и В1–В10.
- Источники: Ткачук, «60 уроков», базовая теория «100 баллов», Gordin_7-9 для геометрии.
- Глубина: восстановление школьных основ → типовые задания → А-часть на 90%+.
- Сложные В15/В16/В18/В19/В20 — обзорно («как минимум подступиться»), без обязательного мастерства.
**🟡 Трек «Ядро» (средний уровень, цель — высокий балл).**
- Фокус: все модули M1–M29, акцент на часть В и слабые темы из диагностики.
- Источники: «100 баллов» (тесты), РТ всех лет, ЦТ прошлых лет, точечные «Кедр»-отработки слабых тем.
- Глубина: уверенно вся часть А + В1–В19; В20 — координатным методом.
**🔴 Трек «Продвинутый» (сильная база, цель — 90–100).**
- Фокус: M9M14, M17, M20, M27M32 + полный разбор ошибок.
- Источники: Сканави, Высоцкий (параметры), Прасолов/Понарин/Калинин-Терёшин (геометрия), Барвенов/Бахтина, папка ЕГЭ (задачи 18/19 уровня).
- Глубина: метод рационализации, параметры, сложная стереометрия, скоростное решение полных вариантов; работа над оформлением и «глупыми» ошибками.
### 4.3. Правила ветвления
- Диагностика по разделу < 50% → раздел проходится с трека «База» независимо от общего трека.
- Раздел освоен на ≥85% → можно пропустить базовые модули и идти на повышенную сложность.
- Финальная фаза для всех треков — **режим пробников** (полные варианты РТ/ЦТ на время) + адресная доработка слабых тем.
---
## 5. Единый шаблон модуля (как модуль становится контентом платформы)
Каждый модуль M-N разворачивается в одинаковую структуру (= один `course_section` или связка `lesson`+тесты):
1. **Теория**`lesson` из `lesson_blocks`: `heading``text`/`formula` (вывод, а не список) → `callout` (типичные ошибки) → при необходимости `sim`/`geogebra` (геометрия, графики, тригокруг) → `flashcard` (формулы-атомы модуля).
2. **Разбор эталонных задач** — 3–5 решённых типовых заданий именно того номера теста (`text`+`formula` блоки или решённые `exam_tasks` с `solution_html`).
3. **Тренировка (трёхуровневая)** — наборы заданий 🟢/🟡/🔴 из `exam_tasks` (классифицированы `difficulty` 15, `topic`/`subtopic`). Практика-режим платформы: `GET /exam-prep/:key/topics/:slug/practice`.
4. **Мини-тест модуля** — короткий контрольный набор; порог mastery (см. §7).
5. **Карточки формул**`flashcard_deck` модуля, выдаётся классу/ученику (`flashcard_deck_access`), интервальное повторение.
6. **Привязка к реальным заданиям** — задания этого номера из РТ/ЦТ прошлых лет (банк `exam_tasks`), с привязкой к §учебника (`textbook_slug`/`textbook_paragraph`) для добора теории при ошибке.
7. **Критерий освоения** — конкретный порог по мини-тесту/практике (из §3 модуля).
---
## 6. Маппинг на платформу BQ-System
> ⚠️ Раздел ниже описывал маппинг на **exam-prep** (`exam_tasks`) — это оказалось НЕ тем местом:
> весь контент ЦЭ/ЦТ уже лежит в банке `questions`. **Актуальный маппинг — в
> [BUILD_ON_QUESTIONS.md](BUILD_ON_QUESTIONS.md).** Текст ниже сохранён как альтернатива (exam-prep,
> миграция 077 оставлена опцией), но основной путь — `questions`/`tests`/`assignments`/`courses`.
Платформа уже имеет почти всё необходимое (модуль exam-prep + курсы + флешкарты + sims + доступы). Наполнение = заполнение данных, не разработка движков.
### 6.1. Экзаменационный трек и банк заданий
- **`exam_tracks`**: создать трек, напр. `exam_key='ctmath'` (или `cemath`), `title='Подготовка к ЦЭ/ЦТ по математике'`, `subject_slug='math'`, `tasks_per_variant=30`, `duration_min=180`, `scoring_json` = шкала РИКЗ текущего года, `intro_html` = карта теста (§1).
- **`exam_topics`** (дерево тем): разделы (parent=NULL) = 9 блоков из §3; подтемы = модули M1–M32 (`slug`, `title`, `sort_order`, `textbook_slug`/`textbook_paragraph` как fallback). Это даёт навигацию по темам и детектор слабых тем.
- **`exam_tasks`** (главный актив): оцифровать задания из РТ/ЦТ. Каждой задаче проставить `variant`, `task_idx` (130), `task_type` (`mc` для А, `open`/`long` для В), `text_html`, `figure_html`, `opts_json` (для А), `answer`, `solution_html`, `topic`/`subtopic` (= slug модуля), `difficulty` (15), `textbook_slug`+`textbook_paragraph` (добор теории). Полные варианты → можно собирать `exam_mock_sessions` (пробники на время).
- Практика/пробники/слабые темы/дашборд — **уже реализованы** (`/api/exam-prep/...`), включаются автоматически после наполнения данными.
### 6.2. Теория как курс
- **`courses`**: `subject_slug='math'`, `title='ЦЭ/ЦТ: математика — теория и техника'`, `is_published=1`, обложка.
- **`course_sections`**: по одному на блок (I–IX).
- **`lessons`** + **`lesson_blocks`**: по шаблону §5. Типы блоков под рукой: `text`, `formula`, `callout`, `quiz`, `sim`, `geogebra`, `flashcard`, `image`, `table`, `accordion`.
- При ошибке в задании ученик уходит в `textbooks` (учебники-главы через `parent_slug`) по ссылке `textbook_slug`/`textbook_paragraph`.
### 6.3. Формулы — флешкарты со spaced repetition
- **`flashcard_decks`** по модулям (тригонометрия, логарифмы, площади/объёмы, прогрессии…), **`flashcard_cards`** = формула/факт.
- **`flashcard_reviews`** (SM-2 + learning steps) ведёт интервальное повторение; **`flashcard_deck_access`** раздаёт колоды классу/ученику.
### 6.4. Геометрия — симуляции
- **`lab_sims`** уже содержит математические: `graph`, `graphtransform`, `trigcircle`, `geometry`, `stereo`. Встраивать `{type:'sim'}` в уроки модулей M16, M18, M29.
- **`lab_sim_links`** связывает sim с темой/§учебника (`kind='topic'|'textbook'`, `ref_id`).
- (Опционально) задания на построение — `geometry_tasks`/`geometry_submissions`.
### 6.5. Выдача и доступ
- **`content_access`** (allowlist) — открыть курс/трек/учебники классу или ученику (`content_type`, `content_ref`, `scope`, `target_id`, `allow=1`).
- **`assignments`** — домашки: режимы `exam|practice|topic|repeat`, привязка `textbook_id`+`textbook_paragraphs` (чтение+тренировка), дедлайн, `max_attempts`.
- **`class_courses`** — назначить курс классу.
- **`exam_user_plan`** — личный план ученика (дата экзамена, дневная норма, фокус на слабых темах) — опционально, т.к. курс без жёстких дат.
### 6.6. Прогресс и аналитика
- `lesson_progress` (теория), `exam_attempts` (каждая попытка + верность + просмотр решения), `exam_mock_sessions` (пробники со счётом), `textbook_progress` (чтение §).
- Дашборд ученика (`/api/exam-prep/:key/dashboard`): heatmap по темам, точность, серия, прогноз балла по `scoring_json`.
---
## 7. Контроль, аналитика, пробники
- **Диагностика** (вход) → трек + приоритетные модули.
- **Mastery-порог модуля** (рекомендация): ≥80% точности на мини-тесте при ≥8 попытках для 🟡; ≥90% для модулей части А (🟢). Не освоено → модуль остаётся в фокусе.
- **Детектор слабых тем** (есть в платформе): подтема с точностью <60% на 3+ попытках → авто-фокус, доп. практика + ссылка на §учебника/урок.
- **Пробники** (`exam_mock_sessions`): полные варианты РТ/ЦТ на 180 мин; финальная фаза каждого трека. Источник вариантов — банк `exam_tasks` (по `variant`) из РТ 20162025 и ЦТ 20172024.
- **Прогноз балла**: точность по пробникам → первичный → тестовый через `scoring_json`.
- **Работа над ошибками** — обязательный шаг после каждого пробника (для 🔴 — анализ «глупых» ошибок и оформления).
---
## 8. Порядок наполнения контентом (приоритеты для построения курса)
Чтобы курс был полезен максимально быстро, наполнять в порядке «частотность × вес в балле × доступность готового материала»:
1. **Каркас платформы**: создать `exam_track`, дерево `exam_topics` (блоки I–IX → модули M1–M32), курс + секции.
2. **Часть А (А1–А10)** — оцифровать задания этих номеров из ЦТ/РТ (дешёвые гарантированные баллы, нужны всем трекам).
3. **В1–В10** — средний уровень, основной вес для «Ядра».
4. **Стереометрия** (M26M29) и **тригонометрия** (M18–M20) — частые и «дорогие»; здесь же готовые «Кедр»-отработки (B20, шар, скрещивающиеся, угол).
5. **Сложная часть В** (В14–В20: M11M14, M17, M20, M27, M29) — для «Продвинутого».
6. **Флешкарты формул** по мере наполнения теории модулей.
7. **Полные варианты-пробники** (сборка `exam_mock_sessions`) из РТ/ЦТ 20172025.
8. **Продвинутый блок IX** (параметры, комбинированные) — в последнюю очередь.
Оцифровка реальных заданий из PDF (РТ/ЦТ) — отдельная задача (OCR/ручной ввод в `exam_tasks`); в репозитории уже есть практика переноса сборников ЦТ (см. память `project_ct_seeded`).
---
## 9. Открытые вопросы и решения по умолчанию
| Вопрос | Решение по умолчанию (если не уточнят) |
|---|---|
| `exam_key` трека | `ctmath` (единый трек ЦЭ+ЦТ, формат совпадает) |
| Целевой год / шкала баллов | актуальная шкала РИКЗ; обновлять `scoring_json` ежегодно |
| Какой банк вариантов оцифровывать первым | РТ 2022–2025 + ЦТ 2024 (ближе всего к текущему формату) |
| Учебники для `textbook_slug`-привязки | переиспользовать существующие учебники платформы (алгебра/геометрия 7–11) + при пробелах создавать главы-справки |
| Язык контента | русский |
| Нужен ли отдельный «продвинутый» трек контентом сразу | нет — сначала База+Ядро (часть А + В1–В19), Продвинутый блок IX позже |
---
## 10. Что дальше
После согласования этого плана возможные следующие шаги (по запросу):
- Детализировать **один блок до уровня занятий** (теория-источник с номерами страниц, конкретные наборы задач, мини-тесты) — как пилот.
- Спроектировать **дерево `exam_topics`** в виде готового seed (slug-и, заголовки, привязки к учебникам).
- Составить **спецификацию оцифровки** заданий РТ/ЦТ в `exam_tasks` (поля, классификатор темы/сложности).
- Собрать **диагностический тест** (набор `exam_tasks` для входа).
+64
View File
@@ -0,0 +1,64 @@
# Курс «Подготовка к ЦЭ/ЦТ по математике» — план для BQ-System
Модульная программа подготовки к ЦЭ/ЦТ по математике, оформленная как курс платформы LearnSpace/BQ-System.
Универсальная (диагностика + 3 уровня), без жёсткой привязки к датам. Построена на разборе папки
`F:\!Рабочие\ЦТ\Математика\` и реального формата РИКЗ-2024.
> ⚠️ **ПИВОТ (2026-06-14)****ОТДЕЛЬНЫЙ МОДУЛЬ (2026-06-15).** Контент ЦЭ/ЦТ был уже в БД (банк
> `questions`, 1753 задания). По решению пользователя ЦТ оформлен как **отдельный модуль exam-prep**:
> реальные задания ЦТ-11 перенесены из `questions` в `exam_tasks` (exam_key=`ctmath`). Модуль живёт на
> **`/exam-prep/ctmath`**. Технический документ — **[BUILD_ON_QUESTIONS.md](BUILD_ON_QUESTIONS.md)** (§0a).
> Параллельно есть и теория-курс (courses.id=13) + диагностика — на общих подсистемах.
## Документы
| Файл | Что внутри | Статус |
|---|---|---|
| [PLAN.md](PLAN.md) | **Программа.** Карта экзамена, методика, 9 блоков / ~32 модуля, уровни, шаблон модуля. | актуально (кроме §6/§8 — см. пивот) |
| [BUILD_ON_QUESTIONS.md](BUILD_ON_QUESTIONS.md) | **Главный тех-документ.** Сборка курса на существующем банке `questions`: режимы `mode='ct'`/`'topic'`, таксономия тем, курс/уроки, диагностика, пробники, прогресс, порядок работ. | актуально |
| [PILOT_TRIGONOMETRY.md](PILOT_TRIGONOMETRY.md) | Эталон блока «Тригонометрия» до уроков/блоков/карточек — шаблон тиражирования. | актуально (тренажёр = `mode='topic'`) |
| [PILOT_STEREOMETRY.md](PILOT_STEREOMETRY.md) | Второй эталон — «Стереометрия» (координатный метод В20, sim `stereo`). | актуально |
| [RESOURCES.md](RESOURCES.md) | Инвентарь материалов папки по модулям/уровням (для добивки/гэпов). | актуально |
| [TOPICS_SEED.md](TOPICS_SEED.md) | Seed exam-prep (`exam_tracks/exam_topics`, миграция 077). | вторично (опция exam-prep) |
| [DIGITIZATION_SPEC.md](DIGITIZATION_SPEC.md) | Оцифровка РТ/ЦТ в `exam_tasks`. | вторично (оцифровка уже сделана) |
**Код:** [`backend/src/db/migrations/077_ctmath_track_topics.sql`](../../backend/src/db/migrations/077_ctmath_track_topics.sql) — миграция трека `ctmath` + дерева тем для exam-prep (валидирована in-memory). **Оставлена как опция, в БД НЕ применена.** Основной путь — банк `questions`.
## Ключевые факты
- **Формат экзамена**: часть А — А1–А10 (выбор из 5), часть В — В1–В20 (открытый ответ), всего **30 заданий**, ~180 мин, до 100 тестовых баллов; часть В весит больше.
- **Контент уже есть**: банк `questions` (`subject_id=3`) — **1753 задания** 20112025 (ЦЭ-2024 = 117, набор 2025 = 1020), размечены по темам (`topics`, 19 шт.) и годам. Залиты `backend/scripts/seed_math_ct*.js`.
- **Готовый механизм ЦТ**: `assignments` с `mode='ct'` собирает вариант (Часть A из `single/true_false` + Часть B из `multi/short_answer`); `mode='topic'` — тренажёр по теме. Сложность в банке — 1–3.
- **Самый «дорогой» блок** — стереометрия (~6 заданий, включая сложнейшие В17/В20).
## Порядок реализации (на банке `questions`, см. BUILD_ON_QUESTIONS §8)
1. Таксономия: добавить недостающие темы (Производная, Иррациональные, Модуль, Показательные ур., Параметры).
2. Каркас курса: `courses('math','ЦЭ/ЦТ — Математика')` + 9 `course_sections`.
3. Диагностика: `test` из ~14 реальных вопросов банка (по 1 на тему) → выдать.
4. Уроки по приоритету (стерео, тригонометрия) — теория по пилотам + кнопка практики `mode='topic'`.
5. Пробники: assignment `mode='ct'` (30 заданий) + тематические `mode='topic'`.
6. Карточки формул; выдача классам через `content_access`/`class_courses`.
## Статус
ПЛАН на банке `questions` (пивот). **Каркас курса создан в живой БД** (скрипт
`backend/scripts/seed_ctmath_course.js`, идемпотентный): 6 новых тем (id 7277), DRAFT-курс
«ЦЭ/ЦТ — Математика» (`courses.id=13`, не опубликован) + 9 секций (id 27–35). Существующие данные
не тронуты. Миграция 077 (exam-prep) в БД не применялась.
**Отдельный модуль exam-prep `ctmath` (2026-06-15): ПОДНЯТ.** Трек `ctmath` (enabled), дерево тем 41
(9+32), **723 задания** в `exam_tasks` (525 mc + 191 open + 7 long) из реального банка ЦТ-11.
Работает на `/exam-prep/ctmath` (дашборд, темы, практика, слабые темы, пробники). Скрипт-конвертер:
`backend/scripts/seed_ctmath_exam_tasks.js`.
Также (на общих подсистемах): **теория-курс `courses.id=13`** (черновик) — теперь **все 9 секций, 15 уроков**
(`lessons.id=4155`: тригонометрия 41–43, стереометрия 44–47, числа/преобразования/уравнения×2/функции/
прогрессии/планиметрия/продвинутое 48–55) + **4 колоды флешкарт формул** (`flashcard_decks.id=1114`, 49 карт:
тригонометрия/стереометрия/логарифмы-степени/производная) + диагностика `tests.id=164` + новые темы.
Осталось:
- ✅ пункт сайдбара · ✅ уроки всех блоков · ✅ колоды формул.
- выдать доступ ученикам: `content_access` (exam/ctmath) классу + раздать колоды (`flashcard_deck_access`) + опубликовать курс (`is_published=1`); решить видимость;
- мелкий фикс задач `exam_tasks.id=866, 1248` — скрипт `backend/scripts/fix_ctmath_misc.js --apply` (запускает пользователь);
- (опц.) углубить уроки (2-й урок в «лёгких» секциях); дотегировать вопросы под тонкие подтемы.
+132
View File
@@ -0,0 +1,132 @@
# Инвентарь материалов ЦТ/ЦЭ (математика) → привязка к модулям и уровням
> Карта папки `F:\!Рабочие\ЦТ\Математика\Математика\` к модулям программы из [PLAN.md](PLAN.md).
> Уровни: 🟢 База · 🟡 Ядро · 🔴 Продвинутый.
> Назначение: чтобы автор контента знал, из чего брать теорию/задачи для каждого модуля и что оцифровывать первым.
---
## A. Банки реальных заданий (главный тренажёр) — для `exam_tasks` / пробников
| Папка / файл | Что это | Уровень | Применение |
|---|---|---|---|
| `РТ\` (20062007 … 20242025) | Репетиционное тестирование, 3 этапа × 2 варианта, многие с ответами | 🟢🟡🔴 | Основной банк заданий по номерам; пробники. **Приоритет оцифровки: 2022–2025** (текущий формат А1–А10 / В1–В20) |
| `ЦТ-ЦЭ\` (2004 … 2024) + `…\Если плохо видно ответы…DJVU\` | Реальные ЦТ/ЦЭ прошлых лет + ответы | 🟢🟡🔴 | Банк заданий и эталонных решений. `ЦЭ-ЦТ-2024 МАТ.pdf` — эталон текущего формата |
| `ДРТ\` (20152023, 2024 + консультация) | Досрочное РТ + разборы консультаций | 🟡🔴 | Доп. варианты и методические разборы |
> Эти PDF — источник для наполнения таблицы `exam_tasks` (по варианту/номеру/теме/сложности) и сборки `exam_mock_sessions`.
---
## B. Посекционный курс «100 баллов all» (теория + тесты по темам) — каркас теории
Папка `Прочее\100 баллов all\`. Нумерация совпадает с темами — удобно ложится в модули.
| Файл | Модуль(и) | Уровень |
|---|---|---|
| `01 ПОЧИТАТЬ Числа.docx`, `Vychislenia_doc.pdf` | M1M3 | 🟢 |
| `03_Линейные_уравнения_и_неравенства.docx` | M7 | 🟢 |
| `04_Квадратные_уравнения_и_неравенства.docx` | M8 | 🟢🟡 |
| `05_Рациональные_уравнения_и_неравенства.docx`, `Neravenstva.pdf` | M9 | 🟡 |
| `06_Уравнения_и_неравенства_с_модулями.docx` | M10 | 🟡 |
| `12 Степенная и иррациональные.docx`, `Irratsionalnye_Uravnenia.pdf` | M5, M11 | 🟡🔴 |
| `13-14 Показательные и логарифмы.docx`, `13_14_….docx`, `Показательная_и_начала_логарифмов.docx` | M12, M13 | 🟡 |
| `15 Логарифмическая.docx` (+ УПРОЩЕННОЕ), `15-16 Логарифмические и функции.docx` | M13, M15 | 🟡🔴 |
| `16 Функции.docx` | M15, M16 | 🟡 |
| `09-11 Тригонометрия.docx`, `Trigonometrii_1.pdf`, `Trigonometria_2..5.pdf` | M18M20 | 🟢🟡🔴 |
| `Progressii_I_Textovye_Zadachi.pdf` | M21, M22 | 🟢🟡 |
| `17-18 Прямые, углы, треугольник.docx`, `18 Произвольный треугольник.docx`, `13_testy_Planimetria.pdf` | M23 | 🟡 |
| `19-20 РСТТ, параллелограмм.pdf`, `20 Параллелограмм и ромб.docx`, `21_22_Прямоугольник,_квадрат,_трапеция.*` | M24 | 🟡 |
| `23-25 Окружность.docx`, `23_24_Окружность…pdf`, `25_26_Окружность_и_4_угольники…pdf`, `23Ответы.docx` | M24, M25 | 🟡 |
| `26-… Стереометрия.docx`, `27-28 Начала стереометрии.docx` | M26 | 🟡 |
| `Функциональный метод.docx` | M11, M32 | 🔴 |
| `ВводныйТест-24.docx` + `Разбор вводного теста.pdf` | Диагностика | — |
---
## C. «Кедр от Егора» — точечная отработка слабых/частых тем
Папка `Кедр от Егора (бесплатные с тгк)\`.
| Файл | Модуль | Уровень |
|---|---|---|
| `Все_формулы_по_тригонометрии_для_ЦТ (1).png`, `Подборка_заданий_триг_уравнений.png` | M18–M20 (+флешкарты формул) | 🟢🟡🔴 |
| `Шпора_по_свойствам_функций_ct_matem.pdf` | M13, M15 (+флешкарты) | 🟡 |
| `Материал по системам.pdf`, `Операции_с_двойными_неравенствами.pdf` | M7 | 🟢🟡 |
| `Отработка функций.pdf` | M15 | 🟡 |
| `Отработка на сплавы_растворы (1).pdf` | M22 | 🟡 |
| `Свойства четырехугольников.pdf` | M24 | 🟡 |
| `Уравнение окружности _ Материал.pdf` | M25 | 🟡 |
| `ШПОРА по СТЕОМЕ.pdf` | M26 (+флешкарты) | 🟡 |
| `Отработка по Шару.pdf` | M28 | 🟡🔴 |
| `Отработка скрещивающиеся.pdf`, `Отработка__Угол_между_прям_и_плоск.pdf` | M29 | 🔴 |
| `Лабораторная для отработки В20.pdf`, `Отработка В20 из РЦЭ-2025.pdf` | M29 (целевая отработка В20) | 🔴 |
| `Математический_Адвент_Календарь (1).pdf` | смешанная практика | 🟡 |
| `читаем!!.docx` | методич. навигация | — |
---
## D. Сборники и справочники (фундамент / углубление)
Папка `сборники, справочники и не только\` + `Прочее\`.
| Файл | Назначение | Модули | Уровень |
|---|---|---|---|
| `Прочее\ЕГЭ\Ткачук_математика_абитуре.pdf` | «с низов до верхов» — базовый курс абитуриента | M1–M17 | 🟢🟡 |
| `60 уроков.pdf` | антология 60 уроков, систематический базовый курс | M1–M24 | 🟢🟡 |
| `sirotina…posobie_dlya_podgotovki_k_tsentralizo.pdf` | пособие для подготовки к ЦТ (Сиротина) | сквозное | 🟡 |
| `Веременюк.pdf` | пособие/задачник для ЦТ | сквозное | 🟡 |
| `тренинг… Барвенов С.А., Бахтина Т.П.pdf` | тренинг задач именно ЦТ | M31 (смешанная практика) | 🟡🔴 |
| `Федорако Практикум_2016.(pdf/djvu)` | практикум по математике | M31 | 🟡🔴 |
| `Ларченко_2021.pdf` | большой задачник (в т.ч. текстовые) | M22, M31 | 🟡🔴 |
| `Сканави_2013.pdf` | классический сборник для абитуриентов | M9–M31 | 🔴 |
| `Эффективные пути решения неравенств.pdf`, `Функциональные методы решения уравнений.pdf` | продвинутые техники (рационализация и др.) | M9, M11, M14 | 🔴 |
| `Метод Кавальери поиска объема тел.pdf` | объёмы тел | M27 | 🔴 |
| `Решение задач на концентрации матекатика егэ.pdf` | смеси/сплавы/растворы | M22 | 🟡🔴 |
| `Текстовые задачи пути решения Инишева.pdf` | методика текстовых задач | M22 | 🟡 |
| `Калинин А.Ю., Терешин Д.А. Стереометрия 10.djvu` | сильная стереометрия | M26–M29 | 🔴 |
| `Серпинский… О решении уравнений в целых числах.djvu` | уравнения в целых числах (олимп.) | M32 | 🔴 |
| `Зельдович_вышмата для физиков.djvu` | справочник анализа (бонус) | M17 | 🔴 |
---
## E. Папка `Прочее\ЕГЭ\` — углублённая геометрия и параметры (трек 🔴)
| Файл | Модули | Уровень |
|---|---|---|
| `Vysotskiy_Parametr.pdf`, `Задачи с параметрами при подготовке к ЕГЭ_Высоцкий В.С.djvu`, `Задачи с параметрами_Прокофьев А.А.pdf` | M30 (параметры) | 🔴 |
| `ПРОСОЛОВ ПЛАНИМЕТРИЯ.pdf`, `Ponarin … t.1 ПЛАНИМЕТРИЯ.pdf` | M23M25 | 🔴 |
| `ЗАДАЧИ ПО СТЕРЕОМЕТРИИ ПРАСОЛОВ.pdf`, `Ponarin t.2 СТЕРЕОМЕТРИЯ.pdf`, `Stereoma_Gordin.pdf`, `Векторы на экзаменах…Шестаков.djvu` | M26M29 | 🔴 |
| `Gordin_7-9.pdf` | база геометрии (восстановление) | M23–M24 | 🟢🟡 |
| `Сборник задач по аналитической геометрии_Клетеник.pdf` | координатный метод | M25, M29 | 🔴 |
| `Пратусевич … Алгебра и начала мат. анализа 11 (профиль).djvu`, `Пратусевич_метод рекомендации.pdf` | производная/анализ | M17 | 🔴 |
| `ПРАСОЛОВ ЗАДАЧИ ПО АЛГЕБРЕ.djvu`, `основы алгебры Кострикин(1..3).pdf` | углублённая алгебра (бонус) | M31–M32 | 🔴 |
| `Shestakov_S_EGE2019_…18.pdf`, `ege17.pdf`, `me-d15.pdf`, `Книга_Wild-a.pdf`, `Gordin_7-9.pdf` | разборы сложных задач ЕГЭ-уровня | M30–M31 | 🔴 |
> Примечание: материалы ЕГЭ — российский экзамен; берём как **источник методов и сложных задач**, а формат/специфику держим по белорусским РТ/ЦТ.
---
## F. Текстовые задачи (отдельный мини-банк)
Папка `Прочее\СОЧНАЯ подборка ТЕкстовых задач\`: `сборка текстовых задач.docx` + `ответики на сборку.docx` → модуль **M22**, уровни 🟡🔴.
---
## G. Навигационные / методические документы (не контент, а ориентиры)
| Файл | Роль |
|---|---|
| `К прочтению..._.docx` | roadmap автора подборки: философия, ссылки на видеоразборы (Трушин, Wild Mathing, П. Маслов), приоритетные слабые темы |
| `Прочее\Полный Курс по подготовке.docx` | список тем «полного курса» (совпадает с блоками программы) |
| `Кедр…\читаем!!.docx` | навигация по «Кедр»-материалам |
---
## H. Что оцифровывать в первую очередь (сводка приоритета)
1. `ЦТ-ЦЭ\ЦЭ-ЦТ-2024 МАТ.pdf` + `РТ\2022-2023 … 2024-2025` — текущий формат, для `exam_tasks` части А и В1–В10.
2. `Кедр…` отработки стереометрии/тригонометрии — готовые наборы по «дорогим» темам.
3. `100 баллов all` — теория модулей (в `lesson_blocks`) + тематические тесты.
4. Формулы из `Все_формулы_по_тригонометрии`, `ШПОРА по СТЕОМЕ`, `Шпора_по_свойствам_функций``flashcard_decks`.
5. Полные варианты РТ/ЦТ 2017–2025 → `exam_mock_sessions` (пробники).
+135
View File
@@ -0,0 +1,135 @@
# Seed: трек `ctmath` и дерево тем `exam_topics`
> ⚠️ **ВТОРИЧНО (опция exam-prep).** Это seed для подсистемы exam-prep (миграция 077, оставлена как
> опция). **Основной путь** — банк `questions`, см. **[BUILD_ON_QUESTIONS.md](BUILD_ON_QUESTIONS.md)**.
> Этот документ полезен, если в будущем решим использовать exam-prep (варианты/пробники/детектор
> слабых тем). Реальные slug-и учебников отсюда переиспользуются и для ссылок из уроков.
> Готовый к переносу в SQL-миграцию seed дерева тем для курса ЦЭ/ЦТ по математике.
> Соответствует реальной схеме платформы (см. `022_exam_prep.sql`, `024_exam_topics_seed.sql`, `028_exam_topic_textbook_links.sql`).
> Двухуровневая иерархия: **раздел** (`parent_slug=NULL`) → **подтема** (`parent_slug` = slug раздела). Slug — kebab-case.
> Соглашение из существующего `math9`: раздел sort 10/20/30…, подтемы внутри 11,12,13…
>
> ⚠️ `textbook_paragraph` намеренно оставлен NULL почти везде (точные номера § уточняются при маппинге контента — не выдумываем). `textbook_slug` проставлен реальными slug-ами учебников платформы (главы/хабы).
---
## 1. Трек `exam_tracks`
```sql
INSERT INTO exam_tracks (
exam_key, title, subject_slug, grade, duration_min,
tasks_per_variant, variants_count, scoring_json, intro_html, enabled, sort_order
) VALUES (
'ctmath',
'ЦЭ/ЦТ — Математика',
'math',
11,
180,
30,
0, -- variants_count: проставить по числу оцифрованных вариантов
'<scoring_json>', -- см. §4 ниже
'<intro_html>', -- см. §5 ниже
1,
20
);
```
---
## 2. Разделы (sections, `parent_slug = NULL`)
| slug | title | description | sort | textbook_slug |
|---|---|---|---|---|
| `numbers` | Числа и вычисления | Действительные числа, делимость, проценты, преобразование числовых выражений | 10 | `math-6` |
| `expressions` | Алгебраические преобразования | Многочлены, степени и корни, рациональные дроби, ОДЗ | 20 | `algebra-7` |
| `equations` | Уравнения и неравенства | Линейные, квадратные, рациональные, модуль, иррациональные, показательные, логарифмические; метод рационализации | 30 | `algebra-9` |
| `functions` | Функции и производная | Свойства функций, графики, исследование с производной | 40 | `algebra-9-ch2` |
| `trigonometry` | Тригонометрия | Круг, тождества, уравнения и отбор корней | 50 | `algebra-10-ch1` |
| `word-sequences` | Прогрессии и текстовые задачи | Арифметическая/геометрическая прогрессии; проценты, движение, работа, смеси | 60 | `algebra-9-ch4` |
| `planimetry` | Планиметрия | Треугольники, четырёхугольники, окружность; координатный метод | 70 | `geometry-8` |
| `stereometry` | Стереометрия | Расположение, многогранники, тела вращения, углы и расстояния | 80 | `geometry-10` |
| `advanced` | Продвинутое и комбинированное | Параметры, комбинированные задачи, функциональные методы | 90 | NULL |
---
## 3. Подтемы (модули M1–M32, `parent_slug` = раздел)
Колонка «Позиции» — номера заданий теста (из карты §1.2 PLAN.md), помогает классификатору и приоритизации.
| slug | parent | title | sort | позиции | textbook_slug |
|---|---|---|---|---|---|
| `num-real` | numbers | Действительные числа, координатная прямая | 11 | А1, А4 | `math-6` |
| `num-divisibility` | numbers | Делимость, дроби, НОД/НОК | 12 | А4, В3, В12 | `math-5-ch1` |
| `num-expressions` | numbers | Преобразование числовых выражений | 13 | сквозное | `algebra-7-ch2` |
| `expr-polynomials` | expressions | Многочлены, ФСУ, разложение на множители | 21 | сквозное | `algebra-7-ch2` |
| `expr-powers-roots` | expressions | Степени и корни, ОДЗ выражений | 22 | А10 | `algebra-10-ch2` |
| `expr-fractions` | expressions | Рациональные (алгебраические) дроби | 23 | — | `algebra-9-ch1` |
| `eq-linear` | equations | Линейные уравнения/неравенства, системы | 31 | А6, В8 | `algebra-7-ch3` |
| `eq-quadratic` | equations | Квадратные уравнения/неравенства, Виет | 32 | А5 | `algebra-8` |
| `eq-rational` | equations | Рациональные уравнения/неравенства, метод интервалов | 33 | В-уровень | `algebra-9-ch3` |
| `eq-modulus` | equations | Уравнения и неравенства с модулем | 34 | — | `algebra-9` |
| `eq-irrational` | equations | Иррациональные уравнения/неравенства | 35 | В18 | `algebra-10-ch2` |
| `eq-exponential` | equations | Показательные уравнения/неравенства | 36 | В14 | `algebra-11-ch2` |
| `eq-logarithmic` | equations | Логарифмические уравнения/неравенства | 37 | В11, В16 | `algebra-11-ch3` |
| `eq-rationalization` | equations | Метод рационализации (замена множителей) | 38 | В16, В14 | `algebra-11` |
| `fn-properties` | functions | Свойства функций: ОДЗ, чётность, монотонность | 41 | А10, В2, В9 | `algebra-9-ch2` |
| `fn-graphs` | functions | Графики и их преобразования, чтение графиков | 42 | В2, В9 | `algebra-9-ch2` |
| `fn-derivative` | functions | Производная: монотонность, экстремумы, исследование | 43 | В19 | `algebra-10-ch3` |
| `trig-circle` | trigonometry | Тригонометрический круг, значения, простейшие ур-ия | 51 | А3 | `algebra-10-ch1` |
| `trig-identities` | trigonometry | Тождества и формулы (вывод), обратные функции | 52 | А8, В4 | `algebra-10-ch1` |
| `trig-equations` | trigonometry | Триг. уравнения, отбор корней на промежутке | 53 | В15 | `algebra-10-ch1` |
| `seq-progressions` | word-sequences | Арифметическая и геометрическая прогрессии | 61 | В3, В6 | `algebra-9-ch4` |
| `word-problems` | word-sequences | Текстовые: проценты, движение, работа, смеси | 62 | А7, В7 | `math-6-ch2` |
| `plan-triangles` | planimetry | Треугольники, площади, теоремы синусов/косинусов, окружности | 71 | В5 | `geometry-8` |
| `plan-quadrilaterals` | planimetry | Четырёхугольники и правильные многоугольники | 72 | В10 | `geometry-8-ch1` |
| `plan-circle` | planimetry | Окружность: углы, касательные; координатный метод | 73 | В5, В10 | `geometry-8-ch4` |
| `ster-basics` | stereometry | Расположение прямых/плоскостей, сечения | 81 | А2, В1 | `geometry-10` |
| `ster-polyhedra` | stereometry | Многогранники: объёмы, площади, сечения, подобие | 82 | В13, В17 | `geometry-10` |
| `ster-rotation` | stereometry | Тела вращения: цилиндр, конус, шар/сфера | 83 | А9, В13 | `geometry-11` |
| `ster-angles-distances` | stereometry | Углы и расстояния; координатно-векторный метод | 84 | В20, В1 | `geometry-11` |
| `adv-parameters` | advanced | Задачи с параметрами | 91 | — | NULL |
| `adv-combined` | advanced | Комбинированные задачи, нестандартные приёмы | 92 | — | NULL |
| `adv-functional` | advanced | Функциональные методы, целые числа (бонус) | 93 | — | NULL |
---
## 4. `scoring_json` (шкала балла)
⚠️ Платформенный `scoring_json` отображает **число верных → балл**. Реальный ЦЭ/ЦТ: первичный балл (часть В весит больше) → 100 тестовых по официальной таблице РИКЗ. Здесь — **иллюстративный placeholder** на 100-балльную шкалу; заменить на официальную таблицу года, в идеале — учитывая вес В-заданий ≈ 2× А в первичном.
```json
[
{"correct":30,"score":100},{"correct":28,"score":92},{"correct":26,"score":85},
{"correct":24,"score":78},{"correct":22,"score":71},{"correct":20,"score":64},
{"correct":18,"score":57},{"correct":16,"score":50},{"correct":14,"score":43},
{"correct":12,"score":36},{"correct":10,"score":30},{"correct":8,"score":24},
{"correct":6,"score":17},{"correct":4,"score":11},{"correct":2,"score":5},
{"correct":0,"score":0}
]
```
> Точнее: ввести веса (`primary = Σ(A:1) + Σ(B:2)` → max 10+40=50 первичных), затем official primary→test grid. Если будете расширять модель — это правка `exam_tracks.scoring_json` + логики подсчёта в `exam-prep.js` (отдельная задача, не для seed).
---
## 5. `intro_html` (вступление трека)
Готовый текст для `exam_tracks.intro_html` (карта теста + что внутри):
```html
<p><b>Подготовка к ЦЭ/ЦТ по математике.</b> Формат: часть А — 10 заданий с выбором ответа (А1–А10),
часть В — 20 заданий с открытым ответом (В1–В20), всего 30 заданий, ~180 минут, без калькулятора.</p>
<p>Курс устроен по темам с входной диагностикой и тремя уровнями сложности (База / Ядро / Продвинутый):
теория с выводом формул, разбор эталонных задач, тренажёр по темам, карточки формул с интервальным
повторением и пробные экзамены с таймером на реальных вариантах РТ/ЦТ прошлых лет.</p>
```
---
## 6. Заметки по переносу в миграцию
- Файл: новая миграция `backend/src/db/migrations/0XX_ctmath_track_topics.sql` (номер — следующий свободный; в репозитории миграции до 076+, проверить актуальный максимум перед созданием).
- Порядок: `INSERT exam_tracks``INSERT exam_topics` (сначала разделы, потом подтемы — FK на parent нет, но для читаемости) → `UPDATE … SET textbook_slug` по таблицам §2–§3.
- `content_access`: после создания трека открыть его классам/ученикам (`content_type='exam'`, `content_ref='ctmath'`).
- Совместимость: `exam_key='ctmath'` уникален, существующие треки (`math9` и др.) не затрагиваются.