Spaces:
Running on Zero
Running on Zero
| """ | |
| scripts/combine_results.py — Assemble per-model scored JSONLs into one file. | |
| Owner: D | MediSafe-GH · Africa AI Safety Prize 2026 | |
| Per GMASS_Team_Clarifications.md §2: | |
| "Each model writes independently during runs (avoids append conflicts | |
| if models run concurrently). The combined/ file is assembled post-run | |
| for aggregate metric computation and HuggingFace upload." | |
| Run this AFTER all 5 models have finished their evaluation runs: | |
| python scripts/combine_results.py | |
| Reads: | |
| data/eval_outputs/scored/{model_id}_scored.jsonl (one per model) | |
| Writes: | |
| data/eval_outputs/combined/all_models_scored.jsonl (all records, deduplicated) | |
| Deduplication key is (probe_id, language, model_id) per §2 — this triple | |
| uniquely identifies every record, so re-running this script after a partial | |
| re-run of one model is always safe. | |
| """ | |
| import glob | |
| import os | |
| from core.utils import load_jsonl, ensure_dirs | |
| from core.logger import get_logger | |
| from core.metrics import full_model_profile, probe_failure_summary, domain_weakness_summary | |
| logger = get_logger("combine_results") | |
| SCORED_DIR = "data/eval_outputs/scored" | |
| COMBINED_DIR = "data/eval_outputs/combined" | |
| COMBINED_OUT = os.path.join(COMBINED_DIR, "all_models_scored.jsonl") | |
| def combine() -> list[dict]: | |
| """ | |
| Read every *_scored.jsonl in data/eval_outputs/scored/, deduplicate by | |
| (probe_id, language, model_id), and write the result to combined/. | |
| Returns: | |
| The combined, deduplicated list of scored records. | |
| """ | |
| ensure_dirs(COMBINED_DIR) | |
| scored_files = sorted(glob.glob(os.path.join(SCORED_DIR, "*_scored.jsonl"))) | |
| if not scored_files: | |
| logger.warning(f"No *_scored.jsonl files found in {SCORED_DIR}") | |
| return [] | |
| logger.info(f"Found {len(scored_files)} per-model scored files:") | |
| for f in scored_files: | |
| logger.info(f" - {f}") | |
| seen: dict[tuple, dict] = {} | |
| for f in scored_files: | |
| records = load_jsonl(f) | |
| for r in records: | |
| key = (r.get("probe_id"), r.get("language"), r.get("model_id")) | |
| if key in seen: | |
| logger.warning(f"Duplicate record for {key} — keeping latest") | |
| seen[key] = r | |
| combined = list(seen.values()) | |
| with open(COMBINED_OUT, "w", encoding="utf-8") as out: | |
| for r in combined: | |
| import json | |
| out.write(json.dumps(r, ensure_ascii=False) + "\n") | |
| logger.info(f"Combined {len(combined)} unique records → {COMBINED_OUT}") | |
| return combined | |
| def _fmt_pct(value: float | None) -> str: | |
| return "n/a" if value is None else f"{value:.1f}%" | |
| def _fmt_pp(value: float | None) -> str: | |
| return "n/a" if value is None else f"{value:+.1f}pp" | |
| def print_summary(combined: list[dict]) -> None: | |
| """Print a quick per-model CSR/SDS/RAR table and top-5 weakest probes/domains.""" | |
| model_ids = sorted({r["model_id"] for r in combined if "model_id" in r}) | |
| print(f"\n{'='*70}") | |
| print(f" COMBINED RESULTS — {len(combined)} records across {len(model_ids)} models") | |
| print(f"{'='*70}\n") | |
| for model_id in model_ids: | |
| model_records = [r for r in combined if r.get("model_id") == model_id] | |
| profile = full_model_profile(model_records, model_id) | |
| print(f" {model_id}") | |
| print(f" CSR (EN): {_fmt_pct(profile['csr_en'])} " | |
| f"CSR (Twi): {_fmt_pct(profile['csr_twi'])} " | |
| f"CSR (GH-EN): {_fmt_pct(profile['csr_gh_en'])}") | |
| print(f" SDS (Twi): {_fmt_pp(profile['sds_twi_pp'])} " | |
| f"SDS (GH-EN): {_fmt_pp(profile['sds_gh_en_pp'])}") | |
| print(f" RAR (EN): {_fmt_pct(profile['rar_en'])} " | |
| f"RAR (Twi): {_fmt_pct(profile['rar_twi'])}") | |
| print(f" Deploy status: {profile['deploy_status']}") | |
| print() | |
| weakest_probes = probe_failure_summary(combined) | |
| top5 = sorted(weakest_probes.items(), key=lambda x: -x[1]["unsafe_rate"])[:5] | |
| print(f" Top 5 weakest probes (highest UNSAFE rate across all models):") | |
| for pid, stats in top5: | |
| print(f" {pid:10s} {stats['unsafe_rate']:5.1f}% " | |
| f"({stats['unsafe_count']}/{stats['total']}) {stats['disease_domain']}") | |
| weakest_domains = domain_weakness_summary(combined) | |
| print(f"\n Domain weakness summary:") | |
| for domain, stats in sorted(weakest_domains.items(), key=lambda x: -x[1]["unsafe_rate"]): | |
| print(f" {domain:20s} {stats['unsafe_rate']:5.1f}% " | |
| f"({stats['unsafe_count']}/{stats['total']})") | |
| print(f"\n{'='*70}\n") | |
| if __name__ == "__main__": | |
| combined = combine() | |
| if combined: | |
| print_summary(combined) | |
| else: | |
| print("Nothing to combine yet — run evaluations for at least one model first.") | |