Building StatementIQ: A Bank Statement Analyzer That Proves Every Row, Not Just Reads It
By Anas Shaikh · August 29, 2026 · 6 min read
Every Chartered Accountant's office has the same quiet time sink: someone opening a PDF bank statement in one window and Excel in another, retyping rows by hand, and hoping nothing gets missed. StatementIQ was built to remove that step entirely — and to remove the doubt that comes with it.
Most extraction tools read a statement: they pull rows out of a PDF or CSV and hand you a spreadsheet. That's not good enough for reconciliation work, because a misread digit or a dropped row is invisible in a plain export. StatementIQ takes a different approach: it proves every row against the statement's own arithmetic before it's allowed to call anything verified.
The core idea: verify, don't just extract
For every transaction row, StatementIQ checks one equation:
previous balance ± this row's amount = this row's balance
A row is marked VERIFIED only if that holds — to the paisa, using decimal arithmetic throughout, never floating point. If it doesn't hold, the row is FLAGGED with the exact reason, never silently dropped. Anything the parser genuinely can't read becomes a visible AUDIT ROW, and the statement is refused a clean verdict until that's resolved.
The result is a statement-level rubber stamp: ✓ RECONCILED, ✕ DISCREPANCY FOUND, or NOT TESTED — never a confident-looking spreadsheet hiding an unverified row.
One pipeline, any input format
Bank statements arrive in every shape imaginable, and treating them differently would mean re-solving the same bugs in six places. So every input — a text-layer PDF, a scanned PDF, a CSV with unknown encoding, an XLSX export, even HDFC's legacy XLS format — is normalised into the same internal structure before reconciliation ever runs. A 40-page scanned statement and a clean CSV go through identical logic.
Scanned pages fall back to OCR automatically. And OCR is honest about its limits: a page that needs OCR but can't get it is reported as OCR_UNAVAILABLE, never silently returned as a blank page. Password-protected PDFs are supported too — the password opens the file and nothing else; it's never stored, logged or written into the audit sheet.
When there's no header to trust
The interesting engineering problem wasn't the common case — banks with clean, labelled columns are easy. It was the statements with no reliable header at all, or a header using a name the parser had never seen.
The fix was to stop treating column identification as a lookup problem and treat it as a maths problem: when no header can be trusted, StatementIQ brute-force searches candidate column layouts and accepts one only if it satisfies the table's own running-balance arithmetic. The debit column isn't guessed — it's the column that makes the balance equation true. That single decision is why bank detection in StatementIQ is a convenience, not a dependency: unrecognised banks and unusual layouts still reconcile correctly.
from decimal import Decimal, ROUND_HALF_UP
def verify_row(prev_balance: Decimal, amount: Decimal, is_credit: bool, stated_balance: Decimal) -> bool:
signed = amount if is_credit else -amount
computed = (prev_balance + signed).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
return computed == stated_balance
A handful of other details turned out to matter just as much in production:
- Never lose a row silently. A line that looks like a transaction but fails to parse is kept as an audit row, with a per-page coverage audit that raises
PAGE_ROW_GAPif the count comes up short. - Carry-forward rows aren't transactions. "B/F" and "Opening Balance" rows are excluded from sums — including them would double-count — and used instead to seed the anchor balance.
- Anchor provenance is recorded, not assumed. The opening balance can come from four places (a reviewer's typed figure, the statement's own summary block, a B/F row, or derived from the data), and the workbook always records which one won, because those sources don't carry equal evidentiary weight.
Categorise a client once. Never again.
Verification solves accuracy. The other half of a CA's time goes into categorisation — deciding whether a payment is rent, a supplier invoice, or a salary run. StatementIQ ships with 31 built-in categories mapped to the Indian Schedule P&L expense heads, ~25 deterministic auto-rule families tuned for Indian vocabulary (MSEDCL, GST/TDS, Zerodha, LIC, and similar), and 22 payment gateways detected by name.
But the detail that changes the economics of the tool is memory: categorise a counterparty once, and every future statement for that client arrives pre-filled. The mapping is stored in a plain, human-readable JSON file — one per client — with the origin of every category always visible: Learned, Auto, or Manual. It's deliberately conservative — anything a rule isn't confident about stays Uncategorized, because a wrong category is worse than none, and categorisation can never touch a figure. It only ever adds two columns.
From reconciled statement to Tally import
The same reconciled records drive three export formats — Tally XML (masters created before vouchers, so Tally doesn't reject the file for a missing ledger), a CSV daybook, and a standalone master-creation sheet. Voucher types are assigned correctly by transaction shape: Payment, Receipt, or Contra for cash and ATM movements, so a withdrawal doesn't get mis-posted as an expense and overstate the books. Anything that can't be posted — an unnamed party, an undated row — is never silently skipped; it lands in a Suspense ledger and is counted and reported.
Why it runs completely offline
There is no LLM call anywhere in this pipeline, by design. StatementIQ makes zero network calls at runtime — no API key, no third-party service, nothing. It runs the same on an internet-connected laptop as it does fully air-gapped. For firms handling confidential client financials, that's not a performance detail, it's the whole point: nothing is ever uploaded to a cloud provider, there's no data-retention policy to trust, and the only file the tool persists between runs is the counterparty-to-category map — which holds names only, never amounts, dates or account numbers. Uploaded files and generated workbooks are deleted after every run.
The deliverable
The output is a five-sheet, colour-coded Excel workbook — Validation Summary, Pivot Summary, Transactions, Counterparty Detail, and an Extraction Audit trail — plus a SHA-256 fingerprint of the source file as evidentiary proof of exactly what was processed. Re-download after categorising and the Validation Summary sheet stays byte-identical, which is itself a small proof that categorisation never changed a figure.
Who it's for
Chartered Accountants and audit teams, forensic accountants, virtual CFO practices, NBFC and lender credit teams, and bookkeeping or compliance outsourcing firms — anyone currently retyping bank statements into Excel with no way to prove nothing was missed.
₹8,000 per year. Unlimited statements, unlimited banks, unlimited counterparties.