Case study
The number the firm priced on was wrong.
The firm quoted fixed fees against a blended average cost per case. The average was real, the arithmetic was correct, and it had been drawn from the wrong population.
The problem
Fixed-fee pricing is a bet on what a matter will cost to run. The firm was making that bet against a single blended figure covering all family work. It was the most consequential number in the business, and nobody had audited how it was produced.
What the rebuild found
Two independent errors, both pushing the same direction. The population included matters that were never representation at all — administrative files, balance-forward entries, test records. And the case-type filter relied on a field that was blank on roughly seven hundred genuine family matters, so they were simply missing from the count.
Corrected, the monthly burn was understated by about seventy percent. It had also concealed a threefold spread between case types that were being priced identically.
2,559
Matters analysed from source billing records
~70%
The benchmark was understated by
3x
Spread between case types priced the same
The finding that changed the pricing
A fixed fee is quoted in engagement months, so it has to be measured in engagement months — what a matter bills in its own first month, second, third — rather than across a whole lifetime. No existing report could answer that.
Once it could, the real exposure was obvious, and it was not the price. Very few matters exceeded the quoted fee inside the fixed period. Most were still billing well after that period ended. The firm was not underpricing the work. It was underpricing the duration, which is a different problem with a different fix.
An average taken across the wrong population is not a small error. It is a confident number pointing the wrong way.
The bug that makes this worth reading
Every paginated query in the first version requested two thousand rows at a time from a database that caps responses at one thousand. The loop's exit condition — fewer rows returned than requested — was therefore true on the very first request, so every query silently stopped at row one thousand.
One page reported 999 matters where 1,673 matched. Results were ordered newest first, so the records dropped were the oldest, biasing every average toward newer and less-billed work. The type checker passed. The build passed. Nothing in the interface looked wrong. Only running it against real data and counting the answer catches a failure of that shape.
Want to know whether this applies to your firm?
Thirty minutes, an honest read on where your friction actually is, and a recommendation. If we are not the right fit, we will tell you.
Book an Operational Strategy Call