Financial Statement Normalization AI: Why One Prompt Fails
One prompt cannot normalize financial statements safely: errors pass silently. See the design where models pick labels, code moves numbers and arithmetic decides, with measured results.
Financial statement normalization AI looks easy in a demo and fails quietly in production. You paste a messy P&L into a model, ask for a clean version, and get something that looks right. Then an analyst finds a subtotal counted twice, a thousands unit read as units, or a revenue line that moved into the wrong bucket.
For a lender, that is the expensive failure. Spreading each statement by hand costs analyst time. A wrong figure nobody notices costs more, because it feeds a credit decision. The question is not whether a model can read a statement. It is how you know the result is right, which is the test for any financial statement normalization AI.
This post explains why a single prompt cannot answer that, and the design we use in UW Assistant, our own multi-agent underwriting engine, with a live demo on fictional companies. It includes measured results, a failure-mode table and a reusable checklist.
Why statements arrive in every shape
Normalization means mapping each borrower's statement to one standard taxonomy, so that revenue, cost of sales and equity mean the same thing across every file. The input fights you.
- Layouts differ. Excel exports, CSVs, digital PDFs and scanned pages, often in one application.
- Languages differ. English, Swedish and Spanish labels for the same line.
- Units differ. Amounts in units, thousands or millions, sometimes stated once in a header.
- Number formats differ. Decimal commas, parentheses or minus signs for negatives, expenses shown as negative or positive.
- Structure differs. Subtotal and total rows mixed with detail rows, quarterly columns beside annual ones, balance sheets shown as deltas.
- Files repeat. The same upload arrives twice.
- Labels are unclear. Typos, abbreviations and unseen company-specific names.
Expect most of these in one batch of files.
Why one prompt fails at financial statement normalization AI
The obvious design is one call: "Here is the statement. Return it in our taxonomy." It fails for a structural reason. The model reads the numbers and writes them back out, so every figure passes through generation. A generated number can be wrong without any signal that it is wrong.
We learned this in an earlier version of UW Assistant: asking one model to rewrite a whole statement fails silently. The output looks like a statement and the totals look plausible.
| Failure mode | What happens | Why a prompt does not catch it |
|---|---|---|
| Digit drift | A figure is copied with a changed digit | The output is still a valid number |
| Unit slip | Thousands read as units | Every line shifts consistently, so it looks coherent |
| Double counting | A subtotal row is mapped as a line item | Both rows look like real accounts |
| Sign flip | Negative expenses are read as income | The label is right, the sign is not |
| Wrong bucket | A label is mapped to a near neighbor | The model is confident either way |
Asking the model to double-check its own work shares the original blind spots. A check is independent only if it uses a different mechanism, and a model's stated confidence is not one.
Financial statement normalization AI that code can verify
In UW Assistant, financial statement normalization AI rests on one rule: models choose labels, code moves numbers, and arithmetic decides whether the result is right.
That splits the work in three:
- Models choose labels. The mapping step uses Claude Sonnet to decide which taxonomy line a source row belongs to. It sees labels, never amounts.
- Code moves numbers. Values are copied from the source cell to the target line by deterministic code. A digit cannot drift, because no model writes it.
- Arithmetic decides. Accounting identities test the result: components add up to subtotals, assets equal liabilities plus equity, and so on. These checks run in every period.
Sending only labels also keeps a borrower's amounts away from the model.
How a failed identity gets repaired
When an identity fails, a repair step proposes a change, for example a different mapping or a corrected sign. The rule for accepting it is strict: a fix that makes an identity hold in every period at once is accepted. A fix that helps one period and breaks another is not.
Anything still failing becomes one short question to a person. We do not ask someone to re-spread the statement, only which of two lines a row belongs to.
Deterministic first, a model only when unsure
The engine has 12 agents, each with one job: intake, structure, triage, subscriptions, bank, map, understand, check, repair, review, key figures and report. The deterministic path runs first, using local multilingual ONNX embeddings among other things. A model is called only when that is not enough, which also keeps cost down.
Other parts of the stack, for the technical reader:
- LangGraph orchestrator with Postgres checkpoints and a human-review interrupt.
- Postgres with row-level security per tenant, so one lender's data and remembered answers never mix with another's.
- pdfplumber and local Tesseract OCR for PDFs and scanned pages.
- FastAPI with a Postgres queue worker, in one Docker image.
For the credit report, Claude Opus writes the narrative, but numbers enter only through placeholders that code fills in. A gate removes any sentence in which the model tried to write a number itself. Every number in the report cites its source cell. A person makes the credit decision, and the score is advisory, computed by code from the lender's policy.
Measured results: financial statement normalization AI in an evaluation gate
An evaluation gate runs on every change to UW Assistant. It is the check that tells us whether a change made things worse.
| Metric | Result |
|---|---|
| Scenario runs | 77 |
| Rows checked | 2,106 |
| Silent errors (a wrong number that passes unnoticed) | 0 |
| Rows mapped by free layers alone | 82% |
| Rows mapped with the model | 98% |
| Model cost for all 77 runs | about $1.10 |
The free layers, meaning deterministic rules and local embeddings, map 82% of rows without a model call. The model closes most of the rest, and anything still failing becomes a question to a person.
Live demo. The public read-only demo at uw.byteandrise.com runs a fictional Swedish SaaS company. All 108 of 108 checks reconcile, 30 rows are mapped, no questions are raised and the advisory score is 95.
What these numbers do not show about financial statement normalization AI:
- They come from our evaluation scenarios and a fictional demo company, not from a lender's borrower files.
- 98% of rows mapped is not 100%. The rest go to a person instead of passing silently.
- UW Assistant is our own product. The demo runs on AWS (Stockholm) today, and Azure AI Foundry, Google Vertex AI and AWS Bedrock versions are planned.
The result that matters is the zero. Accuracy will vary with your files, and a system that surfaces its misses lets you work with that.
Book an AI scoping call
Bring one workflow. We'll tell you if an agent should run it.
Book an AI scoping call →What still needs a person, and how answers are remembered
Some questions have no arithmetic answer. A label such as "other operating items" may fit two lines, and both can satisfy every identity. Someone with credit judgment has to choose.
The design treats these as a cost to drive down:
- Each unresolved item becomes one short question, not a re-spread.
- The answer is remembered per tenant, so the same label does not come back as a question.
This memory is how questions fall over time. How fast questions fall on your files will depend on how repetitive they are.
How ByteandRise would approach this for your company
If you spread statements by hand or have a prompt that works until it doesn't, we would scope it in three stages.
1. Scoping sprint. We review a sample of your statements, covering formats, languages and units, and agree your target taxonomy. We write down your identities and what counts as a blocking failure. You leave with a plan your team can act on, with or without us.
2. Pilot. We build the narrow path on a set of files you can share under NDA: intake, mapping with labels only, number copying by code, identity checks and a review queue. We build an evaluation set from your messy cases and measure auto-accuracy, questions per statement, and silent errors before widening scope.
3. Production. We add tenants, per-tenant memory and a review process for your analysts. Deployment targets the cloud where your data lives: Azure AI Foundry, Google Vertex AI, AWS Bedrock or Claude Platform. We have no shipped production work on those clouds yet, and we say so.
Your results will differ from ours, and we would not quote a number before the pilot measures one.
A checklist for evaluating financial statement normalization AI
Use this on our design, a vendor's or your own:
| Question | Good answer |
|---|---|
| Does a model write any amount? | No: code copies values from source cells |
| What proves a result is right? | Identities that hold in every period |
| What happens when a check fails? | One question to a person, not a silent pass |
| Can you trace a figure to its source? | Yes, down to the cell |
| Are miss rates measured on messy data? | Yes, with a gate that runs on every change |
For the cost side of running an agent system, see our guide to AI agent cost control. For the broader build, see our AI agent services and the UW Assistant project page. Unsure whether a step needs an agent or a fixed rule? Try the free agent or workflow assessment. You can also see how we run ByteandRise on agents.
For an independent answer key, filings.xbrl.org publishes machine-readable iXBRL annual reports you can test against.
Book an AI scoping call
If statements are your bottleneck, book an AI scoping call. Bring 3 to 5 of your messiest statements, with identifying details removed. You get an assessment of where a label-only model with code-checked numbers fits, a draft taxonomy and identity list, and a pilot outline with evaluation metrics.
FAQ
What is financial statement normalization AI?
Financial statement normalization AI maps statements from different companies, formats and languages to one standard taxonomy so they can be compared and analyzed. In our design, models pick the label for each row, code copies the numbers and accounting identities verify the result.
Why can't I just ask an LLM to rewrite the statement?
The model generates every figure, so a wrong one looks the same as a right one. Errors such as a drifted digit, a unit slip or a double-counted subtotal pass unnoticed. We found this in an earlier version of UW Assistant.
How do accounting identities catch errors?
Subtotals must equal their parts, and assets must equal liabilities plus equity, in every period. A fix is accepted only if it makes the identities hold in all periods at once. Anything still failing goes to a person.
Does it send financial amounts to the model?
In UW Assistant, the Claude Sonnet mapping step receives labels only, never amounts. The narrative step receives placeholders that code fills in.
Is UW Assistant in production at a lender?
It is our own product with a public read-only demo on fictional companies at uw.byteandrise.com. The demo runs on AWS (Stockholm) today, and Azure, Google Cloud and AWS Bedrock versions are planned.
