← Projects
11 Jul 2026 · Erlang C, in every language

Erlang C in Python

Part of Erlang C, in every language — the same call-centre staffing formula, one language at a time. Here it is in Python: standard library only, nothing to pip install.

The job is the same as the Excel version: give it the offered call load and a service-level target, and it returns how many agents you need. The one wrinkle worth knowing is that a naïve factorial overflows for a large centre, so the Poisson terms are computed in log-space with a bundled Lanczos lgamma — which is why this leans on nothing but math.

"""Erlang C — call-centre staffing. Standard library only."""
import math

_G = 7
_C = [0.99999999999980993, 676.5203681218851, -1259.1392167224028,
      771.32342877765313, -176.61502916214059, 12.507343278686905,
      -0.13857109526572012, 9.9843695780195716e-6, 1.5056327351493116e-7]

def lgamma(x):
    """ln Γ(x) — Lanczos approximation (g=7)."""
    if x < 0.5:
        return math.log(math.pi / math.sin(math.pi * x)) - lgamma(1 - x)
    x -= 1
    a = _C[0]
    t = x + _G + 0.5
    for i in range(1, 9):
        a += _C[i] / (x + i)
    return 0.5 * math.log(2 * math.pi) + (x + 0.5) * math.log(t) - t + math.log(a)

def _log_pois(k, u):
    return k * math.log(u) - lgamma(k + 1) - u

def erlang_c(agents, traffic):
    """P(an arriving call has to wait)."""
    rho = traffic / agents
    if rho >= 1:
        return 1.0
    top = math.exp(_log_pois(agents, traffic))
    cum = sum(math.exp(_log_pois(k, traffic)) for k in range(agents))
    return top / (top + (1 - rho) * cum)

def service_level(agents, traffic, aht, target):
    """Fraction answered within `target` seconds."""
    if agents <= traffic:
        return 0.0
    return 1 - erlang_c(agents, traffic) * math.exp(-(agents - traffic) * target / aht)

def asa(agents, traffic, aht):
    """Average speed of answer, seconds."""
    if agents <= traffic:
        return float("inf")
    return erlang_c(agents, traffic) * aht / (agents * (1 - traffic / agents))

def agents_required(traffic, aht, sl_goal, asa_goal):
    m = int(traffic) + 1
    while m < 10000:
        if service_level(m, traffic, aht, asa_goal) >= sl_goal \
                and asa(m, traffic, aht) <= asa_goal:
            return m
        m += 1
    return m

Using it

traffic is the offered load in Erlangs — calls aht / period, in consistent time units. For 100 calls in 30 minutes at a 180-second handle time, that’s 100 180 / (30 * 60) = 10. Then:

agents_required(traffic=10, aht=180, sl_goal=0.80, asa_goal=20)   # -> 14

Thirteen agents miss the 80/20 target; fourteen clear it.

The maths, the worked example, and the same five functions in 29 other languages live on GitHub: