textmachine/docs/archive/reports/POLYGON_CHECKER_LABELS_2026-07-26.md

53 KiB
Raw Blame History

Полигон, пакет-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, данные пары — dc-checkers.txt:41-54.

Моё определение. Дефект = в источнике стоит длительность, измеренная в 时辰 (китайский «большой час» = 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.

Моё определение. Дефект = в русском выходе осталось целое ЛАТИНСКОЕ слово, которое там быть не должно: непереведённый кусок исходного/промежуточного английского («открыл их 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, данные цели — target-ru.txt:9 (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, форма реплики — chevronSpeechShape (:250), «это речь вслух?» — isSpokenChevronLine (checkers.go:467-483), данные цели — target-ru.txt:16-53.

Класс распадается на два независимых правила; меряю их РАЗДЕЛЬНО, потому что и живут они врозь.

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). Дефект = порядок величины из источника (三万 = 3·10⁴) не представлен в переводе ни числом, ни словом-магнитудой нужного порядка. НЕ дефект по коду: магнитуда, пересказанная прозой без слов-магнитуд (:453-459 — молчит); идиомы 万一/万分/千万/亿万 без цифры перед 万 (:491); голые единицы 十万/百万 (там нет цифры перед маркером).
  • K5b — DC2 千万 / 数十万 (checkers.go:281-302). Дефект = 千万 (10⁷) отдано как «тысячи», 数十万 (несколько ×10⁵) — как «десятки тысяч». Код сам признаёт неустранимую двусмысленность: 千万 сплошь и рядом ГИПЕРБОЛА («тьма», «мириады»), и её «тысячи» — в регистре (§5-A4, cheapgates.go:90-93). Размечаю гиперболу отдельным подклассом hyperbole, решение по ней — не моё (идёт владельцу).
  • K5c — 成-процент (checkers.go:346-370). Дефект = 成 (десятая доля: 六成 = 60%) отдано десятичной ДОЛЕЙ вместо процента. Внимание: код считает дефектом ФОРМУ, а не значение (cheapgates.go:84-89 — сознательно оставлено). Значит «три десятых» для 三成 — по коду дефект, хотя значение верное. Размечаю по коду (form-defect), а «значение верно/неверно» веду ВТОРОЙ колонкой.
  • K5d — дрейф чисел черновик→финал (regressionguard.go:64-76). Дефект = многозначное арабское число есть в черновике и пропало в финале (или появилось из ниоткуда). НЕ дефект по коду: однозначные числа (их штатно пишут словом); число, переписанное словами при редактуре, — код сам называет это своим ложным флагом (:24-28). Гейт включён только в мини-прогоне (minirun/pipeline.yaml: regression_guard.enabled: true); в rerun2 его нет.

Единица разметки. K5a/K5b/K5c — вхождение триггера в исходнике × прогон. K5d — (юнит, числовой токен, сторона).

K6. Глоссарий-промахи

Якорь: mempostcheck.go:61-122.

Моё определение. Дефект = термин глоссария, чей ИСХОДНЫЙ вид встретился в исходнике юнита, а в переводе не появилось ни одной принятой формы его перевода: термин выброшен, переведён по-другому или искажён.

Что кодом НЕ считается дефектом: 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. Разметка: что сделано и как

Разметил я, читая исходник и перевод; вердикты чекеров были получены ТОЛЬКО после того, как все файлы 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). То есть на реальном материале правило сработало ровно наоборот замыслу: промолчало там, где шкала действительно сломана в 10 раз, и заговорило там, где всё верно. По конвенции самого кода (форма-дробь = дефект) precision=1.000 — цифры расходятся не из-за реализации, а из-за того, ЧТО считать дефектом. Это вопрос владельцу (§5).

K4b (dialogue_dash, смешение) — «8→0» проверено. Правка v5 в основном работает: из 100 размеченных ёлочных МЫСЛЕЙ чекер флагнул одну (99 из 100 — молчание). Но ёлочных реплик ВСЛУХ в корпусе 46 (31% ёлочных строк), и из них поймано 5. Разбор 41 пропуска:

причина строк
безатрибутивная реплика — связки «…», — нет вообще 23
реплика оканчивается на ! / ? / , запятой после » нет — сознательный промах кода (cheapgates.go:248-249) 12
закрывающая » дальше 400 знаков (длинная реплика) 3
связка есть, но глагол говорения не из закрытого списка («раздался голос», «покинули его») 3

Единственное ложное срабатывание вскрывает механику: строка «Месть не входит в мои планы…», — подумав так, Фан Юань усмехнулся … и равнодушно произнёс: — — мысль, но isSpokenChevronLine ищет глагол говорения по ВСЕЙ строке (checkers.go:471-481), а «произнёс» стоит в другом предложении и относится к следующей реплике.

K4a — правило не проверяемо на этом корпусе. Строк, начинающихся с дефиса, короткого тире или ASCII-кавычки, во всём корпусе (6 863 непустых строки) — ноль. Ветка hyphenLike (cheapgates.go:213-222) ни разу не получила входа. Precision/recall неизмеримы, и это надо знать: половина класса живёт непроверенной.

Класс, которого у чекера нет: ОБРАТНАЯ ошибка маркера. 4 строки из 80 размеченных тире-строк — внутренняя речь, набранная тире как речь вслух: «— Жаль только этого хорошего гу, — вздохнул ПРО СЕБЯ Фан Юань», «— …, — недовольно хмыкнул ПРО СЕБЯ старейшина», «— Как же так… — бормотал СЕБЕ ПОД НОС Фан Чжэн». Маркеры «про себя» / «себе под нос» уже есть в данных цели как inner_marker, но применяются только к ёлочкам. По конструкции чекер считает любую тире-строку правильной, поэтому recall здесь 0.000 из 4.

K5a (магнитуда 万) — ноль срабатываний на всём корпусе, и это структурно. Из 67 вхождений 55 — идиомы (万物之灵, 万一, 千辛万苦, 万家灯火, 成千上万, 罪该万死, 十万八千里…). Оставшиеся реальные магнитуды (数十万, 千万, 数万) не проходят предохранитель «цифра перед большим маркером» (cheapgates.go:513-526): 数 и 十 — не цифры класса Digit, 千 — единица, а не цифра. Предохранитель отсекает идиомы правильно, но вместе с ними — и все реальные магнитуды этого корпуса. Единственный подлинный дефект класса (数十万 → «десятки тысяч» у mistral) пойман ДРУГИМ подчекером, DC2.

K5d (дрейф чисел) — 4 срабатывания, все безобидные. Три — номера глав в заголовке, которого не было в черновике; одно — 66% → «шестьдесят шесть процентов, то есть ровно тот ложный флаг, который код за собой признаёт (regressionguard.go:24-28). Подлинного дрейфа в корпусе нет: многозначных арабских чисел во всех 91 юните — 19.

K6 (глоссарий) — 14 ложных на 1 подлинный. Разбор:

  • 6 — однознаковый ключ сработал на ГЛАГОЛЕ (转身 «обернулся», 转过转角 «за поворотом», 辗转 «мыкался», 转为 «стать», 脑筋转不快 «мысль ворочается медленно»). Ранга в исходнике нет, переводить нечего, но условие «src сработал» — подстрочное вхождение.
  • 7 — целевая форма есть, но во МНОЖЕСТВЕННОМ числе, которого нет в decl.forms: «первокамней» при формах первокамня/первокамню/первокамень, «смертных» при смертного/…, «гу-тварей» при гу-твари/гу-тварь. Это ровно inflection_gap, который код называет пробелом СИДА, а не кода (mempostcheck.go:109-111) — теперь он измерен: 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), токены короче трёх («гс латинской y вместо кириллической у — гомоглиф) и токены с цифрой; битые формы ловят ровно один шаблон «-йть» из десяти найденных мной битых слов.

