Extracting Fields From a Debit Note
9 min read · updated August 11, 2026
A debit note and a credit memo have the same fields, the same layout and usually the same template. They differ in who owes whom afterwards, and that difference is not reliably printed anywhere on the page.
The direction is the whole problem
Both documents adjust an invoice that has already been issued. A credit memo reduces the amount the buyer owes; a debit note increases it. If you post the wrong one, the arithmetic is still correct and the ledger is wrong by twice the amount, which is the most expensive kind of clean data.
The reason you cannot simply read the title is that either party can issue either document, and the vocabulary flips with the issuer. A supplier issues a debit note to charge more — an undercharge on the original invoice, a price escalation, a freight surcharge. A buyer issues a debit note to say the opposite: goods were returned or short-shipped, and the buyer is debiting the supplier’s account, which from the supplier’s ledger reads exactly like a credit memo it did not write. Same title, same fields, opposite sign, and both are in circulation.
Purchase-to-pay systems make it worse by naming the document after the workflow rather than the accounting: “debit memo”, “adjustment note”, “supplementary invoice” and “chargeback” all appear as headers on documents that do the same two things. So the extraction cannot output a document type and hope; it has to output a direction.
Deriving a signed amount
The amount on the page is almost always printed unsigned and positive. The sign is a derived field, and the inputs to that derivation are all extractable:
- Issuer and recipient. Which party’s letterhead and tax identifier is at the top, and which is in the “bill to” block. This is the single most decisive field and it is often the one an extraction schema forgets, because on an invoice it is obvious.
- The stated document type, verbatim, exactly as printed — not normalised to an enum. Normalising “chargeback” to “credit_note” at extraction time hides the evidence you will need when the direction is disputed.
- The reason. Return, short shipment, price correction, quality rejection, retrospective rebate. A returns reason on a supplier-issued document is a strong signal the header is wrong.
- Which account the document says it affects, when it says so at all: “your account has been debited” is the direction stated in prose.
Compute the ledger delta from those and store both. Represent the extracted amount as it appears, plus a separate signed value relative to a named party:
{
"document_type_as_printed": "DEBIT NOTE",
"issuer": "supplier",
"amount_as_printed": 1840.00,
"currency": "EUR",
"delta_to_buyer_payable": 1840.00,
"direction_basis": "issuer=supplier + reason=price_correction",
"reason_code": "price_correction"
}The direction_basis field is the point. It costs nothing to emit, and it is the difference between a reviewer spending ten seconds on a flagged document and spending ten minutes reconstructing why the pipeline decided what it decided. This is the same argument the published page on per-field extraction confidence makes about scores, applied to a field that has no score at all.
The reference to the original invoice
An adjustment that does not resolve to an original document is not usable. The reference field is therefore closer to a foreign key than to a string, and it has three shapes that need different handling.
The first is a single explicit reference, sometimes labelled “against invoice” and sometimes buried in a narrative line on the item table. The second is several references on one note, because a quarter’s worth of short shipments was aggregated into one adjustment — a schema with a scalar original_invoice_number silently keeps the first and drops the rest. The third, and the one worth designing for, is no reference at all: an adjustment issued against a purchase order, a delivery note or an account balance rather than an invoice. That is a legitimate document, so the correct extraction is a typed reference — { "ref_type": "purchase_order", "ref_value": "PO-4471" } — and not a null in a field named for invoices.
Model the reference as an array of typed references from the start. Widening a scalar to an array afterwards means reprocessing everything you already ingested, and the multi-reference case is common enough that you will hit it in the first few thousand documents.
The tax line moves too
Under the EU VAT system, a document that amends and refers specifically to an initial invoice is treated as an invoice in its own right — the rule sits in Article 219 of Council Directive 2006/112/EC, published by the EU in EUR-Lex. That has a practical consequence for extraction: the adjustment carries its own tax treatment, and the tax must be extracted as its own field rather than inferred by applying a rate to the net.
Inferring it fails in ordinary cases. The original invoice may have been at a rate that has since changed, and the adjustment follows the original rate. Part of the adjustment may be a non-taxable element — a freight charge or a penalty — sitting in the same total as a taxable one. A cross-border adjustment may be zero-rated with a reverse-charge annotation while the original was not. Extract net, tax_amount, tax_rate and tax_treatment_note as printed, then check that net plus tax equals the gross rather than deriving one from the others.
Where it goes wrong
- The total is the wrong total. Some templates print the adjustment amount; others print the revised invoice total after adjustment; a few print both, stacked, with labels that differ by one word. Anchor on the label text and keep it in the output.
- Parenthesised negatives.
(1,840.00)is negative in accounting convention and parses as a positive 1840 in every naive numeric cast. Combined with a direction flag that is also negative, you get a double negation and a perfectly plausible wrong number. - Decimal commas.
1.840,00and1,840.00are the same amount under different conventions, and1.840alone is genuinely ambiguous between 1.84 and 1840. Resolve it from the document’s locale evidence — date format, currency symbol placement, issuer country — not from the number in isolation; the general treatment is in the currency amount validation rule. - The same note arriving twice. Adjustments get resent, and a resent note with a new document number against the same original invoice and the same amount is either a duplicate or a second, genuinely identical adjustment. Neither the model nor the arithmetic can tell you which; a rule on (issuer, original reference, amount, date) can flag it for a human, which is a review-routing decision rather than an extraction one.
- Currency mismatch with the original. An adjustment in a different currency from the invoice it references needs a rate and a rate date, and if the document does not state them the correct output is a flag rather than a converted figure.
Everything above is a reconciliation problem more than a reading problem, which is the general shape of adjustment documents. The related check on a declarations page, where line items must reconcile to two different totals, has the same character: the value of the extraction is that it can be tested against arithmetic somebody else already did.