Spaces:
Running
Running
Download src/doc_verify/validation.py from gaurabdas/doc-verify: direct link, hf CLI and curl.
- Browser
- Download file 5.57 kB
-
https://huggingface.co/spaces/gaurabdas/doc-verify/resolve/main/src/doc_verify/validation.py
- Command line
-
hf download hf://spaces/gaurabdas/doc-verify/src/doc_verify/validation.py
-
curl -L -o validation.py https://huggingface.co/spaces/gaurabdas/doc-verify/resolve/main/src/doc_verify/validation.py
5.57 kB
| """Checks facts supplied to it; makes no model calls or tax-policy decisions.""" | |
| import re | |
| from decimal import ROUND_HALF_UP, Decimal | |
| from .models import Document | |
| def verify(invoice, purchase): | |
| inv = Document.model_validate(invoice) | |
| po = Document.model_validate(purchase) if purchase else None | |
| findings = [] | |
| def flag(code, field, message): | |
| findings.append({"code": code, "field": field, "message": message}) | |
| for name, doc in (("invoice", inv), ("purchase", po)): | |
| if doc is None: | |
| continue | |
| for field in ("vendor", "number", "date", "currency", "subtotal", "tax", "total"): | |
| if getattr(doc, field) is None: | |
| flag( | |
| "missing_field", | |
| f"{name}.{field}", | |
| "Not extracted; review the original document.", | |
| ) | |
| quantum = Decimal("1") if doc.currency == "JPY" else Decimal("0.01") | |
| if doc.currency not in {"INR", "USD", "EUR", "GBP", "JPY"}: | |
| flag( | |
| "unsupported_currency", | |
| f"{name}.currency", | |
| "Currency precision is not supported; review monetary calculations.", | |
| ) | |
| arithmetic = False | |
| else: | |
| arithmetic = True | |
| if not doc.lines: | |
| flag("missing_lines", name, "No line items extracted.") | |
| for index, line in enumerate(doc.lines): | |
| if any(getattr(line, key) is None for key in ("quantity", "unit_price", "amount")): | |
| flag( | |
| "missing_field", | |
| f"{name}.lines.{index}", | |
| "Line amount, quantity, or price is unknown.", | |
| ) | |
| elif arithmetic and (line.quantity * line.unit_price).quantize( | |
| quantum, rounding=ROUND_HALF_UP | |
| ) != line.amount.quantize(quantum, rounding=ROUND_HALF_UP): | |
| flag( | |
| "line_total_mismatch", | |
| f"{name}.lines.{index}", | |
| "Quantity × unit price does not equal line amount.", | |
| ) | |
| if ( | |
| arithmetic | |
| and doc.lines | |
| and all(line.amount is not None for line in doc.lines) | |
| and doc.subtotal is not None | |
| ): | |
| expected = sum((line.amount for line in doc.lines), Decimal(0)).quantize( | |
| quantum, rounding=ROUND_HALF_UP | |
| ) | |
| if expected != doc.subtotal.quantize(quantum, rounding=ROUND_HALF_UP): | |
| flag( | |
| "subtotal_mismatch", | |
| f"{name}.subtotal", | |
| "Sum of line amounts differs from subtotal.", | |
| ) | |
| if arithmetic and all( | |
| getattr(doc, field) is not None for field in ("subtotal", "tax", "total") | |
| ): | |
| if (doc.subtotal + doc.tax).quantize( | |
| quantum, rounding=ROUND_HALF_UP | |
| ) != doc.total.quantize(quantum, rounding=ROUND_HALF_UP): | |
| flag( | |
| "total_mismatch", | |
| f"{name}.total", | |
| "Subtotal + stated tax differs from total. Check discounts or other charges manually.", | |
| ) | |
| if po: | |
| currencies_match = bool(inv.currency and po.currency and inv.currency == po.currency) | |
| if not currencies_match: | |
| flag( | |
| "currency_mismatch", | |
| "currency", | |
| "Currencies differ or are unknown; monetary comparison skipped.", | |
| ) | |
| used = set() | |
| for index, line in enumerate(inv.lines): | |
| def normalize(text): | |
| return re.sub(r"\s+", " ", (text or "").strip().casefold()) | |
| matches = [ | |
| (j, other) | |
| for j, other in enumerate(po.lines) | |
| if (line.sku and other.sku == line.sku) | |
| or ( | |
| not line.sku | |
| and line.description | |
| and normalize(other.description) == normalize(line.description) | |
| ) | |
| ] | |
| if len(matches) != 1 or matches[0][0] in used: | |
| flag( | |
| "ambiguous_match" if matches else "unmatched_line", | |
| f"invoice.lines.{index}", | |
| "Cannot establish a unique purchase-order line; review manually.", | |
| ) | |
| continue | |
| matched, other = matches[0] | |
| used.add(matched) | |
| if ( | |
| line.quantity is not None | |
| and other.quantity is not None | |
| and line.quantity != other.quantity | |
| ): | |
| flag( | |
| "quantity_mismatch", | |
| f"invoice.lines.{index}", | |
| "Invoice quantity differs from purchase order.", | |
| ) | |
| if ( | |
| currencies_match | |
| and line.unit_price is not None | |
| and other.unit_price is not None | |
| and line.unit_price != other.unit_price | |
| ): | |
| flag( | |
| "price_mismatch", | |
| f"invoice.lines.{index}", | |
| "Invoice unit price differs from purchase order.", | |
| ) | |
| for index in set(range(len(po.lines))) - used: | |
| flag( | |
| "unmatched_purchase_line", | |
| f"purchase.lines.{index}", | |
| "Purchase-order line was not uniquely matched; partial fulfillment may be intentional.", | |
| ) | |
| return findings | |