diff --git a/backend/scripts/seed_ctmath_flashcards_p2.js b/backend/scripts/seed_ctmath_flashcards_p2.js new file mode 100644 index 0000000..5ad07b0 --- /dev/null +++ b/backend/scripts/seed_ctmath_flashcards_p2.js @@ -0,0 +1,147 @@ +'use strict'; +/* ─────────────────────────────────────────────────────────────────────────── + seed_ctmath_flashcards_p2.js + Ещё две колоды карточек для подготовки к ЦЭ/ЦТ (интервальное повторение): + + 1. «ЦТ · Планиметрия — формулы» — треугольники, четырёхугольники, окружность + 2. «ЦТ · Свойства функций» — чтение графика: D(y), E(y), нули, + монотонность, экстремумы, f'(x), чётность, сдвиги + + Источники (бесплатные материалы Кедр от Егора): + • Свойства четырехугольников.pdf (параллелограмм/прямоугольник/ромб/квадрат) + • Уравнение окружности _ Материал.pdf (уравнение, радиус, расстояние, прямая) + • Шпора_по_свойствам_функций_ct_matem.pdf (графический разбор свойств) + Формулы треугольника и площадей — базовый набор ЦТ (в шпорах Кедр их нет). + + Математика — KaTeX inline $…$ (страница флешкарт рендерит \( \), \[ \], $ $; НЕ $$). + Те же владелец/таблицы/стиль, что seed_ctmath_flashcards.js (Тригонометрия и т.д.). + + Идемпотентность: колода ищется по (user_id, title). Если уже есть и наполнена — + пропуск (не клобберит SR-прогресс). Пустую/новую — наполняет. + + Запуск: + node backend/scripts/seed_ctmath_flashcards_p2.js # DRY-RUN (по умолчанию) + node backend/scripts/seed_ctmath_flashcards_p2.js --apply # запись в БД + + ⚠️ Массовую запись в БД запускает ПОЛЬЗОВАТЕЛЬ вручную (авто-режим Claude Code + блокирует продакшн-записи). Без --apply ничего не пишется — только сводка. + ─────────────────────────────────────────────────────────────────────────── */ + +const db = require('../src/db/db'); +const APPLY = process.argv.includes('--apply'); + +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: '#FB5607', + descr: 'Треугольники, четырёхугольники, окружность. Формулы для планиметрических задач ЦЭ/ЦТ.', + cards: [ + // ── Треугольник ────────────────────────────────────────────────────── + ['Сумма углов треугольника', '$\\angle A+\\angle B+\\angle C=180^\\circ$'], + ['Площадь треугольника через основание и высоту', '$S=\\dfrac12\\,a\\,h_a$'], + ['Площадь треугольника через две стороны и угол', '$S=\\dfrac12\\,ab\\sin C$'], + ['Теорема Пифагора (прямоугольный $\\triangle$)', '$c^2=a^2+b^2$, где $c$ — гипотенуза'], + ['Теорема косинусов', '$c^2=a^2+b^2-2ab\\cos C$'], + ['Теорема синусов', '$\\dfrac{a}{\\sin A}=\\dfrac{b}{\\sin B}=\\dfrac{c}{\\sin C}=2R$'], + ['Формула Герона', '$S=\\sqrt{p(p-a)(p-b)(p-c)}$, где $p=\\dfrac{a+b+c}{2}$'], + ['Радиус вписанной окружности ($\\triangle$)', '$r=\\dfrac{S}{p}$ ($p$ — полупериметр)'], + ['Радиус описанной окружности ($\\triangle$)', '$R=\\dfrac{abc}{4S}$'], + ['Площадь равностороннего треугольника', '$S=\\dfrac{a^2\\sqrt3}{4}$'], + ['Средняя линия треугольника', 'Параллельна стороне и равна её половине: $m=\\dfrac{a}{2}$'], + ['Медиана к гипотенузе (прямоугольный $\\triangle$)', 'Равна половине гипотенузы: $m_c=\\dfrac{c}{2}$'], + // ── Четырёхугольники ───────────────────────────────────────────────── + ['Параллелограмм — определение', 'Четырёхугольник, у которого противоположные стороны попарно параллельны'], + ['Свойства параллелограмма', 'Противолежащие стороны и углы равны; сумма углов при одной стороне $180^\\circ$; диагонали точкой пересечения делятся пополам'], + ['Сумма квадратов диагоналей параллелограмма', '$d_1^2+d_2^2=2(a^2+b^2)$'], + ['Площадь параллелограмма', '$S=a\\,h_a=ab\\sin\\alpha$'], + ['Прямоугольник — отличие и диагонали', 'Параллелограмм со всеми прямыми углами; диагонали равны: $AC=BD$'], + ['Площадь прямоугольника', '$S=ab$'], + ['Ромб — отличие и диагонали', 'Параллелограмм со всеми равными сторонами; диагонали $\\perp$ и являются биссектрисами углов'], + ['Площадь ромба', '$S=\\dfrac12\\,d_1 d_2=a^2\\sin\\alpha$'], + ['Квадрат — диагональ и площадь', '$d=a\\sqrt2$; $S=a^2=\\dfrac12\\,d^2$'], + ['Средняя линия трапеции', '$m=\\dfrac{a+b}{2}$ — полусумма оснований'], + ['Площадь трапеции', '$S=\\dfrac{a+b}{2}\\cdot h$'], + // ── Окружность ─────────────────────────────────────────────────────── + ['Длина окружности', '$C=2\\pi R=\\pi D$'], + ['Площадь круга', '$S=\\pi R^2$'], + ['Длина дуги в $n^\\circ$', '$l=\\dfrac{\\pi R n}{180}$'], + ['Площадь сектора в $n^\\circ$', '$S=\\dfrac{\\pi R^2 n}{360}$'], + ['Вписанный угол', 'Равен половине центрального, опирающегося на ту же дугу'], + ['Угол, опирающийся на диаметр', 'Прямой: $90^\\circ$'], + ['Уравнение окружности', '$(x-x_0)^2+(y-y_0)^2=R^2$ — центр $(x_0;y_0)$, радиус $R$'], + ['Расстояние между точками', '$d=\\sqrt{(x_2-x_1)^2+(y_2-y_1)^2}$'], + ]}, + + { title: 'ЦТ · Свойства функций', color: '#3A86FF', + descr: 'Чтение графика функции: область определения и значений, нули, монотонность, экстремумы, производная, чётность, сдвиги.', + cards: [ + ["Область определения $D(y)$", "Множество всех значений $x$, при которых функция существует (проекция графика на ось $Ox$)"], + ["Множество значений $E(y)$", "Множество всех значений $y$, которые принимает функция (проекция графика на ось $Oy$)"], + ["Нули функции", "Значения $x$, при которых $f(x)=0$ — точки пересечения графика с осью $Ox$"], + ["Пересечение графика с осью $Oy$", "Точка $(0;\\,f(0))$ — подставляем $x=0$"], + ["Наибольшее и наименьшее значения функции", "Ордината $y$ самой высокой и самой низкой точек графика"], + ["Функция возрастает на промежутке", "Большему $x$ соответствует большее $y$ (график идёт вверх); признак: $f'(x)>0$"], + ["Функция убывает на промежутке", "Большему $x$ соответствует меньшее $y$ (график идёт вниз); признак: $f'(x)<0$"], + ["Промежутки знакопостоянства", "$y>0$ — где график выше оси $Ox$; $y<0$ — где ниже оси $Ox$"], + ["Точка максимума $x_{\\max}$", "Точка, в которой функция меняет возрастание на убывание ($f'$ меняет знак с $+$ на $-$)"], + ["Точка минимума $x_{\\min}$", "Точка, в которой функция меняет убывание на возрастание ($f'$ меняет знак с $-$ на $+$)"], + ["Экстремум функции", "Значение $y=f(x)$ в точке максимума или минимума (сама точка $x$ — точка экстремума)"], + ["Знак $f'(x)$ и монотонность", "$f'(x)>0$ → функция возрастает; $f'(x)<0$ → функция убывает"], + ["Условие $f'(x)=0$", "Точка, подозрительная на экстремум: экстремум есть, если $f'$ при переходе меняет знак"], + ["Чётная функция", "$f(-x)=f(x)$; график симметричен относительно оси $Oy$"], + ["Нечётная функция", "$f(-x)=-f(x)$; график симметричен относительно начала координат"], + ["Сдвиг $y=f(x)+a$ (при $a>0$)", "График сдвигается вверх на $a$"], + ["Сдвиг $y=f(x)-a$ (при $a>0$)", "График сдвигается вниз на $a$"], + ["Сдвиг $y=f(x+a)$ (при $a>0$)", "График сдвигается влево на $a$"], + ["Сдвиг $y=f(x-a)$ (при $a>0$)", "График сдвигается вправо на $a$"], + ]}, +]; + +/* ── self-check: чаще всего KaTeX ломают непарные $ или {} ─────────────────── */ +let bad = 0; +for (const d of DECKS) { + d.cards.forEach(([f, b], i) => { + [['front', f], ['back', b]].forEach(([side, s]) => { + const dollars = (s.match(/\$/g) || []).length; + const braces = (s.match(/\{/g) || []).length - (s.match(/\}/g) || []).length; + if (dollars % 2 !== 0) { console.error(`✗ непарный $ — «${d.title}» #${i + 1} (${side}): ${s}`); bad++; } + if (braces !== 0) { console.error(`✗ непарные {} — «${d.title}» #${i + 1} (${side}): ${s}`); bad++; } + }); + }); +} +if (bad) { console.error(`\nСамопроверка: ${bad} проблем — исправь до записи.\n`); process.exit(1); } + +const findDeck = db.prepare('SELECT id FROM flashcard_decks WHERE user_id=? AND title=?'); +const countCard = db.prepare('SELECT COUNT(*) c FROM flashcard_cards WHERE deck_id=?'); +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(`\n=== seed_ctmath_flashcards_p2 (${APPLY ? 'APPLY' : 'DRY-RUN'}) ===`); +console.log('владелец user_id =', owner, '\n'); + +let plannedDecks = 0, plannedCards = 0; + +for (const d of DECKS) { + const ex = findDeck.get(owner, d.title); + if (ex) { + const have = countCard.get(ex.id).c; + if (have > 0) { console.log(`• «${d.title}» (id ${ex.id}) — уже наполнена (${have} карт), пропуск`); continue; } + // колода есть, но пустая → дольём карты + plannedDecks++; plannedCards += d.cards.length; + console.log(`+ «${d.title}» (id ${ex.id}) — пустая, долить ${d.cards.length} карт`); + if (APPLY) d.cards.forEach(([f, b], i) => insCard.run(ex.id, f, b, i)); + continue; + } + plannedDecks++; plannedCards += d.cards.length; + console.log(`+ «${d.title}» — новая колода, ${d.cards.length} карт`); + if (APPLY) { + const did = insDeck.run(owner, d.title, d.descr, d.color).lastInsertRowid; + d.cards.forEach(([f, b], i) => insCard.run(did, f, b, i)); + console.log(` создана id ${did}`); + } +} + +console.log(`\nИтого к ${APPLY ? 'записи' : 'добавлению'}: колод ${plannedDecks}, карт ${plannedCards}.`); +if (!APPLY) console.log('DRY-RUN: ничего не записано. Для записи: node backend/scripts/seed_ctmath_flashcards_p2.js --apply\n'); +else console.log('Готово. Колоды добавлены (владелец — admin; раздать классу — через доступ к колоде).\n');