File size: 9,744 Bytes
a6af677
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1c376a4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a6af677
 
 
 
 
 
 
 
 
 
 
 
 
1c376a4
 
a6af677
 
 
 
 
 
1c376a4
a6af677
 
 
1c376a4
 
a6af677
1c376a4
 
 
 
 
a6af677
 
 
 
 
 
 
 
1c376a4
 
a6af677
1c376a4
a6af677
1c376a4
 
a6af677
 
 
 
 
 
 
 
 
 
1c376a4
 
a6af677
1c376a4
 
a6af677
1c376a4
 
a6af677
 
 
 
1c376a4
 
 
a6af677
 
1c376a4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a6af677
 
1c376a4
a6af677
 
 
 
 
 
1c376a4
 
a6af677
 
 
 
 
 
 
 
 
 
 
1c376a4
a6af677
 
 
 
 
 
 
1c376a4
a6af677
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
"""
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()