Spaces:
Running
Running
Download src/doc_verify/evaluate.py from gaurabdas/doc-verify: direct link, hf CLI and curl.
- Browser
- Download file 4.52 kB
-
https://huggingface.co/spaces/gaurabdas/doc-verify/resolve/main/src/doc_verify/evaluate.py
- Command line
-
hf download hf://spaces/gaurabdas/doc-verify/src/doc_verify/evaluate.py
-
curl -L -o evaluate.py https://huggingface.co/spaces/gaurabdas/doc-verify/resolve/main/src/doc_verify/evaluate.py
4.52 kB
| import json | |
| import time | |
| from datetime import datetime, timezone | |
| from decimal import Decimal | |
| from itertools import zip_longest | |
| from pathlib import Path | |
| from .extraction import extract, pages | |
| from .runtime import FreeModel, Settings | |
| from .validation import verify | |
| def extraction_metrics(actual, expected): | |
| numeric = {"subtotal", "tax", "total", "quantity", "unit_price", "amount"} | |
| def same(key, left, right): | |
| if left is None or right is None: | |
| return left is right | |
| return Decimal(str(left)) == Decimal(str(right)) if key in numeric else left == right | |
| header = ("vendor", "number", "date", "currency", "subtotal", "tax", "total") | |
| line_fields = ("sku", "description", "quantity", "unit_price", "amount") | |
| scores = [ | |
| same(key, a.get(key), b.get(key)) | |
| for a, b in zip_longest(actual.get("lines", []), expected.get("lines", []), fillvalue={}) | |
| for key in line_fields | |
| ] | |
| return { | |
| "header_accuracy": sum(same(key, actual.get(key), expected.get(key)) for key in header) | |
| / len(header), | |
| "line_accuracy": sum(scores) / len(scores) if scores else 1.0, | |
| } | |
| def evaluate(live=False, limit=None, resume=False, cases=None): | |
| root = Path("data/samples") | |
| labels = json.loads((root / "labels.json").read_text()) | |
| model = FreeModel(Settings()) if live else None | |
| if model: | |
| model.qualify() | |
| output = Path("reports") / ("live.json" if live else "deterministic.json") | |
| rows = [] | |
| if resume and output.exists(): | |
| previous = json.loads(output.read_text()) | |
| if previous.get("model") != (model.settings.model_id if model else None): | |
| raise ValueError("Cannot resume an evaluation with a different model.") | |
| rows = previous["cases"] | |
| completed = {row["case"] for row in rows} | |
| attempted = 0 | |
| def save(): | |
| output.parent.mkdir(exist_ok=True) | |
| output.write_text( | |
| json.dumps( | |
| { | |
| "mode": "live extraction" | |
| if live | |
| else "labeled structured inputs; does not measure extraction or OCR", | |
| "dataset": "fictional-documents-v1", | |
| "date_utc": datetime.now(timezone.utc).isoformat(), | |
| "model": model.settings.model_id if model else None, | |
| "unattempted": len(labels) - len(rows), | |
| "cases": rows, | |
| }, | |
| indent=2, | |
| ) | |
| ) | |
| for case in labels: | |
| if case["id"] in completed or (cases and case["id"] not in cases): | |
| continue | |
| if limit is not None and attempted >= limit: | |
| break | |
| attempted += 1 | |
| start = time.monotonic() | |
| try: | |
| if live: | |
| inv_path, po_path = root / case["invoice_file"], root / case["purchase_file"] | |
| inv = extract(model, pages(inv_path.read_bytes(), inv_path.name)).model_dump( | |
| mode="json" | |
| ) | |
| time.sleep(8) | |
| po = extract(model, pages(po_path.read_bytes(), po_path.name)).model_dump( | |
| mode="json" | |
| ) | |
| else: | |
| inv, po = case["invoice"], case["purchase"] | |
| actual = set(f["code"] for f in verify(inv, po)) | |
| expected = set(case["expected_codes"]) | |
| rows.append( | |
| { | |
| "case": case["id"], | |
| "split": case["split"], | |
| "passed": actual == expected, | |
| "expected": sorted(expected), | |
| "actual": sorted(actual), | |
| "invoice_extraction": extraction_metrics(inv, case["invoice"]) | |
| if live | |
| else None, | |
| "purchase_extraction": extraction_metrics(po, case["purchase"]) | |
| if live | |
| else None, | |
| "invoice": inv if live else None, | |
| "purchase": po if live else None, | |
| "seconds": time.monotonic() - start, | |
| } | |
| ) | |
| except (ValueError, RuntimeError) as error: | |
| rows.append({"case": case["id"], "passed": False, "error": str(error)}) | |
| if "RateLimitError" in str(error): | |
| save() | |
| break | |
| save() | |
| if live: | |
| time.sleep(8) | |
| save() | |
| print(f"{sum(row['passed'] for row in rows)}/{len(rows)} scenarios passed; {output}") | |