Case Study · Spotlight 001

A bank-statement parser
that trusts math over AI.

NovaConvert is a production SaaS I designed, architected, and shipped end-to-end. An accounting invariant — not the language model — has the final vote over every extracted transaction. Patent pending (Canada).

98%+
Extraction Accuracy
1,100+
Statements Processed
PAT.
Pending — Canada
SaaS
Multi-tenant · Production
NovaConvert reconciliation page in production — balance checkpoint mismatches warning visible above the transaction table; each row shows a computed running balance alongside the bank's stated balance.
publicThe reconciliation page, shipped — novaconvert.ca
The Core Insight

Make the invariant the authority.

A closed-form check that every bank statement already satisfies — used as the oracle, not the LLM.

The accounting identity is a free, deterministic check:

opening + credits − debits = closing

Every bank statement in existence satisfies this equation. If the extracted transactions don't, the extraction is wrong — full stop.

Making it the final authority in the pipeline — rather than the LLM — inverts the usual problem. Instead of trying to make the model accurate, you make the architecture tolerant of the model being wrong.

The deterministic check can always veto. That single inversion is what the rest of the system is built around.

The Spreadsheet

A reconciliation page, built as a spreadsheet.

FIRE flags the mismatch; the UI is where it gets resolved. A cell-addressable grid, live balance recomputation on every edit, and the bank's stated closing shown beside the computed one. When they match, the invariant turns green.

NovaConvert reconciliation page with FIRE flagging 70 balance checkpoint mismatches; amber warning card lists specific merchants where the bank's running balance diverges from the computed one.
FIRE doing its job on a real statement. The amber card lists specific merchants where the bank's running balance diverges from the extracted one — the rows a bookkeeper needs to look at, ranked and named.
reconcile.tsx
R3:D= 150.00 (debit) ⚠ diverges from bank by −$300.00
DateDescriptionDebitCreditBalance
R1Feb 18
Deposit · payroll
2,450.00
8,450.00
Bank: 8,450.00
R2Feb 19
POS · CO-OP GAS
87.42
8,362.58
Bank: 8,362.58
R3Feb 20
priority_highINTERAC e-Transfer · inbound
150.00
8,212.58
Bank: 8,512.58
R4Feb 21
POS · TIM HORTONS
15.26
8,197.32
Bank: 8,497.32
R5Feb 22
POS · MCDONALD'S
18.00
8,179.32
Bank: 8,479.32
Accounting Invariant
computed closing vs stated closing
Mismatch
Δ −$300.00
The real thing, in motion
A short silent loop from the actual reconciliation page. The synthetic demo above shows the mechanic; this shows the density of real use.
Shoebox Ready

Even shoeboxes get sorted.

A scanned PDF. Multiple monthly statements, out of order, some spanning page breaks. Most tools can't handle it. NovaConvert sorts it automatically — before extraction even starts.

boundary_detect.py
Input PDF · 7 pages · scrambled
OCR · headers only
p1
FEB 2024 · p.2 of 3 · continued
p2
SEPTEMBER 2023 STATEMENT · p.1 of 2
p3
FEB 2024 · p.3 of 3 · closing
p4
NOVEMBER 2023 STATEMENT · p.1 of 2
p5
FEBRUARY 2024 STATEMENT · p.1 of 3
p6
SEPT 2023 · p.2 of 2 · closing
p7
NOV 2023 · p.2 of 2 · closing
Awaiting classification
Claude Sonnet
category
Claude classifies each OCR'd header
and groups pages by statement boundary.
The engineering

Two passes. The cheap one runs on everything.

Lightweight OCR reads only the header region of each page — bank name, account number, date range. That thin text stream is sent to Claude Sonnet, which classifies every page and groups them by the statement they belong to. The expensive full-extraction model never sees pages from the wrong statement, and never wastes cycles deciding where the boundaries are.

PatternCheap common path, expensive rare path — the same shape as FIRE's pre-check.
Why it matters

The longest step in the job, automated.

Most extraction tools assume one statement per file. For accounting firms handling shoebox clients, scanned archives, or bulk batch uploads, manually splitting PDFs is often the longest step of the job — and the most thankless.

