Files
wehub-resource-sync 94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:38:16 +08:00

632 lines
23 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Evaluation utilities for the OCR benchmark (olmOCR-bench test classes).
Implements:
- text_presence : short text segment must be present in OCR output
- text_absence : text (headers/footers/page numbers) must NOT appear
- natural_reading_order : two text spans must appear in correct relative order
- table_accuracy : cell values with correct neighbor relationships (Markdown + HTML)
- math_formula_accuracy : LaTeX key-symbol token matching (simplified; no KaTeX/playwright)
Also provides:
- normalized_edit_distance() for OmniDocBench-style text quality measurement
- aggregate_results() / print_results_table() for summary reporting
"""
import re
import unicodedata
from difflib import SequenceMatcher
from html.parser import HTMLParser
from typing import Dict, List, Optional
# ── Unicode normalization ─────────────────────────────────────────────────────
_HYPHEN_RE = re.compile(
r"[\u2010\u2011\u2012\u2013\u2014\u2015\u2212\uFE58\uFE63\uFF0D]"
)
_DQUOTE_RE = re.compile(
r"[\u00AB\u00BB\u201C\u201D\u201E\u201F\u2033\u2036\u276E\u276F\u3003\uFF02]"
)
_SQUOTE_RE = re.compile(
r"[\u2018\u2019\u201A\u201B\u2032\u2035\u2039\u203A\u2C8D\uFF07]"
)
_MARKDOWN_RE = re.compile(r"(\*{1,3}|_{1,3}|`{1,3}|~~|#{1,6}\s?)")
def normalize_text(text: str) -> str:
"""Apply olmOCR-bench standard Unicode normalization."""
text = unicodedata.normalize("NFC", text)
text = _HYPHEN_RE.sub("-", text)
text = _DQUOTE_RE.sub('"', text)
text = _SQUOTE_RE.sub("'", text)
return text
def strip_markdown(text: str) -> str:
"""Remove Markdown syntax markers for soft matching."""
return _MARKDOWN_RE.sub("", text)
# ── Matching helpers ──────────────────────────────────────────────────────────
def fuzzy_contains(needle: str, haystack: str, threshold: float = 0.85) -> bool:
"""Check if needle appears in haystack using fuzzy sliding-window matching."""
needle = normalize_text(strip_markdown(needle).strip())
haystack = normalize_text(strip_markdown(haystack))
# Fast exact check first
if needle.lower() in haystack.lower():
return True
n = len(needle)
if n == 0:
return True
step = max(1, n // 4)
for i in range(0, max(1, len(haystack) - n + 1), step):
window = haystack[i : i + n]
ratio = SequenceMatcher(None, needle.lower(), window.lower()).ratio()
if ratio >= threshold:
return True
return False
def exact_contains(needle: str, haystack: str, case_sensitive: bool = True) -> bool:
"""Check if needle appears exactly in haystack (after normalization)."""
needle = normalize_text(strip_markdown(needle).strip())
haystack = normalize_text(strip_markdown(haystack))
if not case_sensitive:
return needle.lower() in haystack.lower()
return needle in haystack
def _get_words_slice(text: str, first_n: Optional[int], last_n: Optional[int]) -> str:
"""Return the first or last N whitespace-separated words of text."""
if first_n is None and last_n is None:
return text
words = text.split()
if first_n is not None:
return " ".join(words[:first_n])
if last_n is not None:
return " ".join(words[-last_n:])
return text
# ── olmOCR-bench test evaluators ─────────────────────────────────────────────
def eval_text_presence(test: dict, ocr_output: str) -> bool:
"""Evaluate a present/text_presence test: target text must appear in OCR output.
Supports olmOCR-bench flat schema (max_diffs, first_n, last_n) and the
legacy nested-position schema.
"""
needle = test.get("text", "")
max_diffs = test.get("max_diffs", 0)
case_sensitive = test.get("case_sensitive", True)
first_n = test.get("first_n", None)
last_n = test.get("last_n", None)
haystack = _get_words_slice(ocr_output, first_n, last_n)
if max_diffs == 0:
return exact_contains(needle, haystack, case_sensitive=case_sensitive)
# Fuzzy: compute similarity threshold from allowed diffs
n = max(1, len(needle))
threshold = max(0.6, (n - max_diffs) / n)
return fuzzy_contains(needle, haystack, threshold=threshold)
def eval_text_absence(test: dict, ocr_output: str) -> bool:
"""Evaluate an absent/text_absence test: target text must NOT appear in OCR output.
Supports olmOCR-bench flat schema (max_diffs, first_n, last_n) and the
legacy nested-position schema.
"""
needle = test.get("text", "")
max_diffs = test.get("max_diffs", 0)
case_sensitive = test.get("case_sensitive", False)
first_n = test.get("first_n", None)
last_n = test.get("last_n", None)
haystack = _get_words_slice(ocr_output, first_n, last_n)
if max_diffs == 0:
present = exact_contains(needle, haystack, case_sensitive=case_sensitive)
else:
n = max(1, len(needle))
threshold = max(0.6, (n - max_diffs) / n)
present = fuzzy_contains(needle, haystack, threshold=threshold)
return not present
def eval_reading_order(test: dict, ocr_output: str) -> bool:
"""Evaluate an order/natural_reading_order test: 'before' text must precede 'after'.
Uses max_diffs to choose exact vs fuzzy matching.
"""
before_text = test.get("before", "")
after_text = test.get("after", "")
max_diffs = test.get("max_diffs", 0)
fuzzy = max_diffs > 0
output_norm = normalize_text(strip_markdown(ocr_output))
def find_approx_pos(needle: str, text: str) -> int:
needle = normalize_text(strip_markdown(needle).strip())
n = len(needle)
if n == 0:
return 0
# Exact first
idx = text.lower().find(needle.lower())
if idx != -1:
return idx
# Fuzzy fallback
step = max(1, n // 4)
best_pos, best_ratio = -1, 0.0
for i in range(0, max(1, len(text) - n + 1), step):
window = text[i : i + n]
ratio = SequenceMatcher(None, needle.lower(), window.lower()).ratio()
if ratio > best_ratio:
best_ratio = ratio
best_pos = i
return best_pos if best_ratio >= 0.80 else -1
if fuzzy:
pos_before = find_approx_pos(before_text, output_norm)
pos_after = find_approx_pos(after_text, output_norm)
else:
b = normalize_text(strip_markdown(before_text).strip())
a = normalize_text(strip_markdown(after_text).strip())
pos_before = output_norm.find(b)
pos_after = output_norm.find(a)
if pos_before == -1 or pos_after == -1:
return False
return pos_before < pos_after
# ── Table parsing helpers ─────────────────────────────────────────────────────
def _parse_markdown_table(text: str) -> List[List[str]]:
"""Parse a Markdown table into a list-of-rows, each row a list of cells."""
rows: List[List[str]] = []
for line in text.splitlines():
stripped = line.strip()
if "|" not in stripped:
continue
# Skip separator rows like |---|---|
if re.match(r"^\|?[-:| ]+\|?$", stripped):
continue
cells = [c.strip() for c in stripped.strip("|").split("|")]
if cells:
rows.append(cells)
return rows
class _HTMLTableParser(HTMLParser):
"""Minimal HTML table parser (does not handle colspan/rowspan)."""
def __init__(self) -> None:
super().__init__()
self.rows: List[List[str]] = []
self._current_row: List[str] = []
self._current_cell: str = ""
self._in_cell: bool = False
def handle_starttag(self, tag: str, attrs) -> None:
if tag == "tr":
self._current_row = []
elif tag in ("td", "th"):
self._in_cell = True
self._current_cell = ""
def handle_endtag(self, tag: str) -> None:
if tag in ("td", "th"):
self._current_row.append(self._current_cell.strip())
self._in_cell = False
elif tag == "tr" and self._current_row:
self.rows.append(self._current_row)
def handle_data(self, data: str) -> None:
if self._in_cell:
self._current_cell += data
def _extract_tables(ocr_output: str) -> List[List[List[str]]]:
"""Extract all tables (HTML + Markdown) from OCR output."""
tables: List[List[List[str]]] = []
# HTML tables
for match in re.finditer(
r"<table[^>]*>.*?</table>", ocr_output, re.DOTALL | re.IGNORECASE
):
parser = _HTMLTableParser()
parser.feed(match.group(0))
if parser.rows:
tables.append(parser.rows)
# Markdown tables
md_pattern = re.compile(
r"(\|[^\n]+\|\n(?:\|[-:| ]+\|\n)?(?:\|[^\n]+\|?\n?)+)", re.MULTILINE
)
for match in md_pattern.finditer(ocr_output):
rows = _parse_markdown_table(match.group(0))
if len(rows) >= 2: # At least header + one data row
tables.append(rows)
return tables
def eval_table_flat(test: dict, ocr_output: str) -> bool:
"""
Evaluate a flat-schema olmOCR-bench 'table' test.
Schema fields:
cell text of the target cell to locate
up/down/left/right expected text of the neighbor in that direction (null = skip)
top_heading expected column heading (row 0, same column)
left_heading expected row heading (column 0, same row)
All non-null fields must fuzzy-match for the test to pass.
Supports:
1. Structured tables (HTML with <tr>/<td>, Markdown) full positional check
2. Flat <table>content</table> blocks (DeepSeek-OCR-2 format) text presence
fallback when no rows are parseable from HTML
"""
cell_text = test.get("cell", "")
directional = {
"up": test.get("up"),
"down": test.get("down"),
"left": test.get("left"),
"right": test.get("right"),
}
top_heading = test.get("top_heading")
left_heading = test.get("left_heading")
checks = [(d, v) for d, v in directional.items() if v is not None]
# ── 1. Structured tables (HTML <tr>/<td> or Markdown) ────────────────────
for rows in _extract_tables(ocr_output):
if not rows:
continue
header_row = rows[0]
for r_idx, row in enumerate(rows):
for c_idx, cell in enumerate(row):
if not fuzzy_contains(cell_text, cell, threshold=0.85):
continue
# Verify all directional neighbors
all_ok = True
for direction, expected in checks:
if direction == "up" and r_idx > 0:
prev_row = rows[r_idx - 1]
nb = prev_row[c_idx] if c_idx < len(prev_row) else ""
if not fuzzy_contains(expected, nb, threshold=0.85):
all_ok = False
break
elif direction == "down" and r_idx < len(rows) - 1:
next_row = rows[r_idx + 1]
nb = next_row[c_idx] if c_idx < len(next_row) else ""
if not fuzzy_contains(expected, nb, threshold=0.85):
all_ok = False
break
elif direction == "left" and c_idx > 0:
nb = row[c_idx - 1]
if not fuzzy_contains(expected, nb, threshold=0.85):
all_ok = False
break
elif direction == "right" and c_idx < len(row) - 1:
nb = row[c_idx + 1]
if not fuzzy_contains(expected, nb, threshold=0.85):
all_ok = False
break
else:
all_ok = False
break # expected neighbor out of bounds
if not all_ok:
continue
# Verify top_heading (column header, row 0)
if top_heading is not None:
th = header_row[c_idx] if c_idx < len(header_row) else ""
if not fuzzy_contains(top_heading, th, threshold=0.85):
continue
# Verify left_heading (first cell of same row)
if left_heading is not None:
lh = row[0] if row else ""
if not fuzzy_contains(left_heading, lh, threshold=0.85):
continue
return True
# ── 2. Flat <table>…</table> fallback (DeepSeek-OCR-2 format) ────────────
# The model emits <table>AllCellsConcatenated</table> without <tr>/<td>.
# Fall back to checking that cell + all non-null headings/neighbors appear
# somewhere within the same table block.
flat_blocks = re.findall(
r"<table[^>]*>(.*?)</table>", ocr_output, re.DOTALL | re.IGNORECASE
)
for flat_text in flat_blocks:
# Strip any residual HTML tags (e.g. inline <br>) and bounding-box annotations
flat_clean = re.sub(r"<[^>]+>", " ", flat_text)
flat_clean = re.sub(r"\[\[\d+,\s*\d+,\s*\d+,\s*\d+\]\]", " ", flat_clean)
if not fuzzy_contains(cell_text, flat_clean, threshold=0.85):
continue
if top_heading is not None and not fuzzy_contains(
top_heading, flat_clean, threshold=0.85
):
continue
if left_heading is not None and not fuzzy_contains(
left_heading, flat_clean, threshold=0.85
):
continue
all_ok = all(
fuzzy_contains(expected, flat_clean, threshold=0.85)
for _, expected in checks
)
if all_ok:
return True
return False
def eval_baseline(test: dict, ocr_output: str) -> bool:
"""
Evaluate a baseline/sanity test.
When check_disallowed_characters is False (the common case), always passes.
When True, checks that the OCR output contains no non-printable control
characters (excluding normal whitespace).
"""
if not test.get("check_disallowed_characters", False):
return True
for ch in ocr_output:
cat = unicodedata.category(ch)
if cat.startswith("C") and ch not in ("\n", "\t", "\r", " "):
return False
return bool(ocr_output.strip()) # also fail if completely empty
def eval_table_accuracy(test: dict, ocr_output: str) -> bool:
"""
Evaluate a table_accuracy test (legacy nested schema).
Checks that a cell with ``cell_text`` exists in a table and that its
neighbor in the specified ``relationship`` (above/below/left/right)
contains ``neighbor_text``.
"""
cell_text = test.get("cell_text", "")
neighbor_text = test.get("neighbor_text", "")
relationship = test.get("relationship", "")
for rows in _extract_tables(ocr_output):
for r_idx, row in enumerate(rows):
for c_idx, cell in enumerate(row):
if not fuzzy_contains(cell_text, cell, threshold=0.88):
continue
# Found the target cell — check its neighbor
if relationship == "above" and r_idx > 0:
prev_row = rows[r_idx - 1]
nb = prev_row[c_idx] if c_idx < len(prev_row) else ""
if fuzzy_contains(neighbor_text, nb, threshold=0.88):
return True
elif relationship == "below" and r_idx < len(rows) - 1:
next_row = rows[r_idx + 1]
nb = next_row[c_idx] if c_idx < len(next_row) else ""
if fuzzy_contains(neighbor_text, nb, threshold=0.88):
return True
elif relationship == "left" and c_idx > 0:
nb = row[c_idx - 1]
if fuzzy_contains(neighbor_text, nb, threshold=0.88):
return True
elif relationship == "right" and c_idx < len(row) - 1:
nb = row[c_idx + 1]
if fuzzy_contains(neighbor_text, nb, threshold=0.88):
return True
return False
# Math formula evaluation ─────────────────────────────────────────────────────
_MATH_REGION_RE = re.compile(
r"\$\$[\s\S]*?\$\$" # $$ block $$
r"|\$[^$\n]+?\$" # inline $...$
r"|\\?\\\[[\s\S]*?\\?\\\]" # \[...\]
r"|\\?\\\([\s\S]*?\\?\\\)", # \(...\)
)
_LATEX_TOKEN_RE = re.compile(r"\\[a-zA-Z]+|[a-zA-Z0-9]|[+\-*/=<>^_{}()\[\]]")
def eval_math_formula_accuracy(test: dict, ocr_output: str) -> bool:
"""
Simplified math formula accuracy check.
Checks that the key symbol tokens from a LaTeX expression appear in
math-delimited regions of the OCR output.
Note: Full KaTeX bounding-box matching (as used by the official
olmOCR-bench) requires playwright and is not performed here.
"""
latex = (test.get("math") or test.get("latex") or "").strip()
if not latex:
return False
math_text = " ".join(m.group(0) for m in _MATH_REGION_RE.finditer(ocr_output))
if not math_text:
# Fall back to full output if no delimited regions found
math_text = ocr_output
tokens = _LATEX_TOKEN_RE.findall(latex)
if not tokens:
return False
present = sum(1 for t in tokens if t in math_text)
return present / len(tokens) >= 0.70
# ── Main dispatcher ───────────────────────────────────────────────────────────
_TEST_EVALUATORS = {
# olmOCR-bench flat-JSONL type names
"present": eval_text_presence,
"absent": eval_text_absence,
"order": eval_reading_order,
"math": eval_math_formula_accuracy,
"table": eval_table_flat,
"baseline": eval_baseline,
# Legacy / aliased names
"text_presence": eval_text_presence,
"text_absence": eval_text_absence,
"natural_reading_order": eval_reading_order,
"table_accuracy": eval_table_accuracy,
"math_formula_accuracy": eval_math_formula_accuracy,
}
def evaluate_olmocr_tests(tests: List[dict], ocr_output: str) -> List[dict]:
"""Run all olmOCR-bench unit tests against OCR output; return per-test results."""
results: List[dict] = []
for test in tests:
test_type = test.get("type", "")
evaluator = _TEST_EVALUATORS.get(test_type)
if evaluator is None:
results.append(
{
"type": test_type,
"passed": False,
"error": f"Unknown type: {test_type}",
}
)
continue
try:
passed = bool(evaluator(test, ocr_output))
except Exception as exc:
results.append({"type": test_type, "passed": False, "error": str(exc)})
continue
results.append({"type": test_type, "passed": passed})
return results
# ── Aggregation & reporting ───────────────────────────────────────────────────
def aggregate_results(split_name: str, sample_results: List[dict]) -> dict:
"""Aggregate per-sample results into split-level statistics."""
by_type: Dict[str, Dict[str, int]] = {}
total_passed = 0
total_tests = 0
error_count = 0
for sample in sample_results:
if "error" in sample and not sample.get("test_results"):
error_count += 1
continue
for tr in sample.get("test_results", []):
t = tr.get("type", "unknown")
by_type.setdefault(t, {"passed": 0, "total": 0})
by_type[t]["total"] += 1
total_tests += 1
if tr.get("passed"):
by_type[t]["passed"] += 1
total_passed += 1
type_scores = {
t: round(100.0 * v["passed"] / v["total"], 1) if v["total"] > 0 else 0.0
for t, v in by_type.items()
}
overall = round(100.0 * total_passed / total_tests, 1) if total_tests > 0 else 0.0
return {
"split": split_name,
"total_samples": len(sample_results),
"error_samples": error_count,
"total_tests": total_tests,
"total_passed": total_passed,
"overall_score": overall,
"by_type": type_scores,
"by_type_counts": by_type,
}
def print_results_table(all_results: Dict[str, dict]) -> None:
"""Print a formatted results summary table to stdout."""
sep = "=" * 70
print(f"\n{sep}")
print(" olmOCR-bench Results Summary (DeepSeek-OCR-2 via sglang)")
print(sep)
print(f"{'Split':<22} {'Tests':>8} {'Passed':>8} {'Score':>8}")
print("-" * 50)
splits = list(all_results.keys())
scores = []
for split in splits:
r = all_results[split]
score = r.get("overall_score", 0.0)
scores.append(score)
print(
f"{split:<22} {r['total_tests']:>8} {r['total_passed']:>8} {score:>7.1f}%"
)
print("-" * 50)
total_tests = sum(r["total_tests"] for r in all_results.values())
total_passed = sum(r["total_passed"] for r in all_results.values())
overall = round(100.0 * total_passed / total_tests, 1) if total_tests > 0 else 0.0
mean_score = round(sum(scores) / len(scores), 1) if scores else 0.0
print(f"{'TOTAL':<22} {total_tests:>8} {total_passed:>8} {overall:>7.1f}%")
print(f"{'Mean across splits':<22} {'':>17} {mean_score:>7.1f}%")
print(sep)
# Per-type breakdown
all_types: set = set()
for r in all_results.values():
all_types.update(r.get("by_type", {}).keys())
if all_types:
print("\nPer-test-type breakdown:")
print(f"{'Test Type':<35} {'Tests':>8} {'Score':>8}")
print("-" * 55)
for t in sorted(all_types):
totals = {"passed": 0, "total": 0}
for r in all_results.values():
counts = r.get("by_type_counts", {}).get(t, {"passed": 0, "total": 0})
totals["passed"] += counts["passed"]
totals["total"] += counts["total"]
type_score = (
round(100.0 * totals["passed"] / totals["total"], 1)
if totals["total"] > 0
else 0.0
)
print(f"{t:<35} {totals['total']:>8} {type_score:>7.1f}%")
print(sep)
# ── Normalized Edit Distance (OmniDocBench-style text quality metric) ─────────
def normalized_edit_distance(pred: str, ref: str) -> float:
"""
Character-level Normalized Edit Distance in [0, 1].
0.0 = identical, 1.0 = completely different.
"""
pred = normalize_text(pred.strip())
ref = normalize_text(ref.strip())
if not ref and not pred:
return 0.0
if not ref or not pred:
return 1.0
m, n = len(pred), len(ref)
# Space-optimised single-row DP
dp = list(range(n + 1))
for i in range(1, m + 1):
prev = dp[0]
dp[0] = i
for j in range(1, n + 1):
temp = dp[j]
if pred[i - 1] == ref[j - 1]:
dp[j] = prev
else:
dp[j] = 1 + min(prev, dp[j], dp[j - 1])
prev = temp
return dp[n] / max(m, n)