CLI-first TurboTable-style calculations for contact-center planning.
mod_turbotab keeps the historical name known by call-center planning and traffic analysts, while exposing turbotab as the primary interface for humans, scripts, and AI agents.
turbotab staffing required --sla 0.80 --service-time 20 --calls-per-interval 25 --aht 180 --json
{"calculation": "staffing.required", "inputs": {"aht": 180, "calls_per_interval": 25.0, "interval": 600.0, "service_time": 20, "sla": 0.8}, "result": {"name": "agents", "unit": "agents", "value": 11}, "schema_version": "1.0"}
Why
mod_turbotab answers operational questions that show up constantly in contact-center planning:
| Question | Command |
|---|---|
| How many agents do I need? | turbotab staffing required ... |
| What SLA will this staffing achieve? | turbotab sla achieved ... |
| How long will the queue wait be? | turbotab queue wait ... |
| How many trunks are required? | turbotab telecom trunks ... |
| What is the Erlang B/C/A result? | turbotab erlang ... |
It provides Erlang B, extended Erlang B, Engset B, Erlang C, Erlang A, queue metrics, staffing metrics, call capacity, and telephony trunk sizing with no third-party runtime dependencies.
Quick Start
Required staffing for 80% SLA in 20 seconds:
turbotab staffing required \
--sla 0.80 \
--service-time 20 \
--calls-per-interval 25 \
--aht 180 \
--json
Achieved SLA for a fixed staffing level:
turbotab sla achieved \
--agents 11 \
--service-time 20 \
--calls-per-interval 25 \
--aht 180 \
--json
Average queue wait:
turbotab queue wait \
--agents 11 \
--calls-per-interval 25 \
--aht 180 \
--json
Required trunks:
turbotab telecom trunks \
--agents 11 \
--calls-per-interval 25 \
--aht 180 \
--json
Every command group falls back to contextual help:
turbotab
turbotab sla
turbotab staffing required --help
CLI
Agent-facing commands are intent-first:
turbotab
├── staffing
│ ├── required
│ ├── asa
│ ├── capacity
│ ├── fractional-required
│ └── fractional-capacity
├── sla
│ ├── achieved
│ └── target-time
├── queue
│ ├── wait
│ ├── size
│ └── probability
└── telecom
└── trunks
Detailed formula/module commands are also available:
turbotab agents ...
turbotab queues ...
turbotab erlang ...
turbotab traffic ...
turbotab trunks ...
Use --json when calling from agents or automation. Invalid inputs exit non-zero and print a concise error to stderr.
Agent usage
Agents should prefer the CLI with --json instead of parsing text output or importing Python internals.
turbotab staffing required --sla 0.80 --service-time 20 --calls-per-interval 25 --aht 180 --json
JSON output is the stable agent contract:
{
"schema_version": "1.0",
"calculation": "staffing.required",
"inputs": {
"aht": 180,
"calls_per_interval": 25.0,
"interval": 600.0,
"service_time": 20,
"sla": 0.8
},
"result": {
"name": "agents",
"unit": "agents",
"value": 11
}
}
The bundled skill lives at skills/mod-turbotab/SKILL.md. It includes command recipes, unit rules, and agent guardrails.
Units and assumptions
This project uses interval-based planning buckets.
Every function or CLI command that accepts call volume uses calls_per_interval, not calls per hour by default.
| Parameter | Meaning |
|---|---|
calls_per_interval / --calls-per-interval |
Arrivals in the planning bucket |
interval / --interval |
Planning bucket in seconds |
Default interval |
600 seconds, or 10 minutes |
aht / --aht |
Average handle time in seconds |
service_time / --service-time |
Target answer time in seconds |
sla / --sla |
Ratio, for example 0.80 for 80% |
For hourly semantics, pass --interval 3600:
turbotab staffing required --sla 0.80 --service-time 20 --calls-per-interval 150 --aht 180 --interval 3600 --json
Traffic intensity is computed as:
A = \frac{\lambda \cdot h}{I}
where A is offered traffic in erlangs, lambda is arrivals per interval, h is AHT in seconds, and I is interval length in seconds.
Worked example
With the default 10-minute bucket:
| Input | Value |
|---|---|
| Calls | 25 per 10 minutes |
| AHT | 180 seconds |
| Target SLA | 0.80 |
| Target answer time | 20 seconds |
CLI:
turbotab staffing required --sla 0.80 --service-time 20 --calls-per-interval 25 --aht 180 --json
turbotab sla achieved --agents 11 --service-time 20 --calls-per-interval 25 --aht 180 --json
turbotab queue wait --agents 11 --calls-per-interval 25 --aht 180 --json
turbotab telecom trunks --agents 11 --calls-per-interval 25 --aht 180 --json
Expected headline results:
| Metric | Result |
|---|---|
| Required agents | 11 |
| Achieved SLA | 0.880836 |
| Average queue wait | 51 seconds |
| Required trunks | 18 |
Python API equivalent:
from mod_turbotab.agents.capacity import agents_required
print(agents_required(0.80, 20, 25, 180))
Mathematical model
Notation:
| Symbol | Meaning |
|---|---|
N |
Agents, servers, or trunks depending on context |
lambda |
Arrival volume per configured interval |
h |
Average handle time in seconds |
I |
Planning interval in seconds |
mu |
Service completions per interval per server |
A |
Offered traffic in erlangs |
rho |
Utilization |
B(N, A) |
Erlang B blocking probability |
C(N, A) |
Erlang C queueing probability |
Core conversions:
\mu = \frac{I}{h}
A = \frac{\lambda}{\mu} = \frac{\lambda h}{I}
\rho = \frac{A}{N}
Erlang B recurrence:
B_0 = 1
B_n = \frac{A B_{n-1}}{n + A B_{n-1}}
Erlang C:
C(N, A) = \frac{B(N, A)}{\left(\frac{A}{N}\right) B(N, A) + \left(1 - \frac{A}{N}\right)}
Queue wait:
W_q = \frac{1}{N \mu (1 - \rho)}
SLA:
\mathrm{SLA}(t) = 1 - C(N, A)\exp\left(-\frac{N - A}{h}t\right)
ASA:
\mathrm{ASA} = \frac{C(N, A)}{N \mu (1 - \rho)}
Erlang A extends Erlang C with abandonment through average patience. When patience=None, pure Erlang C is used.
API reference
The CLI is the primary interface, but the Python API remains available.
| Module | Public functions |
|---|---|
calculations.erlang |
erlang_b, erlang_b_ext, engset_b, erlang_c, erlang_a |
calculations.traffic |
traffic, looping_traffic |
agents.capacity |
agents_required, asa, agents_asa, nb_agents, call_capacity, fractional_agents, fractional_call_capacity, occupancy, is_within_occupancy |
queues.queues |
queued, queue_size, queue_time, service_time, sla_metric |
trunks.trunks |
number_trunks, trunks_required |
utils |
min_max, int_ceiling, secs |
Import example:
from mod_turbotab.agents.capacity import agents_required
agents = agents_required(
sla=0.80,
service_time=20,
calls_per_interval=25,
aht=180,
)
Exceptions
Project-specific exceptions live in exceptions.py:
| Exception | Meaning |
|---|---|
InputValidationError |
Invalid argument values |
CalculationError |
Calculation failed or search could not converge |
Example:
from mod_turbotab.exceptions import CalculationError, InputValidationError
try:
...
except InputValidationError:
...
except CalculationError:
...
Occupancy cap
Erlang C alone can recommend staffing levels that drive sustained occupancy above 90%, which is associated with burnout and attrition. agents_required accepts an optional max_occupancy ceiling that lifts the headcount whenever the Erlang result would breach it; defaults are unchanged when the parameter is omitted.
CLI:
turbotab staffing required --sla 0.80 --service-time 20 --calls-per-interval 100 --aht 180 --max-occupancy 0.85 --json
# {"...": "...", "inputs": {"max_occupancy": 0.85, "...": "..."}, "result": {"name": "agents", "unit": "agents", "value": 36}}
Python API:
from mod_turbotab.agents.capacity import (
agents_required,
occupancy,
is_within_occupancy,
)
agents_required(0.80, 20, 25, 180) # 11 (no cap)
agents_required(0.80, 20, 25, 180, max_occupancy=0.85) # 11 (already under 85%)
agents_required(0.80, 20, 100, 180, max_occupancy=0.85) # 36 (lifted from Erlang result to keep A/N <= 0.85)
occupancy(11, 25, 180) # 0.6818 (A/N)
is_within_occupancy(33, 100, 180, 0.85) # False
is_within_occupancy(36, 100, 180, 0.85) # True
The cap is max(erlang_c, ceil(A / max_occupancy)) where A = calls_per_interval * aht / interval.
Limitations
number_trunks()uses a fixed blocking threshold of0.001.- Some zero-value edge cases still return wrapped calculation errors instead of purpose-built validation messages.
- Shrinkage, absenteeism, and intraday simulation are tracked as future work — see issues labeled
roadmap.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file turbotab-0.2.0.tar.gz.
File metadata
- Download URL: turbotab-0.2.0.tar.gz
- Upload date:
- Size: 59.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8bd8b7dcf87474a7303413635931d4d01a87d14d9cf491d362a7f3a70d8b4723
|
|
| MD5 |
5afdf396f9dc8146619958cdd9fde5c8
|
|
| BLAKE2b-256 |
0e4d1d91e0ba0142e2beace636d5939991d2054e19c1292cdcedff180602cfeb
|
Provenance
The following attestation bundles were made for turbotab-0.2.0.tar.gz:
Publisher:
publish.yml on gstvbatista/mod_turbotab
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
turbotab-0.2.0.tar.gz -
Subject digest:
8bd8b7dcf87474a7303413635931d4d01a87d14d9cf491d362a7f3a70d8b4723 - Sigstore transparency entry: 2227094871
- Sigstore integration time:
-
Permalink:
gstvbatista/mod_turbotab@8ba2a6a4bae5dcea827ff4808429f3dc1841fe0a -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/gstvbatista
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8ba2a6a4bae5dcea827ff4808429f3dc1841fe0a -
Trigger Event:
release
-
Statement type:
File details
Details for the file turbotab-0.2.0-py3-none-any.whl.
File metadata
- Download URL: turbotab-0.2.0-py3-none-any.whl
- Upload date:
- Size: 44.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8361e8611b6effff2348e2b45acf952f1f90d7667fb1c91f418eadf90f27d2bf
|
|
| MD5 |
b1cfd1f3ae0f8bfbd6f4055fdb25a4a0
|
|
| BLAKE2b-256 |
846d47a979db215fc16b9341b261682cee4f2c8a20e6de9513d3706743395d87
|
Provenance
The following attestation bundles were made for turbotab-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on gstvbatista/mod_turbotab
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
turbotab-0.2.0-py3-none-any.whl -
Subject digest:
8361e8611b6effff2348e2b45acf952f1f90d7667fb1c91f418eadf90f27d2bf - Sigstore transparency entry: 2227095113
- Sigstore integration time:
-
Permalink:
gstvbatista/mod_turbotab@8ba2a6a4bae5dcea827ff4808429f3dc1841fe0a -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/gstvbatista
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8ba2a6a4bae5dcea827ff4808429f3dc1841fe0a -
Trigger Event:
release
-
Statement type: