""" 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