Context
A small logistics client received dozens of supplier invoices as PDFs each week. Staff retyped vendor name, invoice number, line totals, and tax into their accounting tool. Typos showed up in month-end reconciliation; nobody trusted "AI" to post directly.
The ask was not a chatbot. It was fewer hours of transcription with a safety net.
What I built
An upload queue: PDF in → structured draft out → human approves or edits → export row to their existing workflow.
Extracted fields: vendor, invoice ID, date, currency, subtotal, tax, line-item summary (collapsed when the PDF was messy). Low-confidence parses highlighted in yellow. Every approved row logged with who signed off and the original file hash.
Kill switch: feature flag in env config. If the API is down, staff fall back to manual entry-no blocked workflow.
Technical approach
PDF text layer first; OCR path only when needed. Prompt template locked to JSON schema output; invalid JSON rejected, not silently stored.
No training data left the client's bucket. API calls used vendor documents only; retention policy documented in handoff doc.
Review UI built as a thin internal page-not embedded in a flashy copilot panel-so staff treated it like data entry with suggestions, not magic.
Outcomes
- Roughly 70% of standard-format invoices usable with minor edits instead of full retype
- Zero auto-posted rows in production; compliance with their "human checks money" rule
- Owner could explain the feature in one sentence and disable it without calling me
Stack
Node.js · TypeScript · LLM API · PDF.js · PostgreSQL · Simple React review UI