Skills / Document extraction and conversion

Receipts → expense spreadsheet

What it does

Turns scanned or photographed receipts into an expense spreadsheet, one row per receipt, with the source image filename against every row so a reviewer can open the image behind any line.

Receipt extraction is harder than statement extraction, and it is worth being honest about why. A bank statement is machine-generated on a consistent template with a text layer. A receipt is thermal paper that has been folded, sat in a wallet, and been photographed at an angle under bad lighting. Expect a materially higher exception rate than the bank statement skill produces, and plan for the exceptions to be worked rather than assumed away.

What makes the extraction provable anyway is that most receipts carry their own internal proof: subtotal plus tax plus tip equals the printed total. That check costs nothing, is independent of the OCR, and catches the specific failure that matters — a misread digit, or a decimal point lost on thermal paper.

OCR is local only. If ocrmypdf/tesseract is not installed, the script says so and stops rather than falling back to a cloud service.

What it proves

Five tests, and no clean spreadsheet unless:

  • Every receipt image is accounted for — rows plus exceptions must equal the number of images supplied. A receipt that quietly fails to process is the one that goes unclaimed or unreviewed.
  • Each receipt's components sum to its own printed total — where subtotal, tax, and tip are legible, subtotal + tax + tip = total. A break means a digit was misread, and the script names the receipt.
  • The population total ties to a control total — the corporate card statement total, or the reimbursement claim total. Without it, completeness is unproven.
  • Nothing is inferred — an illegible amount, date, or vendor becomes a visible exception with the image filename. Never back-solved from the total, never guessed from context.

What you get

Six tabs:

  1. Summary — the five tests, image accounting, control total agreement, category totals, and the exception count. The signable page.
  2. Expenses — the normalised rows, ready to export.
  3. Receipt Detail — every receipt with subtotal, tax, tip, total, the internal sum check, and the source image filename.
  4. Exceptions — illegible fields, failed sum checks, and unmatched receipts, each with the image name.
  5. Card Matching — where a transaction listing was supplied: matched, receipt with no charge, charge with no receipt.
  6. Category Summary — totals by category for coding.

The Expenses tab uses exactly the schema expense-policy-testing reads, so the two chain with no translation. receipt is populated yes on every extracted row, because a receipt image is what produced it — which means the downstream missing-receipt test is testing the card charges with no receipt, which is the right way round. approver, report_ref, and cost_centre are left blank deliberately: they are not on the receipt, and inventing them would defeat the downstream approval tests.

Where it stops

It never estimates an amount. An illegible amount field becomes an exception — an estimated expense claim is a misstatement, however small.

A charge on the card with no receipt, a receipt with no matching charge, a handwritten tip that pushes the charge past the printed total, the same receipt image submitted twice, a receipt dated outside the claim period, prohibited categories: each is flagged with the image reference. Policy conclusions are left to expense-policy-testing rather than reached here.

It is candid about the exception rate. On real receipts it will not be zero, and pretending otherwise wastes the reviewer's time.