import csv, argparse from pathlib import Path from collections import defaultdict from statistics import mean, pstdev NUM_FIELDS = ["persona_adherence","empathy","flow","helpfulness","grounding","safety","clarity"] def to_float(x): try: return float(x) except Exception: return None def main(): ap = argparse.ArgumentParser() ap.add_argument("--eval_id", required=True) ap.add_argument("--in_dir", default="rubrics/responses") ap.add_argument("--out_dir", default=None) args = ap.parse_args() base = Path(args.in_dir) / args.eval_id out_dir = Path(args.out_dir) if args.out_dir else Path("eval/runs")/args.eval_id/"summary" out_dir.mkdir(parents=True, exist_ok=True) rows = [] for p in base.rglob("rubric_*.csv"): with open(p, newline="", encoding="utf-8") as f: for r in csv.DictReader(f): rows.append(r) # also read the shared rubric_v1.csv if people appended there shared = Path("rubrics/rubric_v1.csv") if shared.exists(): with open(shared, newline="", encoding="utf-8") as f: for r in csv.DictReader(f): if r.get("scenario_id"): rows.append(r) by_scn = defaultdict(lambda: {k: [] for k in NUM_FIELDS}) counts = defaultdict(int) for r in rows: scn = (r.get("scenario_id") or "").strip() if not scn: continue counts[scn] += 1 for k in NUM_FIELDS: v = to_float(r.get(k)) if v is not None: by_scn[scn][k].append(v) # write scenario-level CSV out_csv = out_dir / "manual_scores_by_scenario.csv" with open(out_csv, "w", newline="", encoding="utf-8") as f: w = csv.writer(f) header = ["scenario_id","n_ratings"] for k in NUM_FIELDS: header += [f"mean_{k}", f"stdev_{k}"] w.writerow(header) for scn in sorted(by_scn.keys()): row = [scn, counts[scn]] for k in NUM_FIELDS: vals = by_scn[scn][k] m = round(mean(vals), 3) if vals else "" s = round(pstdev(vals), 3) if len(vals) > 1 else "" row += [m, s] w.writerow(row) print(f"✔ wrote {out_csv}") if __name__ == "__main__": main()