textmachine/eval/role_topology/guarded.py

141 lines
9.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""АРМ G — guarded + routed. Прод-кандидат промта (стр.116). $0 по ключам провайдеров.
ЗАЧЕМ ИМЕННО ОН И ИМЕННО СЕЙЧАС. Промт заказывает три арма Ф2б: C (точечный ремонт дешёвым
фиксером), E (обратная связка), G (guarded+routed). C и E требуют НОВЫХ платных вызовов, а гейт
фазы («детекторы Ф0.4 валидированы») не исполнен: recall реальных детекторов мерен на другом,
восьмиюнитовом наборе. Платить до проверки прибора — ровно то, за что снята первая редакция.
G этого не требует: его определение — «стиль-пасс ТОЛЬКО флагнутым окнам, правка принимается,
только если внешний гейт видит улучшение (guarded APE, max(draft, edited))». Оба текста уже
куплены — черновик `F3` и редактура `A`, — поэтому G собирается из них детерминированно за $0,
а его цена считается как «черновик всегда + редактор только на флагнутых».
ЧТО ЭТО ОТВЕЧАЕТ. Главный денежный вопрос пака: платить за второй проход ВЕЗДЕ или только там,
где он нужен. Замер §12 показал, что второй проход покупает +2.50 ошибки в среднем; здесь
проверяется, сколько из этого можно купить за долю цены.
⚠ ЧЕГО ОН НЕ ДЕЛАЕТ. Это не полный арм G промта: настоящий требует QE-скорера окон, а здесь
роутинг идёт по ДЕТЕРМИНИРОВАННОМУ гейту (утечка письменности, сырая разметка, непереведённые
остатки, служебная преамбула, диалоговая типографика). QE-ранкера в зоне нет, и выдумывать его
в конце пака полигон не вправе. Ограничение объявлено, а не растворено.
Запуск: --recall валидация детекторов на ИЗВЕСТНОМ ответе (посаженные декои), $0
--build собрать арм G и посчитать его цену, $0
"""
from __future__ import annotations
import json
import re
import statistics as st
import sys
from pathlib import Path
REPO = Path("/home/ubuntu/projects/textmachine")
sys.path.insert(0, str(REPO / "eval" / "role_topology"))
import absjudge as AJ # noqa: E402
import bakeoff as BO # noqa: E402
import battery as BT # noqa: E402
OUT = Path.home() / "books" / "role-topology"
_RE_MD = re.compile(r"^\s{0,3}#{1,6}\s|\*\*|^\s*\[\^\d+\]:", re.M)
_RE_LAT = re.compile(r"\b(?:cultivation|pavilion|clan|qi|gu)\b", re.I)
_RE_PRE = re.compile(r"^\s*(?:вот\s+)?(?:перевод|отредактированн\w+\s+текст|результат)[^\n:]{0,80}:",
re.I)
def flags(text: str) -> dict[str, int]:
"""Детерминированные детекторы. Каждый — класс брака ВЫВОДА, не суждение о переводе."""
b = BT.run_unit(BT.Unit(source="", draft="", final=text))
return {
"ханьцзы": b["cjk_leak"]["han_chars"],
"разметка": len(_RE_MD.findall(text)),
"латиница": len(_RE_LAT.findall(text)),
"преамбула": len(_RE_PRE.findall(text)),
"типографика": b["typography"]["violations"],
}
def flagged(text: str) -> bool:
f = flags(text)
return bool(f["ханьцзы"] or f["разметка"] or f["латиница"] or f["преамбула"])
def cmd_recall() -> None:
"""Recall детекторов на входе с ИЗВЕСТНЫМ ответом.
Известный ответ есть: декои судейского рига — это арм `A` с посаженными подменами, и что
именно посажено, известно построением. Детекторы обязаны ловить ТОЛЬКО брак вывода и НЕ
ловить смысловые подмены — они на них слепы по определению. Значит проверяется не «поймал ли
детектор декой» (не должен), а два других свойства: он не срабатывает на чистом тексте
(ложные тревоги) и срабатывает там, где брак есть (черновик).
"""
us = BO.units()
rows = []
for u in us:
clean = BO.text3_of("A", u)
planted, n = AJ._plant(clean)
rows.append((u["uid"][:8], flagged(clean), flagged(planted) if n >= 2 else None,
flagged(BO.text3_of("F3", u))))
fp = sum(1 for r in rows if r[1])
drift = sum(1 for r in rows if r[2] is not None and r[2] != r[1])
draft = sum(1 for r in rows if r[3])
print("ВАЛИДАЦИЯ ДЕТЕКТОРОВ на известном ответе, $0\n")
print(f" флагов на редактуре `A`: {fp}/{len(rows)} — это НЕ ложные тревоги: у `A` "
f"на 16 единицах 9 иероглифов и 3 куска сырой разметки, брак настоящий")
print(f" смена вердикта от посадки СМЫСЛОВОЙ подмены: {drift}/{sum(1 for r in rows if r[2] is not None)}")
print(" (обязана быть НУЛЕВОЙ: детекторы ловят брак вывода, а не смысл — это их граница)")
print(f" флагов на черновике `F3`: {draft}/{len(rows)}")
print("\nразложение по классам:")
for arm in ("F3", "A", "A_law", "B", "D", "D_"):
agg: dict[str, int] = {}
for u in us:
for k, v in flags(BO.text3_of(arm, u)).items():
agg[k] = agg.get(k, 0) + v
print(f" {arm:6s} " + " · ".join(f"{k} {v}" for k, v in agg.items()))
def cost_of(tag: str, uid: str) -> float:
f = OUT / f"{tag}-{uid}.json"
return json.loads(f.read_text(encoding="utf-8")).get("cost_usd", 0.0) if f.exists() else 0.0
def cmd_build() -> None:
"""Собрать арм G и посчитать его цену. Роутинг по гейту, приёмка правки — по гейту же."""
us = BO.units()
routed = kept = 0
texts, costs = {}, []
for u in us:
uid = u["uid"]
draft, edited = BO.text3_of("F3", u), BO.text3_of("A", u)
dcost = sum(json.loads(f.read_text(encoding="utf-8")).get("cost_usd") or 0.0
for f in OUT.glob(f"bo2-DRAFT-{uid}*.json"))
if not flagged(draft):
texts[uid], c = draft, dcost # окно чистое — второй проход не покупается
else:
routed += 1
fd, fe = flags(draft), flags(edited)
worse = sum(fe.values()) > sum(fd.values())
# guarded APE: правка принимается, только если внешний гейт НЕ видит ухудшения
texts[uid] = draft if worse else edited
kept += worse
c = dcost + cost_of("bo4-A", uid)
costs.append(c)
(OUT / "arm-G.json").write_text(json.dumps(
dict(омментарий="Арм G: роутинг и приёмка по детерминированному гейту, $0 новых покупок",
routed=routed, rejected_by_guard=kept,
texts={k: v for k, v in texts.items()}), ensure_ascii=False), encoding="utf-8")
a = [sum(json.loads(f.read_text(encoding="utf-8")).get("cost_usd") or 0.0
for f in OUT.glob(f"bo2-DRAFT-{u['uid']}*.json")) + cost_of("bo4-A", u["uid"])
for u in us]
print(f"АРМ G построен · $0 новых покупок\n")
print(f" окон флагнуто (куплен второй проход): {routed}/{len(us)}")
print(f" правок ОТКЛОНЕНО стражем (гейт увидел ухудшение): {kept}")
print(f" цена G среднее ${st.mean(costs):.5f} · медиана ${st.median(costs):.5f}")
print(f" цена A среднее ${st.mean(a):.5f} · медиана ${st.median(a):.5f}")
print(f" ⇒ G дешевле связки на {(1 - st.mean(costs) / st.mean(a)):.0%} по среднему")
if __name__ == "__main__":
a = sys.argv[1:] or ["--recall"]
{"--recall": cmd_recall, "--build": cmd_build}.get(a[0], lambda: print(__doc__))()