#!/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("АРМ 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__))()