From c3816baf99e55123ec300aa063deef30af9af169 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Sun, 14 Jun 2026 22:10:22 +0300 Subject: [PATCH] =?UTF-8?q?feat(ct-math):=20=D0=BA=D0=B0=D1=80=D0=BA=D0=B0?= =?UTF-8?q?=D1=81=20=D0=BA=D1=83=D1=80=D1=81=D0=B0=20=D0=A6=D0=AD/=D0=A6?= =?UTF-8?q?=D0=A2=20=D0=BD=D0=B0=20=D0=B1=D0=B0=D0=BD=D0=BA=D0=B5=20questi?= =?UTF-8?q?ons=20(=D1=82=D0=B5=D0=BC=D1=8B=20+=20draft-=D0=BA=D1=83=D1=80?= =?UTF-8?q?=D1=81=20+=20=D1=81=D0=B5=D0=BA=D1=86=D0=B8=D0=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - backend/scripts/seed_ctmath_course.js — идемпотентный аддитивный seed: +6 тем (Преобразование выражений/Модуль/Иррациональные ур./Показательные ур./ Производная/Параметры), DRAFT-курс «ЦЭ/ЦТ — Математика» + 9 секций. Применён на живой БД: course id=13 (is_published=0), topics 72-77, sections 27-35. Существующие данные не тронуты; повторный запуск ничего не дублирует. - BUILD_ON_QUESTIONS.md: уточнения инспекции банка (year=2025 = «Экзамен 9», без тем; реальный ЦТ-11 = ~733 размеч., Часть B = fill-blank → гоча mode='ct') + блок «Состояние реализации». - README: статус каркаса. Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/scripts/seed_ctmath_course.js | 93 +++++++++++++++++++++++++++ plans/ct-math/BUILD_ON_QUESTIONS.md | 19 +++++- plans/ct-math/README.md | 18 +++--- 3 files changed, 121 insertions(+), 9 deletions(-) create mode 100644 backend/scripts/seed_ctmath_course.js diff --git a/backend/scripts/seed_ctmath_course.js b/backend/scripts/seed_ctmath_course.js new file mode 100644 index 0000000..e2bfc58 --- /dev/null +++ b/backend/scripts/seed_ctmath_course.js @@ -0,0 +1,93 @@ +'use strict'; +/* + * Каркас курса «ЦЭ/ЦТ — Математика» на существующем банке questions. + * План: plans/ct-math/ (BUILD_ON_QUESTIONS.md). + * ИДЕМПОТЕНТЕН и АДДИТИВЕН: добавляет недостающие темы (topics), + * создаёт DRAFT-курс (is_published=0) + 9 секций. Существующие данные не трогает. + * Запуск: node backend/scripts/seed_ctmath_course.js (применить) + * node backend/scripts/seed_ctmath_course.js --dry (только показать план) + */ +const db = require('../src/db/db'); +const DRY = process.argv.includes('--dry'); +const MATH_ID = 3; + +// 1) Недостающие темы под модульную карту (см. BUILD_ON_QUESTIONS §3) +const NEW_TOPICS = [ + 'Преобразование выражений', + 'Модуль', + 'Иррациональные уравнения', + 'Показательные уравнения', + 'Производная', + 'Параметры', +]; + +// 2) Секции курса = 9 блоков (PLAN §3) +const SECTIONS = [ + 'Числа и вычисления', + 'Алгебраические преобразования', + 'Уравнения и неравенства', + 'Функции и производная', + 'Тригонометрия', + 'Прогрессии и текстовые задачи', + 'Планиметрия', + 'Стереометрия', + 'Продвинутое и комбинированное', +]; + +const COURSE_TITLE = 'ЦЭ/ЦТ — Математика'; +const COURSE_DESC = 'Подготовка к ЦЭ/ЦТ по математике: 30 заданий (часть А — А1–А10, часть В — В1–В20). Теория по темам, тренажёр на банке заданий прошлых лет, карточки формул, пробные варианты.'; + +function topicExists(name) { + return db.prepare('SELECT id FROM topics WHERE subject_id=? AND LOWER(name)=LOWER(?)').get(MATH_ID, name); +} +function adminId() { + const u = 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(); + return u && u.id; +} + +let addedTopics = 0, skippedTopics = 0; +console.log(DRY ? '[DRY-RUN] план изменений:' : '[APPLY] вношу изменения:'); + +console.log('\n— Темы (topics) —'); +for (const name of NEW_TOPICS) { + const ex = topicExists(name); + if (ex) { console.log(` есть: ${name} (id ${ex.id})`); skippedTopics++; continue; } + if (DRY) { console.log(` + добавить: ${name}`); addedTopics++; continue; } + const id = db.prepare('INSERT INTO topics (subject_id,name) VALUES (?,?)').run(MATH_ID, name).lastInsertRowid; + console.log(` + добавлено: ${name} (id ${id})`); + addedTopics++; +} + +console.log('\n— Курс (courses) —'); +let course = db.prepare("SELECT id,is_published FROM courses WHERE subject_slug='math' AND title=?").get(COURSE_TITLE); +let courseId; +if (course) { + courseId = course.id; + console.log(` есть курс «${COURSE_TITLE}» (id ${courseId}, ${course.is_published ? 'published' : 'draft'})`); +} else if (DRY) { + console.log(` + создать DRAFT-курс «${COURSE_TITLE}» (created_by=${adminId()})`); +} else { + const by = adminId(); + // cover_emoji не указываем — применится дефолт схемы; в коде эмодзи не вводим + courseId = db.prepare( + 'INSERT INTO courses (subject_slug,title,description,is_published,created_by) VALUES (?,?,?,0,?)' + ).run('math', COURSE_TITLE, COURSE_DESC, by).lastInsertRowid; + console.log(` + создан DRAFT-курс «${COURSE_TITLE}» (id ${courseId}, created_by=${by})`); +} + +console.log('\n— Секции (course_sections) —'); +if (!courseId && DRY) { + SECTIONS.forEach((t, i) => console.log(` + секция [${i + 1}] ${t}`)); +} else if (courseId) { + SECTIONS.forEach((title, i) => { + const ex = db.prepare('SELECT id FROM course_sections WHERE course_id=? AND title=?').get(courseId, title); + if (ex) { console.log(` есть: [${i + 1}] ${title} (id ${ex.id})`); return; } + if (DRY) { console.log(` + секция [${i + 1}] ${title}`); return; } + const id = db.prepare('INSERT INTO course_sections (course_id,title,order_index) VALUES (?,?,?)').run(courseId, title, i + 1).lastInsertRowid; + console.log(` + секция [${i + 1}] ${title} (id ${id})`); + }); +} + +console.log(`\nИтог: темы +${addedTopics} (есть ${skippedTopics}); курс id=${courseId || '(dry)'}; секций ${SECTIONS.length}.`); +console.log(DRY ? 'DRY-RUN: ничего не записано.' : 'Готово. Курс создан как ЧЕРНОВИК (is_published=0) — ученикам не виден до публикации.'); diff --git a/plans/ct-math/BUILD_ON_QUESTIONS.md b/plans/ct-math/BUILD_ON_QUESTIONS.md index 1caa3ec..9b972ee 100644 --- a/plans/ct-math/BUILD_ON_QUESTIONS.md +++ b/plans/ct-math/BUILD_ON_QUESTIONS.md @@ -13,6 +13,17 @@ --- +## 0. Состояние реализации + +**Сделано (скрипт `backend/scripts/seed_ctmath_course.js`, идемпотентный, применён на живой БД 2026-06-14):** +- ✅ Добавлены 6 тем (`topics`, subject_id=3): Преобразование выражений (72), Модуль (73), Иррациональные уравнения (74), Показательные уравнения (75), Производная (76), Параметры (77). +- ✅ Создан **DRAFT-курс** «ЦЭ/ЦТ — Математика» (`courses.id=13`, `is_published=0`, created_by=2) — ученикам НЕ виден до публикации. +- ✅ 9 секций (`course_sections.id=27..35`) = блоки I–IX. + +**Дальше (не сделано):** уроки (`lesson_blocks`) по пилотам; диагностический `test`; assignment-практика `mode='topic'`; колоды формул; публикация курса. См. §8. + +--- + ## 1. Что уже есть (проверено чтением БД) | Таблица | Роль | Факт | @@ -26,6 +37,12 @@ | `courses`/`course_sections`/`lessons`/`lesson_blocks` | теория | общий слой контента | | `flashcard_*` | карточки + SR | для формул | +### 1.1. Уточнение по инспекции (2026-06-14) — ВАЖНО + +Из 1753 матем. заданий: +- **~733 — реальный банк ЦЭ/ЦТ-11** (2011–2024), **размечены по темам**. Часть A = `single`, **Часть B = `fill-blank`** (198 шт.), `short_answer` у них НЕТ. **Это база курса.** +- **1020 — набор `year=2025`, `source_type='экзамен 9'` = «Экзамен 9 класс», БЕЗ тем** (`topic_id` пустой), все `difficulty=1`, типы `single`+`short_answer`. Это контент 9-класса (из него собраны тесты «Экзамен 9 — Вариант N»). **Для курса ЦЭ/ЦТ-11 — НЕ основа** (другой экзамен/класс; не размечен). + **Типы заданий (`questions.type`)**: `single`, `multi`, `true_false`, `short_answer`, `matching`, `fill-blank`. **Сложность**: `difficulty` 1–3 (CHECK), 1=базовый/2=средний/3=продвинутый. (В [PLAN.md](PLAN.md) шкала 1–5 — привести к 1–3: А-часть и лёгкая В = 1, средняя В = 2, сложная В14–В20 = 3.) **Год**: `year` (2011…2025) — фильтр по году/варианту. @@ -39,7 +56,7 @@ Логика `assignmentController.startAssignment` (подтверждено по коду): -- **`mode='ct'`** — собирает ЦТ-вариант: ~половина из `type IN ('single','true_false')` (Часть A) + остаток из `type IN ('multi','short_answer')` (Часть B), добор при нехватке. **Это готовый «пробник» формата ЦТ.** Можно ограничить `topic_id`. +- **`mode='ct'`** — собирает ЦТ-вариант: ~половина из `type IN ('single','true_false')` (Часть A) + остаток из `type IN ('multi','short_answer')` (Часть B), добор при нехватке. ⚠️ **Гоча:** реальный банк ЦТ-11 имеет Часть B типа **`fill-blank`**, который `mode='ct'` НЕ выбирает (он ждёт `short_answer`) → для ЦТ-11 Часть B соберётся только «добором». `mode='ct'` хорошо ложится на набор «Экзамен 9» (там Часть B = `short_answer`). **Для ЦТ-11 надёжнее `mode='topic'` или ручная сборка `test` (single + fill-blank).** - **`mode='topic'`** — `SELECT id FROM questions WHERE subject_id=3 AND topic_id=? ORDER BY RANDOM() LIMIT count`. **Тренажёр по теме модуля.** - **`mode='exam'|'practice'|'repeat'`** — случайные `count` по предмету (или теме). - **`test_id` задан** — берутся ИМЕННО вопросы теста в его порядке (перекрывает режим). **Так делается фиксированная диагностика.** diff --git a/plans/ct-math/README.md b/plans/ct-math/README.md index d628485..1176ab9 100644 --- a/plans/ct-math/README.md +++ b/plans/ct-math/README.md @@ -40,12 +40,14 @@ ## Статус -ПЛАН переориентирован на банк `questions` (пивот). Закоммичено в master (`7eb6cb2`): PLAN, TOPICS_SEED, -два пилота, DIGITIZATION_SPEC, RESOURCES, миграция 077. Добавлен BUILD_ON_QUESTIONS.md + примечания-пивот -(этот коммит). Миграция 077 в БД **не применялась**; ничего в живой БД не менялось. +ПЛАН на банке `questions` (пивот). **Каркас курса создан в живой БД** (скрипт +`backend/scripts/seed_ctmath_course.js`, идемпотентный): 6 новых тем (id 72–77), DRAFT-курс +«ЦЭ/ЦТ — Математика» (`courses.id=13`, не опубликован) + 9 секций (id 27–35). Существующие данные +не тронуты. Миграция 077 (exam-prep) в БД не применялась. -Следующий конкретный шаг на выбор: -- добавить недостающие `topics` (миграция/скрипт) и собрать каркас курса; -- собрать диагностический `test` из реальных вопросов банка; -- детализировать ещё блок теории (уравнения/неравенства или функции+производная); -- закоммитить изменения этого захода. +Реализация (BUILD_ON_QUESTIONS §8): ✅1 темы · ✅2 каркас курса · ⬜3 диагностика · ⬜4 уроки · ⬜5 пробники · ⬜6 карточки/публикация. + +Следующий шаг на выбор: +- собрать диагностический `test` из реальных вопросов банка (по 1 на тему); +- наполнить уроки первого блока (стерео/тригонометрия) по пилотам; +- настроить практику `mode='topic'` по темам.