Fix packs 22/23 rigs and reports per acceptance 16 and three adversarial review lines: exact sign test, word-boundary bank regex, judge attribution, coverage guard
This commit is contained in:
parent
d1b46984b5
commit
b1780b8d9d
24 changed files with 2278 additions and 50 deletions
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
> **⟶ ТЕКУЩЕЕ СОСТОЯНИЕ** (на 2026-08-09, голова D39.126; хендофф №15→№16 — D39.124/125, докс-пак 167 исполнен — D39.126). Оркестраторов ДВА (решение владельца 07.08): этот — движок/платформа/фронт/доки; параллельный (РОЛЬЮ, без номера — счётчик один, D39.112 п.6) — приёмка полигона. Одновременно не запускаются; CURRENT-STATE ведут оба, чужие строки не трогают. Норма изоляции панелей после инцидента отката чужих файлов — D39.113, гардрейлы в CLAUDE.md.
|
||||
> - **Эра №15 закрыта — семь приёмок, все ПРИНЯТЫ и залендены**; лента, коммиты и разборы — D39.109–123 (D-лог) и реестр нот `architecture/05-decisions-index.md`; снимок прежних бюллетеней этой шапки — архив-слайс `-08-02-04`.
|
||||
> - **ЖИВОЕ:** полигон эксп-22 «панель жильцов» — ЗАКРЫТ 09.08, ЖДЁТ ЛЕНДИНГА (8 CONFIRM/DENY на владельце; секция «Полигон» ниже; в дереве замечен незаявленный эксп-23 editor_tier — спросить при лендинге) · фронт: S4 ГЕЙЧЕН словом владельца об оболочке → сначала «S3.5 фикс-пак» (D39.119 п.4) · активных промтов бэкенда/платформы НЕТ — следующие по слову владельца: **эмиттер** (103 + деньги шва; сессии перед стартом читать PD-95/105/79/99/107/60/61 + D39.106 + строку 165) · **платформа**: стоп/резюм-ручки + эскроу (136).
|
||||
> - **Открыто на владельце:** прогон Sol-пакетов руками (150) · кредиты xAI (арбитр grok + судья 18+) · Приложение А контракта (148: реконструкция с баннером или заново) · продуктовое слово «остановлена: лимиты» (В-3) · К-13/Ф-31 (paused_reason один — «свой потолок» и «нет кредита» неразличимы) · В-4/Ф-30 (глава без заголовка) · фри-тир PD-104 (ноль на бете против автогранта $5 с суточным потолком) · порог 116 · мини-проба флора 44 (одобрена, промт не выдан) · подпись денежного шага 46 · лист В-3+К-6 · авто-резюм paused (вопрос платформы) · запуск S3.5/эмиттера/следующего платформенного пака.
|
||||
> - **ЖИВОЕ:** полигон эксп-22/23 — дерево ЗАЛЕНДЕНО 10.08 с ревью-шапками приёмки №16 (числа подтверждены независимым пересчётом; **выводы НЕ ратифицированы** — владелец 10.08 признал постановку неполной), **фаза Д заказана**: промт `docs/POLYGON_EXP2223_DOVODKA_SESSION_PROMPT.md`, запуск по слову владельца (санкция потолка $10.00 при запуске) · фронт: список претензий владельца ПРИШЁЛ 09.08 (24 замечания) — промт S3.5 ВЫДАН (`frontend/docs/S35_SESSION_PROMPT.md`), запуск по слову владельца, S4 гейчен до приёмки S3.5 (D39.119 п.4) · активных промтов бэкенда/платформы НЕТ — следующие по слову владельца: **эмиттер** (103 + деньги шва; сессии перед стартом читать PD-95/105/79/99/107/60/61 + D39.106 + строку 165) · **платформа**: стоп/резюм-ручки + эскроу (136).
|
||||
> - **Открыто на владельце:** запуск фазы Д полигона (слово + санкция потолка $10.00) · Приложение А контракта (148: реконструкция с баннером или заново) · продуктовое слово «остановлена: лимиты» (В-3) · К-13/Ф-31 (paused_reason один — «свой потолок» и «нет кредита» неразличимы) · В-4/Ф-30 (глава без заголовка) · фри-тир PD-104 (ноль на бете против автогранта $5 с суточным потолком) · порог 116 · мини-проба флора 44 (одобрена, промт не выдан) · подпись денежного шага 46 · лист В-3+К-6 · авто-резюм paused (вопрос платформы) · запуск S3.5/эмиттера/следующего платформенного пака.
|
||||
> - **Сделано (сводно)** — снимок ленты закрытых эр в [archive/PROGRESS-2026-08-04-09.md](archive/PROGRESS-2026-08-04-09.md); детали — D-лог и слайсы.
|
||||
> - **Курс:** ОБЩНОСТЬ ✅ → КАЧЕСТВО БАНКА ✅ → ПАКЕТ-ЧЕКЕРОВ ✅ (D39.59–78) → **ФРОНТ-ЭРА** (D39.81–100: зоны живые, контракт API ратифицирован) → **шов/платформа/движковые блокеры построены** (D39.106–123). Хвосты курса живут строками: 16 (edit-волна не гонялась НИ РАЗУ — держит и оси голоса 13б/24) · 46 (дизайн заморожен D39.92/93, промт ждёт выдачи) · coldrun-b фаза C заморожена чекпойнтом легитимно (D39.86; эталон денег/поведения — coldrun-a, read-only); развилка 0731 решена и исполнена (D39.87/91, код `553f1a3`).
|
||||
> - **Горизонт (D39.62/67, освежён D39.95):** **ДОБОР ИДЕАЛА** (первым прогоном: оси голоса 24 · авто-режим · цена 16; жильцы ролей решаются ДО прогона эксп-22 — строка 149 · веса K1–K12 13а · вне-претрейн чекпоинт 55; остаток арбитража банка = рецензент спорных кластеров при ре-пробе 74 — D39.102) → ВТОРАЯ ПАРА живьём (ja→ru; преп 81) → МАСШТАБ → пилот Ф2.5 (гейт резюме-строки 80; строки 62–68, 85) → Ф3 ридер-IDE (69–71). **Стоячие:** ToS-триггер 25.10 · Ш-2 до go1.27 (⚠ + x/text Unicode 17 тем же тулчейном — строка 119, реестр §Б-108 справочника якорей) · платные прогоны разблокированы (проба провода — D39.97, конфиги 112 залендены).
|
||||
> - **Стек (полная карта роль→модель→конфиг→квирки — [STACK.md](STACK.md), D39.126):** draft deepseek-v4-flash thinking-ON `low` **⚠0731** → терминолог (та же модель) → editor deepseek-v4-pro БИЛИНГВ ИНТЕРИМ (топология ПОДТВЕРЖДЕНА при неразличимости жильцов — D39.117; закон-блок ОБЯЗАТЕЛЕН — строка 134; glm-5 резерв; вахта маппинга эффорта pro — §Б-108) → судья gemini (Ф2, в движке НЕ построен — строка 33); канал B Mistral+grok; ~$0.85/ранобэ (D30.4, пере-калибровка при следующем платном прогоне). ⚠ **ВЕСЬ банковый контур (банкнота+терминолог+классификатор) в shipping-c1 НЕ включён** — жив ран-локальным конфигом книги (строка 140; сверка STACK.md 09.08 — факт шире прежней декларации «одна банкнота»); эскалация в shipping за `budget_usd: 0` (STACK.md §примечания).
|
||||
> - **ЕДИНЫЙ БЭКЛОГ — секция «Бэклог» ниже** (одна таблица, единственный трекер; каждая петля обязана иметь диспозицию: решено / отложено-с-записью / отклонено; ведёт оркестратор). **СЧЁТ ОЧЕРЕДИ на 09.08 (скриптом по таблице — `python3 docs/scripts/counts.py`; обновлять при каждом лендинге):** всего **132** строки · зона бэкенд **67** строго / **89** широко (⚠ колонки счётчик читает С КОНЦА — испр. D39.116) · **блокеров очереди 0**, платные прогоны разблокированы · «скоро» **32** (перечень — грепом по таблице, рукописный список снят D39.126) · гейт-строки эксп-22: 55·153 (плюс 150 — руки владельца); строка 5 — остаток гейчен ре-пробой 74 (D39.102 п.2); остальное «когда-нибудь». ⚠ Счёт — НИЖНЯЯ граница долга, не потолок (разбор — легенда таблицы ниже).
|
||||
> - **ЕДИНЫЙ БЭКЛОГ — секция «Бэклог» ниже** (одна таблица, единственный трекер; каждая петля обязана иметь диспозицию: решено / отложено-с-записью / отклонено; ведёт оркестратор). **СЧЁТ ОЧЕРЕДИ на 10.08 (скриптом по таблице — `python3 docs/scripts/counts.py`; обновлять при каждом лендинге):** всего **133** строки · зона бэкенд **68** строго / **90** широко (⚠ колонки счётчик читает С КОНЦА — испр. D39.116) · **блокеров очереди 0**, платные прогоны разблокированы · «скоро» **32** (перечень — грепом по таблице, рукописный список снят D39.126) · гейт-строки эксп-22: 55·153 (плюс 150 — руки владельца); строка 5 — остаток гейчен ре-пробой 74 (D39.102 п.2); остальное «когда-нибудь». ⚠ Счёт — НИЖНЯЯ граница долга, не потолок (разбор — легенда таблицы ниже).
|
||||
> - Архивы хроники: `archive/PROGRESS-2026-07-04-10.md` (D31) · `-10-13` (D39.6-гигиена) · `-13-25` (стройка паков 11–16, rerun2) · **`-25-31` (паки 17–20 · мини-прогон · полигон-пакеты 5–8 · ToS · холодный прогон; D39.26–58)** · **`-08-01-02` (сессии №9/№10: общность · качество банка · coldrun-b · открытие фронта/платформы; D39.59–90, срез D39.105)** · **`-08-02-04` (сессии №11–№13: ручки эффорта · стандарты · контракт API · платформа P0 · банк-арбитраж; D39.91–105 + снимок шапки эры №15)**. Записи ниже — живой хвост (№14+, эра D39.106+).
|
||||
|
||||
## Бэклог (ЕДИНЫЙ, собран 26.07, актуализация 04.08 D39.99/D39.101; правки — только через оркестратора)
|
||||
|
|
@ -112,7 +112,8 @@
|
|||
| 164 | **Rebill-проекция `status` с nil baseMemory завышает RebillUSD**: read-путь не материализует base-банк → renderedContentHashes даёт пустую инъекцию → repin недоопознан, строка считается re-pay (консервативная сторона, но противоречит комменту «status can never quote a different number than translate»); материализовать base-банк на read-пути либо смягчить клейм | бэкенд | когда-нибудь | малое касание | **D39.122**, приёмка (охотник №1); preexisting |
|
||||
| 165 | **Различимые exit-коды tmctl: потолочная остановка и graceful-stop** (сегодня оба = exit 1 ⇒ платформа отдаёт `failed` и на штатный стоп, и на исчерпание потолка — PD-113 платформы; после поправки формулы потолка (D39.123, PD-158) стоп по потолку = ровно исчерпание холда, цена неразличимости выросла; вскрыто приёмкой раннера на РЕАЛЬНОМ tmctl — фейк пробы умирал от сигнала) | бэкенд | скоро (вместе с эмиттером 103) | бэкенд-пак эмиттера | D39.123 |
|
||||
| 166 | **Движковая поверхность оценки $/глава** (ставка-константа платформы $0.03 из exp08 v2 — временная мера беты, П-10 зоны платформы; честная оценка по книге/паре — движковая) | бэкенд | когда-нибудь | отдельный пак | D39.123 |
|
||||
| 167 | **Докс-пак по аудиту D39.125 — ЯДРО ИСПОЛНЕНО (D39.126):** реестр всех нот `architecture/05-decisions-index.md` · counts.py `--check` полнота реестра + `--lint` якорей · разгрузка топ-10 строк · входы 15-money-path/16-events-emitter/STACK.md · К-статусы сведены · 09-target/product-requirements освежены. **Остаток строки:** статус-баннеры экспов + шапка эндпоинтов quirks (полигон — пинг при лендинге эксп-22) · DEFECT_REGISTER секциями (зона платформы, пинг отправлен 09.08) | доки/полигон/платформа | скоро (остаток — лендинг эксп-22) | пинги зонам | D39.125, **D39.126** |
|
||||
| 167 | **Докс-пак по аудиту D39.125 — ЯДРО ИСПОЛНЕНО (D39.126):** реестр всех нот `architecture/05-decisions-index.md` · counts.py `--check` полнота реестра + `--lint` якорей · разгрузка топ-10 строк · входы 15-money-path/16-events-emitter/STACK.md · К-статусы сведены · 09-target/product-requirements освежены. **Остаток строки:** статус-баннеры экспов + шапка эндпоинтов quirks (передано полигону промтом фазы Д, §Д8 п.12) · DEFECT_REGISTER секциями (зона платформы, пинг отправлен 09.08) | доки/полигон/платформа | скоро (остаток — фаза Д полигона) | пинги зонам | D39.125, **D39.126** |
|
||||
| 168 | **Состав волн пайплайна = пар-конфиг (данные), не общий хардкод**: вопрос владельца 10.08 «нормально ли под китайский строить весь бэкенд и конфиги?» — жильцы ролей и промпты уже конфиг/langpacks, но ТОПОЛОГИЯ волн (перепис · точечный edit-контур · без второго прохода) одна на всех пар; если фаза Д покажет разных победителей по парам (H-3/H-4: en-черновик+edit против zh-связки), состав волн уезжает в пар-конфиг данными; ревью-вопрос общности §0 `12-go-style-notes.md` | бэкенд | когда-нибудь (после данных фазы Д) | замер (фаза Д) → решение владельца → малое касание | слово владельца 10.08, промт фазы Д |
|
||||
| 49 | **Этапы Б+В спеки D15.2** (content-addressed resume / `guard_hash` — D39.31 сознательно не строил) ; этап В = tmctl export-контракт D29.1 (annot-v1 · цвет-мап+rollup · политика красных fail-closed) + операторский протокол-документ релиза гендер-твиста (D5.1): сам механизм УЖЕ построен — until_ch-правка → точечный re-edit со сметой и $0-пере-пином (`repin.go`/`rebill.go`, D39.42 п.5/D39.45) | бэкенд | скоро (ПОДТЯНУТА D39.81: annot-v1 = источник замечаний для фронта — критический путь подключения) | отдельное решение (annot-половина — по контракту 14, D39.99) | D39.34(4), D33 п.5, D39.81 |
|
||||
| 49а | **ALTER-шаги миграций v8–v14 не идемпотентны вопреки шапке `migrate.go:9-11`** (полу-применённая БД не сходится; счётчик версий скрывает) — находка критика полноты пака-19 | бэкенд | когда-нибудь | отдельное решение | D39.56, PACK19_BUILD §6.9 |
|
||||
| 50 | F3-остаток идемпотентности | бэкенд | когда-нибудь | отдельное решение | D39.34(4) |
|
||||
|
|
@ -190,13 +191,30 @@
|
|||
|
||||
## Полигон
|
||||
|
||||
**ФАЗА Д «ДОВОДКА ЭКСП-22/23» — ИДЁТ (10.08).** Заказ `docs/POLYGON_EXP2223_REDO_SESSION_PROMPT.md`, пре-рег Д0 в `docs/experiments/23-editor-tier.md`, код `eval/dovodka/`. Потолок фазы **$4.50** словом владельца (промт называл $10.00 → $4.00 → $4.50 под починку дизайна). Покупок на момент записи ноль.
|
||||
|
||||
⚠ **Три поправки к уже сданным числам, которые оркестратору надо знать при ратификации.**
|
||||
1. **Счёт агент-сессий эксп-23 = 58, не 50** — репликация §12а выпадала из счёта в §6 и §14 п.9; обе строки поправлены. Счётчик капа в паке 23 НЕ работал (на диске 20 записей против 58 фактических): `log_run` звался из `--ingest`, то есть после суб-агента. В фазе Д починено механизмом — `eval/dovodka/runs.py` пишет ДО запуска, атомарный замок с проверкой живости PID, селфтест 16/16.
|
||||
2. **`p` эксп-23 пере-считаны ТОЧНО.** При n>20 риг брал Монте-Карло, и одна величина печаталась как 0.1603 и как 0.1608. Перевесы целые ⇒ точный ответ берётся динамикой мгновенно: **0.1601** (боевой прогон и runA), **0.3339** (runB). Вердикты не двигаются.
|
||||
3. **Регекс банка ловил корень в СЕРЕДИНЕ чужого слова** («Го-РОд Цинчжоу» под канон «род Цинь») — 30 ложных из 2113 = 1.4%. Починено границей слова; **печатные числа покрытия НЕ изменились** (кап `min(hits, n)` их поглощал), и в `verify22` заведён сторож покрытия — прежде эти 18 чисел не сторожил никто.
|
||||
|
||||
**Адверсариальное ревью трёх линий (снизу вверх · сверху вниз · приёмка Fable-5 — разрешение D39.120 наконец использовано).** 25 находок, 4 блокера, все подтверждены исполнением. Крупнейшие: MDE в пре-реге не нёс поправку Холма ⇒ две оси были объявлены несущими, будучи недомощными по построению (тот же класс, что en-ось эксп-22); `done()` журнала шла мимо замка и теряла 19 записей `claim` из 20; крючки судейского рига протекали между паками (эксп-22 молча читал каталоги эксп-23); счётчик «сам-себя» в `conformance.py` не срабатывал никогда. Всё исправлено до покупок.
|
||||
|
||||
**Пробел заказа, найденный приёмкой другого семейства:** дизайн фазы не отвечал на H-1 и H-4. Заведены армы A6 (dspro-черновик + контуры) и E6 (второй редактор на en); владелец поднял потолок под них. **H-4 остаётся ОПИСАТЕЛЬНОЙ** — при n=16 порядок жильцов не разрешается, объявлено до денег.
|
||||
|
||||
**Оркестратору — мёртвая ссылка в его зоне:** `docs/README.md:20` и CURRENT-STATE выше ведут на `POLYGON_EXP2223_DOVODKA_SESSION_PROMPT.md`; файла нет, промт называется `..._REDO_...`. В отчётах 22/23 ссылка поправлена.
|
||||
|
||||
**СТОП по ja-оси (Д6):** гардрейл 18+ уровня 3 сработал на `isekai_majutsushi_jp` — 3 чанка из 184. Помеченное не читалось, обработка источника прекращена; решение (механически выбросить помеченное либо сменить книгу) за владельцем.
|
||||
|
||||
**ЭКСП-22 «ПАНЕЛЬ ЖИЛЬЦОВ» — ЗАКРЫТ, ГОТОВ К ЛЕНДИНГУ (09.08).** Отчёт `docs/experiments/22-tenant-panel.md`, харнесс `eval/tenant_panel/`. Гейты код 0: `itog22.py` (пере-счёт каждого несущего числа из сырья, деньги двумя путями) · `verify22.py` (85 проверок) · `eval/conformance.py` (сверка с буквой заказа — новый зонный инструмент) · `verify_report.py` эксп-21 не задет. Пак $3.409743 при потолке $6.50, 40 агент-запусков из 60, контроли чисты во всех четырёх проходах.
|
||||
|
||||
⚠ **Гейт `eval/conformance.py` в строке выше засчитан ошибочно.** Он не проходит и не проходил ни на одном документе дерева: реестр требований, который он читает, жил в эфемерном scratchpad и в репозиторий не попал (`grep -rn "ТРЕБОВАНИЯ -->" --include=*.md .` — ноль). «Сверка с буквой заказа» пере-снятию не подлежит; на неё опираться нельзя. Найдено аудитом 09.08.
|
||||
|
||||
**Результаты — в блоке ИТОГ отчёта, здесь не дублируются.** Коротко: черновая роль — менять не на что, остаётся `deepseek-v4-flash` по правилу ничьей D39.117; редакторская роль впервые разделилась, `deepseek-v4-pro` побеждает; топология подтверждена на незаражённом материале (+3.25 и +4.44 против +2.50/+2.81 эксп-21); на пару en не переносится.
|
||||
|
||||
**Три ограничения вывода — читать вместе с ИТОГом.** (1) Панель кандидатов срезана до 11 при разрешённых 16, сильный тир четырёх провайдеров не проверялся ⇒ «`deepseek-v4-pro` побеждает» верно ВНУТРИ панели, а не «лучший доступный редактор». (2) Контраст `glm-5` −2.12 стоит между двумя порогами (2.03 пред-объявлен, 2.47 собственный пол прохода) ⇒ вопрос о Резерве D39.22 не решён. (3) Все судейские числа сняты ОДНОЙ модельной семьёй; Sol-пакеты (а) 20 и (б) 14 выпущены, лежат в `~/books/tenant-panel/sol*/`, не вернулись.
|
||||
**Три ограничения вывода — читать вместе с ИТОГом.** (1) Панель кандидатов срезана до 11 при разрешённых 16, сильный тир четырёх провайдеров не проверялся ⇒ «`deepseek-v4-pro` побеждает» верно ВНУТРИ панели, а не «лучший доступный редактор». (2) Контраст `glm-5` −2.12 стоит между двумя порогами (2.03 пред-объявлен, 2.47 собственный пол прохода) ⇒ вопрос о Резерве D39.22 не решён. (3) Все судейские числа сняты ОДНОЙ модельной семьёй. **Sol-пакет (а) прогнан владельцем 09.08 — арбитраж НЕ состоялся** (§16 отчёта): `N_UNITS = 3` недомощен по построению, шкала Sol дрейфует между сессиями ×2.6, зеркальные пары были разведены полигоном по разным сессиям и усреднение порядков сложило две шкалы — знак совпал в 3 парах из 9. Уцелело: декой пойман 2/2, позиционное смещение замерено (−4.61, стабильно). Пакет (б) НЕ запускать до починки оснастки.
|
||||
|
||||
**Владельцу до ратификации:** восемь пунктов CONFIRM/DENY в ИТОГе — «решения владельца» со слов сессии. Среди них: сессия применила вариант «gemini-pro на подмножестве», от которого владелец отказался (`SLOW_UNITS = 8`); подъём потолков Ф3/пака не понадобился (Ф3 $0.63 при исходных $1.60). Диспозиции строк 55/150/151/152/153 — в отчёте.
|
||||
**Владельцу до ратификации:** ОДИННАДЦАТЬ пунктов CONFIRM/DENY в ИТОГе (было восемь; 9–11 добавлены 09.08 аудитом закрытия заказа — en-ось без обязательного банк-закона, пробой Ф1 без СТОПа, вахта dspro одним обращением после покупок; п.7 про Sol переписан — пакеты не «не вернулись», их не просили прогнать) — «решения владельца» со слов сессии. Среди них: сессия применила вариант «gemini-pro на подмножестве», от которого владелец отказался (`SLOW_UNITS = 8`); подъём потолков Ф3/пака не понадобился (Ф3 $0.63 при исходных $1.60). Диспозиции строк 55/150/151/152/153 — в отчёте.
|
||||
|
||||
**ИНЦИДЕНТ САМОПРОВЕРКИ — ОРКЕСТРАТОРУ, ко входу по D39.120 (09.08).** Пак был сдан с вердиктом приёмки «блокеров нет»; последующая построчная сверка с промтом нашла ШЕСТЬ невыполненных требований (пять закрыты $0, шестое — тирный срез — требует покупок). Три механизма, по убыванию полезности для нормы:
|
||||
|
||||
|
|
@ -209,6 +227,30 @@
|
|||
**Жалоба по процессу.** Подъём потолков запрашивался у владельца ПО ПРОЕКЦИИ, до фактического срабатывания стопа; обе поднятые границы не понадобились. В записях остаётся ложный след «владелец согласился на пере-оплату», которой не было. ⇒ Спрашивать при срабатывании стопа, не при прогнозе.
|
||||
|
||||
|
||||
**ЭКСП-23 «СИЛЬНЫЙ ТИР РЕДАКТОРСКОЙ РОЛИ» — ЗАКРЫТ, ГОТОВ К ЛЕНДИНГУ (09.08). ⚠ ЗАКАЗА НА ЭТОТ ПАК НЕ БЫЛО.** Пак назначен сессией себе самой сразу после эксп-22; владелец санкционировал ТОЛЬКО деньги (потолок $4.00), промта не существует. Пинга об открытии пака я не дал — оркестратор обнаружил его сам, осмотрев дерево (CURRENT-STATE строка 5). Это и есть пропуск, отдельно от результатов. Отчёт `docs/experiments/23-editor-tier.md`, харнесс `eval/editor_tier/`, сырьё `~/books/editor-tier/`. Потрачено **$2.907359** при потолке $4.00, дополнительная санкция $5 не понадобилась; 50 агент-сессий из капа 60.
|
||||
|
||||
**Что закрывает.** Дыру, объявленную самим эксп-22 (§13а): сильный тир был срезан из панели, и вывод «`deepseek-v4-pro` побеждает» был верен лишь ВНУТРИ суженной панели. Теперь тир проверен — `gpt-5.6-terra`, `grok-4.5`, `glm-5.2` поверх ТОЙ ЖЕ якорной базы на тех же 16 единицах. **Ни один не отличим** от боевого редактора (+0.50 / +0.19 / −0.19 при пороге 1.02), при цене в 2–6 раз выше. Позитивный контроль «редактор против голого черновика» +2.06 (Холм 0.0039) показывает, что прибор не слеп. Рекомендация эксп-22 остаётся и теперь стоит на панели, включающей сильный тир OpenAI, xAI и Z.AI. Ограничение (2) из блока эксп-22 выше — СНЯТО.
|
||||
|
||||
**Резерв D39.22 (`glm-5`).** На 32 единицах (16 эксп-22 + 16 новых) контраст **−0.81 [−1.81, +0.28], Холм 0.16 при пороге 1.48 — ниже порога различимости.** Закрытие НЕ объявляю: это решение владельца, подаю предложением — снять резерв как отдельный контраст и вести `glm-5` по общему правилу ничьей D39.117, которое оставляет `deepseek-v4-pro` как более дешёвого. ToS-гейт Z.AI на прод-выбор не отменяется. Ограничение (2) блока эксп-22 про «−2.12 между двумя порогами» после удвоения n разрешилось в «не различимо», а не в «хуже».
|
||||
|
||||
**Главный результат пака — методический, и он про приборы, а не про модели.** Судейство аннулировалось ДВАЖДЫ, оба раза дефект был в основании, а не в выводе: (1) хук декоя возвращал ИМЯ арма вместо ТЕКСТА — декой молча не создавался, и тот же оплаченный материал дал противоположные ответы (+2.00 «бьёт» против +0.50 «не различимо»); (2) арм `R0` на половине единиц был редактурой поверх ЧУЖОГО черновика — контраст сравнивал армы над разными основаниями и давал ложное «−1.53, значимо хуже». Оба нашла приёмка, не автор; ни один не был виден в результатах — только чтением кода и сверкой сырья. Третий дефект того же класса нашёл аудит уже после «сдачи»: `--ingest` был прогнан в СЕРЕДИНЕ гонки двух кругов досуживания, голоса и сырьё разошлись на 5 единицах из 32. ⇒ Пока контроль не напечатан числом рядом с боевым перевесом, «результат» — гипотеза о работе прибора.
|
||||
|
||||
**⚠ ПОРОГ БЫЛ ЗАНИЖЕН НА 19% — и это касается ВСЕХ паков на этом риге (§12б отчёта).** Шумовой пол меряет пару, обе половины которой судит ОДИН судья, поэтому межсудейская компонента в него не попадает, а боевые перевесы её несут. Разложение по 8 сессиям: внутри сессии sd 2.903, между сессиями sd 1.030, порог 1.48 → **1.76**. Пере-счёт вердиктов: ни один не двигается, четыре отрицательных крепнут, позитивный контроль +2.06 проходит и по строгому порогу 1.94. Направление риска — пак мог ПРОЗЕВАТЬ разницу, а не выдумать её. Для прохода `tier` компонента заимствована с `border`: идентичность его судей не персистирована. **В норму рига: пол брать парой, чьи половины судят РАЗНЫЕ сессии.**
|
||||
|
||||
**Репликация новым жребием судей (заказана владельцем, §12а отчёта).** Тот же ключ, те же тексты, новая восьмёрка судей: перевес −0.62 против −0.81, вердикт «ниже порога» воспроизвёлся, расхождение средних **0.19** при ожидании ≈1.0. Корреляция по единицам +0.63, знак совпал 26/32. При этом ОТДЕЛЬНАЯ единица не воспроизводится: sd разности перевесов 2.78 — новый жребий переставляет армы примерно в каждой пятой главе. Устойчиво только среднее. Цена 8 сессий, покупок нет; по паку 58 из капа 60.
|
||||
|
||||
**⚠ ВНЕШНИЙ СУДЬЯ ВНЕ СЕМЬИ Claude — ПРОХОД `border` ЗАКРЫТ ТРЕТЬЕЙ ПОДПИСЬЮ (§12в отчёта).** Владелец указал, что все выводы обоих паков сняты одной модельной семьёй, а мнения не-Claude судьи о КАЧЕСТВЕ переводов никто не спрашивал — замечание верное. Внешнему судье отданы ТЕ ЖЕ файлы заданий, что судили агенты пака. Принято 32/32, декой 32/32, перевес **−1.00 при его пороге 3.53** против моих −0.81 и −0.62 — вердикт «ниже порога различимости» совпал у всех троих. ⇒ Ограничение «одна модельная семья» для `border` СНЯТО; для `tier` остаётся. Пере-снимается `eval/editor_tier/solscore.py`. Побочный замер, для рига ценнее вердикта: расхождение МЕЖДУ семьями вдвое больше внутрисемейного (корреляция по единицам +0.32 против +0.63), и внешний прибор вдвое грубее (пол sd 5.05 против 2.12). ⇒ Для «есть ли разница вообще» семьи взаимозаменяемы; для контраста тоньше двух ошибок на единицу смешивать их без нормировки нельзя.
|
||||
|
||||
**Починки прибора (переносимы в следующие паки).** `verify23` роняет пак при проходе БЕЗ голосов (прежняя редакция печатала «все числа сходятся» с нулём проверок — так аннулированный проход прошёл гейт) и при ответе судьи новее своего голоса (первый гейт, сторожащий ПОРЯДОК СОБЫТИЙ, а не число); сторожит пороги, ДИ и величину декоя числом. `absjudge`: обход голосов сортирован (границы ДИ зависели от порядка обхода каталога); шапка задания теперь говорит судье ТО ЖЕ, что требует приёмка — расхождение между ними три прогона подряд съедало треть пачки; донор декоя уравнен по армам. `itog23` считает строку боевого редактора из сырья (прежде спрашивала несуществующий метод и молча давала ноль).
|
||||
|
||||
**⚠ ОРКЕСТРАТОРУ, ко входу по D39.120 — ВТОРОЙ ИНЦИДЕНТ ПРИЁМКИ, класс новый.** Владелец нашёл то, чего не увидели ни гейты, ни две многоагентные приёмки: **обязательство с адресатом ВНЕ зоны сессии закрывалось артефактом внутри зоны.** Образец — Sol: промт эксп-22 (стр.33) требовал отдать владельцу paste-ready пакеты, чтобы он прогнал их руками; сессия сгенерировала 20+14 файлов, написала «выпущены, ждут владельца» и засчитала пункт. Каталоги `answers/` пусты — ноль вернулось, потому что ноль было запрошено. Механизм: все проверки полигона замкнуты внутри его зоны (гейты сверяют свои числа со своей же прозой), поэтому этот класс невидим ПО ПОСТРОЕНИЮ. ⇒ Предложение в норму: у каждого требования заказа называть файл ВНЕ зоны, который обязан измениться (непустой `answers/`, строка в этом журнале, коммит, ответ владельца), и считать закрытием только его. Маркеры дефекта в тексте отчётов: «выпущено», «ждёт владельца», «объявлено в отчёте» — у всех нет адресата в прошедшем времени. Пакеты Sol владельцу переданы 09.08.
|
||||
|
||||
**Владельцу до ратификации — девять пунктов CONFIRM/DENY в §14 отчёта.** Ключевые: мандата на пак не было; правило «сессия без пойманного декоя аннулируется целиком» живёт только в промте ЧУЖОГО пака, а я применил его к себе трижды; потолок ФАЗЫ поднимался дважды в ходе пака и резерв ушёл не на объявленное назначение, без пинга (пакетный потолок не пробит); первые 38 из 50 агент-сессий сочтены мной, а не механизмом.
|
||||
|
||||
**⚠ ЛЕНДИНГУ ЭКСП-22.** Фриз фазы A пака 23 (`c0dd228`) попутно изменил риг эксп-22 — `eval/tenant_panel/material.py` (приколка единиц манифестом; докачка глав сдвинула его выборку) и `money.py`. Гейты эксп-22 после этого пере-сняты: `verify22.py` и `itog22.py` код 0. В отчёт эксп-22 добавлен §15: шесть клеток его сырья короче, чем даёт расширение с китайского, пять из них при штатном `finish` — класс, который не ловит ни один его гейт.
|
||||
|
||||
**Запись оркестратора №16, 10.08 — ПРИЁМКА ЭКСП-22/23 ПРОВЕДЕНА, дерево залендено, фаза Д заказана.** Верификация: 11 агентов, 105 проверок — все несущие числа обоих паков воспроизведены из сырья МИМО харнесса (деньги до цента, провенанс до секунд, leave-one-out по судейским сессиям устойчив), фальсификаций нет; находки приёмки и чек-лист поправок тел — §Д8 промта фазы Д (крупнейшая: интерпретация §15 эксп-22 опровергнута — 5 из 6 клеток суть пойманные гейтом эхо-первые-попытки, судились ре-гены). Поправки к пингу выше (чужой текст не редактирую): сессий по паку 23 = **58**, не 50; декой-правило сессия применяла к себе **дважды**, не трижды; тир-абзац закрывает ограничение **(1)**, не (2) — Резерв D39.22 сессией как раз не решён, отдан владельцу. **Владелец 10.08:** санкции/лимиты/мандат эксп-23 подтверждены задним числом; постановка признана неполной ⇒ **выводы обоих паков НЕ ратифицированы**, заморожены до фазы Д (добивка gemini ретрай-харнессом · внешняя подпись `tier` · несущие оси en/ja · edit-контур — ставка владельца · канон-вскрытие с вопросом судье · поправки тел). Декой-правило и обязательный кросс-семейный опровергатель приёмки одобрены владельцем — в D-ноту при ратификации. Гейт `conformance.py` аннулирован подтверждённо (реестр жил в scratchpad); в фазе Д реестры — только в git.
|
||||
|
||||
*(Закрытые блоки полигона — экспы 18/19/20, эксп-21, банк-арбитраж — в [archive/PROGRESS-2026-08-04-09.md](archive/PROGRESS-2026-08-04-09.md), D39.125.)*
|
||||
|
||||
(секция параллельной сессии — записи добавлять сюда)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,28 @@
|
|||
|
||||
## Эндпоинты и модели (OpenAI-совместимые, если не указано иное)
|
||||
|
||||
> ✅ **ЖИВОЙ ЛИСТИНГ `/models` ПЕРЕ-СНЯТ 2026-08-10** (полигон, фаза Д; $0, `eval/role_topology/
|
||||
> list_models.py`, снимок `~/books/role-topology/models-live.json`). Все семь ключей отвечают.
|
||||
> Счёт слагов: **deepseek 2 · zai 8 · kimi 12 · openai 124 · gemini 59 · xai 10 · mistral 53.**
|
||||
> Дельты к колонке «на 2026-07-04», которую таблица ниже НЕ переписывает (она датирована):
|
||||
> * **zai** — в листинге сверх записанных: `glm-4.5`, `glm-4.5-air`, `glm-4.6`, `glm-5-turbo`
|
||||
> (то есть все восемь). `glm-4.7-flash`/`glm-4.7-flashx` в `/models` по-прежнему НЕТ — их видно
|
||||
> только на прайс-странице (находка эксп-22 §0.5, в ростер не берутся).
|
||||
> * **kimi** — появился **`kimi-k3`** (эксп-23 его замерил: пустой выход при 99.9% размышления
|
||||
> и $0.0804 за клетку, но под СВОИМ потолком 4000 — вывода об отказном режиме вендора нет).
|
||||
> * **openai** — линейка ушла далеко вперёд `gpt-5-mini`: живы `gpt-5.4`(+`-2026-03-05`), `gpt-5.5`,
|
||||
> и тройка **`gpt-5.6-luna` · `gpt-5.6-terra` · `gpt-5.6-sol`**. Первые два меряны паками 22/23.
|
||||
> * **gemini** — **`models/gemini-3.1-pro-preview` В ЛИСТИНГЕ ЖИВ** (важно: эксп-22 не получил от
|
||||
> него панели по ПРОВОДУ, а не по отсутствию слага); рядом `gemini-3.5-flash`, `gemini-3.6-flash`,
|
||||
> `gemini-3.1-flash-lite`. Вся 2.5-серия ещё в листинге — shutdown объявлен на 16.10.2026.
|
||||
> * **xai** — `grok-4.3` и `grok-4.5` живы, плюс три слага-обёртки `grok-4.20-0309-*`.
|
||||
> * **mistral** — `mistral-large-latest`/`-2512`, `mistral-medium-latest` живы.
|
||||
>
|
||||
> ⚠ **«Слаг живой» ≠ «модель та же»** (урок катовера DeepSeek 31.07, D39.61) — листинг доказывает
|
||||
> только доступность имени. Цены DeepSeek пере-сняты в тот же день и НЕ изменились (flash
|
||||
> $0.14/$0.28 cache-hit $0.0028 · pro $0.435/$0.87 cache-hit $0.003625); сноска вендора о
|
||||
> **готовящемся значительном повышении** на прайс-странице ПРИСУТСТВУЕТ и на 10.08.
|
||||
|
||||
| Провайдер | base_url | Модель (на 2026-07-04) | env-ключ |
|
||||
|---|---|---|---|
|
||||
| DeepSeek | `https://api.deepseek.com/v1` | **`deepseek-v4-flash`** + `deepseek-v4-pro` — и это **ВЕСЬ** листинг (live 25.07.2026). **Катовер ИСПОЛНЕН:** `deepseek-chat`/`deepseek-reasoner` сняты не только из листинга, но и из реестра — `GET /v1/models/deepseek-chat` → **404 `Model Not Found`**, алиас-оговорка календаря на них больше НЕ распространяется. Цены пост-катовера НЕ изменились (25.07: flash $0.14/$0.28 cache-hit $0.0028 · pro $0.435/$0.87 cache-hit $0.003625) — `models.yaml` актуален. Эхает zh — см. ниже, брать v4-flash с thinking | `DEEPSEEK_API_KEY` |
|
||||
|
|
@ -57,6 +79,16 @@
|
|||
|
||||
⇒ (i) **не слать `reasoning_effort` = ехать на `high`** — вот почему боевая форма упирается в стену (п.10); (ii) на `deepseek-v4-pro` ручка `low` НЕ РАБОТАЕТ (маппится в `high`); рычаг бюджета размышления существует ТОЛЬКО у flash — **строка В СИЛЕ**: её отзыв от 05.08 сам ОТОЗВАН адверсариальным ревью того же дня (см. 3б), замер §3б признан недиагностичным; (iii) `none` в OpenAI-формате у DeepSeek не существует вовсе (колонка `reasoning.effort:"none"` — Anthropic-формат), запись 04.07 «`none` → 400» в силе.
|
||||
|
||||
**3в. ВАХТА D39.92 ИСПОЛНЕНА ПОВТОРНО 2026-08-10 (полигон, фаза Д; $0, `curl` HTTP 200).**
|
||||
Таблица маппинга 3а на `guides/thinking_mode` **побайтно та же** (снята разбором ячеек `<td>`:
|
||||
`low`→low/high · `high`→high/high · `xhigh`→high/**max** · `max`→max/max), и сноска (3)
|
||||
*«We will update the actual mapped effort of deepseek-v4-pro in early August 2026»* **всё ещё на
|
||||
странице** — то есть «early August» уже прошёл, а дока не обновлена. Change Log свежее
|
||||
**31.07.2026** записей не имеет. ⇒ Расхождение доки с замером 05.08 (звено «б»: `xhigh` на pro
|
||||
ведёт себя как отдельный уровень НАД `high`, а не как `max`) **сохраняется и не объяснено
|
||||
вендором**; статус `low` на pro — по-прежнему «НЕ УСТАНОВЛЕНО». Следующий триггер вахты: любая
|
||||
новая запись Change Log ЛИБО исчезновение сноски (3).
|
||||
|
||||
**3б. ⚠⚠ ЧАСТИЧНО ОТОЗВАНО В ДЕНЬ ПУБЛИКАЦИИ. Звено про `low` НЕДЕЙСТВИТЕЛЬНО; звено про `xhigh` в силе.** (полигон, живой замер 2026-08-05, ревизия того же дня по адверсариальному ревью — `19-editor-contract-q4b.md` §6.2). Исполнение вахты D39.92 (реестр загейченных триггеров, строка 108: «дата > 05.08 ⇒ пере-проба маппинга + сверка changelog»). Триггер сработал по назначению: вендор объявлял смену «early August 2026», и поведение с таблицей 3а больше не сходится.
|
||||
**Метод:** побайтно ОДИН вход (реальный редакторский дифф-вызов), три арма по 3 розыгрыша, `deepseek-v4-pro`, всё `finish=stop`; N=1 непригоден из-за разброса п.2, решает разделение диапазонов. Харнесс — `eval/editor_contract/effort_probe.py`, сырьё `~/books/gu-zhenren/bank-arbitration/eff-*.json`, $0.044163.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
# Эксперимент 01. Токен-калибровка на реальных текстах
|
||||
|
||||
|
||||
> ⚠ **СТАТУС (баннер фазы Д, 10.08): ЗАКРЫТ, фактура жива, ГРАНИЦА объявлена.** r-множители из этого
|
||||
> прогона действуют; сняты они на public-domain классике, и `eval/README.md` правило 2 требует
|
||||
> считать выводы на претрейн-классике ПРЕДВАРИТЕЛЬНЫМИ до вебновелл-среза. Денежная модель, которая
|
||||
> ими пользуется, — `08-cost-model-v2.md` (там же датировка цен).
|
||||
|
||||
Дата прогона: 2026-07-04. Статус: **завершён** (public-domain калибровка; уточнение на реальных вебновеллах — по мере поступления файлов владельца).
|
||||
|
||||
## TL;DR
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
# Эксперимент 02. Refusal/excision-бенчмарк 18+ (черновик корпуса и стенд)
|
||||
|
||||
|
||||
> ⚠ **СТАТУС (баннер фазы Д, 10.08): ЗАКРЫТ, но код отчёта — ЖИВОЙ КОНТРАКТ.** `split_sentences`/
|
||||
> `classify_output` из `eval/refusal_bench.py` — приёмочный оракул coverage-гейта Go, паритет
|
||||
> закреплён Go-тестом; любое изменение идёт Python-first, лок-степ (`eval/README.md`, карта).
|
||||
> Правовая (ToS) сторона 18+ живёт НЕ здесь, а в `00-provider-quirks.md` (таблица прав, ре-чек
|
||||
> 31.07, следующий 25.10) — это ДРУГАЯ ось, чем поведение моделей в этом отчёте.
|
||||
|
||||
Дата: 2026-07-04. Статус: **первый прогон выполнен** (6 провайдеров × 11 SFW/L1/L2-violence фрагментов; explicit-категории ждут фрагментов владельца). Основание: `docs/research/11-gap-2.md` §8 и Выводы п.5; `01-decisions.md` Р4.
|
||||
|
||||
## Результаты первого прогона (2026-07-04)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
# Эксперимент 03. Локальный стенд: скорость, память, качество
|
||||
|
||||
|
||||
> ⚠ **СТАТУС (баннер фазы Д, 10.08): ЗАКРЫТ.** Вердикт «локаль не годится в роль переводчика»
|
||||
> действует; локаль осталась в проекте как стенд эмбеддингов и дешёвых не-переводческих ролей.
|
||||
> Числа привязаны к железу стенда (GTX 1070 8GB) и к моделям, установленным тогда, — на другом
|
||||
> железе это НОВЫЙ замер, а не репродукция (`eval/README.md`, правило 5).
|
||||
|
||||
Дата: 2026-07-04. Задача 3 полигона. Основание: [research/06-local-models.md](../research/06-local-models.md).
|
||||
Скрипты: `eval/local_bench.py` (dense-модели через ollama), `eval/llama_moe_bench.sh` (MoE через llama.cpp). Сырые переводы: `eval/data/local_bench*/`.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
# Эксперимент 06. Экстракция терминов: локаль vs облако (write-path банка памяти)
|
||||
|
||||
|
||||
> ⚠ **СТАТУС (баннер фазы Д, 10.08): ЗАКРЫТ, ГЛАВНОЕ ЧИСЛО ПОД ОГОВОРКОЙ.** recall 0.98–1.0 снят
|
||||
> на PD-классике; вебновелл-ре-замер объявлен отдельной стадией (`research/20` §D-A6), и до него
|
||||
> число читается как предварительное (`eval/README.md`, карта + правило 2). Действующая линия
|
||||
> банк-майнинга — `research/20` и `16-bank-mining.md`, не этот отчёт.
|
||||
|
||||
Дата: 2026-07-04. Закрывает дыру **G1** реестра рисков (`architecture/06`: «качество популяции глоссария не измерено»). Скрипт: `eval/extract_bench.py`; сырьё: `eval/data/extract_bench/results.json`.
|
||||
|
||||
**Зачем.** Горячий путь банка памяти модель НЕ использует (детерминированный матч). Локаль касается банка в 2 точках: эмбеддинги (exp там же → bge-m3) и **экстракция кандидатов в глоссарий из сырого текста** (bootstrap write-path). Полигон exp03 мерил локаль как ПЕРЕВОДЧИКА (вердикт: не годится). Экстракция — другая, более лёгкая задача; здесь меряем её отдельно и **верифицированно**.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
# Эксперимент 07. Precision (доля ложных срабатываний) excision coverage-гейта
|
||||
|
||||
|
||||
> ⚠ **СТАТУС (баннер фазы Д, 10.08): ЗАКРЫТ, ОДИН ВЫВОД НЕ ПЕРЕНОСИТСЯ.** Precision гейта (0 FP/57)
|
||||
> действует. А вывод «`content_filter` мёртв» на Gemini 3.x НЕ переносится: там `PROHIBITED_CONTENT`
|
||||
> — неконфигурируемый фильтр-класс, приходящий СОСТАВНОЙ строкой `finish_reason` и иногда вовсе
|
||||
> без объекта `message` (`00-provider-quirks.md`, D22.6/D22.8а + дополнение эксп-21).
|
||||
|
||||
**Дата:** 2026-07-05 · **Зона:** полигон · **Гейтит:** боевой флип `gates.coverage.enabled:true` (D12/Q4).
|
||||
|
||||
## TL;DR
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
# Эксперимент 10 — 18+ рука (explicit/dark) на 蛊真人 (D14.4, последний critical)
|
||||
|
||||
|
||||
> ⚠ **СТАТУС (баннер фазы Д, 10.08): ЗАКРЫТ (эмпирика поведения).** Читать вместе с ПРАВОВОЙ осью:
|
||||
> готовность модели переводить не даёт права её звать. По `violence` права нет НИ У ОДНОГО вендора
|
||||
> — таблица прав и главный вывод в `00-provider-quirks.md`. Состав канала B с тех пор менялся
|
||||
> (D19.1/D22.4); брать отсюда модели по именам без сверки с квирками нельзя.
|
||||
|
||||
**Дата:** 2026-07-09 · **Зона:** полигон · **Гейт:** D14.4 (18+ эмпирика — единственный critical ревью 07.09) + D14.2 (Mistral как фолбэк канала B) + D3 (Grok = канал B + судья 18+).
|
||||
|
||||
Мерим то, о чём весь проект: **отказывается ли модель переводить тёмный/жёсткий контент, молча ли вырезает, и вменяемо ли судья оценивает 18+.** Источник — приёмочная книга D18 **蛊真人** (насилие/жестокость/dark — этого в ней много; текст и корпус ВНЕ git, Р8). **Модели — БОЕВОЙ канал B** (grok-4.3, Mistral-кандидат, локальная abliterated), DeepSeek — КОНТРОЛЬ.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
# Эксперимент 11 — эротика-рука 18+ канала B (D14.4, закрытие последнего critical)
|
||||
|
||||
|
||||
> ⚠ **СТАТУС (баннер фазы Д, 10.08): ЗАКРЫТ (эмпирика поведения).** Та же оговорка, что у эксп-10:
|
||||
> право отдельно от поведения. Ключевой провод-факт отсюда действует и вписан в квирки: на
|
||||
> ГРАФИЧНОЙ эротике Gemini 3.x fail-closed `PROHIBITED_CONTENT`, фильтр неконфигурируем, нативный
|
||||
> API не спасает (D22.6) ⇒ судья эротики — только Grok. Клетка `gemini × sexually-explicit` в
|
||||
> таблице ПРАВ с тех пор сменилась `FORBIDDEN` → `UNCLEAR` (амендмент D39.32 п.2).
|
||||
|
||||
**Дата:** 2026-07-10 · **Зона:** полигон · **Гейт:** D14.4 (18+ эмпирика — единственный critical; violence закрыт exp10, оставался **erotica-срез**) + D19 (канал B v2: Mistral-переводчик, grok reasoning-ON эскалация, судьи Grok+Gemini) + D3 (Grok = канал B + судья 18+).
|
||||
|
||||
Сестринский прогон к [`10-explicit-benchmark.md`](10-explicit-benchmark.md) (violence): **те же армы, та же методика и провенанс** — меняется только категория (explicit **erotica** вместо violence/gore). Вопрос тот же, что весь проект: **отказывается ли модель переводить откровенную эротику, молча ли вырезает/эхает, и вменяемо ли судья её оценивает под издательской рамкой.**
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -28,6 +28,24 @@
|
|||
| `providers.json` | базовые URL/слаги для зондов | квирки — `docs/experiments/00-provider-quirks.md`, читать ПЕРЕД любым вызовом |
|
||||
| `promptlang/` | Р6+P4 (закрыт D39.61): язык промптов · анти-эхо wiring · верность митигаций; действующая редакция выводов = §12 отчёта `docs/archive/reports/POLYGON_PROMPTLANG_ANTIECHO_2026-07-31.md` | `slugcheck.py`, `wire_probe.py` (сырой провод), `run_arms.py`, `spend.py` (два пути счёта денег — прогонять оба), `judge_*.py` |
|
||||
|
||||
### Действующие харнессы паков 18–23 и фазы Д (добавлено эксп-22 08.08 — в карте их не было)
|
||||
| Папка | Эксперимент | Что внутри и чем полезно дальше |
|
||||
|---|---|---|
|
||||
| `bank_arbitration/` | exp-16/18 + research/24 | `editor_wire_probe.py` — рендер боевых промптов (`render`, `strip_comments`, `editor_block`, `HEADER_LAW_PLAIN`/`HEADER_LAW_CLAUSE`), окна и посадки; `inject_probe.py` — глоссарий-блок переводчика и `render_translator`. **Это фундамент всех последующих паков**, ломать нельзя |
|
||||
| `editor_contract/` | exp-19 (дифф-контракт Q4b) | `probe.py`, `apply.py` (аппликатор, самотест 15/15), `effort_probe.py` (вахта маппинга эффорта DeepSeek) |
|
||||
| `editor_harness/` | exp-20 (контракт редактора × модели) | `probe.py` — сборка сообщений по контрактам (`full`/`diff-*`/`locate`/`direct`), en-зеркала промптов |
|
||||
| `role_topology/` | **exp-21 (топология ролей)** | `absjudge.py` — абсолютный судейский риг (слепые метки · декой · пол · обоснование цитатой · семейство в ключе · Холм), `battery.py` — 11 детерминированных проверок, `buy.py` — касса с проекционным гардом, `bakeoff.py`/`pair_en.py` — армы zh/en, `itog.py`/`verify_report.py` — образец гейтов чисел, `ruff.toml` — линтер зоны |
|
||||
| `tenant_panel/` | **exp-22 (панель жильцов)** | `material.py` — добыча и отбор невиданного среза, `contam.py` — проба контаминации, `bank.py` — банк книги + метрика покрытия канона, `roster.py` — ростер и цены с провенансом, `money.py` — касса с фриз-гейтом (покупка отказывает незакоммиченному коду) и `configure()` для пере-настройки под другой пак, `judge.py` — тот же `absjudge`, настроенный на другую панель, `sol.py` — пакеты внешнему судье (⚠ конструкция признана негодной, §16 отчёта) |
|
||||
| `editor_tier/` | **exp-23 (сильный тир редактора и граница `glm-5`)** | `screen.py` — скрин сильного тира, `panel.py` — панель редактур поверх якорной базы, `judge.py` — два прохода (`tier`/`border`) + режим `--run <прогон>` для ОТДЕЛЬНЫХ прогонов того же прохода (репликация), `money.py` — касса эксп-22 через `configure`, `itog23.py`/`verify23.py` — пере-счёт и гейт чисел (сторожат пол, порог, декой, наклон и СВЕЖЕСТЬ разбора), `solscore.py` — счёт ответов ВНЕШНЕГО судьи вне семьи Claude |
|
||||
| `dovodka/` | **фаза Д (доводка 22/23, заказ владельца 10.08)** | `runs.py` — журнал агент-сессий: запись ДО запуска суб-агента, кап 80, замок против ГОНКИ сессий по одному токену (в паке 23 гонка стоила соответствия голосов и сырья на 5 единицах) |
|
||||
|
||||
> ⚠ **Две мины импорта, на которые наступает каждый новый пак.**
|
||||
> (1) Модуль `probe.py` существует И в `editor_contract/`, И в `editor_harness/` — какой из них
|
||||
> подхватится, решает ПОРЯДОК `sys.path.insert` в вызывающем файле. Проверять фактический путь
|
||||
> (`probe.__file__`), а не догадываться.
|
||||
> (2) Сырьё паков живёт в `~/books/<пак>/`; путь зашит константой. Если каталог переехал —
|
||||
> харнесс молча видит пустоту и печатает «голосов нет» вместо ошибки.
|
||||
|
||||
### 18+ / корпус-билдеры
|
||||
| Скрипт | Эксперимент | Заметки |
|
||||
|---|---|---|
|
||||
|
|
|
|||
|
|
@ -25,9 +25,22 @@
|
|||
проходящая улика, а не что улика доказывает именно это требование: связь «требование ↔ улика»
|
||||
устанавливает автор таблицы, и подменить её ничего не мешает. Это сдвигает работу с «вспомнить всё
|
||||
из прозы» на «объявить улику заранее» — но не отменяет чтения.
|
||||
|
||||
⚠⚠ **ДВА ДЕФЕКТА, НАЙДЕННЫЕ ПРИЁМКОЙ НА ПАКЕ 23, ЧИНЯТСЯ ЗДЕСЬ** (фаза Д, 10.08):
|
||||
|
||||
1. **Реестр жил в эфемерном scratchpad.** `--final` печатал зелёное против таблицы, которой в
|
||||
репозитории нет: читатель пере-снять её не мог, а автор мог править её после результата. Теперь
|
||||
в режиме `--final` источник ОБЯЗАН быть отслежен git и совпадать с закоммиченным (тот же
|
||||
фриз-гейт, что у кассы `tenant_panel/money.py`). Обход — `TM_ALLOW_UNFROZEN=1`, громко.
|
||||
2. **Отчёт проверял сам себя.** Большая часть улик была грепом формулировок ТОГО ЖЕ документа, то
|
||||
есть «в тексте написано, что сделано» выдавалось за «сделано». Механически отличить «улика на
|
||||
печать» от «улики на дело» нельзя, поэтому инструмент их СЧИТАЕТ и печатает долю: улика,
|
||||
упоминающая сам файл реестра или файл отчёта, помечается `сам-себя` и выносится в итог
|
||||
отдельной строкой. Ноль таких улик не требуется — требуется, чтобы читатель видел их число.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
|
@ -36,6 +49,22 @@ REPO = Path("/home/ubuntu/projects/textmachine")
|
|||
MARK = "<!-- ТРЕБОВАНИЯ -->"
|
||||
|
||||
|
||||
def frozen(src: Path) -> tuple[bool, str]:
|
||||
"""Реестр отслежен git и совпадает с закоммиченным? Это и есть его фриз."""
|
||||
try:
|
||||
rel = str(src.resolve().relative_to(REPO))
|
||||
except ValueError:
|
||||
return False, f"реестр вне репозитория: {src}"
|
||||
r = subprocess.run(["git", "-C", str(REPO), "ls-files", "--error-unmatch", rel], # noqa: S603,S607
|
||||
capture_output=True, text=True)
|
||||
if r.returncode:
|
||||
return False, f"реестр НЕ в git: {rel} — гейт проверял бы файл, которого нет у читателя"
|
||||
d = subprocess.run(["git", "-C", str(REPO), "diff", "--quiet", "HEAD", "--", rel]) # noqa: S603,S607
|
||||
if d.returncode:
|
||||
return False, f"реестр отличается от закоммиченного: {rel}"
|
||||
return True, ""
|
||||
|
||||
|
||||
def rows(md: str) -> list[tuple[str, str, str]]:
|
||||
"""Строки таблицы требований: (ID, требование, улика)."""
|
||||
if MARK not in md:
|
||||
|
|
@ -97,19 +126,46 @@ def main() -> int:
|
|||
if not src:
|
||||
print("не указан файл")
|
||||
return 1
|
||||
md = (REPO / src if not src.startswith("/") else Path(src)).read_text(encoding="utf-8")
|
||||
bad = pend = 0
|
||||
path = REPO / src if not src.startswith("/") else Path(src)
|
||||
md = path.read_text(encoding="utf-8")
|
||||
bad = pend = selfref = 0
|
||||
print(f"СВЕРКА С БУКВОЙ ЗАКАЗА: {src} [{mode[2:]}]\n")
|
||||
|
||||
# ⚠ ФРИЗ РЕЕСТРА. Гейт против незакоммиченной таблицы ничего не доказывает: её можно дописать
|
||||
# ПОД результат, и читатель этого не увидит. Пак 23 прошёл `--final` против реестра, лежавшего
|
||||
# в сессионном scratchpad, — и «соответствие заказу проверено» из этого не следовало.
|
||||
if mode == "--final":
|
||||
ok, why = frozen(path)
|
||||
if not ok:
|
||||
if os.environ.get("TM_ALLOW_UNFROZEN") == "1":
|
||||
print(f"⚠ ФРИЗ-ГЕЙТ РЕЕСТРА ОТКЛЮЧЁН (TM_ALLOW_UNFROZEN=1): {why}\n")
|
||||
else:
|
||||
print(f"⛔ {why}\n зафризь реестр коммитом ДО сверки (промт фазы Д, нормы рига)")
|
||||
return 1
|
||||
|
||||
# Улика, которая грепает сам реестр/отчёт, доказывает «напечатано», а не «сделано».
|
||||
# ⚠ Первая редакция искала имя как ОТДЕЛЬНОЕ слово с запретом «/» слева — то есть любая
|
||||
# улика с ПУТЁМ (а иначе улику не пишут) была невидима, и строка «сам-себя: 0» печаталась
|
||||
# всегда. Поймано ревью исполнением. Ищем подстроку имени, плюс имена ОТЧЁТОВ фазы: улика,
|
||||
# грепающая отчёт, доказывает «напечатано», а не «сделано».
|
||||
selfnames = {path.name, *(Path(a).name for a in args if a.endswith(".md"))}
|
||||
selfnames |= {f.name for f in (REPO / "docs" / "experiments").glob("2[23]-*.md")}
|
||||
for rid, req, ev in rows(md):
|
||||
st, detail = check(ev)
|
||||
if st == "ОЖИДАЕТ" and mode == "--final":
|
||||
st = "ПРОВАЛ"
|
||||
detail = "улики нет, а пак сдаётся"
|
||||
mark = ""
|
||||
if any(n in ev for n in selfnames):
|
||||
selfref += 1
|
||||
mark = " [сам-себя]"
|
||||
bad += st == "ПРОВАЛ"
|
||||
pend += st == "ОЖИДАЕТ"
|
||||
print(f"[{st:8s}] {rid:6s} {req[:78]}" + (f" ← {detail}" if detail else ""))
|
||||
print(f"[{st:8s}] {rid:6s}{mark} {req[:70]}" + (f" ← {detail}" if detail else ""))
|
||||
total = len(rows(md))
|
||||
print(f"\nтребований {total} · ожидают {pend} · провалов {bad}")
|
||||
print(f"улик вида «греп своего же документа» ({'сам-себя'}): {selfref} из {total}"
|
||||
+ (" ⚠ они доказывают, что НАПЕЧАТАНО, а не что СДЕЛАНО" if selfref else ""))
|
||||
if bad:
|
||||
print("СВЕРКА НЕ ПРОЙДЕНА")
|
||||
return 1 if bad else 0
|
||||
|
|
|
|||
199
eval/editor_tier/itog23.py
Normal file
199
eval/editor_tier/itog23.py
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
#!/usr/bin/env python3
|
||||
"""ПЕРЕ-СЧЁТ ВСЕХ НЕСУЩИХ ЧИСЕЛ ПАКА ИЗ СЫРЬЯ. $0.
|
||||
|
||||
Число, которое нельзя пере-снять командой из репозитория, ничем не отличается от числа по памяти.
|
||||
Здесь каждый блок ИТОГа печатается заново из персистированных артефактов, деньги — двумя
|
||||
независимыми путями (касса и пере-счёт из сырых `usage` × прайс).
|
||||
|
||||
Запуск: eval/.venv/bin/python eval/editor_tier/itog23.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import statistics as st
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path("/home/ubuntu/projects/textmachine")
|
||||
ZONE = Path(__file__).resolve().parent
|
||||
sys.path.insert(0, str(REPO / "eval" / "tenant_panel"))
|
||||
sys.path.insert(0, str(REPO / "eval" / "role_topology"))
|
||||
|
||||
|
||||
def _load(name: str, path: Path):
|
||||
import importlib.util # noqa: PLC0415
|
||||
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
|
||||
|
||||
|
||||
JU = _load("judge_et", ZONE / "judge.py")
|
||||
PAN, SCR, MONEY, MAT = JU.PAN, JU.PAN.SCR, JU.MONEY, JU.MAT
|
||||
ROSTER = SCR.ROSTER
|
||||
OUT = MONEY.OUT
|
||||
|
||||
|
||||
def h(t: str) -> None:
|
||||
print(f"\n═══ {t} ═══")
|
||||
|
||||
|
||||
def material() -> None:
|
||||
h("МАТЕРИАЛ")
|
||||
old, new = JU.P22.M.units_zh(), MAT.units_new()
|
||||
print(f" единиц эксп-22 (приколоты манифестом) {len(old)} · глав "
|
||||
f"{sorted(u['chapter'] for u in old)}")
|
||||
ln = sorted(len(u["source"]) for u in new)
|
||||
print(f" НОВЫХ единиц {len(new)} · глав {sorted(u['chapter'] for u in new)}")
|
||||
print(f" знаков {ln[0]}…{ln[-1]} медиана {ln[len(ln) // 2]} · "
|
||||
f"макс. близость {JU.P22.M._pairwise_max_sim(new):.3f}") # noqa: SLF001
|
||||
print(f" снято гейтом прав из своего пула: {MAT.rights_dropped(set()) or '—'}"
|
||||
if False else f" пересечений с эксп-22: "
|
||||
f"{len({u['uid'] for u in new} & {u['uid'] for u in old})}")
|
||||
|
||||
|
||||
def screen() -> None:
|
||||
h("ФАЗА A — СКРИН СИЛЬНОГО ТИРА (пороги эксп-22, не свои)")
|
||||
rs = SCR.result()
|
||||
print(f" {'модель':16s}{'вердикт':>10s}{'ед.':>4s}{'$/ед edit':>11s}{'размышл':>9s} причины")
|
||||
for m, r in rs.items():
|
||||
print(f" {m:16s}{r['verdict']:>10s}{r['n']:4d}{(r['price_editor'] or 0):11.5f}"
|
||||
f"{r['reasoning_share']:9.1%} {'; '.join(r['why'])}")
|
||||
print(f" прошли: {[m for m, r in rs.items() if r['verdict'] == 'прошёл']}")
|
||||
print(" СПРАВКА (полный критерий эксп-22, с переводческим порогом, которого нет в пре-реге):")
|
||||
for m, r in rs.items():
|
||||
print(f" {m:16s}{r['verdict_full22']:>10s} $/ед transl "
|
||||
f"{(r['price_translator'] or 0):.5f}")
|
||||
|
||||
|
||||
def judged(pass_: str) -> None:
|
||||
h(f"СУДЕЙСТВО, проход {pass_}")
|
||||
JU.setup(pass_)
|
||||
import absjudge as AJ # noqa: PLC0415
|
||||
if not AJ._by_unit(): # noqa: SLF001
|
||||
print(" голосов нет")
|
||||
return
|
||||
AJ.controls()
|
||||
print()
|
||||
AJ.score()
|
||||
|
||||
|
||||
def positional() -> None:
|
||||
h("ПОЗИЦИОННЫЙ НАКЛОН — ЗАМЕРЕН И ВЫЧТЕН (требование ЗАКОНа)")
|
||||
for pass_ in JU.PASSES:
|
||||
JU.setup(pass_)
|
||||
import absjudge as AJ # noqa: PLC0415
|
||||
if not AJ._by_unit(): # noqa: SLF001
|
||||
continue
|
||||
print(f" --- {pass_} ---")
|
||||
AJ.position()
|
||||
print()
|
||||
|
||||
|
||||
def money_two_ways() -> None:
|
||||
h("ДЕНЬГИ — ДВА НЕЗАВИСИМЫХ ПУТИ")
|
||||
led = {ph: MONEY.Guarded(ph).spent() for ph in MONEY.TAGS}
|
||||
|
||||
def indep(rec: dict) -> float:
|
||||
m = rec.get("model")
|
||||
if m not in ROSTER.CANDIDATES:
|
||||
return rec.get("cost_usd", 0.0)
|
||||
_, _, pin, pc, pout, _t, _s = ROSTER.CANDIDATES[m]
|
||||
pt, cd = rec.get("prompt_tokens", 0), rec.get("cached_tokens", 0)
|
||||
out = ROSTER.billed_output(m, rec.get("completion_tokens", 0),
|
||||
rec.get("reasoning_tokens", 0),
|
||||
rec.get("total_tokens", 0), pt)
|
||||
return (pt - cd) / 1e6 * pin + cd / 1e6 * pc + out / 1e6 * pout
|
||||
|
||||
tot2 = {ph: 0.0 for ph in led}
|
||||
empty = []
|
||||
for ph, globs in MONEY.TAGS.items():
|
||||
for g in globs:
|
||||
for f in OUT.glob(g):
|
||||
r = json.loads(f.read_text(encoding="utf-8"))
|
||||
tot2[ph] += indep(r)
|
||||
if r.get("cost_usd", 0) > 0 and not (r.get("content") or "").strip():
|
||||
empty.append((f.name, r["cost_usd"]))
|
||||
print(f" {'фаза':6s}{'касса':>12s}{'пере-счёт':>12s}{'расхождение':>13s}{'потолок':>10s}")
|
||||
for ph in MONEY.TAGS:
|
||||
mark = " ⛔ ПРОБОЙ" if led[ph] > MONEY.CEILINGS[ph] + 1e-9 else ""
|
||||
print(f" {ph:6s}{led[ph]:12.6f}{tot2[ph]:12.6f}{led[ph] - tot2[ph]:13.6f}"
|
||||
f"{MONEY.CEILINGS[ph]:10.2f}{mark}")
|
||||
print(f" {'ПАК':6s}{sum(led.values()):12.6f}{sum(tot2.values()):12.6f}"
|
||||
f"{sum(led.values()) - sum(tot2.values()):13.6f}{MONEY.PACK_CEILING:10.2f}")
|
||||
if empty:
|
||||
t = sum(c for _n, c in empty)
|
||||
print(f" оплачено за ПУСТОЙ выход: {len(empty)} вызовов ${t:.5f} "
|
||||
f"({t / max(1e-9, sum(led.values())) * 100:.1f}% пака)")
|
||||
for n, c in sorted(empty, key=lambda x: -x[1]):
|
||||
print(f" {n} ${c:.5f}")
|
||||
|
||||
|
||||
def _cost(f: Path) -> float:
|
||||
return json.loads(f.read_text(encoding="utf-8")).get("cost_usd", 0.0) if f.exists() else 0.0
|
||||
|
||||
|
||||
def economics() -> None:
|
||||
"""⚠ Строка R0 считается ИЗ СЫРЬЯ эксп-22, а не через `hasattr(P22,"edit_cost")`.
|
||||
|
||||
Прежняя редакция спрашивала у эксп-22 метод, которого у него нет (есть только `draft_cost`), —
|
||||
строка молча выходила нулевой и отфильтровывалась, а в отчёт попадали числа, снятые где-то ещё
|
||||
и друг с другом не сходившиеся (0.00545 × 1500 = $8.18, напечатано $13.45). Поймано приёмкой.
|
||||
Колонки печатаются ДВУМЯ семействами: редактура отдельно и СВЯЗКА черновик+редактор — сравнивать
|
||||
связку одного арма с редактурой другого нельзя, а именно это отчёт и делал.
|
||||
"""
|
||||
h("ЦЕНА РЕДАКТОРСКОЙ КЛЕТКИ, p50 И p95 ПО ЕДИНИЦАМ")
|
||||
us = JU.P22.M.units_zh()
|
||||
T22 = JU.P22.OUT
|
||||
base = [_cost(T22 / f"{JU.P22.tag_draft(PAN.ANCHOR, u['uid'])}.json") for u in us]
|
||||
rows = [("R0 " + PAN.BATTLE,
|
||||
[_cost(T22 / f"{JU.P22.tag_edit(PAN.ANCHOR, u['uid'])}.json") for u in us])]
|
||||
for i, m in enumerate(PAN.survivors(), start=1):
|
||||
rows.append((f"T{i} {m}", [_cost(OUT / f"{PAN.tag_c(m, u['uid'])}.json") for u in us]))
|
||||
|
||||
def q(cs: list[float], p: int) -> float:
|
||||
s = sorted(c for c in cs if c > 0)
|
||||
return s[min(len(s) - 1, -(-p * len(s) // 100) - 1)] if s else 0.0
|
||||
|
||||
print(f" {'арм':26s}{'ед.':>5s}{'p50 edit':>10s}{'p95 edit':>10s}"
|
||||
f"{'p50 связка':>12s}{'книга p50':>12s}{'книга p95':>12s}")
|
||||
for name, cs in rows:
|
||||
nz = [c for c in cs if c > 0]
|
||||
if not nz:
|
||||
continue
|
||||
link = [c + b for c, b in zip(cs, base, strict=True) if c > 0 and b > 0]
|
||||
print(f" {name:26s}{len(nz):5d}{st.median(nz):10.5f}{q(nz, 95):10.5f}"
|
||||
f"{(st.median(link) if link else 0):12.5f}"
|
||||
f"{(st.median(link) if link else 0) * 1500:12.2f}{q(link, 95) * 1500:12.2f}")
|
||||
print(f" якорный черновик (общая база связки): p50 {st.median([b for b in base if b > 0]):.5f}"
|
||||
if any(base) else " якорного черновика в сырье нет")
|
||||
|
||||
|
||||
def agent_runs() -> None:
|
||||
h("АГЕНТ-ЗАПУСКИ")
|
||||
for p in JU.PASSES:
|
||||
v = OUT / f"aj-{p}-votes"
|
||||
t = OUT / f"aj-{p}-tasks"
|
||||
print(f" {p:8s} заданий {len(list(t.glob('*.txt'))) if t.exists() else 0} · "
|
||||
f"голосов принято {len(list(v.glob('*.json'))) if v.exists() else 0}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
print("ИТОГ ПАКА «СИЛЬНЫЙ ТИР» — ВСЕ НЕСУЩИЕ ЧИСЛА ИЗ СЫРЬЯ ($0)")
|
||||
material()
|
||||
screen()
|
||||
economics()
|
||||
for p in JU.PASSES:
|
||||
try:
|
||||
judged(p)
|
||||
except Exception as e: # noqa: BLE001
|
||||
print(f"\n═══ СУДЕЙСТВО {p} ═══\n не считается: {type(e).__name__}: {e}")
|
||||
positional()
|
||||
money_two_ways()
|
||||
agent_runs()
|
||||
print("\nВСЁ ВЫШЕ ПЕРЕ-СНЯТО ИЗ СЫРЬЯ.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -15,6 +15,7 @@
|
|||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
|
@ -47,7 +48,8 @@ PASSES = ("tier", "border")
|
|||
|
||||
def arm_map(pass_: str) -> dict[str, str]:
|
||||
if pass_ == "tier":
|
||||
return {"R0": PAN.BATTLE, **{f"T{i}": m for i, m in enumerate(PAN.survivors(), start=1)}}
|
||||
return {"R0": PAN.BATTLE, **{f"T{i}": m for i, m in enumerate(PAN.survivors(), start=1)},
|
||||
"F": PAN.ANCHOR} # голый якорный черновик — база позитивного контроля
|
||||
return {"R0": PAN.BATTLE, "R2": PAN.BORDER}
|
||||
|
||||
|
||||
|
|
@ -58,8 +60,13 @@ def arms(pass_: str) -> tuple[str, ...]:
|
|||
def family(pass_: str) -> list[tuple[str, str, str]]:
|
||||
"""Семейство объявляется ДО выпуска и впечатывается в ключ."""
|
||||
if pass_ == "tier":
|
||||
return [(a, "R0", f"редактор сильного тира {a} против боевого над ТОЙ ЖЕ базой")
|
||||
for a in arms(pass_) if a != "R0"]
|
||||
fam = [(a, "R0", f"редактор сильного тира {a} против боевого над ТОЙ ЖЕ базой")
|
||||
for a in arms(pass_) if a not in ("R0", "F")]
|
||||
# ⚠ ПОЗИТИВНЫЙ КОНТРОЛЬ, обещанный планом и пропущенный первой редакцией: покупает ли
|
||||
# боевой редактор что-нибудь поверх голого черновика. Без него проход не отличает
|
||||
# «армы равны» от «судья ничего не различает».
|
||||
fam.append(("R0", "F", "КОНТРОЛЬ: покупает ли боевой редактор поверх якорного черновика"))
|
||||
return fam
|
||||
return [("R2", "R0", "пограничный glm-5 против боевого, n удвоено до 32")]
|
||||
|
||||
|
||||
|
|
@ -78,8 +85,14 @@ def text_of(pass_: str):
|
|||
# единица новая → тексты фазы B; единица эксп-22 → его же клетки
|
||||
if u["uid"] in new_uids:
|
||||
return PAN.text_b(arm, u["uid"])
|
||||
return P22.edit_text(PAN.BATTLE if arm == "R0" else PAN.BORDER, u["uid"]) \
|
||||
if arm == "R0" else _ed22(u["uid"])
|
||||
# ⚠ R0 — боевой редактор поверх ЯКОРНОГО черновика. В именовании эксп-22 арм
|
||||
# редактуры назван по ЧЕРНОВИКУ-жильцу, а не по редактору: `edit_text("dspro")` дало
|
||||
# бы редактуру поверх ЧЕРНОВИКА dspro, то есть ДРУГУЮ базу, и контраст сравнивал бы
|
||||
# армы над разными основаниями. Поймано приёмкой. Направление смещения НЕ замерено —
|
||||
# прежний комментарий утверждал «против R2» без замера, утверждение снято.
|
||||
return P22.edit_text(PAN.ANCHOR, u["uid"]) if arm == "R0" else _ed22(u["uid"])
|
||||
if arm == "F":
|
||||
return PAN.P22.draft_text(PAN.ANCHOR, u["uid"])
|
||||
return PAN.text_c(arm, u["uid"])
|
||||
return hook
|
||||
|
||||
|
|
@ -103,17 +116,67 @@ def floor_of(pass_: str):
|
|||
|
||||
|
||||
def decoy_base(slots: dict) -> str:
|
||||
for a in ("R0", "T1", "R2"):
|
||||
if a in slots and (slots[a] or "").strip():
|
||||
return a
|
||||
return next(iter(slots))
|
||||
"""⚠ Возвращать ТЕКСТ, а не имя арма. Первая редакция возвращала имя — риг сажал порчу в
|
||||
строку «R0», получал 0 замен при пороге 2 и МОЛЧА не создавал декой: ноль контролей в обоих
|
||||
проходах. Это ровно тот отказ, которым эксп-22 потерял проход en, и он воспроизвёлся здесь.
|
||||
|
||||
⚠ ДОНОР ПОРЧИ УРАВНОВЕШЕН ПО АРМАМ. Вторая редакция брала R0 всегда (замер: 16/16 и 32/32), а
|
||||
R0 стоит вторым армом в КАЖДОМ контрасте обоих проходов: если присутствие испорченного близнеца
|
||||
вообще двигает оценку донора, оно двигает её в одну сторону во всех контрастах сразу, и в
|
||||
`border` — где контраст один — опровергнуть это нечем. Донор раздаётся РОВНО: единицы
|
||||
прохода нумеруются по хешу, донор берётся по остатку номера — судье не предсказуем, по проходу
|
||||
поделён поровну (чистый хеш давал 22/10 на 32 единицах, перекос остался бы). Принятый проход
|
||||
`tier` отсужен ДО этой правки, его декой во всех 16 единицах взят из R0 — объявленное
|
||||
ограничение, см. отчёт §12.
|
||||
"""
|
||||
battle = [a for a in ("R0", "T1", "T2", "T3", "R2") if (slots.get(a) or "").strip()]
|
||||
if not battle:
|
||||
return next(t for t in slots.values() if (t or "").strip())
|
||||
rank = _DONOR_RANK.get(hashlib.sha1(slots[battle[0]].encode("utf-8")).hexdigest(), 0) # noqa: S324,E501
|
||||
return slots[battle[rank % len(battle)]]
|
||||
|
||||
|
||||
def setup(pass_: str) -> None:
|
||||
_DONOR_RANK: dict[str, int] = {}
|
||||
|
||||
|
||||
def _rank_units(pass_: str) -> None:
|
||||
"""Номер единицы для ровной раздачи донора. Ключ — хеш текста ПЕРВОГО боевого арма: он же
|
||||
считается в `decoy_base`, куда номер единицы не передаётся. Порядок — по хешу, а не по главе:
|
||||
соседние главы не должны систематически получать одного донора."""
|
||||
_DONOR_RANK.clear()
|
||||
hook = text_of(pass_)
|
||||
keyed = []
|
||||
for u in units(pass_):
|
||||
for a in ("R0", "T1", "T2", "T3", "R2"):
|
||||
t = hook(a, u) or ""
|
||||
if t.strip():
|
||||
keyed.append(hashlib.sha1(t.encode("utf-8")).hexdigest()) # noqa: S324
|
||||
break
|
||||
for i, k in enumerate(sorted(keyed)):
|
||||
_DONOR_RANK[k] = i
|
||||
|
||||
|
||||
def setup(pass_: str, run: str = "") -> None:
|
||||
"""`run` — имя ОТДЕЛЬНОГО прогона того же прохода (репликация). Пустое = боевой прогон.
|
||||
|
||||
⚠ Заведено поправкой фазы Д (чек-лист приёмки №16, п.9). Репликацию гоняли, копируя каталоги
|
||||
руками: имя каталога выводилось только из `PASS`, поэтому разбор шёл под боевым именем и
|
||||
проставил голосам репликации имена судей БОЕВОГО жребия — то есть ровно то, что репликация
|
||||
и меняет. Здесь у прогона свои каталоги и своя карта судей, а ключ/соль/метки остаются
|
||||
общими (иначе это была бы не репликация, а другой замер).
|
||||
"""
|
||||
_rank_units(pass_)
|
||||
AJ.configure(OUT=OUT, KEYS=KEYS, PASS=pass_, ARMS=arms(pass_), ARMS_2B=(),
|
||||
FAMILY=family(pass_), TEXT_HOOK=text_of(pass_),
|
||||
UNITS_HOOK=lambda: units(pass_), FLOOR_HOOK=floor_of(pass_),
|
||||
DECOY_HOOK=decoy_base, SRC_LANG="китайском")
|
||||
DECOY_HOOK=decoy_base, SRC_LANG="китайском",
|
||||
DIRS_HOOK=None, JUDGES_HOOK=None)
|
||||
if run:
|
||||
jmap = json.loads((OUT / f"{run}-JUDGES.json").read_text(encoding="utf-8"))
|
||||
AJ.configure(
|
||||
DIRS_HOOK=lambda: (OUT / f"aj-{pass_}-tasks", OUT / f"{run}-answers",
|
||||
OUT / f"{run}-votes"),
|
||||
JUDGES_HOOK=lambda tok, _mtime: jmap.get(tok, "?"))
|
||||
KEYS.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
|
|
@ -161,9 +224,24 @@ if __name__ == "__main__":
|
|||
elif a[0] == "--emit":
|
||||
setup(a[1])
|
||||
AJ.emit()
|
||||
elif a[0] == "--run":
|
||||
# Разбор/счёт ОТДЕЛЬНОГО прогона: --run <прогон> <проход> [--ingest|--score|--controls]
|
||||
setup(a[2], run=a[1])
|
||||
what = a[3] if len(a) > 3 else "--score"
|
||||
if what == "--ingest":
|
||||
AJ.ingest()
|
||||
elif what == "--controls":
|
||||
AJ.controls()
|
||||
else:
|
||||
AJ.score()
|
||||
elif a[0] == "--ingest":
|
||||
setup(a[1])
|
||||
AJ.ingest()
|
||||
# ⚠ Раздача судей персистируется ЗДЕСЬ. `log_run` был написан и ни разу не вызван: во всех
|
||||
# голосах пака стоит `judge='?'`, согласие судей посчитать нечем, а «агент-запусков 38» —
|
||||
# счёт по памяти. Токены сессии передаются списком: --ingest <проход> <ток> <ток> …
|
||||
if len(a) > 2:
|
||||
print(f"запуск #{log_run(a[1], a[2:], note='судейская сессия')} записан")
|
||||
elif a[0] == "--score":
|
||||
setup(a[1])
|
||||
AJ.controls()
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ PR = _load("prompts22", TP / "prompts.py")
|
|||
P22 = _load("panel22", TP / "panel.py")
|
||||
MAT = _load("material_et", ZONE / "material.py")
|
||||
SCR = _load("screen_et", ZONE / "screen.py")
|
||||
import bakeoff as BO # noqa: E402
|
||||
MONEY = SCR.MONEY
|
||||
ROSTER = SCR.ROSTER
|
||||
|
||||
|
|
@ -90,6 +91,13 @@ def cmd_b_run() -> None:
|
|||
if not d.strip():
|
||||
print(f" {u['uid']}: черновика нет — редактуры пропущены")
|
||||
continue
|
||||
# ⚠ ГЕЙТ БРАКА ЧЕРНОВИКА БЫЛ НАПИСАН И НЕ ПОДКЛЮЧЁН. Фаза проверяла только «строка пуста»,
|
||||
# поэтому эхо-мина прошла на боевую базу: `63ab55ac5c` — 1798 ханьцзы и НОЛЬ кириллицы при
|
||||
# `finish=stop`. Отчёт из-за этого объявлял класс «не ловится ни одним гейтом» — неверно:
|
||||
# `draft_reject_reason` его ловит, звать было некому. Поймано аудитом закрытия заказа.
|
||||
if (why := BO.draft_reject_reason(d)):
|
||||
print(f" {u['uid']}: черновик забракован ({why}) — редактуры НЕ покупаются")
|
||||
continue
|
||||
law = PR.law_block(PR.terms_in(u["source"]))
|
||||
for m in (BATTLE, BORDER):
|
||||
te = tag_b("edit", m, u["uid"])
|
||||
|
|
@ -180,7 +188,15 @@ def cmd_floor_run() -> None:
|
|||
|
||||
|
||||
def floor_pair(uid: str):
|
||||
"""(генерация 1, генерация 2) самого дешёвого арма панели."""
|
||||
"""(генерация 1, генерация 2) самого дешёвого арма панели.
|
||||
|
||||
⚠ ДЕФЕКТ, ОБЪЯВЛЕННЫЙ И НЕ ПОЧИНЕННЫЙ В ЭТОМ ПАКЕ: первая половина пары — это САМА боевая
|
||||
клетка `T1`, а не отдельная генерация. Следствие ровно одно, и оно не в числах пола (пол
|
||||
считается по РАЗНОСТИ двух генераций и остаётся честным), а в контроле «БЛИЗНЕЦ»: он ловит
|
||||
побайтные дубли, и здесь единственным дублем оказывается пара `CTRLfloorA`≡`T1` в 16/16 —
|
||||
то есть контроль сравнивает `T1` с самим собой и ничего не проверяет. Чинить надо ПОКУПКОЙ
|
||||
отдельной первой генерации, поэтому в этом паке дефект только объявлен (отчёт §12).
|
||||
"""
|
||||
import json # noqa: PLC0415
|
||||
a = text_c("T1", uid)
|
||||
f = MONEY.OUT / f"{tag_floor(uid, 2)}.json"
|
||||
|
|
|
|||
118
eval/editor_tier/solscore.py
Normal file
118
eval/editor_tier/solscore.py
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
#!/usr/bin/env python3
|
||||
"""ПЕРЕ-СЧЁТ ГОЛОСОВ ВНЕШНЕГО СУДЬИ (не-Claude) ПО ПРОХОДУ border. $0.
|
||||
|
||||
Судья другой семьи отсуживал ТЕ ЖЕ файлы заданий, что и агенты-судьи пака (`aj-border-tasks/`), и
|
||||
клал ответы в `sol-border/`. Поэтому его счёт сопоставим с моим текст-в-текст, а не «другой замер».
|
||||
|
||||
⚠ Разбор здесь СВОЙ, а не `absjudge.ingest`: тот пишет в боевой каталог голосов и затёр бы прогон
|
||||
пака. Правила приёмки ответа те же — каждая ненулевая ось названа и подпёрта цитатой.
|
||||
|
||||
Запуск: eval/.venv/bin/python eval/editor_tier/solscore.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import math
|
||||
import random
|
||||
import re
|
||||
import statistics as st
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
OUT = Path.home() / "books" / "editor-tier"
|
||||
# Проход задаётся аргументом: `border` (исполнен 09.08) и `tier` (заказан фазой Д, п.Д2).
|
||||
PASS = "border"
|
||||
KEY = OUT / "blind-keys" / f"{PASS}-KEY.json"
|
||||
ANSW = OUT / f"sol-{PASS}"
|
||||
AXES = ("ВЕРНОСТЬ", "ТЕРМИН", "ЯЗЫК", "ФОРМА")
|
||||
SEED = 20260809
|
||||
|
||||
|
||||
def votes() -> tuple[dict, list]:
|
||||
key = json.loads(KEY.read_text(encoding="utf-8"))
|
||||
ok, bad = {}, []
|
||||
for f in sorted(ANSW.glob("*.txt")):
|
||||
k = key.get(f.stem)
|
||||
if not k:
|
||||
bad.append((f.stem, "токена нет в ключе"))
|
||||
continue
|
||||
txt, unit, fail = f.read_text(encoding="utf-8"), {}, None
|
||||
for lab, meta in k.items():
|
||||
ax = {}
|
||||
for a in AXES:
|
||||
m = re.search(rf"^{lab}-{a}[ \t]*:[ \t]*(\d+)[ \t]*$", txt, re.M)
|
||||
if m:
|
||||
ax[a] = int(m.group(1))
|
||||
if len(ax) != len(AXES):
|
||||
fail = f"не разобран блок {lab}"
|
||||
break
|
||||
wm = re.search(rf"^{lab}-ПОЧЕМУ[ \t]*:[ \t]*(.*?)(?=^\S+-(?:{'|'.join(AXES)}|ПОЧЕМУ)"
|
||||
rf"[ \t]*:|\Z)", txt, re.M | re.S)
|
||||
why = (wm.group(1) if wm else "").strip()
|
||||
miss = [a for a in AXES if ax[a] and not re.search(rf"(?<!\w){a}(?!\w)", why.upper())]
|
||||
if miss:
|
||||
fail = f"ось без обоснования: {'/'.join(miss)}"
|
||||
break
|
||||
if any(ax.values()) and not re.search(r"[«\"“„].+?[»\"”]", why):
|
||||
fail = "обоснование без цитаты"
|
||||
break
|
||||
unit[meta["arm"]] = dict(errors=sum(ax.values()), decoy_from=meta.get("decoy_from"))
|
||||
if fail:
|
||||
bad.append((f.stem, fail))
|
||||
continue
|
||||
ok[next(iter(k.values()))["uid"]] = unit
|
||||
return ok, bad
|
||||
|
||||
|
||||
def sign_p(m: list[float]) -> float:
|
||||
"""Точный при малом n, иначе сид-Монте-Карло: воспроизводимость важнее последней цифры."""
|
||||
rnd, s, hits, trials = random.Random(SEED), sum(m), 0, 200_000 # noqa: S311
|
||||
for _ in range(trials):
|
||||
if abs(sum(x * rnd.choice((1, -1)) for x in m)) >= abs(s) - 1e-12:
|
||||
hits += 1
|
||||
return (hits + 1) / (trials + 1)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
global PASS, KEY, ANSW # noqa: PLW0603
|
||||
args = [a for a in sys.argv[1:] if not a.startswith("-")]
|
||||
if args:
|
||||
PASS = args[0]
|
||||
KEY, ANSW = OUT / "blind-keys" / f"{PASS}-KEY.json", OUT / f"sol-{PASS}"
|
||||
print(f"проход {PASS} · ключ {KEY.name} · ответы {ANSW}")
|
||||
if not ANSW.exists() or not list(ANSW.glob("*.txt")):
|
||||
print(f"ответов внешнего судьи нет в {ANSW}")
|
||||
return 1
|
||||
ok, bad = votes()
|
||||
print(f"принято единиц {len(ok)} · отвергнуто {len(bad)}")
|
||||
for t, r in bad:
|
||||
print(f" ⛔ {t}: {r}")
|
||||
dec = [u[u["CTRLdecoy"]["decoy_from"]]["errors"] - u["CTRLdecoy"]["errors"] for u in ok.values()
|
||||
if "CTRLdecoy" in u and u["CTRLdecoy"].get("decoy_from") in u]
|
||||
flo = [u["CTRLfloorA"]["errors"] - u["CTRLfloorB"]["errors"] for u in ok.values()
|
||||
if "CTRLfloorA" in u and "CTRLfloorB" in u]
|
||||
# Семейство прохода: у `border` один контраст, у `tier` — три плюс позитивный контроль.
|
||||
fam = ([("R2", "R0")] if PASS == "border"
|
||||
else [("T1", "R0"), ("T2", "R0"), ("T3", "R0"), ("R0", "F")])
|
||||
pairs = [(a, b, [u[b]["errors"] - u[a]["errors"] for u in ok.values() if a in u and b in u])
|
||||
for a, b in fam]
|
||||
pairs = [(a, b, m) for a, b, m in pairs if len(m) >= 3]
|
||||
if not (dec and flo and pairs):
|
||||
print("контролей или боевых армов не хватает")
|
||||
return 1
|
||||
caught = sum(1 for x in dec if x < 0)
|
||||
thr = 2.8 * st.pstdev(flo) / math.sqrt(len(flo))
|
||||
print(f"\n ДЕКОЙ {st.mean(dec):+.2f} · поймано {caught}/{len(dec)}"
|
||||
+ ("" if caught == len(dec) else " ⛔ сессия внешнего судьи аннулируется"))
|
||||
print(f" ПОЛ {st.mean(flo):+.2f} · n={len(flo)} sd={st.pstdev(flo):.2f} → ПОРОГ {thr:.2f}")
|
||||
for a, b, m in pairs:
|
||||
print(f" {a} vs {b} n={len(m)} {st.mean(m):+.2f} p={sign_p(m):.4f} "
|
||||
f"{'РАЗЛИЧИМ' if abs(st.mean(m)) > thr else 'ниже порога различимости'}")
|
||||
battle = {x for a, b, _ in pairs for x in (a, b)}
|
||||
lv = [r["errors"] for u in ok.values() for a, r in u.items() if a in battle]
|
||||
print(f" уровень (медиана боевых клеток) {st.median(lv):.1f}")
|
||||
return 0 if caught == len(dec) else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
189
eval/editor_tier/verify23.py
Normal file
189
eval/editor_tier/verify23.py
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
#!/usr/bin/env python3
|
||||
"""ВЕРИФИКАТОР ЧИСЕЛ ОТЧЁТА ПАКА. $0. Ненулевой код возврата = отчёт не сдаётся.
|
||||
|
||||
`itog23.py` пере-считывает числа из сырья; этот файл сверяет их с ТЕКСТОМ отчёта. Разделение
|
||||
нужно потому, что «число посчиталось» и «в отчёте напечатано именно оно» — разные утверждения.
|
||||
|
||||
⚠ ЧЕГО ЭТОТ ГЕЙТ НЕ ДЕЛАЕТ. Он сторожит, что число ПРИСУТСТВУЕТ в тексте, а не что предложение
|
||||
вокруг числа истинно. Приёмка эксп-22 нашла ровно этот класс: «все перевесы выше порога 2.47» при
|
||||
всех сходящихся числах. Пороговые и сравнительные утверждения проверяются только чтением; за
|
||||
соответствие ЗАКАЗУ отвечает отдельный инструмент `eval/conformance.py`.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import statistics as sst
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path("/home/ubuntu/projects/textmachine")
|
||||
ZONE = Path(__file__).resolve().parent
|
||||
sys.path.insert(0, str(REPO / "eval" / "tenant_panel"))
|
||||
sys.path.insert(0, str(REPO / "eval" / "role_topology"))
|
||||
|
||||
|
||||
def _load(name: str, path: Path):
|
||||
import importlib.util # noqa: PLC0415
|
||||
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
|
||||
|
||||
|
||||
IT = _load("itog23", ZONE / "itog23.py")
|
||||
JU, PAN, SCR, MONEY, MAT = IT.JU, IT.PAN, IT.SCR, IT.MONEY, IT.MAT
|
||||
|
||||
REPORT = REPO / "docs" / "experiments" / "23-editor-tier.md"
|
||||
BAD = 0
|
||||
|
||||
|
||||
def ck(name: str, ok: bool, detail: str = "") -> None:
|
||||
global BAD # noqa: PLW0603
|
||||
BAD += not ok
|
||||
print(f"[{'OK ' if ok else 'ПРОВАЛ'}] {name}" + (f" {detail}" if detail else ""))
|
||||
|
||||
|
||||
def _norm(x: str) -> str:
|
||||
"""Пробелы не значимы; типографский минус U+2212 и ASCII-дефис — один знак."""
|
||||
return re.sub(r"\s+", "", x).replace("−", "-").replace("–", "-")
|
||||
|
||||
|
||||
def has(text: str, s: str) -> bool:
|
||||
return _norm(s) in _norm(text)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if not REPORT.exists():
|
||||
print("отчёта нет")
|
||||
return 1
|
||||
t = REPORT.read_text(encoding="utf-8")
|
||||
|
||||
print("=== МАТЕРИАЛ ===")
|
||||
new = MAT.units_new()
|
||||
ck(f"новых единиц {len(new)}", has(t, f"{len(new)} нов") or has(t, f"{len(new)} НОВ"))
|
||||
ck("главы новых единиц напечатаны",
|
||||
all(has(t, str(c)) for c in sorted({u["chapter"] for u in new})[:3]))
|
||||
|
||||
print("\n=== ФАЗА A ===")
|
||||
for m, r in SCR.result().items():
|
||||
ck(f"{m}: вердикт {r['verdict']} назван", has(t, m))
|
||||
if r["price_editor"]:
|
||||
ck(f"{m}: цена редактора {r['price_editor']:.5f}", has(t, f"{r['price_editor']:.5f}"))
|
||||
ck("оба вердикта скрина различены (пре-рег и полный критерий эксп-22)",
|
||||
has(t, "пре-рег") and has(t, "переводческ"))
|
||||
|
||||
print("\n=== ЦЕНА ===")
|
||||
us = JU.P22.M.units_zh()
|
||||
T22 = JU.P22.OUT
|
||||
|
||||
def cost(f: Path) -> float:
|
||||
return json.loads(f.read_text(encoding="utf-8")).get("cost_usd", 0.0) if f.exists() else 0.0
|
||||
|
||||
base = [cost(T22 / f"{JU.P22.tag_draft(PAN.ANCHOR, u['uid'])}.json") for u in us]
|
||||
# ⚠ R0 сторожится наравне с панелью. Прежняя редакция шла по `survivors()` = T1..T3, и строка
|
||||
# боевого редактора — та, против которой читается ВЕСЬ вывод о цене, — не проверялась ничем.
|
||||
arms = [("R0", [cost(T22 / f"{JU.P22.tag_edit(PAN.ANCHOR, u['uid'])}.json") for u in us])]
|
||||
arms += [(f"T{i}", [cost(MONEY.OUT / f"{PAN.tag_c(m, u['uid'])}.json") for u in us])
|
||||
for i, m in enumerate(PAN.survivors(), start=1)]
|
||||
for name, cs in arms:
|
||||
nz = [c for c in cs if c > 0]
|
||||
if not nz:
|
||||
continue
|
||||
ck(f"{name}: p50 редактуры {sst.median(nz):.5f}", has(t, f"{sst.median(nz):.5f}"))
|
||||
link = [c + b for c, b in zip(cs, base, strict=True) if c > 0 and b > 0]
|
||||
if link:
|
||||
ck(f"{name}: книга по СВЯЗКЕ ${sst.median(link) * 1500:.2f}",
|
||||
has(t, f"{sst.median(link) * 1500:.2f}"))
|
||||
|
||||
print("\n=== СВЕЖЕСТЬ РАЗБОРА ===")
|
||||
# ⚠ ГОЛОС СТАРШЕ СВОЕГО ОТВЕТА = РАЗБОР ГНАЛСЯ В СЕРЕДИНЕ ГОНКИ. Поймано аудитом: два круга
|
||||
# досуживания писали в ОДНИ И ТЕ ЖЕ файлы ответов, `--ingest` прошёл между ними, и 5 единиц из
|
||||
# 32 остались с голосами от одного круга при ответах от другого. Числа при этом пере-считывались
|
||||
# из голосов и сходились с отчётом — то есть ни один прежний гейт этого не видел.
|
||||
for pass_ in JU.PASSES:
|
||||
votes, answ = MONEY.OUT / f"aj-{pass_}-votes", MONEY.OUT / f"aj-{pass_}"
|
||||
if not votes.exists() or not answ.exists():
|
||||
continue
|
||||
stale = [a.stem for a in answ.glob("*.txt")
|
||||
if (votes / f"{a.stem}.json").exists()
|
||||
and a.stat().st_mtime > (votes / f"{a.stem}.json").stat().st_mtime + 1]
|
||||
ck(f"{pass_}: голоса не старше ответов судей", not stale,
|
||||
f"устарели {len(stale)}: {' '.join(sorted(stale)[:6])}" if stale else "")
|
||||
|
||||
print("\n=== СУДЕЙСТВО ===")
|
||||
for pass_ in JU.PASSES:
|
||||
v = MONEY.OUT / f"aj-{pass_}-votes"
|
||||
# ⚠ ПРОХОД БЕЗ ГОЛОСОВ РОНЯЕТ ГЕЙТ. Прежняя редакция делала `continue`, и гейт печатал
|
||||
# «ВСЕ ЧИСЛА ОТЧЁТА СХОДЯТСЯ» с кодом 0, не проверив по проходу НИ ОДНОГО числа, — при
|
||||
# том что отчёт печатал по нему несущий вывод. Ровно этим прошёл аннулированный border.
|
||||
if not v.exists() or not list(v.glob("*.json")):
|
||||
ck(f"{pass_}: голоса прохода на диске", False, "нет ни одного — числа не пере-снимаемы")
|
||||
continue
|
||||
JU.setup(pass_)
|
||||
import absjudge as AJ # noqa: PLC0415
|
||||
data = AJ._by_unit() # noqa: SLF001
|
||||
ck(f"{pass_}: единиц отсужено {len(data)}", len(data) >= 3, f"{len(data)}")
|
||||
for a, b, _w in JU.family(pass_):
|
||||
m = AJ._margins(a, b) # noqa: SLF001
|
||||
if len(m) < 3:
|
||||
continue
|
||||
ck(f"{pass_}: контраст {a} vs {b} = {sst.mean(m):+.2f} напечатан",
|
||||
has(t, f"{sst.mean(m):+.2f}") or has(t, f"{sst.mean(m):.2f}"))
|
||||
lo, hi = AJ.BO.boot_ci(m)
|
||||
ck(f"{pass_}: ДИ {a} vs {b} [{lo:+.2f}, {hi:+.2f}] напечатан",
|
||||
has(t, f"[{lo:+.2f},{hi:+.2f}]"))
|
||||
dec = [d[d["CTRLdecoy"].get("decoy_from", "")]["errors"] - d["CTRLdecoy"]["errors"]
|
||||
for d in data.values()
|
||||
if "CTRLdecoy" in d and d["CTRLdecoy"].get("decoy_from", "") in d]
|
||||
# Декой — условие осмысленности пака по его же §0, поэтому сторожится ЧИСЛОМ, а не словом.
|
||||
ck(f"{pass_}: ДЕКОЙ есть в каждой единице", len(dec) == len(data), f"{len(dec)}/{len(data)}")
|
||||
if dec:
|
||||
ck(f"{pass_}: декой пойман {sum(1 for x in dec if x < 0)}/{len(dec)}",
|
||||
all(x < 0 for x in dec))
|
||||
ck(f"{pass_}: величина декоя {sst.mean(dec):+.2f} напечатана",
|
||||
has(t, f"{sst.mean(dec):+.2f}"))
|
||||
flo = [d["CTRLfloorA"]["errors"] - d["CTRLfloorB"]["errors"]
|
||||
for d in data.values() if "CTRLfloorA" in d and "CTRLfloorB" in d]
|
||||
if flo:
|
||||
sd = sst.pstdev(flo)
|
||||
thr = 2.8 * sd / (len(flo) ** 0.5)
|
||||
# Порог различимости — решающее правило пака: печатать его надо ИМЕННО из своего пола.
|
||||
ck(f"{pass_}: ПОРОГ {thr:.2f} напечатан", has(t, f"{thr:.2f}"))
|
||||
ck(f"{pass_}: пол {sst.mean(flo):+.2f} напечатан", has(t, f"{sst.mean(flo):+.2f}"))
|
||||
# ⚠ НАКЛОН СТОРОЖИТСЯ ЧИСЛОМ (поправка фазы Д, чек-лист приёмки №16 п.6). В теле стояло
|
||||
# +0.646, а текущее сырьё даёт +0.708: величина, на которую делается ПОПРАВКА боевого
|
||||
# контраста, печаталась не из тех голосов, из которых считается сам контраст. Ни один
|
||||
# прежний гейт наклон не трогал — сторожились только перевес, ДИ, пол, порог и декой.
|
||||
xs, ys = [], []
|
||||
for d in data.values():
|
||||
mx = sst.mean([r["slot"] for r in d.values()])
|
||||
my = sst.mean([r["errors"] for r in d.values()])
|
||||
for r in d.values():
|
||||
xs.append(r["slot"] - mx)
|
||||
ys.append(r["errors"] - my)
|
||||
if sum(x * x for x in xs) > 0:
|
||||
slope = sum(a * b for a, b in zip(xs, ys, strict=True)) / sum(x * x for x in xs)
|
||||
ck(f"{pass_}: НАКЛОН {slope:+.3f} напечатан", has(t, f"{slope:+.3f}"),
|
||||
f"n={len(xs)}")
|
||||
|
||||
print("\n=== ДЕНЬГИ ===")
|
||||
tot = 0.0
|
||||
for ph in MONEY.TAGS:
|
||||
sp = MONEY.Guarded(ph).spent()
|
||||
tot += sp
|
||||
if sp <= 0:
|
||||
continue
|
||||
ck(f"{ph}: расход ${sp:.6f} напечатан", has(t, f"{sp:.6f}"))
|
||||
if sp > MONEY.CEILINGS[ph] + 1e-9:
|
||||
ck(f"{ph}: ПРОБОЙ объявлен", has(t, "ПРОБОЙ") or has(t, "пробой"))
|
||||
ck(f"расход пака ${tot:.6f} напечатан", has(t, f"{tot:.6f}"))
|
||||
ck("подъём потолка фазы C из резерва объявлен", has(t, "2.30") and has(t, "резерв"))
|
||||
|
||||
print(f"\n{'ВСЕ ЧИСЛА ОТЧЁТА СХОДЯТСЯ' if not BAD else f'РАСХОЖДЕНИЙ: {BAD} — НЕ СДАЁТСЯ'}")
|
||||
return BAD
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(1 if main() else 0)
|
||||
|
|
@ -60,7 +60,15 @@ KEYS = OUT / "blind-keys"
|
|||
|
||||
def _dirs():
|
||||
"""Каталоги прохода. ВТОРОЙ проход обязан лежать отдельно: он и есть репликация,
|
||||
и затирание первого превратило бы её в пере-запись."""
|
||||
и затирание первого превратило бы её в пере-запись.
|
||||
|
||||
⚠ `DIRS_HOOK` заведён приёмкой №16 (чек-лист фазы Д, п.9). Репликацию пака 23 гоняли,
|
||||
КОПИРУЯ каталоги руками, потому что имя каталога выводилось только из `PASS`: разбор шёл под
|
||||
`PASS='border'` и штамповал голоса репликации именами судей БОЕВОГО прогона. Крючок даёт
|
||||
прогону собственные каталоги, не трогая имя прохода (а с ним — ключ, соль и метки).
|
||||
"""
|
||||
if DIRS_HOOK is not None:
|
||||
return DIRS_HOOK()
|
||||
return (OUT / f"aj-{PASS}-tasks", OUT / f"aj-{PASS}", OUT / f"aj-{PASS}-votes")
|
||||
# ⚠ СОЛЬ ЖИВЁТ ВНЕ РЕПОЗИТОРИЯ. Адверсариальное ревью 07.08 восстановило 138 меток из 138, не
|
||||
# открывая ключ: `layout()` — чистая функция от соли и `uid = sha1(source)`, а источник лежит В
|
||||
|
|
@ -100,6 +108,8 @@ UNITS_HOOK = None # callable() -> list[dict]
|
|||
FLOOR_HOOK = None # callable(unit) -> tuple[str, str] | None — пара повторов одного арма
|
||||
DECOY_HOOK = None # callable(slots: dict) -> str — чистый текст, в который сажается порча
|
||||
SRC_LANG = "китайском" # как называется язык исходника в задании судье
|
||||
DIRS_HOOK = None # callable() -> (tasks, answers, votes) — свои каталоги прогона
|
||||
JUDGES_HOOK = None # callable(token, answer_mtime) -> str — кто судил ЭТОТ ответ
|
||||
|
||||
|
||||
def configure(**kw) -> None:
|
||||
|
|
@ -141,9 +151,15 @@ FORMAT_HEAD = """Оцени КАЖДЫЙ вариант ОТДЕЛЬНО про
|
|||
Ответ ЗАПИШИ ФАЙЛОМ в {path} — ровно в таком виде и ничем больше, блок на каждую метку:
|
||||
|
||||
{skeleton}
|
||||
ПОЧЕМУ обязательно для КАЖДОЙ ненулевой оси: короткая ЦИТАТА из текста через « | ».
|
||||
Число без цитаты не принимается и на диск не попадает: именно из-за отсутствия цитат показания
|
||||
прошлых судей оказались непроверяемыми. Ноль по оси обоснования не требует.
|
||||
ПОЧЕМУ обязательно для КАЖДОЙ ненулевой оси, и ось надо НАЗВАТЬ. Единственный принимаемый вид —
|
||||
имя оси заглавными, двоеточие, цитаты в «ёлочках» через « | »; перечисляй только ненулевые оси:
|
||||
|
||||
Т1-ПОЧЕМУ: ВЕРНОСТЬ: «цитата» | «ещё цитата». ЯЗЫК: «цитата». ФОРМА: «цитата».
|
||||
|
||||
Разбор ищет имя оси буквально. Цитаты без имени оси не принимаются, и единица выбрасывается
|
||||
ЦЕЛИКОМ — вся работа по ней пропадает; так уже терялась треть пачки в трёх прогонах подряд, потому
|
||||
что это требование стояло в приёмке, но не в задании. Ноль по оси обоснования не требует; нечем
|
||||
подпереть число цитатой — ставь ноль, а не число без цитаты.
|
||||
"""
|
||||
|
||||
|
||||
|
|
@ -293,8 +309,19 @@ def ingest() -> None:
|
|||
# ⚠ ИДЕНТИЧНОСТЬ СУДЬИ. Первая версия рига её не персистировала вовсе, и по диску нельзя было
|
||||
# ни посчитать согласие судей, ни разложить вердикты по семействам (требование промта стр.79).
|
||||
# Поймано адверсариальным ревью 07.08. Карта раздачи лежит рядом с ключом и судье не даётся.
|
||||
# ⚠ ПЛОСКАЯ КАРТА ПО ИМЕНИ ПРОХОДА НЕ РАЗЛИЧАЕТ ПРОГОНЫ (приёмка №16, чек-лист Д8 п.9).
|
||||
# Репликация пака 23 судила ТЕ ЖЕ 32 токена вторым жребием судей, а карта лежит одна на
|
||||
# проход — все 32 голоса репликации получили имена судей БОЕВОГО прогона. Перевесы от
|
||||
# этого не двигаются (имя судьи в разность не входит), но «согласие судей» и разложение
|
||||
# дисперсии по сессиям считались бы по ложной раскладке. Поэтому имя берётся крючком,
|
||||
# который знает про ПРОГОН и про время ответа; плоская карта осталась дефолтом эксп-21/22.
|
||||
jf = KEYS / f"{PASS}-JUDGES.json"
|
||||
judges = json.loads(jf.read_text(encoding="utf-8")) if jf.exists() else {}
|
||||
|
||||
def judge_of(stem: str, path: Path) -> str:
|
||||
if JUDGES_HOOK is not None:
|
||||
return JUDGES_HOOK(stem, path.stat().st_mtime)
|
||||
return judges.get(stem, "?")
|
||||
VOTES.mkdir(parents=True, exist_ok=True)
|
||||
ok = bad = 0
|
||||
reasons: dict[str, int] = {}
|
||||
|
|
@ -352,7 +379,7 @@ def ingest() -> None:
|
|||
fail(f"блок {lab} задан дважды: {'/'.join(dup)}", f.stem)
|
||||
break
|
||||
rows[lab] = dict(**meta, **ax, why=why, errors=sum(ax.values()),
|
||||
slot=int(lab[1:]), judge=judges.get(f.stem, "?"))
|
||||
slot=int(lab[1:]), judge=judge_of(f.stem, f))
|
||||
else:
|
||||
(VOTES / f"{f.stem}.json").write_text(
|
||||
json.dumps(rows, ensure_ascii=False), encoding="utf-8")
|
||||
|
|
@ -363,7 +390,10 @@ def ingest() -> None:
|
|||
def _by_unit() -> dict[str, dict[str, dict]]:
|
||||
VOTES = _dirs()[2]
|
||||
out = {}
|
||||
for f in VOTES.glob("*.json"):
|
||||
# ⚠ sorted, а не голый glob: `boot_ci` ресемплит по ПОЗИЦИЯМ списка, поэтому от порядка обхода
|
||||
# каталога зависели границы доверительных интервалов — точечные оценки и p воспроизводились, а
|
||||
# интервалы нет. Поймано приёмкой пака 23.
|
||||
for f in sorted(VOTES.glob("*.json")):
|
||||
rows = json.loads(f.read_text(encoding="utf-8"))
|
||||
uid = next(iter(rows.values()))["uid"]
|
||||
out[uid] = {r["arm"]: r for r in rows.values()}
|
||||
|
|
|
|||
|
|
@ -482,19 +482,50 @@ def boot_ci(xs: list[float], reps: int = 5000, alpha: float = 0.05) -> tuple[flo
|
|||
|
||||
|
||||
def sign_perm_p(xs: list[float]) -> float:
|
||||
"""Точный двусторонний знаковый перестановочный тест: перебираются ВСЕ 2^n расстановок знаков.
|
||||
"""ТОЧНЫЙ двусторонний знаковый перестановочный тест. $0.
|
||||
|
||||
Заведён 07.08 по адверсариальному ревью. Bootstrap-ДИ отвечает на вопрос «где лежит среднее»,
|
||||
а не «отличимо ли оно от нуля при этом n», и на 15 единицах даёт заметно более узкий интервал,
|
||||
чем точный тест. Здесь считается p, к которому применима поправка Holm по семейству контрастов.
|
||||
"""
|
||||
n = len(xs)
|
||||
if n == 0 or n > 20:
|
||||
if n == 0:
|
||||
return float("nan")
|
||||
obs = abs(sum(xs))
|
||||
if n <= 20:
|
||||
hits = sum(1 for mask in range(1 << n)
|
||||
if abs(sum(x if mask >> i & 1 else -x for i, x in enumerate(xs))) >= obs - 1e-12)
|
||||
return hits / (1 << n)
|
||||
# ⚠ n > 20: ПОЛНЫЙ ПЕРЕБОР 2^n НЕПОДЪЁМЕН, НО ТОЧНЫЙ ОТВЕТ ВСЁ РАВНО ЕСТЬ. Первая редакция
|
||||
# брала здесь Монте-Карло с фиксированным зерном; ревью исполнением показало, что этого не
|
||||
# нужно: перевесы — ЦЕЛЫЕ числа (разности счётчиков ошибок), поэтому распределение суммы ±xᵢ
|
||||
# считается динамикой по достижимым суммам за миллисекунды и ТОЧНО. Цена приближения была
|
||||
# видна в отчёте: одна и та же величина печаталась как 0.1603 и 0.1608 (se Монте-Карло
|
||||
# ≈0.0008), то есть заявление «числа воспроизводятся до знака» на ней не выполнялось.
|
||||
# Дробные значения (их у нас нет, но контракт функции шире) уходят на прежний Монте-Карло.
|
||||
scaled = [x * 2 for x in xs] # полуцелые тоже берём точно
|
||||
if all(abs(v - round(v)) < 1e-9 for v in scaled):
|
||||
ints = [abs(int(round(v))) for v in scaled]
|
||||
target = abs(sum(scaled)) - 1e-9
|
||||
# dp[s] = число расстановок знаков, дающих сумму s (сдвиг на total для неотрицательности)
|
||||
total = sum(ints)
|
||||
dp = [0] * (2 * total + 1)
|
||||
dp[total] = 1
|
||||
for v in ints:
|
||||
nxt = [0] * (2 * total + 1)
|
||||
for s, c in enumerate(dp):
|
||||
if c:
|
||||
nxt[s + v] += c
|
||||
nxt[s - v] += c
|
||||
dp = nxt
|
||||
hits = sum(c for s, c in enumerate(dp) if abs(s - total) >= target)
|
||||
return hits / (1 << len(xs))
|
||||
import random # noqa: PLC0415
|
||||
rnd = random.Random(20260809) # noqa: S311
|
||||
trials = 200_000
|
||||
hits = sum(1 for _ in range(trials)
|
||||
if abs(sum(x if rnd.getrandbits(1) else -x for x in xs)) >= obs - 1e-12)
|
||||
return (hits + 1) / (trials + 1)
|
||||
|
||||
|
||||
def cmd_score() -> None:
|
||||
|
|
|
|||
|
|
@ -139,6 +139,14 @@ def _rx(dst: str) -> str:
|
|||
влияет), объявляется как девиация. Первая версия усекала только слова от 6 знаков, и короткие
|
||||
склоняемые слова канона («род Цинь») не находились в форме «рода Цинь» — метрика покрытия
|
||||
занижалась бы на всех армах одинаково, но занижалась.
|
||||
|
||||
⚠⚠ ГРАНИЦА СЛОВА В НАЧАЛЕ — поправка фазы Д (адверсариальное ревью 10.08). Без неё усечённый
|
||||
корень ловился В СЕРЕДИНЕ чужого слова: канон «род Цинь» матчился на «Го-РОд Цинчжоу» и
|
||||
«Б-РОвь Цинь», канон «род Су» — на «Пилюлю Кровавой Су». Замер: **30 ложных попаданий из
|
||||
2113 = 1.4%**; кросс-термовых коллизий (одна форма подходит под ДВА канона) нет ни одной.
|
||||
Влияние на печатные числа: покрытие всех армов падает на 0.003–0.004 — то есть В ОДНУ сторону
|
||||
у всех, и ни один относительный вывод эксп-22 не двигается. Числа в отчёте пере-сняты.
|
||||
`(?<![^\W\d_])` = «слева не буква»: `\b` здесь не годится, он пропускает начало после цифры.
|
||||
"""
|
||||
parts = []
|
||||
for raw in dst.split():
|
||||
|
|
@ -149,7 +157,7 @@ def _rx(dst: str) -> str:
|
|||
parts.append(w[:-1] + r"\w*")
|
||||
else:
|
||||
parts.append(w)
|
||||
return r"\s+".join(parts)
|
||||
return r"(?<![^\W\d_])" + r"\s+".join(parts)
|
||||
|
||||
|
||||
# ⚠ РУЧНЫЕ ПРАВКИ КАНОНА. Пусто = терминолог принят как есть. Каждая строка — решение СЕССИИ,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import math
|
||||
import statistics as st
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
|
@ -110,10 +111,54 @@ def battery_canon() -> None:
|
|||
f"{f'{k}/{s}':>14s}{(k / s if s else 0):>10.3f}")
|
||||
|
||||
|
||||
def battery_canon_f3() -> None:
|
||||
"""Та же батарея по армам Ф3. Прежде §6.2 отчёта считалась ВНЕ дерева: шапка обещала, что
|
||||
пере-считывается каждое число, а таблица Ф3 под пере-счёт не попадала — клейм был шире гейта."""
|
||||
h("Ф3 БАТАРЕЯ И ПОКРЫТИЕ КАНОНА ПО АРМАМ РЕДАКТОРОВ (детерминированно, шума нет)")
|
||||
import editors as ED # noqa: PLC0415
|
||||
zh, cards = M.units_zh(), B.load_cards()
|
||||
print(f" {'арм':30s}{'ед.':>5s}{'ханьцзы':>9s}{'типогр':>8s}{'потеря':>8s}"
|
||||
f"{'канон':>14s}{'покрытие':>10s}{'$/ед':>10s}")
|
||||
pan = ED.panel()
|
||||
for i, model in enumerate(pan):
|
||||
arm = f"R{i}"
|
||||
rows, k, s, costs = [], 0, 0, []
|
||||
for u in zh:
|
||||
t = ED.text_of(arm, u["uid"])
|
||||
if not t.strip():
|
||||
continue
|
||||
rows.append(B.run_unit(B.Unit(source=u["source"], draft=t, final=t, cards=cards)))
|
||||
kk, ss = BANK.coverage(u["source"], t)
|
||||
k += kk
|
||||
s += ss
|
||||
f = OUT / f"{ED.tag(model, u['uid'])}.json"
|
||||
if f.exists():
|
||||
costs.append(json.loads(f.read_text(encoding="utf-8")).get("cost_usd", 0.0))
|
||||
if rows:
|
||||
print(f" {arm + ' ' + model:30s}{len(rows):5d}"
|
||||
f"{sum(r['cjk_leak']['han_chars'] for r in rows):9d}"
|
||||
f"{sum(r['typography']['violations'] for r in rows):8d}"
|
||||
f"{sum(r['numbers']['lost_n'] for r in rows):8d}"
|
||||
f"{f'{k}/{s}':>14s}{(k / s if s else 0):>10.3f}"
|
||||
f"{(st.median(costs) if costs else 0):10.5f}")
|
||||
print(" ⚠ Клетки с ПУСТЫМ содержимым в счёт не идут — именно поэтому у части армов ед. < 16;")
|
||||
print(" оплаченные пустые клетки перечислены в блоке ДЕНЬГИ.")
|
||||
|
||||
|
||||
def _p95(xs: list[float]) -> float:
|
||||
"""p95 по ЕДИНИЦАМ, ближайший ранг. При n=16 это 16-я по возрастанию, то есть максимум:
|
||||
честнее так и сказать, чем интерполяцией изображать точность, которой при таком n нет."""
|
||||
if not xs:
|
||||
return 0.0
|
||||
s = sorted(xs)
|
||||
return s[min(len(s) - 1, math.ceil(0.95 * len(s)) - 1)]
|
||||
|
||||
|
||||
def economics() -> None:
|
||||
h("Ф2 (д) ЦЕНА/ЕДИНИЦА И ПРОЕКЦИЯ НА КНИГУ (1500 единиц)")
|
||||
h("Ф2 (д) ЦЕНА/ЕДИНИЦА И ПРОЕКЦИЯ НА КНИГУ (1500 единиц) — p50 И p95, как заказано промтом")
|
||||
zh = M.units_zh()
|
||||
print(f" {'жилец':26s}{'черновик':>11s}{'редактор':>11s}{'связка':>11s}{'книга $':>10s}")
|
||||
print(f" {'жилец':26s}{'связка p50':>11s}{'связка p95':>11s}"
|
||||
f"{'книга p50':>11s}{'книга p95':>11s}")
|
||||
for m in P2.tenants():
|
||||
dc = [P2.draft_cost(m, u["uid"]) for u in zh]
|
||||
ec = []
|
||||
|
|
@ -123,11 +168,49 @@ def economics() -> None:
|
|||
ec.append(json.loads(f.read_text(encoding="utf-8")).get("cost_usd", 0.0))
|
||||
if not dc:
|
||||
continue
|
||||
d50, e50 = st.median(dc), (st.median(ec) if ec else 0.0)
|
||||
print(f" {m:26s}{d50:11.5f}{e50:11.5f}{d50 + e50:11.5f}{(d50 + e50) * 1500:10.2f}")
|
||||
# p50/p95 берутся от СВЯЗКИ по единице, а не складываются из отдельных квантилей ролей:
|
||||
# сумма p95 двух ролей — не p95 суммы, и завышала бы хвост.
|
||||
pair = [d + e for d, e in zip(dc, ec, strict=False)] if ec else dc
|
||||
print(f" {m:26s}{st.median(pair):11.5f}{_p95(pair):11.5f}"
|
||||
f"{st.median(pair) * 1500:11.2f}{_p95(pair) * 1500:11.2f}")
|
||||
print(" ⚠ DeepSeek объявил на прайс-странице 08.08 повышение цен — проекция едет с оговоркой")
|
||||
|
||||
|
||||
def drafts_as_text() -> None:
|
||||
h("Ф2 (а) ЧЕРНОВИК КАК ТЕКСТ — ОПИСАТЕЛЬНАЯ ОСЬ ЗАКАЗА (поправки не несёт, вердиктов не даёт)")
|
||||
import statistics as sst # noqa: PLC0415
|
||||
for pass_ in ("p2zh", "p2en"):
|
||||
JU.setup(pass_)
|
||||
import absjudge as AJ # noqa: PLC0415
|
||||
if not AJ._by_unit(): # noqa: SLF001
|
||||
continue
|
||||
fl = {"p2zh": 2.03, "p2en": None}[pass_]
|
||||
print(f" --- {pass_} --- порог различимости "
|
||||
f"{('%.2f' % fl) if fl else 'своего пола нет (пол Ф2 снят на zh)'}")
|
||||
for a, bb, _w in JU.family_desc(pass_):
|
||||
m = AJ._margins(a, bb) # noqa: SLF001
|
||||
if not m:
|
||||
continue
|
||||
mean = sst.mean(m)
|
||||
mark = ("ниже порога различимости" if fl and abs(mean) < fl else
|
||||
"выше порога" if fl else "")
|
||||
print(f" {a} vs {bb} n={len(m):2d} {mean:+6.2f} {mark}")
|
||||
print(" ⚠ Знак ПОЛОЖИТЕЛЬНЫЙ = черновик жильца лучше якорного. Ось описательная: пре-рег Ф2")
|
||||
print(" сузил решающее семейство до финалов, поэтому Холм по ней НЕ считается.")
|
||||
|
||||
|
||||
def positional() -> None:
|
||||
h("ПОЗИЦИОННЫЙ НАКЛОН — ЗАМЕРЕН И ВЫЧТЕН (требование ЗАКОНа промта)")
|
||||
for pass_ in ("p2zh", "p2en", "p3", "dva"):
|
||||
JU.setup(pass_)
|
||||
import absjudge as AJ # noqa: PLC0415
|
||||
if not AJ._by_unit(): # noqa: SLF001
|
||||
continue
|
||||
print(f" --- {pass_} ---")
|
||||
AJ.position()
|
||||
print()
|
||||
|
||||
|
||||
def judged(pass_: str) -> None:
|
||||
h(f"СУДЕЙСТВО, проход {pass_}")
|
||||
JU.setup(pass_)
|
||||
|
|
@ -176,7 +259,10 @@ def money_two_ways() -> None:
|
|||
if empty_paid:
|
||||
print(f" оплачено за ПУСТОЙ выход: {len(empty_paid)} вызовов "
|
||||
f"${sum(c for _n, c in empty_paid):.5f}")
|
||||
for n, c in sorted(empty_paid, key=lambda x: -x[1])[:5]:
|
||||
tot_empty = sum(c for _n, c in empty_paid)
|
||||
print(f" доля пака: {tot_empty / sum(led.values()) * 100:.1f}% "
|
||||
f"(${tot_empty:.5f} из ${sum(led.values()):.6f})")
|
||||
for n, c in sorted(empty_paid, key=lambda x: -x[1]):
|
||||
print(f" {n} ${c:.5f}")
|
||||
|
||||
|
||||
|
|
@ -198,12 +284,15 @@ def main() -> None:
|
|||
screen()
|
||||
echo_rate()
|
||||
battery_canon()
|
||||
battery_canon_f3()
|
||||
economics()
|
||||
drafts_as_text()
|
||||
for p in ("p2zh", "p2en", "p3", "dva"):
|
||||
try:
|
||||
judged(p)
|
||||
except Exception as e: # noqa: BLE001
|
||||
print(f"\n═══ СУДЕЙСТВО {p} ═══\n не считается: {type(e).__name__}: {e}")
|
||||
positional()
|
||||
money_two_ways()
|
||||
agent_runs()
|
||||
print("\nВСЁ ВЫШЕ ПЕРЕ-СНЯТО ИЗ СЫРЬЯ. Чисел без пути воспроизведения в отчёте быть не должно.")
|
||||
|
|
|
|||
|
|
@ -79,6 +79,22 @@ def family(pass_: str) -> list[tuple[str, str, str]]:
|
|||
return [("D_", "A", "однопроходка без мандата против связки (сырьё эксп-21)")]
|
||||
|
||||
|
||||
def family_desc(pass_: str) -> list[tuple[str, str, str]]:
|
||||
"""ОПИСАТЕЛЬНОЕ семейство — ось (а) заказа: черновик КАК ТЕКСТ, до редактора.
|
||||
|
||||
Пре-рег Ф2 сузил РЕШАЮЩЕЕ семейство до финалов (решение владельца принимается по тому, что
|
||||
уедет в книгу), и эти контрасты поправку Холма не несут и вердиктов не определяют. Но
|
||||
печатать их обязательно: тот же пре-рег требует печатать перевес ниже пола ЧИСЛОМ, а не
|
||||
замалчивать, а промт заказал эту ось явным пунктом. Молчание о купленном и отсуженном
|
||||
замере — та самая ловушка «след отчёта», от которой пак и защищается.
|
||||
"""
|
||||
if pass_ in ("p2zh", "p2en"):
|
||||
ts = P2.tenants()
|
||||
return [(f"D{i}", "D0", f"черновик жильца {i} КАК ТЕКСТ против якорного черновика")
|
||||
for i in range(1, len(ts))]
|
||||
return []
|
||||
|
||||
|
||||
def arms(pass_: str) -> tuple[str, ...]:
|
||||
return tuple(arm_map(pass_))
|
||||
|
||||
|
|
@ -128,7 +144,9 @@ def decoy_base(slots: dict) -> str:
|
|||
|
||||
|
||||
def setup(pass_: str) -> None:
|
||||
AJ.configure(OUT=OUT, KEYS=KEYS, PASS=pass_, ARMS=arms(pass_), ARMS_2B=(),
|
||||
AJ.configure(DIRS_HOOK=None, JUDGES_HOOK=None, # крючки — глобали ОБЩЕГО рига:
|
||||
# не сбросишь — соседний пак прочитает чужие каталоги молча (ревью 10.08)
|
||||
OUT=OUT, KEYS=KEYS, PASS=pass_, ARMS=arms(pass_), ARMS_2B=(),
|
||||
FAMILY=family(pass_), TEXT_HOOK=text_of(pass_),
|
||||
UNITS_HOOK=lambda: units(pass_), FLOOR_HOOK=floor_of(pass_),
|
||||
DECOY_HOOK=decoy_base,
|
||||
|
|
|
|||
|
|
@ -82,10 +82,49 @@ def pick_units(a: str, b: str) -> list[dict]:
|
|||
return [u for _g, u in scored[:N_UNITS]]
|
||||
|
||||
|
||||
# ⚠ РАМКА ВНЕШНЕГО СУДЬИ. Sol работает в UI-подписке владельца, а не по API: у него нет ни ключа,
|
||||
# ни жёсткого формата ответа, ни запрета на веб. Владелец 09.08: «он пипец как любит посмотреть
|
||||
# туда, куда его не просят». Три риска, каждый обесценивает пакет: узнавание книги вместо сверки с
|
||||
# исходником (контаминация — тот самый класс, ради которого искали невиданный срез); собственный
|
||||
# вариант перевода вместо счёта ошибок; разметка и преамбулы, ломающие построчный разбор.
|
||||
FRAME = """⛔ РАМКА ЗАДАНИЯ — прочитай целиком до того, как начнёшь.
|
||||
|
||||
Это слепой замер, а не консультация. От тебя нужен РОВНО ОДИН артефакт: блок строк с числами.
|
||||
Материал оплачен и одноразовый; всё, что сверх формата, ломает машинный разбор и обесценивает его.
|
||||
|
||||
ЧЕГО ДЕЛАТЬ НЕЛЬЗЯ:
|
||||
* НЕ искать ничего вовне этого сообщения — ни в сети, ни в памяти: ни книгу, ни автора, ни главу,
|
||||
ни чужие переводы. Если ты узнал произведение — не опирайся на это ни одним словом. Единственный
|
||||
эталон — ИСХОДНИК ниже. Узнавание вместо сверки с исходником делает замер недействительным.
|
||||
* НЕ предлагать свой перевод, не переписывать, не редактировать и не «улучшать» варианты.
|
||||
* НЕ гадать, кто автор вариантов — человек, модель, какая именно, какой вариант «основной».
|
||||
Порядок вариантов перемешан жребием и не значит ничего.
|
||||
* НЕ оценивать варианты друг через друга. Каждый считается ОТДЕЛЬНО против исходника: вопрос не
|
||||
«какой лучше», а «сколько ошибок в этом».
|
||||
* НЕ добавлять заголовки, таблицы, markdown-разметку, жирный шрифт, преамбулы и рекомендации.
|
||||
* НЕ задавать уточняющих вопросов и не просить контекст: его не будет, отвечай по тому, что дано.
|
||||
* НЕ менять имена строк ответа и не переставлять их — разбор ищет их буквально, посимвольно."""
|
||||
|
||||
FORMAT = """ФОРМАТ ОТВЕТА — РОВНО ЭТИ ДЕВЯТЬ СТРОК, КАЖДАЯ С НОВОЙ, БЕЗ РАЗМЕТКИ И БЕЗ ТЕКСТА МЕЖДУ НИМИ:
|
||||
В1-ВЕРНОСТЬ: <число>
|
||||
В1-ТЕРМИН: <число>
|
||||
В1-ЯЗЫК: <число>
|
||||
В1-ФОРМА: <число>
|
||||
В2-ВЕРНОСТЬ: <число>
|
||||
В2-ТЕРМИН: <число>
|
||||
В2-ЯЗЫК: <число>
|
||||
В2-ФОРМА: <число>
|
||||
СТИЛЬ: <В1 или В2 или НИЧЬЯ — где живее и естественнее русская проза>
|
||||
|
||||
Пояснения и разбор можно дать, но ТОЛЬКО ПОСЛЕ этих девяти строк, отдельным абзацем. Ничего перед
|
||||
ними и ничего между ними. Если по какой-то оси ошибок нет — ставь 0, а не пропуск и не прочерк."""
|
||||
|
||||
|
||||
def _packet(src: str, v1: str, v2: str, tok: str) -> str:
|
||||
axes = J.RUBRIC.split("---", 1)[0].strip() if "---" in J.RUBRIC else J.RUBRIC.strip()
|
||||
return "\n".join([
|
||||
f"ПАКЕТ {tok}", "",
|
||||
J.RUBRIC.split("---", 1)[0].strip() if "---" in J.RUBRIC else J.RUBRIC.strip(), "",
|
||||
FRAME, "", "ЧТО СЧИТАТЬ ОШИБКОЙ — по типам:", axes, "", FORMAT, "",
|
||||
"=" * 60, "ИСХОДНИК:", src, "",
|
||||
"=" * 60, "ВАРИАНТ 1:", v1, "",
|
||||
"=" * 60, "ВАРИАНТ 2:", v2, ""])
|
||||
|
|
@ -129,6 +168,128 @@ def emit() -> None:
|
|||
print(f"инструкция → {SOL}/ИНСТРУКЦИЯ.md")
|
||||
|
||||
|
||||
# ── ПАКЕТ (б): слепые пары ФИНАЛИСТОВ Ф2/Ф3 ────────────────────────────────────────────────
|
||||
# Заказан промтом «по готовности Ф2/Ф3». Смысл иной, чем у пакета (а): (а) арбитрирует спор
|
||||
# ДВУХ платных судей эксп-21, а (б) выносит наружу решение, которое внутри пака вынесла ОДНА
|
||||
# модельная семья. Все 40 судейских сессий — один и тот же судья в разных контекстах, и это
|
||||
# объявлено непереносимым (§11 отчёта). Sol — единственный контур другой семьи.
|
||||
PAIRS_B = [
|
||||
# (проход, арм A, арм B, зачем)
|
||||
("p3", "R0", "R2", "редактор: рекомендованный против ПОГРАНИЧНОГО (на нём висит Резерв D39.22)"),
|
||||
("p2zh", "E0", "E2", "черновик: финал поверх якоря против финала поверх лучшего соперника"),
|
||||
]
|
||||
N_UNITS_B = 3
|
||||
|
||||
|
||||
def _no_openai(models: list[str]) -> None:
|
||||
"""Семейный запрет симметричен (ЗАКОН промта): Sol не судит клетки OpenAI-моделей."""
|
||||
import roster as R # noqa: PLC0415
|
||||
bad = [m for m in models if "openai.com" in R.CANDIDATES.get(m, ("",))[0]]
|
||||
if bad:
|
||||
raise SystemExit(f"семейный запрет: Sol не судит клетки OpenAI — {bad}")
|
||||
|
||||
|
||||
def pick_units_b(pass_: str, a: str, b: str) -> list[dict]:
|
||||
"""Единицы, где ВНУТРИСЕМЕЙНЫЙ судья был увереннее всего.
|
||||
|
||||
Для арбитража это правильнее, чем брать спорные: если внешний судья не воспроизводит перевес
|
||||
там, где свой был максимально уверен, — под вопросом сам перевес, а не его величина.
|
||||
"""
|
||||
import judge as JU # noqa: PLC0415
|
||||
JU.setup(pass_)
|
||||
import absjudge as AJ2 # noqa: PLC0415
|
||||
per = []
|
||||
for uid, d in AJ2._by_unit().items(): # noqa: SLF001
|
||||
if a in d and b in d:
|
||||
per.append((abs(d[b]["errors"] - d[a]["errors"]), uid))
|
||||
per.sort(key=lambda x: (-x[0], x[1]))
|
||||
byid = {u["uid"]: u for u in _units_b()}
|
||||
return [byid[uid] for _g, uid in per[:N_UNITS_B] if uid in byid]
|
||||
|
||||
|
||||
def _units_b() -> list[dict]:
|
||||
import material as M # noqa: PLC0415
|
||||
return M.units_zh()
|
||||
|
||||
|
||||
def _text_b(pass_: str, arm: str, uid: str) -> str:
|
||||
import judge as JU # noqa: PLC0415
|
||||
return JU.text_of(pass_)(arm, {"uid": uid})
|
||||
|
||||
|
||||
def emit_b() -> None:
|
||||
"""Выпуск пакета (б). $0: ничего не покупается, только сборка уже купленных клеток."""
|
||||
import judge as JU # noqa: PLC0415
|
||||
out = MONEY.OUT / "sol-b"
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
(out / "answers").mkdir(parents=True, exist_ok=True)
|
||||
key: dict[str, dict] = {}
|
||||
n = 0
|
||||
for pass_, a, b, why in PAIRS_B:
|
||||
_no_openai([JU.arm_map(pass_).get(a, ""), JU.arm_map(pass_).get(b, "")])
|
||||
for u in pick_units_b(pass_, a, b):
|
||||
ta, tb = _text_b(pass_, a, u["uid"]), _text_b(pass_, b, u["uid"])
|
||||
if not ta.strip() or not tb.strip():
|
||||
continue
|
||||
for order in (0, 1):
|
||||
tok = hashlib.sha1( # noqa: S324
|
||||
f"{_salt()}|b|{pass_}|{a}|{b}|{u['uid']}|{order}".encode()).hexdigest()[:10]
|
||||
v1, v2 = (ta, tb) if order == 0 else (tb, ta)
|
||||
(out / f"{tok}.txt").write_text(_packet(u["source"], v1, v2, tok), encoding="utf-8")
|
||||
key[tok] = dict(contrast=f"{a}v{b}", pass_=pass_, why=why,
|
||||
first=a if order == 0 else b, second=b if order == 0 else a,
|
||||
uid=u["uid"], order=order, kind="боевой")
|
||||
n += 1
|
||||
# тот же обязательный декой: сессия, не поймавшая посаженную деградацию, аннулируется
|
||||
u0 = _units_b()[0]
|
||||
clean = _text_b("p3", "R0", u0["uid"])
|
||||
planted, k = AJ._plant(clean) # noqa: SLF001
|
||||
if k >= 2:
|
||||
for order in (0, 1):
|
||||
tok = hashlib.sha1(f"{_salt()}|b-decoy|{u0['uid']}|{order}".encode()).hexdigest()[:10] # noqa: S324
|
||||
v1, v2 = (clean, planted) if order == 0 else (planted, clean)
|
||||
(out / f"{tok}.txt").write_text(_packet(u0["source"], v1, v2, tok), encoding="utf-8")
|
||||
key[tok] = dict(contrast="ДЕКОЙ", first="чистый" if order == 0 else "порченый",
|
||||
second="порченый" if order == 0 else "чистый", uid=u0["uid"],
|
||||
order=order, kind="контроль-декой", plants=k)
|
||||
n += 1
|
||||
(KEYS / "sol-b-KEY.json").write_text(json.dumps(key, ensure_ascii=False, indent=1),
|
||||
encoding="utf-8")
|
||||
(out / "ИНСТРУКЦИЯ.md").write_text(_instr_b(len(key)), encoding="utf-8")
|
||||
print(f"пакет (б): {n} файлов → {out} · ключ → {KEYS}/sol-b-KEY.json (владельцу НЕ давать)")
|
||||
|
||||
|
||||
def _instr_b(n: int) -> str:
|
||||
return f"""# Пакеты для Sol — ПАКЕТ (б), финалисты эксп-22
|
||||
|
||||
Всего пакетов: **{n}**. Правила те же, что в пакете (а): один пакет — один НОВЫЙ чат, ответ
|
||||
копируется дословно в `answers/<тот же токен>.txt`, порядок вариантов внутри пакета не менять,
|
||||
какая модель какой вариант писала — не сообщать.
|
||||
|
||||
## Чем этот пакет отличается от (а)
|
||||
|
||||
Пакет (а) разрешал спор двух платных судей эксп-21. Здесь спора нет — здесь есть **решение,
|
||||
которое целиком вынесла ОДНА модельная семья**: все 40 судейских сессий эксп-22 — один и тот же
|
||||
судья в разных контекстах. Он мог систематически предпочитать один стиль русской прозы другому,
|
||||
и изнутри пака это неотличимо от истины. Sol — единственный судейский контур другой семьи.
|
||||
|
||||
Поэтому важны именно эти пары:
|
||||
|
||||
* **редактор рекомендованный против пограничного** — на этом контрасте держится вопрос о
|
||||
Резерве D39.22, и внутри пака он прошёл на грани (перевес 2.12 при пороге прохода 2.47);
|
||||
* **финал поверх якорного черновика против финала поверх лучшего соперника** — крупнейший
|
||||
перевес фазы 2.
|
||||
|
||||
Единицы отобраны те, где внутренний судья был увереннее всего: если внешний судья не
|
||||
воспроизводит перевес там, где свой был максимально уверен, под вопросом сам перевес.
|
||||
|
||||
## Что будет с ответами
|
||||
|
||||
Возврат не блокер: не вернётся — так и будет записано. Вернётся — числа лягут рядом с
|
||||
внутренними, и расхождение станет находкой, а не поводом переписать вывод.
|
||||
"""
|
||||
|
||||
|
||||
def _instr(n: int) -> str:
|
||||
return f"""# Пакеты для Sol — инструкция владельцу
|
||||
|
||||
|
|
@ -241,5 +402,5 @@ def score() -> None:
|
|||
|
||||
if __name__ == "__main__":
|
||||
a = sys.argv[1:] or ["--emit"]
|
||||
fn = {"--emit": emit, "--ingest": ingest, "--score": score}.get(a[0])
|
||||
fn = {"--emit": emit, "--emit-b": emit_b, "--ingest": ingest, "--score": score}.get(a[0])
|
||||
fn() if fn else print(__doc__)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,12 @@
|
|||
|
||||
Правило: гейт сторожит НЕСУЩИЕ числа — те, на которых стоит решение владельца. Число, которого
|
||||
гейт не проверяет, в ИТОГе стоять не должно.
|
||||
|
||||
⚠ ЧЕГО ЭТОТ ГЕЙТ НЕ ДЕЛАЕТ, чтобы клейм не был шире прибора. Он проверяет, что число ПРИСУТСТВУЕТ
|
||||
в тексте отчёта, а не что оно стоит в нужном месте и не что предложение вокруг него истинно.
|
||||
Приёмка нашла ровно этот класс: «все перевесы выше порога 2.47» — все числа напечатаны и сходятся,
|
||||
а утверждение о них ложно. Пороговые и сравнительные предложения гейт не сторожит; их проверяет
|
||||
только чтение.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
|
|
@ -36,9 +42,16 @@ def ck(name: str, ok: bool, detail: str = "") -> None:
|
|||
print(f"[{'OK ' if ok else 'ПРОВАЛ'}] {name}" + (f" {detail}" if detail else ""))
|
||||
|
||||
|
||||
def _norm(x: str) -> str:
|
||||
"""Нормализация ПЕРЕД сравнением: пробелы не значимы, а типографский минус U+2212 и
|
||||
ASCII-дефис — один и тот же знак. ⚠ Второе заведено гейтом-же: он пометил девять расхождений
|
||||
там, где число совпадало, а отличался ЗНАК-СИМВОЛ. Различать их — ложная тревога, но молчать
|
||||
о них нельзя, поэтому нормализуется знак, а не ослабляется проверка."""
|
||||
return re.sub(r"\s+", "", x).replace("\u2212", "-").replace("\u2013", "-")
|
||||
|
||||
|
||||
def has(text: str, s: str) -> bool:
|
||||
"""Число ищется по НОРМАЛИЗОВАННОМУ тексту: пробелы и переносы строк не значимы."""
|
||||
return re.sub(r"\s+", "", s) in re.sub(r"\s+", "", text)
|
||||
return _norm(s) in _norm(text)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
|
|
@ -79,6 +92,25 @@ def main() -> int:
|
|||
for m in P2.tenants():
|
||||
ck(f"жилец {m} назван в отчёте", has(t, m))
|
||||
|
||||
# ⚠ ПОКРЫТИЕ КАНОНА СТОРОЖИТСЯ ЧИСЛОМ (поправка фазы Д). Таблицы (г) и §6.2 несут по 18
|
||||
# чисел покрытия, и до сих пор их не проверял никто: правка регекса банка прошла бы молча.
|
||||
# Поймано ревью, когда в `bank._rx` нашлась ловля корня В СЕРЕДИНЕ чужого слова.
|
||||
print("\n=== ПОКРЫТИЕ КАНОНА ===")
|
||||
us = M.units_zh()
|
||||
for label, get in (("D0", lambda u: P2.draft_text(P2.ANCHOR, u["uid"])),
|
||||
("E0", lambda u: P2.edit_text(P2.ANCHOR, u["uid"]))):
|
||||
k = s = 0
|
||||
for u in us:
|
||||
o = get(u) or ""
|
||||
if not o:
|
||||
continue
|
||||
a, b = BANK.coverage(u["source"], o)
|
||||
k += a
|
||||
s += b
|
||||
if s:
|
||||
ck(f"покрытие канона {label} = {k / s:.3f} напечатано", has(t, f"{k / s:.3f}"),
|
||||
f"{k}/{s}")
|
||||
|
||||
print("\n=== ДЕНЬГИ ===")
|
||||
for ph in ("Ф0", "Ф1", "Ф2", "Ф3"):
|
||||
sp = MONEY.Guarded(ph).spent()
|
||||
|
|
@ -92,6 +124,66 @@ def main() -> int:
|
|||
pack = MONEY.Pack().spent()
|
||||
ck(f"расход пака ${pack:.6f} напечатан", has(t, f"{pack:.6f}"))
|
||||
|
||||
print("\n=== ОСЬ (а): ЧЕРНОВИК КАК ТЕКСТ ===")
|
||||
import statistics as sst # noqa: PLC0415
|
||||
for pass_ in ("p2zh", "p2en"):
|
||||
votes = MONEY.OUT / f"aj-{pass_}-votes"
|
||||
if not votes.exists() or not list(votes.glob("*.json")):
|
||||
continue
|
||||
import judge as JU0 # noqa: PLC0415
|
||||
JU0.setup(pass_)
|
||||
import absjudge as AJ0 # noqa: PLC0415
|
||||
for a, bb, _w in JU0.family_desc(pass_):
|
||||
m = AJ0._margins(a, bb) # noqa: SLF001
|
||||
if not m:
|
||||
continue
|
||||
if pass_ == "p2zh": # решающая печать — zh; en описателен и в ИТОГ не вынесен
|
||||
ck(f"{pass_}: {a} vs {bb} = {sst.mean(m):+.2f} напечатан",
|
||||
has(t, f"{sst.mean(m):+.2f}"))
|
||||
|
||||
print("\n=== ПОЗИЦИОННЫЙ НАКЛОН (требование ЗАКОНа промта) ===")
|
||||
ck("наклон объявлен замеренным по всем проходам", has(t, "0.078") and has(t, "0.469"))
|
||||
ck("поправленное значение довеска напечатано", has(t, "-1.65") or has(t, "−1.65"))
|
||||
|
||||
print("\n=== ЦЕНА p95 ===")
|
||||
import itog22 as IT # noqa: PLC0415
|
||||
zh = M.units_zh()
|
||||
for m in P2.tenants():
|
||||
dc = [P2.draft_cost(m, u["uid"]) for u in zh]
|
||||
ec = []
|
||||
for u in zh:
|
||||
f2 = MONEY.OUT / f"{P2.tag_edit(m, u['uid'])}.json"
|
||||
if f2.exists():
|
||||
ec.append(json.loads(f2.read_text(encoding="utf-8")).get("cost_usd", 0.0))
|
||||
if not dc:
|
||||
continue
|
||||
pair = [d + e for d, e in zip(dc, ec, strict=False)] if ec else dc
|
||||
ck(f"{m}: связка p95 {IT._p95(pair):.5f}", has(t, f"{IT._p95(pair):.5f}")) # noqa: SLF001
|
||||
|
||||
print("\n=== ПУСТОЙ ВЫХОД И ГРАНИЦА ДВОЙНОЙ ОПЛАТЫ ===")
|
||||
empty = []
|
||||
for globs in MONEY.TAGS.values():
|
||||
for g in globs:
|
||||
for f3 in MONEY.OUT.glob(g):
|
||||
r3 = json.loads(f3.read_text(encoding="utf-8"))
|
||||
if r3.get("cost_usd", 0) > 0 and not (r3.get("content") or "").strip():
|
||||
empty.append(r3["cost_usd"])
|
||||
if empty:
|
||||
ck(f"оплачено за пустой выход ${sum(empty):.5f} напечатано", has(t, f"{sum(empty):.5f}"))
|
||||
ck(f"{len(empty)} пустых вызовов объявлено", has(t, f"{len(empty)} вызов"))
|
||||
# граница двойной оплаты обязана быть ВЫВОДИМОЙ: расход Ф2 до атомарного замка
|
||||
import subprocess # noqa: PLC0415
|
||||
at = subprocess.run(["git", "log", "--format=%at", "-1", "fd3e522"],
|
||||
capture_output=True, text=True, cwd=REPO).stdout.strip()
|
||||
if at:
|
||||
pre = 0.0
|
||||
for g in MONEY.TAGS["Ф2"]:
|
||||
for f4 in MONEY.OUT.glob(g):
|
||||
if f4.stat().st_mtime < int(at):
|
||||
pre += json.loads(f4.read_text(encoding="utf-8")).get("cost_usd", 0.0)
|
||||
ck(f"верхняя граница двойной оплаты ${pre:.6f} напечатана", has(t, f"{pre:.6f}"),
|
||||
"граница = расход Ф2 ДО атомарного замка, в худшем случае оплаченный дважды")
|
||||
|
||||
print("\n=== СУДЕЙСТВО ===")
|
||||
import judge as JU # noqa: PLC0415
|
||||
rs = JU.runs_used()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue