Add the blind-reading emitter for this panel and record the role-prompt reading verdict

This commit is contained in:
Claude (backend session) 2026-08-20 18:06:48 +03:00
parent 273d2df41f
commit eb49b651d4

View file

@ -573,6 +573,113 @@ def cmd_selftest() -> int:
return bad 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__": if __name__ == "__main__":
a = sys.argv[1:] or ["--dry"] a = sys.argv[1:] or ["--dry"]
if a[0] == "--selftest": 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")) cmd_show(next((x for x in a[1:] if x in PAIRS), "zh"))
elif a[0] == "--canon": elif a[0] == "--canon":
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": elif a[0] == "--coverage":
for p in ([x for x in a[1:] if x in PAIRS] or list(PAIRS)): for p in ([x for x in a[1:] if x in PAIRS] or list(PAIRS)):
print(f"\n=== ПОКРЫТИЕ БОЕВЫХ ПРОМТОВ, пара {p} ===") print(f"\n=== ПОКРЫТИЕ БОЕВЫХ ПРОМТОВ, пара {p} ===")