Linekeeper

How it works

One supplier document, from the PDF landing in an inbox to the record your system reads. It's the demo built into the app, so you can run it yourself.

01Intake

Two ways in, both ending at the same place.

  • UploadDrag a PDF onto the app, or pick it from disk. Useful when you already have the file, or you're working through a backlog.
  • EmailSuppliers email an intake address as they always have. The PDF comes in on its own, and the sender's address tells Linekeeper which supplier it is. Nothing changes on their end.

Every file is stored under its SHA-256 hash, so a document sent twice is stored once. The original PDF is always kept, not just the reading of it.

02Extraction

The PDF goes to a model that has to return a fixed set of fields, or the read fails. No free text gets parsed after the fact.

Header: document type, PO number, supplier, confirmation number, order date, promised ship date, currency, subtotal, tax, shipping, grand total. Then a row per line: line number, SKU, description, quantity, unit price, line total, unit of measure, notes.

Two rules that matter more than accuracy

  • Empty beats wrongA field it can't read comes back null. A guessed price is worse than a blank one, because a blank one gets looked at.
  • Confidence per fieldEach line, and the document as a whole, gets a score. Under 0.5 on a line, or 0.6 overall, raises a flag even when the numbers match.

It gets better per supplier

Notes you keep on a supplier, like where their PO number sits or whether prices are ex-GST, go into every read of that supplier's documents. So do the last few readings someone corrected and confirmed, as worked examples. A layout you see every week gets steadily more reliable.

03Matching

First it finds the order: an open one whose PO number matches, ignoring punctuation and case, so PO-10432, po 10432 and PO10432 are the same. If two orders share a number, the supplier decides. With no readable PO number, it tries supplier plus grand total within 1%, and only when exactly one order fits.

No order found is not an error: it's the PO_UNMATCHED flag, and every line comes through as unexpected so a person can point it at the right order.

Then line by line

Each document row is paired with an order row by SKU first. Suppliers often use their own codes, so when those differ it compares descriptions and needs 60% of the words to agree. Each order line can be claimed once, so a repeated SKU shows up as an extra row instead of quietly overwriting the first.

Quantity has to match exactly. Price gets a tolerance you set, the larger of a fixed amount and a percentage, so rounding noise doesn't page anyone at 6am.

An invoice covering several POs is matched against each order separately. A short delivery isn't wrong, just incomplete: the order stays open for what's still owed, the next document is checked against that balance, and the order closes once everything has arrived. Counting the delivery when it lands does the same for what was really in the box.

04The flags

Per line, one of five verdicts:

  • OKMatched an order line, quantity exact, price inside tolerance. Never needs a person.
  • QTY_MISMATCHMatched, but the quantity differs. Usually a partial allocation or a pack-size assumption.
  • PRICE_MISMATCHMatched, but the unit price is outside tolerance. The delta and the expected price are both carried through.
  • MISSING_IN_DOCOn your order, absent from their document. They dropped a line, or split the shipment without saying so.
  • UNEXPECTED_IN_DOCOn their document, not on your order. A substitution, a freight line, or the wrong PO.

And at the header, four more that apply to the document as a whole:

PO_UNMATCHED TOTAL_MISMATCH CURRENCY_MISMATCH LOW_CONFIDENCE LOW_CONFIDENCE_LINE

TOTAL_MISMATCH compares the total of your order lines with the document's subtotal, not its grand total, because tax and freight aren't on your order.

What the review screen shows.

Four ordered lines, four verdicts. This is the demo document in the app, matched against order PO-10432 from Kaimai Components Ltd.

Order confirmation KC-88213: PO-10432NZD
VerdictSKUDescription QtyUnitExpected
OKBRK-14ABracket, 14mm, zinc406.50-
PRICE_MISMATCHGSK-22Gasket set, viton1239.0035.00
OKPLT-09Cover plate, powder-coated869.50-
MISSING_IN_DOCSHM-03Shim, 0.5mm stainless--100 @ 0.80

