#!/usr/bin/env python3
"""offline rejudge 읽기 — **같은 그림**을 세 판정자가 어떻게 봤나.

## 왜 이렇게 재나 (Codex 조건)

새 판정 쌍을 넣은 주행에서는 **롤(후보 a/b)까지 다시 구워진다** — 선정
쌍이 샷 지문에 접히기 때문이다. 그래서 그 주행의 그림 차이를 판정자
품질로 귀속하면 안 된다. 판정자만 따로 재려면 **같은 저장 후보**를
다른 판정자로 다시 물어야 한다. 이 도구가 그 결과를 읽는다.

입력 = `judge_model_bakeoff.py --recipe-dir=<백업>` 이 샷마다 남긴
`<out>/<tag>/calls.json`. ★유료 호출 0 — 기록에서만 읽는다.

usage: read_offline_rejudge.py <결과디렉토리> [백업recipe경로]
"""
from __future__ import annotations

import json
import pathlib
import sys
from collections import Counter

sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent))
import _refs  # noqa: E402

ROOT = pathlib.Path(__file__).resolve().parents[3]
SHORT = {"google/gemini-3.1-pro-preview": "Gemini 3.1 Pro",
         "openai/gpt-5.6-sol": "GPT-5.6 Sol",
         "x-ai/grok-4.6": "Grok 4.6"}


def main() -> int:
    argv = [a for a in sys.argv[1:] if not a.startswith("--")]
    base = pathlib.Path(argv[0]) if argv else (
        ROOT / "artifact" / "20260829_offline_rejudge")
    bk = pathlib.Path(argv[1]) if len(argv) > 1 else (
        ROOT / "artifact" / "20260829_prejit_baseline"
        / "images" / "scene" / "recipe")
    if not base.is_dir():
        raise SystemExit(f"★결과 디렉토리를 못 찾았다: {base}")

    old = {}
    f = bk / "records.json"
    if f.is_file():
        old = json.loads(f.read_text())

    rows = []
    for d in sorted(base.iterdir()):
        c = d / "calls.json"
        if c.is_file():
            rows.extend(json.loads(c.read_text()))
    if not rows:
        raise SystemExit(f"★calls.json 을 하나도 못 찾았다: {base}")

    tags = sorted({r["tag"] for r in rows})

    # ★참조가 붙었는지 본다 (2026-08-29 Codex BLOCK-2). 갤러리에서 막은
    #  오독이 여기서 재발했다 — `--no-refs` 결과도 언제나 옛 프로덕션과
    #  직접 대조했다. 프로덕션 `judge_fn` 은 샷마다 참조를 2~3장 붙인다.
    #  ★판정은 `_refs.ref_mode` **한 군데**에만 있다 — 갤러리와 여기가
    #   따로 세면 한쪽만 고쳐진다.
    no_ref, ref_show = _refs.ref_mode(rows)

    print(f"샷 {len(tags)}개 · 판정 {len(rows)}건 "
          f"(같은 저장 후보 · 유료 호출 0으로 읽음)")
    if no_ref:
        print("★이 판은 **참조 이미지를 안 붙였다**. 프로덕션 judge_fn 은 "
              "샷마다 2~3장을 붙인다 — 옛 프로덕션 판정과 직접 견주지 마라.")
    else:
        print(f"참조 {ref_show}장 (프로덕션과 같게 붙임 — 샷마다 인물 수가 "
              "달라 장수가 갈린다)")
    print()

    hv = Counter(); filed = Counter(); ok = Counter(); win = {}
    print(f"{'샷':8s} {'모델':16s} {'고른 것':7s} {'하드위반':>7s}  {'시간':>7s}")
    for t in tags:
        for r in sorted((x for x in rows if x["tag"] == t),
                        key=lambda x: SHORT.get(x["model"], x["model"])):
            m = SHORT.get(r["model"], r["model"])
            if not r.get("winner"):
                print(f"{t:8s} {m:16s} ✗ 실패 {str(r.get('error'))[:40]}")
                continue
            ok[m] += 1
            n = sum(len(v) for v in (r.get("hard_by_label") or {}).values())
            hv[m] += n
            if n:
                filed[m] += 1
            win[(t, m)] = r["winner"]
            # ★`duration_s` 는 마지막 시도만 — 누적 provider 시간을 쓴다
            pc = r.get("physical_calls")
            ps = r.get("provider_s", r.get("wall_s"))
            print(f"{t:8s} {m:16s} {r['winner']:7s} {n:>6}건  "
                  f"{(ps if ps is not None else r['duration_s']):6.1f}초"
                  + (f" ×{pc}콜" if (pc or 1) > 1 else ""))
        print()

    print("═══ 같은 그림에서 누가 무엇을 올렸나 ═══")
    print(f"{'모델':16s} {'성공':>4s} {'하드위반 총':>10s} {'낸 샷':>6s}")
    for m in sorted(ok, key=lambda x: -hv[x]):
        print(f"{m:16s} {ok[m]:>4} {hv[m]:>10}건 {filed[m]:>5}/{ok[m]}")
    print("\n★샷 6개 · 모델당 한 판이다. 개수 그대로 읽고 백분율로 쓰지 않는다.")

    if no_ref:
        print("\n★참조를 안 붙였으므로 **옛 프로덕션 대조표는 안 낸다** — "
              "판정자와 참조 유무가 함께 바뀐 대조라 같은 물음이 아니다.")
        return 0
    # 옛 프로덕션 기록(같은 그림)과 대조 — 그때 둘째 슬롯은 grok 이었다
    print("\n═══ 옛 프로덕션 판정(같은 그림)과 대조 ═══")
    for t in tags:
        r = old.get(t) or {}
        cm = r.get("cross_model_order") or {}
        line = " · ".join(
            f"{SHORT.get(m, m)}={win.get((t, SHORT.get(m, m)), '?')}"
            for m in SHORT)
        print(f"  {t:8s} 옛 선정={r.get('selected')} "
              f"({cm.get('route')})   지금 다시 물으니 → {line}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
