c9f3eed8ed
- classify(): bestScore стартует с 0 (нужно совпадение>0), иначе берётся явный fallback (последнее правило), а не первое. Чинит свал theory-statements→§15 и word-problems→проценты. - optsText(): анализ текста вариантов ответа (формат пар [label, html]) — theory-statements размечаются по содержанию утверждений. - alg-word-problems fallback → algebra-7-ch3 §16 (задачи уравнением), не проценты. - Таксономия §: перенесена с gitignore-пути data/ на отслеживаемый backend/scripts/exam-textbook-sections.json + генератор gen-exam-textbook-sections.js. - Результат: 784/800 (98%) размечено, спреды по подтемам корректны. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
13 KiB
13 KiB
План: точная привязка задач экзамена math9 к § учебников
Составлен Opus 2026-06-03 по итогам discovery. Исполнитель — Sonnet, по фазам, с верификацией. Эталон §-таксономии:
plans/exam-textbook-links/taxonomy.md(читаемая) иbackend/scripts/data/g9_textbook_sections.json(машиночитаемая: {chapter_slug, subject, grade, para_id, num, title}).
Контекст (проверено по коду/БД)
exam_tasks: 800 задачmath9, у ВСЕХ заполненsubtopic(16 подтем) иtopic.- Текущая связь — КОСВЕННАЯ и грубая:
exam_tasks.subtopic→exam_topics.slug→ одинtextbook_slug+textbook_paragraphна всю подтему (миграция 028). Контроллерbackend/src/routes/exam-prep.jsштампуетtopic_refчерезgetTopicRefMap()/shapeTask(). Фронтfrontend/js/exam-prep/task-card.js:42строит ссылку/textbook/<slug>#sec-p<N>. - Покрытие сейчас: 546/800 → конкретный §, 158 → только хаб
algebra-9(alg-numbers/arithmetic/ powers/polynomials/word-problems), 96 (theory-statements) → никуда. - Экзамен 9 кл. проверяет программу 5–9, а интерактивный учебник 9 кл. — только материал 9 года. Учебники 5–11 все есть (см. dump в discovery). Для math9 релевантны 5–9.
Готчи нумерации § (ВАЖНО для классификатора и ссылок)
- algebra-7, algebra-9, geometry-7, geometry-9 — СКВОЗНАЯ нумерация
sec-pNпо всему учебнику, но каждый § физически лежит в своём файле-главе. Ключ ссылки = (slug-главы, pN). - geometry-8 — ПОГЛАВНАЯ нумерация (каждая глава заново
sec-p1). Поэтому ОБЯЗАТЕЛЬНО (slug-главы, pN-внутри-этого-файла). JSON-таксономия уже хранит правильную пару. - algebra-8 — сквозная (
sec-p1..p18по 3 главам). - math-5 / math-6 — рендерятся движком
frontend/js/math6_engine.js(<section id="sec-"+p.id>, p.id изwindow.M6.paras). Статическихsec-pNв файле нет. Якорь =sec-<p.id>. Если нужно вести в math-5/6 — para_id брать из конфига M6 главы.
КРИТИЧНО: deep-link сейчас НЕ РАБОТАЕТ
- Статические страницы algebra/geometry (
algebra_9_ch3.htmlи т.п.) подключают только katex/api.js/xp.js —textbook-tracker.jsтам НЕ подключён. Ихinit()всегда вызываетgoTo('p10')иlocation.hashИГНОРИРУЕТ. Используют.psel-card[data-id]. textbook-tracker.js(есть на math-5/6)handleHashNav()матчит только/^#(p\d+)$/(т.е.#pN, НЕ#sec-pN) и кликает.para-pill[data-para].- exam-prep строит
#sec-pN. → ссылка ведёт на главу, но НЕ открывает нужный §. - Сервер отдаёт
/textbook/:slugобычнымsendFile(инъекция только при?embed=1, см.backend/src/server.js:437). Есть_renderEmbed()+_embedCache.
Цель
Каждая из 800 задач math9 ведёт на наиболее подходящий § учебника 5–9, и клик реально открывает этот §.
Фаза 1 — Починить навигацию deep-link (prerequisite; без неё связь невидима)
Централизованный хелпер, внедряемый СЕРВЕРОМ в HTML учебника для всех режимов /textbook/:slug.
backend/src/server.js: обобщить выдачу — всегда читать файл и инжектить<script id="__ls_deeplink__">…</script>перед</head>(или перед</body>), кэш по (filePath, mode='plain'|'embed'). Сейчас в обычном режимеsendFile— заменить на read+inject+send c теми же no-store заголовками. Embed-инъекция остаётся как есть, плюс этот же хелпер.- Хелпер (vanilla, идемпотентный): на
DOMContentLoaded(+ небольшой setTimeout, чтобы билдеры/ движок успели построить карточки) и наhashchange:- распарсить
location.hash: принять#sec-pNИ#pN→ нормализовать кid = 'p'+N; - навигация по приоритету:
document.querySelector('.psel-card[data-id="'+id+'"]')?.click()→ иначе.para-pill[data-para="'+id+'"]'?.click() → иначеwindow.goTo?.(id)→ иначеgetElementById('sec-'+id)?.scrollIntoView(). - не запускать дважды; не конфликтовать с textbook-tracker (тот матчит
#pN— наш хелпер дополнительно поддержит#sec-pN). Если на странице есть textbook-tracker и хэш в форме#pN, можно отдать навигацию ему (наш хелпер сработает как фолбэк, клик идемпотентен).
- распарсить
- НЕ ломать embed-bridge (
ls_tb_nav/ls_tb_apply). Хелпер ставить ПОСЛЕ страничных скриптов. - Проверка:
/textbook/algebra-9-ch3#sec-p13открывает §13 (метод интервалов), не §10;/textbook/geometry-8-ch2#sec-p4открывает «Площадь треугольника».
Фаза 2 — Схема: per-task anchor
- Миграция
backend/src/db/migrations/0NN_exam_task_textbook.sql:ALTER TABLE exam_tasks ADD COLUMN textbook_slug TEXT;ALTER TABLE exam_tasks ADD COLUMN textbook_paragraph INTEGER;(ADD COLUMN — без пересборки; runner оборачивает в транзакцию.) npm run migrateна живой БД.
Фаза 3 — Эвристический классификатор (ядро)
backend/scripts/tag-exam-textbook.js (по образцу существующего tag-exam-tasks.js):
- Грузит
backend/scripts/data/g9_textbook_sections.json(§-таксономия 7–9). - Для каждой задачи math9:
stripText(text_html)(как в tag-exam-tasks.js) +subtopic→ выбрать (slug, para):- По
subtopic→ набор кандидатных глав/§ (карта ниже). - Внутри кандидатов — keyword-скоринг по тексту (ключевые слова из § titles + синонимы: «дискриминант/Виета»→квадратные, «процент»→math-6 проценты, «синус/косинус/теорема»→geometry-9, «прогресс/разность d/знаменатель q»→прогрессии §15/§17, «параллелограмм/ромб/трапеция»→geometry-8-ch1, …).
- Фолбэк: primary § подтемы (если уверенного матча нет).
- По
- Флаги:
--exam math9,--dry-run,--report(распределение по §, сколько без §). Идемпотентно перезаписываетexam_tasks.textbook_slug/paragraph.
Карта subtopic → primary §/главы (исправляет ошибки 028)
alg-numbers→ math-6 (рацион. числа) / math-5 (натуральные) по тексту; иначе хабmath-6.alg-arithmetic→ math-5/6.alg-powers→algebra-7-ch1§1 (натур.), §2 (целый показатель), §3 (станд. вид).alg-expressions→algebra-7-ch2§4–5 /algebra-9-ch1§5 (преобр. рацион.).alg-polynomials→algebra-7-ch2§8–14 (одночлены/многочлены/ФСУ/разложение).alg-fractions→algebra-9-ch1§1–5 (рацион. дроби) ИЛИ math-6 (обыкн. дроби) по тексту.alg-equations→ линейныеalgebra-7-ch3§15; квадратныеalgebra-8-ch2§7–9; дробно-рацион.algebra-9-ch3§10; системыalgebra-7-ch4§23–24 — по тексту.alg-inequalities→algebra-8-ch3§13–18; метод интерваловalgebra-9-ch3§13 /algebra-8-ch3§17.alg-functions→ линейнаяalgebra-7-ch3§19–20; свойства/графикиalgebra-9-ch2§6–9.alg-progressions→algebra-9-ch4: арифм. §15/§16, геом. §17–19 (по «d»/«q»).alg-word-problems→ по теме: проценты→math-6-ch2; уравнением→algebra-7-ch3§16; системой→algebra-7-ch4§25; квадратным→algebra-8-ch2§11.geom-triangles→ признакиgeometry-7-ch2; Пифагорgeometry-8-ch2§11; подобиеgeometry-8-ch3; тригонометрия пр. треуг.geometry-9-ch1— по тексту.geom-quadrilaterals→geometry-8-ch1§4–16 (паралл./прямоуг./ромб/квадрат/трапеция); вписанные/описанныеgeometry-9-ch2§9.geom-circle→geometry-8-ch4(вписанные углы и т.п.) /geometry-9-ch2.geom-coordinates→ уравнение окружностиalgebra-9-ch3§12; координаты — geometry/algebra по тексту.theory-statements→ распределить по теме утверждения (тот же скоринг); иначе оставить NULL (фолбэк на subtopic-уровень, см. Фазу 5).
Фаза 4 — Контроллер: предпочесть task-level
backend/src/routes/exam-prep.js:
- В SELECT задач (
getVariantTasks,practiceRandom/Unsolved,pickRandomByDifficulty,topicTasks*,weakBatchTasks,getTasksByIds) добавитьt.textbook_slug, t.textbook_paragraph. shapeTask()и формированиеtopic_refв/variants/:n/tasks: если у задачи естьtextbook_slug→topic_ref = { title, slug: task.textbook_slug, paragraph: task.textbook_paragraph }(title — из таксономии § или из подтемы), иначе фолбэк наrefMap.get(subtopic).- Title для task-level: можно прокинуть из классификатора в отдельную таблицу/JSON или
собрать map para→title из таксономии при старте (как
getTopicRefMap).
Фаза 5 — Улучшить subtopic-фолбэк (миграция данных)
- Новая миграция: переписать
exam_topics.textbook_slug/paragraphкорректно (исправитьalg-equations→дробно-рацион. только частный случай и т.п.), заполнить хаб-only разумным §. Это фолбэк для задач, которым классификатор не дал task-level.
Фаза 6 — Тесты + верификация
backend/tests/exam-textbook-links.test.js: миграция применилась (колонки есть); после классификатора ≥90% задач имеютtextbook_slug(или явно объяснённый остаток);GET /:examKey/variants/:n/tasksотдаётtopic_refсparagraphдля размеченных задач; ссылка формата/textbook/<slug>#sec-p<N>для валидного slug из таблицы textbooks.- Прогон
node tag-exam-textbook.js --report— приложить распределение. node --test tests/*.test.jsв рамках baseline (3 Auth + флака «intro» chemistry8).- Перезапуск сервера (подхватить миграцию + новые SELECT-поля;
_topicRefCacheдля фолбэка).
Ограничения исполнителю (Sonnet)
- БД: встроенный
node:sqlite(DatabaseSync), НЕ better-sqlite3. - Большие файлы (server.js, exam-prep.js, HTML) — ТОЛЬКО точечный
Edit, НЕ Write целиком. - ⛔ Без эмоджи; иконки — только inline SVG
.ic. - Миграции — ADD COLUMN/UPDATE; НЕ пересобирать users/центральные таблицы.
fetchorigin перед работой (в master параллельно коммитят другие сессии);git addпоимённо.- Поиск: ast-index/Read, НЕ Grep tool. Каждый Edit верифицировать.
- Коммитить поэтапно осмысленными сообщениями + push (CLAUDE.md), трейлер
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>.