This is the reference doc. See it running on real generated data, or build it yourself in the Studio.
Generate Synthetic Contact Center / Erlang C Data in Python
Every commercial staffing calculator (Assembled, NICE, Genesys, and dozens of standalone Erlang calculators) runs the same 1917 formula to answer one question: given how many calls are coming in and how long each one takes, how many agents does it take to hit a service level target? A call_volume column and an agents_staffed column with no relationship between them can't test a WFM pipeline — the whole point of workforce management is that staffing is derived from volume and a target, not chosen independently.
import misata
schema = {
"queues": {
"__rows__": 4,
"queue_id": {"type": "integer", "primary_key": True},
},
"intervals": {
"__rows__": 2400,
"interval_id": {"type": "integer", "primary_key": True},
"queue_id": {"type": "integer", "foreign_key": {"table": "queues", "column": "queue_id"}},
},
}
tables = misata.generate_from_schema(misata.from_dict_schema(schema, seed=17))
print(list(tables.keys())) # ['queues', 'intervals']
That's the structural shape. The full example — four queues with real SLA policies, a realistic intraday call-volume curve, and agents_staffed reverse-solved from Erlang C itself — is a working, runnable script: examples/contact_center_queueing.py. Run it directly:
python examples/contact_center_queueing.py
It prints every guarantee below, checked against the data it just generated:
queues: 4 intervals: 2400
[OK] agents_staffed exceeds traffic_intensity_erlangs on every interval (queue stability)
[OK] 'Billing Support' clears its 80% SLA target on 100.0% of intervals
[OK] 'Retention' clears its 75% SLA target on 100.0% of intervals
[OK] 'Sales' clears its 90% SLA target on 100.0% of intervals
[OK] 'Technical Support' clears its 80% SLA target on 100.0% of intervals
[OK] service_level_pct matches an independent Erlang C recomputation on every interval
[OK] calls_answered + calls_abandoned equals offered_calls on every interval
[OK] occupancy_pct and service_level_pct correlate at -0.31 (higher load, worse service)
[OK] 'Billing Support' midday volume (40.7) exceeds opening volume (13.8)
[OK] 'Retention' midday volume (14.9) exceeds opening volume (6.1)
[OK] 'Sales' midday volume (25.8) exceeds opening volume (9.2)
[OK] 'Technical Support' midday volume (29.5) exceeds opening volume (10.7)
[OK] 'Billing Support' abandons 1.8% of calls over the full month
[OK] 'Retention' abandons 4.0% of calls over the full month
[OK] 'Sales' abandons 0.8% of calls over the full month
[OK] 'Technical Support' abandons 1.7% of calls over the full month
[OK] intervals.queue_id has zero orphans against queues.queue_id
[OK] every interval_start falls exactly on a half-hour mark
[OK] agents_staffed is a positive integer on every interval
ALL CHECKS PASSED
What each number is grounded in
Traffic intensity. Measured in Erlangs — the actual unit, named after the man: A = offered_calls x AHT / interval_length. Not an arbitrary "load score."
Probability of waiting. The Erlang C formula, computed through the numerically stable Erlang B recursion (Sundt-Jewell) rather than the raw factorial form it's algebraically identical to, which overflows past N ≈ 170:
B(0, A) = 1
B(n, A) = A x B(n-1, A) / (n + A x B(n-1, A))
C(N, A) = B(N, A) / (1 - (A/N) x (1 - B(N, A)))
Average speed of answer and service level. The standard Erlang C results: ASA = C(N, A) x AHT / (N - A) and SL(T) = 1 - C(N, A) x exp(-(N - A) x T / AHT).
Staffing. Not assigned — reverse-solved. Given a queue's declared SLA target (e.g. "80% of calls answered within 20 seconds," the industry's most-cited baseline) and an interval's forecast volume, agents_staffed is the smallest N for which the Erlang C service-level formula actually clears that target, plus a 20% shrinkage buffer (breaks, training, absenteeism — the standard WFM planning concept; ICMI's commonly cited range is 25-35%). This is the same reverse search every commercial staffing calculator runs, and it's why service_level_pct measurably clears each queue's own declared target on essentially every interval rather than sometimes falling short.
Abandonment. A real queueing-theory result, not a random flag: in an Erlang C / M/M/N queue, a call that has to wait experiences a wait time that is itself exponentially distributed with rate (N x mu - lambda) (Gross & Harris, Fundamentals of Queueing Theory). Modeling customer patience as a second, independent exponential clock turns "does this call abandon" into a race between two exponentials, which has a closed form: patience_rate / (patience_rate + wait_rate). This is this example's own reasonable extension of the exact Erlang C result — see "What this is not" below for what it isn't claiming.
Expected vs. realized, the same split credit-risk-portfolio uses. wait_probability_pct, asa_sec, and service_level_pct are deterministic — exactly what the Erlang C formula implies for that interval's own (volume, staffing, AHT), recomputable and checked above. calls_abandoned and calls_answered are the realized, stochastic outcome: a real Binomial draw at the implied abandonment probability, not the expected rate copy-pasted into every row. That's why the aggregate abandonment rate lands close to what the formula implies, while any single sparse interval can look noisier — exactly like a real WFM report.
The queues
Four queues with deliberately different traffic, handle times, SLA policies, and patience — a sales queue's callers hang up sooner than a billing queue's, and its SLA is tighter because a missed sales call is a lost lead:
| queue | SLA target | patience (mean) | peak volume/interval | AHT |
|---|---|---|---|---|
| Billing Support | 80% in 20s | 75s | 40 | 300s |
| Technical Support | 80% in 30s | 90s | 30 | 420s |
| Sales | 90% in 15s | 45s | 25 | 240s |
| Retention | 75% in 25s | 60s | 15 | 360s |
What this is not
This models a single voice queue per line of business with Erlang C, the classical model that ignores abandonment in its own staffing formula — real Erlang A (Palm, 1957) has a closed form involving the confluent hypergeometric function that this example does not implement; the abandonment modeled here is this example's own extension via a competing-exponentials approximation, not a claim to reproduce Erlang A exactly. It does not model chat, email, or blended queues, which need different math than phone queueing theory. Call volumes and AHT are declared, realistic-shaped assumptions, not measured from a real center's actual historical data.