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>
This commit is contained in:
@@ -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);
|
||||||
@@ -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: ничего не записано.' : 'Готово. Уроки стереометрии добавлены (черновик курса).');
|
||||||
@@ -52,11 +52,11 @@
|
|||||||
Работает на `/exam-prep/ctmath` (дашборд, темы, практика, слабые темы, пробники). Скрипт-конвертер:
|
Работает на `/exam-prep/ctmath` (дашборд, темы, практика, слабые темы, пробники). Скрипт-конвертер:
|
||||||
`backend/scripts/seed_ctmath_exam_tasks.js`.
|
`backend/scripts/seed_ctmath_exam_tasks.js`.
|
||||||
|
|
||||||
Также (на общих подсистемах): теория-курс `courses.id=13` (черновик) + уроки тригонометрии `41–43` +
|
Также (на общих подсистемах): теория-курс `courses.id=13` (черновик) + уроки **тригонометрии `41–43`**
|
||||||
диагностика `tests.id=164` + новые темы.
|
и **стереометрии `44–47`** (по пилотам) + диагностика `tests.id=164` + новые темы.
|
||||||
|
|
||||||
Осталось:
|
Осталось:
|
||||||
- выдать доступ ученикам: `content_access` (content_type='exam', content_ref='ctmath') классу/ученику;
|
- ✅ пункт сайдбара на `/exam-prep/ctmath` — добавлен.
|
||||||
- добавить пункт сайдбара на `/exam-prep/ctmath`;
|
- выдать доступ ученикам: `content_access` (content_type='exam', content_ref='ctmath') классу/ученику; решить видимость пункта для учеников;
|
||||||
- мелкий фикс задачи `exam_tasks.id=1248` (бракованный источник);
|
- мелкий фикс задач `exam_tasks.id=866, 1248` — скрипт `backend/scripts/fix_ctmath_misc.js --apply` (запускает пользователь);
|
||||||
- (опц.) дотегировать вопросы под тонкие подтемы; дополнить уроки остальных блоков.
|
- (опц.) уроки остальных 7 блоков; колоды карточек формул; дотегировать вопросы под тонкие подтемы.
|
||||||
|
|||||||
Reference in New Issue
Block a user