"""Precision by tier from the annotated validation sample (LLM second annotator + operator review), with Wilson CIs, plus the UPC-based precision/recall for tiers B/C. Writes data/processed/match_validation_summary.csv.""" import numpy as np, pandas as pd from scipy.stats import beta from src.common import path, get_logger log = get_logger("validation") def wilson(k, n, z=1.96): if n == 0: return (np.nan, np.nan) p = k / n; d = 1 + z**2 / n; c = p + z**2 / (2 * n); h = z * np.sqrt(p * (1 - p) / n + z**2 / (4 * n**2)) return ((c - h) / d, (c + h) / d) def main(): s = pd.read_csv(path("processed") / "match_validation_sample.csv") lab = s["label_operator"].where(s["label_operator"].fillna("") != "", s["label_llm"]) s["label_final"] = lab rows = [] for t, g in s.groupby("tier"): n = len(g); same = (g.label_final == "SAME").sum(); diff = (g.label_final == "DIFFERENT").sum(); uns = (g.label_final == "UNSURE").sum() lo, hi = wilson(same, n); lo2, hi2 = wilson(same, n - uns) rows.append(dict(tier=t, n=n, same=same, different=diff, unsure=uns, precision_strict=same / n, ci_lo=lo, ci_hi=hi, precision_excl_unsure=same / max(n - uns, 1), ci2_lo=lo2, ci2_hi=hi2, llm_operator_agreement=(g.label_llm == g.label_final).mean())) out = pd.DataFrame(rows) cal = pd.read_csv(path("processed") / "match_threshold_calibration.csv") upc_b = cal[(cal.scorer == "tokens_equal")].iloc[0]; upc_c = cal[(cal.scorer == "sort_ratio") & (cal.thr == 0.90)].iloc[0] out["upc_precision"] = out.tier.map({"A": 1.0, "B": upc_b.precision, "C": upc_c.precision}); out["upc_recall"] = out.tier.map({"A": np.nan, "B": upc_b.recall, "C": upc_c.recall}) out.to_csv(path("processed") / "match_validation_summary.csv", index=False) log.info("\n%s", out.round(3).to_string(index=False)) if __name__ == "__main__": main()