← All projects

Rectify: Invoice Reconciliation Against a Standard That Learns

Cover Image for Rectify: Invoice Reconciliation Against a Standard That Learns

The problem

A supplier sends an invoice as a PDF. Somebody opens it next to a price list and checks it line by line — is that the right SKU, is that the contracted price, is that the right unit of measure. It's slow, it's error-prone, and the knowledge gained ("this vendor calls SKU-4471 a Widget Pro 2K") evaporates the moment the check is done. Next month, the same person does the same work on the same vendor's invoice.

What we built

Rectify was my team's project for Cloudflare Singapore Developers Day, built for the hackathon on 27 August 2026, and it's live on Workers.

Upload an invoice. It's parsed to structured JSON, every line is matched against a canonical product standard, and every field difference is flagged. A reviewer accepts, rejects, or edits each flag in a live UI. Accepting a correction writes back into the standard — the corrected price, and the vendor's odd product name recorded as an alias. Then a clean, corrected invoice is published as a PDF.

The payoff: the standard gets smarter with every document. Invoice #1 needs manual decisions. Invoice #2 from the same vendor auto-matches, because the system learned.

The boundary we held throughout: the model reads and compares; it does not decide. Only a human resolution changes the standard.

How it works

PDF upload
   → extraction to structured JSON        (Workers AI + Browser Rendering)
   → line-by-line match vs. the standard  (exact, alias, then vector search)
   → flags reviewed by a human            (accept / reject / edit, live UI)
   → accepted corrections update the standard  (price + vendor alias)
   → corrected invoice published as PDF

Fuzzy product matching uses Vectorize, so a vendor's "Widget Pro 2K" can still find SKU-4471 before any alias exists — the human then confirms it once, and it's an exact alias match forever after.

Built for a demo that cannot fail

Hackathon demos die on stage, so the project has a drop ladder:

  • wrangler.local.jsonc runs the entire session flow — upload, review, publish — in Miniflare with no Cloudflare login
  • ?demo=1 answers every API call from fixtures, so the UI works with no backend at all
  • Two Playwright E2E scripts walk the full flow: one against the fixture demo, one against the real backend
  • The 30-second pitch trailer is rendered with Remotion from the same fixture data, so every figure on screen is real — invoice NW-INV-24817, S$4,325.34 corrected to S$4,255.58, overstated by S$69.76 across four lines

Tech stack

Layer Technology Why
Compute Cloudflare Workers The whole app, one deploy
Extraction Workers AI + Browser Rendering PDF to structured JSON
Fuzzy matching Vectorize Embedding search over the product standard
Contract Shared TypeScript contracts One frozen file every workstream codes against
Local dev Miniflare via Wrangler 4 Full flow with no Cloudflare account
Trailer Remotion The pitch video is code, rendered from fixtures

What building this taught me

1. A frozen contract is how a team parallelizes in a day

We split the work one file per person, all coding against contracts.ts, frozen early. Nobody blocked on anybody: the UI ran on fixtures while extraction was still being built, and everything met in the middle because the seam was decided first.

2. Human-in-the-loop is an architecture, not a checkbox

Deciding that only a human resolution mutates the standard simplified everything downstream: the model's output is always a proposal, the review UI is the only write path, and the audit trail falls out for free.

3. Degradation ladders beat rehearsal

The fixture-backed demo mode wasn't a fallback we hoped to avoid — it made development faster all week, made the E2E tests deterministic, and meant the live pitch had a floor it could not fall through.

Links

Let's build something together.

Got an idea? I'm always up for a new challenge, whether it's a side project, a startup, or something in between.

© 2026 Cyrus David Pastelero. All rights reserved.

Built with Next.js, TypeScript & Tailwind CSS