#!/usr/bin/env python3 """Ф0.6 (часть) — калибровка гейта арма G НА ЦЕЛОЙ ЕДИНИЦЕ, без выравнивания. $0. Зачем отдельно от `qe_guard.py`. Посегментный замер требует сопоставить черновиковый сегмент с финальным, а редактор меняет разбиение — и любое сопоставление вносит смещение: • точное совпадение групп теряет 596 сегментов из 1143, причём СИСТЕМАТИЧЕСКИ там, где редактор перестраивал предложения, то есть где он и работал (смещение в сторону консервативных правок); • пересечение групп теряет ноль, но при слиянии предложений даёт финальной стороне более широкий кусок источника, чем черновой (смещение В ПОЛЬЗУ финала). Единица целиком не требует выравнивания вовсе ⇒ у неё нет ни того, ни другого смещения. Это и есть арбитр между двумя посегментными числами. Что решается. Арм G принимает правку, только если внешний гейт видит улучшение. Здесь меряется, что гейт скажет о РЕАЛЬНОЙ редакторской правке, которая уже сделана и лежит в корпусе пакета-6. ⚠ Шкала перевёрнута: Δ = QE(final) − QE(draft); Δ<0 значит «редактор улучшил по мнению гейта». Запуск (офлайн, $0, ~25 мин на CPU): ~/.venvs/tm-qe/bin/python eval/role_topology/qe_guard_units.py """ from __future__ import annotations import json import os import statistics import sys from math import comb from pathlib import Path REPO = Path("/home/ubuntu/projects/textmachine") sys.path.insert(0, str(REPO / "eval" / "role_topology")) from qe_bench import QE, MODEL # noqa: E402 CORPUS = Path.home() / "books" / "gu-zhenren" / "labels" / "raw" / "corpus.jsonl" OUT = Path.home() / "books" / "role-topology" def two_sided_sign_p(better: int, n: int) -> float: k = max(better, n - better) return min(1.0, 2 * sum(comb(n, i) for i in range(k, n + 1)) / (2 ** n)) def main() -> None: units = [json.loads(l) for l in CORPUS.read_text(encoding="utf-8").splitlines() if l.strip()] units = [u for u in units if all((u.get(k) or "").strip() for k in ("source", "draft", "final"))] qe = QE() rows = [dict(draft=qe.score(u["source"], u["draft"]), final=qe.score(u["source"], u["final"])) for u in units] d = [r["final"] - r["draft"] for r in rows] n = len(d) better = sum(1 for x in d if x < 0) print(f"КАЛИБРОВКА ГЕЙТА АРМА G НА ЦЕЛОЙ ЕДИНИЦЕ — модель {MODEL}") print(f"n = {n} единиц, выравнивание не применялось ⇒ потерь и смещения нет по построению\n") print(f"медиана Δ (final − draft) {statistics.median(d):+.4f}") print(f"среднее Δ {statistics.mean(d):+.4f}") print(f"редактор УЛУЧШИЛ по гейту {better}/{n} = {better / n:.0%}") print(f"двусторонний знаковый тест p = {two_sided_sign_p(better, n):.4f}") print(f"гейт видит паритет (|Δ|<0.25) {sum(1 for x in d if abs(x) < 0.25)}/{n}") print(f"|Δ| < 1.0 {sum(1 for x in d if abs(x) < 1.0)}/{n}") (OUT / "qe-guard-units-all.json").write_text(json.dumps(rows), encoding="utf-8") if __name__ == "__main__": os.environ.setdefault("PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION", "python") main()