Case study 04 · Real-time & analysis
Measure before you build
- Context
- The call-centre platform of an enterprise client, built on a commercial phone system
- My role
- Main contributor since mid-2026 on a system a larger team built before me.
- When
- 2026 – now
- Stack
- .NET · SQL Server · Redis · SignalR · MassTransit · Azure Service Bus
The problem
Too many callers were hanging up in the queue. The obvious fixes on the table were more staff, or a new routing feature the business had already asked for. Before building anything, I wanted to know why it was actually happening.
What I did
- Made the data trustworthy first. The phone vendor’s API was throttling statistics requests. I batched and parallelised them, cached them in Redis by day with a fallback when Redis is unavailable, retried throttled calls, and added the indexes the queries needed. I also added a nightly sync of abandoned calls so the data team could work from the same numbers.
- Investigated the cause. I looked at every claim about the problem and labelled each one measured, inferred or refuted, using standard queueing maths to check what staffing could and couldn’t explain.
- Found it. The biggest cause wasn’t staffing or skills. The application’s own status logic was logging agents out of their queues.
- Pushed back with evidence. A feasibility report showed the requested routing feature couldn’t be built as specified, and would have addressed somewhere between 0.1% and 2% of abandoned calls.
- Fixed what mattered. Recording which queue each call came through (with a backfill), requiring an outcome before an agent goes back to ready, a flag-gated fallback queue, an open/closed status, and reserving a line before it rings with a Redis gate that lets calls through if Redis fails.
Decisions and tradeoffs
- Evidence over momentum. Saying “don’t build the thing you asked for” only works with numbers behind it, and with a better option on the table.
- Fail open where callers are involved. Every new guard in the call path lets the call through if its own dependency is down. A missed optimisation is better than a dropped call.
- Flags for anything that changes routing. New behaviour ships switched off and turns on deliberately.
Alongside it
I also took over an AI voice receptionist that answers and routes calls: measuring the delay callers actually hear, routing only to queues that are open, and spoken handoffs before every transfer.
my partexisting
Switch on “Show working” for the annotations.