Freeze the panel-0 pre-registration and its counter result: register the foreign-phase arm E6, build the blind source etalon, and name three defects found in the instrument itself
This commit is contained in:
parent
1ccd5e4edf
commit
bedc39a751
4 changed files with 2167 additions and 7 deletions
|
|
@ -3982,3 +3982,351 @@ DeepSeek, отношение от пина не зависит, обе пары,
|
|||
⛔ **Открытый денежный вопрос:** 12 клеток умерли по таймауту и записаны с нулевой ценой; списал ли
|
||||
вендор токены за оборванное соединение, по диску не определить. Потенциально невидимый кассе
|
||||
расход $0.1–0.3.
|
||||
|
||||
---
|
||||
|
||||
## Д32 — ⭐ ПАНЕЛЬ-0: «АРХИТЕКТУРА × СЛАБЫЙ ЖИЛЕЦ». ПРЕ-РЕГ, ЗАПИСАН ДО СБОРКИ ПАКЕТОВ
|
||||
|
||||
> ⚠ **Номер Д31 намеренно пуст и зарезервирован** за вердиктом по вендорам: планом 22.08 (§7 п.6)
|
||||
> он выдан Шагу 2 — второму чтению панели `vendor2`, — и до метрики (ii) того шага вердикт печатать
|
||||
> запрещено. Дыра в нумерации есть бронь, а не потерянная секция.
|
||||
|
||||
Замер отвечает на **минимаксный вопрос владельца** дословно: «даже на худших вендорах наша
|
||||
архитектура должна отрабатывать лучше, чем худшие вендоры на другой архитектуре». На диске лежит
|
||||
природный эксперимент, за который уже заплачено: один и тот же жилец `glm-5` работает на одних и
|
||||
тех же 16 английских главах в ДВУХ топологиях — редактором поверх боевого черновика (`E6`) и
|
||||
однопроходкой (`RG`). Панель $0.
|
||||
|
||||
### Д32.0 ⛔ БЛОКЕР СБОРКИ, КОТОРЫЙ ПРИШЛОСЬ СНЯТЬ ПЕРВЫМ
|
||||
|
||||
`rol.py` арма `E6` **не знал**: единственное его вхождение в файле было комментарием об эррате.
|
||||
Клетки `dv-g-e6-*` куплены другим ригом (`en_axis.py --e6`) из кассы **ФД-G**, лежат под буквой
|
||||
чужой фазы и несут ДРУГУЮ схему — нет полей `pair`, `podacha`, `gen`, зато `role="editor3"`.
|
||||
|
||||
**Чем это было опасно, и это ровно класс «реализация не то, чем названа».** Неизвестное имя арма
|
||||
падало в последнюю ветку `read_texts` (`ZK.cell(ZK.tag(…))`), та строила несуществующий путь
|
||||
`dv-j-e6-<uid>`, файла не находила и возвращала **пустую строку**; `emit_read` выбрасывает главу,
|
||||
у которой пуст хоть один арм. Команда `--emit-read --panel=RG,E6,…` не упала бы и не соврала бы
|
||||
вслух — она собрала бы панель из скольких-то глав, и число это зависело бы от того, чьи файлы
|
||||
случайно нашлись рядом.
|
||||
|
||||
**Починка** (`rol.py`): заведён реестр `FOREIGN` — армы чужих фаз со своим префиксом, кассой,
|
||||
ригом-владельцем и ожидаемыми полями клетки; загрузчик `foreign_text` на любой беде даёт **СТОП**,
|
||||
а не пустую строку; чистая функция `foreign_defect` проверяет клетку **по содержимому**, а не по
|
||||
имени файла (имя мы строим сами, оно не удостоверяет ничего): `arm` · `uid` · `role` · модель ·
|
||||
`finish=stop` · непустой `content`. Единая точка `arm_text` перечисляет все четыре реестра, и имя,
|
||||
не известное ни одному, роняет сборку — прежде это была тихая пустая строка.
|
||||
|
||||
**Гейты проверены ФАЛЬСИФИКАЦИЕЙ, а не зеленью.** Селфтест кормит гейт шестью заведомо больными
|
||||
клетками, полученными мутацией настоящей: чужой арм · чужая глава · чужая роль · чужая модель ·
|
||||
`finish=length` · пустой `content`. Отдельно проверено, что при обезвреженном `foreign_defect`
|
||||
селфтест краснеет на шести пунктах, при снятой проверке пары — на одном, при возврате пустой
|
||||
строки вместо СТОПа — на одном. Гейт, зелёный только на здоровом входе, не сторожит ничего: ровно
|
||||
этим болел селфтест блокера Б8 — он фильтровал арм по наличию файла клетки, файлов не было, и
|
||||
гейт зеленел вхолостую.
|
||||
|
||||
### Д32.1 СОСТАВ ПАНЕЛИ И ЗАМОРОЖЕННЫЙ СПИСОК ГЛАВ
|
||||
|
||||
Тег панели **новый**; существующие теги не пере-эмитируются (пере-эмиссия с другим составом армов
|
||||
переписала бы раскладку уже прочитанной панели, и ответы стали бы мусором молча).
|
||||
|
||||
| арм | что это | модель | промт | потолок вывода | клеток |
|
||||
|---|---|---|---|---|---|
|
||||
| `ZD` | голый черновик | `deepseek-v4-flash` | translator, 3268 зн. | 32000 | 16/16 |
|
||||
| `Z0` | черновик → боевой редактор | `deepseek-v4-pro` | editor, **4404 зн.** | **16000** | 16/16 |
|
||||
| `ZF` | он же, ВТОРАЯ генерация (пол) | `deepseek-v4-pro` | editor, **4404 зн.** | **32000** | 16/16 |
|
||||
| `E6` | черновик → редактор `glm-5` | `glm-5` | editor, **4404 зн.** | **16000** | 16/16 |
|
||||
| `RG` | однопроходка-роль | `glm-5` | onepass, 7909 зн. | 32000 | 16/16 |
|
||||
|
||||
⭐ **Системный промт `Z0`, `ZF` и `E6` — ПОБАЙТНО ОДИН** (сверено исполнением). Значит:
|
||||
* `Z0 ↔ E6` — **чистый контраст жильца**: топология, промт, вход и черновик тождественны, меняется
|
||||
только модель;
|
||||
* `Z0 ↔ ZF` — **пол**: тот же запрос, вторая генерация;
|
||||
* `E6 ↔ RG` — **контраст топологии при одном жильце**, тот самый минимаксный.
|
||||
|
||||
Шестнадцать глав — пересечение, полное у всех пяти армов. Список заморожен ДО чтения (порядок —
|
||||
детерминированный отбор захода Д17):
|
||||
|
||||
```
|
||||
f2274720c9 53f1a12dff b065a92dc0 26c3bff1d4 49ca859c94 100b088f71 197f98eb61 d3237e1dea
|
||||
eefdade2c3 27cb3a7e69 3bd6481182 8e50448cea 90a20cb443 001e6ac4eb c3517980c7 5a8f48d24a
|
||||
```
|
||||
|
||||
**Объём: 26 243 знака исходника = 7.89 главы Гу.** Панель бесплатная и потому идёт на 7.89, а не
|
||||
на стандартные 6 глав Гу: стандарт заведён для ПОКУПАЕМЫХ ранговых панелей. За все 80 клеток уже
|
||||
заплачено $0.906 (`ZD` 0.016 · `Z0` 0.124 · `ZF` 0.668 · `RG` 0.046 · `E6` 0.052).
|
||||
|
||||
### Д32.2 ШАПКА: ЖИЛЕЦ УРАВНЕН, И УРАВНЕН МЕХАНИЗМОМ, А НЕ СОВПАДЕНИЕМ
|
||||
|
||||
Сверено по клеткам, 16 из 16 у обоих армов: модель `glm-5` (и `model_returned` та же) ·
|
||||
`finish=stop` · **`reasoning_tokens=0`**.
|
||||
|
||||
⚠ И причина нуля названа кодом, а не наблюдением: у `RG` объявленный уровень `THINK_LEVEL["RG"]
|
||||
= "off"`, то есть подавление ростера остаётся; `E6` покупался прямым `ROSTER.call_kwargs` без
|
||||
переопределения. На проводе у обоих **`extra_body: {"thinking": {"type": "disabled"}}`** —
|
||||
одно и то же наше подавление. Это блокер Б7 в обоих армах СРАЗУ, и именно поэтому он их не
|
||||
разводит: конфаундер общий. `temperature: 0.3` у обоих; честно ли Z.ai её принимает — по-прежнему
|
||||
НЕ ПРОВЕРЕНО (дыра ревью §4).
|
||||
|
||||
### Д32.3 ⛔ ЧТО В ЭТОЙ ПАНЕЛИ НЕ УРАВНЕНО — НАЗЫВАЕТСЯ ДО ЗАМЕРА, А НЕ ПОСЛЕ
|
||||
|
||||
1. **Объём инструкции. `RG` 7909 знаков против 4404 у `E6` — ×1.80.** Контраст топологии
|
||||
неизбежно несёт на себе и промт: у однопроходки и у редактора не может быть одного промта.
|
||||
⇒ читать вывод только так: «топология ВМЕСТЕ с её промтом». Направление конфаундера играет
|
||||
ЗА однопроходку (в эксп-21 разница объёма ×1.92 работала на длинный промт), поэтому проигрыш
|
||||
`RG` был бы выводом СИЛЬНЕЕ объявленного, а выигрыш — слабее.
|
||||
2. **⛔ Потолки вывода РАЗНЫЕ, и это новая находка, ревью 21.08 её не несёт.** `Z0` и `E6` куплены
|
||||
под 16000, `ZF` и `RG` — под 32000. Асимметрия внутри самой пары пола `Z0/ZF`. Обрывов нет
|
||||
(все `finish=stop`), но **у `Z0` максимум `completion_tokens` = 15 835 при потолке 16 000 —
|
||||
99.0%**: распределение длины размышления у половин пола цензуровано по-разному. Селфтест
|
||||
захода, объявленный сторожем («ZF = ТОТ ЖЕ запрос, что у боевой связки»), сличает только
|
||||
`msgs` и к телу вызова слеп — тот же класс В7.
|
||||
3. **`RG` черновика не видит вовсе** — он однопроходка. «Буфер» здесь означает не «второй проход»,
|
||||
а «дорогая чужая модель сделала черновик, а `glm-5` только правил».
|
||||
4. **Цена клетки `ZF` впятеро выше `Z0`** при том же промте и модели — следствие ценового пина
|
||||
DeepSeek (Б11), а не свойство арма. Замера не касается: панель бесплатна.
|
||||
5. **Ни `E6`, ни `RG` не хранят `call_kwargs` и `reasoning_text`** — провенанс восстановлен по
|
||||
коду покупки и по токенам, прямого доказательства провода на диске нет (долг Шага 6).
|
||||
|
||||
### Д32.4 ПЕРВИЧНЫЙ ЭНДПОЙНТ — ДЕФЕКТ-КЛАССЫ, А НЕ МЕСТА
|
||||
|
||||
**Почему не места.** Мощностная таблица консилиума 22.08: на английской паре эффект в ОДНО место
|
||||
стоит 68 глав Гу, в половину места — 271. Панель-0 — 7.89. ⇒ **ранговый эндпойнт этой панели
|
||||
объявлен ОПИСАТЕЛЬНЫМ ЗАРАНЕЕ**, до всякого числа. Несущим объявляется счётный: дефект-класс,
|
||||
ломающийся в 20–30% глав при ~0 у компаратора, разрешается уже на 25–30 единицах.
|
||||
|
||||
Прибор — `eval/dovodka/schet.py`, $0, детерминированный. Четыре класса объявлены ЗДЕСЬ и до снятия:
|
||||
|
||||
| класс | что считается |
|
||||
|---|---|
|
||||
| **род** | форма 1 л. ед. ч. прош. вр. с неверным родом. Три подкласса, не пересекаются: **«эталон»** — пол голоса внутреннего рассказа по окну установлен, форма ему противоречит; **«разнобой»** — пол по окну НЕ устанавливается, а арм ставит в одном слое ОБА рода (дефект внутренний, исходника для него не требуется; дефектом считается МЕНЬШИНСТВО форм); **«речь»** — род, не принадлежащий ни одному объявленному цитируемому говорящему |
|
||||
| **язык** | латиница в русском выходе, КРОМЕ объявленных эталоном иноязычных вставок и римских цифр |
|
||||
| **приём** | авторский типографский приём исходника (разрядка `P A T I E N C E`), потерянный выходом |
|
||||
| **банк** | термины банка, чья канонная форма в выходе встречается реже, чем в исходнике (`canon_gaps`) |
|
||||
|
||||
⚠ **Класс «приём» применим к ОДНОЙ главе из шестнадцати** (`197f98eb61`, `P A T I E N C E`) — это
|
||||
анекдот, а не класс, и он объявляется описательным ДО того, как будет посчитан.
|
||||
|
||||
**⛔ Отбор форм 1 л. ужесточён после проверки исполнением, и первая редакция была негодной.** Она
|
||||
брала любую форму на `-л/-ла` с местоимением «я» в окне ±40 знаков — и считала третьи лица
|
||||
(«Габриэль пожал плечами») и СУЩЕСТВИТЕЛЬНЫЕ на `-л` («Пьющий пепел»). На главе `f2274720c9` она
|
||||
давала 2ж/5м там, где настоящих форм три и все мужские. **Предварительный сигнал «D0 3/1 · E6 3/2 ·
|
||||
RG 0/5», записанный в план 22.08, снят этим ригом и потому НЕ ЯВЛЯЕТСЯ показанием** — он
|
||||
воспроизводится только негодной редакцией счётчика. В пакеты читателей он не идёт (запрет плана),
|
||||
и в выводы тоже не идёт.
|
||||
|
||||
Починка: местоимение обязано стоять в синтаксической связи — сразу перед глаголом (через 0–3
|
||||
служебных слова) или сразу после него. Правило разделения «повествование / прямая речь» —
|
||||
механическое и объявлено здесь: абзац-реплика начинается с тире, речь идёт до первого закрывающего
|
||||
тире перед словами автора, всё после него — повествование.
|
||||
|
||||
### Д32.4а ⛔ ЭТАЛОН СНЯТ ПО ИСХОДНИКУ И СЛЕПО К ПЕРЕВОДАМ
|
||||
|
||||
Требование п.3а плана: «эталон грепов фиксируется ДО снятия, иначе их можно подогнать под
|
||||
увиденное». Исполнено буквально. Каждую из 16 глав читали **три независимых агента** — строитель
|
||||
и два скептика, одному из которых выдан мандат ОПРОВЕРГНУТЬ вывод. Каждому был открыт **ровно один
|
||||
файл**: английский исходник его главы. Русские переводы, код зоны, отчёт и `~/books` были закрыты
|
||||
запретом в промте.
|
||||
|
||||
**⚠⚠ ПЕРВЫЙ КРУГ ЭТАЛОНА ОКАЗАЛСЯ НЕГОДЕН, И ПОЙМАЛА ЭТО ЕГО ЖЕ СВЕРКА.** Вопрос был задан про
|
||||
«пол рассказчика», а книга **рамочная**: хронист Jean-François пишет третьим лицом, а рассказ
|
||||
героя идёт от первого лица внутри кавычек, часто без закрывающей. Половина строителей сочла
|
||||
внутренний рассказ «прямой речью» и объявила первое лицо отсутствующим — **на восьми главах из
|
||||
шестнадцати**; скептики это сняли на четырёх, а на остальных согласились с ошибкой. Круг
|
||||
переспрошен с НАЗВАННОЙ рамкой. Переводы агентам оставались закрыты в обоих кругах — то есть
|
||||
исправлена ПОСТАНОВКА ВОПРОСА, а не подогнан ответ; оба круга лежат в `eval/dovodka/etalon-panel0.json`
|
||||
целиком, расхождение видно построчно.
|
||||
|
||||
⚠ **Девиация, которую надо назвать вслух.** Строя счётчик, я до фриза эталона видел выход армов на
|
||||
нескольких главах — иначе нельзя было поймать, что первая редакция отбора форм считает
|
||||
существительные. Это увиденное повлияло на ПРИБОР (регекс ужесточён) и не могло повлиять на
|
||||
ЭТАЛОН: эталон снят агентами, которым выход был закрыт. Считаю девиацию объявленной и не
|
||||
компенсируемой иначе.
|
||||
|
||||
⚠ **Расход агент-сессий.** Построение эталона стоило 48 сессий в первом круге и 48 во втором. В
|
||||
журнале `runs.py` они НЕ записаны: журнал держит судейские и читательские сессии, привязанные к
|
||||
токенам панелей, а эти агенты не судят ничего и токенов не держат. Число называю здесь, чтобы оно
|
||||
не пропало: при капе 200 и израсходованных 98 читательских это отдельная статья, и её учёт —
|
||||
вопрос владельцу, а не решение сессии.
|
||||
|
||||
### Д32.5 ПРАВИЛО РЕШЕНИЯ, ОБЪЯВЛЕННОЕ ДО ЧИСЕЛ
|
||||
|
||||
* **Первичная величина — число ГЛАВ С ≥1 ФАТАЛОМ** любого из четырёх классов (агрегат).
|
||||
* **Тест — парный знаковый по главам, `p < 0.05`.** Реализация одна на зону (`zsud._sign_p`),
|
||||
второй не заводится.
|
||||
* По-классовые разбивки — **вторичные**, печатаются рядом и вердикта не несут.
|
||||
* Ранги слепого чтения — **описательные**, объявлено выше.
|
||||
* Вердикт формулируется как **«интеракция архитектура × жилец на ИСПЫТАННЫХ жильцах»**, НЕ как
|
||||
«минимакс»: худший редактор не искался, связка двухвендорна (черновик — DeepSeek).
|
||||
|
||||
### Д32.6 СВЕРКИ ДО ЧТЕНИЯ
|
||||
|
||||
1. **Исходник у всех армов побайтно один — и это доказано построением, а не сличением.**
|
||||
`uid = sha1(source.strip())[:10]` (`bakeoff.uid_of`), а совпадающий uid у клетки `E6` и клетки
|
||||
`RG` есть на всех 16 главах. Разный исходник дал бы разный uid.
|
||||
2. **Потолки вывода напечатаны** — таблица Д32.1, асимметрия названа в Д32.3.
|
||||
3. **Промт `E6` распечатан `--wire`-аналогом и прочитан.** Аналог собран **кодом его собственного
|
||||
рига** (`en_axis`: исходник, черновик `D0`, `PR.editor_msgs` пары en, тело вызова строит
|
||||
РОСТЕР НАПРЯМУЮ), а не пере-написан в `rol.py`, — иначе гейт показывал бы, что я думаю о
|
||||
клетке, а не что ушло в модель. Прочитано вслух: промт **пар-корректен** (несёт «МЕРЫ
|
||||
английской традиции… НЕ пересчитывай в метрические», «КАЛЬКИ en→ru», французский слой) —
|
||||
блокера Б1 здесь нет; глоссарий доехал (`Dior → «Диор»`, `Reyne → «Рейн»`, `Celene → «Селена»`
|
||||
и ещё пять). ⚠ И ровно на этом промте видно то, что Шаг 7 плана называет второй половиной мины:
|
||||
**в глоссарии есть имена и нет ни одного поля пола** — «Рейн» приезжает к модели как строка,
|
||||
без указания, он это или она.
|
||||
4. `--verify-read` обязан вернуть **EXIT=0**; ключ пишется ДО ответов; раскладка своя на каждую
|
||||
главу; один читатель на главу.
|
||||
|
||||
### Д32.7 ⛔ ТРИ ДЕФЕКТА НАШЛИСЬ В САМОМ ПРИБОРЕ, И НАШЛИ ИХ ГЛАЗА, А НЕ ГЕЙТ
|
||||
|
||||
Порядок работы был такой: блокер сборки снят → эталон снят агентами вслепую → классы и правило
|
||||
решения объявлены → счётчик прогнан → **улики прочитаны глазами по каждому попаданию** → найдены
|
||||
три дефекта прибора → починены → счётчик прогнан заново. Печатаю оба свода, чтобы читатель судил
|
||||
сам: правка, сделанная ПОСЛЕ первых чисел, обязана предъявлять и старые числа.
|
||||
|
||||
1. **Правило слоёв не знало ВОЗОБНОВЛЕНИЯ реплики.** Русская вёрстка диалога чередуется:
|
||||
«— речь, — слова автора. — речь». Первая редакция обрывала речь на первом тире и всё дальнейшее
|
||||
звала повествованием. Цена замерена: на главе `49ca859c94` реплика Жан-Франсуа «…как и я,
|
||||
мадемуазель Кастия. Нет, я сбежал в Огюстен…» ложилась в ПОВЕСТВОВАНИЕ, и мужская форма
|
||||
мужского говорящего шла в дефекты рассказчицы — **у всех трёх армов сразу**. Прибор производил
|
||||
брак вместо того, чтобы его находить.
|
||||
2. **Речь проверялась даже там, где пол голоса не установлен.** На `5a8f48d24a` собственная
|
||||
реплика рассказчицы «— Где я была?» уходила в дефекты как «род ж не принадлежит ни одному
|
||||
говорящему главы»: в списке говорящих стоят только Персиваль и Вульфрик (оба «м»), а сама
|
||||
рассказчица в него не входит по определению. Теперь неустановленный пол голоса выключает
|
||||
проверку речи целиком.
|
||||
3. **Класс «язык» был слеп к латинице В РАЗРЯДКУ.** Регекс требовал двух букв подряд, а
|
||||
`P A T I E N C E` — восемь одиночных через пробел. Поймано чтением: **`RG` единственный
|
||||
сохранил авторскую разрядку главы `197f98eb61` — и сохранил её ПО-АНГЛИЙСКИ.** Свод хвалил бы
|
||||
арм за непереведённое слово. ⚠ И это снимает предварительный сигнал плана «разрядку потерял сам
|
||||
черновик → потеряли и `E6`, и `RG`»: разрядку сохранил `ZF` (кириллицей, верно) и `RG`
|
||||
(латиницей, не переведя), потеряли `ZD`, `Z0` и `E6`.
|
||||
|
||||
Каждая починка закрыта ФАЛЬСИФИКАЦИЕЙ в селфтесте (подсаженный брак обязан ловиться, чистый текст
|
||||
обязан давать ноль), и правки двигали числа в ОБЕ стороны: первая убрала дефекты у всех армов,
|
||||
третья добавила один арму `RG`.
|
||||
|
||||
**⛔ И ЧЕТВЁРТАЯ НАХОДКА — НЕ В ПРИБОРЕ, А В ДВИЖКЕ ЗАМЕРА: ЛАТЕНТНАЯ МИНА ДВУХ КОНТУРОВ ВЫСТРЕЛИЛА.**
|
||||
Селфтест `rol.py` с 22.08 печатал предупреждение: в процессе живут ДВА экземпляра `contour`, и
|
||||
`wire()` настраивал только один — второй оставался на китайской паре; тогда было записано «до
|
||||
данных не добралось». 23.08 добралось. Скрипт, честно позвавший `PAIRS["en"]["wire"]()` и сразу
|
||||
спросивший текст БЕСПЛАТНОГО арма, получил **пустую строку**: `free_text` идёт в контур ЗАХОДА, тот
|
||||
искал китайскую клетку английской главы, не нашёл и вернул пусто с одним `warning`. В своде это
|
||||
читалось бы как «у арма на этой главе мало дефектов» — пустой текст дефектов не имеет по построению.
|
||||
Починено в корне: пара включается в ОБОИХ экземплярах одной командой (`rol._wire`), проверка
|
||||
селфтеста оставлена сторожем на случай третьего экземпляра. Дополнительно счётчик **выбрасывает из
|
||||
счёта главу с пустым текстом хоть у одного арма и печатает это вслух** — арм с пропавшей клеткой
|
||||
не имеет права выглядеть лучшим.
|
||||
|
||||
### Д32.8 ⭐ РЕЗУЛЬТАТ СЧЁТНОГО ПРИБОРА
|
||||
|
||||
**Попаданий (не глав), 16 глав, четыре класса. Слева — до починок прибора, справа — после.**
|
||||
|
||||
| класс | ZD | Z0 | ZF | RG | E6 |
|
||||
|---|---|---|---|---|---|
|
||||
| **род** | 2 → **0** | 2 → **0** | 2 → **0** | 8 → **5** | 4 → **1** |
|
||||
| **язык** | 2 → **2** | 0 → **0** | 0 → **0** | 8 → **9** | 0 → **0** |
|
||||
| **приём** | 1 → **1** | 1 → **1** | 0 → **0** | 0 → **0** | 1 → **1** |
|
||||
| **банк** | 25 → **25** | 9 → **9** | 7 → **7** | 10 → **10** | 9 → **9** |
|
||||
| **фаталов всего** | 30 → **28** | 12 → **10** | 9 → **7** | 26 → **24** | 14 → **11** |
|
||||
| **глав с ≥1 фаталом** | 13 → **13** | 10 → **8** | 8 → **6** | 11 → **12** | 10 → **8** |
|
||||
|
||||
**Частота глав с дефектом и 95% интервал Уилсона** (требование плана §4: по редким классам печатать
|
||||
частоту с интервалом, а не вердикт):
|
||||
|
||||
| класс | ZD | Z0 | ZF | RG | E6 |
|
||||
|---|---|---|---|---|---|
|
||||
| род | 0/16 [0.00;0.19] | 0/16 [0.00;0.19] | 0/16 [0.00;0.19] | **2/16** [0.03;0.36] | 1/16 [0.01;0.28] |
|
||||
| язык | 2/16 [0.03;0.36] | 0/16 [0.00;0.19] | 0/16 [0.00;0.19] | **4/16** [0.10;0.49] | 0/16 [0.00;0.19] |
|
||||
| приём | 1/16 | 1/16 | 0/16 | 0/16 | 1/16 |
|
||||
| банк | 12/16 [0.51;0.90] | 7/16 [0.23;0.67] | 6/16 [0.18;0.61] | 7/16 [0.23;0.67] | 7/16 [0.23;0.67] |
|
||||
| **все** | 13/16 [0.57;0.93] | 8/16 [0.28;0.72] | 6/16 [0.18;0.61] | 12/16 [0.51;0.90] | 8/16 [0.28;0.72] |
|
||||
|
||||
**Парный знаковый тест. ① — правило, объявленное планом (бинарно «глава с ≥1 фаталом»);
|
||||
② — вторичное, число фаталов на главу.**
|
||||
|
||||
| контраст | ① бинарно | ② счётно |
|
||||
|---|---|---|
|
||||
| `Z0 − E6` (**один промт, разные жильцы**) | 0 глав различия, p=1.0000 | −1, 1 глава, p=1.0000 |
|
||||
| `RG − E6` (**один жилец, разные топологии**) | +4, 4 главы, **все за `E6`**, p=0.1250 | +13, 6 глав, **все за `E6`**, **p=0.0312 РАЗЛИЧИМО** |
|
||||
| `Z0 − RG` | −4, 4 главы, все за `Z0`, p=0.1250 | −14, 6 глав, все за `Z0`, **p=0.0312 РАЗЛИЧИМО** |
|
||||
| `ZF − RG` | −6, 6 глав, все за `ZF`, **p=0.0312 РАЗЛИЧИМО** | −17, 8 глав, все за `ZF`, **p=0.0078 РАЗЛИЧИМО** |
|
||||
| `ZD − Z0` | +5, 5 глав, все за `Z0`, p=0.0625 | +18, 10 глав, все за `Z0`, **p=0.0020 РАЗЛИЧИМО** |
|
||||
| `ZD − E6` | +5, 5 глав, все за `E6`, p=0.0625 | +17, 9 глав, все за `E6`, **p=0.0039 РАЗЛИЧИМО** |
|
||||
| `ZD − RG` | +1, 5 глав, 3 за `RG`, p=1.0000 | +4, 11 глав, 7 за `RG`, p=0.5488 |
|
||||
|
||||
⚠ **ПОТОЛОК МОЩНОСТИ, ПЕЧАТАЕТСЯ РЯДОМ С КАЖДЫМ «НЕ РАЗЛИЧИМО».** При всех расхождениях в одну
|
||||
сторону двусторонний знаковый p = 2/2^k, то есть **p<0.05 недостижим, пока глав с ненулевой
|
||||
разницей меньше шести**. Классы «род» (2 главы) и «язык» (4 главы) эта панель не разрешит НИКОГДА,
|
||||
как бы односторонен ни был результат. «Не различимо» здесь означает «прибор слеп», а не «армы
|
||||
одинаковы» — и по построению, а не по невезению.
|
||||
|
||||
### Д32.9 ⭐⭐ ЧТО ЭТО ЗНАЧИТ — И ЧЕГО НЕ ЗНАЧИТ
|
||||
|
||||
**Читается это так.** Возьмите один и тот же слабый жилец `glm-5` с погашенным размышлением.
|
||||
|
||||
* Поставьте его **редактором** над боевым черновиком (`E6`) — он даёт **8 глав из 16 с браком,
|
||||
1 попадание по роду, 0 по языку**. Боевой `deepseek-v4-pro` в той же топологии, с тем же
|
||||
побайтно промтом (`Z0`) — **8 из 16, 0 и 0**. Между ними **НОЛЬ глав различия** по первичному
|
||||
правилу и −1 фатал по вторичному. То есть по классу А смена жильца внутри архитектуры прибором
|
||||
НЕ ловится.
|
||||
* Поставьте того же жильца **однопроходкой** (`RG`) — **12 глав из 16, 5 попаданий по роду,
|
||||
9 по языку**. Против `E6` разница в 6 главах, **все шесть в одну сторону**, p=0.0312 по
|
||||
вторичному правилу; против второй генерации связки `ZF` — 6 глав, все в одну сторону, p=0.0312
|
||||
**по ПЕРВИЧНОМУ**.
|
||||
|
||||
⇒ **Буфер существует и замерен.** Топология «дорогой чужой черновик → слабый жилец редактором»
|
||||
снимает у слабого жильца 4 из 5 дефектов рода и все 9 дефектов языка по сравнению с тем же
|
||||
жильцом, делающим работу в одиночку.
|
||||
|
||||
⛔⛔ **И ЧЕГО ЭТО НЕ ЗНАЧИТ — ПЯТЬ ОГОВОРОК, КАЖДАЯ СНИМАЕТ ЧАСТЬ ВЫВОДА.**
|
||||
|
||||
1. **Первичное правило контраст `RG−E6` НЕ ЗАКРЫВАЕТ** (p=0.1250). Закрывает вторичное и закрывает
|
||||
`ZF−RG`. По конъюнкции, которой фаза меряет вендоров, это «направление единогласно, значимость
|
||||
не достигнута». Печатать «однопроходка различимо хуже редактора» нельзя; печатать «различимо
|
||||
хуже второй генерации связки» — можно.
|
||||
2. **Это интеракция «архитектура × жилец на ИСПЫТАННЫХ жильцах», а не минимакс.** Худший редактор
|
||||
не искался. `glm-5` — не «худший вендор», а единственный второй, которого мы пробовали в этой
|
||||
роли.
|
||||
3. **Буфер ДВУХВЕНДОРЕН.** Черновик делает `deepseek-v4-flash`. Архитектура, которая «спасает
|
||||
слабого жильца», сама содержит клетку другого вендора — и на неё же опирается. Утверждение
|
||||
«наша архитектура вендоро-независима» этим замером НЕ проверено.
|
||||
4. **Буфер чинит только то, что черновик донёс, и это видно прямо здесь.** Разрядку
|
||||
`P A T I E N C E` черновик `ZD` потерял → потерял и `E6`. Уцелела она у `ZF` (вторая генерация
|
||||
связки, кириллицей) и у `RG` (латиницей, не переведя). Контракт «что обязан гарантировать
|
||||
черновик» — не украшение, а условие работы буфера.
|
||||
5. **Конфаундер объёма промта играет ЗА однопроходку** (×1.80), значит её проигрыш — вывод
|
||||
более сильный, чем объявленный, а не более слабый. Это единственная оговорка, работающая в
|
||||
пользу вывода, и потому её мало.
|
||||
|
||||
**Отдельно — то, чего никто не заказывал, а замер отдал бесплатно.** Черновик `deepseek-v4-flash`
|
||||
за $0.00055 за клетку дал **0 дефектов рода на всех 16 главах** — там, где `glm-5` однопроходкой за
|
||||
впятеро большие деньги дал 5. Дешёвая модель в узкой роли не уступила дорогой в широкой.
|
||||
⚠ Но она же дала **25 потерь банка против 9–10 у всех прочих** и 2 непереведённых английских слова:
|
||||
черновик хорош ровно там, где его страхуют.
|
||||
|
||||
### Д32.10 ⭐⭐ НАХОДКА ПОД ВОПРОС 0, ПОЛУЧЕННАЯ ЗА $0
|
||||
|
||||
Эталон, снятый по исходнику вслепую, дал побочный результат, которого никто не искал, и он бьёт
|
||||
прямо в метавопрос владельца.
|
||||
|
||||
**В 2 окнах из 8, где есть рассказчик от первого лица, его пол ПО ОКНУ НЕ УСТАНАВЛИВАЕТСЯ вовсе.**
|
||||
Голос главы `5a8f48d24a` — Селена (цепочка «обращение по имени → немедленное „Где я была?“»
|
||||
замыкается жёстко), но во всём окне нет ни одного `she` о ней; голос `c3517980c7` — Гейб, и точно
|
||||
так же ни одного `he`. Это установлено ТРЕМЯ независимыми чтениями каждой главы, причём двоим из
|
||||
трёх был выдан мандат опровергнуть.
|
||||
|
||||
⇒ **Пайплайн, который видит только главу, по построению слеп примерно в четверти случаев.** Он не
|
||||
«ошибается» — ему нечем узнать. Узнать может ТОЛЬКО книжная память: тот же голос (Селена) ведёт
|
||||
ещё три окна нашей выборки, и там пол устанавливается прямо. То же у Габриэля: 3 окна из 4.
|
||||
|
||||
⛔ **А в банке полей пола НЕТ.** `bank-en.json` несёт имена и канонные формы — «Рейн», «Селена»,
|
||||
«Диор» — и ни одного поля рода; это видно на распечатке промта `E6` (Д32.6 п.3). То есть
|
||||
механизм, который единственный мог бы закрыть эти 25% глав, в наших прогонах **не существовал**.
|
||||
|
||||
⇒ Это первое ПРЯМОЕ и бесплатное подтверждение тезиса Шага 7 плана: «решение „банк чинит пол“ не
|
||||
имеет ни одного замера», и «пол плывёт у всех» его не опровергает — механизма просто не было.
|
||||
Теперь известно и сколько он стоил бы: без него ~25% первололичных глав неразрешимы в принципе.
|
||||
|
||||
⚠ Оценка 2 из 8 снята на маленькой выборке: интервал Уилсона [0.07; 0.59]. Число — порядок
|
||||
величины, не константа.
|
||||
|
|
|
|||
1139
eval/dovodka/etalon-panel0.json
Normal file
1139
eval/dovodka/etalon-panel0.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -83,12 +83,29 @@ CORE = ZONE / "prompts" / "onepass-core.md"
|
|||
MAX_OUT = 32000 # как в заходе Д17: обрыв на потолке стоит денег и не даёт данных
|
||||
|
||||
# ── ПАРА КАК ДАННЫЕ. Ни одна ветка ниже не спрашивает «а это китайский?» ──────────────────────
|
||||
# ⛔⛔ МИНА ДВУХ ЭКЗЕМПЛЯРОВ КОНТУРА — ОБЕЗВРЕЖЕНА ЗДЕСЬ, А НЕ ОБЪЯВЛЕНА. Селфтест с 22.08 печатал
|
||||
# предупреждение: в процессе живут ДВА независимых `contour` (свой у `rol` через `en_axis`, свой у
|
||||
# `zakhod`), и `wire()` настраивал ТОЛЬКО первый — второй оставался на китайской паре. Тогда было
|
||||
# записано «до данных не добралось». 23.08 добралось: скрипт, честно позвавший `PAIRS["en"]["wire"]()`
|
||||
# и сразу спросивший текст БЕСПЛАТНОГО арма, получил ПУСТУЮ СТРОКУ — `free_text` идёт в контур
|
||||
# ЗАХОДА, тот искал китайскую клетку английской главы, не нашёл и вернул пусто с одним warning'ом.
|
||||
# В своде это выглядело бы как «у арма на этой главе мало дефектов»: пустой текст дефектов не имеет.
|
||||
# ⇒ Пара включается в ОБОИХ экземплярах одной командой. Проверка селфтеста остаётся сторожем на
|
||||
# случай третьего экземпляра.
|
||||
_WIRE_OWN = {"zh": lambda: C.configure(PARA.ZH), "en": EN.wire}
|
||||
|
||||
|
||||
def _wire(p: str) -> None:
|
||||
_WIRE_OWN[p]()
|
||||
ZK.PAIRS[p]["wire"]()
|
||||
|
||||
|
||||
PAIRS = {
|
||||
"zh": {"letter": "m", "phase": "ФД-M", "onepass": "A2",
|
||||
"wire": lambda: C.configure(PARA.ZH), "units": lambda: C.units_ok(),
|
||||
"wire": lambda: _wire("zh"), "units": lambda: C.units_ok(),
|
||||
"para": lambda: PARA.ZH},
|
||||
"en": {"letter": "n", "phase": "ФД-N", "onepass": "E2",
|
||||
"wire": EN.wire, "units": EN.units, "para": lambda: PARA.EN},
|
||||
"wire": lambda: _wire("en"), "units": EN.units, "para": lambda: PARA.EN},
|
||||
}
|
||||
# ── ВОЗВРАТ АВТОРСКИХ АБЗАЦЕВ ИСХОДНИКУ ──────────────────────────────────────────────────────
|
||||
# ⚠⚠ ПОЧЕМУ ЭТО ОТДЕЛЬНЫЙ АРМ, А НЕ ПОЧИНКА ЭКСТРАКТОРА. Наш английский экстрактор
|
||||
|
|
@ -444,6 +461,101 @@ def chosen(p: str) -> list[dict]:
|
|||
return ZK.chosen(p)
|
||||
|
||||
|
||||
# ── АРМЫ ЧУЖИХ ФАЗ: клетки куплены ДРУГИМ РИГОМ, и схема клетки у них СВОЯ ────────────────────
|
||||
# ⛔ ПОЧЕМУ НЕ СТРОКА В `ARMS`. `ARMS` — реестр того, что покупает ЭТОТ риг: из него растут тег
|
||||
# (`dv-n-…` на en), смета, гейт покупки и объявленный уровень размышления. `E6` ничем этим не
|
||||
# является: его 16 клеток куплены `en_axis.py --e6` из кассы ФД-G, лежат под буквой ЧУЖОЙ фазы
|
||||
# (`dv-g-e6-…`) и несут ДРУГИЕ поля — нет ни `pair`, ни `podacha`, ни `gen`, зато `role="editor3"`.
|
||||
# Вписать его в `ARMS` значило бы объявить покупаемым то, что этот риг покупать не умеет и не должен.
|
||||
#
|
||||
# ⚠⚠ ЧЕМ ЭТО БЫЛО ОПАСНО ДО ПРАВКИ — И ЭТО РОВНО КЛАСС «РЕАЛИЗАЦИЯ НЕ ТО, ЧЕМ НАЗВАНА». Имя, не
|
||||
# известное ни одному реестру, падало в последнюю ветку `read_texts` (`ZK.cell(ZK.tag(…))`), та
|
||||
# строила `dv-j-e6-<uid>`, файла не находила и возвращала ПУСТУЮ СТРОКУ. А `emit_read` выбрасывает
|
||||
# главу, у которой пуст хоть один арм. То есть команда `--panel=RG,E6,Z0,ZF` не упала бы и не
|
||||
# соврала бы вслух — она собрала бы панель из СКОЛЬКИХ-ТО глав, и число это зависело бы от того,
|
||||
# чьи файлы случайно нашлись. ⇒ ниже всё ГРОМКО: неизвестный арм — СТОП, негодная клетка — СТОП.
|
||||
def _e6_wire(uid: str) -> tuple[list[dict], str, dict]:
|
||||
"""WIRE-АНАЛОГ `E6` — СОБСТВЕННЫМ КОДОМ ЕГО РИГА (`en_axis.cmd_e6`), не пере-написанным здесь.
|
||||
|
||||
⛔ Пере-написанный промт показал бы, что Я думаю о клетке, а не что ушло в модель, — то есть
|
||||
ровно тот самый класс, против которого гейт `--wire` и заведён. Поэтому вызываются те же
|
||||
функции в том же порядке: исходник и черновик берутся у `en_axis`, сообщения собирает боевой
|
||||
`PR.editor_msgs` пары en, тело вызова строит РОСТЕР НАПРЯМУЮ (`ROSTER.call_kwargs`), а не
|
||||
`rol.call_kwargs` — у `E6` иной потолок вывода, и подменять его нашим значило бы напечатать
|
||||
не тот провод (класс В7: несущий контраст, собранный под разными потолками).
|
||||
"""
|
||||
src = next(u["source"] for u in EN.units() if u["uid"] == uid)
|
||||
msgs = C.PR.editor_msgs(src, EN.base_draft(uid), EN._law(src), pair="en-ru") # noqa: SLF001
|
||||
return msgs, EN.EDITOR2, ROSTER.call_kwargs(EN.EDITOR2, msgs)
|
||||
|
||||
|
||||
FOREIGN = {
|
||||
"E6": {"pair": "en", "prefix": "dv-g-e6-", "model": "glm-5", "role": "editor3",
|
||||
"phase": "ФД-G", "rig": "eval/dovodka/en_axis.py --e6", "wire": _e6_wire,
|
||||
"что": "ВТОРОЙ редактор glm-5 поверх боевого черновика D0 (носитель гипотезы H-4)"},
|
||||
}
|
||||
|
||||
|
||||
def foreign_defect(d: dict, arm: str, uid: str) -> str:
|
||||
"""Почему клетка чужой фазы НЕ годится в панель. Пусто = годна.
|
||||
|
||||
⚠ Проверки идут по СОДЕРЖИМОМУ клетки, а не по имени файла: имя мы строим сами, и оно не
|
||||
удостоверяет ничего. Клетка обязана сама сказать, чей она арм, какой главы, какой модели и
|
||||
какой роли, — иначе под именем `E6` в панель уедет что угодно, что лежит по этому пути.
|
||||
⚠ Функция ЧИСТАЯ (dict → строка) намеренно: только так селфтест может уронить её на заведомо
|
||||
больном входе, не подкладывая порченых файлов в `~/books`.
|
||||
"""
|
||||
spec = FOREIGN[arm]
|
||||
got_model = d.get("model_returned") or d.get("model")
|
||||
for what, got, want in (("arm", d.get("arm"), arm), ("uid", d.get("uid"), uid),
|
||||
("role", d.get("role"), spec["role"]),
|
||||
("модель", got_model, spec["model"])):
|
||||
if got != want:
|
||||
return f"поле {what}={got!r}, ожидалось {want!r}"
|
||||
if d.get("finish") != "stop":
|
||||
return f"finish={d.get('finish')!r} — клетка оборвана либо мертва"
|
||||
if not (d.get("content") or "").strip():
|
||||
return "content пуст при finish=stop — деньги списаны, текста нет (класс В5)"
|
||||
return ""
|
||||
|
||||
|
||||
def foreign_text(p: str, arm: str, uid: str) -> str:
|
||||
"""Текст арма чужой фазы. Любая беда — СТОП, а НЕ пустая строка: молча выброшенная глава
|
||||
меняет состав панели, объявленный пре-регом, и делает это невидимо."""
|
||||
spec = FOREIGN[arm]
|
||||
if spec["pair"] != p:
|
||||
raise SystemExit(f"⛔ арм {arm} существует только на паре «{spec['pair']}», запрошена «{p}»"
|
||||
f" — его клетки купил {spec['rig']} из кассы {spec['phase']}")
|
||||
f = OUT / f"{spec['prefix']}{uid}.json"
|
||||
if not f.exists():
|
||||
raise SystemExit(f"⛔ клетки {arm} для главы {uid} нет на диске ({f.name}). Этот риг её "
|
||||
f"купить не может — она из кассы {spec['phase']}, риг {spec['rig']}")
|
||||
d = json.loads(f.read_text(encoding="utf-8"))
|
||||
why = foreign_defect(d, arm, uid)
|
||||
if why:
|
||||
raise SystemExit(f"⛔ клетка {arm}/{uid} НЕГОДНА: {why}")
|
||||
return d["content"]
|
||||
|
||||
|
||||
def arm_text(p: str, arm: str, uid: str) -> str:
|
||||
"""ОДНА точка правды о том, откуда берётся текст арма. Неизвестное имя — СТОП.
|
||||
|
||||
⚠ Прежде неизвестное имя молча уходило в риг захода и возвращало пусто (см. блок выше). Теперь
|
||||
реестры перечислены явно, и опечатка в `--panel=` роняет сборку, а не режет панель.
|
||||
"""
|
||||
if arm in FOREIGN:
|
||||
return foreign_text(p, arm, uid)
|
||||
if arm in FREE:
|
||||
return free_text(p, arm, uid)
|
||||
if arm in ARMS:
|
||||
return cell(tag(p, arm, uid))
|
||||
if arm in ZK.ARMS:
|
||||
return ZK.cell(ZK.tag(p, arm, uid))
|
||||
raise SystemExit(f"⛔ арм {arm!r} не известен НИ ОДНОМУ реестру: бесплатные {FREE}, "
|
||||
f"покупаемые этим ригом {tuple(ARMS)}, армы захода {ZK.ARMS}, "
|
||||
f"чужих фаз {tuple(FOREIGN)}. Панель НЕ собирается")
|
||||
|
||||
|
||||
# ⛔⛔ БЛОКЕРЫ Б6/Б7 РЕВЬЮ 21.08. Ростер кодирует ОДНИМ режимом `none` («ручку не шлём») две
|
||||
# ПРОТИВОПОЛОЖНЫЕ вещи: у `deepseek-v4-pro` вендор-дефолт — размышление HIGH, у `grok-4.3` — LOW
|
||||
# (квирк-бюллетень :60/:80). Значит «одинаковая конфигурация по ростеру» означает РАЗНОЕ
|
||||
|
|
@ -817,6 +929,40 @@ def cmd_selftest() -> int:
|
|||
else:
|
||||
ok, d = True, ""
|
||||
ck(f"Б6 {a}: на провод уходит объявленный уровень «{lvl}»", ok, d)
|
||||
|
||||
# ── ШАГ 1 п.0 ПЛАНА 22.08: АРМЫ ЧУЖИХ ФАЗ (`E6`) ────────────────────────────────────────
|
||||
# ⚠ ГЕЙТ ПРОВЕРЯЕТСЯ НА ЗАВЕДОМО БОЛЬНОМ ВХОДЕ. «Зелено на здоровом» не сторожит ничего —
|
||||
# ровно так селфтест Б8 был зелен вхолостую на арме, у которого файлов не было вовсе. Больной
|
||||
# вход делается МУТАЦИЕЙ НАСТОЯЩЕЙ клетки, чтобы проверка не разъехалась с реальной схемой.
|
||||
for fa, spec in FOREIGN.items():
|
||||
pp = spec["pair"]
|
||||
PAIRS[pp]["wire"]()
|
||||
us = chosen(pp)
|
||||
have = [u["uid"] for u in us if (OUT / f"{spec['prefix']}{u['uid']}.json").exists()]
|
||||
ck(f"{fa}: клетка есть на КАЖДОЙ главе пары {pp}", len(have) == len(us),
|
||||
f"{len(have)} из {len(us)}")
|
||||
if not have:
|
||||
continue
|
||||
fu = have[0]
|
||||
real = json.loads((OUT / f"{spec['prefix']}{fu}.json").read_text(encoding="utf-8"))
|
||||
ck(f"{fa}: настоящая клетка гейт ПРОХОДИТ", not foreign_defect(real, fa, fu),
|
||||
foreign_defect(real, fa, fu))
|
||||
for name, patch in (("чужой арм", {"arm": "RG"}), ("чужая глава", {"uid": "0" * 10}),
|
||||
("чужая роль", {"role": "onepass"}),
|
||||
("чужая модель", {"model_returned": "grok-4.3"}),
|
||||
("обрыв finish=length", {"finish": "length"}),
|
||||
("пустой content", {"content": " "})):
|
||||
ck(f"{fa}: гейт РОНЯЕТ больной вход «{name}»",
|
||||
bool(foreign_defect(dict(real, **patch), fa, fu)))
|
||||
other = next((x for x in PAIRS if x != pp), pp)
|
||||
for name, fn in ((f"{fa} на чужой паре {other}", lambda: foreign_text(other, fa, fu)),
|
||||
("неизвестное имя арма", lambda: arm_text(pp, "ZZZ", fu))):
|
||||
try:
|
||||
fn()
|
||||
ck(f"{fa}: {name} — это СТОП", False, "вернулось молча, без исключения")
|
||||
except SystemExit:
|
||||
ck(f"{fa}: {name} — это СТОП", True)
|
||||
|
||||
print(f"\n{'ЗАМЕР ГОДЕН К ПОКУПКЕ' if not bad else f'ПРОВАЛОВ: {bad}'}")
|
||||
return bad
|
||||
|
||||
|
|
@ -880,9 +1026,7 @@ def read_texts(p: str, uid: str) -> dict:
|
|||
"""Тексты панели чтения. Пусто у любого арма — глава в пакет не идёт целиком."""
|
||||
out = {"_src": next(u["source"] for u in chosen(p) if u["uid"] == uid)}
|
||||
for a in READ_PANEL:
|
||||
t = free_text(p, a, uid) if a in FREE else (
|
||||
cell(tag(p, a, uid)) if a in ARMS else ZK.cell(ZK.tag(p, a, uid)))
|
||||
out[a] = t
|
||||
out[a] = arm_text(p, a, uid)
|
||||
return out
|
||||
|
||||
|
||||
|
|
@ -1027,14 +1171,19 @@ def wire_dump(p: str, only: str = "") -> int:
|
|||
("Z7 канон-фиксер", C.fix_msgs(src, d, ["КАНОН: термин X"]), ARMS_MODEL["Z0"]),
|
||||
("ZP однопроходка-склейка", C.a2_msgs(src), ARMS_MODEL["Z0"])]
|
||||
cases += [(f"{a} однопроходка-роль", rol_msgs(p, src, a), ARMS[a][0]) for a in ARMS]
|
||||
# ⚠ Армы чужих фаз печатаются ТЕМ ЖЕ гейтом и рядом с остальными: панель их смешивает, значит
|
||||
# и читать их провод надо в одном месте. Тело вызова у них СВОЁ — см. `_e6_wire`.
|
||||
cases = [(n, m, mo, None) for n, m, mo in cases]
|
||||
cases += [(f"{fa} {sp['что']} · касса {sp['phase']}", *sp["wire"](u["uid"]))
|
||||
for fa, sp in FOREIGN.items() if sp["pair"] == p]
|
||||
base = None
|
||||
for name, msgs, model in cases:
|
||||
for name, msgs, model, own_kw in cases:
|
||||
arm = name.split()[0]
|
||||
if only and only != arm:
|
||||
continue
|
||||
sysm = "\n\n".join(x["content"] for x in msgs if x["role"] == "system")
|
||||
base = base or len(sysm)
|
||||
kw = call_kwargs(model, msgs, arm)
|
||||
kw = own_kw if own_kw is not None else call_kwargs(model, msgs, arm)
|
||||
print("\n" + "=" * 100)
|
||||
print(f"АРМ {name} · модель {model} · размышление: {THINK_LEVEL.get(arm, 'как у ростера')}")
|
||||
print(f"инструкции {len(sysm)} знаков (×{len(sysm) / base:.2f} к первому арму) · "
|
||||
|
|
|
|||
524
eval/dovodka/schet.py
Normal file
524
eval/dovodka/schet.py
Normal file
|
|
@ -0,0 +1,524 @@
|
|||
#!/usr/bin/env python3
|
||||
"""СЧЁТЧИКИ ДЕФЕКТ-КЛАССОВ — первичный прибор панели-0. $0, детерминированно.
|
||||
|
||||
⚠ ЗАЧЕМ ОТДЕЛЬНЫЙ ПРИБОР, КОГДА ЕСТЬ СЛЕПОЕ ЧТЕНИЕ. Ранговый прибор при наших размерах безнадёжен:
|
||||
эффект в ОДНО место стоит 40 глав Гу на китайской паре и 68 на английской (мощностная таблица
|
||||
консилиума 22.08). Панель-0 — 7.8 главы Гу, её MDE ≈ 2 места. ⇒ ранговый эндпойнт этой панели
|
||||
объявлен ОПИСАТЕЛЬНЫМ ЗАРАНЕЕ, а несущим становится СЧЁТНЫЙ: дефект-класс, ломающийся в 20–30%
|
||||
глав при ~0 у компаратора, ловится уже на 25–30 единицах.
|
||||
|
||||
⚠ И ЭТО ЖЕ ДОЛГ ШАГА 5.2 ПЛАНА. Покупать 15 глав Гу под вопрос о кросс-главной консистентности,
|
||||
не имея счётчиков, значит повторить класс В20 ревью — оплаченные клетки, которых не прочёл ни один
|
||||
прибор. Счётчики отлаживаются ЗДЕСЬ, на уже купленном, и только потом едут в дорогой замер.
|
||||
|
||||
ЧТО СЧИТАЕТСЯ (четыре класса, объявлены ДО снятия — пре-рег Д32.0):
|
||||
|
||||
род формы 1 л. ед. ч. прош. вр. с неверным родом. ТРИ непересекающихся подкласса:
|
||||
«эталон» — пол голоса внутреннего рассказа по окну УСТАНОВЛЕН, форма ему противоречит;
|
||||
«разнобой» — пол по окну НЕ устанавливается, но арм ставит в одном слое ОБА рода
|
||||
(источник-независимый дефект: винить за выбор нельзя, за два сразу можно);
|
||||
«речь» — род, не принадлежащий ни одному объявленному цитируемому говорящему
|
||||
язык непереведённый кусок исходного языка (латиница), КРОМЕ римских цифр и вставной
|
||||
французской РЕПЛИКИ (её и только её боевой пар-мандат велит оставить чужой; имена,
|
||||
топонимы и «шато/мадемуазель» он велит передавать кириллицей — латиница на их месте
|
||||
есть тот самый дефект). На всю панель такая реплика ОДНА: «A farmer's son, oui.»
|
||||
приём авторский типографский приём исходника (разрядка «P A T I E N C E»), ПОТЕРЯННЫЙ выходом
|
||||
банк термины банка, чья канонная форма в выходе встречается реже, чем в исходнике
|
||||
|
||||
⛔⛔ ЭТАЛОН СНЯТ ПО ИСХОДНИКУ И СЛЕПО К ПЕРЕВОДАМ, И ИНАЧЕ ОН НЕ СТОИТ НИЧЕГО. Требование п.3а
|
||||
плана: «эталон грепов фиксируется ДО снятия, иначе их можно подогнать под увиденное». Исполнено
|
||||
буквально: каждую главу читали три независимых агента (строитель + два скептика, один из них с
|
||||
мандатом ОПРОВЕРГНУТЬ), которым был открыт РОВНО ОДИН файл — английский исходник главы; русские
|
||||
переводы, код зоны и отчёт им были закрыты. Расхождения разрешены руками и названы в отчёте
|
||||
поимённо. Эталон лежит рядом файлом и фризится своим коммитом ДО сборки пакетов.
|
||||
|
||||
⚠⚠ ПЕРВЫЙ КРУГ ЭТАЛОНА БЫЛ НЕГОДЕН, И ЭТО ПОЙМАЛА ЕГО ЖЕ СВЕРКА. Вопрос был задан про «пол
|
||||
рассказчика», а книга РАМОЧНАЯ: хронист пишет в третьем лице, а рассказ героя идёт от первого
|
||||
лица внутри кавычек. Половина строителей сочла внутренний рассказ «прямой речью» и объявила
|
||||
первое лицо отсутствующим — на восьми главах из шестнадцати. Круг переспрошен с НАЗВАННОЙ рамкой;
|
||||
переводы агентам оставались закрыты в обоих кругах, то есть исправлена постановка вопроса, а не
|
||||
подогнан ответ. Оба круга лежат в эталоне, расхождение видно.
|
||||
|
||||
⚠ ПРИБОР ПЕЧАТАЕТ СТРОКИ, А НЕ ТОЛЬКО ЧИСЛА (требование п.3а). Счёт без улик не проверяем никем,
|
||||
включая меня: ровно так «13 катастроф» эксп-13 оказались пятью-шестью при оверфлаге судьи.
|
||||
|
||||
⚠ ЧЕГО ПРИБОР НЕ УМЕЕТ, И ЭТО ОБЪЯВЛЕНО ДО ЗАМЕРА:
|
||||
· рассказчик 1-го лица без имени лукапу по банку недоступен — род берётся из эталона главы,
|
||||
а не из банка (полей пола в `bank-en.json` нет вовсе, см. Шаг 7 плана);
|
||||
· разделение «повествование против прямой речи» механическое (см. `_speech_spans`) и у разных
|
||||
армов может лечь по-разному — поэтому классификация КАЖДОГО попадания печатается, а число
|
||||
попаданий, сменивших класс между армами, выносится в свод отдельной строкой;
|
||||
· класс «приём» на этой панели применим к ОДНОЙ главе из шестнадцати — это анекдот, а не класс,
|
||||
и он объявляется описательным до того, как будет посчитан.
|
||||
|
||||
Запуск: schet.py --selftest гейты прибора, включая ФАЛЬСИФИКАЦИЮ на подсаженном браке
|
||||
schet.py --report [армы…] таблица по главам и агрегат со знаковым тестом
|
||||
schet.py --hits <АРМ> [uid] все попадания с контекстом — для чтения глазами
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path("/home/ubuntu/projects/textmachine")
|
||||
ZONE = Path(__file__).resolve().parent
|
||||
for d in ("dovodka", "editor_tier", "tenant_panel", "role_topology", "bank_arbitration"):
|
||||
sys.path.insert(0, str(REPO / "eval" / d))
|
||||
|
||||
|
||||
def _load(name: str, path: Path):
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
sys.modules[name] = mod
|
||||
spec.loader.exec_module(mod)
|
||||
return mod
|
||||
|
||||
|
||||
# ⚠ Тексты армов берутся У `rol`, а не читаются заново: там уже стоят реестры источников и гейт
|
||||
# годности клетки чужой фазы. Второй загрузчик разъехался бы с ним молча.
|
||||
R = _load("rol_schet", ZONE / "rol.py")
|
||||
ZS = _load("zsud_schet", ZONE / "zsud.py") # знаковый тест — один на зону, не второй
|
||||
|
||||
ETALON = ZONE / "etalon-panel0.json"
|
||||
PAIR = "en"
|
||||
PANEL = ("ZD", "Z0", "ZF", "RG", "E6")
|
||||
|
||||
# ── КЛАСС «РОД» ──────────────────────────────────────────────────────────────────────────────
|
||||
# ⚠ ОТБОР ФОРМ УЖЕСТОЧЁН ПОСЛЕ ПРОВЕРКИ ИСПОЛНЕНИЕМ, И ПЕРВАЯ РЕДАКЦИЯ БЫЛА НЕГОДНОЙ. Она брала
|
||||
# любую форму на -л/-ла, у которой в окне ±40 знаков стояло «я», — и считала третьи лица
|
||||
# («Габриэль пожал плечами») и СУЩЕСТВИТЕЛЬНЫЕ на -л («Пьющий пепел»). На главе `f2274720c9` она
|
||||
# давала 2ж/5м там, где настоящих форм 1 л. три и все мужские. ⇒ местоимение обязано стоять в
|
||||
# синтаксической связи: сразу перед глаголом (через 0–3 служебных слова) или сразу после него.
|
||||
_ADV = (r"(?:не|уже|бы|тоже|также|всё|ещё|лишь|только|чуть|едва|снова|вновь|даже|так|там|тут|"
|
||||
r"потом|тогда|сразу|вдруг|наконец|почти|словно|будто|нехотя|молча|тихо|быстро)\s+")
|
||||
_FORM = r"[а-яё]+л(?:ась|ся|а)?"
|
||||
_RE_ROD = (re.compile(rf"\bя\s+(?:{_ADV}){{0,3}}({_FORM})\b", re.I),
|
||||
re.compile(rf"\b({_FORM})\s+(?:{_ADV}){{0,1}}я\b", re.I))
|
||||
_FEM = ("ла", "лась")
|
||||
|
||||
# ── КЛАСС «ЯЗЫК» ─────────────────────────────────────────────────────────────────────────────
|
||||
_RE_LAT = re.compile(r"[A-Za-z][A-Za-z'’\-]+")
|
||||
# ⚠ Римская цифра — НЕ кусок исходного языка: «Людовик VIII» законен в русской прозе и стоит во
|
||||
# всех армах одинаково. Считать её браком значило бы вписать в счётчик константу шума.
|
||||
_RE_ROMAN = re.compile(r"^[IVXLCDM]+$")
|
||||
# ⛔ ЛАТИНИЦА В РАЗРЯДКУ — ОТДЕЛЬНЫЙ ДЕТЕКТОР, И ЕГО НЕ БЫЛО. Первая редакция `_RE_LAT` требовала
|
||||
# от слова ДВУХ букв подряд, а `P A T I E N C E` — восемь одиночных букв через пробел: класс «язык»
|
||||
# был к ней слеп по построению. Поймано чтением улик: `RG` единственный сохранил авторскую разрядку
|
||||
# главы `197f98eb61` — и сохранил её ПО-АНГЛИЙСКИ, то есть приём удержал, а слово не перевёл.
|
||||
# Без этого детектора свод печатал бы «приём цел, языка нет» и хвалил бы арм за непереведённое слово.
|
||||
_RE_LAT_SP = re.compile(r"(?:(?<=\s)|^)(?:[A-Za-z]\s){2,}[A-Za-z](?=[\s.,!?:;—»]|$)")
|
||||
|
||||
# ── КЛАСС «ПРИЁМ» ────────────────────────────────────────────────────────────────────────────
|
||||
# Разрядка: три и более одиночных буквы через пробел. Ищется В ВЫХОДЕ по факту наличия ЛЮБОЙ
|
||||
# разрядки, а не по совпадению букв: русское слово другое, приём тот же.
|
||||
_RE_SPACED = re.compile(r"(?:(?<=\s)|^)(?:[A-Za-zА-Яа-яЁё]\s){2,}[A-Za-zА-Яа-яЁё](?=[\s.,!?:;—]|$)")
|
||||
|
||||
|
||||
def etalon() -> dict:
|
||||
if not ETALON.exists():
|
||||
raise SystemExit(f"⛔ эталона нет ({ETALON.name}). Он снимается ПО ИСХОДНИКУ и ДО замера — "
|
||||
"снять его после того, как счётчик показал числа, значит подогнать")
|
||||
return json.loads(ETALON.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def _paras(t: str) -> list[tuple[int, str]]:
|
||||
"""(смещение, абзац). Абзац — строка: русская вёрстка реплик поабзацная."""
|
||||
out, pos = [], 0
|
||||
for line in t.split("\n"):
|
||||
if line.strip():
|
||||
out.append((pos, line))
|
||||
pos += len(line) + 1
|
||||
return out
|
||||
|
||||
|
||||
def _speech_spans(t: str) -> list[tuple[int, int]]:
|
||||
"""Куски текста, лежащие в ПРЯМОЙ РЕЧИ, а не в повествовании.
|
||||
|
||||
⚠ Правило механическое и объявлено ДО замера: абзац-реплика начинается с тире; каждое
|
||||
«закрывающее» тире (`, — `, `. — `, `? — `, `! — `, `… — `) ПЕРЕКЛЮЧАЕТ слой. Куски чётные —
|
||||
речь, нечётные — слова автора, то есть повествование. Нет ни одного тире — речью считается
|
||||
весь абзац.
|
||||
|
||||
⛔⛔ ПЕРВАЯ РЕДАКЦИЯ БЫЛА НЕГОДНОЙ, И ПОЙМАЛО ЕЁ ЧТЕНИЕ УЛИК ГЛАЗАМИ, А НЕ ГЕЙТ. Она обрывала
|
||||
речь на ПЕРВОМ тире и всё дальнейшее звала повествованием — то есть не знала ВОЗОБНОВЛЕНИЯ
|
||||
реплики, а русская вёрстка диалога именно чередуется: «— речь, — слова автора. — речь». Цена
|
||||
ошибки замерена: на главе `49ca859c94` реплика Жан-Франсуа «…как и я, мадемуазель Кастия. Нет,
|
||||
я сбежал в Огюстен…» ложилась в ПОВЕСТВОВАНИЕ, и мужская форма мужского говорящего шла в
|
||||
дефекты рассказчицы — у ВСЕХ ТРЁХ армов сразу. Это класс «прибор производит брак вместо того,
|
||||
чтобы его находить»; общий знаменатель парного контраста не портил, но абсолютные числа врал.
|
||||
"""
|
||||
out = []
|
||||
for pos, line in _paras(t):
|
||||
s = line.lstrip()
|
||||
if not s.startswith(("—", "–", "-")):
|
||||
continue
|
||||
head = pos + (len(line) - len(s))
|
||||
cuts = [m.start() + 1 for m in re.finditer(r"[,.!?…»]\s*[—–]\s", s)]
|
||||
b = [0, *cuts, len(s)]
|
||||
out += [(head + b[k], head + b[k + 1]) for k in range(len(b) - 1) if k % 2 == 0]
|
||||
return out
|
||||
|
||||
|
||||
def rod_hits(text: str) -> list[dict]:
|
||||
"""Все формы 1 л. ед. ч. прош. вр. с родом, местом и классом «речь/повествование»."""
|
||||
sp = _speech_spans(text)
|
||||
seen, out = set(), []
|
||||
for rx in _RE_ROD:
|
||||
for m in rx.finditer(text):
|
||||
i = m.start(1)
|
||||
if i in seen:
|
||||
continue
|
||||
seen.add(i)
|
||||
w = m.group(1).lower()
|
||||
lo, hi = max(0, i - 60), min(len(text), m.end(1) + 30)
|
||||
out.append({"i": i, "слово": w,
|
||||
"род": "ж" if w.endswith(_FEM) else "м",
|
||||
"речь": any(a <= i < b for a, b in sp),
|
||||
"ктx": text[lo:hi].replace("\n", " ")})
|
||||
return sorted(out, key=lambda x: x["i"])
|
||||
|
||||
|
||||
def rod_defects(text: str, et: dict) -> list[dict]:
|
||||
"""Попадания, чей род ЭТАЛОНУ ПРОТИВОРЕЧИТ. Два ПОДКЛАССА, и они не пересекаются.
|
||||
|
||||
⚠⚠ ПОДКЛАСС «ЭТАЛОН» работает там, где пол голоса внутреннего рассказа ПО ОКНУ УСТАНОВЛЕН.
|
||||
Тогда каждая форма слоя повествования обязана нести именно его. Это сильная проверка.
|
||||
|
||||
⚠⚠ ПОДКЛАСС «РАЗНОБОЙ» работает там, где пол по окну НЕ устанавливается, — и он ИСТОЧНИК-
|
||||
НЕЗАВИСИМ. Окно в 1600 знаков часто не даёт улики вовсе; арм видел ровно то же окно и большего
|
||||
знать не мог, поэтому винить его за выбор рода нельзя. Но нельзя и не винить за ДВА рода
|
||||
сразу в одном слое одной главы: это ошибка внутренняя, для неё исходника не требуется. Ровно
|
||||
её владелец увидел сам («дрейф пола Рейн между отрывками, у всех четырёх армов») и пометил
|
||||
как то, «что счётчик не увидит». Теперь увидит.
|
||||
|
||||
⚠ ПРЯМАЯ РЕЧЬ проверяется СЛАБО и это объявляется: говорящих несколько, дефектом считается
|
||||
только род, не принадлежащий НИ ОДНОМУ объявленному говорящему. А если хоть у одного из них
|
||||
пол по окну не устанавливается, речь не проверяется ВОВСЕ — иначе законная форма неизвестного
|
||||
говорящего пошла бы в дефекты, и счётчик стал бы производить брак вместо того, чтобы его
|
||||
находить.
|
||||
"""
|
||||
voice = et.get("род_голоса")
|
||||
speakers = et.get("говорящие", [])
|
||||
allowed = ({voice} | {s.get("род") for s in speakers}) - {None, "unknown"}
|
||||
# ⛔ РЕЧЬ НЕ ПРОВЕРЯЕТСЯ, ЕСЛИ ХОТЬ ОДИН ГОВОРЯЩИЙ ГЛАВЫ НЕ ИМЕЕТ ОБЪЯВЛЕННОГО ПОЛА — включая
|
||||
# САМОГО РАССКАЗЧИКА. Первая редакция этого не делала, и вот что выходило: на главе
|
||||
# `5a8f48d24a` пол голоса по окну не устанавливается, в списке остались только Персиваль и
|
||||
# Вульфрик (оба «м»), и собственная реплика рассказчицы «— Где я была?» шла в дефекты как
|
||||
# «род ж не принадлежит ни одному говорящему главы». Прибор ловил не арм, а сам себя.
|
||||
speech_blind = voice in (None, "unknown") or any(
|
||||
s.get("род") in (None, "unknown") for s in speakers)
|
||||
bad, narr = [], []
|
||||
for h in rod_hits(text):
|
||||
if h["речь"]:
|
||||
if allowed and not speech_blind and h["род"] not in allowed:
|
||||
bad.append(dict(h, подкласс="речь",
|
||||
почему=f"род {h['род']} не принадлежит ни одному говорящему главы "
|
||||
f"{sorted(allowed)}"))
|
||||
continue
|
||||
narr.append(h)
|
||||
if voice in ("ж", "м") and h["род"] != voice:
|
||||
bad.append(dict(h, подкласс="эталон",
|
||||
почему=f"повествование: голос рассказа {voice}, форма {h['род']}"))
|
||||
if voice not in ("ж", "м") and len({h["род"] for h in narr}) > 1:
|
||||
# ⚠ Дефектом считается МЕНЬШИНСТВО форм, а не все: разнобой из 3ж+1м есть одна ошибка,
|
||||
# а не четыре, и мерить его надо так же, как подкласс «эталон», иначе агрегат перекосит.
|
||||
few = min({"ж", "м"}, key=lambda g: sum(1 for h in narr if h["род"] == g))
|
||||
for h in narr:
|
||||
if h["род"] == few:
|
||||
bad.append(dict(h, подкласс="разнобой",
|
||||
почему="в слое повествования одной главы стоят ОБА рода; пол по "
|
||||
"окну не устанавливается, поэтому дефект — меньшинство форм"))
|
||||
return bad
|
||||
|
||||
|
||||
def lang_defects(text: str, et: dict) -> list[dict]:
|
||||
"""Куски исходного языка. Законные иноязычные вставки эталона и римские цифры не в счёт."""
|
||||
# ⚠ БЕЛЫЙ СПИСОК — НЕ «всё французское», а РОВНО вставная реплика. Основание — боевой
|
||||
# пар-мандат `en-ru/translator.md`, секция ФРАНЦУЗСКИЙ СЛОЙ: имена и топонимы ТРАНСКРИБИРУЮТСЯ,
|
||||
# мадемуазель/месье/шато идут РУССКОЙ традицией галлицизмов, и чужими остаются только вставные
|
||||
# французские РЕПЛИКИ. Значит `château` или `Capitaine` латиницей в русском выходе есть тот
|
||||
# самый дефект, который класс ловит, а не законная вставка.
|
||||
ok = {w.lower() for ins in et.get("латиница_законна", []) for w in _RE_LAT.findall(ins)}
|
||||
out, taken = [], []
|
||||
for m in _RE_LAT_SP.finditer(text):
|
||||
w = m.group(0)
|
||||
flat = w.replace(" ", "")
|
||||
taken.append((m.start(), m.end()))
|
||||
if flat.lower() in ok or _RE_ROMAN.match(flat):
|
||||
continue
|
||||
lo, hi = max(0, m.start() - 45), min(len(text), m.end() + 25)
|
||||
out.append({"i": m.start(), "слово": w, "ктx": text[lo:hi].replace("\n", " "),
|
||||
"почему": "латиница В РАЗРЯДКУ: авторский приём удержан, слово НЕ переведено"})
|
||||
for m in _RE_LAT.finditer(text):
|
||||
w = m.group(0)
|
||||
if w.lower() in ok or _RE_ROMAN.match(w) or any(a <= m.start() < b for a, b in taken):
|
||||
continue
|
||||
lo, hi = max(0, m.start() - 45), min(len(text), m.end() + 25)
|
||||
out.append({"i": m.start(), "слово": w, "ктx": text[lo:hi].replace("\n", " "),
|
||||
"почему": "латиница вне вставной французской реплики"})
|
||||
return sorted(out, key=lambda x: x["i"])
|
||||
|
||||
|
||||
def priem_defects(text: str, et: dict) -> list[dict]:
|
||||
"""Авторский типографский приём исходника, которого в выходе НЕТ."""
|
||||
if not et.get("приёмы"):
|
||||
return []
|
||||
if _RE_SPACED.search(text):
|
||||
return []
|
||||
return [{"i": -1, "слово": "", "ктx": "; ".join(x["текст"] for x in et["приёмы"]),
|
||||
"почему": "разрядка исходника в выходе отсутствует"}]
|
||||
|
||||
|
||||
def bank_defects(src: str, text: str) -> list[dict]:
|
||||
return [{"i": -1, "слово": g, "ктx": "", "почему": "канонная форма банка не доехала"}
|
||||
for g in R.C.canon_gaps(src, text)]
|
||||
|
||||
|
||||
CLASSES = ("род", "язык", "приём", "банк")
|
||||
|
||||
|
||||
def defects(src: str, text: str, et: dict) -> dict:
|
||||
return {"род": rod_defects(text, et), "язык": lang_defects(text, et),
|
||||
"приём": priem_defects(text, et), "банк": bank_defects(src, text)}
|
||||
|
||||
|
||||
# ── СВОД ─────────────────────────────────────────────────────────────────────────────────────
|
||||
def collect(arms: tuple = PANEL) -> tuple[list, dict]:
|
||||
et_all = etalon()
|
||||
R.PAIRS[PAIR]["wire"]()
|
||||
us, data, empty = [], {}, []
|
||||
for u in R.chosen(PAIR):
|
||||
if u["uid"] not in et_all:
|
||||
continue
|
||||
tx = {a: R.arm_text(PAIR, a, u["uid"]) for a in arms}
|
||||
# ⛔ ГЛАВА С ПУСТЫМ ТЕКСТОМ ХОТЬ У ОДНОГО АРМА ИЗ СЧЁТА ВЫБРАСЫВАЕТСЯ, И ВСЛУХ. Пустой текст
|
||||
# дефектов не имеет по построению, то есть арм с пропавшей клеткой выглядел бы ЛУЧШИМ.
|
||||
# Ровно так мина двух контуров чуть не подарила черновику «чистую» главу (см. rol._wire).
|
||||
if not all(tx[a].strip() for a in arms):
|
||||
empty.append((u["uid"], [a for a in arms if not tx[a].strip()]))
|
||||
continue
|
||||
us.append(u)
|
||||
for a in arms:
|
||||
data[(u["uid"], a)] = defects(u["source"], tx[a], et_all[u["uid"]])
|
||||
if empty:
|
||||
print(f" ⛔ ГЛАВ ВЫБРОШЕНО ИЗ СЧЁТА (пустой текст у арма): {len(empty)}")
|
||||
for uid, aa in empty:
|
||||
print(f" {uid}: пусто у {aa}")
|
||||
return us, data
|
||||
|
||||
|
||||
def report(arms: tuple = PANEL) -> int:
|
||||
et_all = etalon()
|
||||
us, data = collect(arms)
|
||||
print(f"\n=== СЧЁТНЫЙ ПРИБОР ПАНЕЛИ-0 · пара {PAIR} · глав {len(us)} · армы {' '.join(arms)} ===")
|
||||
print("⚠ первичная величина — ЧИСЛО ГЛАВ С ≥1 ФАТАЛОМ (агрегат по четырём классам);\n"
|
||||
" по-классовые разбивки вторичны; ранги этой панели объявлены ОПИСАТЕЛЬНЫМИ заранее\n")
|
||||
for cl in CLASSES:
|
||||
print(f" {'глава':12s}" + "".join(f"{a:>6s}" for a in arms) + f" класс «{cl}»")
|
||||
for u in us:
|
||||
row = [len(data[(u["uid"], a)][cl]) for a in arms]
|
||||
mark = "" if len(set(row)) == 1 else " ←"
|
||||
print(f" {u['uid']:12s}" + "".join(f"{x:6d}" for x in row) + mark)
|
||||
print(f" {'ИТОГО':12s}"
|
||||
+ "".join(f"{sum(len(data[(u['uid'], a)][cl]) for u in us):6d}" for a in arms) + "\n")
|
||||
print(f" {'глава':12s}" + "".join(f"{a:>6s}" for a in arms) + " ВСЕ КЛАССЫ (фаталов)")
|
||||
tot = {a: 0 for a in arms}
|
||||
chap = {a: 0 for a in arms}
|
||||
for u in us:
|
||||
row = []
|
||||
for a in arms:
|
||||
n = sum(len(data[(u["uid"], a)][c]) for c in CLASSES)
|
||||
tot[a] += n
|
||||
chap[a] += n > 0
|
||||
row.append(n)
|
||||
print(f" {u['uid']:12s}" + "".join(f"{x:6d}" for x in row))
|
||||
print(f" {'фаталов':12s}" + "".join(f"{tot[a]:6d}" for a in arms))
|
||||
print(f" {'глав с ≥1':12s}" + "".join(f"{chap[a]:6d}" for a in arms) + f" из {len(us)}")
|
||||
# ⚠ Сколько попаданий сменило класс «речь/повествование» между армами — цена механического
|
||||
# правила. Печатается всегда, а не только когда мала.
|
||||
moved = 0
|
||||
for u in us:
|
||||
cls = [{h["слово"]: h["речь"] for h in rod_hits(R.arm_text(PAIR, a, u["uid"]))}
|
||||
for a in arms]
|
||||
keys = set().union(*cls)
|
||||
moved += sum(1 for k in keys if len({c[k] for c in cls if k in c}) > 1)
|
||||
print(f"\n ⚠ попаданий, сменивших класс «речь/повествование» между армами: {moved}")
|
||||
|
||||
def _test(title: str, val) -> None:
|
||||
print(f"\n {title}")
|
||||
for i, a in enumerate(arms):
|
||||
for b in arms[i + 1:]:
|
||||
d = [val(u["uid"], a) - val(u["uid"], b) for u in us]
|
||||
nz = [x for x in d if x]
|
||||
p = ZS._sign_p(d) # noqa: SLF001
|
||||
print(f" {a} − {b}: сумма {sum(d):+4d} · глав с разницей {len(nz):2d} "
|
||||
f"(в пользу {b}: {sum(1 for x in nz if x > 0)}) · p={p:.4f}"
|
||||
f"{' РАЗЛИЧИМО' if p < 0.05 else ''}")
|
||||
|
||||
def _all(uid: str, a: str) -> int:
|
||||
return sum(len(data[(uid, a)][c]) for c in CLASSES)
|
||||
|
||||
# ⛔ ПЕРВИЧНОЕ ПРАВИЛО ПЕЧАТАЕТСЯ ПЕРВЫМ И ПЕЧАТАЕТСЯ ВСЕГДА, каким бы ни вышло. Оно объявлено
|
||||
# п.3б плана ДО чисел: величина — БИНАРНАЯ «есть ли в главе хоть один фатал», тест — парный
|
||||
# знаковый. Подменить его на счётный, увидев, что счётный красивее, значит подогнать правило
|
||||
# под результат; счётный идёт НИЖЕ и назван вторичным, как и объявлено.
|
||||
_test("① ПЕРВИЧНОЕ ПРАВИЛО п.3б — бинарно «глава с ≥1 фаталом», парный знаковый:",
|
||||
lambda uid, a: int(_all(uid, a) > 0))
|
||||
_test("② вторичное — ЧИСЛО фаталов на главу:", _all)
|
||||
for cl in CLASSES:
|
||||
_test(f"③ вторичное, по классу «{cl}»:", lambda uid, a, c=cl: len(data[(uid, a)][c]))
|
||||
|
||||
# ── ЧАСТОТА С ИНТЕРВАЛОМ, А НЕ ВЕРДИКТ ──────────────────────────────────────────────────
|
||||
# ⛔ Требование плана 22.08 §4: «редкий класс (ломается в ~10% глав) при 15 главах Гу НЕ
|
||||
# РАЗРЕШАЕТСЯ — по редким печатать ЧАСТОТУ С ИНТЕРВАЛОМ, а не вердикт». Здесь оно и исполняется.
|
||||
# ⚠ И печатается ПОТОЛОК МОЩНОСТИ знакового теста: при всех расхождениях в одну сторону
|
||||
# двусторонний p = 2/2^k, то есть p<0.05 недостижим, пока глав с ненулевой разницей меньше
|
||||
# шести. Класс, который ломается на трёх главах из шестнадцати, эта панель не разрешит НИКОГДА,
|
||||
# как бы односторонен ни был результат. Число печатается, чтобы «не различимо» не читалось как
|
||||
# «одинаково».
|
||||
k = 1
|
||||
while 2 / 2 ** k >= 0.05:
|
||||
k += 1
|
||||
print(f"\n ④ ЧАСТОТА ГЛАВ С ДЕФЕКТОМ (доля из {len(us)}) и 95% интервал Уилсона.")
|
||||
print(f" ⚠ потолок мощности: знаковый тест даёт p<0.05 не раньше, чем на {k} главах с "
|
||||
f"ненулевой разницей, ВСЕ в одну сторону (2/2^{k} = {2 / 2 ** k:.4f}).")
|
||||
print(f" {'класс':8s}" + "".join(f"{a:>22s}" for a in arms))
|
||||
for cl in (*CLASSES, "ВСЕ"):
|
||||
line = f" {cl:8s}"
|
||||
for a in arms:
|
||||
n = len(us)
|
||||
x = sum(1 for u in us if (len(data[(u["uid"], a)][cl]) if cl != "ВСЕ"
|
||||
else _all(u["uid"], a)) > 0)
|
||||
ph, z = x / n, 1.96
|
||||
den = 1 + z * z / n
|
||||
c = (ph + z * z / (2 * n)) / den
|
||||
hw = z * ((ph * (1 - ph) / n + z * z / (4 * n * n)) ** 0.5) / den
|
||||
line += f"{f'{x}/{n} [{max(0, c - hw):.2f};{min(1, c + hw):.2f}]':>22s}"
|
||||
print(line)
|
||||
print(f"\n ⚠ ПОПАДАНИЙ ВСЕГО (не глав) — описательно, парности не имеет:")
|
||||
for cl in CLASSES:
|
||||
print(f" {cl:8s}" + "".join(
|
||||
f"{sum(len(data[(u['uid'], a)][cl]) for u in us):>22d}" for a in arms))
|
||||
print(f"\n эталон: {ETALON.name} · глав в эталоне {len(et_all)}")
|
||||
return 0
|
||||
|
||||
|
||||
def hits(arm: str, uid: str = "") -> int:
|
||||
"""Все попадания с контекстом — чтобы улики читались ГЛАЗАМИ, а не принимались на слово."""
|
||||
et_all = etalon()
|
||||
R.PAIRS[PAIR]["wire"]()
|
||||
for u in R.chosen(PAIR):
|
||||
if u["uid"] not in et_all or (uid and not u["uid"].startswith(uid)):
|
||||
continue
|
||||
et = et_all[u["uid"]]
|
||||
t = R.arm_text(PAIR, arm, u["uid"])
|
||||
d = defects(u["source"], t, et)
|
||||
n = sum(len(v) for v in d.values())
|
||||
print(f"\n{'=' * 100}\n{u['uid']} арм {arm} фаталов {n} "
|
||||
f"эталон: рассказчик {et.get('род_голоса')} · "
|
||||
f"говорящие {[(s['имя'], s['род']) for s in et.get('говорящие', [])]}")
|
||||
for h in rod_hits(t):
|
||||
print(f" род [{h['род']}] {'речь ' if h['речь'] else 'повес'} "
|
||||
f"{h['слово']:14s} …{h['ктx']}…")
|
||||
for cl in CLASSES:
|
||||
for x in d[cl]:
|
||||
print(f" ⛔ {cl:6s} {x['слово'][:24]:24s} {x['почему']}")
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_selftest() -> int:
|
||||
bad = 0
|
||||
|
||||
def ck(n: str, ok: bool, d: str = "") -> None:
|
||||
nonlocal bad
|
||||
bad += not ok
|
||||
print(f"[{'OK ' if ok else 'ПРОВАЛ'}] {n}" + (f" {d}" if d else ""))
|
||||
|
||||
# ── 1. ОТБОР ФОРМ. Гейт стоит на том, чем первая редакция болела.
|
||||
for txt, want in (("Габриэль пожал плечами. — Я рассказал ей всё.", ["рассказал"]),
|
||||
("Фиби. Пьющий пепел. Битва при Санкте.", []),
|
||||
("Я поймала себя на мягкой улыбке.", ["поймала"]),
|
||||
("— Дело не в Диор, — прорычал я. — Поедешь с нами.", ["прорычал"]),
|
||||
("Это была молитва, поняла я.", ["поняла"]),
|
||||
("Он повесил голову, и я отвернулся к лошади.", ["отвернулся"])):
|
||||
got = [h["слово"] for h in rod_hits(txt)]
|
||||
ck(f"отбор форм: {txt[:44]!r}", got == want, f"нашлось {got}, ожидалось {want}")
|
||||
|
||||
# ── 2. РЕЧЬ ПРОТИВ ПОВЕСТВОВАНИЯ — механическое правило на наблюдённых формах панели.
|
||||
for txt, want in (("— Дело не в Диор, — прорычал я. — Поедешь с нами.", False),
|
||||
("— Я велел тебе сказать мэтру, что вернусь.", True),
|
||||
("Я подошёл к Хоакину и сжал его плечо.", False),
|
||||
("— Где я была? — Глаза мои сузились.", True),
|
||||
("— Благого рассвета, брат. — Я кивнул здоровяку.", False),
|
||||
# ⛔ ВОЗОБНОВЛЕНИЕ РЕПЛИКИ ПОСЛЕ СЛОВ АВТОРА — на этом первая редакция и
|
||||
# ломалась: третий кусок абзаца снова речь, а не повествование.
|
||||
("— Вряд ли, — фыркнул Жан-Франсуа. — Нет, я сбежал в Огюстен.", True),
|
||||
("— Да, — сказал он, — но я ушёл первым.", True)):
|
||||
h = rod_hits(txt)
|
||||
ck(f"речь/повествование: {txt[:44]!r}", bool(h) and h[0]["речь"] == want,
|
||||
f"{'речь' if h and h[0]['речь'] else 'повествование'}, ожидалось "
|
||||
f"{'речь' if want else 'повествование'}")
|
||||
|
||||
# ── 3. ⛔ ФАЛЬСИФИКАЦИЯ: счётчик ОБЯЗАН найти брак, ПОДСАЖЕННЫЙ в заведомо чистый текст.
|
||||
# Гейт, который зелен только на здоровом входе, не сторожит ничего — этим был болен селфтест
|
||||
# Б8: он фильтровал арм по наличию файла клетки, файлов не было, и гейт зеленел вхолостую.
|
||||
et_ok = {"род_голоса": "ж", "говорящие": [],
|
||||
"латиница_законна": ["oui"], "приёмы": [{"текст": "P A T I E N C E"}]}
|
||||
clean = "Я поймала себя на улыбке.\nЭто была молитва, поняла я.\nОн сказал oui, и я ушла.\nТ Е Р П Е Н И Е"
|
||||
ck("фальсификация: чистый текст даёт НОЛЬ фаталов",
|
||||
sum(len(v) for v in defects("", clean, et_ok).values()) == 0,
|
||||
str({k: [x["слово"] for x in v] for k, v in defects("", clean, et_ok).items() if v}))
|
||||
for name, sick, cl in (
|
||||
("род сломан в повествовании", clean.replace("Я поймала", "Я поймал"), "род"),
|
||||
("род сломан вторым местом", clean.replace("поняла я", "понял я"), "род"),
|
||||
("непереведённый кусок исходника", clean.replace("и я ушла", "и я ушла, swift as silver"), "язык"),
|
||||
("разрядка потеряна", clean.replace("\nТ Е Р П Е Н И Е", ""), "приём"),
|
||||
# ⛔ приём УДЕРЖАН, но слово НЕ ПЕРЕВЕДЕНО — латиница в разрядку. Первая редакция
|
||||
# класса «язык» этого не видела: её регекс требовал двух букв подряд.
|
||||
("разрядка удержана, но по-английски",
|
||||
clean.replace("Т Е Р П Е Н И Е", "P A T I E N C E"), "язык")):
|
||||
got = defects("", sick, et_ok)
|
||||
ck(f"фальсификация: счётчик ЛОВИТ «{name}»", bool(got[cl]),
|
||||
f"класс «{cl}»: {[x['слово'] or x['почему'] for x in got[cl]] or 'ПУСТ — брак не пойман'}")
|
||||
# обратная сторона: объявленная эталоном вставка браком НЕ считается
|
||||
ck("законная иноязычная вставка эталона браком НЕ считается",
|
||||
not lang_defects("Он сказал oui.", et_ok))
|
||||
ck("римская цифра браком НЕ считается", not lang_defects("Людовик VIII умер.", et_ok))
|
||||
|
||||
# ── 4. ЭТАЛОН ПОКРЫВАЕТ ПАНЕЛЬ, И КАЖДАЯ ГЛАВА В НЁМ ОБЪЯВЛЕНА ЯВНО.
|
||||
# ⚠ «Главы нет в эталоне» обязано быть ПРОВАЛОМ, а не тихим пропуском: `collect` отбирает
|
||||
# только главы эталона, и молчаливая недостача урезала бы панель ровно тем классом, против
|
||||
# которого весь план и написан.
|
||||
if not ETALON.exists():
|
||||
ck("эталон снят и лежит рядом", False, f"нет файла {ETALON.name}")
|
||||
else:
|
||||
et_all = etalon()
|
||||
R.PAIRS[PAIR]["wire"]()
|
||||
us = [u["uid"] for u in R.chosen(PAIR)]
|
||||
ck("эталон покрывает ВСЕ главы панели", all(u in et_all for u in us),
|
||||
f"нет в эталоне: {[u for u in us if u not in et_all]}")
|
||||
need = ("род_голоса", "говорящие", "латиница_законна", "приёмы", "определим_по_окну")
|
||||
miss = {u: [k for k in need if k not in et_all.get(u, {})] for u in us if u in et_all}
|
||||
ck("у каждой главы эталона объявлены все четыре поля",
|
||||
not any(miss.values()), str({u: v for u, v in miss.items() if v}))
|
||||
ck("род рассказчика объявлен ЗНАЧЕНИЕМ, а не пропуском",
|
||||
all(et_all[u].get("род_голоса") in ("ж", "м", None) for u in us if u in et_all),
|
||||
str({u: et_all[u].get("род_голоса") for u in us
|
||||
if u in et_all and et_all[u].get("род_голоса") not in ("ж", "м", None)}))
|
||||
print(f"\n{'ПРИБОР ГОДЕН' if not bad else f'ПРОВАЛОВ: {bad}'}")
|
||||
return bad
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
a = sys.argv[1:] or ["--report"]
|
||||
if a[0] == "--selftest":
|
||||
sys.exit(1 if cmd_selftest() else 0)
|
||||
elif a[0] == "--report":
|
||||
sys.exit(report(tuple(x for x in a[1:] if x.isupper()) or PANEL))
|
||||
elif a[0] == "--hits":
|
||||
sys.exit(hits(a[1], a[2] if len(a) > 2 else ""))
|
||||
else:
|
||||
raise SystemExit(f"⛔ неизвестный режим {a[0]!r}; знаю --selftest --report --hits")
|
||||
Loading…
Add table
Reference in a new issue