One number. Four answers.
We agreed what “90+ days overdue” meant, built it, and shipped it into exactly one screen — and it was not the screen the decision-maker reads.
The owner of the business told us that the overdue calculation had already been discussed and finalised, and asked why the figure on his screen was still wrong. He was right on both counts, and that is what makes this worth writing down rather than quietly fixing. The definition had been agreed. We had built it. We had built it into one screen, and left every other screen showing the same label to carry whatever arithmetic it had always used.
This is our live product — reps, dealers, suppliers and a head-office desk across a 93-branch commercial-vehicle parts distributor, sitting on top of a twenty-year-old VB.NET ERP of roughly 3.4 million lines. Real receivables, in production every day.
When we counted, the same question — how much of what dealers owe is more than ninety days overdue — had four live answers on the same product at the same time.
The company ageing bar took each bill’s age, gross, and bucketed each bill separately. That was the agreed definition. Beside it sat a netted variant of the same bar, credit notes subtracted. A third worked at dealer level, measuring days past terms rather than bill age. The fourth was the exceptions row on the head-office dashboard — the one he actually looks at, computed separately from all three, and the furthest of the four from the answer we had agreed. Roughly half of it.
That outlier deviated three ways at once, and the deviations compounded. It aged money from the due date rather than the invoice date, which on normal trade terms is weeks of difference. It bucketed per dealer rather than per bill, so a dealer’s entire balance landed in the bucket of their worst invoice. And it netted credit notes out. Each of those three choices is defensible on its own. Together they answer a different question, under the same words.
Nothing was broken. No test failed, no alert fired, no query errored, every screen rendered, and each of the four numbers was internally consistent and entirely plausible. Nothing in the product had any notion that two of its own figures were meant to agree, so nothing could notice that they did not. It was found the only way it could have been found: a person put two screens carrying the same label side by side and saw different money. What reads as a process complaint was a precise and correct bug report.
The cause was not a coding slip. A line on our own backlog read, in effect, one place that owns outstanding and overdue. We had written it down and recognised it as right. We skipped it, more than once, in favour of shipping screens — because it would have produced no new screen of its own. That is the mistake, and the incentive behind it is worth naming plainly: the work that stops a number drifting is invisible on the day you do it, and becomes visible only on the day someone loses faith in a figure. We took the visible work. Meanwhile the definition we had agreed went onto a screen the decision-maker does not use, while the screen he does use carried the oldest and most divergent version. Nobody had checked which screen the person complaining was reading.
The per-dealer 90+ amount and its bill count are now produced inside the same single pass over the bill list, through the same bucketing rule, as the company-level band. The headline bar, the exceptions row and the drill-down beneath it are no longer three implementations that happen to agree; they are one piece of arithmetic displayed three times, and cannot disagree. We checked it against the full book: the two independently assembled totals landed one rupee apart — per-dealer rounding — with identical bill counts. We relabelled the row so it states the basis it measures instead of implying a different one. We also had to apply the fix twice, because the first pass corrected the arithmetic without correcting the selection that decided which records the arithmetic ran over.
One detail worth copying: the row’s total sums over every dealer, not only the ones listed underneath it. A few carry a negative pending balance from an over-applied receipt and are left out of the list — but they must stay in the total, or the row and the bar drift apart again, which is the entire bug.
There is a second mistake here, and it is the better one. The check we wrote to stop this recurring — a standing comparison that fires when two figures that must match do not — was written to abort the data build. It fired on real data. It killed every rebuild for hours. So while fixing a stale number we froze the whole product on stale numbers, and the figure that started all this sat at its old value all afternoon. We turned a reporting defect into an availability one, in the middle of fixing the reporting defect. The check now warns loudly and lets the build finish.
A guard on a number must never be able to take the product down. Stale but consistent beats confidently absent.
A refused build hands every user yesterday’s data on every screen — far worse than the discrepancy it was looking for.
Days later we went looking on purpose and found seven more places that had each hand-written their own answer to “what does this dealer owe” — a rep’s home band, the credit strip on the order screen, an export, a review list, an exposure view among them — all now behind one server-side definition and one front-end pair. One habit came out of the same week: a code release and a data rebuild land at different times, so a screen must confirm a newly added figure is really there before it depends on it.
The last decision is the one we would most like a sceptical reader to check us on, because it is where we deliberately did not unify. What we display is gross. What blocks an order stays net, on purpose. Moving the block onto the gross basis would begin blocking dealers who were not blocked yesterday, and that is a change of credit policy wearing an engineering cleanup’s clothes. It is the owner’s decision, not ours — one variable when he wants it. Until then the two figures answer two different questions, and each one says which question it answers.
A number with no owner will disagree with itself. Agreeing a definition is not the same as owning one: unless one piece of code computes a business figure and everything else reads that computation, every screen re-derives it, and the derivations drift silently, because each is individually correct and nothing compares them. That is how we build. One version of the truth does not mean one number — it means one owner per number, every number stating its basis, and the line between an engineering decision and a business one drawn where the business can see it. All four of those answers were working software. The fix shipped no new screen at all. It moved a computation.
Begin
How many answers does your business have to its own most important number? Let’s find out.
A conversation about your business — not a product demo.