#!/usr/bin/env python3
"""exp15 — Q1b (full-chunk re-judge) + FLOOR analysis. Reads the fresh full-chunk judge_results_Q1b.json
and judge_results_FLOOR.json and produces the corrected, floor-relative Q1b verdict.

Key checks (the whole point of the re-judge):
 1. CORRECTED effect: oracle-minus-flat paired diff, primary (T-ana+T-ell) and per type, with 90% CI, LOO,
    catastrophes — vs the v1 head-excerpt headline (-0.564) to show the artifact's magnitude.
 2. ARTIFACT-GONE check: split by oracle_frac (dep position in the oracle chunk). Under the old 1100-char
    head-excerpt, dep-in-window cells were +0.238 and dep-out were -0.872 (the truncation signature). With
    FULL chunks the split MUST collapse (both similar) — if dep-out is still strongly negative the fix failed.
 3. FLOOR-relative: is |Q1b effect| within the A0-vs-A0' full-chunk noise floor? Effects under the floor
    are not interpretable (freeze §1.5).
 4. Per-vote audit available (judges.py now persists votes) — catastrophe counts reconstructable.

Run (after both blocks): python q1b_reanalysis.py
"""
from __future__ import annotations
import json
import math
from pathlib import Path

import numpy as np
from scipy import stats

SD = Path("/home/ubuntu/books/gu-zhenren/exp15")
PRIMARY = ("T-ana", "T-ell")


def ci90(xs):
    xs = np.asarray(xs, float); n = len(xs)
    if n < 2:
        return (round(float(xs.mean()), 4) if n else 0.0, None, None, n)
    m = xs.mean(); se = xs.std(ddof=1) / math.sqrt(n); t = stats.t.ppf(0.95, n - 1)
    return round(float(m), 4), round(float(m - t * se), 4), round(float(m + t * se), 4), n


def load(name):
    p = SD / name
    if not p.exists():
        return None
    d = json.load(open(p))
    return d["results"] if isinstance(d, dict) and "results" in d else d


def block_stats(rows, label):
    pd = [r["paired_diff_arm_minus_a0"] for r in rows if r.get("paired_diff_arm_minus_a0") is not None]
    m, lo, hi, n = ci90(pd)
    cat_arm = sum(r["catastrophe"]["arm"] for r in rows)
    cat_a0 = sum(r["catastrophe"]["a0"] for r in rows)
    loo = {"without_grok-4.3": [], "without_mistral-large-latest": []}
    for r in rows:
        for k, v in (r.get("leave_one_judge_out") or {}).items():
            if k in loo:
                loo[k].append(v)
    loo_m = {k: round(float(np.mean(v)), 4) for k, v in loo.items() if v}
    trunc = sum(1 for r in rows if r.get("budget_truncated"))
    return {"label": label, "mean": m, "CI90": [lo, hi], "n": n,
            "catastrophes_arm_vs_a0": [cat_arm, cat_a0], "loo_mean": loo_m, "budget_truncated_cells": trunc}


