From a982628d04342932f974138fb8d5431463206427 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Mon, 15 Jun 2026 11:48:39 +0300 Subject: [PATCH] =?UTF-8?q?feat(ct-math):=20=D1=83=D1=80=D0=BE=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=B2=D1=81=D0=B5=D1=85=20=D0=BE=D1=81=D1=82=D0=B0=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D1=8B=D1=85=20=D0=B1=D0=BB=D0=BE=D0=BA=D0=BE=D0=B2?= =?UTF-8?q?=20(48-55)=20+=204=20=D0=BA=D0=BE=D0=BB=D0=BE=D0=B4=D1=8B=20?= =?UTF-8?q?=D1=84=D0=BB=D0=B5=D1=88=D0=BA=D0=B0=D1=80=D1=82=20=D1=84=D0=BE?= =?UTF-8?q?=D1=80=D0=BC=D1=83=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- backend/scripts/seed_ctmath_flashcards.js | 87 ++++++++++ backend/scripts/seed_ctmath_lessons_rest.js | 176 ++++++++++++++++++++ plans/ct-math/README.md | 12 +- 3 files changed, 270 insertions(+), 5 deletions(-) create mode 100644 backend/scripts/seed_ctmath_flashcards.js create mode 100644 backend/scripts/seed_ctmath_lessons_rest.js diff --git a/backend/scripts/seed_ctmath_flashcards.js b/backend/scripts/seed_ctmath_flashcards.js new file mode 100644 index 0000000..ef909c4 --- /dev/null +++ b/backend/scripts/seed_ctmath_flashcards.js @@ -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; раздать классу можно через доступ к колоде).'); diff --git a/backend/scripts/seed_ctmath_lessons_rest.js b/backend/scripts/seed_ctmath_lessons_rest.js new file mode 100644 index 0000000..755d666 --- /dev/null +++ b/backend/scripts/seed_ctmath_lessons_rest.js @@ -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 r0', 'Равносильные переходы'), + 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\'>0\\Rightarrow\\text{возрастает};\\quad f\'<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: ничего не записано.' : 'Готово. Уроки остальных блоков добавлены (черновик курса).'); diff --git a/plans/ct-math/README.md b/plans/ct-math/README.md index 26546c1..69ab2ac 100644 --- a/plans/ct-math/README.md +++ b/plans/ct-math/README.md @@ -52,11 +52,13 @@ Работает на `/exam-prep/ctmath` (дашборд, темы, практика, слабые темы, пробники). Скрипт-конвертер: `backend/scripts/seed_ctmath_exam_tasks.js`. -Также (на общих подсистемах): теория-курс `courses.id=13` (черновик) + уроки **тригонометрии `41–43`** -и **стереометрии `44–47`** (по пилотам) + диагностика `tests.id=164` + новые темы. +Также (на общих подсистемах): **теория-курс `courses.id=13`** (черновик) — теперь **все 9 секций, 15 уроков** +(`lessons.id=41–55`: тригонометрия 41–43, стереометрия 44–47, числа/преобразования/уравнения×2/функции/ +прогрессии/планиметрия/продвинутое 48–55) + **4 колоды флешкарт формул** (`flashcard_decks.id=11–14`, 49 карт: +тригонометрия/стереометрия/логарифмы-степени/производная) + диагностика `tests.id=164` + новые темы. Осталось: -- ✅ пункт сайдбара на `/exam-prep/ctmath` — добавлен. -- выдать доступ ученикам: `content_access` (content_type='exam', content_ref='ctmath') классу/ученику; решить видимость пункта для учеников; +- ✅ пункт сайдбара · ✅ уроки всех блоков · ✅ колоды формул. +- выдать доступ ученикам: `content_access` (exam/ctmath) классу + раздать колоды (`flashcard_deck_access`) + опубликовать курс (`is_published=1`); решить видимость; - мелкий фикс задач `exam_tasks.id=866, 1248` — скрипт `backend/scripts/fix_ctmath_misc.js --apply` (запускает пользователь); -- (опц.) уроки остальных 7 блоков; колоды карточек формул; дотегировать вопросы под тонкие подтемы. +- (опц.) углубить уроки (2-й урок в «лёгких» секциях); дотегировать вопросы под тонкие подтемы.