"""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