|
| 1 | +# Post-benchmark follow-ups (cross-experiment) |
| 2 | + |
| 3 | +Tasks that cut across all five experiments — defer until E05 lands and the chained stack is complete. Pick up from this list once you have all five `benchmark-run.json` files committed. |
| 4 | + |
| 5 | +## 1. Re-evaluate every run under strict equality |
| 6 | + |
| 7 | +**Why.** All five experiments are evaluated with the same `schema-aware` evaluator using `defaultRule: { rule: "fuzzy", fuzzyThreshold: 0.85 }, passThreshold: 0.8` (wired in [`apps/shared/prisma/seed.ts:2044-2062`](../apps/shared/prisma/seed.ts#L2044-L2062)). This is correct for cross-experiment comparison — same rule, same dataset, same evaluator — but fuzzy@0.85 is forgiving on close-but-not-exact OCR misreads (e.g. predicted `2326.4` vs ground truth `2326.47`). For a stricter view of "the model got the value exactly right," re-evaluate every run with the rule changed to `"exact"` (or `fuzzyThreshold: 1.0`). |
| 8 | + |
| 9 | +**What this affects** ([detailed in `experiments/results/04-vlm-direct/SUMMARY.md` "Gaps"](results/04-vlm-direct/SUMMARY.md)): |
| 10 | + |
| 11 | +- All `metrics.*` aggregates: `pass_rate`, `f1.*`, `precision.*`, `recall.*`, `matchedFields.*`, `truePositives.*`, `falseNegatives.*`, `falsePositives.*`, `passing_samples`, `failing_samples`. |
| 12 | +- Per-sample: `metrics`, `pass`, `evaluationDetails[].matched`. |
| 13 | +- `perFieldResults[].{accuracy, correctCount, errorCount, errorRate, errors[]}`. |
| 14 | +- `errorDetectionAnalysis.fields[].{errorCount, errorRate, curve, suggestedBestBalance, suggestedCatch90, suggestedMinimizeReview}`. |
| 15 | + |
| 16 | +The raw `similarity`, `predicted`, `expected`, `confidence` values are preserved per evaluation pair — re-evaluation only needs to recompute `matched` and the aggregates downstream of it. **No re-running the model.** |
| 17 | + |
| 18 | +**Two ways to do it:** |
| 19 | + |
| 20 | +### Option A — re-run the benchmarks under a tighter rule |
| 21 | + |
| 22 | +Change the seed config: |
| 23 | + |
| 24 | +```ts |
| 25 | +// apps/shared/prisma/seed.ts L2044-L2062 |
| 26 | +evaluatorType: "schema-aware", |
| 27 | +evaluatorConfig: { |
| 28 | + defaultRule: { rule: "exact" }, // <- was { rule: "fuzzy", fuzzyThreshold: 0.85 } |
| 29 | + passThreshold: 0.8, |
| 30 | +}, |
| 31 | +``` |
| 32 | + |
| 33 | +`npm run test:db:reset` to re-seed, then re-trigger every experiment via: |
| 34 | + |
| 35 | +```bash |
| 36 | +cd apps/temporal |
| 37 | +for slug in 01 02 03 04 05; do |
| 38 | + npx tsx -r tsconfig-paths/register src/scripts/trigger-experiment-benchmark.ts $slug |
| 39 | +done |
| 40 | +``` |
| 41 | + |
| 42 | +Save the new exports to `experiments/results/<slug>/benchmark-run-strict.json` (parallel to `benchmark-run.json` so we keep both). Cost: full benchmark re-run for all five experiments (~$15-25, ~30 min wallclock). |
| 43 | + |
| 44 | +### Option B — re-evaluate the existing exports without re-running |
| 45 | + |
| 46 | +Write a script that reads each `benchmark-run.json` and recomputes metrics from the preserved `evaluationDetails[].similarity`. No paid calls, instant turnaround: |
| 47 | + |
| 48 | +```ts |
| 49 | +// experiments/scripts/reevaluate-strict.ts (sketch) |
| 50 | +// |
| 51 | +// For each run: |
| 52 | +// - read perSampleResults[].evaluationDetails |
| 53 | +// - recompute matched = (similarity == 1.0) // or some other rule |
| 54 | +// - re-aggregate truePositives, falseNegatives, falsePositives per sample |
| 55 | +// - recompute precision, recall, f1 per sample |
| 56 | +// - re-aggregate to top-level metrics, perFieldResults, errorDetectionAnalysis |
| 57 | +// - write benchmark-run-strict.json |
| 58 | +``` |
| 59 | + |
| 60 | +Option B is the recommended path — it's lossless w.r.t. raw model output, runs in seconds, and produces a parallel comparison artifact you can diff against the fuzzy-evaluated original. |
| 61 | + |
| 62 | +**Expected impact** (estimated from E04's distribution): |
| 63 | + |
| 64 | +- E04 (VLM-direct): drops ~3-4 pp on f1.median because gpt-5.4 vision produces more close-but-not-exact OCR misreads (76 of 1646 matches in the canonical run are fuzzy-only). |
| 65 | +- E03 (CU): drops ~1-2 pp — CU's dedicated OCR layer transcribes digits exactly more often. |
| 66 | +- E02 (Mistral): somewhere in between. |
| 67 | +- Rank order stays the same; the *gap* between E03 and E04 widens slightly. |
| 68 | + |
| 69 | +## 2. Cross-experiment cost normalisation |
| 70 | + |
| 71 | +Each engine bills differently: |
| 72 | + |
| 73 | +- E01 (Azure DI Neural): per-page custom-model invocation |
| 74 | +- E02 (Mistral DocAI): per-page OCR + per-token annotation |
| 75 | +- E03 (Azure CU): per-page content-extraction + per-token gpt-5.2 generative |
| 76 | +- E04 (gpt-5.4 VLM-direct): per-token (input + output) only |
| 77 | +- E05 (hybrid): E01-style DI Read per-page + E04-style VLM tokens |
| 78 | + |
| 79 | +`benchmark-run.json` has `usage_info` / `prompt_tokens` / `completion_tokens` etc. but cross-engine cost normalisation requires hard-coded rate cards or a per-engine cost-aggregation activity. Defer until E05 lands; then add a `cost_per_page_usd` column to the comparison table. |
| 80 | + |
| 81 | +## 3. P50 / P95 latency per engine |
| 82 | + |
| 83 | +`benchmark-run.json` carries per-sample `startedAt` / `completedAt`. Compute P50 / P95 / P99 wallclock per engine and add to the comparison table. One small script reading all five exports. |
| 84 | + |
| 85 | +## 4. Rebaseline `iterate-*-extraction.ts` scorer |
| 86 | + |
| 87 | +The per-experiment iteration scripts use strict-with-normalisation matching, but the canonical benchmark uses fuzzy@0.85. This divergence cost time on E04 (iterating to 70.3% per-field accuracy that turned out not to predict the benchmark median). After re-evaluating runs under both rules (item 1), update the iteration scripts to optionally accept a `--scorer fuzzy|strict` flag so future experiments can iterate against whichever matches the configured benchmark rule. |
0 commit comments