def main():
    q1b = load("judge_results_Q1b.json")
    floor = load("judge_results_FLOOR.json")
    v1 = load("judge_results_Q1b_v1_headexcerpt.json")
    out = {}

    if q1b is None:
        print("Q1b results not present yet."); return
    prim = [r for r in q1b if r["type"] in PRIMARY]
    tana = [r for r in q1b if r["type"] == "T-ana"]
    tent = [r for r in q1b if r["type"] == "T-ent"]
    out["Q1b_fullchunk"] = {"primary": block_stats(prim, "primary(T-ana+T-ell)"),
                            "T-ana": block_stats(tana, "T-ana"),
                            "T-ent_control": block_stats(tent, "T-ent control") if tent else None}
    print("=== Q1b FULL-CHUNK (oracle - flat; + = oracle better) ===")
    for k in ("primary", "T-ana", "T-ent_control"):
        s = out["Q1b_fullchunk"][k]
        if s:
            print(f"  {s['label']:22} mean {s['mean']:+.4f}  CI90 {s['CI90']}  n={s['n']}  "
                  f"cat oracle/flat {s['catastrophes_arm_vs_a0']}  LOO {s['loo_mean']}  trunc={s['budget_truncated_cells']}")

    # artifact-gone check: split by oracle_frac
    din = [r for r in prim if (r.get("oracle_frac") or 0) < 0.5]
    dout = [r for r in prim if (r.get("oracle_frac") or 0) >= 0.5]
    si, so = ci90([r["paired_diff_arm_minus_a0"] for r in din]), ci90([r["paired_diff_arm_minus_a0"] for r in dout])
    out["artifact_gone_check"] = {"dep_in_window_frac<0.5": {"mean": si[0], "n": si[3]},
                                  "dep_out_window_frac>=0.5": {"mean": so[0], "n": so[3]},
                                  "v1_reference": {"dep_in": 0.238, "dep_out": -0.872},
                                  "collapsed": abs(si[0] - so[0]) < 0.3}
    print(f"\n=== ARTIFACT-GONE check (full chunks should collapse the split) ===")
    print(f"  dep-in  (ofrac<0.5): mean {si[0]:+.4f} n={si[3]}   (v1 head-excerpt: +0.238)")
    print(f"  dep-out (ofrac>=0.5): mean {so[0]:+.4f} n={so[3]}   (v1 head-excerpt: -0.872)")
    print(f"  -> split {'COLLAPSED (artifact gone)' if out['artifact_gone_check']['collapsed'] else 'STILL PRESENT — investigate'}")

    if v1:
        vp = [r for r in v1 if r["type"] == "T-ana"]
        vm, *_ = ci90([r["paired_diff_arm_minus_a0"] for r in vp])
        out["v1_vs_v2"] = {"v1_Tana_headexcerpt": vm, "v2_Tana_fullchunk": out["Q1b_fullchunk"]["T-ana"]["mean"],
                           "artifact_magnitude": round(out["Q1b_fullchunk"]["T-ana"]["mean"] - vm, 4)}
        print(f"\n=== v1 (head-excerpt) vs v2 (full-chunk) T-ana ===")
        print(f"  v1 {vm:+.4f}  ->  v2 {out['Q1b_fullchunk']['T-ana']['mean']:+.4f}  "
              f"(the artifact moved the headline by {out['v1_vs_v2']['artifact_magnitude']:+.4f})")

    if floor is not None:
        fpd = [abs(r["paired_diff_arm_minus_a0"]) for r in floor if r.get("paired_diff_arm_minus_a0") is not None]
        fmean = round(float(np.mean(fpd)), 4); fmax = round(float(np.max(fpd)), 4)
        fsigned = ci90([r["paired_diff_arm_minus_a0"] for r in floor if r.get("paired_diff_arm_minus_a0") is not None])
        out["FLOOR_fullchunk"] = {"mean_abs_diff": fmean, "max_abs_diff": fmax,
                                  "signed_mean": fsigned[0], "signed_CI90": [fsigned[1], fsigned[2]], "n": fsigned[3]}
        prim_abs = abs(out["Q1b_fullchunk"]["primary"]["mean"])
        out["FLOOR_fullchunk"]["Q1b_primary_exceeds_floor"] = bool(prim_abs > fmean)
        print(f"\n=== FLOOR (A0 vs A0', full-chunk) ===")
        print(f"  |diff| mean {fmean}  max {fmax}  signed {fsigned[0]:+.4f} CI90 [{fsigned[1]},{fsigned[2]}] n={fsigned[3]}")
        print(f"  Q1b primary |{prim_abs:.4f}| {'EXCEEDS' if prim_abs>fmean else 'is WITHIN'} the floor {fmean}")
    else:
        print("\n(FLOOR results not present yet — run FLOOR block first.)")

    (SD / "q1b_reanalysis.json").write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8")
    print(f"\n-> {SD/'q1b_reanalysis.json'}")


if __name__ == "__main__":
    main()
