← All notes
Document & OCR Automation7 min

The invariant that stops a clever parser from inventing data

Recovery passes that mine descriptions for part codes are valuable and dangerous: the same cleverness invents plausible fakes from ordinary words. Two invariants hold the line — only touch lines whose current code is already useless, and never adopt a candidate until the manufacturer database confirms it exists.

The failure that looks like success

Most parser bugs announce themselves. A field is empty, a number is obviously wrong, a line is missing. Someone notices. The dangerous failure is different: the parser produces a part code that is well-formed, plausible, and wrong. It flows through matching, reconciles against something, lands on a manufacturer PO, and gets ordered.

This is not hypothetical with description-mining heuristics. Product descriptions are full of tokens that look like part codes — model fragments, finish codes, dimensions, sometimes an ordinary capitalized word. A pass that scans a description for 'the thing that looks most like a part number' will find something in nearly every description, including the ones where there is nothing to find.

An empty field costs you a review. A confidently wrong field costs you an order. Those are not the same kind of bug, and the code should not treat them as the same kind of risk.

Rule one: only touch lines that are already useless

Every recovery pass is gated on the existing code being unusable. Concretely, that check is isPureStockNumber — the line's current code is a vendor stock number or something equally meaningless to the manufacturer, so there is nothing to lose by trying to replace it. If the line already carries a code that could be real, no pass touches it.

This inverts the usual instinct, which is to run the smart heuristic everywhere and let it improve whatever it can. Running everywhere means a pass can overwrite a correct code with its own guess, and now your best-case documents are exposed to your most speculative logic. Gating on uselessness means the passes can only move a line from 'definitely wrong' toward 'maybe right.' The worst outcome of a bad recovery is a line that was already going to fail matching and will still fail matching.

It also makes the blast radius of every future pass legible in advance. When someone proposes a new heuristic, the first question is not whether it is accurate. It is whether it is gated, because a gated inaccurate pass is a nuisance and an ungated one is an incident.

Why recovery needs a gate
Ungated recovery
  • Pulls a code out of any description
  • Can promote an English word to a part number
  • Produces data that looks completely valid
  • The error surfaces weeks later, on an invoice
Gated recovery
  • Only touches lines whose code is already useless
  • Must confirm the candidate exists in the manufacturer DB
  • Exact matches first; typo variants only for tokens 6+ chars
  • Fails closed — a flagged line, not a wrong one

The gate is the difference between a tool that saves time and one that quietly manufactures wrong orders.

// Two conditions, both required, before any recovered code is adopted.
function adoptRecoveredCode(line: LineItem, candidate: string) {
  // 1. Only touch lines whose existing code is already useless.
  if (!isPureStockNumber(line.code)) return null
  // 2. The candidate must exist in the manufacturer database.
  if (!manufacturerDb.has(candidate)) return null
  return candidate
}
Without the second check, a heuristic clever enough to find a part number is clever enough to invent one.

Rule two: confirm the candidate exists before adopting it

A pass never promotes its candidate to the line's part code on the strength of pattern matching alone. It looks the candidate up in the manufacturer database first. If the thing it extracted is not a part the manufacturer sells, the pass declines and the line stays flagged for human review.

This is the rule that actually kills the invented-data failure, because the space of strings that look like part codes is enormous and the space of strings that are part codes is a finite list we have on hand. Requiring membership in that list converts a generative problem into a lookup, and lookups do not hallucinate.

The practical effect is that heuristics can be much more speculative than they otherwise could be. A pass is allowed to try several interpretations of a description precisely because none of them survive without confirmation. The creativity is free; the adoption is not.

Fuzzy matching on a short leash

Confirmation cannot be strictly exact, because OCR damage is real and a code that arrives with one wrong character would otherwise be lost. So matching tries exact first, always, and only falls back to typo variants when exact fails. The order matters: a fuzzy pass that runs before the exact pass will occasionally prefer a near-match to a real one.

The fallback is also restricted by token length. Typo variants are only generated for tokens of six characters or more. Short tokens carry too little information to distinguish a real code from a coincidence — with three or four characters, a single-character variant lands on some other valid part often enough to matter, and a valid-but-wrong part is exactly the failure this whole structure exists to prevent.

Six is a threshold chosen against this catalog, not a universal constant. If you are building something similar, derive your own from the code space you are matching into. The principle that transfers is that fuzzy matching needs a length floor, because below some width every string is close to every other string.

Why this is an invariant, not a guideline

We treat these two rules as invariants rather than good practice because they are the difference between a pipeline whose errors are visible and one whose errors are silent. Every pass, current and future, must be gated on the existing code being useless and must confirm its candidate against the manufacturer database. A pass that violates either is not a lower-quality pass; it is a different and much worse kind of software.

The cost is real. Lines that a human could resolve at a glance still get flagged, because the pass that would have fixed them could not confirm its guess. Staff correct things the machine nearly had. We accept that trade deliberately: the review queue is where uncertainty is supposed to accumulate, and pushing uncertainty out of the queue and into the outgoing PO is not an improvement.

If you take one thing from this: when a parsing heuristic gets clever, the question to ask is not how often it is right. It is what happens the times it is wrong, and whether anyone downstream would be able to tell.

Questions

Why gate recovery passes on an already-useless part code?
Running speculative heuristics everywhere can overwrite a correct code with a guess. Gating on uselessness means the worst case of a bad recovery is a line that was already going to fail matching — blast radius stays legible.
What stops a description-mining pass from inventing part numbers?
Mandatory catalog confirmation before promotion. The space of code-shaped strings is huge; the space of sellable parts is a finite list you have. Pattern match proposes; membership decides.
Why is a confidently wrong part code worse than an empty field?
Empty fields go to review. Plausible wrong codes can reconcile, land on a manufacturer PO, and get ordered. Treat invented success as a different failure class than missing data.

Sources

  1. PO processing tool case study
  2. NIST — AI RMF (measurement & oversight)
  3. PostgreSQL — unique constraintsAuthoritative part tables are what make confirmation checks cheap and exact.

Have something to build?

Tell us what you're working on and we'll tell you honestly whether we're the right fit.

Work with us