Accept polygon package-6 labeled checker set as D39.39, land report with review header, update tracker, archive executed prompt

This commit is contained in:
Claude (backend session) 2026-07-26 03:36:44 +03:00
parent 5aa843e850
commit 0539915764
4 changed files with 519 additions and 3 deletions

File diff suppressed because one or more lines are too long

View file

@ -1058,3 +1058,11 @@ API-529-долг закрыт: 8-осевой refute-by-default воркфлоу
**Очередь ратифицирована владельцем:** пак-20 «банк: флаговый стоп + покрытие WHAT→WHICH» (двухфазный — синк → дизайн → ратификация → стройка) → пак-19 «голос/состояние» → свип «стартовый промт + гипотезы ресёрчей 0122 против построенного» (владелец спросил «прошлись ли по всем?» — честный ответ был «не уверен»; свип $0, аудит-воркфлоу класса D39.34) → добор идеала (4 вопроса границы D39.33 всё ещё у владельца) → МАСШТАБ → пилот. Параллельно полигон: пакет-6 «размеченный набор для чекеров» (200300 строк/класс, разметка до просмотра вердиктов, precision/recall текущих чекеров — линейки вместо глазомера).
**Петля ремонта: НЕ включается** — отложено-с-записью: замер D39.37 дал 0 кандидатов в её классах, чинить нечего; вернуться при расширении набора классов и ненулевом остатке. **Мандат владельца в нормы промтов:** сессия ОБЯЗАНА синковать свою имплементацию об ратифицированную документацию и окружающий код ДО дизайна — таблица «источник ↔ код ↔ вердикт» в отчёте (шаг 0 пака-20; переносится во все последующие кодинг-промты). Контекст мандата: вопросы владельца об алгоритме банка показали, что доверие держится на прослеживаемости «ресёрч → ратификация → код», и её надо ПОКАЗЫВАТЬ, а не подразумевать.
## D39.39 — Пакет-6 ПРИНЯТ: линейки чекеров впервые измерены против человеческой разметки (3015 позиций, 6 реальных прогонов, $0) — где линейка стреляет, она почти не врёт; но recall почти везде низкий, K5c инвертирован, K6 шумит 14:1; «остаток=0» D39.37 уточнён как «ноль в охвате узких детекторов», НЕ «финал чист» (26.07, оркестратор №8). ✅
**Приёмка исполнением:** `metrics.json` воспроизведён ПОБАЙТНО ре-раном харнесса (настоящие `internal/checks` через replace-модуль); разметка пересчитана независимо (3015 позиций: 70 дефектов, 21 спорная); баг U+0301 в `TokenizeCyrillic` воспроизведён живьём («бо́льшим» → «бо»+«льшим»); инверсия K5c подтверждена по сырому корпусу (`acceptance:10:0`: 10×-ошибки шкалы «один процент» для 一成 при слове «процент» в юните, глушащем подчекер целиком — `checkers.go:355`); код-якоря заявленных дефектов сверены. Протокол «разметка ДО вердиктов» соблюдён по конструкции сборки. Отчёт: `docs/archive/reports/POLYGON_CHECKER_LABELS_2026-07-26.md`; набор — на стенде `books/gu-zhenren/labels/` (текст книги, вне git), ре-ран детерминирован.
**Сводка первого честного замера** (полный разбор — в отчёте): K1 时辰 1.000/1.000 (N=15) · K2 латиница 1.000/0.75 как детектор и 0.50 в боевом охвате — заглавные («Cultivation» в ФИНАЛЕ), гомоглифы («гy») и короткие токены вне класса · K3 битые формы 1.000/0.100 — один шаблон «-йть» вместо класса · K4b смешение 0.833/0.109 — «8→0» подтверждено на мыслях (99/100 молчат), но 41/46 реплик ВСЛУХ не опознаны · K4a — 0 входов на 6863 строках, непроверяем · K5a магнитуда — 0 срабатываний на 149k знаков (предохранитель отсекает идиомы вместе со всеми реальными магнитудами) · K5b 1.000/1.000 (N=8) · K5c — 0.167 читательской меркой / 1.000 конвенцией кода (вопрос Р2) · K5d — precision 0.000 (4 срабатывания, все безобидные) · K6 глоссарий 0.067 — 14 ложных на 1 подлинный (7 — мн.число вне `decl.forms`, 6 — однознаковый ключ 转 на глаголах, 1 — кавычки в форме). 12 дефектов чекеров зафиксированы с file:line (не чинились — полигон не автор).
**Следствия.** (а) Чтение D39.37: «остаток=0» = ноль срабатываний детекторов, чьи recall теперь ИЗМЕРЕНЫ (петлевые классы по боевому дефолту `repair.go:117`: латиница 0.50 в охвате, битые формы 0.10). Решение «петлю не включать» НЕ пересматривается — петля видит теми же детекторами, чинить ей нечего; но «финал чист» из этой цифры не следует. (б) Набор = постоянный измерительный стенд: любой фикс чекеров теперь приёмуется против него за $0 — «глазомер сессий» в этом слое закрыт как класс. (в) Вход будущего фикс-пака «чекеры»: 12 дефектов file:line + supressor K5c пер-вхождением + атрибуция K4b + `inner_marker` на тире + предохранитель K5a + 两 в `cheng_re` + U+0301 + мн.число в сид-тулинге + однознаковые ключи пост-чека; место в очереди — решение владельца. (г) Четыре вопроса разметки Р1Р4 — владельцу (гипербола 千万 · что есть дефект в 成 · сокращённая форма термина · неразрешимая офлайн речь/мысль); от Р2 зависит целевая семантика K5c, от Р4 — потолок recall K4b by construction. (д) 38/40 дефектов латиницы живут в ЧЕРНОВИКАХ — вне охвата гейтов по сознательному контракту (`waverun.go:486`); в финалы доехало 2, поймано 1 — вопрос «гейт черновика» отдельный и не срочный (редактор черновик переписывает).

View file

