Three-way match is a retrieval problem with a paper trail
Purchase order, goods receipt, invoice. In an ERP they are joined on a key. In a folder of PDFs there is no key, so the join has to be made from evidence, and then shown, because nobody approves a payment on the word of a program.
18 June 2026
A three-way match asks a simple question: did we order this, did we receive it, and is the invoice consistent with both. In a well-run procurement system it is a database join, and the interesting part is the tolerance rules. In the situation most small teams are actually in (a shared drive, some email threads, PDFs) there is no join key, and half the work is establishing that these three documents are about the same thing at all.
This is the most requested recipe in our tracker, so this post is what the community recipe does, why it stops where it stops, and where it will be wrong.
Stage one: find the candidates
Start from the invoice, because that is the document with a deadline attached. Retrieve candidate purchase orders and receipts using every signal available, in decreasing order of strength:
- An explicit PO reference on the invoice. Present about 60% of the time in our sample corpora, and nearly always correct when present. Watch for references that are close but not exact: a truncated number, a missing prefix, a transposed pair of digits on a scan.
- Vendor plus date window plus amount proximity. The workhorse when there is no reference.
- Line-item description similarity. Slow, and the only signal that works for split deliveries where amounts do not match anything.
- Delivery note numbers appearing in both a receipt and the invoice text, which is common and almost never in a structured field.
Candidates come back ranked, not resolved. A single-candidate result at high confidence is the common case and it still goes through the same path as a five-candidate result, because the moment a program starts treating high confidence as certainty, the failure mode changes from "a person picked wrong" to "nobody knows why this was approved".
Stage two: compare, with tolerances
Once a triple is proposed, the comparison is arithmetic. Ordered quantity against received against invoiced, unit price against agreed price, totals against each other, dates in a plausible sequence.
| Check | Default tolerance | Most common legitimate cause of a miss |
|---|---|---|
| Invoiced quantity vs received | 0 | Partial delivery, later shipment against the same PO |
| Unit price vs PO price | 2% | Agreed price change not reflected in an old PO |
| Invoice total vs PO total | 2% or 25 units of currency | Shipping or handling not on the order |
| Invoice date vs receipt date | invoice not before receipt, 60 day window | Invoice issued on dispatch, before delivery |
| Currency | exact | Almost never legitimate. Investigate every one |
Those defaults come from three donated corpora and will not be right for your vendors. They are configuration for exactly that reason. The one we would not loosen is currency: in the sample we studied, every currency mismatch was either an extraction error or a real problem, and none were benign.
Stage three: show the work
The output is not "matched" or "not matched". It is a small evidence sheet: the three documents, the fields compared, the values, the differences, and a citation for every value that points back at the region of the page it came from.
Approval is a human act with a signature attached to it. The tool's job is to make that act take ninety seconds instead of twenty minutes, not to perform it.
Groups who have run this report the time per exception dropping from roughly fifteen minutes to two. That is the whole value. The matching itself is not the hard part and never was. The hard part is assembling the evidence from three documents that live in three different places — precisely what a retrieval system with span-level citations is for.
Where this recipe is wrong
Stated plainly, because a recipe that only lists its successes is marketing:
- Split deliveries against one PO. Handled poorly. Partial matching across several invoices summing to one order is a set-cover problem and the recipe currently proposes candidates instead of solving it.
- Blanket orders. A standing order drawn against for a year matches everything, weakly. Needs a running consumed-total the recipe does not maintain.
- Services without receipts. A two-way match with no delivery evidence at all. The recipe says so instead of pretending, but it is a real gap.
- Credit notes. Handled as ordinary negative-amount documents, which is wrong in enough edge cases that we would not rely on it for a period close.
All four are open issues with a named file and a done condition, our standard for anything we would like help with. The blanket-order one is a good first issue for anybody who has actually worked in procurement, because the hard part is knowing the rules; the code is the easy half.
Recipe and configuration live in github.com/getquire/recipes under three-way-match. It runs entirely offline, like everything else here.
Every duplicate invoice is a near-duplicate
Blocking keys, an asymmetric cost function, and why the threshold sits at 0.70.
The Quire Note
One email a month, engineering-first.