#!/usr/bin/env python3 """exp13 — агрегация судейских вердиктов + гейтов + стоимости в сводку (§2). $0. Средняя позиция (1=лучшая) по осям и суммарно; разброс (leak-устойчивость); согласие судей; эхо-rate/объём из gates.json; суммарная стоимость из costs.jsonl + judge_costs.jsonl. Запуск: eval/.venv/bin/python eval/exp13_aggregate.py Выход: печать + diag/arms13/summary.json """ from __future__ import annotations import json, statistics as st from collections import defaultdict from pathlib import Path ARMS = Path("/home/ubuntu/books/gu-zhenren/diag/arms13") LEGEND = {"T1": "deepseek-v4-flash", "T2": "mistral-large-2512", "T3": "glm-5", "T4": "deepseek-v4-pro", "T5": "grok-4.3 reasoning-ON", "T6": "qwen-flash"} def load_jsonl(p): if not p.exists(): return [] return [json.loads(l) for l in p.read_text(encoding="utf-8").splitlines() if l.strip()] def positions(verdicts, axis=None, judge=None): """arm -> list of positions (1=best).""" pos = defaultdict(list) for v in verdicts: if axis and v["axis"] != axis: continue if judge and v["judge"] != judge: continue r = v["arm_ranking"] for i, arm in enumerate(r): pos[arm].append(i + 1) return pos def summarize(pos): out = {} for arm, ps in pos.items(): out[arm] = {"mean": round(st.mean(ps), 3), "n": len(ps), "std": round(st.pstdev(ps), 3) if len(ps) > 1 else 0.0} return out def table(title, pos): print(f"\n== {title} ==") rows = sorted(summarize(pos).items(), key=lambda kv: kv[1]["mean"]) print(f"{'arm':<5} {'model':<24} {'mean_pos':>8} {'std':>6} {'n':>4}") for arm, s in rows: print(f"{arm:<5} {LEGEND.get(arm, arm):<24} {s['mean']:>8} {s['std']:>6} {s['n']:>4}") return {arm: s for arm, s in rows} def main(): verdicts = load_jsonl(ARMS / "judges" / "verdicts.jsonl") print(f"валидных вердиктов: {len(verdicts)}") judges = sorted({v["judge"] for v in verdicts}) axes = sorted({v["axis"] for v in verdicts}) summary = {"n_verdicts": len(verdicts), "judges": judges, "axes": axes} summary["overall"] = table("ОБЩЕЕ (обе оси, все судьи)", positions(verdicts)) for axis in axes: summary[f"axis_{axis}"] = table(f"ось: {axis}", positions(verdicts, axis=axis)) # согласие судей (по каждой оси, средняя позиция у каждого судьи) summary["per_judge"] = {} for judge in judges: summary["per_judge"][judge] = {} for axis in axes: summary["per_judge"][judge][axis] = summarize(positions(verdicts, axis=axis, judge=judge)) # гейты gates = {} gp = ARMS / "gates.json" if gp.exists(): g = json.loads(gp.read_text(encoding="utf-8"))["per_arm"] print("\n== ГЕЙТЫ ==") print(f"{'arm':<5} {'echo_rate':>9} {'refusal':>7} {'volBad':>6} {'empty':>5} {'han_avg':>8} {'vol_avg':>7}") for arm in sorted(g): a = g[arm] print(f"{arm:<5} {a['echo_rate']:>9} {a['refusal']:>7} {a['vol_bad']:>6} {a['empty']:>5} {a['han_avg']:>8} {a['vol_avg']:>7}") gates[arm] = {k: a[k] for k in ("echo_rate", "refusal", "vol_bad", "empty", "han_avg", "vol_avg")} summary["gates"] = gates # стоимость arm_costs = load_jsonl(ARMS / "costs.jsonl") judge_costs = load_jsonl(ARMS / "judge_costs.jsonl") per_arm_cost = defaultdict(float) for r in arm_costs: per_arm_cost[r["arm"]] += r.get("cost", 0) or 0 arm_total = sum(per_arm_cost.values()) judge_total = sum((r.get("cost", 0) or 0) for r in judge_costs) print("\n== СТОИМОСТЬ ==") print(f"{'arm':<5} {'model':<24} {'$/6chunks':>10} {'$/chunk':>9}") for arm in sorted(per_arm_cost): c = per_arm_cost[arm] print(f"{arm:<5} {LEGEND.get(arm, arm):<24} {c:>10.4f} {c/6:>9.5f}") print(f"\nармы: ${arm_total:.4f} судьи: ${judge_total:.4f} ИТОГО: ${arm_total+judge_total:.4f} (кап $5)") summary["cost"] = {"per_arm": {k: round(v, 5) for k, v in per_arm_cost.items()}, "arm_total": round(arm_total, 4), "judge_total": round(judge_total, 4), "grand_total": round(arm_total + judge_total, 4)} (ARMS / "summary.json").write_text(json.dumps(summary, ensure_ascii=False, indent=1), encoding="utf-8") print("\nsummary → diag/arms13/summary.json") if __name__ == "__main__": main()