Group intercompany balances routinely fail to eliminate at close, and the difference gets "plugged" to a suspense account to hit the deadline. The plug then ages invisibly until an auditor or a restatement finds it. This workbench does the reconciliation the way it should have been done all along: deterministic code pairs 400 due-to/due-from documents across a EUR parent and a USD subsidiary, recomputes every retranslation from the real ECB reference rate, and classifies whatever fails as timing, rate, missing counterpart or rounding. The residual can still be plugged. What it cannot be is anonymous: the plug button demands a named approver, and the plug stays on the face of the dashboard as an aged open item.
Figures from a synthetic two-entity close (Meridian, Q4 FY2025) computed against real ECB reference rates. They demonstrate the mechanism, not a client outcome.
The intercompany step of a group close is still run like correspondence. Each entity accountant emails a due-to/due-from balance listing. A group accountant pastes them into a matrix spreadsheet. Differences are chased through email threads with screenshots attached. The FX retranslation is argued about because each side applied a different rate, or the same rate to a different date. And at the deadline, whatever is still open is journaled to an "IC out of balance" suspense account so the consolidation ties.
The survey evidence says this is the norm rather than the exception. Deloitte's intercompany accounting survey of 81 companies found about 30% reporting significant out-of-balance positions that get plugged at close, and 54% running the process manually with no counterparty visibility. The plug is not a neutral shortcut. It builds restatement risk on the balance sheet, it breaks the transfer-pricing documentation trail that tax authorities expect to reconcile to the ledgers, and it is precisely the population a group auditor samples. The same dispute then resurfaces the following month, because nothing recorded who owed the answer.
An intercompany difference is a disagreement between two legal entities about money. Handing that to a language model without hard boundaries fails in specific, predictable ways:
So the design question is a boundary question: which parts are deterministic reconciliation, which small part is language work, and which single action needs a human name attached before it can happen.
Every pairing, every retranslation and every classification is computed deterministically. The engine pairs lines on counterparty + document reference + transaction-currency amount, recomputes each EUR retranslation from the ECB reference rate for the booking date (it checks the stored rate against the published one, and believes the published one), and classifies every failure by rule. The language model sits strictly downstream: it drafts the dispute note to the counterparty controller, citing the specific line pairs, the ECB series and date, and the rule classification. It cannot reclassify an item and it cannot change an amount. The one consequential action, plugging the residual, is gated behind a named human approver and lands on a hash-chained ledger.
The decision function is small enough to read in one screen, and that is deliberate. It handles the absent counterpart with the cutoff rule, recomputes the retranslation from the published rate, and assigns an owner as part of the classification itself:
function classify(p, s) {
if (s === null) { // no counterpart at all
const cls = p.posting_date >= L.cutoff_start ? "TIMING" : "MISSING";
return { cls, owner: L.entities[other(p.company)].controller };
}
const dTxn = Math.abs(p.amount_usd - s.amount_usd); // rounding noise?
if (p.func_ccy !== "USD") { // recompute the retranslation
const ecb = rateOnOrBefore(p.posting_date); // the rate code derives,
const expected = p.amount_usd / ecb.rate; // never the stored one
if (Math.abs(p.amount_func - expected) > L.tolerance.fx_eur) {
return { cls: "RATE", owner: L.entities[p.company].controller, ecb };
}
}
return { cls: "ELIMINATE", rounding: dTxn > 0, dTxn };
}
Take document IC-2025-4051, a trade invoice for USD 666,782.04. Both entities booked it, and the pair matches to the cent in transaction currency. But the parent translated its EUR payable at a stored rate of 1.0801, a stale vintage that survived in a rate table. The ECB reference rate for the booking date, 2025-11-06, is 1.1533. The engine recomputes: USD 666,782.04 at 1.1533 is EUR 578,151.43, against EUR 617,333.62 booked. The parent's ledger overstates the intercompany payable by EUR 39,182.19, an amount that corresponds to no transaction at all.
| Line | Ledger | Txn (USD) | Rate applied | EUR booked | EUR recomputed | Class |
|---|---|---|---|---|---|---|
| P-0051 | 1000 · due-to | 666,782.04 | 1.0801 stored | 617,333.62 | 578,151.43 | Rate |
| S-0051 | 2100 · due-from | 666,782.04 | USD functional | n/a | n/a | Matched in txn ccy |
| P-0399 | 1000 · due-to | 435,972.87 | 1.1553 · agrees | 377,367.67 | 377,367.67 | Missing counterpart · abstained on cause |
The classification lands on L. Verhoeven, the controller of the entity that misbooked, and the model drafts the dispute note: it cites P-0051 and S-0051, names the ECB series and date, states both EUR figures and the difference, and closes with the one sentence that defines its role: this note cannot change either amount. For IC-2025-4399, the 78-day-old item with no counterpart anywhere in the subsidiary's 397 open lines, the note abstains on the cause because neither ledger carries evidence of one.
Across the 400 synthetic documents, the engine auto-eliminated 394 pairs worth €37,613,345.64 of the €40,014,197.50 total intercompany value, which is 94.0%, with every retranslation recomputed from the ECB reference rate rather than taken from either ledger. Nine of those pairs differed by cents and were auto-cleared under the $0.05 rounding tolerance, $0.25 in total. The residual is €2,400,851.86 across six items, aged 1 to 78 days: two timing gaps at cutoff, two wrong-rate bookings, two missing counterparts. Each carries a named owner and a drafted, cited dispute note. A replay re-pairs all 400 documents and reproduces every classification and every cent, with the ledger hash chain intact.
Where the data comes from, honestly. The FX component is real: 64 daily observations of the ECB's US dollar/Euro reference rate for Q4 2025, series EXR.D.USD.EUR.SP00.A, reproduced with source acknowledgement as the ECB requires. The two ledgers are synthetic, generated by a fixed-seed script (gen_data.py) so a third party can re-run the seed and obtain byte-identical data, identical classifications and identical hashes. The 94.0% match rate is therefore a property of the seeded faults, and it is illustrative. The mechanism is what the demo proves.
The honest limits. This is two entities and one currency pair; real groups run dozens of entities, netting hubs and multiple GAAPs. The classification rules are simplified: no partial deliveries, no intercompany profit in inventory, no disputed pricing logic. Transfer-pricing implications are flagged on the open items, and they are analysed no further than that. And the workbench cannot make two controllers agree; it can only make their disagreement precise and impossible to lose. The month-end argument still happens. It just happens once, over cited line pairs, instead of every month over screenshots.
seq : 5 action : classify subject : IC-2025-4051 agent : anti-plug-engine v1 human : none class : RATE eur_at_close : 567,474.08 age : 55 d owner : L. Verhoeven prev_hash : 52fbf2b5 hash : fe901744 ← = fnv1a(prev_hash + canonical(entry))
Every close I have seen has a drawer where the differences go, and the drawer is always anonymous. So when I built the intercompany step, I spent the automation budget where it belongs, on the deterministic 94%: pairing, recomputing the FX from the published ECB rate, classifying by rule, replaying to the cent. That part should never have been manual. But the design decision I care about is the residual 6%. The tempting move is to let software make the leftover disappear, and that is the restatement machine again with better tooling. So the plug is allowed to exist, because deadlines are real, and it is required to carry a human name and an age on the face of the dashboard. The model is confined to drafting the dispute note from cited line pairs, and where the evidence runs out, the note says so instead of guessing. If a tool like this feels uncomfortable, that is the discomfort of a difference you can no longer lose.
The pairing run, the ECB recompute, the residual register with its six owners, the dispute-note drafts, the named-approver plug gate, the suspense anti-pattern for contrast, and the tamper-and-replay ledger all run as a working, offline application.