From cdc5a5f1560e82f91626954d464245b5005a1942 Mon Sep 17 00:00:00 2001 From: heaven Date: Sun, 30 Aug 2026 20:10:54 +0300 Subject: [PATCH] Land stage 0 item 1: the bank holds its terms, and the audit found on disk a production-profile run, a July measurement of the same question, and a ratified editor-price input this phase never executed --- docs/experiments/23-editor-tier.md | 174 +++++++++++++ eval/dovodka/KONSILIUM-30-08.md | 25 ++ eval/dovodka/SESSION-30-08.md | 31 ++- eval/dovodka/bank.py | 397 +++++++++++++++++++++++++++++ 4 files changed, 626 insertions(+), 1 deletion(-) create mode 100644 eval/dovodka/bank.py diff --git a/docs/experiments/23-editor-tier.md b/docs/experiments/23-editor-tier.md index 21c7dff7..ede8a0b6 100644 --- a/docs/experiments/23-editor-tier.md +++ b/docs/experiments/23-editor-tier.md @@ -7534,3 +7534,177 @@ ln 38 = 3.64, то есть **примерно 3.1 sd — событие ред компонента одна — наклон раскладки: он в тексте не живёт по построению. Корреляция — справочно. * **Объём правки не предсказывает место `Z0` у читателя** (0.18, незначимо): «больше правил — лучше прочли» не наблюдается. + +--- + +## Д50. Стадия 0 п.1 исполнена: банк держит термины — но главное найдено НЕ в замере, а на диске + +**Дата:** 30.08.2026, полигон. **Стоимость: $0** (только артефакты на диске). Носитель прибора — +`eval/dovodka/bank.py` (пре-регистрация в шапке файла, записана ДО первого числа). + +### Д50.1. ⭐ Четыре вещи, которых консилиум не знал, и все они лежали на диске + +Консилиум 30.08 назначил аудит прогона `acceptance`. Исполнение показало, что заказ был построен +на неполной карте собственного архива: + +1. **Рядом лежит более боевой прогон `rerun`** (11.07): промты `v1-reflow` вместо `v0-draft`, + редактор БИЛИНГВ (D30.1), переведены 57/57 чанков против 54/57. Движковых промахов 27 против 55. +2. **Лежит и сам БОЕВОЙ ПРОФИЛЬ — `rerun2-dspro`** (23.07, 5 глав): черновик `deepseek-v4-flash` + + редактор **`deepseek-v4-pro`**, плюс арм `mistral-large-2512`, которого нет ни в одной нашей + таблице моделей. ⇒ Утверждение фазы «боевой профиль недоступен без Шага 7 (~$2)» верно только + про ОБЪЁМ (5 глав против 15), а не про наличие. +3. **Эту работу уже делали в июле, и сильнее.** `docs/archive/PROGRESS-2026-07-10-13.md:149` — замер + B5 D10 независимым скриптом: presence 91.0% (647/711), occurrence 95.8%, по типам (титулы — + слабейший класс, 78.7%), классы промахов `decl_gap 0` · `inflection_gap 54` · `genuine_absent 10`, + эффективная консистентность 98.6%. +4. **Три редактора уже отсужены на боевой китайской паре** — `rerun2/RERUN_REPORT.md`: 56 игр на арм, + судья `gemini-3.1-pro` + `grok-4.3`-фолбэк, шумовой пол замерен (14/14 tie на идентичном тексте). + Ранг: **dspro 0.679 > glm-5 0.589 >> mistral 0.232**. ⇒ Оговорка консилиума §4 «вся наша фактура + по дешёвым моделям АНГЛИЙСКАЯ» неверна для ВЫБОРА РЕДАКТОРА: он мерен на китайской паре. + +⛔ **Класс ошибки один и он не про эти четыре пункта, а про фазу: «фаза не знала собственный архив».** +Вчера тем же классом было «фаза не знала собственный бэкенд». Дешёвое лекарство очевидно и стоит $0: +**прежде чем строить прибор — смотреть, не построен ли он.** + +⛔ **ПЯТАЯ ТИХАЯ ПОЛОМКА ПЕРЕЕЗДА.** Июльские скрипты (`d10_consistency.py`, `audit.sh`, `reflow.py`, +`extract.py`) НЕ ПЕРЕЖИЛИ переезд машины: `PROGRESS-2026-07-10-13.md:190` перечисляет их поимённо с +пометкой «Всё ВНЕ git», и в каталоге остались только ДАННЫЕ. После путей `~/books`, обнулённых mtime +и подчёркиваний в `.env` — четвёртая известная, а по счёту находок пятая. **Класс: что вне git, то +переезд убивает молча.** `bank.py` заведён в git намеренно. + +### Д50.2. Результат замера: гипотеза «банк держит» НЕ ОПРОВЕРГНУТА + +Единица — пара (термин, чанк) со сработавшим ключом; принятое множество форм = `dst` ∪ `decl.forms` +(ратифицированное D24.4). Эндпойнт — CONFIRMED (`approved`); с AMBIGUOUS спрашивать нельзя, движок +её таковой не объявлял (`mempostcheck.go:52-64`). + +| прогон / режим | пар | presence | взвешенная доля промахов | эффективная консистентность | +|---|---|---|---|---| +| `acceptance`, native | 375 | 98.9% | **0.23%** | 100.0% | +| `acceptance`, substring | 390 | 98.5% | **0.62%** | 99.5% | +| `rerun`, native | 531 | 97.4% | **0.91%** | 99.6% | +| `rerun`, substring (методика июля) | 548 | 96.9% | **1.29%** | 99.3% | + +⚠ Числа сняты с ФИНАЛЬНОЙ версии прибора. Промежуточная, до починки регистра, давала presence на +0.5–0.9 пункта ниже; таблица едва не ушла в коммит с ними — см. Д50.4 п.4. + +**Метрика A (порог 2%) — в пределах во всех четырёх сечениях.** Метрика B пробита в одном сечении +единственным термином `炼化` и разобрана ниже как ЛОЖНАЯ. + +⭐ **Взаимная верификация двух независимых реализаций через полтора месяца.** Мой прибор +самостоятельно воспроизвёл июльские `decl_gap = 0`, `genuine_absent = 10` (до починки эвристики) и — +поимённо — редчайший класс `古月 → Гуюэ` ровно в главах **18/0 и 20/1**, названных июльским отчётом. +Совпадение поимённое, а не по величине. + +### Д50.3. ⭐ Слепое пятно, которого не видит НИ ОДИН из двух приборов + +Чтение кандидатов глазами дало находку, не сводимую к обоим счётчикам. Гл.18/ч0: оригинал +`古月方源`, в переводе — **«Гу Юэ Фан Юань»**, тогда как канон банка — слитное «Гуюэ». + +Почему молчат оба прибора: `古月方源` заведён **алиасом полного имени** (`glossary_aliases`, +`alias_type=fullname`) к записи `方源 → Фан Юань`. Автомат движка берёт длиннейший ключ, ждёт в +выходе «Фан Юань» — и находит его. Родовая приставка канону не соответствует, но её никто не +проверяет: ожидаемая часть на месте. ⇒ **Дрейф ВНУТРИ составного имени не наблюдаем ни пост-чеком +движка, ни `presence`-метрикой.** Дёшево чинится: проверять алиас-ключ на собственную канонную +форму, а не только dst записи-хозяина. + +### Д50.4. Четыре ложные тревоги МОЕГО прибора — все пойманы чтением, ни одна не опубликована + +Прибор строился и правился в один заход, и каждая правка рождала новую ошибку — ровно вчерашний урок +«правка правки не безопаснее правки», подтверждённый четырежды за один пункт плана: + +1. ⛔ **Зелёный вердикт на ПУСТОМ знаменателе.** Парсер не понял формат `rerun` (там `--- чанк 0 ---` + без флага), разобрал 0 чанков — и прибор напечатал «порог не пробит, гипотеза не опровергнута». + Лечение — не только формат: заведён гейт `assert_measured`, отказывающий с кодом 2 при нулевом + знаменателе. **Нулевой знаменатель — не «в пределах», а отсутствие замера.** +2. ⛔ **Промахи на непереведённых чанках.** 4 чанка `acceptance` гейт покрытия не переводил вовсе; + отсутствие термина там — дефект ПОКРЫТИЯ, не банка. +3. ⛔ **Односимвольные ханьские ключи** `蛊`/`转`: `memory.go:319` удаляет такой ключ из автомата — + записи не инжектировались НИКОГДА. Мой счёт спрашивал за них и дал ⛔ по метрике B. +4. ⛔ **Регистр и чередование согласных.** `开窍 → «открытие апертуры»` считалось промахом при + «Открытие апертуры удалось!» в тексте; `炼化 → «очистить и подчинить»` уезжало в кандидаты дрейфа, + потому что «очистить»[:4] = «очис», а в тексте «очищает». + +⚠ **Все семь «промахов» `炼化` — верный перевод** («очищает и подчиняет», «очистил и подчинил», «по +очистке и подчинению») при **ПУСТОМ `decl.forms`**. Метрика B по нему пробита формально и по существу +ложна: это дефект ЗАСЕВА глагольного термина, а не дрейф. + +### Д50.5. ⛔ Что из этого — долг полигона (моя зона) + +* **Глагольные и многословные термины засеяны без форм.** `炼化 → «очистить и подчинить»`, + `forms=[]` — пост-чек по такому термину не может не врать. Требуется засев форм либо явное + исключение класса из пост-чека. +* **`inflection_gap` живёт** (16–17 CONFIRMED на `rerun`): мн.ч. дописано сидом v2 не всюду. +* ⚠ **Осторожно, поправка к самому себе:** первая редакция этого разбора объявила июльскую задачу + «дописать мн.ч.» НЕВЫПОЛНЕННОЙ — по чтению `books/gu-zhenren/guzhenren-seed.yaml`. Это неверно: + рядом лежит **`guzhenren-seed-v2.yaml`**, и мн.ч. там есть («первобытные камни», «гу-мастеров», + «смертные»), плюс 8 новых записей. Класс ошибки: **вывод по первому найденному файлу без проверки, + единственный ли он.** Поймано до публикации. + +--- + +## Д51. ⭐⭐ Вход ратификации, висевший на фазе Д, исполнен: выбор редактора держится на цене, а цена перевернулась + +**Дата:** 30.08.2026, полигон. **Стоимость: $0.** Носитель — пересчёт оплаченных клеток `rerun2`. + +### Д51.1. Задача была поставлена ратифицированной нотой ЭТОЙ фазе и не исполнена + +Цепочка, восстановленная по журналу решений (не по пересказу): + +* **D39.22** (23.07): редактор = `deepseek-v4-pro`, интерим. Основания: судья №1 · лучшая проза и + глоссарий · дефекты чинибельного класса · **«×2 дешевле glm»**. +* **D39.117 п.3, строка 65 ЗАКРЫТА** (08.08): «движки dspro/glm-5 **неразличимы** (`A/B` +0.06 + p=0.95; `A_law/B` −0.69 порога не проходит) ⇒ **жилец редактора — вопрос ЦЕНЫ, то есть конфиг**». +* **D39.137** (15.08): пере-пин вендора. «Посылка интерим-редактора D39.22 в ПИКЕ ПЕРЕВЁРНУТА — + dspro пик ×1.26 ДОРОЖЕ glm-5… **Вход ратификации жильцов при фазе Д**» (`05-decisions-log.md:380`). + +⇒ **Ратификация пере-выбора редактора формально висит на фазе Д с 15.08 и не исполнена.** Консилиум +29–30.08 обсуждал «резать ли редакторскую стадию», не зная, что настоящий открытый вопрос — +не «резать», а «кто в ней живёт по нынешней цене», и что он адресован именно нам. + +### Д51.2. Замер: одна нагрузка, оба редактора, СОБСТВЕННЫЕ токены каждого + +D39.137 считала контрфактику по оси цены **на токенах glm** и сама назвала это оговоркой. В `rerun2` +есть лучшее: три арма редактора **на одном и том же оплаченном черновике**, каждый со своим расходом. + +**Валидация метода — прежде результата:** пересчёт по СТАРОМУ прайсу воспроизводит записанный +движком `cost_usd` **бит-в-бит на всех трёх армах** (glm $0.076583, dspro $0.040555, mistral +$0.040093 — расхождение 0.0%). Значит прайс-таблица и формула те самые, которыми считал движок. + +| редактор | prompt / cached / completion | нынешний прайс, 5 глав | книга 1500 глав | к glm | +|---|---|---|---|---| +| `glm-5` | 33 831 / 3 456 / 14 224 | $0.0766 | **$22.97** | — | +| `deepseek-v4-pro`, пик | 39 795 / 8 192 / 30 779 | $0.1640 | **$49.19** | **×2.14** | +| `deepseek-v4-pro`, офф-пик | те же | $0.0820 | $24.59 | ×1.07 | +| `mistral-large-2512` | 39 119 / 1 232 / 13 689 | $0.0401 | $12.03 | ×0.52 ⛔ исключён D39.20 | + +**Оговорка, снимающая часть разрыва:** из 11 записей арма dspro три — записи санитайзера по $0, а +одна оборвалась по `finish=length` и пере-генерировалась. Обрыв — артефакт флора `min_max_tokens` +8000, поднятого позже до 16000. Без него: пик $0.1238 → **книга $37.14 (×1.62)**, офф-пик $0.0619 → +**$18.57 (×0.81)**. + +### Д51.3. Что из этого следует и чего НЕ следует + +* ⭐ **В ПИКЕ dspro дороже glm-5 при любом обращении с артефактом — в 1.6–2.1 раза.** Экономическое + основание D39.22 в пиковые часы не просто ослабло, а сменило знак. +* ⚠ **Уточнение к D39.137, и оно в сторону ХУДШЕГО для dspro.** Нота дала «офф-пик дешевле ×1.59»; + на собственных токенах офф-пик даёт от ×1.07 ДОРОЖЕ до ×0.81 дешевле. Причина проста и видна в + таблице: **dspro тратит вдвое больше completion-токенов** (30 779 против 14 224), чего контрфактика + на чужих токенах увидеть не могла. +* ⛔ **Из этого НЕ следует «менять редактора».** Три причины, и все три — не мои полномочия: + (1) D39.117 закрыл строку 65 на «неразличимы», но неразличимость мерена ригом фазы, а судья + `rerun2` ставил dspro первым (0.679 против 0.589) — приборы согласны лишь в том, что зазор мал; + (2) пик у DeepSeek — только будни 01–04 и 06–10 UTC, то есть ответ зависит от РАСПИСАНИЯ прогона, + а не только от модели; (3) выбор жильца — конфиг `backend/`, зона бэкенда и лендинг оркестратора. +* ⛔ **Носитель решения врёт прямо сейчас.** `backend/configs/pipeline-c1.yaml:65` и + `backend/configs/pipeline-arm-glm.yaml:6` до сих пор обосновывают выбор словами «×2 дешевле glm». + Это устаревшее обоснование в БОЕВОМ конфиге. Правка — зона бэкенда; **пинг сдан этой секцией.** + +### Д51.4. Дешёвый ход, который виден из этих же данных + +Если жилец — вопрос цены (D39.117), то у вопроса есть ответ, не требующий смены модели: +**гнать редакторскую волну в офф-пик.** Для DeepSeek офф-пик — это все выходные целиком и будни вне +двух окон; скидка ровно ½ и не требует ни решения о модели, ни правки Go. На книге это разница +между $49 и $25 у того же самого редактора, с которым фаза уже согласилась по качеству. +⚠ Тарифная погода не климат: несущую архитектуру на скидочное окно не ставят, но ПЛАНИРОВЩИК волн — +ровно то место, где расписание законно. diff --git a/eval/dovodka/KONSILIUM-30-08.md b/eval/dovodka/KONSILIUM-30-08.md index 8dc1781f..65210687 100644 --- a/eval/dovodka/KONSILIUM-30-08.md +++ b/eval/dovodka/KONSILIUM-30-08.md @@ -6,6 +6,31 @@ # КОНСИЛИУМ 30.08 — АРХИТЕКТУРА ГЕНЕРАЦИИ, МОДЕЛИ, УБОРКА +> ⚠⚠ **ПОПРАВКИ ОТ 30.08 ПОСЛЕ ИСПОЛНЕНИЯ СТАДИИ 0 п.1 — ЧИТАТЬ ПРЕЖДЕ ТЕЛА.** Разбор — **Д50 и +> Д51** отчёта `docs/experiments/23-editor-tier.md`. Консилиум строился на неполной карте +> собственного архива, и четыре его положения этим затронуты: +> +> 1. ⛔ **§5 п.1 назначал аудит `acceptance`. Объект выбран неверно:** рядом лежит более боевой +> `rerun` (11.07, промты `v1-reflow`, редактор-билингв, 57/57 чанков), а также **`rerun2-dspro` +> (23.07) — настоящий БОЕВОЙ профиль с редактором `deepseek-v4-pro`** на 5 главах. «Боевого +> профиля нет без Шага 7» верно про ОБЪЁМ, не про наличие. +> 2. ✅ **Сам п.1 ИСПОЛНЕН, вывод: гипотеза «банк держит» в терминной половине НЕ ОПРОВЕРГНУТА** — +> взвешенная доля промахов 0.23–1.29% при пороге 2%, эффективная консистентность 99.3–100%. +> Прибор `eval/dovodka/bank.py` независимо воспроизвёл июльский замер, включая поимённое +> совпадение редчайшего класса. **Работа уже делалась в июле; её скрипты погибли при переезде +> (были вне git) — ПЯТАЯ тихая поломка того же класса.** +> 3. ⛔ **§4 «вся наша фактура по дешёвым моделям АНГЛИЙСКАЯ» — неверно для ВЫБОРА РЕДАКТОРА.** +> Три редактора отсужены на боевой китайской паре (`rerun2/RERUN_REPORT.md`, 56 игр на арм, +> шумовой пол замерен): dspro 0.679 > glm-5 0.589 >> mistral 0.232. +> 4. ⛔⛔ **§7 и §6 п.3 ставят вопрос не там.** Открытый вопрос не «резать ли редактора», а «кто в +> нём живёт по нынешней цене» — и он адресован ФАЗЕ Д ратифицированной нотой **D39.137 от +> 15.08** («вход ратификации жильцов при фазе Д»), не исполненной. Замер сделан (Д51): +> **в пике `deepseek-v4-pro` дороже `glm-5` в 1.6–2.1 раза**, в офф-пик — от ×1.07 дороже до +> ×0.81 дешевле. ⚠ Это ХУЖЕ для dspro, чем оценка самой D39.137 (×1.59 дешевле в офф-пик), +> потому что та считала на чужих токенах, а dspro тратит вдвое больше своих. +> ⛔ Боевой конфиг `backend/configs/pipeline-c1.yaml:65` до сих пор обосновывает выбор словами +> «×2 дешевле glm» — устаревшее обоснование в живом файле; пинг бэкенду сдан в Д51.3. + **Заказ владельца дословно:** (1) насколько сложной делать архитектуру харнесса — качество без взлёта цены, с учётом банка памяти; что делает черновая модель; нет ли сдвигов в ландшафте; (2) на каких моделях выгодно ехать по цене/качеству; (3) нет ли ошибок в замерах и ошибок в выводах diff --git a/eval/dovodka/SESSION-30-08.md b/eval/dovodka/SESSION-30-08.md index e9a16705..59be6284 100644 --- a/eval/dovodka/SESSION-30-08.md +++ b/eval/dovodka/SESSION-30-08.md @@ -7,7 +7,36 @@ > ⚠ Персист, не scratch (норма владельца 01.08): файл лежит в зоне и коммитится, потому что > эфемерный scratchpad умирает вместе с сессией. -## 0-БИС. ⭐⭐ ГЛАВНОЕ ПОСЛЕ КОНСИЛИУМА 30.08 — ЧИТАТЬ ПЕРВЫМ, ОСТАЛЬНОЕ НИЖЕ ИСТОРИЧНО +## 0-ТРИ. ⭐⭐ СОСТОЯНИЕ НА ВЕЧЕР 30.08 — САМОЕ СВЕЖЕЕ, ЧИТАТЬ ПЕРВЫМ + +**Стадия 0 п.1 ИСПОЛНЕНА.** Разбор — **Д50 и Д51** в `docs/experiments/23-editor-tier.md`, прибор — +`eval/dovodka/bank.py` (в git намеренно), поправки к курсу — врезка в шапке `KONSILIUM-30-08.md`. + +1. ✅ **Банк держит термины.** Взвешенная доля промахов CONFIRMED 0.23–1.29% при пороге 2%; + эффективная консистентность 99.3–100%. Гипотеза в терминной половине НЕ ОПРОВЕРГНУТА. +2. ⭐⭐ **Главное найдено не замером, а на диске.** Лежат: более боевой прогон `rerun`; **БОЕВОЙ + профиль `rerun2-dspro` (редактор `deepseek-v4-pro`, 5 глав)**; июльский замер того же вопроса; + **отсуженная на КИТАЙСКОЙ паре тройка редакторов** (dspro 0.679 > glm 0.589 >> mistral 0.232). +3. ⭐⭐ **Ратифицированный вход фазы Д, висевший с 15.08, исполнен (Д51).** D39.117: жилец редактора — + вопрос ЦЕНЫ. D39.137: посылка «dspro ×2 дешевле glm» перевёрнута. Замер на одной нагрузке + собственными токенами: **в пике dspro дороже glm в 1.6–2.1×**, в офф-пик от ×1.07 дороже до ×0.81 + дешевле. Дешёвый ход без смены модели — **гнать редакторскую волну в офф-пик** (÷2). +4. ⛔ **Пинги, сданные секцией (чужие зоны, сам не правил):** `pipeline-c1.yaml:65` и + `pipeline-arm-glm.yaml:6` обосновывают выбор устаревшим «×2 дешевле glm»; `docs/PROGRESS.md:610` + несёт снятую сильную форму «полей пола в банке нет вовсе, механизма не существовало». +5. ⛔ **ПЯТАЯ тихая поломка переезда:** июльские скрипты замера (`d10_consistency.py` и др.) были ВНЕ + git и погибли. Класс: **что вне git, то переезд убивает молча.** +6. ⚠ **Четыре ложные тревоги собственного прибора, пойманные чтением, — Д50.4.** Из них главная: + **прибор напечатал «гипотеза не опровергнута» на НУЛЕВОМ знаменателе**, когда парсер не понял + формат. Заведён гейт `assert_measured` (код 2). Плюс: непереведённые чанки · односимвольные + ханьские ключи вне автомата · регистр и чередование согласных. + ⚠ И поправка к себе: «долг полигона по мн.ч. не выполнен» — НЕВЕРНО, есть `guzhenren-seed-v2.yaml`; + читал первый найденный файл, не проверив, единственный ли он. + +**Остаток Стадии 0:** п.2 (разбор диффов по категориям) · п.3 (позиционный аудит панелей + латинский +квадрат) · п.5 (базис Жаккара) · п.6 (уборка). П.4 — греп исполнен, находка в п.4 выше. + +## 0-БИС. ⭐⭐ ГЛАВНОЕ ПОСЛЕ КОНСИЛИУМА 30.08 — ОСТАЛЬНОЕ НИЖЕ ИСТОРИЧНО **Твой курс лежит в `eval/dovodka/KONSILIUM-30-08.md`.** Там итог шести раундов с архитектором, четырёх со скептиком и одного с разведкой: что установлено, что СНЯТО, план по стадиям, и что diff --git a/eval/dovodka/bank.py b/eval/dovodka/bank.py new file mode 100644 index 00000000..9719e74e --- /dev/null +++ b/eval/dovodka/bank.py @@ -0,0 +1,397 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +bank.py — АУДИТ УДЕРЖАНИЯ БАНКА на движковых прогонах 蛊真人 (zh→ru, 25 глав). + +Стадия 0 п.1 плана `eval/dovodka/KONSILIUM-30-08.md` §5. Стоимость $0: только артефакты на диске. + +═══════════════════════════════════════════════════════════════════════════════════════════════ +⚠⚠ ПОПРАВКА КУРСА, НАЙДЕННАЯ ИСПОЛНЕНИЕМ 30.08 — ЧИТАТЬ ПРЕЖДЕ ЧИСЕЛ +═══════════════════════════════════════════════════════════════════════════════════════════════ +Консилиум назначил аудит прогона `acceptance`, НЕ ЗНАЯ ДВУХ ВЕЩЕЙ: + +1. ⭐ РЯДОМ ЛЕЖИТ БОЛЕЕ ПОЗДНИЙ И БОЛЕЕ БОЕВОЙ ПРОГОН `rerun` (11.07, `~/books/gu-zhenren/rerun/`). + Он собран из боевого `pipeline-c1.yaml` post-D33 байт-в-байт: промты `v1-reflow` (не `v0-draft`), + редактор БИЛИНГВ (D30.1), переведены все 57 чанков (у `acceptance` — 54 из 57). Движковых + промахов там 27 против 55. ⇒ ОБЪЕКТ АУДИТА — ОБА, и `rerun` важнее. + ⚠ Боевым он всё равно НЕ является: редактор `glm-5`, а не `deepseek-v4-pro`. + +2. ⛔ ЭТУ РАБОТУ УЖЕ ДЕЛАЛИ В ИЮЛЕ, и результат сильнее плановой заявки. `docs/archive/ + PROGRESS-2026-07-10-13.md:149` (замер B5 D10, независимый скрипт `rerun/d10_consistency.py`): + presence 91.0% (647/711) · occurrence 95.8% (3072/3207) + по типам: имена 98.6% · клички 100% · места 96.0% · термины 94.9% · ТИТУЛЫ 78.7% + промахи: decl_gap 0 · inflection_gap 54 · genuine_absent 10 ⇒ эффективная 98.6% + ⛔ Сами СКРИПТЫ не пережили переезд машины — лежали ВНЕ git (`PROGRESS-2026-07-10-13.md:190` + перечисляет их поимённо; в каталоге остались только данные). Это ЧЕТВЁРТАЯ тихая поломка + переезда после путей `~/books`, обнулённых mtime и подчёркиваний в `.env`. Класс один: + ЧТО ВНЕ GIT, ТО ПЕРЕЕЗД УБИВАЕТ МОЛЧА. Этот файл — в git намеренно. + ⇒ Июльские числа здесь не пересказываются как свои: они ЭТАЛОН СВЕРКИ. Совпадение = две + независимые реализации через полтора месяца согласились. Расхождение = находка, а не шум. + +═══════════════════════════════════════════════════════════════════════════════════════════════ +ПРЕ-РЕГИСТРАЦИЯ — ПРАВИЛО РЕШЕНИЯ ЗАПИСАНО ДО ПЕРВОГО ЧИСЛА +═══════════════════════════════════════════════════════════════════════════════════════════════ +ЧТО МЕРЯЕМ. Вопрос 0 в его ТЕРМИННОЙ половине: удерживает ли банк канонные формы через 25 глав. +Голосовая половина им НЕ закрывается ПО ПОСТРОЕНИЮ — колонки `first_person`/`speech` пусты 0/49 +(Д32.16, консилиум §5 п.1). + +ЕДИНИЦА — пара (термин, чанк), в которой ключ термина СРАБОТАЛ в китайском оригинале чанка. Ровно +это условие включает инжекцию записи в промт, поэтому только такие пары и подсудны. + +ДИСПОЗИЦИИ (`backend/internal/membank/memory.go:121,650-663` — прочитано, не пересказано): + approved → CONFIRMED (авторитетно) · auto/draft → AMBIGUOUS (кандидат, обязательности нет). +Блок ограничений редактора — CONFIRMED-only (`memory.go:745-750`). +⇒ ОСНОВНОЙ ЭНДПОЙНТ — CONFIRMED. С AMBIGUOUS-записи спрашивать соблюдения нельзя: движок её +таковой не объявлял, и `mempostcheck.go:52-64` прямо зовёт наказание за неё инверсией контракта. + +ПРИНЯТОЕ МНОЖЕСТВО ФОРМ = базовый `dst` ∪ `decl.forms`. Это РАТИФИЦИРОВАННОЕ D24.4, а не мой +выбор: проверка одних косвенных давала 37 ложных промахов из 55 на этих же данных (nominative_gap). + +ТРИ СТРУКТУРНЫХ ИСКЛЮЧЕНИЯ — каждое установлено кодом или данными, ни одно не «для красоты»: + 1. ЧАНКИ БЕЗ ПЕРЕВОДА. Гейт покрытия пометил их флагом и не переводил вовсе; в параллельном + файле у них нет секции `[ПЕРЕВОД]`. Отсутствие термина там — дефект ПОКРЫТИЯ, не банка. + ⚠ Печатается отдельной строкой, а не заминается. + 2. ОДНОСИМВОЛЬНЫЕ ХАНЬСКИЕ КЛЮЧИ без `allow_short` (蛊, 转). `memory.go:319` УДАЛЯЕТ такой ключ + из автомата — записи не инжектировались НИКОГДА, спрашивать за них нельзя. (В живом коде есть + ещё и демот таких промахов, `mempostcheck.go:122`, но он от D39.39 — ПОЗЖЕ этих прогонов.) + 3. STICKY (scene-inertia, A5) — ключа в чанке нет по построению, `mempostcheck.go:31-33` + исключает их сознательно. Тоже печатается числом. + +ПРАВИЛО РЕШЕНИЯ (порог консилиума §5 п.1; двусмысленность его второй половины разрешена ЗДЕСЬ, +до чисел, и разрешение объявлено явно): + A. взвешенная доля промахов CONFIRMED > 2% ⇒ «банк держит» ложна в сильной форме; + B. «любая сущность топ-50» двусмысленна — записей всего 49, и буквальное чтение убивает гипотезу + одной случайной строкой. Читаю как проверку на СИСТЕМАТИЧЕСКИЙ провал ядра: термин из ТОП-10 + по весу ключа с долей промахнутых чанков >= 20%. + ⚠ Обе печатаются всегда и не пересматриваются после просмотра чисел. + +КЛАССИФИКАЦИЯ ПРОМАХА (июльская номенклатура, чтобы числа были сравнимы): + decl_gap — принятая форма есть, но её не увидели бы БЕЗ union D24.4 (контроль: должен быть 0); + inflection_gap — основа dst присутствует в форме, которой в сиде НЕТ (обычно мн.ч.) ⇒ читатель + видит верный термин, это дефект ЗАСЕВА, а не дрейф; + genuine_absent — основы нет вовсе ⇒ КАНДИДАТ дрейфа, требующий чтения глазами. НЕ дрейф сам по себе. +⚠ Граница признана честно: определитель основы здесь — ЭВРИСТИКА общего префикса, а не морфология +(в движке стеммер настоящий). Поэтому `genuine_absent` объявляется КАНДИДАТОМ и подлежит чтению. + +ДВА РЕЖИМА МАТЧИНГА, потому что эталоны построены по-разному: + native — longest-match, как автомат движка (古月 внутри 古月漠北 не считается); + substring — независимая подстрока на термин, как июльский скрипт: он этим и обошёл alias-слепое + пятно D24.2, поймав 古月→Гуюэ ×2. Числа июля сравнивать нужно с ЭТИМ режимом. + +ВТОРОЙ КОНТУР — СВЕРКА С ДВИЖКОВЫМ ПОСТ-ЧЕКОМ (`postcheck_gate: false` ⇒ флаггер пишет промахи в +`retrieval_state`). Расхождение ЛЮБОГО знака — находка: + движок нашёл, я нет ⇒ ложная тревога гейта (ожидаемо: базы писаны ДО фикса D24.4); + я нашёл, движок нет ⇒ слепота гейта — то, чего он не смотрит. + +ЧЕГО ПРИБОР НЕ УМЕЕТ: он не читает смысл — термин в правильной форме не на своём месте он засчитает. +""" + +import argparse +import json +import re +import sqlite3 +import sys +from collections import defaultdict +from pathlib import Path + +RUNS = { + "acceptance": { + "db": Path.home() / "books/gu-zhenren/acceptance/guzhenren-acc-a.db", + "parallel": Path.home() / "books/gu-zhenren/acceptance/guzhenren-25ch-parallel.txt", + "book_id": "guzhenren-acc-a", + "note": "10.07, промты v0-draft, редактор glm-5 монолингв, 54/57 чанков переведены", + }, + "rerun": { + "db": Path.home() / "books/gu-zhenren/rerun/guzhenren-rerun.db", + "parallel": Path.home() / "books/gu-zhenren/rerun/rerun-parallel.txt", + "book_id": None, # берётся из базы: имя книги у пере-прогона своё + "note": "11.07, промты v1-reflow, редактор glm-5 БИЛИНГВ, 57/57 чанков — ближе к боевому", + }, +} + +WEIGHTED_MISS_MAX = 0.02 +TOP_N = 10 +CORE_MISS_MAX = 0.20 +# Эвристика основы для КЛАССИФИКАЦИИ промаха (на сам вердикт не влияет — метрики A и B считают +# промахи, а не классы, поэтому это число ничего подогнать не может). +# ⚠ 4 знака ЛОМАЮТСЯ НА ЧЕРЕДОВАНИИ СОГЛАСНЫХ: «очистить»[:4] = «очис», а в тексте стоит «очищает» — +# и верно переведённый термин уезжает в `genuine_absent`, то есть в кандидаты дрейфа. Поймано +# чтением текста 30.08 на 炼化 → «очистить и подчинить»: все 7 его «промахов» суть верный перевод +# при ПУСТОМ `decl.forms`. 3 знака чередование переживают; цена — более грубое сито. +STEM_MIN = 3 + +CHAPTER_RE = re.compile(r"^【 Глава (\d+) 】", re.M) +# ⚠ Флаг в скобках есть у `acceptance` и ОТСУТСТВУЕТ у `rerun` (`--- чанк 0 ---`). Первая редакция +# требовала скобок — и на `rerun` разобрала НОЛЬ чанков, после чего прибор напечатал «порог не +# пробит» и «гипотеза не опровергнута» НА ПУСТОМ ЗНАМЕНАТЕЛЕ. Отсюда же гейт `assert_measured`. +CHUNK_RE = re.compile(r"^--- чанк (\d+)(?: \[([^\]]*)\])? ---$", re.M) +HAN_RE = re.compile(r"[一-鿿]") + + +def load_glossary(con, book_id): + aliases = defaultdict(list) + for term_id, alias in con.execute( + "select term_id, alias from glossary_aliases where book_id=?", (book_id,) + ): + aliases[term_id].append(alias) + rows = [] + for (tid, src, dst, typ, status, gender, decl, allow_short) in con.execute( + "select id, src, dst, type, status, gender, decl, allow_short " + "from glossary where book_id=? order by id", + (book_id,), + ): + forms = [] + if decl: + try: + forms = [f for f in json.loads(decl).get("forms", []) if f] + except json.JSONDecodeError: + pass + keys = [src] + aliases[tid] + # Структурное исключение 2: односимвольный ханьский ключ без allow_short в автомат не попал. + eligible = [k for k in keys if allow_short or not (len(k) == 1 and HAN_RE.match(k))] + rows.append( + { + "id": tid, "src": src, "dst": dst, "type": typ, "status": status, + "gender": gender, "keys": eligible, "banned": [k for k in keys if k not in eligible], + "decl_forms": forms, "accepted": sorted({dst, *forms}, key=len, reverse=True), + "confirmed": status == "approved", + "stem": dst[:STEM_MIN] if len(dst) >= STEM_MIN else dst, + } + ) + return rows + + +def split_chunks(text): + out = [] + chapters = list(CHAPTER_RE.finditer(text)) + for i, cm in enumerate(chapters): + ch = int(cm.group(1)) + body = text[cm.end(): chapters[i + 1].start() if i + 1 < len(chapters) else len(text)] + marks = list(CHUNK_RE.finditer(body)) + for j, km in enumerate(marks): + seg = body[km.end(): marks[j + 1].start() if j + 1 < len(marks) else len(body)] + translated = "[ПЕРЕВОД]" in seg + zh, ru = (seg.split("[ПЕРЕВОД]", 1) if translated else (seg, "")) + out.append((ch, int(km.group(1)), km.group(2) or "", zh.replace("[ОРИГ]", ""), ru, + translated)) + return out + + +def assert_measured(what, n, ctx): + """ГЕЙТ ПРОТИВ ЗЕЛЕНИ НА ПУСТОТЕ. Прибор, ничего не измеривший, обязан ОТКАЗАТЬ, а не напечатать + «порог не пробит»: пустой знаменатель читается как успех и ровно так и был прочитан 30.08, когда + парсер не понял формат `rerun` и вердикт «гипотеза не опровергнута» встал на нуле наблюдений.""" + if n <= 0: + print(f"\n⛔⛔ ПРИБОР ОТКАЗЫВАЕТ: {what} = 0 ({ctx}). Вердикт НЕ выносится: " + "нулевой знаменатель — это не «в пределах», это отсутствие замера.") + sys.exit(2) + + +def count_keys(zh, terms, mode): + """mode=native — longest-match (автомат движка); mode=substring — независимо на термин (июль).""" + hits = {} + if mode == "substring": + for t in terms: + n = sum(zh.count(k) for k in t["keys"]) + if n: + hits[t["id"]] = n + return hits + occupied = bytearray(len(zh)) + ordered = sorted( + (t for t in terms if t["keys"]), + key=lambda t: max(len(k) for k in t["keys"]), reverse=True, + ) + for t in ordered: + n = 0 + for key in sorted(t["keys"], key=len, reverse=True): + start = 0 + while (p := zh.find(key, start)) >= 0: + if not any(occupied[p:p + len(key)]): + occupied[p:p + len(key)] = b"\x01" * len(key) + n += 1 + start = p + 1 + if n: + hits[t["id"]] = n + return hits + + +def engine_state(con, book_id): + st = {} + for ch, idx, n_miss, detail, sticky, spoil, evict, exact in con.execute( + "select chapter, chunk_idx, n_postcheck_miss, postcheck_detail, n_sticky, " + "n_spoiler_blocked, n_evicted, n_exact_hits from retrieval_state where book_id=?", + (book_id,), + ): + items = json.loads(detail) if detail else [] + st[(ch, idx)] = { + "confirmed_miss": {d["src"] for d in items if d.get("disp") == "confirmed"}, + "ambiguous_miss": {d["src"] for d in items if d.get("disp") == "ambiguous"}, + "n_miss": n_miss, "sticky": sticky, "spoiler": spoil, + "evicted": evict, "exact": exact, + } + return st + + +def classify(term, ru): + """Промах уже установлен: ни одной принятой формы нет. Чем он является?""" + ru_low = ru.lower() + if any(f and f.lower() in ru_low for f in term["decl_forms"]): + return "decl_gap" # контроль: обязан быть 0 при union D24.4 + if term["stem"] and term["stem"].lower() in ru_low: + return "inflection_gap" # основа есть, формы в сиде нет — дефект ЗАСЕВА + return "genuine_absent" # КАНДИДАТ дрейфа, читать глазами + + +def audit(run, mode, verbose): + cfg = RUNS[run] + con = sqlite3.connect(f"file:{cfg['db']}?mode=ro", uri=True) + book_id = cfg["book_id"] or con.execute("select book_id from glossary limit 1").fetchone()[0] + terms = load_glossary(con, book_id) + by_id = {t["id"]: t for t in terms} + chunks = split_chunks(cfg["parallel"].read_text(encoding="utf-8")) + engine = engine_state(con, book_id) + + print("=" * 98) + print(f"АУДИТ УДЕРЖАНИЯ БАНКА — прогон `{run}` ({book_id}), режим матчинга: {mode}") + print(f" {cfg['note']}") + print(" правило решения — в шапке файла, записано ДО чисел") + print("=" * 98) + banned = sorted({k for t in terms for k in t["banned"]}) + print(f"записей {len(terms)}: confirmed {sum(t['confirmed'] for t in terms)}, " + f"ambiguous {sum(not t['confirmed'] for t in terms)} | " + f"ключей вне автомата (single-Han ban): {', '.join(banned) or '—'}") + print(f"чанков {len(chunks)}, из них переведено {sum(c[5] for c in chunks)}; " + f"строк retrieval_state {len(engine)}") + assert_measured("разобрано чанков параллельного файла", len(chunks), + f"формат {cfg['parallel'].name} не распознан") + assert_measured("переведённых чанков", sum(c[5] for c in chunks), "нечего проверять") + + fired, fired_w, miss_ch, miss_w = (defaultdict(int) for _ in range(4)) + klass = defaultdict(int) + kandidaty, rows = [], [] + agree = only_engine = only_mine = 0 + + for ch, idx, flag, zh, ru, translated in chunks: + if not translated: + continue # структурное исключение 1 + hits = count_keys(zh, terms, mode) + ru_low = ru.lower() + eng = engine.get((ch, idx), {}) + eng_miss_all = eng.get("confirmed_miss", set()) | eng.get("ambiguous_miss", set()) + for tid, n in hits.items(): + t = by_id[tid] + fired[tid] += 1 + fired_w[tid] += n + # ⚠ РЕГИСТР: сравнение регистро-НЕчувствительное. Прямое `in` дало ложный промах на + # 开窍 → «открытие апертуры» (гл.5/ч1): в тексте «Открытие апертуры удалось!» — термин + # верен, заглавная лишь начинает предложение. Поймано чтением 30.08. + held = any(f and f.lower() in ru_low for f in t["accepted"]) + if not held: + miss_ch[tid] += 1 + miss_w[tid] += n + k = classify(t, ru) + klass[(k, t["confirmed"])] += 1 + if k == "genuine_absent" and t["confirmed"]: + kandidaty.append((ch, idx, t["src"], t["dst"], t["type"], n)) + eng_miss = t["src"] in eng_miss_all + if not held and eng_miss: + agree += 1 + elif eng_miss and held: + only_engine += 1 + rows.append(("ЛОЖНАЯ ТРЕВОГА ГЕЙТА", ch, idx, t["src"], t["dst"], t["status"], n)) + elif not held and not eng_miss: + only_mine += 1 + rows.append(("СЛЕПОТА ГЕЙТА", ch, idx, t["src"], t["dst"], t["status"], n)) + + def block(conf, label): + ids = [i for i in fired if by_id[i]["confirmed"] == conf] + c_all, c_miss = sum(fired[i] for i in ids), sum(miss_ch[i] for i in ids) + w_all, w_miss = sum(fired_w[i] for i in ids), sum(miss_w[i] for i in ids) + print(f"\n--- {label} ---") + print(f" пар (термин, чанк) со сработавшим ключом : {c_all}") + print(f" из них промах (presence-провал) : {c_miss}" + + (f" ⇒ presence {1 - c_miss / c_all:.1%}" if c_all else "")) + print(f" вхождений ключа всего / промахнуто : {w_all} / {w_miss}" + + (f" ⇒ occurrence {1 - w_miss / w_all:.1%}" if w_all else "")) + return (w_miss / w_all) if w_all else 0.0, c_all, c_miss + + weighted, conf_all, conf_miss = block(True, "CONFIRMED — ОСНОВНОЙ ЭНДПОЙНТ") + assert_measured("пар CONFIRMED со сработавшим ключом", conf_all, + "ни один подписанный ключ не сработал — матчер или данные не те") + block(False, "AMBIGUOUS — справка: обязательности движок не объявлял") + + print("\n--- классификация промахов (июльская номенклатура, для сверки) ---") + for k in ("decl_gap", "inflection_gap", "genuine_absent"): + c, a = klass[(k, True)], klass[(k, False)] + note = {"decl_gap": "контроль union D24.4 — обязан быть 0", + "inflection_gap": "дефект ЗАСЕВА (мн.ч.), не дрейф", + "genuine_absent": "КАНДИДАТ дрейфа — читать глазами"}[k] + print(f" {k:<16} confirmed {c:>4} ambiguous {a:>4} — {note}") + eff_num = conf_all - klass[("genuine_absent", True)] - klass[("decl_gap", True)] + if conf_all: + print(f" эффективная консистентность CONFIRMED = {eff_num}/{conf_all} = {eff_num / conf_all:.1%}" + " (читатель видит верный термин: инфлексия вне сида ему не мешает)") + + print(f"\n--- метрика B: ядро (топ-{TOP_N} по весу ключа среди CONFIRMED) ---") + core = sorted((i for i in fired if by_id[i]["confirmed"]), + key=lambda i: fired_w[i], reverse=True)[:TOP_N] + core_fail = [] + print(f" {'термин':<34}{'вес':>6}{'чанков':>8}{'промах':>8}{'доля':>7}") + for i in core: + share = miss_ch[i] / fired[i] + if share >= CORE_MISS_MAX: + core_fail.append(by_id[i]["src"]) + print(f" {by_id[i]['src'] + ' → ' + by_id[i]['dst']:<34}{fired_w[i]:>6}{fired[i]:>8}" + f"{miss_ch[i]:>8}{share:>6.0%}{' ⛔' if share >= CORE_MISS_MAX else ''}") + + print("\n--- ВТОРОЙ КОНТУР: сверка с движковым пост-чеком ---") + print(f" согласие : {agree}") + print(f" ⚠ ложная тревога гейта (форма В ТЕКСТЕ есть): {only_engine}" + " ← ожидаемо: базы писаны ДО фикса D24.4") + print(f" ⚠ слепота гейта (промах, гейт молчит) : {only_mine}") + if verbose and rows: + print(f" {'класс':<24}{'гл':>4}{'чанк':>6} {'src':<12}{'dst':<30}{'статус':<10}{'вес':>5}") + for kind, ch, idx, src, dst, status, n in rows: + print(f" {kind:<24}{ch:>4}{idx:>6} {src:<12}{dst:<30}{status:<10}{n:>5}") + + if kandidaty: + print(f"\n--- КАНДИДАТЫ ДРЕЙФА (genuine_absent, confirmed): {len(kandidaty)} — ЧИТАТЬ ГЛАЗАМИ ---") + print(f" {'гл':>4}{'чанк':>6} {'src':<12}{'dst':<30}{'тип':<10}{'вес':>5}") + for ch, idx, src, dst, typ, n in kandidaty: + print(f" {ch:>4}{idx:>6} {src:<12}{dst:<30}{typ:<10}{n:>5}") + + print("\n--- зоны, которые прибор НЕ судит (печатаются, а не заминаются) ---") + print(f" чанков без перевода (гейт покрытия) : {sum(1 for c in chunks if not c[5])}") + print(f" sticky-переносов (инерция сцены, A5) : {sum(e['sticky'] for e in engine.values())}") + print(f" вытеснено бюджетом токенов : {sum(e['evicted'] for e in engine.values())}") + print(f" спойлер-блокировок : {sum(e['spoiler'] for e in engine.values())}") + + print("\n" + "=" * 98) + a_fail = weighted > WEIGHTED_MISS_MAX + print(f"МЕТРИКА A: взвешенная доля промахов CONFIRMED = {weighted:.2%} при пороге " + f"{WEIGHTED_MISS_MAX:.0%} ⇒ {'⛔ ПОРОГ ПРОБИТ' if a_fail else 'в пределах'}") + print(f"МЕТРИКА B: терминов ядра с долей промахов >= {CORE_MISS_MAX:.0%}: {len(core_fail)}" + f"{' — ' + ', '.join(core_fail) if core_fail else ''} ⇒ " + f"{'⛔ ПОРОГ ПРОБИТ' if core_fail else 'в пределах'}") + print("ВЫВОД (строгий presence): гипотеза «банк держит» в СИЛЬНОЙ форме " + + ("⛔ ЛОЖНА" if (a_fail or core_fail) else "не опровергнута")) + print("⚠ Границы: терминная половина Вопроса 0 · июльский профиль · редактор glm-5, не боевой " + "deepseek-v4-pro.") + print("=" * 98) + + +def main(): + ap = argparse.ArgumentParser(description="аудит удержания банка, $0") + ap.add_argument("--run", choices=[*RUNS, "both"], default="both") + ap.add_argument("--match", choices=["native", "substring"], default="native", + help="native — автомат движка; substring — как независимый июльский скрипт") + ap.add_argument("--verbose", action="store_true", help="печатать построчную сверку с гейтом") + a = ap.parse_args() + for run in ([*RUNS] if a.run == "both" else [a.run]): + audit(run, a.match, a.verbose) + print() + return 0 + + +if __name__ == "__main__": + sys.exit(main())