A credit note is not an invoice
A credit memo and a supplier invoice often share a layout: vendor name, reference number, line items, tax, total. Point the same extractor at both and you get plausible JSON either way. On BuilderHelp's supplier-invoice path, that is how a credit becomes a payable and a proforma becomes a bill. The fields were never the hard part. The document type was — and type has to be named before you extract, not inferred after you post.
Same fields, opposite verb
Extraction answers what is on the page. Classification answers what the page is allowed to do next. An invoice posts a payable. A credit note reduces what you owe. A proforma or quote is not payable at all. A packing slip or remittance page may help matching and still must not create an AP voucher.
Those documents look alike enough that a single invoice-shaped schema will happily fill every slot. The model did not fail at reading. The pipeline failed at deciding which reading is allowed to become which action.
Treat document type as a first-class field on the operational record — decided early, versioned with the rules that chose it — not as a label someone might add in review after the numbers already look fine.
BuilderHelp: the mailbox is not an invoice feed
BuilderHelp's AP intake is a real construction mailbox: supplier invoices, credit memos for returned materials, statements, delivery tickets, and the occasional quote that someone forwarded "so we have it." The OCR microservice is there to turn supplier paperwork into structured candidates for matching and posting — not to assume every PDF is a bill.
When type is skipped, the failure mode is quiet. A credit with a clear total extracts cleanly; a normalizer that always posts absolute amounts books it positive; payables swing by twice the credit. The extraction confidence can be high the whole time. Arithmetic on the page can even reconcile. The wrong verb still shipped.
The fix we build toward is boring: classify first into a small closed set — invoice, credit note, proforma or quote, statement, supporting page, unknown — then run type-specific extraction and a type-specific post path. Unknown goes to a person. Supporting pages stay attached as evidence without inventing a voucher.
Sign is a type rule, not a string parse
Credit notes advertise themselves with language ("credit memo", "credit note", "refund") and sometimes with a negative total. Relying on the minus sign alone is fragile: OCR drops it, layout puts CR in a column header, or a normalizer strips non-digits while cleaning currency.
Keep the raw amount and the document type separate. Apply sign from type when you build the posting payload — credits subtract, invoices add — instead of hoping the cleaned number retained polarity. If type is credit and the page shows a positive figure, that is still a credit; do not "fix" it into an invoice because the digits look unsigned.
Deerman's PO pipeline has the same shape in a different costume: a change order or cancel is not "another PO with different quantities." Downstream systems need the correct document class before line extraction is allowed to drive a manufacturer commitment.
What we refuse to auto-post
Auto-post only when type is invoice or credit note, the type score or rule is decisive, high-risk fields pass their checks, and the type-specific invariants hold. A credit should reference an original invoice when the page has one; an invoice should not look like a quote watermarked "proforma."
Mixed batches need a page-level decision, not a file-level hope. A PDF that is three invoice pages and a remittance page should not flatten into four invoice rows. Classify each page, extract what belongs, and attach the rest.
If classification is unsure, stop. Sending an ambiguous document through the invoice extractor "to see the fields" trains the team to trust a shape that was never authorized. Review then confirms or overrides type before anyone talks about totals.
What we ask when a pipeline skips type
What closed set of document types can arrive in this channel, and which of them are allowed to post?
Where is type stored on the record, and which rules or model version produced it?
Does the posting path branch on type — including sign and whether a voucher is created at all — or only on extracted amounts?
What happens to unknown and supporting pages, and who owns that queue?
If those answers are missing, the extractor is not ready for a mixed mailbox. Wiring confidence thresholds on an invoice schema will not save you from booking the wrong verb.