Analyze call-centre service levels with abandonment rules
In this article (6 sections)
A service-level percentage is meaningful only with its waiting-time threshold and denominator. Including short abandons, excluding them or counting only answered contacts can produce different percentages from the same queue records.
Define the rule before comparing teams or periods. Keep callback requests and contacts still waiting visible instead of assigning them an outcome that has not occurred.
Inspect eight synthetic queue contacts
The contact fixture contains four answered calls with queue waits of 10, 20, 40 and 25 seconds; two abandons after 3 and 15 seconds; one callback request; and one open contact.
For this exercise, the primary resolved-contact measure is the number answered within twenty seconds inclusive divided by answered plus abandoned contacts. Callback requests and the open contact are reported separately. An alternative excludes abandons within five seconds inclusive from the denominator.
These are explicit teaching rules, not a claim that every contact-center platform defines service level identically. AWS's metric definitions illustrate the importance of contact states and threshold settings when reproducing a vendor report.
Calculate the underlying counts
SELECT
SUM(outcome='answered' AND queue_seconds<=20) AS answered_within20,
SUM(outcome IN ('answered','abandoned')) AS resolved_voice_contacts,
SUM(outcome='abandoned' AND queue_seconds<=5) AS short_abandons,
SUM(outcome='answered') AS answered_contacts,
SUM(outcome='callback_requested') AS callback_requests,
SUM(outcome='open') AS open_contacts
FROM contacts;Two calls meet the answer threshold. The resolved population is six. The primary measure is 2/6 = 33.3%. Removing the one short abandon gives 2/5 = 40%. Restricting the denominator to answered calls gives 2/4 = 50%, a different conditional measure.
from build_and_verify import database
db = database()
rows = db.execute('SELECT * FROM contacts').fetchall()
db.close()
resolved = [r for r in rows if r[1] in ('answered','abandoned')]
answered = [r for r in resolved if r[1]=='answered']
timely = [r for r in answered if r[2]<=20]
short = [r for r in resolved if r[1]=='abandoned' and r[2]<=5]
assert (len(rows),len(resolved),len(answered),len(timely),len(short))==(8,6,4,2,1)
assert len(timely)/(len(resolved)-len(short))==.4
handle_seconds = sum(r[3]+r[4] for r in answered)/len(answered)
assert handle_seconds==212.5
print({'resolved_contact_service_level':len(timely)/len(resolved),
'excluding_short_abandons':len(timely)/(len(resolved)-len(short)),
'answered_only_within_threshold':len(timely)/len(answered),
'mean_handle_seconds':handle_seconds})Handle time here is talk plus after-contact work, averaging 212.5 seconds. The fixture contains no hold time or transfers. A production definition may include additional components, so do not compare this value without matching scope.
Keep short abandons from becoming a convenient exclusion
A short abandon may reflect a wrong selection or accidental call, but the timestamp alone does not establish the reason. Changing the short-abandon threshold after seeing poor results can improve the reported service level without improving any customer's experience.
Store the threshold with the report and show the excluded count. If the policy changes, restate a comparable history or mark the break clearly.
Resolve the reporting-window question
This fixed extract has one contact still open. A resolved-contact ratio is provisional for the broader arrival population. Longer-waiting contacts may be disproportionately unresolved at a cutoff, making a completed-only view look better.
For an arrival-cohort report, retain open contacts as pending and wait for appropriate maturity or report explicit bounds under a defined contract. For interval operations, document whether metrics are attributed by queue entry, answer or disconnect time. Those choices can shift records across periods.
Callbacks also need their own journey: a request is not a completed callback and should not automatically count as either a successful live answer or an abandonment.
Use the metric to investigate the queue
A low service level can reflect demand spikes, routing, schedule mismatch or long handling times. The aggregate ratio does not identify which mechanism caused it. Inspect interval demand and queue state before making staffing conclusions.
Exercise: change the twenty-second rule from inclusive to exclusive. Identify which contact changes classification and recalculate all three percentages. Then document the boundary used by the actual reporting system.
NeuraPath's Data Analytics with Generative AI course connects operational data with metric contracts. A useful service-level report makes its exclusions and unresolved contacts as visible as its headline percentage.
Continue learning
This article is part of the Domain analytics and business cases sequence. Use the neighbouring tasks when you need the prerequisite or the next application.
- Review the prerequisite or neighbouring task in Measure campaign profitability after refunds and discounts.
- Continue with Healthcare operations analytics using synthetic appointment data.
Pankit Kumar has 10 years in Data Science & AI, building and shipping production systems in regulated pharma and clinical environments. He is a freelance trainer at Boston Institute of Analytics, AnalytixLabs and Scaler, and has taught this material to thousands of working professionals.
This article is part of our Data Analytics with Generative AI programme — 3–4 months. The full analyst stack — Excel, SQL, Power BI and Python pipelines — then a generative-AI layer you can prove is right.
Explore Data Analytics with Generative AI