diff --git a/backend/scripts/seed_ctmath_flashcards_p3.js b/backend/scripts/seed_ctmath_flashcards_p3.js new file mode 100644 index 0000000..88977e3 --- /dev/null +++ b/backend/scripts/seed_ctmath_flashcards_p3.js @@ -0,0 +1,115 @@ +'use strict'; +/* ─────────────────────────────────────────────────────────────────────────── + seed_ctmath_flashcards_p3.js + Ещё две колоды карточек для подготовки к ЦЭ/ЦТ (интервальное повторение): + + 1. «ЦТ · Прогрессии — формулы» — арифметическая + геометрическая + 2. «ЦТ · Двойные неравенства» — оценка a±b, a·b, a/b почленно (+ловушки) + + Источники: + • Прогрессии — канонический набор формул ЦТ (отдельной шпоры Кедр нет). + • Двойные неравенства — Кедр «Операции_с_двойными_неравенствами.pdf» + (сложение/умножение/вычитание/деление двойных неравенств; ловушки строгости + и запрет почленного вычитания/деления). + + Математика — KaTeX inline $…$ (страница флешкарт рендерит \( \), \[ \], $ $; НЕ $$). + Те же владелец/таблицы/стиль, что seed_ctmath_flashcards.js / _p2.js. + + Идемпотентность: колода ищется по (user_id, title). Если уже есть и наполнена — + пропуск (не клобберит SR-прогресс). Пустую/новую — наполняет. + + Запуск: + node backend/scripts/seed_ctmath_flashcards_p3.js # DRY-RUN (по умолчанию) + node backend/scripts/seed_ctmath_flashcards_p3.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: '#06D6A0', + descr: 'Арифметическая и геометрическая прогрессии: n-й член, суммы, характеристические свойства.', + cards: [ + // ── Арифметическая ─────────────────────────────────────────────────── + ['Арифметическая прогрессия — $n$-й член', '$a_n=a_1+(n-1)d$'], + ['Разность арифметической прогрессии', '$d=a_{n+1}-a_n$ (постоянная)'], + ['Характеристическое свойство арифм. прогрессии', '$a_n=\\dfrac{a_{n-1}+a_{n+1}}{2}$ — каждый член есть среднее арифметическое соседних'], + ['Сумма $n$ членов арифм. прогрессии', '$S_n=\\dfrac{a_1+a_n}{2}\\cdot n$'], + ['Сумма арифм. прогрессии через $d$', '$S_n=\\dfrac{2a_1+(n-1)d}{2}\\cdot n$'], + ['Связь двух членов арифм. прогрессии', '$a_n=a_k+(n-k)d$'], + // ── Геометрическая ─────────────────────────────────────────────────── + ['Геометрическая прогрессия — $n$-й член', '$b_n=b_1\\,q^{\\,n-1}$'], + ['Знаменатель геометрической прогрессии', '$q=\\dfrac{b_{n+1}}{b_n}$ (постоянный, $q\\neq0$)'], + ['Характеристическое свойство геом. прогрессии', '$b_n^2=b_{n-1}\\cdot b_{n+1}$'], + ['Сумма $n$ членов геом. прогрессии ($q\\neq1$)', '$S_n=\\dfrac{b_1(q^{\\,n}-1)}{q-1}$'], + ['Сумма бесконечной убывающей геом. прогрессии ($|q|<1$)', '$S=\\dfrac{b_1}{1-q}$'], + ['Связь двух членов геом. прогрессии', '$b_n=b_k\\cdot q^{\\,n-k}$'], + ]}, + + { title: 'ЦТ · Двойные неравенства', color: '#EF476F', + descr: 'Оценка суммы, разности, произведения и частного через двойные неравенства. Ловушки вычитания и деления.', + cards: [ + ['Сложение неравенств', 'Складываем почленно ТОЛЬКО одинаково направленные. $5\\le a\\le6$, $7\\le b\\le9$ $\\Rightarrow$ $12\\le a+b\\le15$'], + ['Умножение неравенств', 'Перемножаем почленно (все части положительны). $5\\le a\\le6$, $7\\le b\\le9$ $\\Rightarrow$ $35\\le ab\\le54$'], + ['Ловушка строгости', 'Если хоть одно исходное неравенство строгое, результат строгий ($<$). Напр. $5\\le a<6$, $70$ берём обратные с переворотом: $7 { + [['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_p3 (${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_p3.js --apply\n'); +else console.log('Готово. Колоды добавлены (владелец — admin; раздать классу — через доступ к колоде).\n');