Header flags on this document: TOTAL_MISMATCH LOW_CONFIDENCE_LINE. The subtotal is off by the missing shim line and the gasket overcharge, and the gasket price came back at 0.41 confidence because it was written by hand.

Two of four lines need a person. The other two are already done.

What it catches, and how it looks.

The same view every time: what you ordered on the left, what arrived on the right, and a note against anything that does not agree. Three different weeks, three different problems.

A price that quietly moved

The most common one, and the easiest to pay without noticing.

PO-10432 Kaimai Components Ltd, engineering supplies 2 of 4 lines need a person
What you orderedWhat they sent
BRK-14A
Bracket, 14mm, zinc
40 × 6.50
BRK-14A
Bracket, 14mm, zinc
40 × 6.50
GSK-22
Gasket set, viton
12 × 35.00
GSK-22
Gasket set, viton
12 × 39.00
Price is $4.00 over the agreed rate. $48 on this line. It was handwritten on their PDF, and the reader flagged it at 0.41 confidence.
PLT-09
Cover plate, powder-coated
8 × 69.50
PLT-09
Cover plate, powder-coated
8 × 69.50
SHM-03
Shim, 0.5mm stainless
100 × 0.80
 
 
not on their document
Dropped without mention. Nothing on the confirmation refers to it. Worth asking before the rest ships.
Two lines matched and never reached anyone. The subtotal is short by $128.00.

A short shipment nobody mentioned

Every price correct, three of four quantities cut. Nothing on the document says so.

PO-77219 Southcape Beverage Co, wholesale drinks short shipment
What you orderedWhat they sent
CDR-750
Cider, 750ml, case of 12
60 × 28.40
CDR-750
Cider, 750ml, case of 12
36 × 28.40
24 cases short. Prices are right, quantities are not. Three of four lines were cut, which usually means an allocation rather than an error.
LAG-330
Lager, 330ml, case of 24
40 × 41.90
LAG-330
Lager, 330ml, case of 24
24 × 41.90
TON-200
Tonic, 200ml, case of 24
15 × 19.60
TON-200
Tonic, 200ml, case of 24
15 × 19.60
GIN-700
Dry gin, 700ml
24 × 31.25
GIN-700
Dry gin, 700ml
12 × 31.25
Half the gin. Confirm what is coming and when the balance follows, before the invoice arrives assuming otherwise.
$2,224.60 of the $3,796.30 ordered. Nothing on the document says the balance is on back order.

A substitution and a charge you never agreed

The hardest to spot by eye, because the paperwork looks complete.

PO-4488 Halberd Electrical, components unexpected items
What you orderedWhat they sent
CBL-6MM
Cable, 6mm twin and earth, 100m
12 × 148.00
CBL-6MM
Cable, 6mm twin and earth, 100m
12 × 148.00
RCB-32A
RCBO, 32A, type C
20 × 44.50
 
 
not on their document
Replaced, not supplied. They sent RCB-32B instead. It is in your catalogue, so it is a real part, but it is not the one you ordered.
RCB-32B
RCBO, 32A, type B
not ordered
RCB-32B
RCBO, 32A, type B
20 × 46.80
A substitution at a higher price. $2.30 more per unit, $46.00 in total, on a part nobody agreed to.
FRT-EXP
Expedited freight
not ordered
FRT-EXP
Expedited freight
1 × 185.00
A charge that was never on the order. Not one of your SKUs at all. Flagged as UNKNOWN_SKU because it matches nothing in your catalogue.
Three lines need an answer before this is paid. $231.00 of it was never agreed.

In all three, the lines that matched went through untouched. Only the marked rows needed anyone.

05Confirm, correct, and hand off

Every field on the review screen is editable. If the reader got a handwritten price wrong, fix it in place and confirm. Three things happen:

  • Re-matchYour corrected values go back through matching, so the flags reflect what's actually true, not what the first read thought.
  • RememberThe corrected reading becomes a worked example for that supplier's next document. The five most recent are kept.
  • Hand offA normalised record is written and made available at a stable URL. If you've set a webhook, it's posted there in the same request.

Rejecting works too: the document is marked and nothing is emitted, but the file and the extraction stay for the record.