| """ |
| 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") |
|
|
| |
| 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}") |
|
|
| |
| 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}") |
|
|
| |
| 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}") |
|
|
| |
| 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}%") |
|
|
| |
| 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_matrix() |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|