Зафиксированные дефекты чекеров (фиксация, НЕ правка)

Я не автор чекеров и ничего здесь не чинил. Каждый пункт — с якорем и с тем, чем он подтверждён на наборе.

# якорь что именно подтверждение на наборе
1 checkers.go:355 процентный супрессор гасит подчекер по ВСЕМУ юниту, если слово «процент» встретилось где угодно единственный юнит с настоящими 10×-ошибками шкалы (acceptance:10:0) пропущен
2 checkers.go:471-481 глагол говорения ищется по всей строке, а не в атрибуции ёлочки 1 из 1 ложных срабатываний K4b
3 cheapgates.go:250-275 форма «…», — — единственный признак ёлочной речи 38 из 46 реплик вслух не опознаны (23 безатрибутивных + 12 с !/?/ + 3 длинных)
4 cheapgates.go:209-233 тире-строка засчитывается корректной ВСЕГДА 4 мысли, набранные тире («вздохнул ПРО СЕБЯ»), невидимы, хотя inner_marker уже есть в данных цели
5 cheapgates.go:513-526 «цифра перед большим маркером» отсекает 数十万 / 千万 / 十万八千 вместе с идиомами подчекер number_magnitude не сработал ни разу на 149 232 знаках исходника
6 checkers.go:391 заглавная буква = «имя собственное» → токен отброшен «Cultivation» в ФИНАЛЕ acceptance:3:0 невидим; маркеры утечки банка TM, BANK — тоже
7 checkers.go:398 minLatinResidueLen = 3 гомоглиф «гy» (латинская y в слове «гу») невидим — 2 вхождения
8 dc-checkers.txt:65 класс символов cheng_re не содержит 两, хотя cjk_numeral содержит (两=2) 两成 в ряду 一成,两成,三成 (minirun ch10) не виден подчекеру процентов
9 mempostcheck.go:112-122 принятое множество = базовый dst decl.forms; множественного числа в сиде нет 7 ложных из 14: «первокамней», «смертных», «гу-тварей»
10 mempostcheck.go:61-80 «src сработал» = подстрочное вхождение; однознаковый ключ совпадает с частым глаголом 6 ложных из 14
11 runes.go:32-50 TokenizeCyrillic рвёт слово на комбинирующем ударении U+0301 (оно не кириллица) «бо́льшим» → «бо» + «льшим» в финале acceptance:25:0; затрагивает и ёфикатор, и битые формы, и пост-чек
12 waverun.go:486 (охват, не баг) 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).

Р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. Ожидаемый результат — побайтно те же цифры: и корпус, и харнесс, и чекеры детерминированы.