"""Summarise rolling-origin harness output side by side. Usage: uv run python scripts/summarize_eval.py models/eval_2026-09-12.json [more.json ...] Aggregates each (station, variant) across folds: mean MAE, mean flood-regime MAE, mean Brier, total false-alarm episodes, and every warning event with its first-alert lead and the 24 h-ahead peak error -- the operational numbers that decide whether a variant ships. """ import json import statistics import sys from collections import OrderedDict def summarize(paths): for path in paths: results = json.load(open(path, encoding="utf-8")) print(f"\n##### {path}") for station in results: print(f"\n=== {station['station']} (warn {station['warn_thr']:.2f} m) ===") agg = OrderedDict() for fold in station["folds"]: for name, m in fold["variants"].items(): a = agg.setdefault( name, {"mae": [], "mae_hi": [], "brier": [], "fa": 0, "events": []} ) a["mae"].append(m["mae"]) if m.get("mae_above_2p5") is not None: a["mae_hi"].append(m["mae_above_2p5"]) if m.get("brier_warn") is not None: a["brier"].append(m["brier_warn"]) a["fa"] += m["false_alarm_episodes"] for e in m["events"]: err = ( None if e["peak_pred_24h_before"] is None else e["peak_pred_24h_before"] - e["peak_level"] ) a["events"].append((fold["year"], e["crossing"][:10], e["lead_h"], e["peak_level"], err)) print(f"{'variant':22} {'MAE':>6} {'MAE_hi':>7} {'Brier':>7} {'FA':>3} events: year crossing lead_h peak(err24h)") for name, a in agg.items(): ev = " ".join( f"{y} {d} {'—' if l is None else format(l, '+.0f')}h {p:.2f}({'—' if err is None else format(err, '+.2f')})" for y, d, l, p, err in a["events"] ) leads = [l for *_, l, _, _ in a["events"] if l is not None] print( f"{name:22} {statistics.mean(a['mae']):6.3f} " f"{statistics.mean(a['mae_hi']) if a['mae_hi'] else float('nan'):7.3f} " f"{statistics.mean(a['brier']) if a['brier'] else float('nan'):7.4f} " f"{a['fa']:>3} {ev}" ) if leads: print(f"{'':22} lead: mean {statistics.mean(leads):+.1f} h, min {min(leads):+.0f} h, " f"missed {sum(1 for *_, l, _, _ in a['events'] if l is None)}/{len(a['events'])}") if __name__ == "__main__": summarize(sys.argv[1:] or ["models/eval_variants.json"])