Data engineering2026

Nothing to point at

Contact rates were falling at every attempt and there was no evidence for why. The answers were already in the dialer, in twenty million attempt records nobody had a way to look at. This is the platform that made them legible, and what it found on the first pass.

SQL ServerPHPChart.jsVanilla JS

Attempt fall-off, caller-ID rotation health and contact rate by hour, reconstructed with synthetic data. Client names, campaign identifiers and caller-ID numbers removed. Volumes perturbed.

See it working →
dialer-intelligence19.7M attempts · 2026-02 → 2026-07synthetic data

Attempt fall-off · contacts

No cliff — yield stays above 1 contact/1k through the measured range

42.1K
8.9K
Att 1
27.4K
5.2K
Att 2
18.2K
3.0K
Att 3
11.9K
1.7K
Att 4
7.6K
1.0K
Att 5
4.7K
0.6K
Att 6
DialsContacts

Fall-off detail

Per-attempt numbers · cumulative shares

attdials→contactsratenew/1kreach∑dials∑
142,1008,93021.2%212.151%38%
227,4005,18018.9%158.476%62%
318,2002,98516.4%119.888%78%
411,9001,74014.6%96.695%89%
57,6001,00513.2%80.398%96%
64,70055511.8%66.0100%100%

Goals by attempt

Bars = leads whose first goal landed at that depth

212
Att 12.4%
148
Att 22.9%
96
Att 33.2%
61
Att 43.5%
37
Att 53.7%
22
Att 64.0%

Conversion is the share of that depth's answers that converted — the denominator moves with the bar, which is why the two read in opposite directions.

Goals detail

First goals · conversion by depth

attfirst goalsgoals∑ans convgoal/1k
121237%2.4%5.04
214863%2.9%5.40
39679%3.2%5.27
46190%3.5%5.13
53796%3.7%4.87
622100%4.0%4.68

Attempt outcomes

What each dial hit at each depth · share of dials

Att 1
Att 2
Att 3
Att 4
Att 5
Att 6
ContactedAnswering machineBurnedNo answer & other

Answering machine climbs at every depth while contact falls. The dials past attempt two are not finding fewer people at random — they are landing in voicemail.

Cap planning

If dialing stopped at each depth

cap atkeep reachedkeep goalssave dials
Attempt 151%37%62% (69,800)
Attempt 276%63%38% (42,400)
Attempt 388%79%22% (24,200)
Attempt 495%90%11% (12,300)
Attempt 598%96%4% (4,700)

Keep-goals is the same quantity the Goals detail prints as goals∑, and dials saved is computed against the derived total rather than by summing the rows past the cap — the curve is truncated at the 99th percentile of depth, so summing undercounts.

Reconstructed with synthetic data — no real agent, customer, client, or caller information appears anywhere on this site.


A call center’s lists are its inventory, and almost nobody can tell you how theirs are performing.

An acquisition list is 500,000+ leads worked across ten attempts. Inside that are the questions that decide whether the operation makes its month:

  • Which regions actually perform, and which ones absorb dials?
  • Which caller IDs are working, and are any of them over- or under-indexing?
  • What is the best time of day to reach a customer, by region?
  • What is the best attempt — where does the return stop justifying the dial?
  • How often are we rotating caller IDs, and does that rotation match the region we are calling?

These are the lifeblood of a floor trying to get the most out of a list. They’re all answerable from data that already exists. Every dial writes a row.

There was no good way to see any of it.

Twenty million rows and no view

The attempts table holds roughly twenty million rows and grows every month. None of that was ever the hard part. Answering any of the questions above meant someone writing a query, and a question you have to commission is a question you stop asking.

So I built the view.

The constraint was fixed before I started: SQL Server Express, which enforces a hard ten-gigabyte ceiling per database. Not a licensing nag. A wall. Page compression is the only reason it fits, and compressed, the data file sits around 3.5GB of the ten available. Uncompressed it doesn’t fit at all, and moving to a larger edition was a procurement conversation nobody was going to have.

Express also runs one memory-grant pool, so parallel cold aggregates starve each other. That one fact shapes more of the architecture than any design preference did.

It’s the honest shape of most real infrastructure work. Nobody chose this stack. The engineering was making something genuinely useful exist inside constraints handed to me.

What it found

Contact rates had been slipping at every attempt. Not a cliff. A decay, across the board, with nothing to substantiate it. Every explanation available was a guess, and none of them could be tested.

The first real pass through caller-ID rotation showed one program sending nearly all of its dials through a single number.

