Python API reference¶
from invoice_agent.data import load_purchase_orders, load_receipts
from invoice_agent.pipeline import Pipeline
pipeline = Pipeline(load_purchase_orders("pos.csv"), load_receipts("receipts.csv"))
result = pipeline.process_pdf("invoice.pdf")
print(result.decision, [r.code for r in result.reasons])
Pipeline¶
invoice_agent.pipeline
¶
End-to-end pipeline: ingest, extract, validate, match, decide.
Pipeline
¶
Process invoices one after another against the same PO data.
The pipeline keeps a :class:Ledger, so the order of calls matters: a
second invoice for the same goods is caught as a duplicate or as
overbilling.
process_invoice(invoice, *, source=None)
¶
Validate and match an already extracted invoice, then record it in the ledger.
decide(reasons)
¶
The strictest severity wins: any reject rejects, any review needs review.
Schema¶
invoice_agent.schema
¶
Strict data models shared by every stage of the pipeline.
Money is always :class:decimal.Decimal. Floats never touch an amount.
LineItem
¶
Bases: _Strict
One billed line on an invoice.
Invoice
¶
Bases: _Strict
Structured invoice produced by an extractor.
missing_required()
¶
Return the names of fields that matching cannot run without.
Reason
¶
Bases: _Strict
A single explainable finding. code is stable; message is for humans.
Result
¶
Bases: _Strict
Pipeline output for one invoice.
Policy¶
invoice_agent.policy.MatchPolicy
¶
Bases: BaseModel
Thresholds used by validation and matching. All fields have safe defaults.
Data loading¶
invoice_agent.data
¶
Load purchase orders and goods receipts from JSON, CSV or SQLite.
JSON files hold a list of objects shaped like :class:PurchaseOrder or
:class:GoodsReceipt. CSV files and SQLite tables hold one row per line:
- purchase orders:
po_number, vendor, currency, receipt_required, line_no, sku, description, quantity, unit_price - receipts:
receipt_id, po_number, date, line_no, quantity
A SQLite database uses the tables po_lines and receipt_lines with the
same columns.
load_purchase_orders(path)
¶
Load purchase orders from a .json, .csv or SQLite file.
load_receipts(path)
¶
Load goods receipts from a .json, .csv or SQLite file.
po_from_rows(rows)
¶
Group flat PO line rows into purchase orders, keeping first-seen order.
receipts_from_rows(rows)
¶
Group flat receipt line rows into goods receipts.
po_to_rows(pos)
¶
Flatten purchase orders into CSV/SQLite rows.
receipts_to_rows(receipts)
¶
Flatten goods receipts into CSV/SQLite rows.
write_sqlite(path, pos, receipts)
¶
Write purchase orders and receipts to a SQLite database.
Matching¶
invoice_agent.match
¶
Two-way and three-way matching of invoices against POs and goods receipts.
The matcher is stateful on purpose. A :class:Ledger remembers every invoice
it has seen and how much of each PO line is already billed, so it can catch
duplicate submissions and cumulative overbilling across several invoices.
Ledger
dataclass
¶
Invoices processed so far, and billed quantity per (PO, line).
record(invoice, decision, po_number, matches, source=None)
¶
Remember an invoice. Quantities count as billed unless it was rejected.
Matcher
¶
Matches invoices against a fixed set of POs and receipts.
infer_po(inv)
¶
Pick the vendor's open PO whose lines best fit the invoice.
vendor_similarity(a, b)
¶
Fuzzy similarity (0-100) of two vendor names after dropping legal suffixes.
description_similarity(a, b)
¶
Similarity (0-1) of two line descriptions, insensitive to case, order and punctuation.
align_lines(lines, po_lines, threshold)
¶
Pair invoice lines with PO lines, one-to-one.
A SKU match scores 1.0. Otherwise the description similarity is used, and
pairs below threshold stay unmatched. Lines where both sides carry a
different SKU never pair. Pairs are assigned greedily, best score first.
Validation¶
invoice_agent.validate
¶
Arithmetic checks: line amounts, subtotal, and total.
validate_arithmetic(inv, policy=None)
¶
Check that the numbers on the invoice add up.
- each line:
quantity * unit_price == amount(withinline_rounding) sum(line amounts) == subtotalsubtotal - discount + tax == total
Differences up to policy.line_rounding per line are reported as
info so the reviewer can see them, without blocking approval.
Normalization¶
invoice_agent.normalize
¶
Parsing helpers for amounts, dates, currencies and identifiers.
parse_amount(text)
¶
Parse 1,234.56, 1.234,56 €, (12.00), USD 99 and similar.
The decimal separator is whichever of . or , appears last and is
followed by one to four digits at the end of the string.
detect_currency(text)
¶
Return the most frequent ISO code or symbol found in text.
parse_date(text, *, day_first=False)
¶
Parse common invoice date formats.
Supports 2026-03-14, 14.03.2026, 03/14/2026 (or 14/03/2026
when day_first), 14 Mar 2026 and March 14, 2026.
normalize_doc_number(value)
¶
Canonical form of an invoice or PO number: uppercase alphanumerics only.
inv-001 042 and INV001042 both become INV001042.
normalize_po(value)
¶
Canonical PO number. Drops a leading PO so PO-2026-0101 equals 2026-0101.