Project notes Completed · Live

Invoice Reconciliation Lab

Upload a statement and your upload batch, map the right columns, and spot missing invoices or wrong amounts instantly — then export the result as CSV. Built because three-way statement reconciliation is the kind of work that should take seconds, not an afternoon in Excel.

FastAPI Vanilla JS Vercel

01 What it does

You give it two files — a statement extract from a supplier or system, and your own uploaded invoice list — and it tells you exactly where they disagree. No spreadsheets, no VLOOKUP gymnastics.

  • Missing from statement — invoices you uploaded that the statement never mentions.
  • Missing from uploaded file — invoices on the statement you haven't accounted for.
  • Amount mismatches — same invoice number, different value, side by side.
  • A one-click CSV export of the full reconciliation, ready to attach to a ticket or working paper.

Privacy: files are parsed in memory and never stored server-side. The serverless function reads the upload, returns the reconciliation, and forgets it. Nothing is written to disk or a database.

02 How it works

The front end is plain HTML/CSS/JS — no framework, no build step. It POSTs both files as multipart/form-data to a FastAPI endpoint at /api/reconcile, running as a Vercel serverless function.

Column detection is dictionary-driven: leave the mapping blank and it looks for familiar headers like Invoice No, Document No, Amount or Value. If your headers are unusual, you can type the exact column names instead. It accepts CSV and Excel (.xlsx/.xlsm).

  • Suffix-tolerant matching — optionally treat 1234A as the same invoice as 1234, for systems that append revision letters.
  • Status filter — ignore rows whose status matches a list you control (defaults: cancelled, void, reversed) so voided duplicates don't pollute the result.
  • Summary metrics — statement vs uploaded row counts, totals, matched count, and how many rows were skipped by the status filter.

Size limit: uploads are capped at roughly 4 MB combined for both files. That's the serverless request-body ceiling on the hosting platform — bigger reconciliations are better run locally against the same API.

03 Design decisions

In-memory, stateless by default. Reconciliation data is often sensitive, so the tool keeps nothing. Each request is self-contained: upload, compute, return, discard. That also keeps the serverless function cheap and cold-start friendly.

Dictionaries first, manual override second. Most files use predictable column names, so auto-detection handles the common case while a manual mapping covers the awkward ones — without forcing anyone to rename headers before uploading.

Tolerant, not magic. Suffix handling and the status filter exist because real ledgers are messy. They're explicit, opt-in toggles rather than hidden heuristics, so the output stays auditable — you can always see why a row was matched or skipped.

Export is a first-class feature. The CSV mirrors what's on screen — match result, invoice, both amounts, both statuses, and previews — so the reconciliation can live in a working paper, not just a browser tab.

04 Status & what's next

This one is done and live — it's the first of my finance tools to ship publicly, and I use it on real statements. It runs on the same Vercel deployment as this site, served from its own subdomain.

  • Wider Excel coverage (multi-sheet workbooks, header rows that aren't on row 1).
  • Saved column-mapping presets per supplier.
  • An optional summary view that groups mismatches by reason.