esya
Selected work · Finance operations

A ledger that refuses to double-post

About 1,800 duplicate journal entries a year were distorting balances, preventing the bank reconciliation from tying, and leaving a senior bookkeeper with three weeks of year-end clean-up.

What it does
Submits batches of journal entries into an accounting ledger, having first read what is already in it.
The control
It refuses any transaction matching one already posted, or another in the same run, on date, amount, counterparty, direction and account. Refusal is the default state and overriding it is an explicit, deliberate act.
Failure
A debt or credit posted twice, a bank reconciliation that will not tie, and three weeks spent finding out why.
Unit of work
A batch checked, rather than a posting typed. Fifty transactions per write.
Authority
  • Acts alone Reads the existing ledger, groups postings into batches of fifty, and records an exit status that tells a clean run from a partial one.
  • Asks first Every commit. An operator confirms before a single posting is written.
  • Refuses Any posting that matches on all five fields. One difference is enough for it to be written.
  • Never exceeds A write budget. The run stops at the budget rather than at the end of the batch, so the worst case is bounded before it starts.
Named systems
Xero · batch write API · double-entry ledger
Claims
Measured: about 1,800 duplicate journal entries a year became none, with 1,740 refused before commit in year one, and year-end clean-up fell from three weeks to under a day. The operation had 18,000 subscriptions; that count is context, not a multiplier. Applying modelled $200 to $400 of debt-or-credit exposure plus $50 to $100 of resolution work to the measured 1,740 refusals gives $435,000 to $870,000 of annual value at risk.
Non-claims
That the modelled range is an audited saving or that cash left the bank twice. The assumed debt, credit and resolution values per refused entry are printed below.
Evidence held
The ledger history, refusal log and year-end reconciliation: about 1,800 duplicates a year became none, with 1,740 stopped before commit in year one.
Status
In production
At length

The same ten fields, with the arguments behind them.

The control

Five fields have to agree before it stops.

A duplicate check reading only an amount and a date rejects honest work and lets real duplicates through. Two payments of the same amount on the same day to different suppliers are not a duplicate; the same payment entered a week apart with the same accounting date is. This one compares date, amount, counterparty, direction and account, and a single difference is enough for the posting to be written.

The same comparison runs against the batch itself, so one run cannot duplicate inside its own boundary. That second check is the one most implementations miss, because it only matters when the source data is imperfect, which is always.

Authority

It reads on its own and commits on your word.

Before it writes anything it reads the existing ledger. It groups postings into batches of fifty and records an exit status distinguishing a clean run from a partial one, so a half-applied batch is visible rather than assumed. A write budget caps exposure before the run starts, and an operator confirms before a single posting is written.

None of that is a setting. Refusal is the default state of the system and overriding it is an explicit act by a person who has to mean it.

Claims

The control gave three weeks back to the finance function.

The client’s ledger had been receiving about 1,800 duplicate journal entries a year. Each one could misstate the debt or credit position until it was found and reversed, and together they left the bank reconciliation unable to tie. Finding the difference at year end took three weeks of a senior bookkeeper’s time.

In the first year, the refusal gate stopped 1,740 duplicate entries and none reached the ledger. Year-end clean-up fell to under a day. That is the measured bottom-line result: almost all of three senior-bookkeeper weeks returned to useful work. The value range below then asks what each refused entry would have exposed through unrecovered debt, misapplied credit and the work needed to investigate and reverse it.

Evidence held

The result and the calculation stay together.

The baseline comes from the ledger history: about 1,800 duplicates in three million journal entries. The after-state comes from the refusal log and the ledger: 1,740 stopped before commit and none admitted in the first year. The labour result comes from the year-end reconciliation: three weeks of senior-bookkeeper clean-up became under a day.

The $435,000 to $870,000 range is a sensitivity model built on an 18,000-subscription operating scale and the measured 1,740 first-year refusals, not client evidence. Every assumption is shown below so a reader can replace it.

How the work moved

From a read-only pass to a governed write.

Nothing wrote to the ledger until the refusal check had been proved against real history with no write path attached. That ordering is the whole engagement, and it is the reason the first duplicate the system ever caught was caught in a dry run.

Map

Read the ledger as it stands.

A full read of the existing postings, with no write path attached and no credentials that could create one.

Two weeks

Build · Rules

The five-field comparison.

Date, amount, counterparty, direction and account, with the same test run against the batch itself.

Build · Bounds

The write budget and the exit status.

The run stops at the budget rather than at the end of the batch, and reports whether it finished clean.

The rules and the bounds were built in parallel, because a refusal with no ceiling behind it is a rule you can talk your way past

Ship

Dry runs against real history.

Every refusal reviewed by the client’s finance team before a write path existed at all.

Eight weeks of dry runs

Run

The operator gate goes live.

Batches commit only after a person confirms, and the exit status is read on every run.

Fourteen weeks from first workshop to production

Client IP throughout. The refusal rules live in the client’s repository.

Before and after.

Measure Before After
Duplicate postings reaching the ledger About 1,800 a year None; 1,740 refused before commit
Write calls for a quarter of a million postings 250,000 5,000
Year-end duplicate clean-up Three weeks of a senior bookkeeper Under a day

The write-call row is a property of the code and needs no measurement: the batch API takes fifty items per call. The other two rows are the client’s own figures.

Modelled annual value

Six figures of exposure, with the assumptions left in.

This is a sensitivity model, not an audited client saving. The operation had 18,000 subscriptions; that count is context, not an input to the arithmetic. The model is driven by the measured 1,740 first-year refusals. The low case assigns $200 of debt or credit exposure and $50 of resolution work to each refusal. The high case assigns $400 and $100 respectively.

Value driver Low case High case Method
Operating scale 18,000 subscriptions 18,000 subscriptions Scenario context, not a multiplier
Refused entries 1,740 1,740 Measured in the first year
Debt or credit exposure per refusal $200 $400 Replaceable scenario assumption
Resolution work per refusal $50 $100 Replaceable scenario assumption
Modelled annual value at risk $435,000 $870,000 1,740 × ($200 + $50) to ($400 + $100)
Reconciliation capacity Three weeks recovered Three weeks recovered Measured separately and excluded from the dollar range

The debt-or-credit and resolution components are explicit scenario assumptions, not client measurements. The separately measured three-week year-end clean-up is excluded from the dollar range, so the same labour is not counted twice.

This is one control inside one finance workflow. When one bounded control puts a modelled six-figure exposure in view before the wider operational benefit is counted, it is no longer an automation line item. It is operating infrastructure.

We did not buy a faster way to post. We bought the only version of this where nobody has to go looking in March for what went in twice in June.

Approved testimonial wording · Financial controller at the client

What this case does not claim.

No claim that every refusal prevented a permanent loss. The debt-or-credit exposure and the resolution work per refusal are replaceable assumptions, printed so a reader can argue with each component rather than being asked to accept the total.

No claim that the subscription count caused the refusal count. The operating scale is context, not a multiplier; the measured refusals are what drive the model.

No claim that the modelled range appeared in the client’s bank account. Duplicate journals can misstate debt or credit without becoming duplicate cash movements. The page therefore publishes the measured reconciliation result and labels the dollar range as a replaceable sensitivity model.

Book a call Read the register