| """Benchmark harness — scoring. Score Themis vs CaseMine top-K with the isolated |
| relevance reviewer (relevant=1, partial=0.5, not=0) -> relevance@K per query, |
| per intent, and overall. Reports the head-to-head + per-query win/loss. |
| |
| Inputs (phase1/eval/): themis_bench_results.json, casemine_bench_results.json |
| each: [{id,intent,query,results:[{case_name, passage|snippet, neutral_citation?, court?, year?}]}] |
| Run: set -a; . ./.env; set +a; .venv/bin/python phase1/scripts/20_score_benchmark.py |
| """ |
| import os, sys, json |
| from collections import defaultdict |
| sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) |
| from reviewer import review |
| from concurrent.futures import ThreadPoolExecutor |
|
|
| HERE = os.path.dirname(__file__); EVAL = os.path.normpath(os.path.join(HERE, "..", "eval")) |
| THEMIS = json.load(open(os.path.join(EVAL, "themis_bench_results.json"))) |
| CM_PATH = os.path.join(EVAL, "casemine_bench_results.json") |
| CASEMINE = json.load(open(CM_PATH)) if os.path.exists(CM_PATH) else [] |
| TOPK = int(os.getenv("TOPK", "10")) |
| SCORE = {"relevant": 1.0, "partial": 0.5, "not": 0.0} |
|
|
| def case_text(r): |
| return (r.get("case_name") or "") + ". " + (r.get("passage") or r.get("snippet") or "") |
|
|
| def score_system(entries): |
| """entries: list of {id,intent,query,results}. Returns {id: relevance@K} + verdict detail.""" |
| by_id = {} |
| tasks = [] |
| for e in entries: |
| for r in e["results"][:TOPK]: |
| tasks.append((e["id"], e["query"], r)) |
| def judge(t): |
| qid, q, r = t |
| v = review(q, case_text(r)) |
| return (qid, SCORE.get(v["verdict"], 0.0)) |
| agg = defaultdict(list) |
| with ThreadPoolExecutor(8) as ex: |
| for qid, s in ex.map(judge, tasks): |
| agg[qid].append(s) |
| for e in entries: |
| sc = agg.get(e["id"], []) |
| by_id[e["id"]] = sum(sc) / TOPK if sc else 0.0 |
| return by_id |
|
|
| print(f"scoring Themis ({len(THEMIS)} queries) ...", flush=True) |
| themis = score_system(THEMIS) |
| cm = score_system(CASEMINE) if CASEMINE else {} |
| intent_of = {e["id"]: e["intent"] for e in THEMIS} |
| qtext = {e["id"]: e["query"] for e in THEMIS} |
|
|
| print("\n=== per-query relevance@%d ===" % TOPK) |
| print(f"{'id':12s} {'intent':9s} | Themis | CaseMine | winner") |
| ti = defaultdict(list); ci = defaultdict(list) |
| for qid in [e["id"] for e in THEMIS]: |
| t = themis.get(qid, 0.0); c = cm.get(qid) |
| ti[intent_of[qid]].append(t) |
| win = "" |
| if c is not None: |
| ci[intent_of[qid]].append(c) |
| win = "THEMIS" if t > c + 0.05 else ("CaseMine" if c > t + 0.05 else "tie") |
| print(f"{qid:12s} {intent_of[qid]:9s} | {t:4.0%} | {('%4.0f%%'%(c*100)) if c is not None else ' -- '} | {win}") |
|
|
| print("\n=== per-intent average ===") |
| for it in ["fact", "issue", "vague", "citation", "casename"]: |
| t = sum(ti[it])/len(ti[it]) if ti[it] else None |
| c = sum(ci[it])/len(ci[it]) if ci[it] else None |
| print(f"{it:10s} Themis {('%3.0f%%'%(t*100)) if t is not None else ' -- '} | CaseMine {('%3.0f%%'%(c*100)) if c is not None else ' -- '}") |
| allt = sum(themis.values())/len(themis) |
| allc = sum(cm.values())/len(cm) if cm else None |
| print(f"\nOVERALL Themis {allt:.0%}" + (f" | CaseMine {allc:.0%} | delta {(allt-allc)*100:+.0f} pts" if allc is not None else " (CaseMine not collected yet)")) |
|
|