bayan-api / tests /v2 /benchmark_matrix.py
youssefreda9's picture
Phase A: Upgrade L1 with TP/FP/FN/TN verdicts + word edits, add F1 scores to matrix
1c376a4
Raw
History Blame Contribute Delete
9.74 kB
"""
BAYAN v2.0 — Benchmark Matrix Runner
======================================
Master script that runs all 3 test levels and produces a side-by-side
comparison matrix showing raw model vs solo API vs integrated pipeline.
Usage:
# Full matrix (all 3 levels)
python tests/v2/benchmark_matrix.py --url URL
# Single level only
python tests/v2/benchmark_matrix.py --url URL --level 1
python tests/v2/benchmark_matrix.py --url URL --level 3
# Single dataset
python tests/v2/benchmark_matrix.py --url URL --dataset spelling
# Compare saved results
python tests/v2/benchmark_matrix.py --compare
"""
import argparse
import json
import sys
import time
from pathlib import Path
REPORT_DIR = Path(__file__).parent / "reports"
def run_level(level: int, url: str, dataset: str = None):
"""Run a specific test level."""
import subprocess
scripts = {
1: "tests/v2/test_level1_raw.py",
2: "tests/v2/test_level2_solo.py",
3: "tests/v2/test_level3_integrated.py",
}
script = scripts[level]
cmd = [sys.executable, script, "--url", url]
if dataset:
cmd.extend(["--dataset", dataset])
print(f"\n{'#'*70}")
print(f"# RUNNING LEVEL {level}: {script}")
print(f"{'#'*70}\n")
result = subprocess.run(cmd, cwd=str(Path(__file__).parent.parent.parent))
return result.returncode == 0
def load_report(level: int) -> dict:
"""Load saved report for a level."""
filenames = {
1: "level1_raw_results.json",
2: "level2_solo_results.json",
3: "level3_integrated_results.json",
}
path = REPORT_DIR / filenames[level]
if path.exists():
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
return None
def _compute_f1(tp, fp, fn):
"""Compute F1 score from TP/FP/FN."""
precision = tp / (tp + fp) if (tp + fp) > 0 else 0
recall = tp / (tp + fn) if (tp + fn) > 0 else 0
f1 = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0
return round(f1, 4), round(precision, 4), round(recall, 4)
def _extract_model_metrics(report, model_key):
"""Extract TP/TN/FP/FN for a model from a report."""
a = report.get("analysis", {})
by_model = a.get("by_model", {})
data = by_model.get(model_key, {})
return data.get("TP", 0), data.get("TN", 0), data.get("FP", 0), data.get("FN", 0)
def print_comparison_matrix():
"""Load all 3 reports and print side-by-side comparison."""
l1 = load_report(1)
l2 = load_report(2)
l3 = load_report(3)
print(f"\n{'='*70}")
print("BAYAN v2.0 — BENCHMARK COMPARISON MATRIX")
print(f"{'='*70}")
for level, report, name in [(1, l1, "L1-Raw"), (2, l2, "L2-Solo"), (3, l3, "L3-Pipeline")]:
if report:
ts = report.get("timestamp", "N/A")
total = report.get("analysis", {}).get("total", "?")
print(f" {name}: {ts} ({total} tests)")
else:
print(f" {name}: NOT RUN")
# ── Level 1 Summary ──
if l1:
print(f"\n{'─'*70}")
print("Level 1: RAW MODEL OUTPUT (through solo API endpoints)")
print(f"{'─'*70}")
a = l1.get("analysis", {})
by_model = a.get("by_model", {})
print(f" {'Model':<12} {'TP':>4} {'TN':>4} {'FP':>4} {'FN':>4} {'Pass%':>7} {'ChgRate':>8} {'F1':>6}")
print(f" {'-'*12} {'-'*4} {'-'*4} {'-'*4} {'-'*4} {'-'*7} {'-'*8} {'-'*6}")
for model, data in by_model.items():
tp, tn, fp, fn = data.get("TP",0), data.get("TN",0), data.get("FP",0), data.get("FN",0)
pr = data.get("pass_rate", 0) * 100
cr = data.get("change_rate", 0) * 100
f1, _, _ = _compute_f1(tp, fp, fn)
print(f" {model:<12} {tp:>4} {tn:>4} {fp:>4} {fn:>4} {pr:>6.1f}% {cr:>6.1f}% {f1:.3f}")
# ── Level 2 Summary ──
if l2:
print(f"\n{'─'*70}")
print("Level 2: SOLO API (single model + filters, no integration)")
print(f"{'─'*70}")
a = l2.get("analysis", {})
by_model = a.get("by_model", {})
print(f" {'Model':<12} {'TP':>4} {'TN':>4} {'FP':>4} {'FN':>4} {'Pass%':>7} {'F1':>6}")
print(f" {'-'*12} {'-'*4} {'-'*4} {'-'*4} {'-'*4} {'-'*7} {'-'*6}")
for model, data in by_model.items():
tp, tn, fp, fn = data.get("TP",0), data.get("TN",0), data.get("FP",0), data.get("FN",0)
pr = data.get("pass_rate", 0) * 100
f1, _, _ = _compute_f1(tp, fp, fn)
print(f" {model:<12} {tp:>4} {tn:>4} {fp:>4} {fn:>4} {pr:>6.1f}% {f1:.3f}")
# ── Level 3 Summary ──
if l3:
print(f"\n{'─'*70}")
print("Level 3: INTEGRATED PIPELINE (full Spelling→Grammar→Punctuation)")
print(f"{'─'*70}")
a = l3.get("analysis", {})
agg = a.get("aggregate", {})
total = a.get("total", 0)
pr = agg.get("pass_rate", 0) * 100
tp, tn, fp, fn = agg.get("TP",0), agg.get("TN",0), agg.get("FP",0), agg.get("FN",0)
f1, prec, rec = _compute_f1(tp, fp, fn)
print(f" Overall: {pr:.1f}% pass ({total} tests)")
print(f" TP={tp} TN={tn} FP={fp} FN={fn}")
print(f" F1={f1:.3f} Precision={prec:.3f} Recall={rec:.3f}")
print(f"\n {'Dataset':<14} {'Pass%':>7} {'TP':>4} {'TN':>4} {'FP':>4} {'FN':>4} {'F1':>6}")
print(f" {'-'*14} {'-'*7} {'-'*4} {'-'*4} {'-'*4} {'-'*4} {'-'*6}")
by_ds = a.get("by_dataset", {})
for ds in sorted(by_ds.keys()):
d = by_ds[ds]
dp = d.get("pass_rate", 0) * 100
dtp, dtn, dfp, dfn = d.get("TP",0), d.get("TN",0), d.get("FP",0), d.get("FN",0)
df1, _, _ = _compute_f1(dtp, dfp, dfn)
print(f" {ds:<14} {dp:>6.1f}% {dtp:>4} {dtn:>4} {dfp:>4} {dfn:>4} {df1:.3f}")
# ── Cross-Level Comparison ──
if l1 and l2:
print(f"\n{'─'*70}")
print("CROSS-LEVEL: L1 (Raw) vs L2 (Solo) — Filter Impact")
print(f"{'─'*70}")
print(f" {'Model':<12} {'L1-Pass%':>9} {'L2-Pass%':>9} {'Delta':>7} {'L1-F1':>7} {'L2-F1':>7}")
print(f" {'-'*12} {'-'*9} {'-'*9} {'-'*7} {'-'*7} {'-'*7}")
for model in ("spelling", "grammar", "punctuation"):
l1m = l1.get("analysis",{}).get("by_model",{}).get(model,{})
l2m = l2.get("analysis",{}).get("by_model",{}).get(model,{})
l1p = l1m.get("pass_rate",0) * 100
l2p = l2m.get("pass_rate",0) * 100
delta = l2p - l1p
l1f1, _, _ = _compute_f1(l1m.get("TP",0), l1m.get("FP",0), l1m.get("FN",0))
l2f1, _, _ = _compute_f1(l2m.get("TP",0), l2m.get("FP",0), l2m.get("FN",0))
print(f" {model:<12} {l1p:>8.1f}% {l2p:>8.1f}% {delta:>+6.1f}% {l1f1:>6.3f} {l2f1:>6.3f}")
if l2 and l3:
print(f"\n{'─'*70}")
print("CROSS-LEVEL: L2 (Solo) vs L3 (Pipeline) — Integration Impact")
print(f"{'─'*70}")
l2_ds = l2.get("analysis", {}).get("by_dataset", {})
l3_ds = l3.get("analysis", {}).get("by_dataset", {})
all_datasets = sorted(set(list(l2_ds.keys()) + list(l3_ds.keys())))
print(f" {'Dataset':<14} {'L2-Best':>8} {'L3-Pipe':>8} {'Delta':>7}")
print(f" {'-'*14} {'-'*8} {'-'*8} {'-'*7}")
for ds in all_datasets:
l2d = l2_ds.get(ds, {})
l3d = l3_ds.get(ds, {})
l2_s = l2d.get("spelling", {}).get("pass_rate", 0) * 100
l2_g = l2d.get("grammar", {}).get("pass_rate", 0) * 100
l2_p = l2d.get("punctuation", {}).get("pass_rate", 0) * 100
l3_p = l3d.get("pass_rate", 0) * 100
best_solo = max(l2_s, l2_g, l2_p)
delta = l3_p - best_solo
print(f" {ds:<14} {best_solo:>7.1f}% {l3_p:>7.1f}% {delta:>+6.1f}%")
# Save comparison
comparison = {
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ"),
"l1_timestamp": l1.get("timestamp") if l1 else None,
"l2_timestamp": l2.get("timestamp") if l2 else None,
"l3_timestamp": l3.get("timestamp") if l3 else None,
"l1_summary": l1.get("analysis", {}).get("by_model") if l1 else None,
"l2_summary": l2.get("analysis", {}).get("by_model") if l2 else None,
"l3_summary": l3.get("analysis", {}).get("aggregate") if l3 else None,
}
REPORT_DIR.mkdir(parents=True, exist_ok=True)
with open(REPORT_DIR / "benchmark_matrix.json", 'w', encoding='utf-8') as f:
json.dump(comparison, f, ensure_ascii=False, indent=2)
print(f"\n[MATRIX] Comparison → {REPORT_DIR / 'benchmark_matrix.json'}")
def main():
parser = argparse.ArgumentParser(description="BAYAN v2.0 Benchmark Matrix")
parser.add_argument("--url", default="https://bayan10-bayan-api.hf.space")
parser.add_argument("--level", type=int, default=None, help="Run specific level (1/2/3)")
parser.add_argument("--dataset", default=None, help="Filter to single dataset")
parser.add_argument("--compare", action="store_true", help="Compare saved results only")
args = parser.parse_args()
if args.compare:
print_comparison_matrix()
return
levels = [args.level] if args.level else [1, 2, 3]
print(f"\n{'#'*70}")
print(f"# BAYAN v2.0 — BENCHMARK MATRIX")
print(f"# Levels: {levels}")
print(f"# Target: {args.url}")
print(f"{'#'*70}")
for level in levels:
success = run_level(level, args.url, args.dataset)
if not success:
print(f"\n❌ Level {level} failed!")
sys.exit(1)
print(f"\n✅ Level {level} complete!")
# Print comparison
print_comparison_matrix()
if __name__ == "__main__":
main()