Wiki:Packs/Dedicated Pool Performance
| Pack | |
|---|---|
| ID | CP-WFM-020
|
| Name | Dedicated Pool Performance |
| Domain | WFM |
| Blocks | 1 instruction + 8 reference |
| Version | 1.0 |
| Source | Fixed-Capacity Service Models · Power of One · Erlang Sensitivity and the Staffing Cliff · The Flaw of Averages · Erlang C · Erlang-A · Shrinkage · Pooling Architecture in Service Workforces |
A deployable Claude project setup for explaining why a dedicated, fixed-size pool of agents misses its service level, and how steeply. The client buys a fixed number of FTE; demand moves with the season; nothing flexes. The pack reproduces the client's own capacity plan, attributes each month's shortfall to supply delivery, the size of the pool and the forecast drivers, and measures the power of one: what a single FTE, a point of shrinkage or a percent of volume is worth in service level in that pool, in that month. It produces three things for the client conversation: an Excel workbook, a standalone interactive HTML explorer and a short deck.
The concepts are set out in Fixed-Capacity Service Models. Copy Block 1 into a project's custom instructions and save the remaining blocks as files uploaded as project knowledge. See Wiki:Packs for how packs work.
When to use it

Use this pack when a client on a dedicated or fixed-fee pool asks why service level is below target, why it varies month to month, or why one absence seems to matter so much. It fits any pool sized once and held flat (a dedicated team, a fixed outsourced seat count, an in-house team with a frozen headcount) serving phone work alone or phone plus deferred work such as email or cases. The usual input is the monthly capacity review the provider already shares: FTE available and required, forecast and actual volume, handle time and shrinkage.
It is not a forecasting method, and it does not schedule. It explains a plan, it does not replace one. For staffing as a probability distribution see Wiki:Packs/Probabilistic Staffing; for decomposing volume variance itself see Wiki:Packs/Demand Variance Decomposition.
How to deploy
- Create a Claude project named for the client conversation, not for the method.
- Copy Block 1 into the project's custom instructions.
- Save Blocks 2–9 under the filenames in their headings and upload them as project knowledge. Blocks 5, 6 and 9 (the model, the explorer and the synthetic inputs) are on Wiki:Packs/Dedicated Pool Performance/Modules.
- In a conversation, upload the client's capacity workbook and any notes on what the client has been told so far. Calibrate to the plan first, then diagnose, then build the explorer and the deck.
Block 1 — project-instructions.md
# Project instructions — Dedicated Pool Performance
## Context
This project explains **why a dedicated, fixed-size pool of agents misses its service level** and
how sensitive that miss is. The client buys a fixed number of FTE. Demand moves with the season.
There is no contractual flex. Service level is therefore a consequence of the purchase, not a
target the operation can hold, and the client usually does not see how steep that consequence is.
Three outputs per engagement: a **monthly diagnosis** (requirement against FTE held, and why each
miss happened), a **sensitivity explorer** (a standalone HTML file, the power of one) and a
**short deck** (four to six slides).
## Routing
| When the task is | Open |
|---|---|
| Why a fixed pool misses; the model; Erlang C and A; shrinkage conventions | `02-method.md` |
| Reading the client workbook, the parameters file, calibration, gaps | `03-intake-and-calibration.md` |
| Attributing a miss, the power of one, the readout | `04-diagnosis-and-sensitivity.md` |
| Running the model; the synthetic inputs | `dpp.py`, `example-params.json` |
| Building the HTML explorer | `explorer.html` |
| The deck | `07-deck-spec.md` |
| The whole flow on synthetic data | `08-worked-example.md` |
## Sequence
1. **Read the workbook before modelling.** Describe each tab, map rows to the parameters file and
list what is missing: hours, target, contract FTE, handle time for deferred work, profile.
2. **Calibrate to the client's own plan.** Reproduce the plan's required-FTE row with
`--calibrate`. Report the fitted deferred handle time, the shrinkage convention and the
residual per month. Do not explain a requirement the model cannot reproduce.
3. **Diagnose.** Run both bases (forecast, and actual where it exists), attribute each month's gap
and compute the power of one for a peak and a trough month.
4. **Build the explorer.** Replace only the `DATA` block in `explorer.html`. Change no model code.
5. **Build the deck** to `07-deck-spec.md`, every number traced to a model output.
## Disciplines
- Shrinkage is applied once, on the supply side. Name the convention: `net` or `gross_up`.
- Every default applied to a client is `[estimated]` until the client or the plan confirms it.
The intraday profile and caller patience are the usual two.
- The shortage has to land somewhere: phones, deferred work, or both. State which policy was
modelled, and never show a service level without the deferred backlog beside it.
- Under Erlang A, quote abandonment with every service level.
- Report our own misses: FTE held below contract is a supply-delivery line, shown first.
- Never tune an input to reach an answer. Name the input that is wrong.
- No client name, figure or file leaves this project.
## Output
Lead with the answer in one sentence: how many months the pool is short, by how much at peak,
and what one FTE is worth in the peak month. Then the evidence. Hand back the Excel workbook, the
HTML explorer and the deck.
Source: Wiki:Packs/Dedicated Pool Performance (CP-WFM-020) v1.0
Block 2 — 02-method.md
# 02 — Method: why a fixed pool misses
## The commercial shape
A **dedicated pool** is a fixed number of FTE serving one client's work, paid as a fixed fee or a
dedicated cost line. The provider forecasts volume, handle time and shrinkage, and reports
transparently against them, but it cannot add heads when demand rises. The pool's size is decided
once, usually against an annual average. Demand is not average in any single month.
Under a transactional or shared-pool model, staffing follows the forecast and service level is the
target. Under a fixed pool the FTE is the target, so **service level becomes the output**: what is
left over after a fixed supply meets a moving demand.
## The model
For each month, three quantities:
**1. Requirement.** Phone volume is spread across the open hours with an intraday profile
(arrivals per interval). Each interval is sized on Erlang C to the smallest whole number of
agents that meets the service-level target and the occupancy cap. Interval agents × interval
length × business days gives phone hours. Deferred work (email, cases, back office) is sized as
workload: volume × handle time ÷ deferred occupancy. Total hours ÷ productive hours per FTE gives
**required FTE**.
**2. Supply.** FTE held × paid hours × (1 − shrinkage) = productive hours. Shrinkage is applied
**once**, here. Two conventions exist:
| Convention | Productive hours per FTE | Note |
|---|---|---|
| `net` | paid × (1 − s) | Correct: s is the share of paid time lost |
| `gross_up` | paid ÷ (1 + s) | Common in planning sheets; understates the requirement as s rises |
At 35% shrinkage the gross-up books 0.65 × 1.35 = 0.88 of the requirement the net form books,
an understatement of about 12%. At 20% it is about 4%. Calibration (`03`) identifies which one
the client's plan uses. Reproduce the plan in its own convention, then say what the net form
would show.
**3. Delivery.** If productive hours fall short of the requirement, the shortage has to land
somewhere. The `policy` decides where:
| Policy | Phones get | Deferred work gets |
|---|---|---|
| `proportional` (default) | their share of the hours held | their share |
| `phones_first` | everything they need | what is left (backlog grows) |
| `email_first` | what is left | everything it needs |
Phone hours held are spread across intervals in proportion to the smooth (fractional) requirement
curve. Service level per interval comes from Erlang C, or Erlang A when callers abandon, with
fractional agents interpolated between whole numbers. Monthly service level is the call-weighted
average of the intervals.
The split runs on the smooth curve, not the whole-agent requirement, so that one more call or one
more second of handle time can never improve service. Without that, a one-agent step in some
interval's requirement reshuffles the split and the sensitivity curves wobble.
## Why the miss is steep: four mechanisms
**Seasonality against a fixed line.** Requirement moves month to month by ±15–25% in a typical
seasonal book. A pool fixed at any single level is short in some months and long in others.
**Surplus cannot be banked.** A long month delivers service above target, but it cannot be
carried into a short month. Held flat at the mean requirement, the call-weighted annual service
level can still come out near or above target while several individual months fail. A monthly
service-level measure fails by construction. See *The Flaw of Averages*.
**Small pools are steep.** A pool of 15–40 FTE puts perhaps 4–9 agents on the phones in any
interval. At that size each agent is a large fraction of capacity, and service level moves 4–7
points per FTE near target, more below it. That is the **power of one**: one FTE of vacancy, one
person sick, or a 1% volume rise is visible in the month's result.
**Long handle times make it steeper.** With handle times of 15–30 minutes, a single long call
ties up a meaningful share of the pool, and a short answer threshold (20–30 seconds) is met only
if an agent is idle when the call arrives. Phone occupancy at target is often only 50–70%. The
idle phone time is real, and it is the basis of the blending lever.
## Erlang C and Erlang A
Erlang C assumes nobody hangs up. Under overload it reports collapsing service levels that real
queues do not show, because real callers abandon, and that shortens the queue for everyone else.
Erlang A (M/M/N+M) adds an exponential patience. Service level is then the share of **all
arrivals** answered within the threshold, and abandonment is reported beside it.
Under Erlang A a shortage shows as a **modest service-level miss plus abandonment** rather than a
service-level collapse. Both describe the same shortfall. A client who sees 80% service level and
10% abandonment has met target on paper while one caller in ten gave up. Patience is `[estimated]` until it is fitted to
actual abandonment (`--fit-patience`).
Sizing uses Erlang C in both cases, because the client's plan almost always does.
## What the model does not do
- One profile for every business day; no day-of-week or within-month shape unless supplied.
- No roster: hours held are spread ideally across intervals, so every service level is a
**ceiling**. Real schedules leave gaps and do worse.
- Deferred work is a workload, not a queue: the backlog is the hours not worked, not a turnaround
distribution.
- No sharing with other pools. If overflow exists, the result understates delivery.
- **Required FTE and the FTE that meets target differ.** Required FTE is sized in whole agents
per interval, the convention plans use; delivery spreads hours ideally. So a month can be
short of its requirement yet still meet target, and the FTE that just meets target sits up to
about one FTE below the stated requirement. A real roster lands between the two.
Block 3 — 03-intake-and-calibration.md
# 03 — Intake and calibration
## The usual source: a monthly capacity review
Most dedicated-pool clients are reported through a monthly capacity sheet: one column per month,
one row per measure, with forecast and actual pairs. Typical row labels and where they go:
| Workbook row (typical label) | Parameters field | Notes |
|---|---|---|
| FTE Available / FTE Held | `months[i].fte_available` | Heads on the roster, before shrinkage |
| FTE Requirement | `months[i].fte_required_plan` | The plan's own answer; calibration target, not an input |
| Net Over/Under | — | Check: Available − Requirement; flag any month where it does not reconcile |
| Forecast / Actual Telephony | `phone.fc` / `phone.act` | Offered calls, not handled |
| AHT Forecast / Actual | `aht.fc` / `aht.act` | Seconds, talk + hold + wrap |
| Forecast / Actual Email (or cases) | `email.fc` / `email.act` | Deferred work units |
| Forecast / Actual Shrinkage | `shrink.fc` / `shrink.act` | As a fraction (0.26, not 26) |
| Actual SL / TSF, Abandon rate | `sl_act`, `aban_act` | Optional; `aban_act` fits patience |
| Var to forecast rows | — | Recomputed; never imported |
Notes boxes often carry the contract terms. Read them: service-level target and threshold (and
any change to it during the year), hours of operation, days, contracted FTE, and anything about
the client's appetite to add FTE.
**Zero is not an actual.** A zero in a future month's actual row means "not yet". Import it as
`null`. A real zero (a closed month) is rare and needs confirming.
## The parameters file
```json
{
"label": "short name, never a client name in anything that leaves the project",
"year": 2026,
"contract_fte": 22,
"service": {"target_sl": 0.80, "answer_s": 30, "model": "C",
"patience_s": 180, "occupancy_cap": 0.95},
"hours": {"open_hours_per_day": 12, "days_per_week": 5, "interval_min": 30,
"paid_hours_per_fte_day": 8, "profile": null},
"email": {"handle_s": 600, "occupancy": 0.85, "blend_credit": 0.0},
"policy": "proportional",
"shrink_mode": "net",
"months": [
{"month": "Jan", "fte_available": 21.6, "fte_required_plan": 24.8,
"phone": {"fc": 4668, "act": 5089}, "aht": {"fc": 720, "act": 760},
"email": {"fc": 10195, "act": 9788}, "shrink": {"fc": 0.30, "act": 0.29},
"aban_act": 0.10}
]
}
```
- `profile` is the share of a day's calls in each interval, open to close. `null` uses a generic
single-hump shape, which is `[estimated]`. Ask for the client's interval arrival pattern: it
moves the phone requirement by up to 1–2 FTE on a long day.
- `business_days` defaults to the weekdays in the month (`days_per_week: 5` = Mon–Fri).
- `blend_credit` is the share of idle phone time credited to deferred work. Keep it at 0 to
reproduce a plan that sizes channels separately. It is a lever, not a fact.
- `fte_available` missing → the contract FTE is used for that month.
## Gap register
Open one table and keep it current. Every row has a status: `client`, `plan`, `fitted` or
`[estimated]`.
| Input | Usual source | If missing |
|---|---|---|
| Service-level target and threshold | Contract or notes | Ask. Never assume 80/20 |
| Hours and days | Contract or notes | Ask |
| Contract FTE | Contract | Ask; FTE available is not the contract |
| Deferred handle time | Plan | **Fit it** (`--calibrate`) |
| Shrinkage convention | Plan | **Fit it** (`--calibrate`) |
| Intraday profile | ACD interval report | Default hump, `[estimated]` |
| Caller patience | Abandon rate by month | **Fit it** (`--fit-patience`); else 180 s `[estimated]` |
| Deferred occupancy | Plan | 0.85 `[estimated]` (folded into the fitted handle time) |
## Calibration
```
python dpp.py params.json --calibrate
```
The command fits the deferred handle time so the model's forecast-basis requirement matches the
plan's `fte_required_plan` row, once under each shrinkage convention, and keeps the better one.
It prints both fits and the residual per month.
Read the result:
- **RMSE under ~0.6 FTE**: the model reproduces the plan. Proceed.
- **One convention clearly better**: that is the plan's convention. If it is `gross_up`, say
so, and show the net-convention requirement beside it. The gross-up understates the
requirement, most in high-shrinkage months.
- **Residuals that track a driver** (high in low-volume months, for instance) point to a
profile, interval length or minimum-staffing rule the plan applies. Ask rather than add
parameters.
- **RMSE above ~1.5 FTE**: the plan is using something the model does not. Find it before
diagnosing anything.
The fitted handle time absorbs deferred occupancy and any non-contact allowance the plan carries.
Do not quote it as a measured handle time.
```
python dpp.py params.json --calibrate --fit-patience --xlsx results.xlsx
```
`--fit-patience` switches to Erlang A and fits patience so modelled abandonment matches the actual
abandonment over the months that carry it, at actual drivers and actual FTE held.
Block 4 — 04-diagnosis-and-sensitivity.md
# 04 — Diagnosis and sensitivity
## Two bases
- **Forecast basis**: forecast drivers, FTE held. What the plan said would happen, which is
usually already a miss in the peak months.
- **Actual basis**: actual drivers where they exist (forecast otherwise), FTE held. What the
month really required.
Show both. A client who sees only actuals blames the forecast; a client who sees only the
forecast does not see what happened.
## Attributing a month's gap
`attribution()` splits the actual-basis gap (FTE held − FTE required at actuals) into three terms
that sum exactly:
| Term | Meaning | Whose |
|---|---|---|
| `supply_delivery` | FTE held − contract FTE | The provider's: vacancy, attrition, hiring lag |
| `structural` | contract FTE − requirement at forecast | The purchase: the pool is the wrong size for this month |
| `miss_phone`, `miss_aht`, `miss_email`, `miss_shrink` | requirement at forecast − requirement at actual, split by driver | The forecast, by driver |
The driver split uses **exact Shapley values**: each driver's contribution is averaged over every
order in which the drivers could be switched from forecast to actual. Because the requirement is
not linear in its drivers (volume and handle time multiply), a sequential "volume first, then
AHT" split would give a different answer for each order. The Shapley split does not depend on
order, and its terms sum to the total.
Present the three terms in that order: our supply first, then the structure, then the forecast.
Where the structural term is the largest, say so: **the pool was the wrong size for this month
before anything went wrong.** Where supply delivery or a forecast driver is larger, say that
instead.
Sign convention: negative = short. A positive `miss_email` means deferred volume came in below
forecast and gave FTE back.
## The power of one
For any month, `power_of_one()` gives service level at the FTE held ±N whole FTE.
`elasticities()` gives the points moved by:
| Lever | Size in the short months of `08-worked-example.md`'s 22-FTE example |
|---|---|
| +1 FTE | +5.3 to +6.6 points |
| −1 FTE | −4.8 to −7.7 points |
| +1% volume (phone and deferred) | −0.8 to −1.35 points |
| +10% volume (phone and deferred) | −9 to −15 points |
| +1% handle time | −0.4 to −0.7 points |
| +1 point of shrinkage | −1.3 to −2.2 points |
In the deep-short months losing an FTE costs more than gaining one returns; near target the
curve can run the other way. The **long months
are flat**: at 95% service level, one FTE is worth 1–2 points. Show a peak and a trough month
side by side; the contrast carries the argument.
Translate the elasticities into the client's terms:
- One FTE of vacancy in a peak month ≈ P points; compare it with the miss in question.
- One point of shrinkage (about four person-days in the month, e.g. one person's sick week) ≈
2 points in a peak month.
- The volume variance the client sees in their own business, ±10%, ≈ ±10–15 points.
## The readout
One sentence first:
> Held at **F** FTE against a requirement that ranges from **R_min** to **R_max**, the pool is
> short in **k** of 12 months, by up to **G** FTE in **Month**. In that month each FTE is worth
> **P** points of service level, and the gap is **mostly structural** (or: mostly supply / mostly
> forecast).
Then:
1. **Supply against demand by month**: the chart that makes the seasonality visible.
2. **What drove each miss**: the attribution table for months with actuals.
3. **The power of one**: SL against FTE for the peak month, with the target line.
4. **Options**, priced in FTE-months or points, never recommended without the client's numbers:
a seasonal flex band or banked hours; a seasonal or annual service-level target; overflow to
a shared pool; blending deferred work into phone idle time (`blend_credit`); hours of
operation or channel shift; or holding the pool and accepting the delivered service level,
which is a legitimate choice once it is a known one.
## Holding the pool flat at the requirement
Two reference points belong on the options slide:
- **Flat at the mean requirement**: the annual service level, and how many months still fail.
- **Flat at the maximum requirement**: what it costs to hit target every month, in extra FTE ×
12, and the service level that buys in the trough months (usually 95%+, unused).
The gap between the two is the price of seasonality under a fixed pool.
## Challenges you will hear
| Challenge | Answer |
|---|---|
| "Your forecast was wrong." | Show the structural term. Even at a perfect forecast, the month was short by that much. |
| "You are not staffed to contract." | Agree, and show `supply_delivery` first. Then show it is the smaller term (if it is). |
| "Service level was fine last year." | Compare the two years' requirement. Volume, handle time or shrinkage moved; name which. |
| "Just work harder in the peaks." | Occupancy at target is already set by the queue maths. Pushing it trades service level for abandonment. |
| "Why does one person matter so much?" | Show the power-of-one curve. That is the size of the pool. |
Block 7 — 07-deck-spec.md
# 07 — Deck specification
A short client deck: **five slides and one appendix slide**. It explains, it does not defend.
Build it with `python-pptx` (native, editable charts, not pictures of charts) or the
presentation skill available in the project. If the user uploads a branded template, use its
layouts and colours. Otherwise use a plain white layout with one dark and one accent colour.
## Rules
- **Every number traces to a model output** (`results.xlsx` sheet and cell, or a named
function). Keep a trace table in the conversation; do not put it on the slides.
- **Titles are sentences that state the finding**, not topic labels. "Requirement exceeds the
pool in 8 of 12 months", not "Capacity review".
- **Our misses first.** If FTE held is below contract, the attribution slide shows it before
the forecast terms.
- **No blame language** ("the client failed to…", "despite our efforts"). The arithmetic carries
the argument.
- **Service level is never shown alone** under Erlang A (abandonment beside it) or under a
shortage (deferred backlog beside it).
- Assumptions marked `[estimated]` appear in the appendix, not as footnotes on the main slides.
- Plain numbers: FTE to one decimal, service level to whole points, volumes rounded to the
nearest 10.
## Slides
**1. The answer.** Title: the one-sentence readout from `04` (months short, peak gap, worth of
one FTE). Body: three numbers in large type: contract FTE, requirement range (min–max), months
below target at the FTE held. One line on what the pool is: hours, target, channels.
**2. Supply against demand.** Title example: "A fixed pool of 22 FTE meets a requirement that
runs from 18 to 26". Chart: clustered columns of required FTE by month (forecast basis), a line
for FTE held and a dashed line for contract FTE. Shade or colour the short months. Mark actual-
basis requirement as a dot where it exists. One callout on the peak month.
**3. What service level that buys.** Title example: "Service level follows the season: 63% in
the February peak, 96% in July". Chart: projected service level by month at the FTE held, with
the target line. Add abandonment as a second series under Erlang A. Footnote the policy if the
deferred backlog is material.
**4. What drove the misses.** Months with actuals only. Stacked bars per month: supply delivery,
structural, then the four forecast drivers (phone volume, handle time, deferred volume,
shrinkage). Negative = short. Title states the dominant term: "Most of each month's gap was
built in before the month started".
**5. The power of one.** Left: service level against FTE (held −4 to +4) for the peak month and
a trough month, target line drawn. Right: the elasticity table for the peak month (+1 FTE, −1
FTE, +1% volume, +10% volume (phone and deferred together), +1% handle time, +1 point shrinkage). Title example: "In a peak
month, one FTE is worth six points of service level". Note that the interactive explorer
accompanies the deck.
**6. Options.** A table, not a recommendation, unless the user asks for one: a seasonal flex
band; flat at the maximum requirement (cost in FTE-months, trough SL); a seasonal or annual SL
target; overflow; blending deferred work into phone idle time; hours or channel changes; hold
the pool and accept the delivered service level. Columns: what changes, FTE-months per year,
effect on peak-month SL, what the client would need to decide.
**Appendix — method and assumptions.** Erlang C sizing, Erlang C or A delivery, interval
profile, shrinkage convention, policy, calibration result (RMSE, fitted handle time, patience),
and the gap register rows still `[estimated]`.
## Speaker notes
Write two to four sentences per slide: what to say, and the one question the slide is likely to
draw, with its answer from `04`'s challenge table.
## Hand-back
Return the `.pptx`, the HTML explorer and `results.xlsx` together. Name them for the
engagement, not the method.
Block 8 — 08-worked-example.md
# 08 — Worked example (synthetic)
Every figure here comes from `dpp.py` run on the synthetic parameters embedded as the default
`DATA` in `explorer.html` (and reproduced by `python dpp.py example-params.json`). They are
properties of the method on an invented pool, not benchmarks.
## The pool
22 contracted FTE. Open 12 hours a day, Monday to Friday. Target 80% of calls in 20 seconds.
Phone handle time 720 s forecast. Deferred cases at a 420 s handle time (set, not fitted) and 85%
occupancy. Shrinkage 29–38% by month, `net` convention. Policy `proportional`. Sized on
Erlang C. FTE held runs 20.4–21.6, slightly under contract all year.
## Requirement against supply (forecast basis)
| Month | Required FTE | Held | Gap | Service level |
|---|---|---|---|---|
| Jan | 24.8 | 21.6 | −3.2 | 69% |
| Feb | 25.5 | 21.4 | −4.1 | 63% |
| Mar | 24.8 | 21.0 | −3.8 | 65% |
| Apr | 22.4 | 21.2 | −1.2 | 79% |
| May | 22.7 | 21.5 | −1.2 | 80% |
| Jun | 20.7 | 21.0 | +0.3 | 88% |
| Jul | 18.5 | 21.3 | +2.8 | 96% |
| Aug | 18.8 | 21.1 | +2.3 | 95% |
| Sep | 23.2 | 20.4 | −2.8 | 70% |
| Oct | 24.4 | 21.0 | −3.4 | 67% |
| Nov | 22.9 | 21.4 | −1.5 | 78% |
| Dec | 18.2 | 21.5 | +3.3 | 97% |
Mean requirement 22.2 FTE, almost exactly the contract. Maximum 25.5. The pool is short in
8 months. Required FTE is sized in whole agents per interval while delivery assumes an ideal
roster, so April and May are short of requirement yet land within a point of target, and
February meets 80% at about 24.6 FTE rather than the 25.5 required. Call-weighted annual service level at the FTE held: **77.1%**.
**Sized to the average.** Held flat at 22.2 FTE every month, annual service level is 82.3%,
above target, yet **5 of 12 months still fall below 80%**. Under a monthly measure, a pool sized
to its average requirement fails by construction.
**Sized to the peak.** Held flat at 25.5 FTE, every month meets target (the lowest is 85.5%) and
annual service level is 94.1%. That costs 3.3 FTE more than the mean, all year, and buys service
in July, August and December that nobody asked for.
## What drove the misses (months with actuals)
| Month | Gap at actual | Supply delivery | Structural | Phone vol. | Handle time | Deferred vol. | Shrinkage |
|---|---|---|---|---|---|---|---|
| Jan | −3.8 | −0.4 | −2.8 | −0.9 | −0.5 | +0.5 | +0.4 |
| Feb | −4.9 | −0.6 | −3.5 | −0.5 | −0.4 | +0.1 | −0.0 |
| Mar | −7.3 | −1.0 | −2.8 | −1.4 | −1.1 | −0.2 | −0.8 |
| Apr | −2.9 | −0.8 | −0.4 | +0.2 | −0.4 | −0.5 | −1.0 |
| May | −4.3 | −0.5 | −0.7 | −0.6 | −1.1 | −0.8 | −0.7 |
| Jun | −2.2 | −1.0 | +1.3 | +0.2 | −0.4 | −1.0 | −1.4 |
Reading: in the first quarter the **structural** term dominates. The pool was the wrong size for
those months before any forecast missed. From April the forecast misses take over, led by
shrinkage, deferred volume and, in May, handle time, and supply delivery (0.4–1.0 FTE under contract) is a steady
third. Each row sums exactly; the driver split is Shapley, so it does not depend on order.
## The power of one (February)
| FTE held | 17.4 | 18.4 | 19.4 | 20.4 | **21.4** | 22.4 | 23.4 | 24.4 | 25.4 |
|---|---|---|---|---|---|---|---|---|---|
| Service level | 28% | 38% | 47% | 55% | **63%** | 69% | 74% | 79% | 85% |
| Lever | Feb (63% SL) | Jul (96% SL) |
|---|---|---|
| +1 FTE | +6.1 pts | +1.4 pts |
| +1% volume (phone and deferred) | −1.35 pts | −0.29 pts |
| +1% handle time | −0.67 pts | −0.16 pts |
| +1 pt shrinkage | −2.15 pts | −0.63 pts |
At peak, about 7 agents are on the phones in the busiest interval and phone occupancy is about 70%.
One FTE moves February by six points and July by one and a half. That contrast is the slide.
## Where the shortage lands (February, 21.4 FTE)
| Policy | Phone SL | Deferred cases not worked in month |
|---|---|---|
| `proportional` | 63% | 1,225 of 9,404 |
| `phones_first` | 80% | 2,578 |
| `email_first` | 39% | 0 |
The same 4.1 FTE shortfall, three ways. Phones can be protected only by growing the backlog.
## Erlang A
With callers abandoning (patience 150 s), the forecast-basis months read: February 81% service
level with 11% abandonment, January 83% / 10%, July 97% / 1.5%. Annual 87.4%. The shortage has
not gone away: it shows up as callers who hang up rather than as a collapsed service level.
Fitting patience to this example's synthetic abandonment row (`--fit-patience`) returns 193 s.
## Levers, briefly
- **Blend 50% of phone idle time into deferred work**: requirement falls 2.3–2.5 FTE a month
(February 25.5 → 22.9), annual SL 87.7% at the same FTE held. The largest lever, and an
operating change, not a contract change. It needs the routing to support it.
- **Gross-up shrinkage convention**: the same inputs book February at 23.0 FTE instead of 25.5.
A plan using it would understate this pool's peak requirement by about 2.5 FTE.
Blocks carrying the code
- Block 5 —
dpp.py - Block 6 —
explorer.html - Block 9 —
example-params.json
These blocks are carried on Wiki:Packs/Dedicated Pool Performance/Modules, which keeps this page a readable length and each block inside the size at which the wiki's syntax highlighting renders. Save them under the filenames in their headings and upload them with the blocks above.
Usage notes
The explorer. Block 6 is a single self-contained HTML file: no external scripts, no network, so it opens from a laptop or an email attachment. Claude replaces only its marked DATA object with the client's parameters. The model code inside it is a port of Block 5, checked against it to within 0.01 FTE and 0.2 points of service level. Any earlier sensitivity file a user uploads is a style reference, not a substitute.
The model's limits are stated in the blocks and should not be edited out. Hours held are spread ideally across intervals, so every service level is a ceiling a real roster will not quite reach. The intraday profile and caller patience are estimates until the client supplies or the data fits them. Deferred work is sized as workload, not as a queue.
Synthetic data. The worked figures in Blocks 4 and 8 come from the synthetic pool in Block 9, generated by the code itself; the ranges in Block 2 are illustrative. None of them is a benchmark.
Drift. The blocks are derived from the pages in the Source field. A material change to any of them is a trigger to regenerate the blocks and increment the version.
Change history
| Version | Date | Change |
|---|---|---|
| 1.0 | 2026-10-01 | Initial publication. |
See also
- Wiki:Packs — the pack index
- Fixed-Capacity Service Models — the concepts this pack implements
- Power of One — the marginal value of one agent
- Erlang Sensitivity and the Staffing Cliff — why small staffing losses cost so much service
- The Flaw of Averages — why sizing to the average fails the months
- Pooling Architecture in Service Workforces — why small pools serve worse at the same occupancy
- Wiki:Packs/Probabilistic Staffing — staffing as a distribution
- Wiki:Packs/Demand Variance Decomposition — decomposing the volume variance itself
