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:

queueSLA targetpatience (mean)peak volume/intervalAHT
Billing Support80% in 20s75s40300s
Technical Support80% in 30s90s30420s
Sales90% in 15s45s25240s
Retention75% in 25s60s15360s

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.