Every number right, three different files.
Ten runs of the same consolidation, raw and through Structura. Every amount came back exact. The delivered files still differed from each other, and only one of the two workflows could tell you why.
Twelve companies are being acquired. Five keep their books in Microsoft Business Central, four in NetSuite, and three in workbooks their finance directors built by hand. The data room holds 36 files, one per company per year, of monthly trial balances covering 2022 to 2024, and they carry what client exports always carry:
- two companies report in thousands and in millions, declared in prose above the table and nowhere in the figures;
- debits and credits split across pairs of columns that have to be netted;
- one of the workbooks prints twelve months side by side in a single wide sheet;
- the same month written three ways:
Jan-24,2024-01,P1 2024; - year-end closing rows that belong to no month;
- preamble above the header, totals below the last account, blank columns inside the table.
The deal is fictional. We built it, called it Project Meridian, and kept the mess faithful, because we needed the one thing a real data room never has: a complete answer key. The correct consolidation of those 36 files has exactly 86,100 rows, one per entity, period and account code, and we know every one of them.
All three files below are correct exports. No two of them agree on how to write down the same three facts.
The data room, one file per system
Highlighted · the account code column
Twelve companies, three accounting systems, 36 files. These are three of those files, opened as they arrived.
ashford_services_bc_FY2022.xlsx
The table starts under five rows of preamble, debits and credits come in two pairs of which only one is the balance, and a period called C31.12.2022 is not a month.
prescott_engineering_ns_FY2022.xlsx
The account code is the whole hierarchy path, printed inside a single cell.
rosewood_manufacturing_fd_FY2024.xlsx
The thousands scale is declared in prose, above the table and again in a footnote, and the twelve months sit in the column headers.
Then we gave the job to Claude Sonnet 5 ten times, with the same instruction, word for word. The instruction never changed. The toggle did.
Consolidate the 36 trial balance files into one table, one row per entity, period and account code, with description and amount.
The files stay where they are.
Five runs got the files directly, the way most people work with an assistant today: attach the spreadsheets, let the model write its own Python. Five runs went through Structura: the model reads a bounded preview of each file, declares how the file should be read, and the platform’s deterministic engine executes the declaration. Same model, same reasoning effort, same files.
It got every number right, both ways.
Every amount, exactly right
Start with the raw runs, because they earned it. Every one of the 86,100 amounts in the answer key came back exact to the cent, five runs out of five. It caught the thousands and the millions declared in a sentence above the table, in both companies, every time. It unpivoted the wide sheets, kept the closing rows apart from December, and read three date formats as one calendar. Not one of the traps we planted cost it a figure.
If you were promised a story about AI getting numbers wrong, we don’t have one. A frontier model did the consolidation arithmetic of a twelve-company group without a single wrong figure, ten runs out of ten across both setups.
86,100
rows in the answer key
every amount in it matched, in all ten runs
36
files across three accounting systems
twelve entities, three years
5 of 5
raw runs with every amount exact
and 5 of 5 through Structura
What the spreadsheet could not tell us
So the amounts were right. Here is what else came back.
In every raw run, the account codes of the four NetSuite companies arrived rewritten: 30,448
rows of the 86,100, about a third of the deliverable. NetSuite prints an account as its full
path, 1000 Assets : 1100 Bank Accounts : 1110 Main Operating Account. The model turned that
into a scheme of its own, 1000.1100.1110, and it had a defensible reason: leaf codes collide
across branches in these books, so a bare leaf is not a safe key. Every run disclosed the
recoding in its final message, reason included.
That disclosure lives in the chat transcript. The spreadsheet went out without it. And a run can only ever tell you about itself:
- Three runs joined the path with dots. Two joined it with hyphens. No run flagged the difference, because no run knows the other runs exist.
- Four runs wrote out 5,220 rows the answer key does not have, one for every blank cell in the wide monthly sheets, each carrying 0.00. The fifth treated a blank as nothing at all and left them out. Neither choice is mentioned anywhere in the deliverables.
- One run closed with a summary that begins: “I’ve consolidated all 34 trial balance files into a single workbook.” There were 36. The run had in fact processed all 36, its own output proves it, and every number in that output is right. The sentence is just wrong. The report about the work is not the work.
As NetSuite prints it
1000 Assets : 1100 Bank Accounts : 1110 Main Operating Account
As the five raw runs delivered it
Through Structura
The obvious reply is that this is cosmetic, and it almost is: a lookup back against the source files puts the printed codes where they belong, if you know it happened. The only warning was a paragraph in a chat log attached to nothing. We knew because we had built the answer key.
Who holds the pen
What the model rewrote this time was labels. And yes, every number was right, five runs out of five. But look at what else got decided, run by run, with nothing said about it: whether a blank cell in a monthly sheet is a nil balance or is not a row at all. Four runs made 5,220 rows out of those blanks. One did not. Nothing in the setup keeps that kind of judgment to cells that happen to be empty.
In the raw workflow the model computes every cell itself, in a script it wrote, inside an environment into which the entire data room has been uploaded. The only assurance that comes back out is the preparer’s own report that everything ties. No reviewer accepts that alone from a person. The section above is the reason not to start accepting it from a workflow.
We could reject those five deliverables only because we had spent weeks building a key to check them against. Your deal doesn’t have one.
A declaration instead of a script
Through Structura, the same model does the same job with one structural difference: it never touches the files, and it never writes a number. It reads a preview, then declares how each file should be read. The declaration is a document. Here is the platform’s read-back for one of the Business Central files, from a real run, abridged:
A reviewer can object to any line of that before it touches a number. Column F minus G is the amount. The totals row below the table is out. The closing period stays apart from December. Thirty-six of these is an hour of reading, and we are not going to pretend otherwise. It is an hour spent on documents you can argue with, which is not the same hour as a night spent working out what a script did. A wrong declaration is wrong where you can see it. A wrong line of Python is wrong inside an answer that already ties.
And the engine does not improvise. When a declaration is wrong in form, it refuses, and we can show that instead of asserting it, because one of our five runs stumbled. One recorded stumble tells you more about the safety net than five clean runs do.
agent · map_source
{"source": "coventry_logistics_ns_FY2022.xlsx", "columns": {"A": "Account Code", "B": null, "C": null, "D": null, "E": "Period"}, "debit_credit": {"Amount": {"debit": "B", "credit": "C"}}, …}
engine · refused × 4
mapping.column_named_twice: B, C are named in columns and claimed by debit_credit. next: A column is either a plain column or one side of a debit_credit pair, not both. Same refusal for finsbury_maintenance, prescott_engineering, westgate_technical.
agent · amended
{"source": "coventry_logistics_ns_FY2022.xlsx", "columns": {"A": "Account Code", "D": null, "E": "Period"}, "debit_credit": {"Amount": {"debit": "B", "credit": "C"}}, …}
engine · accepted
Mapped coventry_logistics_ns_FY2022.xlsx. Declared B minus C → Amount Entity is 'Coventry Logistics Ltd' for every row Consolidate to apply.
A declaration can still be wrong in meaning. That is the one mistake no engine catches, and it is the sharpest objection to everything above: executed deterministically, a wrong declaration is wrong everywhere. But it does not hide. Every output row traces to the file, the range and the transformations that produced it. The declaration has an author and a timestamp. The fix is a one-line amendment, and the re-run changes exactly what the amendment changes. Raw, the same mistake is a line of Python in a transcript: finding it is forensics, and re-running to fix it re-rolls the dice on everything else. That is what three shapes in five runs means: dot codes with the extra rows in three runs, hyphens with them in one, hyphens without them in the last.
One list, whoever made the change
Every consolidation in these runs landed in the workspace history as a change made by the agent’s own identity, timestamped, in the same list where human changes land:
History
Synced "westgate_technical_ns_FY2024.xlsx"
Sync · Claude · 2 minutes ago
2,700 rows added
Synced "prescott_engineering_ns_FY2024.xlsx"
Sync · Claude · 2 minutes ago
2,412 rows added
Synced "finsbury_maintenance_ns_FY2024.xlsx"
Sync · Claude · 3 minutes ago
2,826 rows added
Synced "ashford_services_bc_FY2023.xlsx"
Sync · Claude · 4 minutes ago
2,508 rows added
Synced "ashford_services_bc_FY2022.xlsx"
Sync · Claude · 4 minutes ago
2,524 rows added
Auto-classified by rule
Source-backed · 1 hour ago
1,204 values assigned
Manual edit
Prior year adjustments · 2 hours ago
3 values updated
Each file the agent brought in is a change with a description, an author and a count, sitting in one list with the rule that ran an hour earlier and the manual edit someone made that afternoon. The pair of faces is the attribution: a member of the team, and Claude acting for them, the way a co-authored commit names both.
The five Structura runs produced five identical files, account codes exactly as printed in the sources, five of five exact against the key. Identical output is not an achievement, it is what a deterministic engine is for. What carries information is that five independent readings of the same 36 files converged on the same declarations, and that the engine turned back the one attempt that was malformed instead of executing it.
Consolidation is one step. Classification, adjustments, every other change on the platform lands in that same history, whoever makes it, human or agent.
Where the data went
There is one more difference, and for some readers it is the first question rather than the last. In the raw runs, the entire data room, all 36 files, was uploaded into the AI provider’s execution environment. That is what attaching files means. It is rarely said out loud.
Through Structura, the files stayed on the platform. The model asked for previews, and across the five runs those previews covered under 2% of the data room’s rows, against 100% in the raw runs. We measured that number; we did not enforce it. It is what the model needed under the platform’s bounded defaults, not a hard cap, and we would rather tell you that plainly than round it down to zero.
Files attached to the chat
100% of the rows
Previews requested through Structura
under 2% of the rows
Measured across five runs · a bounded default, not an enforced cap
It also costs less, which is a smaller point but a real one: the median raw run spent about 1.6 times what the median Structura run did, five runs a side. On the clock the two are the same, which is why there is no speed claim anywhere in this post.
| Arm | Model spend | Elapsed time |
|---|---|---|
| Through Structura | $2.12 | 8.4 minutes |
| Through Structura | $2.15 | 8.3 minutes |
| Through Structura | $1.89 | 7.7 minutes |
| Through Structura | $1.69 | 6.8 minutes |
| Through Structura | $2.18 | 9.2 minutes |
| Raw runs | $3.95 | 9.9 minutes |
| Raw runs | $3.47 | 7.6 minutes |
| Raw runs | $2.64 | 6.6 minutes |
| Raw runs | $3.89 | 9.5 minutes |
| Raw runs | $3.06 | 6.0 minutes |
What every step looks like
You don’t buy Structura for this step. We tested ourselves here because consolidation is the step where a complete key is easiest to build. The rule underneath is the product: right numbers with a name on them, a timestamp, and a way back to the source, in a workflow where being wrong would have shown.
If that is the standard you want on the next deal, ask us for a walkthrough and bring your own files. And if someone else builds your databook, send them this and ask where the decisions behind it are written down. Not whether the totals tie. They will.