Files
Learn_System/backend/scripts/seed_ctmath_lessons_stereo.js
T
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

129 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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: ничего не записано.' : 'Готово. Уроки стереометрии добавлены (черновик курса).');