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>
139 lines
13 KiB
Markdown
139 lines
13 KiB
Markdown
# План: точная привязка задач экзамена 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):
|
||
1. По `subtopic` → набор кандидатных глав/§ (карта ниже).
|
||
2. Внутри кандидатов — keyword-скоринг по тексту (ключевые слова из § titles + синонимы:
|
||
«дискриминант/Виета»→квадратные, «процент»→math-6 проценты, «синус/косинус/теорема»→geometry-9,
|
||
«прогресс/разность d/знаменатель q»→прогрессии §15/§17, «параллелограмм/ромб/трапеция»→geometry-8-ch1, …).
|
||
3. Фолбэк: 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/центральные таблицы.
|
||
- `fetch` origin перед работой (в master параллельно коммитят другие сессии); `git add` поимённо.
|
||
- Поиск: ast-index/Read, НЕ Grep tool. Каждый Edit верифицировать.
|
||
- Коммитить поэтапно осмысленными сообщениями + push (CLAUDE.md), трейлер
|
||
`Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>`.
|