← How I Built It
Claude Haiku 4.5

How I built the Invoice Processing demo

Pull structured data — vendor, line items, totals, due date — out of a PDF, Word doc, or plain text invoice with a real text layer. Not a formatted sample PDF; whatever gets forwarded to AP.

Last reviewed

Evidence typeModel demonstrationClaude Haiku 4.5 processes the real file or text a visitor submits, live, via the production endpoint; this page's benchmark (8 hand-authored synthetic invoices, run 2026-08-02) is reported below, not pre-scripted.

The problem

Invoices arrive as PDFs, scanned images saved as PDFs, Word docs, or plain email text pasted into a form. Most invoice-AI demos quietly assume the first case. Real AP inboxes get all of them, plus the occasional file that isn't actually an invoice at all. This demo currently covers PDFs, DOCX, and plain text with a real text layer — a scanned image with no embedded text (a photo, or a PDF that's really just a picture) needs an OCR step this pipeline doesn't run yet, and isn't demonstrated here.

The pipeline

File upload hits a shared /api/extract-text route before the invoice-specific logic ever runs:

  1. 01Detect file type by extension, not MIME type — browsers report MIME types inconsistently across OS/browser combinations, extensions don't.
  2. 02PDF → unpdf (serverless-compatible; pdf-parse and pdfjs-dist both hit Node API assumptions that break under Vercel's runtime — this took two failed attempts to learn).
  3. 03DOCX → mammoth, extracting raw text only.
  4. 04TXT/MD/CSV → read directly, no parsing needed.
  5. 05Text truncated to a 10,000-character ceiling before it ever reaches a model call.

The extraction call

The prompt asks for one JSON object — vendor, invoice number, dates, PO number, line items, subtotal, tax, total, payment instructions, and a self-reported confidence score — and nothing else. The response is regex-matched for the outermost{...}block before JSON.parse, since models occasionally wrap output in a sentence even when told not to.

Why Haiku, not Sonnet

This is field extraction against text that's already been parsed out of the source file — there's no multi-step reasoning, no judgment call, no ambiguity to weigh. Claude Haiku 4.5 handles it at a fraction of the latency and cost of a larger model. The Fusion Cloud AI-Readiness Assessment demo, which does require judgment, runs on Sonnet 5 instead — matching model size to the actual reasoning load is a cost decision I apply to client work, not just this site.

Labeled benchmark

Run 2026-08-02

Model

Claude Haiku 4.5 (claude-haiku-4-5-20251001)

Sample size

8 synthetic invoices, 21 scored fields

Data source

8 hand-authored synthetic invoices (clean, messy, multilingual, non-invoice, credit-memo formats) run live against the production endpoint at tioga.ai — not a cached or pre-recorded result.

Field-match rate (vendor / invoice # / total)

21/21 fields (100%)

Successful extractions

8/8 (HTTP 200)

Average latency

~1.7s

Non-invoice input

Correctly flagged, confidence 5/100, no fabricated fields

Limitations

  • Sample size is small (8 cases) and hand-authored, not drawn from a real-world invoice corpus — a production engagement would validate against your actual invoice formats before go-live.
  • Field-match scoring used fuzzy substring matching, not strict equality — an exact-match check would likely show a lower, more conservative number.
  • Only 3 fields (vendor, invoice number, total) were scored; line-item and date-field accuracy weren't measured in this run.