PricingData qualitySystems

Three prices, one truth

While rebuilding a pricing calculator I found the same job priced three different ways by the same business, with the widest gap close to two thousand pounds. Nobody knew, because each source was "the price" to whoever used it.

I did not go looking for this. I was rebuilding a pricing calculator buried in a page-builder website for a service business, the sort of unglamorous job where you expect to fight page builders and plugin debris, not accounting philosophy. Then one deal came through the CRM with a value that looked slightly off. Not dramatically wrong. Just wrong enough to check.

Checking it took most of a day, because the answer depended on which "the price" you asked.

Three sources, three answers

The business had three places where the price of a job lived. There was the price table shown to customers in the calculator itself. There was a separate calculation in the backend code, written at a different time, which produced the totals that actually flowed into the deals. And there was a price list document in the office, which is what anyone quoting over the phone read from.

Each one was authoritative to the people who used it. Customers believed the screen. The system believed the code. The office believed the document. For most job types the three roughly agreed, which is exactly why nobody had noticed. For one job type, the customer-facing total and the backend total were nearly two thousand pounds apart.

Nobody had lied and nobody had been careless in any single moment. The three sources were born separately, drifted separately, and nothing in the business would ever force them to meet.

The double underscore

The mechanism behind the worst gap was almost insultingly small. The CMS stored pricing fields under text keys, and one key had a double underscore where all of its siblings had a single one. The loading code asked for the single-underscore name, got nothing back, and carried on. One whole price column had silently never loaded, and the calculation fell through to older logic sitting underneath it.

No error, no log line, no customer complaint, because the number on screen still looked like a plausible price. It was a plausible price. It just was not the same plausible price the backend was writing into the deal.

Pick one truth, deliberately

The tempting fix is to correct all three sources until they match, declare victory and move on. That lasts until the next edit, because the structure that let them drift is still standing. The real fix is to decide, out loud, which source is canonical, then demote the others to read-only views of it.

We chose the customer-displayed price. Partly on principle: the number a customer sees and accepts is the one you should be prepared to honour, so it may as well be the number everything else agrees with. Partly for a practical reason: it was the most looked-at of the three, so it had the best chance of already being right.

Everything else was then rebuilt to read from it. The backend calculation no longer holds its own opinion; it prices jobs from the same data the customer sees. The office price list stopped being a separate document to maintain and became an output of the same engine. One source, several views.

Lock it with fixtures

A single source only stays single if something defends it. So the last step was a regression suite of fixture jobs: seven real job shapes, priced by hand, checked with the people who quote for a living and agreed as correct. They live in the test suite, and the build fails if the engine prices any one of them differently.

Seven is not a magic number. It was enough to cover each pricing path plus the awkward cases: the minimum charge, the surcharge, and of course the job type that had the gap. What matters is that pricing drift now breaks a build within minutes instead of surfacing in one odd deal value years later. Cheap, boring, effective.

You almost certainly have this too

I no longer think this business was unusual. Most companies with a website, a spreadsheet and a phone line have quiet price disagreements between them, because each one grew up serving different people. The website was built by an agency, the spreadsheet by a manager, and the phone answers come from memory and a laminated sheet. Nobody compares them, because to each user their copy simply is the price.

The fix is not a bigger spreadsheet, and it is not a stern email about keeping things up to date. Documents that humans have to synchronise will desynchronise. The fix is one pricing engine that everything else reads from, plus a small set of hand-checked jobs that scream when it drifts.

You can test whether you have this problem before you spend anything on fixing it. The audit below fits inside a week, and most of it fits inside an afternoon.

Building or running AI in a real business? Let's talk.

← All writing