Catching an invoice that doesn’t add up

Finance apps: worked example

When an app reads an invoice, it may miss lines or misread numbers. This check compares the sum of the lines read with the printed total, flagging differences for human review.

All names and numbers on this page are made up. The screenshot shows the real app; the example below was written for this portfolio.

In the real app

Invoice review: three lines read by the app beside the original four-line PDF, with a warning that the lines are RM890 short of the printed total; invented data (opens larger view)
The app read 3 of 4 invoice lines. Their total is RM890 below the printed total, triggering “Needs Checking” beside the original PDF.

How the check works

1. Add up what was read

Printed on the invoice
Item 1RM60.00
Item 2RM40.00
TotalRM100.00
What the app read
Item 1RM60.00
Item 2RM37.00 (differs from the invoice)
Adds up toRM97.00

RM3.00 short: needs checking

The gap needs human review: the app may have misread a number, or the invoice itself may be wrong.

2. Give every invoice a clear status

Three invoices before anyone has checked them
InvoicePrinted totalLines add up toStatus
ARM100.00RM97.00Needs checking
BRM150.00RM150.00Totals match
CRM50.00Nothing readLines missing

Invoice C has missing lines, not RM0. With nothing read, it must never appear to have passed.

3. A person checks and fixes it

The reviewer checks the invoice and corrects item 2 from RM37.00 to RM40.00.

Printed on the invoice
Item 1RM60.00
Item 2RM40.00
TotalRM100.00
After the fix
Item 1RM60.00
Item 2RM40.00 (corrected)
Adds up toRM100.00

Totals match. The invoice can move on to approval.

Matching totals confirm arithmetic, not approval. Invoice approval remains a separate step.

For technical reviewers

Sample data, SQL and eight runnable tests reproduce this example.

Show the data model, SQL and tests

  • Invoice: one row per invoice ID, with its declared total.
  • Invoice line: one row per invoice ID and line number, linked by a foreign key. The composite key rejects repeated line keys.
  • Amounts: integer cents keep arithmetic exact. One currency, non-negative amounts only; tax, discounts and refunds are intentionally excluded.

Sum lines by invoice before joining declared totals. A left join preserves invoices with missing lines. The duplicate-key check cannot catch a document reimported under a different invoice ID.

WITH line_totals AS (
    SELECT invoice_id,
           SUM(amount_cents) AS parsed_cents
    FROM invoice_line
    GROUP BY invoice_id
)
SELECT i.invoice_id, i.declared_cents,
       t.parsed_cents,
       t.parsed_cents - i.declared_cents
           AS difference_cents
FROM invoice AS i
LEFT JOIN line_totals AS t
    ON t.invoice_id = i.invoice_id;

The full SQL also returns MISSING_LINES, TOTALS_MATCH or REVIEW_DIFFERENCE. Tests cover mismatches, matching totals, missing lines, reviewed corrections, duplicate line keys, orphan lines, fractional cents, and zero amounts versus missing data.

Save both files together and run the command with Python 3; only the standard library is needed.

python check_invoice_example.py

Back to the finance apps · Contact me