# Полигон, пакет-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, 5–8, 11–12 сверены с кодом (в т.ч. `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 | пере-прогон гл.1–5, арм 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 ещё оставлял в колонке. Расхождение объяснено и ожидаемо, а не молча проигнорировано. ### Физический потолок объёма по классам (посчитан ДО разметки) Промт задаёт ориентир 200–300 строк на класс. **Четыре класса из шести физически столько не содержат** — вот честные потолки на этом корпусе: | класс | что считалось | потолок items | 200–300 достижимо? | |---|---|---:|---| | 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. Разметка: что сделано и как Разметил я, читая исходник и перевод; вердикты чекеров были получены ТОЛЬКО после того, как все файлы `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** | Ориентир 200–300 строк выдержан там, где материал это позволяет (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 (成-процент) — самая тяжёлая находка пакета.** Пять из шести срабатываний пришлись на юниты, где ЗНАЧЕНИЕ передано верно («две-три десятых» для 两三成 = 20–30%, «сорок четыре сотых» для 四成四 = 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» с латинской `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 дефектов латиницы живут в черновиках и по конструкции вне охвата | Пункты 1–4 и 6–8 — это правила, которые можно поменять. Пункты 9–10 код сам называет пробелом СИДА, а не кода; пункт 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`. Ожидаемый результат — побайтно те же цифры: и корпус, и харнесс, и чекеры детерминированы.