docubench-leaderboard / build_data.py
urimer's picture
Deploy DocuBench leaderboard
8a3e08d verified
Raw
History Blame Contribute Delete
5.63 kB
"""Build the self-contained leaderboard.json that the Hugging Face Space renders.
The Space must run standalone on Hugging Face with no access to the parent repository, so
this script merges the repo's canonical artifacts into a single file committed alongside
the app:
- results/summary.json -> scores, aggregates, file-type / language breakdowns
- sources.json -> per-document provenance (name, license, source url)
- docubench-explorer.html -> per-document capability flags (arrays, RTL, CJK, ...)
- results/<engine>/<id>.json -> the model id stamped in each result's meta block
Scores always come from summary.json (the output of `docubench report`); this script
never recomputes them. Run it from anywhere after regenerating the report:
python3 space/build_data.py
"""
from __future__ import annotations
import json
import math
from json import JSONDecoder
from pathlib import Path
from typing import Any
REPO_ROOT = Path(__file__).resolve().parent.parent
SPACE_DIR = Path(__file__).resolve().parent
OUTPUT_PATH = SPACE_DIR / "leaderboard.json"
CAPABILITY_LABELS = {
"arrays": "Array / line-item tables",
"reconcile": "Totals must reconcile",
"rtl": "Right-to-left script",
"cjk": "CJK script",
"handwriting": "Handwriting",
"rotated": "Rotated scan",
"needle": "Needle-in-haystack lookup",
"nested": "Nested objects",
}
def load_json(path: Path) -> Any:
with open(path, encoding="utf-8") as f:
return json.load(f)
def extract_explorer_flags(html_path: Path) -> dict[str, dict[str, bool]]:
"""Pull the per-document capability flags out of the explorer's embedded DATA blob."""
if not html_path.exists():
return {}
text = html_path.read_text(encoding="utf-8")
marker = "const DATA = "
start = text.find(marker)
if start == -1:
return {}
brace = text.find("{", start)
data, _ = JSONDecoder().raw_decode(text[brace:])
flags: dict[str, dict[str, bool]] = {}
for doc in data.get("docs", []):
if "id" in doc and isinstance(doc.get("flags"), dict):
flags[doc["id"]] = {k: bool(v) for k, v in doc["flags"].items()}
return flags
def engine_model(engine: str, doc_ids: list[str]) -> str | None:
"""Best-effort model id from the first result file that records one in meta."""
for doc_id in doc_ids:
path = REPO_ROOT / "results" / engine / f"{doc_id}.json"
if not path.exists():
continue
try:
meta = (load_json(path) or {}).get("meta") or {}
except (json.JSONDecodeError, OSError):
continue
if meta.get("model"):
return str(meta["model"])
return None
def mean(values: list[float]) -> float | None:
values = [v for v in values if v is not None]
return math.fsum(values) / len(values) if values else None
def build() -> dict[str, Any]:
summary = load_json(REPO_ROOT / "results" / "summary.json")
sources = {row["doc_id"]: row for row in load_json(REPO_ROOT / "sources.json") if "doc_id" in row}
flags_by_doc = extract_explorer_flags(REPO_ROOT / "docubench-explorer.html")
engines = list(summary["aggregates"].keys())
display_names = summary.get("engine_display_names", {})
per_doc_scores = {row["doc_id"]: row for row in summary["per_doc"]}
doc_ids = [row["doc_id"] for row in summary["per_doc"]]
documents = []
for doc_id in doc_ids:
src = sources.get(doc_id, {})
scores = {engine: per_doc_scores[doc_id].get(engine) for engine in engines}
documents.append({
"doc_id": doc_id,
"name": src.get("name", doc_id),
"lang": src.get("lang", "unknown"),
"ftype": src.get("ftype", "unknown"),
"pages": src.get("pages"),
"feature": src.get("hard_feature", ""),
"license": src.get("license", ""),
"source_url": src.get("source_url", ""),
"flags": flags_by_doc.get(doc_id, {}),
"scores": scores,
})
# capability breakdown: per-engine mean over the documents carrying each flag
capability_rows = []
for flag, label in CAPABILITY_LABELS.items():
matching = [d for d in documents if d["flags"].get(flag)]
if not matching:
continue
row: dict[str, Any] = {"flag": flag, "label": label, "doc_count": len(matching)}
for engine in engines:
row[engine] = mean([d["scores"].get(engine) for d in matching])
capability_rows.append(row)
return {
"benchmark": summary.get("benchmark", {}),
"engines": [
{
"key": engine,
"display": display_names.get(engine, engine),
"model": engine_model(engine, doc_ids),
"overall": summary["aggregates"][engine],
}
for engine in engines
],
"breakdowns": {
"ftype": summary["breakdowns"].get("ftype", []),
"lang": summary["breakdowns"].get("lang", []),
"capability": capability_rows,
},
"documents": documents,
}
def main() -> int:
data = build()
with open(OUTPUT_PATH, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
f.write("\n")
print(
f"wrote {OUTPUT_PATH.relative_to(REPO_ROOT)}: "
f"{len(data['engines'])} engines, {len(data['documents'])} documents, "
f"{len(data['breakdowns']['capability'])} capability rows"
)
return 0
if __name__ == "__main__":
raise SystemExit(main())