@ -1,6 +1,8 @@
# Промт полигон-сессии: ПАКЕТ 6 — размеченный набор для чекеров (линейки вместо глазомера)
**Статус: ВЫДАН 26.07.2026, оркестратор №8.** $0, с бэкендом не пересекается. Зона записи: `eval/` + `docs/experiments/`; сам набор — НА СТЕНДЕ (он содержит текст книги — производные книги ВНЕ git).
**Статус: ИСПОЛНЕН И ПРИНЯТ 26.07.2026 (D39.39), АРХИВ.** Отчёт: `docs/archive/reports/POLYGON_CHECKER_LABELS_2026-07-26.md`; набор — на стенде `books/gu-zhenren/labels/`.
**Статус выдачи: ВЫДАН 26.07.2026, оркестратор №8.** $0, с бэкендом не пересекается. Зона записи: `eval/` + `docs/experiments/`; сам набор — НА СТЕНДЕ (он содержит текст книги — производные книги ВНЕ git).
**Первый деливерабл — эхо-блок (≤10 строк). ДО работы.**

View file

@ -0,0 +1,502 @@
# Полигон, пакет-6: размеченный набор для чекеров — методика, объёмы, precision/recall
**Сессия:** полигон, 26.07.2026. Промт: `docs/POLYGON_PACKAGE6_CHECKER_LABELS_SESSION_PROMPT.md` (D39.38).
**Стоимость:** $0 — ни одного вызова провайдера. Сессия НЕ коммитит.
**Набор:** на стенде, `/home/ubuntu/books/gu-zhenren/labels/` (содержит текст книги → ВНЕ git).
В git — только этот отчёт и методика.
> **Порядок исполнения (протокол «ровно так»).** Разделы §1 (определения) и §2 (объёмы) записаны
> в файл ДО того, как была размечена хоть одна строка, и ДО того, как был прочитан хоть один
> вердикт чекера. Вердикты (`retrieval_state.n_style_flags`, `style_detail`, `postcheck_detail`,
> детали из `report-final.txt`) в этой сессии не открывались до §4. Колонки вердиктов физически
> не выбирались SQL-запросами сборки корпуса — см. `build_corpus.py`.
> **Ревью-шапка (оркестратор №8, 26.07): ПРИНЯТО, D39.39.** Приёмка исполнением: `metrics.json`
> воспроизведён ПОБАЙТНО ре-раном `measure.py` (харнесс вызывает настоящие `internal/checks` через
> replace-модуль; md5 `7ee20d67…` до и после); разметка пересчитана независимо по `labels/*.jsonl`
> (3015 позиций, дефекты по классам сходятся, кросс-таб K4 100/46/4/9 совпадает); баг U+0301
> воспроизведён живьём на `TokenizeCyrillic` («бо́льшим» → «бо»+«льшим»); инверсия K5c подтверждена
> по сырому корпусу (`acceptance:10:0`: «Один процент, два процента, три процента» для
> 一成,两成,三成 при шести вхождениях «процент», глушащих подчекер — `checkers.go:355`); код-якоря
> дефектов №1, 58, 1112 сверены с кодом (в т.ч. `cheng_re` без 两 при `两=2` в `cjk_numeral`;
> draft внутри cheap-гейтов потребляет только регресс-гард — `cheapgates.go:182`). Нит: в §3 бакет
> «CJK 9» — это `curly-quote`; подкласс `cjk-leak` = 11 строк, как в §6 (счётчики консистентны).
## §1. Определения классов — зафиксированы ДО разметки
Определения выведены из КОДА (код первичен), а не из названий классов. Каждое — моими словами,
с якорем `file:line`. Рядом — что чекер, по коду, НЕ считает дефектом (это важнее определения:
именно тут живут расхождения разметки и вердикта).
### K1. DC1 — время (时辰 / счётные единицы)
Якорь: [checkers.go:207-245](../../../backend/internal/checks/checkers.go#L207-L245),
данные пары — [dc-checkers.txt:41-54](../../../backend/configs/langpacks/zh-ru/dc-checkers.txt#L41-L54).
**Моё определение.** Дефект = в источнике стоит длительность, измеренная в 时辰 (китайский «большой
час» = 2 астрономических часа), а в переводе та же длительность названа в русских часах с ЧИСЛОМ,
взятым из источника без пересчёта. `三个时辰` → «три часа» вместо ~«шесть часов»: длительность
занижена вдвое. Отдельная подформа — дробная: `半个时辰` (≈1 час) → «полчаса», длительность тоже
занижена вдвое.
**Что кодом НЕ считается дефектом (и я размечаю так же, иначе меряю не тот детектор):**
- перевод без явного счёта часов («долго», «через время», «полдня») — валидный парафраз, не дефект
(`checkers.go:230-232`: нет совпадения `ru_hours_re` → 0);
- рендер с ПРАВИЛЬНЫМ числом (三个时辰 → «шесть часов») — не дефект;
- рендер с числом, которое не равно ни исходному счёту, ни удвоенному (三个时辰 → «четыре часа») —
по коду НЕ дефект: условие срабатывания `ruNum == n && ruNum != expectedHours`
(`checkers.go:239`). Такую строку я размечаю как дефект перевода, но помечаю подклассом
`outside-rule` — она обязана попасть в recall-гэп, а не в precision-ошибку.
**Единица разметки.** Одно вхождение 时辰 в исходнике юнита × прогон (один и тот же исходник,
переведённый разными армами, даёт разные рендеры → разные items). ID: `run:chapter:chunk_idx:src_offset`.
### K2. Латиница-остаток
Якорь: [checkers.go:378-411](../../../backend/internal/checks/checkers.go#L378-L411).
**Моё определение.** Дефект = в русском выходе осталось целое ЛАТИНСКОЕ слово, которое там быть не
должно: непереведённый кусок исходного/промежуточного английского («открыл их again»), утечка
служебной лексики промпта/разметки, английский термин вместо русского.
**Что кодом НЕ считается дефектом:**
- токен с заглавной буквой — код отбрасывает (`checkers.go:391`): «сигнал имени собственного/бренда».
Я размечаю такие строки отдельно: заглавная ≠ легитимность (`TM`, `BANK`, `Cultivation` — утечки);
- токен короче 3 букв, токен с цифрой внутри (`v1`), римская цифра — отбрасывается;
- токен в per-project allowlist.
**Единица разметки.** Одно вхождение максимального латинско-цифрового токена в целевом тексте
(финал или черновик). ID: `run:chapter:chunk_idx:field:byte_offset`.
### K3. Битые словоформы
Якорь: [checkers.go:437-454](../../../backend/internal/checks/checkers.go#L437-L454),
данные цели — [target-ru.txt:9](../../../backend/internal/lang/data/target-ru.txt#L9) (`broken_suffix йть`).
**Моё определение.** Дефект = русское слово, которого в языке нет как формы: искажённая морфология
(«войть» вместо «войти»), склеенные/разорванные основы, обрубленное окончание, слово с
несуществующим суффиксом. Не относится: редкие, но правильные слова; авторские неологизмы книги;
транслит имён по Палладию; окказионализмы жанра.
**Что кодом НЕ считается дефектом:** ВСЁ, кроме слов длиной ≥4, заканчивающихся на «-йть».
Код это и заявляет прямо: «Zero false-positive by construction (only a structural signature, no
dictionary)» (`checkers.go:436`). То есть у класса заведомо один-единственный обнаруживаемый шаблон,
а класс «битые словоформы» шире. Это и надо измерить.
**Единица разметки.** Один русский словотип (тип, не токен) + позиция первого вхождения.
ID: `type:<слово>`, плюс список вхождений.
### K4. `dialogue_dash`
Якорь: [cheapgates.go:199-241](../../../backend/internal/checks/cheapgates.go#L199-L241),
форма реплики — `chevronSpeechShape` (`:250`), «это речь вслух?» — `isSpokenChevronLine`
([checkers.go:467-483](../../../backend/internal/checks/checkers.go#L467-L483)),
данные цели — [target-ru.txt:16-53](../../../backend/internal/lang/data/target-ru.txt#L16-L53).
Класс распадается на два независимых правила; меряю их РАЗДЕЛЬНО, потому что и живут они врозь.
**K4a — неверный маркер реплики (строчное правило).** Дефект = строка открывает прямую речь
маркером, которого в русской типографике нет: прямая ASCII-кавычка `"`, дефис `-` или короткое
тире `` вместо длинного `—`. Условие формы: после маркера пробел и буква (для кавычки пробел
необязателен) — `dialogueShape`/`quoteShape` (`:279`,`:293`).
*НЕ дефект по коду:* `— 15 минут спустя` (тире + пробел + цифра — это врезка, не реплика,
`:210`); дефис, приклеенный к слову (перенос).
**K4b — смешение стилей прямой речи (правило уровня чанка).** Дефект = внутри одного чанка речь
вслух оформлена И длинным тире, И кавычками-ёлочками. Ёлочная строка засчитывается, только если
(1) имеет форму атрибуции `«Реплика», — глагол` (`chevronSpeechShape`, `:250`) И (2) её атрибуция
содержит глагол ГОВОРЕНИЯ из закрытого списка цели и НЕ содержит маркера внутренней речи
(«про себя», «мысленно», …) — `isSpokenChevronLine`.
*НЕ дефект по коду и по русской норме:* ёлочки для МЫСЛИ рядом с тире для речи — это правильно
набранная проза, а не столкновение стилей (правка v5, `cheapgates.go:68-81`). Именно тут стоит
цифра «8→0», которую надо проверить честно.
*Сознательные промахи кода:* восклицательная реплика `«Реплика!» —` (без запятой) и безатрибутивная
`«Реплика.»` не распознаются как ёлочная речь (`:248-249`).
**Единица разметки.** K4a/K4b — одна непустая строка целевого текста.
ID: `run:chapter:chunk_idx:field:line_no`. Дополнительно размечаю строки, начинающиеся с БУКВЫ, —
там живёт прямая речь вообще без маркера (это невидимо для чекера by construction, идёт в recall-гэп).
### K5. Число-дрейф / магнитуды
Четыре разных детектора; меряю раздельно.
- **K5a — 万/億-магнитуда** ([cheapgates.go:421-462](../../../backend/internal/checks/cheapgates.go#L421-L462)).
Дефект = порядок величины из источника (`三万` = 3·10⁴) не представлен в переводе ни числом, ни
словом-магнитудой нужного порядка. *НЕ дефект по коду:* магнитуда, пересказанная прозой без
слов-магнитуд (`:453-459` — молчит); идиомы `万一/万分/千万/亿万` без цифры перед 万 (`:491`);
голые единицы `十万/百万` (там нет цифры перед маркером).
- **K5b — DC2 千万 / 数十万** ([checkers.go:281-302](../../../backend/internal/checks/checkers.go#L281-L302)).
Дефект = 千万 (10⁷) отдано как «тысячи», 数十万 (несколько ×10⁵) — как «десятки тысяч».
Код сам признаёт неустранимую двусмысленность: 千万 сплошь и рядом ГИПЕРБОЛА («тьма», «мириады»),
и её «тысячи» — в регистре (§5-A4, `cheapgates.go:90-93`). Размечаю гиперболу отдельным
подклассом `hyperbole`, решение по ней — не моё (идёт владельцу).
- **K5c — 成-процент** ([checkers.go:346-370](../../../backend/internal/checks/checkers.go#L346-L370)).
Дефект = 成 (десятая доля: 六成 = 60%) отдано десятичной ДОЛЕЙ вместо процента.
Внимание: код считает дефектом ФОРМУ, а не значение (`cheapgates.go:84-89` — сознательно оставлено).
Значит «три десятых» для 三成 — по коду дефект, хотя значение верное. Размечаю по коду
(`form-defect`), а «значение верно/неверно» веду ВТОРОЙ колонкой.
- **K5d — дрейф чисел черновик→финал**
([regressionguard.go:64-76](../../../backend/internal/checks/regressionguard.go#L64-L76)).
Дефект = многозначное арабское число есть в черновике и пропало в финале (или появилось из
ниоткуда). *НЕ дефект по коду:* однозначные числа (их штатно пишут словом); число, переписанное
словами при редактуре, — код сам называет это своим ложным флагом (`:24-28`).
Гейт включён только в мини-прогоне (`minirun/pipeline.yaml: regression_guard.enabled: true`);
в rerun2 его нет.
**Единица разметки.** K5a/K5b/K5c — вхождение триггера в исходнике × прогон.
K5d — (юнит, числовой токен, сторона).
### K6. Глоссарий-промахи
Якорь: [mempostcheck.go:61-122](../../../backend/internal/membank/mempostcheck.go#L61-L122).
**Моё определение.** Дефект = термин глоссария, чей ИСХОДНЫЙ вид встретился в исходнике юнита, а в
переводе не появилось ни одной принятой формы его перевода: термин выброшен, переведён по-другому
или искажён.
**Что кодом НЕ считается дефектом:** sticky-записи (их src в этом чанке и не должен быть, `:69`);
записи без dst; записи со статусом `ambiguous` — они не идут в счёт (`CountConfirmedMisses`, `:49`).
Принятое множество = базовый dst сохранённые склонённые формы (`decl.forms`), сравнение —
по целому слову с границами по буквам (`containsWholeWord`, `:128`).
**Единица разметки.** (юнит, термин глоссария, чей src встречается в исходнике юнита).
ID: `run:chapter:chunk_idx:src`.
### Общая шкала меток
Каждый item получает одну метку:
`defect` · `ok` · `disputable` (спорные — владельцу, я их не решаю) ·
плюс необязательный подкласс (`outside-rule`, `hyperbole`, `form-defect`, `unmarked-speech`, …).
`disputable` в знаменатель precision/recall НЕ входит и считается отдельно.
---
## §2. Материал и объёмы — зафиксировано ДО разметки
Корпус: `/home/ubuntu/books/gu-zhenren/labels/raw/corpus.jsonl` — 91 юнит из шести реальных
прогонов на стенде (никакой синтетики).
| прогон | юнитов | исходник, зн. | черновик, зн. | финал, зн. | что это |
|---|---:|---:|---:|---:|---|
| `acceptance` | 53 | 72 646 | 239 534 | 239 522 | приёмка 25 глав, 10.07 (старый стек) |
| `minirun` | 14 | 28 837 | 90 882 | 94 490 | мини-прогон 10 глав, 25.07 (шиппинговая форма) |
| `rerun2-dspro` | 7 | 14 127 | 46 142 | 46 080 | пере-прогон гл.15, арм deepseek-v4-pro |
| `rerun2-glm` | 7 | 14 127 | 46 142 | 45 956 | тот же исходник, арм glm |
| `rerun2-mistral` | 7 | 14 127 | 46 142 | 41 875 | тот же исходник, арм mistral |
| `verify2` | 3 | 5 368 | 17 778 | 17 423 | двухглавая верификация, 26.07 |
| **итого** | **91** | **149 232** | **486 620** | **485 346** | |
Исходники восстановлены детерминированной пере-нарезкой текущим кодом
(`chunk.IngestEncoded` + `chunk.SplitChunks` + группировка по `EditUnitID` — 1:1 с
`status.go:184` и `wave.go:41`), черновики — из `checkpoints.response_text` через
`checks.ExportNormalize`, финалы — из экспортов прогонов.
**Самопроверка сборки:** восстановленный исходник сверен побайтно с колонкой `source` готового
экспорта `rerun2 --pairs`. Совпадение полное на юнитах без заголовка главы; на 5 юнитах-началах
главы расхождение ровно 4 руны — это `第N节`, который текущий код снимает в манифест-заголовок
(политика заголовков пака-13, `export.go:213-220`), а экспорт 23.07 ещё оставлял в колонке.
Расхождение объяснено и ожидаемо, а не молча проигнорировано.
### Физический потолок объёма по классам (посчитан ДО разметки)
Промт задаёт ориентир 200300 строк на класс. **Четыре класса из шести физически столько не
содержат** — вот честные потолки на этом корпусе:
| класс | что считалось | потолок items | 200300 достижимо? |
|---|---|---:|---|
| K1 DC1 时辰 | вхождений 时辰 в исходниках юнитов | **15** (4 в уникальном исходнике) | **нет** |
| K2 латиница | вхождений латинско-цифровых токенов в целевом тексте | **92** (32 типа; из них попадают под правило кода 7 типов) | **нет** |
| K3 битые формы | русских словотипов | 12 946 типов (1 999 hapax) | да, выборкой |
| K4 dialogue_dash | непустых строк целевого текста | 6 863 (тире 1 259 · ёлочки 478 · прочие 5 088 · дефис/кавычка **0**) | да, выборкой |
| K5a магнитуда 万 | числовых пробегов с 万 в исходнике | 37 уникальных (×армы) | нет |
| K5b DC2 | 千万 3 · 数十万 2 уникальных (×армы) | **~15** | **нет** |
| K5c 成-процент | `цифра+成` в исходнике | 35 уникальных (×армы) | нет |
| K5d дрейф чисел | многозначных арабских чисел во всём корпусе | **19** | **нет** |
| K6 глоссарий | (юнит × термин, чей src есть в исходнике) | сотни | да |
Синтетикой я эти классы не добиваю (см. §6 — там отдельная помеченная секция о том, чего набор
не покрывает и что для этого нужно).
Отдельный факт, который стоит прочитать до всех цифр: **строк с дефисом или ASCII-кавычкой в начале
во ВСЁМ корпусе — ноль**. То есть ветка K4a (`hyphenLike`, `cheapgates.go:213-222`) на реальном
материале не срабатывает ни разу и её precision на этом наборе неизмерим — измеряется только K4b.
<!-- §3 (разметка), §4 (precision/recall), §5 (спорные), §6 (не покрывает) заполняются ниже -->
---
## §3. Разметка: что сделано и как
Разметил я, читая исходник и перевод; вердикты чекеров были получены ТОЛЬКО после того, как все
файлы `labels/*.jsonl` были записаны. Сети-кандидаты выведены из определений §1, а не из реализации
чекера (иначе recall меряется сам против себя) и сознательно ШИРЕ правил кода.
| класс | единица разметки | размечено | из них дефект | спорных |
|---|---|---:|---:|---:|
| K1 DC1 时辰 | вхождение 时辰 × прогон | **15** (исчерпывающе) | 6 | 0 |
| K2 латиница | вхождение лат.-цифрового токена | **92** (исчерпывающе) | 40 | 0 |
| K3 битые формы | русский словотип | **2 177** (весь hapax-слой 1 999 + 178 из сетей) | 10 | 0 |
| K4 dialogue_dash | строка целевого текста | **348** (ёлочки 150 · тире 80 · буквенные 80 · markdown 29 · CJK 9) | 4 | 9 |
| K5a магнитуда 万 | вхождение × прогон | **67** (исчерпывающе) | 1 | 3 |
| K5b DC2 | вхождение × прогон | **11** (исчерпывающе) | 1 | 3 |
| K5c 成-процент | вхождение × прогон | **40** (исчерпывающе) | 7 | 4 |
| K5d дрейф чисел | (юнит, число, сторона) | **5** (исчерпывающе) | 0 | 0 |
| K6 глоссарий | (юнит × сработавший термин) | **260** из 1 257 (выборка) | 1 | 2 |
| **итого** | | **3 015** | **70** | **21** |
Ориентир 200300 строк выдержан там, где материал это позволяет (K3, K4, K6). Четыре класса
физически меньше — размечены ЦЕЛИКОМ и цифра названа честно; синтетикой не добивал (см. §6).
Две проверки собственной работы исполнением, обе поймали мою ошибку, не чужую:
1. Восстановленный исходник сверен побайтно с колонкой `source` готового экспорта `rerun2 --pairs`
(§2) — расхождение оказалось ровно в 4 руны заголовка и объяснено.
2. Первый прогон дал по латинице «чекер не сработал ни разу» при том, что пер-юнитный прогон
показывал `latin_residue=1`. Причина — в моей измерялке: Go отдаёт БАЙТОВЫЕ смещения, а пул
считан в символах. После починки — precision 1.000 / recall 0.750. Цифра «0» была моей, не кода.
---
## §4. Первое измерение: precision/recall ТЕКУЩИХ чекеров
Вердикты получены вызовом НАСТОЯЩИХ функций пакета `internal/checks` (харнесс-модуль вне репо,
`replace` на репозиторный backend; ноль правок в репозитории). Спорные строки в знаменатель не
входят. Единица измерения выбрана та, на которой чекер реально выносит вердикт: для src↔tgt
подчекеров это ЮНИТ (одно срабатывание на единицу редактуры), для остальных — строка/токен/словотип.
| класс | уровень | N | TP | FP | FN | TN | **precision** | **recall** |
|---|---|---:|---:|---:|---:|---:|---:|---:|
| K1 DC1 时辰 | юнит | 15 | 6 | 0 | 0 | 9 | **1.000** | **1.000** |
| K2 латиница (детектор) | токен | 92 | 30 | 0 | 10 | 52 | **1.000** | **0.750** |
| K2 латиница (боевой охват: только финалы) | токен | 31 | 1 | 0 | 1 | 29 | **1.000** | **0.500** |
| K3 битые словоформы | словотип | 2 177 | 1 | 0 | 9 | 2 167 | **1.000** | **0.100** |
| K4a неверный маркер реплики | строка | 348 | 0 | 0 | 0 | 348 | н/д | н/д |
| K4b ёлочная речь вслух (смешение) | строка | 146 | 5 | 1 | 41 | 99 | **0.833** | **0.109** |
| K5a магнитуда 万/億 | юнит | 27 | 0 | 0 | 1 | 26 | н/д (0 срабатываний) | **0.000** |
| K5b DC2 千万/数十万 | юнит | 8 | 1 | 0 | 0 | 7 | **1.000** | **1.000** |
| K5c 成-процент (читательская мерка) | юнит | 15 | 1 | 5 | 1 | 8 | **0.167** | **0.500** |
| K5c 成-процент (конвенция кода: форма=дефект) | юнит | 16 | 7 | 0 | 1 | 8 | **1.000** | **0.875** |
| K5d дрейф чисел | позиция | 5 | 0 | 4 | 0 | 1 | **0.000** | н/д |
| K6 глоссарий (все сработавшие термины) | юнит×термин | 258 | 1 | 14 | 0 | 243 | **0.067** | **1.000** |
| K6 глоссарий (только реально инъецированные) | юнит×термин | 215 | 0 | 8 | 0 | 207 | **0.000** | н/д |
«н/д» — метрика не определена: либо чекер не сработал ни разу (нет знаменателя precision), либо
положительных по разметке ноль (нет знаменателя recall). Это тоже результат, а не пропуск.
### Что стоит за цифрами (механика, с якорями)
**K1 (DC1 时辰) — 6/6 и ни одного ложняка.** Единственный класс, где линейка работает ровно как
описана. Шесть подлинных дефектов: `三个时辰 → «три часа»` (три арма rerun2 из четырёх) и
`半个时辰 → «полчаса»` (dspro, glm, acceptance). Девять корректных рендеров, включая два
транслитерированных («чуть больше одного шичэня») — код на них молчит, и это правильно.
**K5c (成-процент) — самая тяжёлая находка пакета.** Пять из шести срабатываний пришлись на юниты,
где ЗНАЧЕНИЕ передано верно («две-три десятых» для 两三成 = 2030%, «сорок четыре сотых» для
四成四 = 0.44). А ЕДИНСТВЕННЫЙ юнит с настоящими ошибками шкалы — `acceptance:10:0`, где
`一成` (10%) отдано как «один процент», `一成,两成,三成` как «Один процент, два процента, три
процента», а `一成四` (14%) как «одна доля и четыре части», — **пропущен**: в этом же юните где-то
есть слово «процент», и супрессор процентной формы гасит подчекер целиком
([checkers.go:355](../../../backend/internal/checks/checkers.go#L355)). То есть на реальном материале
правило сработало ровно наоборот замыслу: промолчало там, где шкала действительно сломана в 10 раз,
и заговорило там, где всё верно. По конвенции самого кода (форма-дробь = дефект) precision=1.000 —
цифры расходятся не из-за реализации, а из-за того, ЧТО считать дефектом. Это вопрос владельцу (§5).
**K4b (`dialogue_dash`, смешение) — «8→0» проверено.** Правка v5 в основном работает: из 100
размеченных ёлочных МЫСЛЕЙ чекер флагнул **одну** (99 из 100 — молчание). Но ёлочных реплик ВСЛУХ
в корпусе 46 (31% ёлочных строк), и из них поймано 5. Разбор 41 пропуска:
| причина | строк |
|---|---:|
| безатрибутивная реплика — связки `«…», —` нет вообще | 23 |
| реплика оканчивается на `!` / `?` / `…`, запятой после `»` нет — сознательный промах кода ([cheapgates.go:248-249](../../../backend/internal/checks/cheapgates.go#L248-L249)) | 12 |
| закрывающая `»` дальше 400 знаков (длинная реплика) | 3 |
| связка есть, но глагол говорения не из закрытого списка («раздался голос», «покинули его») | 3 |
Единственное ложное срабатывание вскрывает механику: строка
`«Месть не входит в мои планы…», — подумав так, Фан Юань усмехнулся … и равнодушно произнёс: —`
— мысль, но `isSpokenChevronLine` ищет глагол говорения по ВСЕЙ строке
([checkers.go:471-481](../../../backend/internal/checks/checkers.go#L471-L481)), а «произнёс»
стоит в другом предложении и относится к следующей реплике.
**K4a — правило не проверяемо на этом корпусе.** Строк, начинающихся с дефиса, короткого тире или
ASCII-кавычки, во всём корпусе (6 863 непустых строки) — **ноль**. Ветка `hyphenLike`
([cheapgates.go:213-222](../../../backend/internal/checks/cheapgates.go#L213-L222)) ни разу не
получила входа. Precision/recall неизмеримы, и это надо знать: половина класса живёт непроверенной.
**Класс, которого у чекера нет: ОБРАТНАЯ ошибка маркера.** 4 строки из 80 размеченных тире-строк —
внутренняя речь, набранная тире как речь вслух: «— Жаль только этого хорошего гу, — вздохнул ПРО
СЕБЯ Фан Юань», «— …, — недовольно хмыкнул ПРО СЕБЯ старейшина», «— Как же так… — бормотал СЕБЕ ПОД
НОС Фан Чжэн». Маркеры «про себя» / «себе под нос» уже есть в данных цели как `inner_marker`, но
применяются только к ёлочкам. По конструкции чекер считает любую тире-строку правильной, поэтому
recall здесь 0.000 из 4.
**K5a (магнитуда 万) — ноль срабатываний на всём корпусе, и это структурно.** Из 67 вхождений 55 —
идиомы (万物之灵, 万一, 千辛万苦, 万家灯火, 成千上万, 罪该万死, 十万八千里…). Оставшиеся реальные
магнитуды (数十万, 千万, 数万) не проходят предохранитель «цифра перед большим маркером»
([cheapgates.go:513-526](../../../backend/internal/checks/cheapgates.go#L513-L526)): 数 и 十 — не
цифры класса `Digit`, 千 — единица, а не цифра. Предохранитель отсекает идиомы правильно, но вместе
с ними — и все реальные магнитуды этого корпуса. Единственный подлинный дефект класса
(数十万 → «десятки тысяч» у mistral) пойман ДРУГИМ подчекером, DC2.
**K5d (дрейф чисел) — 4 срабатывания, все безобидные.** Три — номера глав в заголовке, которого не
было в черновике; одно — `66% → «шестьдесят шесть процентов`, то есть ровно тот ложный флаг, который
код за собой признаёт ([regressionguard.go:24-28](../../../backend/internal/checks/regressionguard.go#L24-L28)).
Подлинного дрейфа в корпусе нет: многозначных арабских чисел во всех 91 юните — 19.
**K6 (глоссарий) — 14 ложных на 1 подлинный.** Разбор:
- **6** — однознаковый ключ `转` сработал на ГЛАГОЛЕ (转身 «обернулся», 转过转角 «за поворотом»,
辗转 «мыкался», 转为 «стать», 脑筋转不快 «мысль ворочается медленно»). Ранга в исходнике нет,
переводить нечего, но условие «src сработал» — подстрочное вхождение.
- **7** — целевая форма есть, но во МНОЖЕСТВЕННОМ числе, которого нет в `decl.forms`:
«первокамней» при формах `первокамня/первокамню/первокамень`, «смертных» при `смертного/…`,
«гу-тварей» при `гу-твари/гу-тварь`. Это ровно `inflection_gap`, который код называет пробелом
СИДА, а не кода ([mempostcheck.go:109-111](../../../backend/internal/membank/mempostcheck.go#L109-L111)) —
теперь он измерен: 7 из 258 пар, ~2.7%.
- **1**`甲等 → «разряд А»`, в тексте «разряда „А“»: типографские кавычки внутри формы рвут
сравнение по целому слову.
Единственный подлинный промах (`minirun:8:1`, 古月方正 ×6 → «Фан Чжэна», клановое имя исчезло)
пойман. Но на подмножестве, которое боевой пост-чек РЕАЛЬНО проверял (217 из 260 пар — остальные не
прошли отбор/бюджет инъекции), подлинных промахов нет вовсе, а ложных 8: precision 0.000.
**K2 и K3 — линейки честные, но узкие.** Ни одного ложного срабатывания в 92 и 2 177 items
соответственно. Recall ограничен конструкцией: латиница пропускает заглавные («Cultivation» в
ФИНАЛЕ acceptance — [checkers.go:391](../../../backend/internal/checks/checkers.go#L391)), токены
короче трёх («гс латинской `y` вместо кириллической `у` — гомоглиф) и токены с цифрой; битые
формы ловят ровно один шаблон «-йть» из десяти найденных мной битых слов.
### Зафиксированные дефекты чекеров (фиксация, НЕ правка)
Я не автор чекеров и ничего здесь не чинил. Каждый пункт — с якорем и с тем, чем он подтверждён
на наборе.
| # | якорь | что именно | подтверждение на наборе |
|---|---|---|---|
| 1 | [checkers.go:355](../../../backend/internal/checks/checkers.go#L355) | процентный супрессор гасит подчекер по ВСЕМУ юниту, если слово «процент» встретилось где угодно | единственный юнит с настоящими 10×-ошибками шкалы (`acceptance:10:0`) пропущен |
| 2 | [checkers.go:471-481](../../../backend/internal/checks/checkers.go#L471-L481) | глагол говорения ищется по всей строке, а не в атрибуции ёлочки | 1 из 1 ложных срабатываний K4b |
| 3 | [cheapgates.go:250-275](../../../backend/internal/checks/cheapgates.go#L250-L275) | форма `«…», —` — единственный признак ёлочной речи | 38 из 46 реплик вслух не опознаны (23 безатрибутивных + 12 с `!`/`?`/`…` + 3 длинных) |
| 4 | [cheapgates.go:209-233](../../../backend/internal/checks/cheapgates.go#L209-L233) | тире-строка засчитывается корректной ВСЕГДА | 4 мысли, набранные тире («вздохнул ПРО СЕБЯ»), невидимы, хотя `inner_marker` уже есть в данных цели |
| 5 | [cheapgates.go:513-526](../../../backend/internal/checks/cheapgates.go#L513-L526) | «цифра перед большим маркером» отсекает 数十万 / 千万 / 十万八千 вместе с идиомами | подчекер `number_magnitude` не сработал ни разу на 149 232 знаках исходника |
| 6 | [checkers.go:391](../../../backend/internal/checks/checkers.go#L391) | заглавная буква = «имя собственное» → токен отброшен | «Cultivation» в ФИНАЛЕ `acceptance:3:0` невидим; маркеры утечки банка `TM`, `BANK` — тоже |
| 7 | [checkers.go:398](../../../backend/internal/checks/checkers.go#L398) | `minLatinResidueLen = 3` | гомоглиф «гy» (латинская `y` в слове «гу») невидим — 2 вхождения |
| 8 | [dc-checkers.txt:65](../../../backend/configs/langpacks/zh-ru/dc-checkers.txt#L65) | класс символов `cheng_re` не содержит 两, хотя `cjk_numeral` содержит (`两=2`) | `两成` в ряду `一成,两成,三成` (minirun ch10) не виден подчекеру процентов |
| 9 | [mempostcheck.go:112-122](../../../backend/internal/membank/mempostcheck.go#L112-L122) | принятое множество = базовый dst `decl.forms`; множественного числа в сиде нет | 7 ложных из 14: «первокамней», «смертных», «гу-тварей» |
| 10 | [mempostcheck.go:61-80](../../../backend/internal/membank/mempostcheck.go#L61-L80) | «src сработал» = подстрочное вхождение; однознаковый ключ `转` совпадает с частым глаголом | 6 ложных из 14 |
| 11 | [runes.go:32-50](../../../backend/internal/text/runes.go#L32-L50) | `TokenizeCyrillic` рвёт слово на комбинирующем ударении U+0301 (оно не кириллица) | «бо́льшим» → «бо» + «льшим» в финале `acceptance:25:0`; затрагивает и ёфикатор, и битые формы, и пост-чек |
| 12 | [waverun.go:486](../../../backend/internal/pipeline/waverun.go#L486) (охват, не баг) | `draft` уходит ТОЛЬКО в регресс-гард; остальные подчекеры видят лишь финал | 38 из 40 дефектов латиницы живут в черновиках и по конструкции вне охвата |
Пункты 14 и 68 — это правила, которые можно поменять. Пункты 910 код сам называет пробелом
СИДА, а не кода; пункт 12 — сознательный контракт. Что из этого чинить и чинить ли — не моё решение.
Побочные наблюдения вне шести классов (не размечал, только фиксирую факт срабатывания на 91 юните):
`dc6_register` — 4 юнита, `yo` — 1 юнит («тёмно»/«темно» в `rerun2-glm:1:0`). Ёфикатор на корпусе
недоразмечен: пар типа «вошел/вошёл», «шепот/шёпот», «отчетливо/отчётливо» в тексте заметно больше
одной — это отдельный класс и отдельный пакет.
---
## §5. Спорные строки — пачкой владельцу, я их не решал
21 item помечен `disputable` и в знаменатель метрик не входит. Все сводятся к четырём решениям.
**Р1. 千万 как гипербола (3 item, класс K5b/K5a).** `杀了千万人的性命` в проклятии-обвинении: три
арма дали «тысячи и тысячи людей» / «тысячи жизней», один — «миллионы жизней». Довод в пользу
гиперболы, которого нет в коде: **тот же автор в главе 2 называет то же самое деяние
`屠杀了数十万人` (~10⁵)**, то есть 10⁷ в главе 1 — риторика персонажа, а не число. Если владелец
признаёт гиперболу не-дефектом, precision DC2 остаётся 1.000; если признаёт дефектом — правило
недосчитывает 3 из 4 армов, и вопрос переходит в «чем отличать гиперболу от магнитуды офлайн».
Код прямо пишет, что офлайн не отличает ([cheapgates.go:90-93](../../../backend/internal/checks/cheapgates.go#L90-L93)).
**Р2. Что считать дефектом в классе 成 (4 item + вся расходящаяся метрика).** Рендер «сорок четыре
сотых» для 四成四 несёт ВЕРНОЕ значение (0.44) в форме дроби. Конвенция кода — форма и есть дефект;
читательская мерка — дефект только когда сломано значение. От выбора зависит **precision 1.000 или
0.167**. Отдельно: 4 item в `minirun:10:0` — «доля» в одном абзаце обозначает и 分 (1%), и 成 (10%)
(«потеряло целую долю», «истощилась на целых три доли»); тут я не смог решить, что имел в виду
переводчик, а не что имел в виду код.
**Р3. Сокращённая форма термина (2 item, K6).** `蛊虫` при одобренном dst «гу-тварь» переведено просто
«гу» («отличительная гу клана Гуюэ»). Понятие передано и внутри юнита последовательно, но
утверждённой формы нет. Это дефект перевода, пробел полноты сида или норма — решать владельцу; от
ответа зависит, считать ли такие срабатывания пост-чека ложными.
**Р4. Неоднозначная речь/мысль (9 item, K4).** Строки, где по тексту нельзя решить, произнесено это
вслух или подумано: «— Пятьсот лет жизни — словно сон» (один арм ставит тире, другой ёлочки на той
же фразе); «— Хе, по срокам как раз он. — Старейшина ... пробормотал себе под нос: —» (бормочет,
но при слушателе); «Пф...» — кто-то не сдержал смешка». Если владелец решит, что такие случаи
принципиально неразрешимы офлайн, то потолок recall у правила смешения ниже 1.0 by construction, и
это надо записать в контракт правила, а не гнаться за ним правками.
Полный список спорных с контекстом — в наборе на стенде: `labels/labels/*.jsonl`, фильтр
`label == "disputable"`.
---
## §6. Чего набор НЕ покрывает
Это главный раздел для того, кто будет пользоваться цифрами §4. Каждая цифра оттуда верна ТОЛЬКО
внутри своих границ.
**1. Одна книга, одна пара, один жанр.** Всё — 蛊真人, zh→ru, сянься. Ни одного среза на другой паре
или книге. Любое утверждение вида «чекер имеет precision X» — это «на этой книге».
**2. Четыре класса физически малы, и метрика на них хрупкая.** K1 = 15 items (4 уникальных вхождения
在 исходнике), K5b = 11, K5d = 5, K2 = 92. `precision = 1.000` на шести положительных K1 — это
«не ошибся шесть раз», а не «работает». Один контрпример уронит цифру на 0.14. Доверительных
интервалов я не считал сознательно: при N=6 они шире самой цифры.
**3. Recall меряется против МОЕЙ сети, а не против истины.** Дефект, который не попал ни в одну из
моих сетей, невидим и для разметки, и для метрики. Где сеть заведомо дырявая:
- **K3**: сплошь просмотрен только hapax-слой (1 999 типов). Битая форма, встретившаяся дважды,
в набор не попала. Вторая независимая сеть (сосед по Левенштейну у частого слова, 117 попаданий)
не добавила ни одного нового дефекта — но это проверка, а не доказательство.
- **K4**: буквенных строк размечено 80 из 5 088. Прямой речи вообще без маркера я в этой выборке
не нашёл, но выборка — 1.6% слоя.
- **K6**: 260 пар из 1 257.
- **K2/K5a/K5b/K5c/K5d/K1**: сплошь, дырок нет.
**4. Ветка K4a непроверяема на этом корпусе.** Ноль строк с дефисом/короткой чертой/ASCII-кавычкой в
начале на 6 863 строках. Её precision/recall не измерены НИКАК — ни хорошо, ни плохо.
**5. Классов чекеров больше, чем шесть.** Не размечены и не измерены: ёфикатор, транслит-междометия,
DC6-регистр, эксцизия-покрытие, коллапс длины, весь санитайзер (преамбулы, markdown-заголовки,
CJK-утечки), хотя markdown-заголовки (29 строк) и CJK-утечки (11 строк) в наборе видны как
подклассы K4 и ждут своего пакета.
**6. Черновики размечены, но боевыми гейтами не проверяются.** 38 из 40 дефектов латиницы и почти вся
утечка инъекционного блока банка (`⟦TM-BANK-v1⟧` с колонками `name`/`title`/`term`/`place` прямо в
тексте `verify2`) живут в ЧЕРНОВИКАХ. Строка «K2 детектор» в §4 меряет линейку, строка «боевой
охват» — то, что реально доезжает до вердикта. Это не одно и то же, и путать их нельзя.
**7. Набор — не оракул качества перевода.** Он отвечает ровно на вопрос «сработал ли чекер там, где
человек видит дефект ЭТОГО класса». Он ничего не говорит о верности, гладкости, регистре и о том,
что перевод вообще хороший.
**8. Метрики K5a/K5b/K5c/K1 — на уровне ЮНИТА.** Юнит с двумя вхождениями, где одно верно, а другое
нет, засчитывается как один положительный. Пер-вхожденческая разметка в наборе есть (`labels/*.jsonl`),
и по ней можно пересчитать иначе — но это будет ДРУГАЯ метрика, не сравнимая с этой.
**9. Синтетики в наборе нет вообще.** Ни одной строки я не дописывал. Там, где класса физически мало,
в §3 стоит настоящее число, а не добитое до 200.
**10. Приёмка воспроизводима, но зависит от стенда.** Набор и харнесс — вне git (текст книги).
`labels/README.md` описывает, как ре-ранить; ключевая ловушка при повторе — снимать детерминированный
заголовок с финала, иначе номер главы читается как «появившееся число».
---
## Итог одной таблицей
| линейка | вердикт по итогам первого честного замера |
|---|---|
| K1 DC1 时辰 | работает как описана: 6/6, ложных нет. Материала мало (15) |
| K5b DC2 | 1/1, ложных нет. Материала мало (11), 3 спорных упираются в вопрос о гиперболе |
| K2 латиница | ложных нет; recall 0.75 как детектор и 0.50 в боевом охвате; заглавные и гомоглифы вне класса |
| K3 битые формы | ложных нет; ловит 1 битую форму из 10 — один шаблон вместо класса |
| K4b смешение стилей | «8→0» подтверждено на мыслях: 99 из 100 ёлочных мыслей молча (1 ложняк). Но 41 реплика вслух из 46 не опознана |
| K4a неверный маркер | **не измерено:** на корпусе нет ни одного входа |
| K5a магнитуда 万 | ноль срабатываний на всём корпусе; предохранитель отсекает и идиомы, и все реальные магнитуды |
| K5c 成-процент | **инвертировано на реальном материале:** промолчал на единственном юните с 10×-ошибками, сработал на пяти с верными значениями |
| K5d дрейф чисел | 4 срабатывания, все безобидные; подлинного дрейфа в корпусе нет |
| K6 глоссарий | 1 подлинный промах против 14 ложных; на реально инъецированном подмножестве — 0 против 8 |
**Заявление = команда.** Приёмка: ре-ранить чекеры против набора по `labels/README.md` и сверить
`metrics.json`. Ожидаемый результат — побайтно те же цифры: и корпус, и харнесс, и чекеры детерминированы.