ImpactA 47-page scan with six statements in random order goes from thirty minutes of manual sorting to a single upload.
How It Fits Together

Three stages. One authority.

With statement boundaries already resolved, each statement flows through three stages. Narrow contracts, testable in isolation. Swappable AI, deterministic core, human finish.

description
01
AI

Extract

For each statement identified by boundary detection, a language model produces a candidate set of transactions — dates, amounts, descriptions, running balances. Gemini, Claude, or Grok behind a factory.

balance
02
Deterministic

Reconcile

FIRE enforces the accounting identity. When values fail the check, it tests alternate interpretations and commits only the one that balances. Every correction is audit-logged.

person_edit
03
Human

Review

Anything the pipeline can't resolve surfaces in a reconciliation UI — the PDF alongside the transactions, live balance recomputation, one-click sign flips.

Deployment

Your data, your perimeter.

Default is shared SaaS behind Cloudflare Access. For firms with data-residency requirements, the same code ships to a private VPS you own — your tenant, your database, zero shared surface.

Default

Shared SaaS

novaconvert.ca
language
novaconvert.ca
public domain
verified_user
Cloudflare Access
shared tenant · SSO + MFA
lan
Cloudflare Tunnel
zero public ports
dns
My VPS
multi-tenant host
database
SQLite
tenant-filtered repository
For solo bookkeepers and small firms. Full tenant isolation enforced in code at the repository layer.
On Request

Private VPS

your-firm.co
language
your-firm.co
your domain
verified_user
Your Cloudflare Access
your identity provider
lan
Cloudflare Tunnel
zero public ports
dns
Your VPS
single-tenant host
database
SQLite
your database · your backups
For firms with data-residency requirements, regulated industries, or enterprise compliance. Shipped, installed, and handed off.
Same code either way

The security model doesn't change. Only the blast radius.

Whether the app runs on my infrastructure or yours, the same four properties ship on day one.

  • verified_user
    Cloudflare Access in front
    SSO, MFA, session management, revocation, device posture — all handled upstream. The application never sees a password.
  • shield
    Multi-tenant isolation at the repository layer
    Every database method takes a tenant key and applies it as a filter. There is no query path that can return another tenant's data.
  • receipt_long
    Tamper-evident audit trail
    Append-only JSONL log with a monotonic sequence counter. Gaps are evidence of tampering. Stored separately from the application database.
  • lock
    Zero public ports
    Cloudflare Tunnel handles ingress. Nothing listens on the open internet — not in shared mode, not in private mode.
Design Decisions

Judgment, not just output.

The interesting thing about a system isn't what's in it. It's what was considered and left out.

D01

FIRE owns the final decision — not the LLM.

This is financial data. 'Mostly right' isn't acceptable — a wrong sign produces a wrong ledger. But blocking on human review for every disagreement would make the product unusable. The LLM gets no vote on the final answer: a deterministic validator arbitrates.

WhyNon-determinism, bounded by a financial invariant.
D02

Corrections are proposed, not applied.

Every auto-correction is staged — never written to the ledger until the invariant re-validates. If a fix makes the balance worse, the whole correction set is rolled back to the primary extraction. No change lands that the check didn't confirm improved the state.

WhyTwo independent unreliable systems can't vote each other into a bad state.
D03

The human is a peer — not a fallback.

No extraction pipeline is 100%. Unresolved cases shouldn't dead-end into an error state. The reconciliation page is built as a live spreadsheet — PDF on one side, transaction grid on the other. Cell-addressable edits, sign flips, and missing-row inserts all recompute the running balance immediately, and the bank's stated closing is rendered alongside the computed one at every row.

WhyMachines do volume. Humans do ambiguity. The UI makes the handoff fast.
The Principle
If your output has a deterministic invariant, make the invariant the authority and let the models propose. Don't try to make models more accurate — make your architecture tolerant of them being wrong, using math they can't argue with.
Hire

Let's build something useful.

I'm taking on new projects. Full-stack SaaS, document-processing pipelines, AI-augmented tools, internal platforms. Free 20-minute discovery call, projects from $2,500 CAD.

steven.sutankayo@novaconvert.ca