diff --git a/eval/dovodka/rol.py b/eval/dovodka/rol.py index 405bee14..3b0c2a6f 100644 --- a/eval/dovodka/rol.py +++ b/eval/dovodka/rol.py @@ -573,6 +573,113 @@ def cmd_selftest() -> int: return bad +# ── СЛЕПОЕ ЧТЕНИЕ ПАНЕЛИ ЭТОГО ЗАМЕРА ──────────────────────────────────────────────────────── +# ⚠ ПЕРВИЧНЫЙ ПРИБОР КОНТРАСТА — ЧТЕНИЕ, А НЕ СЧЁТ (решение владельца 20.08). Основание замерено: +# счётчик ошибок согласен с владельцем на китайской паре (+0.80) и АНТИ-согласен на английской +# (−0.80), а слепое чтение — +0.80 на обеих. Всё, что новый промт меняет (вольность, регистр), +# лежит ровно в осях, к которым счётчик слеп, поэтому судить им этот контраст значит гарантированно +# получить «неотличимо» и не понять, эффекта нет или прибора нет. +# ⚠ РАМКА ПАКЕТА НЕЙТРАЛЬНА. Норма, заведённая 20.08 после того, как я нашёл подсказку в СВОЁМ же +# пакете («вариант с наименьшим числом ошибок читался почти хуже всех»): пакет не имеет права +# называть читателю ни вердикт другого прибора, ни существование контроля. Замер показал, что +# подсказка стоила 0.20 Спирмена — верх и низ порядка устояли, но средние места переставились. +# ⚠ Перетасовка меток берётся у `chtenie.layout` — второй реализации того же не заводится. +CH = _load("chtenie_r", ZONE / "chtenie.py") +READ_SALT = "rol-2026-08-20" +READ_PANEL = ("ZP", "RN", "Z0", "ZF") # склейка · новая роль · боевая связка · её 2-я генерация +READ_DIR = Path.home() / "books" / "chtenie-rol" +READ_KEY = OUT / "blind-keys-rol" / "rol-KEY.json" +READ_HEAD = """ +Ниже {n} перевода одного и того же отрывка, сделанных разными способами. Какой чем сделан — не +сказано намеренно, метки перемешаны, расшифровка лежит отдельно и до твоего ответа не открывается. +Метки сквозные: `Т1` в первом отрывке и `Т1` во втором — один и тот же способ. + +Твоя задача одна: **расставить варианты по тому, какой ты взял бы в книгу** — как русскую +художественную прозу, при условии что смысл исходника не переврали. Исходник приложен. + +## Что записать — в файл `{answer}` + +1. **ПОРЯДОК** — строкой: `Т3 > Т1 > Т2 > Т4`, от лучшего к худшему. Неразличимые соединяй `=`. +2. **По фразе на вариант**: почему. + +Если какой-то вариант покажется откровенно машинным — скажи прямо. + +--- +""" + + +def read_texts(p: str, uid: str) -> dict: + """Тексты панели чтения. Пусто у любого арма — глава в пакет не идёт целиком.""" + out = {"_src": next(u["source"] for u in chosen(p) if u["uid"] == uid)} + for a in READ_PANEL: + t = free_text(p, a, uid) if a in FREE else ( + cell(tag(p, a, uid)) if a in ARMS else ZK.cell(ZK.tag(p, a, uid))) + out[a] = t + return out + + +def emit_read(p: str, n_units: int = 3) -> None: + """Пакет слепого чтения: ключ пишется ДО ответов, раскладка ОДНА на пару.""" + cfg = PAIRS[p] + cfg["wire"]() + full = [] + for u in chosen(p): + tx = read_texts(p, u["uid"]) + if all(tx[a].strip() for a in READ_PANEL): + full.append((u["uid"], tx)) + full.sort(key=lambda x: -sum(len(x[1][a]) for a in READ_PANEL)) + take = full[:n_units] + if not take: + raise SystemExit(f"⛔ у пары {p} нет ни одной главы с ПОЛНОЙ панелью {READ_PANEL}") + READ_DIR.mkdir(parents=True, exist_ok=True) + READ_KEY.parent.mkdir(parents=True, exist_ok=True) + key = json.loads(READ_KEY.read_text(encoding="utf-8")) if READ_KEY.exists() else { + "_что": "слепое чтение панели замера «однопроходка как роль»", + "_соль": READ_SALT, "_панель": list(READ_PANEL), + "_контроль": "Z0 и ZF — две генерации ОДНОЙ боевой связки"} + ans = READ_DIR / f"ОТВЕТ-{p}.md" + lay = CH.layout(p, f"ROL-ALL|{READ_SALT}", "READER", READ_PANEL) + body = [f"# {'КИТАЙСКАЯ' if p == 'zh' else 'АНГЛИЙСКАЯ'} ПАРА — слепое чтение\n", + READ_HEAD.format(n=len(READ_PANEL), answer=ans)] + for j, (uid, tx) in enumerate(take, 1): + body += [f"\n# ОТРЫВОК {j}\n", + f"## Исходник ({'китайский' if p == 'zh' else 'английский'})\n", tx["_src"], ""] + for lab, arm in lay.items(): + body += ["\n---\n", f"## Отрывок {j}, вариант {lab}\n", tx[arm], ""] + key.setdefault(p, {})[str(j)] = {"uid": uid, "метки": lay, + "знаков": {a: len(tx[a]) for a in READ_PANEL}} + (READ_DIR / f"ЧТЕНИЕ-{p}.md").write_text("\n".join(body), encoding="utf-8") + if not ans.exists(): + ans.write_text(f"# Ответ — пара {p}\n\nПОРЯДОК: \n\nПо вариантам:\n", encoding="utf-8") + READ_KEY.write_text(json.dumps(key, ensure_ascii=False, indent=1), encoding="utf-8") + print(f" {p}: глав {len(take)} · вариантов {len(READ_PANEL)} · " + f"{sum(len(tx[a]) for _u, tx in take for a in READ_PANEL) // 1000} тыс. знаков") + print(f" пакет → {READ_DIR / f'ЧТЕНИЕ-{p}.md'} · ключ → {READ_KEY} (читателю НЕ давать)") + + +def score_read() -> None: + if not READ_KEY.exists(): + raise SystemExit("⛔ ключа нет — сначала --emit-read") + key = json.loads(READ_KEY.read_text(encoding="utf-8")) + import re as _re # noqa: PLC0415 + for p in PAIRS: + f = READ_DIR / f"ОТВЕТ-{p}.md" + if not f.exists() or p not in key: + continue + txt = f.read_text(encoding="utf-8", errors="replace") + seen: list[str] = [] + for x in _re.findall(r"Т\d+", txt): + if x not in seen: + seen.append(x) + lay = key[p]["1"]["метки"] + order = [lay[x] for x in seen if x in lay] + if len(order) < len(READ_PANEL): + print(f" {p}: порядок не разобран ({seen})") + continue + print(f" {p}: {' > '.join(order)}") + print(f" контроль шума Z0/ZF на местах {order.index('Z0') + 1} и {order.index('ZF') + 1}") + + if __name__ == "__main__": a = sys.argv[1:] or ["--dry"] if a[0] == "--selftest": @@ -583,6 +690,11 @@ if __name__ == "__main__": cmd_show(next((x for x in a[1:] if x in PAIRS), "zh")) elif a[0] == "--canon": canon() + elif a[0] == "--emit-read": + for x in ([y for y in a[1:] if y in PAIRS] or list(PAIRS)): + emit_read(x) + elif a[0] == "--score-read": + score_read() elif a[0] == "--coverage": for p in ([x for x in a[1:] if x in PAIRS] or list(PAIRS)): print(f"\n=== ПОКРЫТИЕ БОЕВЫХ ПРОМТОВ, пара {p} ===")