Download src/knowledge_parsing/report.py from DataEyond/Agentic-Service-Data-Eyond-Catalog: direct link, hf CLI and curl.
- Browser
- Download file 4.56 kB
-
https://huggingface.co/spaces/DataEyond/Agentic-Service-Data-Eyond-Catalog/resolve/main/src/knowledge_parsing/report.py
- Command line
-
hf download hf://spaces/DataEyond/Agentic-Service-Data-Eyond-Catalog/src/knowledge_parsing/report.py
-
curl -L -o report.py https://huggingface.co/spaces/DataEyond/Agentic-Service-Data-Eyond-Catalog/resolve/main/src/knowledge_parsing/report.py
4.56 kB
| """Summarise every run into figures ready for a resourcing argument. | |
| py -m src.knowledge_parsing.report | |
| py -m src.knowledge_parsing.report --scale 6800 | |
| `--scale` extrapolates to a given page count (e.g. BUMA scale, ~6,700-6,800 | |
| pages) using the MEASURED seconds/page — documents served from cache are left | |
| out, because including them would make the figure look far faster than reality. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| from pathlib import Path | |
| from .config import PipelineConfig | |
| # Below this, the fixed cost of loading models dominates, so seconds/page comes | |
| # out far higher than it would on a large document. Measured on this machine: | |
| # 1 page -> 54.0 s/page, 5 pages -> 33.2, 20 pages -> 19.0. | |
| MIN_PAGES_TO_EXTRAPOLATE = 20 | |
| def _duration(seconds: float) -> str: | |
| if seconds < 90: | |
| return f"{seconds:.0f} s" | |
| if seconds < 5400: | |
| return f"{seconds/60:.1f} min" | |
| return f"{seconds/3600:.1f} h" | |
| def collect(runs_dir: Path) -> list[dict]: | |
| results = [] | |
| for m in sorted(runs_dir.glob("*/manifest.json")): | |
| try: | |
| results.append(json.loads(m.read_text(encoding="utf-8"))) | |
| except (OSError, json.JSONDecodeError) as e: | |
| # Manifest corrupt or half-written (a run that died mid-way). Skipped, | |
| # but reported — a report that silently loses a run gives a wrong | |
| # seconds/page figure with nothing to show for it. | |
| print(f" ! skipping unreadable manifest: {m} ({type(e).__name__})") | |
| return results | |
| def render(runs: list[dict], scale: int | None) -> None: | |
| if not runs: | |
| print("No runs yet. Try: py -m src.knowledge_parsing.run --input data/knowledge_docs/") | |
| return | |
| print(f"{'run':<17} {'backend':<9} {'ver':<8} {'docs':>4} {'pages':>5} " | |
| f"{'s/page':>8} {'warnings':>9}") | |
| print("-" * 66) | |
| for r in runs: | |
| s = r.get("summary", {}) | |
| cfg = r.get("config", {}) | |
| # the backend recorded in MinerU's output is trusted over the config | |
| recorded = {d.get("backend_recorded") for d in r.get("documents", [])} - {None} | |
| backend = "/".join(sorted(recorded)) if recorded else cfg.get("backend", "?") | |
| spp = s.get("seconds_per_page") | |
| print(f"{r.get('run_id',''):<17} {backend:<9} " | |
| f"{str(r.get('mineru_version') or '?')[:7]:<8} " | |
| f"{s.get('documents_ok',0):>4} {s.get('total_pages',0):>5} " | |
| f"{(f'{spp:.2f}' if spp else '-'):>8} {s.get('total_warnings',0):>9}") | |
| measured = [r for r in runs if (r.get("summary") or {}).get("seconds_per_page")] | |
| if not measured: | |
| print("\n(no run has actually parsed anything yet — all cache hits)") | |
| return | |
| print("\n--- extrapolation ---") | |
| target = scale or 6800 | |
| doubtful = False | |
| for r in measured: | |
| s = r["summary"] | |
| spp = s["seconds_per_page"] | |
| pages = s.get("pages_measured", 0) | |
| cfg = r.get("config", {}) | |
| note = "" | |
| if pages < MIN_PAGES_TO_EXTRAPOLATE: | |
| note = f" ⚠ only {pages} page" + ("" if pages == 1 else "s") | |
| doubtful = True | |
| print(f" {cfg.get('backend','?'):<9} {spp:>6.2f} s/page → " | |
| f"{target} pages = {_duration(spp * target)}{note}") | |
| if doubtful: | |
| print( | |
| f"\n ⚠ A run under {MIN_PAGES_TO_EXTRAPOLATE} pages is NOT fit to " | |
| "extrapolate from.\n" | |
| " Model loading is a fixed cost, so a small document looks far\n" | |
| " slower per page. Measured earlier on this machine:\n" | |
| " 1 page -> 54.0 s/page | 5 pages -> 33.2 | 20 pages -> 19.0\n" | |
| " Use a document of >= 20 pages before the number leaves the room." | |
| ) | |
| if len(measured) >= 2: | |
| fastest = min(measured, key=lambda r: r["summary"]["seconds_per_page"]) | |
| slowest = max(measured, key=lambda r: r["summary"]["seconds_per_page"]) | |
| a = slowest["summary"]["seconds_per_page"] | |
| b = fastest["summary"]["seconds_per_page"] | |
| if b: | |
| print(f"\n fastest vs slowest: {a/b:.1f}×") | |
| def main() -> None: | |
| p = argparse.ArgumentParser(description="Summary figures across every run") | |
| p.add_argument("--runs-dir", type=Path, default=None) | |
| p.add_argument("--scale", type=int, default=None, | |
| help="extrapolate to this many pages (e.g. 6800)") | |
| a = p.parse_args() | |
| cfg = PipelineConfig() | |
| render(collect(a.runs_dir or cfg.runs_dir), a.scale) | |
| if __name__ == "__main__": | |
| main() | |