That’s not a cosmetic problem. Carriers watch call patterns, and a number behaving that way gets flagged as spam, at which point answer rates fall for reasons that never appear in any report you own. We held STIR/SHAKEN Attestation A, the highest there is, the carrier vouching that we had the right to use the numbers we called from. It still isn’t a shield. Attestation establishes who you are and says nothing about whether you’re behaving well, so the diligence stays yours.

We believe we were being quietly side-lined, and the decay in contact rate was what that looked like from the inside.

Every one of those dials was a row in a table anyone could have queried. Nobody could see it. Seeing it needed a view of rotation health by attempt and by region, and nobody had built one.

Caching, and being wrong about it

These views read the whole table rather than a slice of it. The heaviest one reads every month at once, four queries across twenty million attempts, and cold it takes about sixteen minutes. Warm, the same view returns in about half a second, and the standard views can be pre-loaded before anyone asks for them. Caching is the product here, not an optimization sitting on top of one.

That ratio is the argument for building it this way. Sixteen minutes is long enough that people quietly stop asking, which puts the floor back where it started, with answers sitting in a table nobody goes and gets.

Cache entries key against per-month version fingerprints. Ingesting new dialer data bumps only the months that ingest actually touched. Replacing disposition mappings bumps everything, because those flags ripple backwards through history. Historical months, once settled, effectively never recompute.

A warming job pre-computes the standard views after each ingest, so nobody’s Monday morning is the slow one.

It was silently warming nothing.

The cache key hashes the request, and parameter order is part of it. The warming job built its URLs by hand. The application built its from its own parameter builders, and the two produced different strings for identical requests. Every warm run diligently populated keys no user would ever hit, and every user still ate the cold compute.

The warming job stocks the shelf ahead of time The dashboard what a user actually opens month client view client month view the name is built from this exact order month·client·view → b91c… client·month·view → 7f3a… The finished report is here it would answer in half a second Nothing here so minutes of compute run, every time not the same name
The name a finished report is filed under is built from the exact wording of the request, in order. These two asked for the same thing in a different word order, so they built two different names. Every run, the warmer filed a perfect report under a name nobody would ever look up.

Nothing errored and nothing logged. The job reported success because it had genuinely done what it was told.

The fix was to make the warmer emit application-identical strings byte for byte. I keep relearning this one: a job that reports success is not evidence that the work happened. Verify the effect, not the exit code.

The things that won’t hold still

One client renumbers its campaign identifiers every quarter, so goals arrive tagged with identifiers that may no longer correspond to anything currently dialing. Rather than hand-mapping that every quarter, the sync resolves each goal row through campaign identity to whichever jobs actually dialed that month, landing the goal once and creating zero-goal placeholders for its siblings so nothing double-counts.

Anything it can’t resolve lands in one of two report lists: goal rows represented nowhere, and dialed work no goal accounts for. Both lists have to come back empty before a sync counts as done. The system knows what it doesn’t know and says so, rather than bucketing the remainder into “Unknown” and letting a wrong number reach a client deck.

The platform also carries two month bases at once, fiscal and calendar, because the clients on it don’t agree about when a month starts. It tracks both and every aggregate declares which one it used. A report that doesn’t tie out to the numbers a client already audits is worthless no matter how good it is.

One dial, placed late in March one call, one row in the table same call. both answers correct. ONE CLIENT COUNTS IT IN MARCH their month runs with the calendar THE OTHER COUNTS IT IN APRIL their month has already rolled over So every row carries both labels and every total has to say which one it used
The same call, counted in two different months, and neither client is wrong. One reports on the calendar, the other closes its month before the calendar does. Get the label wrong and the dials land in the wrong month, which the client's own audit catches before anyone internally does.

Where it stopped

I was furloughed before this one was finished.

The rotation finding was the first pass, not the last. Local versus toll-free by region, rotation depth by attempt, whether the best time to call holds per region or only in aggregate: all answerable now, none of them answered yet.

I’m including it anyway. What I’d want evaluated is the list of five questions at the top. Whether those are the right ones to ask of a dialer was a judgment I had to make before any of this got built, and no tooling was going to make it for me.

On specifics. This work was built for a private employer under a client contract. Client names, personnel, internal hostnames, customer and caller numbers, and commercial figures are withheld or generalized throughout, and every interface on this site is a reconstruction filled with synthetic data. The reasoning is mine to discuss, in as much detail as you want. The data is the client's, and none of it appears on this site.


Want the unabridged version?

Ask me why sale credit comes from the disposition, or what happens when the live feed is empty at 9:15 in the morning.