Split, extract, merge: the architecture that keeps long statements from breaking AI pipelines A 60-page treasury statement comes through the “LLM extraction” pipeline. The LLM returns a clean-looking JSON with high AI accuracy. At a glance, the values for the account number, opening balance, and closing balance are all correct. Then you run the totals and find some forty transactions missing from the middle of the document. Nothing in the output flags it, no errors or warnings.
That is the core problem of processing bank statements with single-shot LLM calls. The output looks right until you check the math.
Here is why this happens: A bank statement is mostly a long, repeating transaction table with metadata at the edges. When you feed all sixty pages into one LLM call, the model pays full attention to the first and last pages and skims the middle. This is called long context attention problem of the large language models. The fix is not a bigger context window. It is the architecture of your extraction pipeline.
Page-parallel architecture of Docspire treats each page of a bank statement as an independent unit, then merges the results. The pattern is simple, but the merge step is where the details live.
The Reality of Bank Statement Length
Not every statement is two pages. A rough distribution from what we see in production:
-
1 to 3 pages: personal checking, low transaction volume
-
4 to 10 pages: small business operating accounts
-
11 to 25 pages: mid-market accounts, payroll accounts, merchant accounts
-
25 to 80 pages: corporate treasury, escrow, trust accounts
-
80+ pages: consolidated multi-account statements, quarter-end reports
A 60-page statement is not an edge case for mortgage processing or commercial lending.
Mortgage underwriters ask for a minimum of two to three months of statements per borrower. A self-employed applicant with a business account and two personal accounts can easily submit 150+ pages per file. The extraction system has to handle that without choking, which is why specialized mortgage document automation is critical.
Why the Naive Approach Fails
The obvious idea is to send the whole PDF to the LLM in one call and ask for structured financial data. It works for short statements. It breaks for long ones, for four specific reasons.
Speed and Cost
A bank statement page is dense. Transaction tables, running balances, check images, footers, disclosures. Call it 2,500 to 3,500 tokens per page.
A 60-page statement is 150,000 to 210,000 tokens of input. One large call takes around 2 to 4 minutes to return. The same 60 pages processed in parallel, completes the task in 5 to 8 seconds. Parallel costs slightly more per document when using a provider like OpenAI or Claude, because of prompt duplication, which can be mitigated with a locally hosted LLM.
For an AP reconciliation team processing a handful of statements per week, the extra wait is annoying. For a mortgage processor ingesting thousands of statements per day, it is a bottleneck that kills the pipeline.
Attention Dilution on the Middle Pages
LLMs pay more attention to the beginning and end of a long context. On a 60-page statement, the first page (header, account number, opening balance) and the last page (closing balance, summary) comes out correctly. The transactions on pages 25 to 40 are where OCR bank statement accuracy drops.
We have seen this directly. Running the same statement through a single-shot prompt versus page-parallel extraction, the single-shot output is missing 30-45% percent of transactions from the middle of the document.
For a bank statement, a missing transaction is not a small error. It changes the running balance, breaks reconciliation, and invalidates any downstream fraud or cash flow analysis.
Hallucination Risk
Bank statements have strong visual patterns. Date, description, debit, credit, balance. When the LLM is stretched thin across 60 pages, it starts pattern-matching instead of reading.
The failure modes we see most often:
-
Inventing a balance when the OCR missed a digit
-
Smoothing a description from “POS DEBIT 4829” into a plausible merchant name
-
Filling in a missing transaction date by interpolating between neighbors
-
Producing a closing balance that looks right but does not match the last transaction plus the running total
Every one of these has a greater consequence than a blank field. A blank field gets flagged for review. A hallucinated balance gets booked.
No Per-Page Optimization
A bank statement is not uniform. The first page has the account header, statement period, and opening balance. The middle pages are pure transaction tables. The last page has the closing balance, fees summary, and sometimes a year-to-date section. A single prompt is not optimized for any of these. You end up re-extracting the header on every page and wasting tokens on repeated footer disclosures.
Process Long Bank Statements with Docspire
Start a Free TrialThe Page-Parallel Approach
The fix is the same pattern we use for long invoices. Split the document by page. Extract each page independently against a shared schema. Merge the results with a union step that knows the difference between a field that should be taken once and a field that should be concatenated.
Three steps: schema, parallel extraction, intelligent union.
Step 1: Schema First
The whole pattern depends on one rule. Every page extraction must return output in the same schema, whether or not that page has the fields.
For bank statements, the schema looks roughly like this:
Four properties matter here:
-
It is comprehensive. Every field that could appear anywhere in the statement is declared.
-
Everything is nullable. A page that does not have the field returns null, not a guess.
-
Transactions is an array. Every page can contribute zero or more.
-
The structure is identical across pages. That is what makes the merge step possible.
Schema Generation for New Banks
Banks format statements differently. Chase does not look like HSBC. A UK building society statement does not look like a US credit union statement. You cannot hard-code one schema and hope it covers every issuer.
Our approach is to generate the schema on first contact. We send the first page, one middle page, and the last page of a sample statement to the LLM and ask it to propose a schema based on what it sees. The LLM identifies the transaction column structure, the balance fields, and the summary section. We cache that schema keyed by bank plus account type.
Every future statement from that bank uses the cached schema. If the bank changes format, feedback-based document reprocessing triggers regeneration.
Step 2: Parallel Page Extraction Once the schema is set, every page is extracted independently. Each page call gets:
-
The schema
-
A short context line saying “this is page X of Y”
-
Instructions to return null for any field not present on this page
-
The page image, or the OCR text for that page, or both
All pages are submitted concurrently. A 60-page statement finishes in roughly the time a single page takes, plus a small overhead for orchestration. Pages that fail retry independently without blocking the others. A timeout on page 27 does not mean the other 59 pages have to wait.
This is also where per-page confidence scoring becomes natural. Each page returns its own result, so you can score each page on its own. If page 14 comes back with three transactions where the running balance does not match the debit and credit columns, that specific page gets flagged for reprocessing. The other 59 pages are fine.
Step 3: Intelligent Union
This is the step that people get wrong.
You now have 60 JSON objects with the same structure. You cannot just spread them into each other. You cannot take the last page and call it the answer. You need different merge rules for different field types.
Rule 1: Singular fields use first non-null
Account number, account holder name, opening balance, statement period start date. These appear once in the statement, usually on page one, sometimes repeated as a header on every page. The merge rule is simple. Walk the pages in order. Take the first non-null value. Ignore every subsequent value, even if it shows up again.
This automatically handles banks that repeat the account number and holder name on every page. Page one wins. Pages two through sixty are ignored for those fields.
Rule 2: Closing fields use last non-null
Closing balance, total deposits, total withdrawals, ending period date. These live on the last page. A first-non-null rule would miss them if the header of page one happens to contain a placeholder. So, for a small set of fields flagged as “closing,” we reverse the rule and take the last non-null value walking the pages in order.
This is the place where the merge rule has to be explicit. A bank statement can have balance-shaped numbers in the header, in running totals, and in the closing summary, and only the last one is the real closing balance. First-non-null would take the wrong one.
Rule 3: Nested objects merge field by field
The account_holder and bank objects are nested. The rule is to apply first-non-null at the field level, not the object level. Page one might have the bank name but not the routing number. Page two might have the routing number in a footer. The merged object should have both.
Rule 4: Transactions concatenate in page order
The transactions array is the whole point of the statement. Every page contributes its slice. The merge is a simple concatenation in page order.
The result is a single array of 1,112 transactions in chronological order.
Bank Statement Edge Cases
Bank statements come with a few edge case scenarios. These are not uncommon but deviate slightly from the happy flow.
Running Balance Continuity
Every transaction on a bank statement has a running balance. The running balance on the last transaction of page 5 should equal the opening running balance context on page 6. If it does not, something went wrong. Either OCR dropped a transaction, or the page split cut through a row, or a transaction was hallucinated.
After the union step, we walk the concatenated transaction array and verify that for every transaction. Any mismatch is flagged, and the two pages around the break are reprocessed with extra context.
The running balance is a built-in consistency test, and we use it as a hard validation gate. Most document types do not give you continuous arithmetic across pages. Bank statements do, and it would be wasteful not to use it.
Process Long Bank Statements with Docspire
Start a Free TrialTransactions Split Across Pages
A transaction description can wrap onto the next line, and the line break can land on a page boundary. Page 12 ends with “ACH DEBIT PAYROLL SVCS” and page 13 starts with “REF 8847291 PMT 11/14” with no date or amount because the amount was on the previous row.
Detection follows the same pattern used for any split record across page boundaries. A transaction record missing required fields (date or amount) at the top of a page, following a record with a wrapped-looking description at the bottom of the previous page, is a continuation. We merge the description, carry the amount forward, and emit one clean transaction.
Check Image Pages
Some banks insert full-page images of scanned checks between transaction pages. These pages have no rows, just images. The extraction for those pages returns an empty transactions array and null for everything else, which is exactly what you want. The union step naturally ignores them. No special handling needed, as long as the schema allows empty pages gracefully.
Multi-Account Statements
Corporate and trust statements often bundle several accounts into one PDF. Account A runs for pages 1 to 20, Account B for pages 21 to 45, and Account C for pages 46 to 60. A single merged output would corrupt the data across accounts.
We detect account boundaries during extraction. Each page includes the account number it belongs to, and the union step groups pages by account number before merging. The final output is an array of accounts, each with its own merged result. One PDF in, three structured statements out.
Opening balance versus previous closing balance
Some banks label the starting number “opening balance,” some label it “previous closing balance,” and some label it “balance forward.” These are the same field with different names. The schema uses one canonical field, and the extraction prompt lists the aliases the LLM should normalize into it. This is also where the cached per-bank schema earns its keep, because the alias list is bank-specific.
The Full Pipeline
Putting it together, here is what happens when a long bank statement enters the system:
-
PDF is split into individual pages.
-
The bank is identified from page one. Schema is loaded from cache or generated if this is a new bank.
-
All pages are submitted to the LLM in parallel, each with the shared schema.
-
Pre-processing pass detects incomplete transaction rows and stitches continuations.
-
Multi-account detection groups pages by account number.
-
Union runs per account: first non-null for singular fields, last non-null for closing fields, concatenation for transactions, field-level merge for nested objects.
-
Running balance validation sweeps the concatenated transactions and flags any arithmetic gaps.
-
Summary fields are cross-checked. Total deposits should equal the sum of credits. Total withdrawals should equal the sum of debits. Closing balance should equal opening plus credits minus debits. Any mismatch is flagged with page-level confidence.
-
Structured JSON is returned, with per-page confidence and a validation report.
A 60-page statement finishes in under 10 seconds on this pipeline. A 150-page multi-account statement finishes in about the same time, because the bottleneck is the slowest page, not the total page count.
Why Page-Parallel Pattern Generalizes
The page-parallel pattern is not specific to bank statements. It works for any document where most of the content is a repeating structure and the metadata lives at the edges. Credit memo and debit note automation, brokerage statements, loan amortization schedules, utility bills with detailed usage tables, medical billing statements, freight manifests. All of them benefit from the same split, extract, merge architecture.
The parts that change between document types are the schema, the per-field merge rules, and the validation checks. The core pattern is the same. Modern AI-driven finance automation relies on this scalability.
What does not work is trying to stretch a single LLM call across the whole document and hoping attention holds up. For anything past 10 or 15 pages, it does not.
How Docspire Handles Long Bank Statements
Docspire is a bank statement verification software that runs on a page-parallel architecture in production across mortgage processing, commercial lending, and AP reconciliation workflows. Customers upload statements of any length, and the platform handles splitting, parallel extraction, per-bank schema caching, union, running balance validation, and multi-account separation without configuration.
What the user gets back is one bank statement JSON conversion per account, with every transaction, every balance, and a confidence score for every page. If something looks wrong, the flagged pages surface in the review queue with the exact reason.
Process Long Bank Statements with Docspire
Start a Free TrialIf you are processing bank statements at any real volume, whether for mortgage underwriting, commercial lending, financial analysis, or AP reconciliation, the page-parallel pattern is the difference between a pipeline that scales and one that quietly drops transactions. Try Docspire on your own statements with a 14-day free trial, or talk to our team about your specific workflow.
P.S. The running balance check is the single most useful validation we added to the bank statement pipeline. If you are building this yourself, do not skip it.