Files
palladium/studies/202512_GenesysCX/ctm-token-calculator/tokencalc/scenarios.py
2026-06-10 14:28:16 -04:00

150 lines
6.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Scenario definitions — Floor / Realistic / Stretch.
Every scenario parameter the cost and benefit engines read lives here;
no magic numbers in the calculation modules. Ships with the spec
defaults; callers may construct custom :class:`Scenario` objects.
"""
from __future__ import annotations
from dataclasses import dataclass, field
@dataclass
class Scenario:
name: str
# ── Cost-side drivers ───────────────────────────────────────────
voice_bot_deflection: float # share of voice volume deflected to bot
voice_bot_avg_minutes: float # bot minutes per deflected call
# Agentic VA deflection is INCREMENTAL — applied to the residual volume
# after the voice bot has already handled its share (layered model).
# Effective total deflection = bot_rate + (1 bot_rate) × va_rate.
agentic_va_deflection: float # share of RESIDUAL voice volume to agentic VA
voice_summarization_eligibility: float
voice_knowledge_eligibility: float
email_auto_respond_rate: float # share of email auto-responded
email_auto_suggest_acceptance: float
# ── Virtual Agent benefit realization factors ───────────────────
# Applied to both Voice Bot and Agentic VA deflection benefits.
# completion_rate — share of "deflected" calls that don't escalate to an agent
# mid-session (bot/VA fully handles the interaction).
# labour_realization — staffing flexibility: deflected volume doesn't reduce
# headcount 1:1 due to minimums, shrinkage, occupancy ceilings.
# callback_discount — fraction of deflected calls that re-enter as repeat contacts
# (poorly-handled deflections drive callbacks).
# Combined realistic factor: 0.70 × 0.80 × (1 0.05) ≈ 0.53
va_completion_rate: float = 0.70
va_labour_realization: float = 0.80
va_callback_discount: float = 0.05
# year -> fraction of full benefit realized
benefit_realization: dict[int, float] = field(default_factory=dict)
# year -> fraction of steady-state consumption cost incurred.
# Per-user licenses are paid in full from day 1; consumption meters
# ramp with usage (default Y1 = 70%).
consumption_cost_realization: dict[int, float] = field(
default_factory=lambda: {1: 0.70, 2: 1.0, 3: 1.0}
)
def realization(self, year: int) -> float:
if year in self.benefit_realization:
return self.benefit_realization[year]
last = max(self.benefit_realization, default=0)
return self.benefit_realization.get(last, 1.0) if year > last else 0.0
def cost_realization(self, year: int) -> float:
if year in self.consumption_cost_realization:
return self.consumption_cost_realization[year]
last = max(self.consumption_cost_realization, default=0)
return (
self.consumption_cost_realization.get(last, 1.0) if year > last else 0.0
)
#: Benefit reduction parameters. ``claim`` = Genesys ROI-doc figure;
#: ``realistic`` = pressure-tested midpoint of the spec's Y1 range.
#: The benefit engine uses ``realistic`` by default; ``claim`` powers
#: the side-by-side comparison view.
BENEFIT_PARAMS: dict[str, dict[str, float]] = {
"voice_aht_knowledge_reduction": {"claim": 0.094, "realistic": 0.055}, # 4-7% Y1
"voice_acw_reduction": {"claim": 1.00, "realistic": 0.40}, # 30-50% Y1
"digital_aht_reduction": {"claim": 0.18, "realistic": 0.085}, # 5-12% Y1
"digital_acw_reduction": {"claim": 1.00, "realistic": 0.40}, # 30-50% Y1
"sta_aht_reduction": {"claim": 0.04, "realistic": 0.015}, # 1-2% Y1
"email_auto_suggest_time_saving": {"claim": 0.40, "realistic": 0.30}, # × acceptance; Genesys claims 40%
# ESTIMATED lines (no Genesys claim published):
"supervisor_copilot_time_saving": {"claim": 0.10, "realistic": 0.05},
"predictive_routing_aht_reduction": {"claim": 0.04, "realistic": 0.02},
# Virtual Agent realization factors.
# ``claim`` = 100% realization (original model assumption — no haircuts).
# ``realistic`` = production-calibrated midpoints per the spec analysis.
"va_completion_rate": {"claim": 1.00, "realistic": 0.70}, # 60-75% voice bot; 50-70% agentic VA Y1
"va_labour_realization": {"claim": 1.00, "realistic": 0.80}, # 70-85% staffing flexibility
"va_callback_discount": {"claim": 0.00, "realistic": 0.05}, # 5-10% deflected re-enter as repeat contacts
}
SCENARIOS: dict[str, Scenario] = {
"floor": Scenario(
name="floor",
voice_bot_deflection=0.20,
voice_bot_avg_minutes=1.0,
agentic_va_deflection=0.05,
voice_summarization_eligibility=0.50,
voice_knowledge_eligibility=0.40,
email_auto_respond_rate=0.10,
email_auto_suggest_acceptance=0.25,
# VA realization: conservative — low completion, limited staffing flex
# Combined: 0.60 × 0.70 × (1 0.05) ≈ 0.40
va_completion_rate=0.60,
va_labour_realization=0.70,
va_callback_discount=0.05,
benefit_realization={1: 0.30, 2: 0.60, 3: 0.80},
),
"realistic": Scenario(
name="realistic",
voice_bot_deflection=0.35,
voice_bot_avg_minutes=1.5,
agentic_va_deflection=0.15,
voice_summarization_eligibility=0.70,
voice_knowledge_eligibility=0.60,
email_auto_respond_rate=0.20,
email_auto_suggest_acceptance=0.40,
# VA realization: production midpoints per spec analysis
# Combined: 0.70 × 0.80 × (1 0.05) ≈ 0.53
va_completion_rate=0.70,
va_labour_realization=0.80,
va_callback_discount=0.05,
benefit_realization={1: 0.50, 2: 0.80, 3: 0.95},
),
"stretch": Scenario(
name="stretch",
voice_bot_deflection=0.50,
voice_bot_avg_minutes=2.0,
agentic_va_deflection=0.25,
voice_summarization_eligibility=0.90,
voice_knowledge_eligibility=0.80,
email_auto_respond_rate=0.50,
email_auto_suggest_acceptance=0.60,
# VA realization: optimistic — high completion, good staffing flexibility
# Combined: 0.75 × 0.85 × (1 0.03) ≈ 0.62
va_completion_rate=0.75,
va_labour_realization=0.85,
va_callback_discount=0.03,
benefit_realization={1: 0.75, 2: 0.95, 3: 1.00},
),
}
def get_scenario(name: str) -> Scenario:
try:
return SCENARIOS[name.lower()]
except KeyError as e:
raise KeyError(
f"Unknown scenario {name!r}. Valid: {sorted(SCENARIOS)}"
) from e