"""Export measured pilot results into LaTeX and a blinded review sheet.""" from __future__ import annotations import argparse import csv import json import random from collections import defaultdict from pathlib import Path def load_lines(path): return [json.loads(s) for s in path.read_text(encoding="utf-8").splitlines() if s.strip()] if path.exists() else [] def main(): p=argparse.ArgumentParser(description=__doc__) p.add_argument("--run",type=Path,required=True) p.add_argument("--before",type=Path,default=Path("outputs/evaluation_pilot")) p.add_argument("--project",type=Path,default=Path("ZAVERECNA_PRACA/ZAVERECNA_PRACA")) args=p.parse_args() rows=list({(r["case_id"],r["mode"]):r for r in load_lines(args.run/"answers.jsonl")}.values()) manifest=json.loads((args.run/"manifest.json").read_text(encoding="utf-8")) expected=len(manifest["identity"]["case_ids"])*len(manifest["identity"]["modes"]) if len(rows)!=expected: raise SystemExit("Run is incomplete; refusing final table") groups=defaultdict(list) for row in rows: groups[row["mode"]].append(row) judgments={(r["case_id"],r["mode"]):r for r in load_lines(args.run/"judgments.jsonl")} tex=[r"Pilot zahŕňa desať otázok a päť konfigurácií, teda 50 plánovaných požiadaviek. " r"Tabuľka uvádza skutočne uložené výstupy, nie odhadované hodnoty. " r"Dve otázky v každej kategórii nestačia na všeobecné závery o kvalite systému.", r"\begin{table}[!htbp]\centering\small", r"\caption{Pilotné výsledky po rezervovaní rozpočtu pre pôvodné texty}", r"\label{tab:pilot-results}", r"\begin{tabularx}{\textwidth}{@{}lrrrrX@{}}\toprule", r"Režim & OK & Chyby & Zdroj & Úryvky & Medián času \\\midrule"] output={} for mode in ["bypass","naive","local","hybrid","mix"]: items=groups[mode]; ok=[r for r in items if r["status"]=="ok"] hits=[r["metrics"]["source_hit"] for r in ok if r["metrics"]["source_hit"] is not None] evidence=sum(bool(r["metrics"]["evidence_chunks"]) for r in ok) times=sorted(r["seconds"] for r in ok) median=(times[(len(times)-1)//2]+times[len(times)//2])/2 if times else None hit=f"{sum(hits)}/{len(hits)}" if hits else "--" time=f"{median:.2f} s" if median is not None else "--" tex.append(f"{mode} & {len(ok)} & {len(items)-len(ok)} & {hit} & {evidence}/{len(ok)} & {time}"+r" \\") output[mode]={"successful":len(ok),"failed":len(items)-len(ok),"source_hit_n":len(hits),"source_hits":sum(hits), "answers_with_source_chunks":evidence,"median_seconds":median} tex += [r"\bottomrule\end{tabularx}\end{table}", r"Stĺpec Zdroj uvádza počet odpovedí obsahujúcich referenčný produkt z ôsmich otázok s referenciou. " r"Stĺpec Úryvky uvádza počet odpovedí, pri ktorých API vrátilo aspoň jeden zdrojový úryvok. " r"Pri režime bypass sa zdroj nevyhodnocuje. Časy zahŕňajú možné zásahy cache, vyťaženosť školského servera " r"a súbežnú funkčnú kontrolu dema; nie sú izolovaným výkonnostným benchmarkom."] before=load_lines(args.before/"answers.jsonl") old=[r for r in before if r["mode"] in {"local","hybrid","mix"} and r["status"]=="ok"] new=[r for r in rows if r["mode"] in {"local","hybrid","mix"} and r["status"]=="ok"] if old and new: a=sum(bool(r["metrics"]["evidence_chunks"]) for r in old) b=sum(bool(r["metrics"]["evidence_chunks"]) for r in new) tex.append(f"Diagnostický beh pred úpravou vrátil zdrojové úryvky pri {a} z {len(old)} grafových odpovedí; " f"po explicitnom obmedzení grafového kontextu ich obsahovalo {b} z {len(new)}. " "V pôvodnom behu bol celkový limit 6\\,000 tokenov bez osobitného zníženia predvolených limitov entít a vzťahov. " "V novom behu je celkový limit 8\\,000 tokenov a grafové limity 1\\,000 a 1\\,500 tokenov. " "Keďže sa zmenili oba parametre, výsledok opisuje opravu konfigurácie a nie izolovaný účinok jednej premennej.") ok_j=[v for v in judgments.values() if v["status"]=="ok"] if ok_j: tex += [r"\begin{table}[!htbp]\centering\small", r"\caption{Orientačná vlastná rubrika LLM-as-a-judge, nie metriky RAGAS}", r"\label{tab:pilot-judge}",r"\begin{tabularx}{\textwidth}{@{}lXXX@{}}\toprule", r"Režim & Podpora tvrdení & Pokrytie referencie & Primerané odmietnutie \\\midrule"] for mode in ["bypass","naive","local","hybrid","mix"]: js=[j for j in ok_j if j["mode"]==mode] vals=[] for metric in ["grounding","coverage"]: scores=[j[metric] for j in js if j.get(metric) is not None and (metric!="coverage" or not j["case_id"].startswith("unanswerable-"))] vals.append(f"{sum(scores)/len(scores):.2f} (n={len(scores)})" if scores else "--") neg=[j for j in js if j["case_id"].startswith("unanswerable-")] abstain=f"{sum(j['verdict']['appropriate_abstention'] for j in neg)}/{len(neg)}" if neg else "--" tex.append(f"{mode} & {vals[0]} & {vals[1]} & {abstain}"+r" \\") output[mode]["judge_successful"]=len(js) tex += [r"\bottomrule\end{tabularx}\end{table}", f"Platný štruktúrovaný verdikt bol získaný pre {len(ok_j)} z {len(rows)} odpovedí. " r"Priemery obsahujú iba definované skóre; počty sú uvedené pri hodnotách. " r"Podpora tvrdení sa nehodnotí pre bypass ani pri chýbajúcom texte. " r"Hodnotiteľ používa rovnakú službu ako generátor a nie je nezávislým odborným posúdením."] tex.append(r"Pilot preukazuje technickú vykonateľnosť merania a opravu dostupnosti zdrojových textov. " r"Nájdenie zdroja ani orientačné skóre hodnotiaceho modelu samo osebe nedokazuje klinickú správnosť. " r"Na potvrdenie prínosu grafu treba širšiu nezávislú sadu, kontrolu referencií a odborné posúdenie odpovedí.") (args.project/"evaluation_results.tex").write_text("\n\n".join(tex)+"\n",encoding="utf-8") (args.run/"report.json").write_text(json.dumps(output,indent=2),encoding="utf-8") # A reproducible shuffle hides configuration labels from a human reviewer. random.Random(20261017).shuffle(rows) cases={c["id"]:c for c in json.loads(Path("evaluation/scenarios.json").read_text(encoding="utf-8"))} with (args.run/"manual_review.csv").open("w",encoding="utf-8-sig",newline="") as f: w=csv.writer(f) w.writerow(["review_id","question","reference","answer","correctness_0_2","completeness_0_2","unsupported_claims","appropriate_abstention","notes"]) for i,row in enumerate(rows): w.writerow([f"R{i+1:03}",row["question"],cases[row["case_id"]]["reference_answer"],row.get("response",{}).get("response",""),"","","","",""]) (args.run/"review_key.json").write_text(json.dumps({f"R{i+1:03}":{"case_id":r["case_id"],"mode":r["mode"]} for i,r in enumerate(rows)},indent=2),encoding="utf-8") print(json.dumps(output,indent=2)) if __name__=="__main__": main()