#!/usr/bin/env python3 """$0 детерминированная батарея по армам (задачи A и B). Судья не участвует. Приборы: · ЭХО — `cjk_share > 0.15`, определение движка (`disposition.go:269-281`, Han|Hiragana|Katakana); порог тот же, что у P4, поэтому числа сравнимы с 09.07 напрямую. · ЯЗЫК ВЫХОДА — доля кириллицы против латиницы среди БУКВ. Это встречный риск en-промпта, названный в промте сессии: английская инструкция может утянуть выход в английский. · АНТИ-ОМИССИЯ — len_ratio (симв. без пробелов, коридор пары [2.2, 4.2] из `pairs/zh-ru.yaml`) и отношение числа предложений (оракул `refusal_bench.split_sentences`). · ФОРМА — преамбула, markdown-заголовок, утёкший разделитель банкноты, латинские прогоны. · ПРОВОД — reasoning_tokens (сырьё, движок их для deepseek сознательно обнуляет), finish, цена. """ from __future__ import annotations import argparse import json import re import sys import unicodedata from pathlib import Path REPO = Path("/home/ubuntu/projects/textmachine") sys.path.insert(0, str(REPO / "eval")) from refusal_bench import split_sentences # noqa: F401 (оракул уже применён в run_arms) PL = Path.home() / "books" / "gu-zhenren" / "promptlang" PREAMBLE_RE = re.compile(r"^\s*(вот|ниже|перевод[:\s]|here is|translation[:\s]|note[:\s])", re.I) MD_RE = re.compile(r"^\s{0,3}#{1,6}\s", re.M) LAT_RUN_RE = re.compile(r"[A-Za-z]{3,}") def script_mix(s: str) -> tuple[float, float, int]: cyr = lat = 0 for ch in s: if not ch.isalpha(): continue n = unicodedata.name(ch, "") if n.startswith("CYRILLIC"): cyr += 1 elif n.startswith("LATIN"): lat += 1 tot = cyr + lat return (cyr / tot if tot else 0.0), (lat / tot if tot else 0.0), tot def main() -> int: ap = argparse.ArgumentParser() ap.add_argument("--arms", default="a0,a1,a2,e0") ap.add_argument("--raw", type=Path, default=PL / "armsraw") a = ap.parse_args() per = {} for arm in a.arms.split(","): rs = [] for f in sorted(a.raw.glob(f"{arm}-f*.json")): j = json.loads(f.read_text(encoding="utf-8")) if "error" in j: rs.append(j) continue c = j["content"] cyr, lat, letters = script_mix(c) j["cyr_share"] = round(cyr, 4) j["lat_share"] = round(lat, 4) j["off_language"] = bool(letters) and cyr < 0.5 # выход не в целевом письме j["preamble"] = bool(PREAMBLE_RE.match(c)) j["markdown"] = bool(MD_RE.search(c)) j["bank_marker"] = "⟦TM-BANK" in c j["latin_runs"] = len(LAT_RUN_RE.findall(c)) j["in_corridor"] = (j["len_ratio"] is not None and 2.2 <= j["len_ratio"] <= 4.2) rs.append(j) per[arm] = rs print(f"{'арм':<5}{'n':>3}{'эхо':>5}{'off_lang':>9}{'вне кор.':>9}{'преамб':>8}{'md':>4}" f"{'лат.прог':>9}{'ср.lr':>7}{'ср.sent':>9}{'ср.rt':>7}{'сумма $':>10}") for arm, rs in per.items(): ok = [r for r in rs if "error" not in r] if not ok: continue clean = [r for r in ok if not r["echo"]] lr = [r["len_ratio"] for r in clean if r["len_ratio"] is not None] sr = [r["sent_out"] / r["sent_src"] for r in clean if r["sent_src"]] print(f"{arm:<5}{len(ok):>3}{sum(r['echo'] for r in ok):>5}" f"{sum(r['off_language'] for r in ok):>9}" f"{sum(1 for r in clean if not r['in_corridor']):>9}" f"{sum(r['preamble'] for r in ok):>8}{sum(r['markdown'] for r in ok):>4}" f"{sum(r['latin_runs'] for r in ok):>9}" f"{(sum(lr)/len(lr) if lr else 0):>7.2f}{(sum(sr)/len(sr) if sr else 0):>9.2f}" f"{sum(r['reasoning_tokens'] for r in ok)/len(ok):>7.0f}" f"{sum(r['cost_usd'] for r in ok):>10.6f}") print("\nпофрагментно (эхо и коридор — по фрагментам, чтобы видеть, ГДЕ именно):") frags = sorted({r["frag"] for rs in per.values() for r in rs}) print(f"{'фраг':<6}" + "".join(f"{arm:>18}" for arm in per)) for fr in frags: cells = [] for arm, rs in per.items(): r = next((x for x in rs if x["frag"] == fr), None) if r is None or "error" in r: cells.append(f"{'—':>18}") continue mark = "ЭХО" if r["echo"] else ("OFFLANG" if r["off_language"] else ("вне кор." if not r["in_corridor"] else "ok")) cells.append(f"{mark + ' lr=' + str(r['len_ratio']):>18}") print(f"{fr:<6}" + "".join(cells)) out = PL / "analyze_arms.json" out.write_text(json.dumps({k: [{kk: vv for kk, vv in r.items() if kk not in ("system", "user", "reasoning_content")} for r in v] for k, v in per.items()}, ensure_ascii=False, indent=1), encoding="utf-8") print(f"\nсырьё сведено: {out}") return 0 if __name__ == "__main__": raise SystemExit(main())