"""Summarize a synthetic/all-query trace, retaining missing ranks in denominators. No inference about the private SOP79 run. Python 3.11+, standard library only. """ from __future__ import annotations import csv import json import sys def summarize(rows: list[dict]) -> dict: ids = [row["query_id"] for row in rows] if not rows or len(ids) != len(set(ids)): raise ValueError("provide a nonempty trace with unique query IDs") total = len(rows) stages = {} for stage in ("dense", "sparse", "fused", "rerank"): ranks = [] for row in rows: value = row.get(stage + "_rank", "") rank = int(value) if value not in (None, "", "-") else None if rank is not None and rank < 1: raise ValueError("ranks are one-based; use blank for missing") ranks.append(rank) hits = sum(rank is not None and rank <= 5 for rank in ranks) stages[stage] = {"queries": total, "rank_observed": sum(x is not None for x in ranks), "top5_hits": hits, "hit_rate_at_5": hits / total} return {"scope": "input rows only; use the full query set", "stages": stages} if __name__ == "__main__": with open(sys.argv[1], newline="", encoding="utf-8") as stream: print(json.dumps(summarize(list(csv.DictReader(stream))), indent=2))