diff --git a/studies/202512_GenesysCX/ctm-token-calculator/docs/Appendix 4 - CCaaS Platform Benefit Calculations (ASIA_Conservative).PDF b/studies/202512_GenesysCX/ctm-token-calculator/docs/Appendix 4 - CCaaS Platform Benefit Calculations (ASIA_Conservative).PDF
new file mode 100644
index 0000000..b907845
Binary files /dev/null and b/studies/202512_GenesysCX/ctm-token-calculator/docs/Appendix 4 - CCaaS Platform Benefit Calculations (ASIA_Conservative).PDF differ
diff --git a/studies/202512_GenesysCX/ctm-token-calculator/docs/Appendix 4 - CCaaS Platform Benefit Calculations (AnZ_Conservative).PDF b/studies/202512_GenesysCX/ctm-token-calculator/docs/Appendix 4 - CCaaS Platform Benefit Calculations (AnZ_Conservative).PDF
new file mode 100644
index 0000000..2e079ff
Binary files /dev/null and b/studies/202512_GenesysCX/ctm-token-calculator/docs/Appendix 4 - CCaaS Platform Benefit Calculations (AnZ_Conservative).PDF differ
diff --git a/studies/202512_GenesysCX/ctm-token-calculator/docs/Appendix 4 - CCaaS Platform Benefit Calculations (Consolidated).pptx b/studies/202512_GenesysCX/ctm-token-calculator/docs/Appendix 4 - CCaaS Platform Benefit Calculations (Consolidated).pptx
new file mode 100644
index 0000000..76ea5f8
Binary files /dev/null and b/studies/202512_GenesysCX/ctm-token-calculator/docs/Appendix 4 - CCaaS Platform Benefit Calculations (Consolidated).pptx differ
diff --git a/studies/202512_GenesysCX/ctm-token-calculator/docs/Appendix 4 - CCaaS Platform Benefit Calculations (EMEA_Conservative).PDF b/studies/202512_GenesysCX/ctm-token-calculator/docs/Appendix 4 - CCaaS Platform Benefit Calculations (EMEA_Conservative).PDF
new file mode 100644
index 0000000..eef0e49
Binary files /dev/null and b/studies/202512_GenesysCX/ctm-token-calculator/docs/Appendix 4 - CCaaS Platform Benefit Calculations (EMEA_Conservative).PDF differ
diff --git a/studies/202512_GenesysCX/ctm-token-calculator/docs/Appendix 4 - CCaaS Platform Benefit Calculations (NA_Conservative).PDF b/studies/202512_GenesysCX/ctm-token-calculator/docs/Appendix 4 - CCaaS Platform Benefit Calculations (NA_Conservative).PDF
new file mode 100644
index 0000000..458ca8b
Binary files /dev/null and b/studies/202512_GenesysCX/ctm-token-calculator/docs/Appendix 4 - CCaaS Platform Benefit Calculations (NA_Conservative).PDF differ
diff --git a/studies/202512_GenesysCX/ctm-token-calculator/docs/Genesys Solutions - CTM ROI - ASIA - Conservative - v.1.4 (1).PDF b/studies/202512_GenesysCX/ctm-token-calculator/docs/Genesys Solutions - CTM ROI - ASIA - Conservative - v.1.4 (1).PDF
new file mode 100644
index 0000000..958ef08
Binary files /dev/null and b/studies/202512_GenesysCX/ctm-token-calculator/docs/Genesys Solutions - CTM ROI - ASIA - Conservative - v.1.4 (1).PDF differ
diff --git a/studies/202512_GenesysCX/ctm-token-calculator/docs/Genesys Solutions - CTM ROI - AnZ - Conservative - v.1.4 (1).PDF b/studies/202512_GenesysCX/ctm-token-calculator/docs/Genesys Solutions - CTM ROI - AnZ - Conservative - v.1.4 (1).PDF
new file mode 100644
index 0000000..4a5911d
Binary files /dev/null and b/studies/202512_GenesysCX/ctm-token-calculator/docs/Genesys Solutions - CTM ROI - AnZ - Conservative - v.1.4 (1).PDF differ
diff --git a/studies/202512_GenesysCX/ctm-token-calculator/docs/Genesys Solutions - CTM ROI - EMEA - Conservative - v.1.4 (1).PDF b/studies/202512_GenesysCX/ctm-token-calculator/docs/Genesys Solutions - CTM ROI - EMEA - Conservative - v.1.4 (1).PDF
new file mode 100644
index 0000000..85fce65
Binary files /dev/null and b/studies/202512_GenesysCX/ctm-token-calculator/docs/Genesys Solutions - CTM ROI - EMEA - Conservative - v.1.4 (1).PDF differ
diff --git a/studies/202512_GenesysCX/ctm-token-calculator/docs/Genesys Solutions - CTM ROI - NA - Conservative - v.1.4 (1).PDF b/studies/202512_GenesysCX/ctm-token-calculator/docs/Genesys Solutions - CTM ROI - NA - Conservative - v.1.4 (1).PDF
new file mode 100644
index 0000000..3c674ac
Binary files /dev/null and b/studies/202512_GenesysCX/ctm-token-calculator/docs/Genesys Solutions - CTM ROI - NA - Conservative - v.1.4 (1).PDF differ
diff --git a/studies/202512_GenesysCX/ctm-token-calculator/docs/ctm_ai_labour_estimate.md b/studies/202512_GenesysCX/ctm-token-calculator/docs/ctm_ai_labour_estimate.md
new file mode 100644
index 0000000..dc09cba
--- /dev/null
+++ b/studies/202512_GenesysCX/ctm-token-calculator/docs/ctm_ai_labour_estimate.md
@@ -0,0 +1,198 @@
+The Genesys ROI documents claim 5 AI feature benefit categories:
+
+Agent Copilot (voice + digital handle time + ACW)
+Email AI (Auto-Respond + Auto-Suggest)
+Speech & Text Analytics
+Supervisor Copilot (AI Translate, AI Summary, Admin)
+Predictive Routing
+None of these are turn-key. Each requires configuration, tuning, and enablement effort. The original case has zero implementation cost.
+
+Framework — four LoE dimensions per feature
+Every AI feature carries four kinds of effort:
+
+Dimension What it is Scales with
+Fixed setup One-time base configuration — instance creation, settings, rules, permissions, security Roughly constant per feature
+Variable configuration Per-scope effort — per queue, per language, per intent, per wrap-up code, per KB article Multipliers × unit count
+Iterative tuning Test → measure → adjust cycles. Non-negotiable for AI features. Typical: 3-6 cycles before production stability Complexity of the feature
+Enablement & change Agent/supervisor training, adoption support, communications, super-user network Headcount + geography
+Plus a steady-state annual line that everyone forgets:
+
+Steady-state What it is
+Annual optimization Retraining, KB refresh, drift correction, model tuning as customer behavior shifts
+Per-feature LoE — Genesys-claimed feature set
+Below is my working LoE structure for the calculator. Hours are for a typical medium-complexity implementation. CTM-specific amplifiers follow in the next section.
+
+1. Agent Copilot
+Per the Genesys documentation you shared, the setup dimensions are: create the Copilot instance, configure settings, configure NLU (intents), configure rules, configure queues, configure per-language variants, wrap-up code configuration, AI Studio for custom summaries, testing, permissions, KB integration for answer highlighting.
+
+Activity Unit Hours per unit Notes
+Base Copilot instance setup Fixed 80-120 Per language variant (one instance per language)
+Settings, rules, permissions config Fixed 40-60
+NLU / intent modeling Per 10 intents 30-50 Includes utterance generation, training, validation
+Wrap-up code mapping Per 20 wrap-ups 8-12 Includes utterance training per code
+Queue configuration Per queue 1-3 Critical CTM scaling factor
+Custom summary templates (AI Studio) Per template 20-40 If custom summaries wanted
+Knowledge base article preparation Per 100 articles 20-40 Only if KB used for answer highlighting — separate from KB creation
+Testing / tuning cycles Per cycle 80-120 Plan for 4-6 cycles Y1
+Agent training Per 100 agents 8-12 Blended live/self-paced
+Supervisor / admin enablement Per site 16-24
+Typical medium implementation (10 queues, 1 language, 100 intents, 100 wrap-ups, 500 agents, 4 tuning cycles, 500 KB articles): ~1,500 hours.
+
+2. Email AI (Auto-Suggest + Auto-Respond)
+Activity Unit Hours per unit
+Base Email AI setup Fixed 60-100
+Intent library for email Per 10 intents 40-60 (higher than voice — more text nuance)
+Response template library (Auto-Suggest) Per 20 templates 30-50
+Auto-Respond flow design Per flow 60-100 (business rules, escalation logic, guardrails)
+Integration to systems of record for response Per integration 80-200
+Testing / tuning cycles Per cycle 100-160
+Agent training on suggested/edit vs. auto Per 100 agents 6-10
+Typical medium implementation: ~1,200-1,800 hours.
+
+Critical note: Auto-Respond at any meaningful rate requires integration to case/order/account data — doesn't work without the ESB. Auto-Suggest is more forgiving. This shapes phasing.
+
+3. Speech & Text Analytics
+Activity Unit Hours per unit
+STA topic/program setup Fixed 80-120
+Program per language Per language 60-100
+Topic library — compliance Per 20 topics 20-30
+Topic library — CX / operational Per 20 topics 20-30
+Category / phrase library tuning Per cycle 60-100 (plan for 3-5 cycles)
+Dashboard / report configuration Per dashboard 20-30
+Supervisor enablement Per site 8-16
+Typical medium implementation: ~600-1,000 hours.
+
+4. Supervisor Copilot
+Activity Unit Hours per unit
+Supervisor Copilot instance & settings Fixed 40-60
+AI Translate configuration per language pair Per pair 8-16
+AI Summary insight configuration Fixed 40-60
+Alerting rules & thresholds Per rule set 20-40
+Supervisor training Per 10 supervisors 8-16
+Typical medium implementation: ~300-500 hours.
+
+5. Predictive Routing
+Activity Unit Hours per unit
+PR model configuration Fixed 60-100
+Data source setup and validation Fixed 40-80
+Per-queue optimization Per queue 2-4
+Baseline measurement & A/B Per cycle 80-120 (plan 2-3 cycles)
+Model retraining automation Fixed 20-40
+Typical medium implementation: ~500-800 hours.
+
+Cross-cutting activities (allocate across features)
+These are the ones that get missed and blow budgets:
+
+Activity Unit Hours
+KB curation & prep (source-of-truth for Copilot, Email AI, and STA) Per 100 articles 40-80
+KB governance setup (versioning, ownership, refresh cadence) Fixed 100-200
+AI governance framework (drift detection, model versioning, escalation paths) Fixed 120-200
+Data pipeline / integration to systems of record Per SoR 200-500
+Testing environment setup Fixed 80-160
+Program management overhead Per month program duration 40-80
+Regulatory / compliance review for AI features Per feature 20-60
+CTM-specific amplifiers
+Now the ugly part. Every parameter above gets multiplied at CTM scale:
+
+Parameter Typical medium CTM
+Tails 10-50 1,000+ (6-10× amplifier on queue-configuration line items)
+Languages 1-3 7+ (English, French, Spanish, German, Mandarin, Cantonese, Japanese)
+Sites 1-3 9 (change management overhead compounds)
+Agent count 100-500 ~1,900 (training scales linearly)
+Regions 1 4 (NAM, EMEA, AUZ, APAC) — program management overhead compounds
+Systems-of-record integration 1-2 pre-built 0 today, ESB Nov 2026+
+KB maturity Unknown Unknown — flag as major risk
+Amplifier math for Agent Copilot at CTM scale
+Using the LoE table above at CTM parameters, mid-range hours:
+
+Activity CTM units Hours
+Base Copilot instance × 7 languages 7 700
+Settings/rules/permissions 1 50
+NLU/intent modeling — 300 intents (large enterprise) 30 1,200
+Wrap-up codes — 500 codes 25 250
+Queue configuration — 1,000 queues at 2 hrs each 1,000 2,000
+Custom summary templates — 15 templates 15 450
+KB article preparation — 5,000 articles 50 1,500
+Testing/tuning — 6 cycles 6 600
+Agent training — 1,900 agents 19 190
+Supervisor enablement — 9 sites 9 180
+Agent Copilot subtotal ~7,100 hours
+Full CTM AI implementation LoE
+Feature Estimated hours
+Agent Copilot 6,500 - 8,500
+Email AI (Auto-Suggest + Auto-Respond) 3,000 - 4,500
+Speech & Text Analytics 1,500 - 2,500
+Supervisor Copilot 600 - 900
+Predictive Routing 1,200 - 1,800
+Feature subtotal 12,800 - 18,200
+Cross-cutting (KB, governance, PM, integration) 4,000 - 7,000
+Total Y1 implementation LoE 16,800 - 25,200 hours
+Translating to dollars
+I don't know your PS rate, but for context using industry-standard blended rates:
+
+Blended rate Y1 implementation cost range
+$175/hr (offshore-heavy blend) $2.9M - $4.4M
+$225/hr (typical NTT DATA blended) $3.8 million - $5.7 million
+$275/hr (onshore-heavy specialist) $4.6M - $6.9M
+Plus annual steady-state at 15-20% of implementation = $430K - $1.4M/yr recurring for ongoing optimization, tuning, KB refresh, model retraining.
+
+What this does to the case
+Adding implementation costs to the model:
+
+Component Y1 Y2 Y3 3-Year
+Platform license $2.79M $2.79M $2.79M $8.37M
+AI token costs (Realistic) $2.0M $3.5M $5.0M $10.5M
+AI implementation LoE (new) $3.8 million-5.7 million $0.6M-1.1M $0.6M-1.1M $5.0M-7.9M
+Legacy platform takeouts ($2.0M) ($2.0M) ($2.0M) ($6.0M)
+Realistic AI benefits ($1.5M) ($4.5M) ($7.5M) ($13.5M)
+NET +$5.1M to +$7.0M +$0.4M to +$0.9M -$1.1M to -$1.6M +$4.4M to +$6.3M
+In the current model, program is net-negative $4-6M over 3 years even in Realistic scenario. Y1 is the ugly year because implementation cost front-loads. Y3 is when benefits catch up — barely.
+
+And that's using Genesys's own claimed benefits, unadjusted. If we apply the realistic haircuts we discussed earlier (Y1 benefit realization at 30-50%), the picture gets worse.
+
+Calculator amendment
+Add to the spec:
+
+New dataclass in inputs.py
+@dataclass
+class ImplementationEffort:
+ feature: str
+ fixed_setup_hours: float
+ per_queue_hours: float
+ per_language_hours: float
+ per_100_intents_hours: float
+ per_20_wrapups_hours: float
+ per_100_kb_articles_hours: float
+ tuning_cycles: int
+ hours_per_tuning_cycle: float
+ per_100_agents_training_hours: float
+ per_site_enablement_hours: float
+
+@dataclass
+class ImplementationCosts:
+ ps_blended_rate: float # $/hr — user-configurable
+ steady_state_pct_annual: float # default 0.175 (17.5%)
+ ramp_phasing: dict[str, float] # feature -> % of hours in Y1 vs Y2
+New module tokencalc/implementation.py
+def calculate_feature_loe(effort: ImplementationEffort,
+ sites: list[SiteInput],
+ feature_scope: FeatureScope,
+ complexity_params: ComplexityParams
+ ) -> pd.DataFrame:
+ """Returns hours breakdown by activity for one feature."""
+
+def calculate_implementation_cost(loe_by_feature: dict,
+ ps_rate: float,
+ phasing: dict
+ ) -> pd.DataFrame:
+ """Distributes hours across Y1/Y2/Y3 with steady-state tail."""
+New Streamlit page: "Implementation Effort"
+LoE inputs per feature (adjustable, defaults from table above)
+Complexity multipliers (queue count, language count, KB size — pulled from Inputs page)
+PS rate slider
+Steady-state % slider
+Output table: hours × rate by feature, by year
+Warning: "Implementation cost was NOT in the original Genesys business case."
+Smell-test rule to add
+If total AI implementation cost < 15% of AI benefit claim, flag as under-modeled. Industry benchmark: 20-40% of Y1 benefit claim goes to implementation.
+
diff --git a/studies/202512_GenesysCX/ctm-token-calculator/docs/ctm_ai_labour_estimate_V2.md b/studies/202512_GenesysCX/ctm-token-calculator/docs/ctm_ai_labour_estimate_V2.md
new file mode 100644
index 0000000..8cb05d3
--- /dev/null
+++ b/studies/202512_GenesysCX/ctm-token-calculator/docs/ctm_ai_labour_estimate_V2.md
@@ -0,0 +1,64 @@
+Correction #1 — Email Auto-Suggest is part of Copilot
+You're correct, and I got this wrong. Per Genesys documentation, Agent Copilot handles both voice and digital channels (email, chat, messaging) — including Auto-Suggest for email. Email Auto-Suggest is not a separate product; it's a Copilot capability on the email channel.
+
+Auto-Respond is the separate line — that's Email Bot / Digital Bot flow territory, distinct from Copilot's suggest-and-agent-approves model.
+
+That collapses my LoE table. Corrected structure:
+
+Feature (corrected) What it actually is
+Agent Copilot Voice + digital channels (including email Auto-Suggest, chat suggest, messaging suggest) — one implementation
+Email Auto-Respond Automated response without agent — separate flow, needs system-of-record integration
+STA Standalone product
+Supervisor Copilot Standalone product
+Predictive Routing Standalone product
+So I was double-counting Copilot email work. Email AI as I described it was inflated.
+
+Correction #2 — The hours were wrong. Let me show my work.
+You called it correctly. 16,800-25,200 hours is 8-12 person-years of labour for five features on a mature platform. That's absurd on its face. Genesys Cloud is not custom software; these features are configuration, not development. Let me diagnose where I inflated:
+
+Where I over-estimated
+Queue configuration at 2 hours × 1,000 queues = 2,000 hours for Copilot alone. Nonsense. Copilot doesn't require per-queue configuration in the way I implied. Enable Copilot on the account, configure once, apply to queues via policy. Real work: ~50-100 hours to define which queue types get Copilot, plus per-queue-type tuning, not per-individual-queue.
+
+NLU / intent modeling at 40 hours per 10 intents = 1,200 hours for 300 intents. Overstated. Copilot leverages Genesys's pre-built LLM understanding — you're not training an NLU from scratch. You're configuring wrap-up code mappings and validating auto-summary quality. Real work: ~200-400 hours for a large enterprise intent library, not 1,200.
+
+KB article prep at 40 hours per 100 articles = 1,500 hours for 5,000 articles. Double-counted. KB curation is either (a) already done in your KB platform and Copilot indexes it, or (b) a separate KB maturity project — not an AI implementation line item. Real work: ~200-400 hours for connector setup and indexing configuration, plus a separate flag that says "KB curation is a prerequisite project, not part of AI implementation."
+
+Testing/tuning at 100 hours × 6 cycles. Overstated. Test cycles for a hosted AI feature aren't full regression cycles — they're validation of prompt output quality and adjustment. Real: ~40-60 hours per cycle, 3-4 cycles typical.
+
+Training at 10 hours per 100 agents. Wildly high. This is a Copilot UI change — 30 minutes of orientation, some job aids, super-user support. Real: ~1-2 hours per 100 agents for train-the-trainer + material creation.
+
+Cross-cutting at 4,000-7,000 hours. Inflated by carrying forward the double-counted items above.
+
+Corrected LoE — Genesys AI features at CTM scale
+Working from realistic effort, not vendor-services-inflation:
+
+Feature Realistic hours (CTM scale)
+Agent Copilot (voice + digital, all languages) 1,200 - 1,800
+Email Auto-Respond (separate from Copilot; needs integration) 800 - 1,400
+STA (topics, programs, tuning for 7 languages) 800 - 1,200
+Supervisor Copilot 200 - 400
+Predictive Routing 400 - 700
+Feature subtotal 3,400 - 5,500
+Cross-cutting (governance, PM, testing environment, integration coordination) 1,000 - 1,800
+KB readiness project (separate line — prerequisite) 500 - 1,500 (flagged separately)
+Total Y1 AI implementation 4,400 - 7,300 hours
+Annual steady-state (Y2, Y3) 500 - 900 hours
+That's 2-3.5 person-years of Y1 effort across 5 features. Still substantial — this is a real, multi-workstream program at CTM scale — but not the fantasy 12 person-years I had before.
+
+Corrected cost impact
+Blended rate Y1 implementation Annual steady-state
+$175/hr $770K - $1.28M $88K - $158K
+$225/hr $990K - $1.64M $113K - $203K
+$275/hr $1.21M - $2.01M $138K - $248K
+Updated combined case
+Using $225/hr blended rate and Realistic scenario:
+
+Component Y1 Y2 Y3 3-Year
+Genesys CX 3 platform license $2.79M $2.79M $2.79M $8.37M
+Base platform implementation $1.5M — — $1.5M
+AI token costs $2.0M $3.5M $5.0M $10.5M
+AI implementation (corrected) $1.3M $0.16M $0.16M $1.6M
+Total future-state cost $7.6M $6.45M $7.95M $22.0M
+Current-state takeout ($7.3M) ($7.3M) ($7.3M) ($21.9M)
+AI benefits (realistic) ($1.5M) ($4.5M) ($7.5M) ($13.5M)
+Program net -$1.2M +$5.35M +$6.85M +$11.0M
diff --git a/studies/202512_GenesysCX/ctm-token-calculator/notebooks/ctm_business_case_corrected.ipynb b/studies/202512_GenesysCX/ctm-token-calculator/notebooks/ctm_business_case_corrected.ipynb
new file mode 100644
index 0000000..7fd3fae
--- /dev/null
+++ b/studies/202512_GenesysCX/ctm-token-calculator/notebooks/ctm_business_case_corrected.ipynb
@@ -0,0 +1,8717 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "aeb959ef",
+ "metadata": {},
+ "source": [
+ "# CTM × Genesys CCaaS — Corrected Business Case\n",
+ "\n",
+ "**Thesis:** the Genesys/Broadreach business case presents **$13.6M annual / $15.0M 3-year benefits**\n",
+ "against a cost case of **$4.3M/yr CCaaS licences + $2.4M professional services + $167K training** —\n",
+ "but every claimed benefit is produced by AI capabilities whose **token consumption**,\n",
+ "**implementation effort**, and **double-billing against the existing platform** are absent from that\n",
+ "cost case. This notebook keeps Genesys's benefits **verbatim** and corrects the cost side only.\n",
+ "\n",
+ "| Layer | Source | Treatment |\n",
+ "|---|---|---|\n",
+ "| Benefits | *Appendix 4 — CCaaS Platform Benefit Calculations (Consolidated).pptx*, slides 5–21 | **Verbatim** — phased by Genesys's own deployment schedule, scaled to the deck's 3-yr totals |\n",
+ "| Base costs | Same deck (TCO slides 5–6) | Verbatim, plus the Genesys ramp programme (12 months licence-free) |\n",
+ "| AI token consumption | `tokencalc` engine, published Genesys meter rates | **Added** — missing cost #1 |\n",
+ "| AI implementation effort | `docs/ctm_ai_labour_estimate_V2.md` | **Added** — missing cost #2 |\n",
+ "| Existing platform run-off | Term contracts to 31 Dec 2027, per-region | **Added** — missing cost #3 (double-billing) |\n",
+ "\n",
+ "Confidence legend: 🟢 confirmed (published/contractual) · 🟡 estimated (working assumption) · 🔴 unknown.\n",
+ "\n",
+ "*Scope note: AI implementation uses the V2 hours-range × blended-rate model; the activity-level\n",
+ "`ImplementationEffort` engine sketched in the labour doc is deferred (see §11). Timeline = 2026–2028,\n",
+ "contract start Jan 2026.*"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "83ba2b80",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-07-07T12:22:42.281815Z",
+ "iopub.status.busy": "2026-07-07T12:22:42.281637Z",
+ "iopub.status.idle": "2026-07-07T12:22:42.521532Z",
+ "shell.execute_reply": "2026-07-07T12:22:42.520768Z"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "tokencalc loaded — 9 sites, 2,088 named users (contracted: 2,088)\n"
+ ]
+ }
+ ],
+ "source": [
+ "# ── Setup ──────────────────────────────────────────────────────────\n",
+ "import sys, pathlib\n",
+ "_ROOT = pathlib.Path.cwd()\n",
+ "if not (_ROOT / \"tokencalc\").exists(): # notebook lives in notebooks/\n",
+ " _ROOT = _ROOT.parent\n",
+ "sys.path.insert(0, str(_ROOT))\n",
+ "\n",
+ "import dataclasses\n",
+ "import datetime as dt\n",
+ "import math\n",
+ "\n",
+ "import pandas as pd\n",
+ "import plotly.graph_objects as go\n",
+ "\n",
+ "from tokencalc import *\n",
+ "\n",
+ "pd.options.display.float_format = \"{:,.0f}\".format\n",
+ "\n",
+ "YEARS = [2026, 2027, 2028] # model years 1..3, contract start Jan 2026\n",
+ "YEAR_INDEX = {2026: 1, 2027: 2, 2028: 3}\n",
+ "\n",
+ "# ── Chart chrome (dataviz reference palette, light surface) ─────────\n",
+ "INK, INK2, MUTED = \"#0b0b0b\", \"#52514e\", \"#898781\"\n",
+ "SURFACE, GRID, BASELINE = \"#fcfcfb\", \"#e1e0d9\", \"#c3c2b7\"\n",
+ "CUMULATIVE, CONTEXT = \"#52514e\", \"#c3c2b7\" # neutral line; de-emphasized context series\n",
+ "FONT_STACK = 'system-ui, -apple-system, \"Segoe UI\", sans-serif'\n",
+ "\n",
+ "# Fixed capability colors — color follows the entity across every figure.\n",
+ "CAP_COLOR = {\n",
+ " \"Agent Copilot\": \"#2a78d6\", # blue\n",
+ " \"WFM\": \"#1baf7a\", # aqua\n",
+ " \"Email\": \"#eda100\", # yellow (sub-3:1 → relief via total labels + tables)\n",
+ " \"STA\": \"#008300\", # green\n",
+ " \"Predictive Routing\": \"#4a3aa7\", # violet\n",
+ " \"Supervisor Copilot\": \"#e34948\", # red\n",
+ "}\n",
+ "COST_COLOR = {\n",
+ " \"CCaaS platform licences (ramp-adjusted)\": \"#2a78d6\",\n",
+ " \"Base professional services + training\": \"#1baf7a\",\n",
+ " \"Existing platform (term-contract run-off)\": \"#eda100\",\n",
+ " \"AI token consumption\": \"#008300\",\n",
+ " \"AI implementation + KB readiness\": \"#4a3aa7\",\n",
+ " \"AI steady-state tuning\": \"#e34948\",\n",
+ "}\n",
+ "SEQ_BLUES = [\"#cde2fb\", \"#b7d3f6\", \"#9ec5f4\", \"#86b6ef\", \"#6da7ec\", \"#5598e7\",\n",
+ " \"#3987e5\", \"#2a78d6\", \"#256abf\", \"#1c5cab\", \"#184f95\", \"#104281\", \"#0d366b\"]\n",
+ "\n",
+ "\n",
+ "def tei_layout(fig, title, subtitle=None, height=460):\n",
+ " t = f\"{title}\"\n",
+ " if subtitle:\n",
+ " t += f\"
{subtitle}\"\n",
+ " fig.update_layout(\n",
+ " title=dict(text=t, font=dict(size=16, color=INK), x=0.02, xanchor=\"left\"),\n",
+ " paper_bgcolor=SURFACE, plot_bgcolor=SURFACE,\n",
+ " font=dict(family=FONT_STACK, size=12, color=INK2),\n",
+ " legend=dict(orientation=\"h\", yanchor=\"top\", y=-0.10, x=0,\n",
+ " font=dict(size=11, color=INK2)),\n",
+ " xaxis=dict(type=\"category\", showgrid=False, linecolor=BASELINE,\n",
+ " tickfont=dict(color=MUTED)),\n",
+ " yaxis=dict(gridcolor=GRID, zerolinecolor=BASELINE, zerolinewidth=1.5,\n",
+ " tickformat=\"$~s\", tickfont=dict(color=MUTED)),\n",
+ " hovermode=\"x unified\", bargap=0.45, height=height,\n",
+ " margin=dict(t=70, r=30, b=80, l=70),\n",
+ " )\n",
+ " return fig\n",
+ "\n",
+ "\n",
+ "def bar(x, y, name, color):\n",
+ " return go.Bar(x=x, y=y, name=name,\n",
+ " marker=dict(color=color, line=dict(width=2, color=SURFACE)),\n",
+ " hovertemplate=\"%{fullData.name}: %{y:$,.0f}\")\n",
+ "\n",
+ "\n",
+ "def cum_line(x, y, name, color=CUMULATIVE, dash=None):\n",
+ " return go.Scatter(x=x, y=y, name=name, mode=\"lines+markers\",\n",
+ " line=dict(color=color, width=2, dash=dash),\n",
+ " marker=dict(size=8, line=dict(width=2, color=SURFACE)),\n",
+ " hovertemplate=\"%{fullData.name}: %{y:$,.0f}\")\n",
+ "\n",
+ "\n",
+ "def money(v):\n",
+ " sign, a = (\"-\" if v < 0 else \"\"), abs(v)\n",
+ " return f\"{sign}${a/1e6:,.1f}M\" if a >= 1e6 else f\"{sign}${a/1e3:,.0f}K\"\n",
+ "\n",
+ "\n",
+ "def html_money(v):\n",
+ " # Plotly text with two or more bare \"$\" triggers MathJax math mode —\n",
+ " # annotations holding several amounts must use the HTML entity instead.\n",
+ " return money(v).replace(\"$\", \"$\")\n",
+ "\n",
+ "\n",
+ "print(f\"tokencalc loaded — {len(CTM_DEFAULT_SITES)} sites, \"\n",
+ " f\"{sum(s.named_users for s in CTM_DEFAULT_SITES):,} named users \"\n",
+ " f\"(contracted: {CONTRACTED_NAMED_USERS:,})\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9139f3c0",
+ "metadata": {},
+ "source": [
+ "## §1 · Current state & contract inputs — data collection\n",
+ "\n",
+ "Verbatim anchors (Appendix 4, slide 5–6): current solution costs **$7.3M/yr globally**\n",
+ "(ANZ, APAC, EMEA, NA — $22M over 3 years); CCaaS is **$4.3M/yr** with **$2.4M professional\n",
+ "services + $167K training in year 1**.\n",
+ "\n",
+ "Three contract facts the deck's cost case ignores, collected here as editable inputs:\n",
+ "\n",
+ "1. **Per-region current cost** — seeded as $7.3M × (region agents ÷ total agents). 🟡 Allocation\n",
+ " estimate; replace with real regional contract values as they arrive.\n",
+ "2. **Contract termination dates** — the current platforms are **term contracts**; CTM pays until\n",
+ " termination (default **31 Dec 2027**) regardless of when Genesys goes live → double-billing.\n",
+ "3. **Genesys ramp programme** — `RAMP_MONTHS = 12`: licences are free for the first 12 months,\n",
+ " so the $4.3M/yr commit bills from month 13."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "7d0c26bd",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-07-07T12:22:42.523771Z",
+ "iopub.status.busy": "2026-07-07T12:22:42.523541Z",
+ "iopub.status.idle": "2026-07-07T12:22:42.546359Z",
+ "shell.execute_reply": "2026-07-07T12:22:42.545634Z"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "current 3-yr = $21.9M (deck: $22.0M) — deck rounding\n",
+ "deck CCaaS 3-yr (no ramp) = $15.5M (deck: $15.4M)\n",
+ "ramp-adjusted licences by year: {2026: '$0K', 2027: '$4.3M', 2028: '$4.3M'}\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " agents | \n",
+ " share | \n",
+ " annual_cost | \n",
+ " contract_termination | \n",
+ " confidence | \n",
+ "
\n",
+ " \n",
+ " | region | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | NA | \n",
+ " 890 | \n",
+ " 0 | \n",
+ " 3,348,969 | \n",
+ " 2027-12-31 | \n",
+ " 🟡 agent-share allocation of the verbatim $7.3M | \n",
+ "
\n",
+ " \n",
+ " | ANZ | \n",
+ " 180 | \n",
+ " 0 | \n",
+ " 677,320 | \n",
+ " 2027-12-31 | \n",
+ " 🟡 agent-share allocation of the verbatim $7.3M | \n",
+ "
\n",
+ " \n",
+ " | EMEA | \n",
+ " 320 | \n",
+ " 0 | \n",
+ " 1,204,124 | \n",
+ " 2027-12-31 | \n",
+ " 🟡 agent-share allocation of the verbatim $7.3M | \n",
+ "
\n",
+ " \n",
+ " | ASIA | \n",
+ " 550 | \n",
+ " 0 | \n",
+ " 2,069,588 | \n",
+ " 2027-12-31 | \n",
+ " 🟡 agent-share allocation of the verbatim $7.3M | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " agents share annual_cost contract_termination \\\n",
+ "region \n",
+ "NA 890 0 3,348,969 2027-12-31 \n",
+ "ANZ 180 0 677,320 2027-12-31 \n",
+ "EMEA 320 0 1,204,124 2027-12-31 \n",
+ "ASIA 550 0 2,069,588 2027-12-31 \n",
+ "\n",
+ " confidence \n",
+ "region \n",
+ "NA 🟡 agent-share allocation of the verbatim $7.3M \n",
+ "ANZ 🟡 agent-share allocation of the verbatim $7.3M \n",
+ "EMEA 🟡 agent-share allocation of the verbatim $7.3M \n",
+ "ASIA 🟡 agent-share allocation of the verbatim $7.3M "
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " data item | \n",
+ " status | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " Per-region current-platform contract values | \n",
+ " 🟡 seeded by agent share above | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " Actual termination / renewal dates per region | \n",
+ " 🟡 defaulted to 31 Dec 2027 | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " Genesys ramp period in the actual order form | \n",
+ " 🟡 12 months assumed | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " Contracted token rate (vs $1.00 US list) | \n",
+ " 🔴 not sourced — list rate used | \n",
+ "
\n",
+ " \n",
+ " | 4 | \n",
+ " Non-NAM site volumes & AHTs | \n",
+ " 🟡 tokencalc placeholders (defaults.py warning) | \n",
+ "
\n",
+ " \n",
+ " | 5 | \n",
+ " Early-termination / co-term options on NICE IE... | \n",
+ " 🔴 unknown | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " data item \\\n",
+ "0 Per-region current-platform contract values \n",
+ "1 Actual termination / renewal dates per region \n",
+ "2 Genesys ramp period in the actual order form \n",
+ "3 Contracted token rate (vs $1.00 US list) \n",
+ "4 Non-NAM site volumes & AHTs \n",
+ "5 Early-termination / co-term options on NICE IE... \n",
+ "\n",
+ " status \n",
+ "0 🟡 seeded by agent share above \n",
+ "1 🟡 defaulted to 31 Dec 2027 \n",
+ "2 🟡 12 months assumed \n",
+ "3 🔴 not sourced — list rate used \n",
+ "4 🟡 tokencalc placeholders (defaults.py warning) \n",
+ "5 🔴 unknown "
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# ── Verbatim TCO anchors (Appendix 4, slides 5-6) ────────────────────\n",
+ "TCO_VERBATIM = {\n",
+ " \"current_annual\": 7_300_000, # current global spend / yr\n",
+ " \"current_3yr\": 22_000_000,\n",
+ " \"ccaas_annual\": 4_300_000, # licence run-rate / yr\n",
+ " \"ccaas_3yr\": 15_400_000, # deck's 3-yr CCaaS investment (no ramp, no AI costs)\n",
+ " \"prof_services_y1\": 2_400_000,\n",
+ " \"training_y1\": 167_000,\n",
+ " \"npv_discount_rate\": 0.135, # deck's benefit-NPV rate\n",
+ "}\n",
+ "RAMP_MONTHS = 12 # Genesys ramp programme: licence-free months from contract start\n",
+ "DISCOUNT_RATE = TCO_VERBATIM[\"npv_discount_rate\"] # 0.08 = CTM treasury alternative\n",
+ "\n",
+ "# ── Region ⇄ site mapping (tokencalc sites → deck regions) ───────────\n",
+ "SITE_REGION = {\"NAM\": \"NA\", \"AUZ\": \"ANZ\", \"EMEA\": \"EMEA\"} # APAC * → ASIA\n",
+ "REGIONS = [\"NA\", \"ANZ\", \"EMEA\", \"ASIA\"]\n",
+ "sites = list(CTM_DEFAULT_SITES)\n",
+ "ALL_SITES = [s.site_name for s in sites]\n",
+ "ASIA_SITES = [n for n in ALL_SITES if n.startswith(\"APAC\")]\n",
+ "REGION_SITES = {r: [n for n in ALL_SITES if SITE_REGION.get(n, \"ASIA\") == r] for r in REGIONS}\n",
+ "agents_by_region = {\n",
+ " r: sum(s.agents for s in sites if s.site_name in REGION_SITES[r]) for r in REGIONS\n",
+ "}\n",
+ "TOTAL_AGENTS = sum(agents_by_region.values())\n",
+ "\n",
+ "# ── Per-region current-state inputs (EDIT HERE as real data arrives) ─\n",
+ "current_state = pd.DataFrame([\n",
+ " {\n",
+ " \"region\": r,\n",
+ " \"agents\": agents_by_region[r],\n",
+ " \"share\": agents_by_region[r] / TOTAL_AGENTS,\n",
+ " \"annual_cost\": TCO_VERBATIM[\"current_annual\"] * agents_by_region[r] / TOTAL_AGENTS,\n",
+ " \"contract_termination\": dt.date(2027, 12, 31),\n",
+ " \"confidence\": \"🟡 agent-share allocation of the verbatim $7.3M\",\n",
+ " }\n",
+ " for r in REGIONS\n",
+ "]).set_index(\"region\")\n",
+ "\n",
+ "\n",
+ "def current_months_in_year(termination: dt.date, cal_year: int) -> int:\n",
+ " # Term contract bills through its termination month, then stops.\n",
+ " if cal_year < termination.year:\n",
+ " return 12\n",
+ " if cal_year > termination.year:\n",
+ " return 0\n",
+ " return termination.month\n",
+ "\n",
+ "\n",
+ "current_by_year = {\n",
+ " y: float(sum(row[\"annual_cost\"] * current_months_in_year(row[\"contract_termination\"], y) / 12\n",
+ " for _, row in current_state.iterrows()))\n",
+ " for y in YEARS\n",
+ "}\n",
+ "\n",
+ "\n",
+ "def licence_months_in_year(year_index: int, ramp_months: int) -> int:\n",
+ " # Ramp programme: billing starts in calendar month ramp_months + 1.\n",
+ " start, end = 12 * (year_index - 1) + 1, 12 * year_index\n",
+ " return max(0, end - max(start, ramp_months + 1) + 1)\n",
+ "\n",
+ "\n",
+ "licence_by_year = {\n",
+ " y: TCO_VERBATIM[\"ccaas_annual\"] * licence_months_in_year(YEAR_INDEX[y], RAMP_MONTHS) / 12\n",
+ " for y in YEARS\n",
+ "}\n",
+ "\n",
+ "assert abs(current_state[\"annual_cost\"].sum() - TCO_VERBATIM[\"current_annual\"]) < 1\n",
+ "print(f\"current 3-yr = {money(3 * TCO_VERBATIM['current_annual'])} \"\n",
+ " f\"(deck: {money(TCO_VERBATIM['current_3yr'])}) — deck rounding\")\n",
+ "print(f\"deck CCaaS 3-yr (no ramp) = \"\n",
+ " f\"{money(3 * TCO_VERBATIM['ccaas_annual'] + TCO_VERBATIM['prof_services_y1'] + TCO_VERBATIM['training_y1'])} \"\n",
+ " f\"(deck: {money(TCO_VERBATIM['ccaas_3yr'])})\")\n",
+ "print(f\"ramp-adjusted licences by year: \"\n",
+ " f\"{ {y: money(v) for y, v in licence_by_year.items()} }\")\n",
+ "display(current_state)\n",
+ "\n",
+ "# ── Remaining data-collection checklist ──────────────────────────────\n",
+ "display(pd.DataFrame([\n",
+ " (\"Per-region current-platform contract values\", \"🟡 seeded by agent share above\"),\n",
+ " (\"Actual termination / renewal dates per region\", \"🟡 defaulted to 31 Dec 2027\"),\n",
+ " (\"Genesys ramp period in the actual order form\", \"🟡 12 months assumed\"),\n",
+ " (\"Contracted token rate (vs $1.00 US list)\", \"🔴 not sourced — list rate used\"),\n",
+ " (\"Non-NAM site volumes & AHTs\", \"🟡 tokencalc placeholders (defaults.py warning)\"),\n",
+ " (\"Early-termination / co-term options on NICE IEX et al.\", \"🔴 unknown\"),\n",
+ "], columns=[\"data item\", \"status\"]))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8af2a53b",
+ "metadata": {},
+ "source": [
+ "## §2 · Verbatim Genesys benefits & deployment schedule\n",
+ "\n",
+ "Benefits are taken **verbatim** from Appendix 4 slides 12–15 (per-region × capability, annual and\n",
+ "3-yr values). The deck gives no per-year split, so each cell's 3-yr value is **phased by Genesys's\n",
+ "own deployment schedule** (slides 17–21): implementation months NA=18, ANZ=21, EMEA=24, ASIA=27,\n",
+ "and *\"business benefits start 3 months after implementation\"* — realization Sep 2027 / Dec 2027 /\n",
+ "Mar 2028 / Jun 2028. Exception: the NA Gantt shows **Email realizing Apr 2027** (implemented month 13).\n",
+ "\n",
+ "**Month convention:** `RolloutPlan.go_live_month = m` means active from calendar month *m+1*; the\n",
+ "deck's labels are inclusive (NA \"realizes Sep 2027\" ⇒ September counts), so rollout keys are set to\n",
+ "*label − 1*. Deck-implied realization windows differ from its own schedule by ±1 month in places —\n",
+ "the per-cell scaling below absorbs that; don't \"fix\" it.\n",
+ "\n",
+ "The result: **benefits in 2026 are $0 — by Genesys's own schedule.**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "da68f997",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-07-07T12:22:42.547907Z",
+ "iopub.status.busy": "2026-07-07T12:22:42.547785Z",
+ "iopub.status.idle": "2026-07-07T12:22:42.577658Z",
+ "shell.execute_reply": "2026-07-07T12:22:42.576992Z"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "cross-foot OK — cell grain sums to $15.0M 3-yr / $13.7M annual (deck headline: $15.0M / $13.6M — deck rounding)\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | region | \n",
+ " ANZ | \n",
+ " ASIA | \n",
+ " EMEA | \n",
+ " NA | \n",
+ " TOTAL | \n",
+ "
\n",
+ " \n",
+ " | capability | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | Agent Copilot | \n",
+ " 3900000 | \n",
+ " 0 | \n",
+ " 64000 | \n",
+ " 3400000 | \n",
+ " 7364000 | \n",
+ "
\n",
+ " \n",
+ " | WFM | \n",
+ " 1400000 | \n",
+ " 914000 | \n",
+ " 687000 | \n",
+ " 0 | \n",
+ " 3001000 | \n",
+ "
\n",
+ " \n",
+ " | Email | \n",
+ " 143000 | \n",
+ " 93000 | \n",
+ " 235000 | \n",
+ " 2500000 | \n",
+ " 2971000 | \n",
+ "
\n",
+ " \n",
+ " | STA | \n",
+ " 105000 | \n",
+ " 72000 | \n",
+ " 131000 | \n",
+ " 506000 | \n",
+ " 814000 | \n",
+ "
\n",
+ " \n",
+ " | Predictive Routing | \n",
+ " 302000 | \n",
+ " 51000 | \n",
+ " 6000 | \n",
+ " 167000 | \n",
+ " 526000 | \n",
+ "
\n",
+ " \n",
+ " | Supervisor Copilot | \n",
+ " 27000 | \n",
+ " 0 | \n",
+ " 49000 | \n",
+ " 291000 | \n",
+ " 367000 | \n",
+ "
\n",
+ " \n",
+ " | TOTAL | \n",
+ " 5877000 | \n",
+ " 1130000 | \n",
+ " 1172000 | \n",
+ " 6864000 | \n",
+ " 15043000 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ "region ANZ ASIA EMEA NA TOTAL\n",
+ "capability \n",
+ "Agent Copilot 3900000 0 64000 3400000 7364000\n",
+ "WFM 1400000 914000 687000 0 3001000\n",
+ "Email 143000 93000 235000 2500000 2971000\n",
+ "STA 105000 72000 131000 506000 814000\n",
+ "Predictive Routing 302000 51000 6000 167000 526000\n",
+ "Supervisor Copilot 27000 0 49000 291000 367000\n",
+ "TOTAL 5877000 1130000 1172000 6864000 15043000"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# ── VERBATIM Appendix 4 slides 12-15: (annual, 3-yr) per region × capability ──\n",
+ "CAPABILITIES = [\"Agent Copilot\", \"WFM\", \"Email\", \"STA\", \"Predictive Routing\", \"Supervisor Copilot\"]\n",
+ "VERBATIM_BENEFITS = { # (annual_value, three_yr_value) — verbatim, do not edit\n",
+ " (\"NA\", \"Agent Copilot\"): (2_400_000, 3_400_000),\n",
+ " (\"NA\", \"Email\"): (1_900_000, 2_500_000),\n",
+ " (\"NA\", \"STA\"): (294_000, 506_000),\n",
+ " (\"NA\", \"Supervisor Copilot\"): (218_000, 291_000),\n",
+ " (\"NA\", \"Predictive Routing\"): (97_000, 167_000),\n",
+ " (\"NA\", \"WFM\"): (0, 0), # NA excluded — has similar feature\n",
+ " (\"ANZ\", \"Agent Copilot\"): (3_600_000, 3_900_000),\n",
+ " (\"ANZ\", \"WFM\"): (1_300_000, 1_400_000),\n",
+ " (\"ANZ\", \"Predictive Routing\"): (279_000, 302_000),\n",
+ " (\"ANZ\", \"Email\"): (132_000, 143_000),\n",
+ " (\"ANZ\", \"STA\"): (97_000, 105_000),\n",
+ " (\"ANZ\", \"Supervisor Copilot\"): (25_000, 27_000),\n",
+ " (\"ASIA\", \"WFM\"): (1_600_000, 914_000),\n",
+ " (\"ASIA\", \"Email\"): (160_000, 93_000),\n",
+ " (\"ASIA\", \"STA\"): (124_000, 72_000),\n",
+ " (\"ASIA\", \"Predictive Routing\"): (87_000, 51_000),\n",
+ " (\"ASIA\", \"Agent Copilot\"): (0, 0),\n",
+ " (\"ASIA\", \"Supervisor Copilot\"): (0, 0),\n",
+ " (\"EMEA\", \"WFM\"): (824_000, 687_000),\n",
+ " (\"EMEA\", \"Email\"): (282_000, 235_000),\n",
+ " (\"EMEA\", \"STA\"): (157_000, 131_000),\n",
+ " (\"EMEA\", \"Agent Copilot\"): (77_000, 64_000),\n",
+ " (\"EMEA\", \"Supervisor Copilot\"): (59_000, 49_000),\n",
+ " (\"EMEA\", \"Predictive Routing\"): (7_000, 6_000),\n",
+ "}\n",
+ "SLIDE_TOTALS = { # deck's own (rounded) summary rows — slides 8-9\n",
+ " \"regional_3yr\": {\"NA\": 6_900_000, \"ANZ\": 5_900_000, \"ASIA\": 1_100_000, \"EMEA\": 1_200_000},\n",
+ " \"capability_3yr\": {\"Agent Copilot\": 7_400_000, \"WFM\": 3_000_000, \"Email\": 2_900_000,\n",
+ " \"STA\": 814_000, \"Predictive Routing\": 526_000,\n",
+ " \"Supervisor Copilot\": 367_000},\n",
+ " \"total_3yr\": 15_000_000,\n",
+ " \"total_annual\": 13_600_000,\n",
+ "}\n",
+ "\n",
+ "verbatim = pd.DataFrame(\n",
+ " [{\"region\": r, \"capability\": c, \"annual\": a, \"three_yr\": t}\n",
+ " for (r, c), (a, t) in VERBATIM_BENEFITS.items()]\n",
+ ")\n",
+ "\n",
+ "\n",
+ "def _tol(v): # deck rounds to $0.1M — its own tables cross-foot ±$50-120K\n",
+ " return max(100_000, 0.015 * v)\n",
+ "\n",
+ "\n",
+ "for r, expect in SLIDE_TOTALS[\"regional_3yr\"].items():\n",
+ " got = verbatim.loc[verbatim.region == r, \"three_yr\"].sum()\n",
+ " assert abs(got - expect) <= _tol(expect), (r, got, expect)\n",
+ "for c, expect in SLIDE_TOTALS[\"capability_3yr\"].items():\n",
+ " got = verbatim.loc[verbatim.capability == c, \"three_yr\"].sum()\n",
+ " assert abs(got - expect) <= _tol(expect), (c, got, expect)\n",
+ "assert abs(verbatim[\"three_yr\"].sum() - SLIDE_TOTALS[\"total_3yr\"]) <= _tol(15e6)\n",
+ "assert abs(verbatim[\"annual\"].sum() - SLIDE_TOTALS[\"total_annual\"]) <= _tol(13.6e6)\n",
+ "print(f\"cross-foot OK — cell grain sums to {money(verbatim['three_yr'].sum())} 3-yr / \"\n",
+ " f\"{money(verbatim['annual'].sum())} annual (deck headline: $15.0M / $13.6M — deck rounding)\")\n",
+ "\n",
+ "display(verbatim.pivot_table(index=\"capability\", columns=\"region\", values=\"three_yr\",\n",
+ " aggfunc=\"sum\", margins=True, margins_name=\"TOTAL\")\n",
+ " .reindex([*CAPABILITIES, \"TOTAL\"]))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "67842fd4",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-07-07T12:22:42.579539Z",
+ "iopub.status.busy": "2026-07-07T12:22:42.579402Z",
+ "iopub.status.idle": "2026-07-07T12:22:42.590942Z",
+ "shell.execute_reply": "2026-07-07T12:22:42.590120Z"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "NA Email exception: implemented Jan 2027, realizes Apr 2027\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " implemented | \n",
+ " benefits realize | \n",
+ " token months 2026 | \n",
+ " token months 2027 | \n",
+ " token months 2028 | \n",
+ " benefit months 2026 | \n",
+ " benefit months 2027 | \n",
+ " benefit months 2028 | \n",
+ "
\n",
+ " \n",
+ " | region | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | NA | \n",
+ " Jun 2027 (m18) | \n",
+ " Sep 2027 (m21) | \n",
+ " 0 | \n",
+ " 7 | \n",
+ " 12 | \n",
+ " 0 | \n",
+ " 4 | \n",
+ " 12 | \n",
+ "
\n",
+ " \n",
+ " | ANZ | \n",
+ " Sep 2027 (m21) | \n",
+ " Dec 2027 (m24) | \n",
+ " 0 | \n",
+ " 4 | \n",
+ " 12 | \n",
+ " 0 | \n",
+ " 1 | \n",
+ " 12 | \n",
+ "
\n",
+ " \n",
+ " | EMEA | \n",
+ " Dec 2027 (m24) | \n",
+ " Mar 2028 (m27) | \n",
+ " 0 | \n",
+ " 1 | \n",
+ " 12 | \n",
+ " 0 | \n",
+ " 0 | \n",
+ " 10 | \n",
+ "
\n",
+ " \n",
+ " | ASIA | \n",
+ " Mar 2028 (m27) | \n",
+ " Jun 2028 (m30) | \n",
+ " 0 | \n",
+ " 0 | \n",
+ " 10 | \n",
+ " 0 | \n",
+ " 0 | \n",
+ " 7 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " implemented benefits realize token months 2026 token months 2027 \\\n",
+ "region \n",
+ "NA Jun 2027 (m18) Sep 2027 (m21) 0 7 \n",
+ "ANZ Sep 2027 (m21) Dec 2027 (m24) 0 4 \n",
+ "EMEA Dec 2027 (m24) Mar 2028 (m27) 0 1 \n",
+ "ASIA Mar 2028 (m27) Jun 2028 (m30) 0 0 \n",
+ "\n",
+ " token months 2028 benefit months 2026 benefit months 2027 \\\n",
+ "region \n",
+ "NA 12 0 4 \n",
+ "ANZ 12 0 1 \n",
+ "EMEA 12 0 0 \n",
+ "ASIA 10 0 0 \n",
+ "\n",
+ " benefit months 2028 \n",
+ "region \n",
+ "NA 12 \n",
+ "ANZ 12 \n",
+ "EMEA 10 \n",
+ "ASIA 7 "
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# ── Genesys/Broadreach deployment schedule (slides 17-21) ────────────\n",
+ "IMPL_MONTH = {\"NA\": 18, \"ANZ\": 21, \"EMEA\": 24, \"ASIA\": 27} # months from Jan 2026, inclusive\n",
+ "BENEFIT_LAG_MONTHS = 3\n",
+ "REALIZE_MONTH = {r: m + BENEFIT_LAG_MONTHS for r, m in IMPL_MONTH.items()}\n",
+ "NA_EMAIL_EARLY = True # NA Gantt exception: Email realizes Apr 2027\n",
+ "NA_EMAIL_IMPL_MONTH = 13 # ⇒ implemented Jan 2027, realizes month 16\n",
+ "\n",
+ "_MONTHS = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\",\n",
+ " \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"]\n",
+ "\n",
+ "\n",
+ "def month_label(m): # m is 1-indexed from Jan 2026\n",
+ " return f\"{_MONTHS[(m - 1) % 12]} {2026 + (m - 1) // 12}\"\n",
+ "\n",
+ "\n",
+ "# go_live_month = label − 1 so the labelled month is included (see §2 note).\n",
+ "TOKEN_ROLLOUT = RolloutPlan(\n",
+ " contract_start=\"2026-01\", build_months=27, ramp_months=RAMP_MONTHS,\n",
+ " first_year_platform_discount=0.0, # licences handled verbatim in §1, not by this plan\n",
+ " go_live_month={\"NAM\": IMPL_MONTH[\"NA\"] - 1, \"AUZ\": IMPL_MONTH[\"ANZ\"] - 1,\n",
+ " \"EMEA\": IMPL_MONTH[\"EMEA\"] - 1,\n",
+ " **{n: IMPL_MONTH[\"ASIA\"] - 1 for n in ASIA_SITES}},\n",
+ ")\n",
+ "EMAIL_TOKEN_ROLLOUT = dataclasses.replace( # Auto-Respond only: NA implements early\n",
+ " TOKEN_ROLLOUT,\n",
+ " go_live_month={**TOKEN_ROLLOUT.go_live_month,\n",
+ " \"NAM\": (NA_EMAIL_IMPL_MONTH - 1) if NA_EMAIL_EARLY else IMPL_MONTH[\"NA\"] - 1},\n",
+ ")\n",
+ "BENEFIT_ROLLOUT = RolloutPlan( # region-keyed; NA_EMAIL covers the (NA, Email) cell only\n",
+ " first_year_platform_discount=0.0,\n",
+ " go_live_month={**{r: REALIZE_MONTH[r] - 1 for r in REGIONS},\n",
+ " \"NA_EMAIL\": (NA_EMAIL_IMPL_MONTH + BENEFIT_LAG_MONTHS - 1)\n",
+ " if NA_EMAIL_EARLY else REALIZE_MONTH[\"NA\"] - 1},\n",
+ ")\n",
+ "\n",
+ "schedule = pd.DataFrame([\n",
+ " {\n",
+ " \"region\": r,\n",
+ " \"implemented\": f\"{month_label(IMPL_MONTH[r])} (m{IMPL_MONTH[r]})\",\n",
+ " \"benefits realize\": f\"{month_label(REALIZE_MONTH[r])} (m{REALIZE_MONTH[r]})\",\n",
+ " **{f\"token months {y}\": TOKEN_ROLLOUT.live_months_in_year(REGION_SITES[r][0], YEAR_INDEX[y])\n",
+ " for y in YEARS},\n",
+ " **{f\"benefit months {y}\": BENEFIT_ROLLOUT.live_months_in_year(r, YEAR_INDEX[y])\n",
+ " for y in YEARS},\n",
+ " }\n",
+ " for r in REGIONS\n",
+ "]).set_index(\"region\")\n",
+ "if NA_EMAIL_EARLY:\n",
+ " print(f\"NA Email exception: implemented {month_label(NA_EMAIL_IMPL_MONTH)}, \"\n",
+ " f\"realizes {month_label(NA_EMAIL_IMPL_MONTH + BENEFIT_LAG_MONTHS)}\")\n",
+ "display(schedule)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "618205b4",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-07-07T12:22:42.593342Z",
+ "iopub.status.busy": "2026-07-07T12:22:42.593199Z",
+ "iopub.status.idle": "2026-07-07T12:22:42.650436Z",
+ "shell.execute_reply": "2026-07-07T12:22:42.649806Z"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "benefits by year: {2026: '$0K', 2027: '$2.6M', 2028: '$12.4M'}\n",
+ "⚠ 2026 benefits are $0 — the honest consequence of the deck's own deployment schedule.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | year | \n",
+ " 2026 | \n",
+ " 2027 | \n",
+ " 2028 | \n",
+ " TOTAL | \n",
+ "
\n",
+ " \n",
+ " | capability | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | Agent Copilot | \n",
+ " 0 | \n",
+ " 1,150,000 | \n",
+ " 6,214,000 | \n",
+ " 7,364,000 | \n",
+ "
\n",
+ " \n",
+ " | WFM | \n",
+ " 0 | \n",
+ " 107,692 | \n",
+ " 2,893,308 | \n",
+ " 3,001,000 | \n",
+ "
\n",
+ " \n",
+ " | Email | \n",
+ " 0 | \n",
+ " 1,082,429 | \n",
+ " 1,888,571 | \n",
+ " 2,971,000 | \n",
+ "
\n",
+ " \n",
+ " | STA | \n",
+ " 0 | \n",
+ " 134,577 | \n",
+ " 679,423 | \n",
+ " 814,000 | \n",
+ "
\n",
+ " \n",
+ " | Predictive Routing | \n",
+ " 0 | \n",
+ " 64,981 | \n",
+ " 461,019 | \n",
+ " 526,000 | \n",
+ "
\n",
+ " \n",
+ " | Supervisor Copilot | \n",
+ " 0 | \n",
+ " 74,827 | \n",
+ " 292,173 | \n",
+ " 367,000 | \n",
+ "
\n",
+ " \n",
+ " | TOTAL | \n",
+ " 0 | \n",
+ " 2,614,505 | \n",
+ " 12,428,495 | \n",
+ " 15,043,000 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ "year 2026 2027 2028 TOTAL\n",
+ "capability \n",
+ "Agent Copilot 0 1,150,000 6,214,000 7,364,000\n",
+ "WFM 0 107,692 2,893,308 3,001,000\n",
+ "Email 0 1,082,429 1,888,571 2,971,000\n",
+ "STA 0 134,577 679,423 814,000\n",
+ "Predictive Routing 0 64,981 461,019 526,000\n",
+ "Supervisor Copilot 0 74,827 292,173 367,000\n",
+ "TOTAL 0 2,614,505 12,428,495 15,043,000"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# ── Phase each verbatim 3-yr value by its region's realization window ─\n",
+ "rows = []\n",
+ "for (region, cap), (annual, three_yr) in VERBATIM_BENEFITS.items():\n",
+ " key = \"NA_EMAIL\" if (region == \"NA\" and cap == \"Email\" and NA_EMAIL_EARLY) else region\n",
+ " live = [BENEFIT_ROLLOUT.live_months_in_year(key, YEAR_INDEX[y]) for y in YEARS]\n",
+ " total_live = sum(live)\n",
+ " for y, m in zip(YEARS, live):\n",
+ " rows.append({\"region\": region, \"capability\": cap, \"year\": y,\n",
+ " \"benefit\": three_yr * m / total_live if total_live else 0.0})\n",
+ "benefits_long = pd.DataFrame(rows)\n",
+ "benefit_total_by_year = benefits_long.groupby(\"year\")[\"benefit\"].sum().to_dict()\n",
+ "\n",
+ "# Scaling is at the finest grain, so every verbatim total is reproduced exactly.\n",
+ "assert benefit_total_by_year[2026] == 0.0, \"2026 must be $0 under Genesys's own schedule\"\n",
+ "for (region, cap), (_, three_yr) in VERBATIM_BENEFITS.items():\n",
+ " got = benefits_long.query(\"region == @region and capability == @cap\")[\"benefit\"].sum()\n",
+ " assert abs(got - three_yr) < 1e-6, (region, cap)\n",
+ "\n",
+ "print(f\"benefits by year: { {y: money(v) for y, v in benefit_total_by_year.items()} }\")\n",
+ "print(\"⚠ 2026 benefits are $0 — the honest consequence of the deck's own deployment schedule.\")\n",
+ "display(benefits_long.pivot_table(index=\"capability\", columns=\"year\", values=\"benefit\",\n",
+ " aggfunc=\"sum\", margins=True, margins_name=\"TOTAL\")\n",
+ " .reindex([*CAPABILITIES, \"TOTAL\"]))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f47fdc0f",
+ "metadata": {},
+ "source": [
+ "## §3 · Feature enablement → token consumption (missing cost #1)\n",
+ "\n",
+ "Every deck capability maps to Genesys AI Experience token meters (all 🟢 published rates unless\n",
+ "noted), consumed from each region's **implementation month** — three months *before* benefits:\n",
+ "\n",
+ "| Deck capability | Token meter(s) | Treatment |\n",
+ "|---|---|---|\n",
+ "| Agent Copilot | Agent Copilot **[named]** — 40 tokens/user/mo | Per V2 correction #1, this **includes email/chat Auto-Suggest** and interaction summarization. Scoped to NA/ANZ/EMEA by default (ASIA claims $0 Copilot benefit — toggle below). |\n",
+ "| Email | Email AI (**Auto-Respond**) — per message | 🔴→🟡 rate not published; working assumption below (anchored to Genesys Cloud Copilot's 20 AI actions/token), deck's 25.5% auto-respond rate. Sensitivity in §9. |\n",
+ "| STA | Speech & Text Analytics **[named]** — 30 tokens/user/mo | All sites. |\n",
+ "| Predictive Routing | Predictive Routing — 17 routed interactions/token | All sites; `PR_ELIGIBILITY` scopes to PR-enabled queue share. |\n",
+ "| Supervisor Copilot | AI Summary & Insights (**$0 by Rule 1** — Copilot's rate already covers summarization) + AI Translate as small-volume proxy | Kept visible to show the coverage rule. |\n",
+ "| WFM | **No token meter** — WEM is licence-included | $0 row; impl effort assumed inside the base $2.4M PS (no V2 line). |\n",
+ "\n",
+ "Consumption uses a **claim-level scenario** (no maturity haircut — Genesys claims none) and bills\n",
+ "with Genesys's monthly `ceil` token rounding, per-region pricing at $1.00/token US list (🔴 contracted\n",
+ "rates not sourced)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "99e510d0",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-07-07T12:22:42.652747Z",
+ "iopub.status.busy": "2026-07-07T12:22:42.652584Z",
+ "iopub.status.idle": "2026-07-07T12:22:42.656004Z",
+ "shell.execute_reply": "2026-07-07T12:22:42.655407Z"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "# ── CONFIG — token model knobs ───────────────────────────────────────\n",
+ "COPILOT_INCLUDES_ASIA = False # deck claims $0 Copilot benefit in ASIA → excluded by default\n",
+ "EMAIL_AUTO_RESPOND_RATE = 0.255 # deck: \"Reduced Email Interactions with Auto-Respond 25.5%\"\n",
+ "EMAIL_AUTORESPOND_TOKENS_PER_MSG = 0.05 # 🟡 ≈ one AI action per generated response (20/token)\n",
+ "PR_ELIGIBILITY = 1.0 # share of voice volume on PR-enabled queues\n",
+ "AI_TRANSLATE_ELIGIBILITY = 0.01 # 🟡 supervisor-evaluation slice of interactions\n",
+ "USE_CONTRACTED_RATES = False # no contracted token rate sourced yet"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "3ea04102",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-07-07T12:22:42.657915Z",
+ "iopub.status.busy": "2026-07-07T12:22:42.657753Z",
+ "iopub.status.idle": "2026-07-07T12:22:42.671127Z",
+ "shell.execute_reply": "2026-07-07T12:22:42.670305Z"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " feature | \n",
+ " meter_type | \n",
+ " units_per_token | \n",
+ " tokens_per_unit | \n",
+ " confidence | \n",
+ " notes | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " Agent Copilot [named] | \n",
+ " per_user_per_month | \n",
+ " NaN | \n",
+ " 40 | \n",
+ " 🟢 confirmed | \n",
+ " 40 tokens per named user per month. Includes i... | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " Speech & Text Analytics [named] | \n",
+ " per_user_per_month | \n",
+ " NaN | \n",
+ " 30 | \n",
+ " 🟢 confirmed | \n",
+ " STA named licence; 30 tokens per named user pe... | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " Predictive Routing | \n",
+ " per_interaction | \n",
+ " 17 | \n",
+ " 0 | \n",
+ " 🟢 confirmed | \n",
+ " Predictive routing; 17 routes per token. | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " AI Summary & Insights | \n",
+ " per_summary | \n",
+ " 50 | \n",
+ " 0 | \n",
+ " 🟢 confirmed | \n",
+ " Supervisor standalone summarization; 50 summar... | \n",
+ "
\n",
+ " \n",
+ " | 4 | \n",
+ " AI Translate | \n",
+ " per_interaction | \n",
+ " 2 | \n",
+ " 0 | \n",
+ " 🟢 confirmed | \n",
+ " AI translation; 2 translations per token. | \n",
+ "
\n",
+ " \n",
+ " | 5 | \n",
+ " Email AI (Auto-Respond) | \n",
+ " per_message | \n",
+ " 20 | \n",
+ " 0 | \n",
+ " 🟡 estimated | \n",
+ " WORKING ASSUMPTION — rate unpublished; ≈1 AI a... | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " feature meter_type units_per_token \\\n",
+ "0 Agent Copilot [named] per_user_per_month NaN \n",
+ "1 Speech & Text Analytics [named] per_user_per_month NaN \n",
+ "2 Predictive Routing per_interaction 17 \n",
+ "3 AI Summary & Insights per_summary 50 \n",
+ "4 AI Translate per_interaction 2 \n",
+ "5 Email AI (Auto-Respond) per_message 20 \n",
+ "\n",
+ " tokens_per_unit confidence \\\n",
+ "0 40 🟢 confirmed \n",
+ "1 30 🟢 confirmed \n",
+ "2 0 🟢 confirmed \n",
+ "3 0 🟢 confirmed \n",
+ "4 0 🟢 confirmed \n",
+ "5 0 🟡 estimated \n",
+ "\n",
+ " notes \n",
+ "0 40 tokens per named user per month. Includes i... \n",
+ "1 STA named licence; 30 tokens per named user pe... \n",
+ "2 Predictive routing; 17 routes per token. \n",
+ "3 Supervisor standalone summarization; 50 summar... \n",
+ "4 AI translation; 2 translations per token. \n",
+ "5 WORKING ASSUMPTION — rate unpublished; ≈1 AI a... "
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# ── Claim-level scenario: deck parameters, no consumption ramp ───────\n",
+ "GENESYS_CLAIM = Scenario(\n",
+ " name=\"genesys-claim\",\n",
+ " voice_bot_deflection=0.0, voice_bot_avg_minutes=0.0, agentic_va_deflection=0.0,\n",
+ " voice_summarization_eligibility=0.0, voice_knowledge_eligibility=0.0, # unused by this scope set\n",
+ " email_auto_respond_rate=EMAIL_AUTO_RESPOND_RATE,\n",
+ " email_auto_suggest_acceptance=0.0, # Auto-Suggest is inside Copilot's per-user rate (V2 #1)\n",
+ " consumption_cost_realization={1: 1.0, 2: 1.0, 3: 1.0}, # Genesys claims no maturity ramp\n",
+ ")\n",
+ "\n",
+ "AUTORESPOND_METER = dataclasses.replace(\n",
+ " DEFAULT_METERS[\"Email AI (Auto-Respond)\"],\n",
+ " units_per_token=1.0 / EMAIL_AUTORESPOND_TOKENS_PER_MSG,\n",
+ " tokens_per_unit=EMAIL_AUTORESPOND_TOKENS_PER_MSG,\n",
+ " confidence=Confidence.ESTIMATED,\n",
+ " notes=\"WORKING ASSUMPTION — rate unpublished; ≈1 AI action per generated response \"\n",
+ " \"(Genesys Cloud Copilot meters 20 AI actions/token). Sensitivity in §9.\",\n",
+ ")\n",
+ "METERS = {**DEFAULT_METERS, \"Email AI (Auto-Respond)\": AUTORESPOND_METER}\n",
+ "\n",
+ "COPILOT_SITES = [\"NAM\", \"AUZ\", \"EMEA\"] + (ASIA_SITES if COPILOT_INCLUDES_ASIA else [])\n",
+ "# NOTE: no adoption_curve on any scope — a curve would silently override the\n",
+ "# claim scenario's flat consumption realization in calculate_consumption_ai_cost.\n",
+ "CORE_SCOPES = [\n",
+ " FeatureScope(\"Agent Copilot [named]\", COPILOT_SITES, phase=1),\n",
+ " FeatureScope(\"Speech & Text Analytics [named]\", ALL_SITES, phase=1),\n",
+ " FeatureScope(\"Predictive Routing\", ALL_SITES, phase=1, eligibility_pct=PR_ELIGIBILITY),\n",
+ " FeatureScope(\"AI Summary & Insights\", COPILOT_SITES, phase=1), # $0 by Rule 1 — kept visible\n",
+ " FeatureScope(\"AI Translate\", ASIA_SITES + [\"EMEA\"], phase=1,\n",
+ " eligibility_pct=AI_TRANSLATE_ELIGIBILITY),\n",
+ "]\n",
+ "EMAIL_SCOPES = [FeatureScope(\"Email AI (Auto-Respond)\", ALL_SITES, phase=1)]\n",
+ "\n",
+ "USED_FEATURES = [sc.feature for sc in CORE_SCOPES + EMAIL_SCOPES]\n",
+ "display(meters_dataframe({f: METERS[f] for f in USED_FEATURES})\n",
+ " .drop(columns=[\"source\"]))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "7727fbb0",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-07-07T12:22:42.672905Z",
+ "iopub.status.busy": "2026-07-07T12:22:42.672732Z",
+ "iopub.status.idle": "2026-07-07T12:22:42.717246Z",
+ "shell.execute_reply": "2026-07-07T12:22:42.716200Z"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "AI token consumption (missing cost #1): {2026: '$0K', 2027: '$1.2M', 2028: '$3.3M'} → 3-yr $4.5M\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | year | \n",
+ " conf | \n",
+ " 2026 | \n",
+ " 2027 | \n",
+ " 2028 | \n",
+ " 3-yr | \n",
+ "
\n",
+ " \n",
+ " | cost_line | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | Predictive Routing | \n",
+ " 🟢 | \n",
+ " 0 | \n",
+ " 583,561 | \n",
+ " 1,764,870 | \n",
+ " 2,348,431 | \n",
+ "
\n",
+ " \n",
+ " | Agent Copilot [named] | \n",
+ " 🟢 | \n",
+ " 0 | \n",
+ " 311,000 | \n",
+ " 715,200 | \n",
+ " 1,026,200 | \n",
+ "
\n",
+ " \n",
+ " | Speech & Text Analytics [named] | \n",
+ " 🟢 | \n",
+ " 0 | \n",
+ " 233,250 | \n",
+ " 715,800 | \n",
+ " 949,050 | \n",
+ "
\n",
+ " \n",
+ " | Email AI (Auto-Respond) | \n",
+ " 🟡 | \n",
+ " 0 | \n",
+ " 46,272 | \n",
+ " 87,136 | \n",
+ " 133,408 | \n",
+ "
\n",
+ " \n",
+ " | AI Translate | \n",
+ " 🟢 | \n",
+ " 0 | \n",
+ " 2,100 | \n",
+ " 62,150 | \n",
+ " 64,250 | \n",
+ "
\n",
+ " \n",
+ " | AI Summary & Insights | \n",
+ " 🟢 | \n",
+ " 0 | \n",
+ " 0 | \n",
+ " 0 | \n",
+ " 0 | \n",
+ "
\n",
+ " \n",
+ " | WFM (no token meter — licence-included) | \n",
+ " 🟢 | \n",
+ " 0 | \n",
+ " 0 | \n",
+ " 0 | \n",
+ " 0 | \n",
+ "
\n",
+ " \n",
+ " | TOTAL | \n",
+ " | \n",
+ " 0 | \n",
+ " 1,176,183 | \n",
+ " 3,345,156 | \n",
+ " 4,521,339 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ "year conf 2026 2027 2028 \\\n",
+ "cost_line \n",
+ "Predictive Routing 🟢 0 583,561 1,764,870 \n",
+ "Agent Copilot [named] 🟢 0 311,000 715,200 \n",
+ "Speech & Text Analytics [named] 🟢 0 233,250 715,800 \n",
+ "Email AI (Auto-Respond) 🟡 0 46,272 87,136 \n",
+ "AI Translate 🟢 0 2,100 62,150 \n",
+ "AI Summary & Insights 🟢 0 0 0 \n",
+ "WFM (no token meter — licence-included) 🟢 0 0 0 \n",
+ "TOTAL 0 1,176,183 3,345,156 \n",
+ "\n",
+ "year 3-yr \n",
+ "cost_line \n",
+ "Predictive Routing 2,348,431 \n",
+ "Agent Copilot [named] 1,026,200 \n",
+ "Speech & Text Analytics [named] 949,050 \n",
+ "Email AI (Auto-Respond) 133,408 \n",
+ "AI Translate 64,250 \n",
+ "AI Summary & Insights 0 \n",
+ "WFM (no token meter — licence-included) 0 \n",
+ "TOTAL 4,521,339 "
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# ── Token cost by year — engine call, rollout-gated ──────────────────\n",
+ "frames = []\n",
+ "for y in YEARS:\n",
+ " core = calculate_total_cost(sites, CORE_SCOPES, METERS, DEFAULT_PRICING,\n",
+ " GENESYS_CLAIM, YEAR_INDEX[y], include_platform=False,\n",
+ " use_contracted=USE_CONTRACTED_RATES, rollout=TOKEN_ROLLOUT)\n",
+ " email = calculate_total_cost(sites, EMAIL_SCOPES, METERS, DEFAULT_PRICING,\n",
+ " GENESYS_CLAIM, YEAR_INDEX[y], include_platform=False,\n",
+ " use_contracted=USE_CONTRACTED_RATES, rollout=EMAIL_TOKEN_ROLLOUT)\n",
+ " part = pd.concat([core, email], ignore_index=True)\n",
+ " part[\"year\"] = y\n",
+ " frames.append(part)\n",
+ "tokens_long = pd.concat(frames, ignore_index=True)\n",
+ "\n",
+ "tokens_pivot = tokens_long.pivot_table(index=\"cost_line\", columns=\"year\",\n",
+ " values=\"annual_cost\", aggfunc=\"sum\")\n",
+ "tokens_pivot.loc[\"WFM (no token meter — licence-included)\"] = [0.0, 0.0, 0.0]\n",
+ "tokens_pivot[\"3-yr\"] = tokens_pivot.sum(axis=1)\n",
+ "tokens_pivot = tokens_pivot.sort_values(\"3-yr\", ascending=False)\n",
+ "tokens_pivot.loc[\"TOTAL\"] = tokens_pivot.sum()\n",
+ "token_total_by_year = {y: float(tokens_pivot.loc[\"TOTAL\", y]) for y in YEARS}\n",
+ "\n",
+ "conf = {f: METERS[f].confidence.icon for f in USED_FEATURES}\n",
+ "tokens_pivot.insert(0, \"conf\", [conf.get(i, \"🟢\") for i in tokens_pivot.index[:-1]] + [\"\"])\n",
+ "print(f\"AI token consumption (missing cost #1): \"\n",
+ " f\"{ {y: money(v) for y, v in token_total_by_year.items()} } \"\n",
+ " f\"→ 3-yr {money(sum(token_total_by_year.values()))}\")\n",
+ "display(tokens_pivot)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "id": "ee088f69",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-07-07T12:22:42.720863Z",
+ "iopub.status.busy": "2026-07-07T12:22:42.720525Z",
+ "iopub.status.idle": "2026-07-07T12:22:43.918533Z",
+ "shell.execute_reply": "2026-07-07T12:22:43.917721Z"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.plotly.v1+json": {
+ "config": {
+ "plotlyServerURL": "https://plot.ly"
+ },
+ "data": [
+ {
+ "hovertemplate": "%{fullData.name}: %{y:$,.0f}",
+ "marker": {
+ "color": "#2a78d6",
+ "line": {
+ "color": "#fcfcfb",
+ "width": 2
+ }
+ },
+ "name": "Agent Copilot [named]",
+ "type": "bar",
+ "x": [
+ "2026",
+ "2027",
+ "2028"
+ ],
+ "y": [
+ 0.0,
+ 311000.0,
+ 715200.0
+ ]
+ },
+ {
+ "hovertemplate": "%{fullData.name}: %{y:$,.0f}",
+ "marker": {
+ "color": "#008300",
+ "line": {
+ "color": "#fcfcfb",
+ "width": 2
+ }
+ },
+ "name": "Speech & Text Analytics [named]",
+ "type": "bar",
+ "x": [
+ "2026",
+ "2027",
+ "2028"
+ ],
+ "y": [
+ 0.0,
+ 233250.0,
+ 715800.0
+ ]
+ },
+ {
+ "hovertemplate": "%{fullData.name}: %{y:$,.0f}",
+ "marker": {
+ "color": "#4a3aa7",
+ "line": {
+ "color": "#fcfcfb",
+ "width": 2
+ }
+ },
+ "name": "Predictive Routing",
+ "type": "bar",
+ "x": [
+ "2026",
+ "2027",
+ "2028"
+ ],
+ "y": [
+ 0.0,
+ 583561.0,
+ 1764870.0
+ ]
+ },
+ {
+ "hovertemplate": "%{fullData.name}: %{y:$,.0f}",
+ "marker": {
+ "color": "#eda100",
+ "line": {
+ "color": "#fcfcfb",
+ "width": 2
+ }
+ },
+ "name": "Email AI (Auto-Respond)",
+ "type": "bar",
+ "x": [
+ "2026",
+ "2027",
+ "2028"
+ ],
+ "y": [
+ 0.0,
+ 46272.0,
+ 87136.0
+ ]
+ },
+ {
+ "hovertemplate": "%{fullData.name}: %{y:$,.0f}",
+ "marker": {
+ "color": "#e34948",
+ "line": {
+ "color": "#fcfcfb",
+ "width": 2
+ }
+ },
+ "name": "AI Translate",
+ "type": "bar",
+ "x": [
+ "2026",
+ "2027",
+ "2028"
+ ],
+ "y": [
+ 0.0,
+ 2100.0,
+ 62150.0
+ ]
+ }
+ ],
+ "layout": {
+ "annotations": [
+ {
+ "font": {
+ "color": "#0b0b0b",
+ "size": 12
+ },
+ "showarrow": false,
+ "text": "$0K",
+ "x": 0,
+ "y": 0.0,
+ "yshift": 12
+ },
+ {
+ "font": {
+ "color": "#0b0b0b",
+ "size": 12
+ },
+ "showarrow": false,
+ "text": "$1.2M",
+ "x": 1,
+ "y": 1176183.0,
+ "yshift": 12
+ },
+ {
+ "font": {
+ "color": "#0b0b0b",
+ "size": 12
+ },
+ "showarrow": false,
+ "text": "$3.3M",
+ "x": 2,
+ "y": 3345156.0,
+ "yshift": 12
+ }
+ ],
+ "bargap": 0.45,
+ "barmode": "stack",
+ "font": {
+ "color": "#52514e",
+ "family": "system-ui, -apple-system, \"Segoe UI\", sans-serif",
+ "size": 12
+ },
+ "height": 460,
+ "hovermode": "x unified",
+ "legend": {
+ "font": {
+ "color": "#52514e",
+ "size": 11
+ },
+ "orientation": "h",
+ "x": 0,
+ "y": -0.1,
+ "yanchor": "top"
+ },
+ "margin": {
+ "b": 80,
+ "l": 70,
+ "r": 30,
+ "t": 70
+ },
+ "paper_bgcolor": "#fcfcfb",
+ "plot_bgcolor": "#fcfcfb",
+ "template": {
+ "data": {
+ "bar": [
+ {
+ "error_x": {
+ "color": "#2a3f5f"
+ },
+ "error_y": {
+ "color": "#2a3f5f"
+ },
+ "marker": {
+ "line": {
+ "color": "#E5ECF6",
+ "width": 0.5
+ },
+ "pattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ }
+ },
+ "type": "bar"
+ }
+ ],
+ "barpolar": [
+ {
+ "marker": {
+ "line": {
+ "color": "#E5ECF6",
+ "width": 0.5
+ },
+ "pattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ }
+ },
+ "type": "barpolar"
+ }
+ ],
+ "carpet": [
+ {
+ "aaxis": {
+ "endlinecolor": "#2a3f5f",
+ "gridcolor": "white",
+ "linecolor": "white",
+ "minorgridcolor": "white",
+ "startlinecolor": "#2a3f5f"
+ },
+ "baxis": {
+ "endlinecolor": "#2a3f5f",
+ "gridcolor": "white",
+ "linecolor": "white",
+ "minorgridcolor": "white",
+ "startlinecolor": "#2a3f5f"
+ },
+ "type": "carpet"
+ }
+ ],
+ "choropleth": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "type": "choropleth"
+ }
+ ],
+ "contour": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "contour"
+ }
+ ],
+ "contourcarpet": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "type": "contourcarpet"
+ }
+ ],
+ "heatmap": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "heatmap"
+ }
+ ],
+ "histogram": [
+ {
+ "marker": {
+ "pattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ }
+ },
+ "type": "histogram"
+ }
+ ],
+ "histogram2d": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "histogram2d"
+ }
+ ],
+ "histogram2dcontour": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "histogram2dcontour"
+ }
+ ],
+ "mesh3d": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "type": "mesh3d"
+ }
+ ],
+ "parcoords": [
+ {
+ "line": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "parcoords"
+ }
+ ],
+ "pie": [
+ {
+ "automargin": true,
+ "type": "pie"
+ }
+ ],
+ "scatter": [
+ {
+ "fillpattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ },
+ "type": "scatter"
+ }
+ ],
+ "scatter3d": [
+ {
+ "line": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatter3d"
+ }
+ ],
+ "scattercarpet": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattercarpet"
+ }
+ ],
+ "scattergeo": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattergeo"
+ }
+ ],
+ "scattergl": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattergl"
+ }
+ ],
+ "scattermap": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattermap"
+ }
+ ],
+ "scattermapbox": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattermapbox"
+ }
+ ],
+ "scatterpolar": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatterpolar"
+ }
+ ],
+ "scatterpolargl": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatterpolargl"
+ }
+ ],
+ "scatterternary": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatterternary"
+ }
+ ],
+ "surface": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "surface"
+ }
+ ],
+ "table": [
+ {
+ "cells": {
+ "fill": {
+ "color": "#EBF0F8"
+ },
+ "line": {
+ "color": "white"
+ }
+ },
+ "header": {
+ "fill": {
+ "color": "#C8D4E3"
+ },
+ "line": {
+ "color": "white"
+ }
+ },
+ "type": "table"
+ }
+ ]
+ },
+ "layout": {
+ "annotationdefaults": {
+ "arrowcolor": "#2a3f5f",
+ "arrowhead": 0,
+ "arrowwidth": 1
+ },
+ "autotypenumbers": "strict",
+ "coloraxis": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "colorscale": {
+ "diverging": [
+ [
+ 0,
+ "#8e0152"
+ ],
+ [
+ 0.1,
+ "#c51b7d"
+ ],
+ [
+ 0.2,
+ "#de77ae"
+ ],
+ [
+ 0.3,
+ "#f1b6da"
+ ],
+ [
+ 0.4,
+ "#fde0ef"
+ ],
+ [
+ 0.5,
+ "#f7f7f7"
+ ],
+ [
+ 0.6,
+ "#e6f5d0"
+ ],
+ [
+ 0.7,
+ "#b8e186"
+ ],
+ [
+ 0.8,
+ "#7fbc41"
+ ],
+ [
+ 0.9,
+ "#4d9221"
+ ],
+ [
+ 1,
+ "#276419"
+ ]
+ ],
+ "sequential": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "sequentialminus": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ]
+ },
+ "colorway": [
+ "#636efa",
+ "#EF553B",
+ "#00cc96",
+ "#ab63fa",
+ "#FFA15A",
+ "#19d3f3",
+ "#FF6692",
+ "#B6E880",
+ "#FF97FF",
+ "#FECB52"
+ ],
+ "font": {
+ "color": "#2a3f5f"
+ },
+ "geo": {
+ "bgcolor": "white",
+ "lakecolor": "white",
+ "landcolor": "#E5ECF6",
+ "showlakes": true,
+ "showland": true,
+ "subunitcolor": "white"
+ },
+ "hoverlabel": {
+ "align": "left"
+ },
+ "hovermode": "closest",
+ "mapbox": {
+ "style": "light"
+ },
+ "paper_bgcolor": "white",
+ "plot_bgcolor": "#E5ECF6",
+ "polar": {
+ "angularaxis": {
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": ""
+ },
+ "bgcolor": "#E5ECF6",
+ "radialaxis": {
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": ""
+ }
+ },
+ "scene": {
+ "xaxis": {
+ "backgroundcolor": "#E5ECF6",
+ "gridcolor": "white",
+ "gridwidth": 2,
+ "linecolor": "white",
+ "showbackground": true,
+ "ticks": "",
+ "zerolinecolor": "white"
+ },
+ "yaxis": {
+ "backgroundcolor": "#E5ECF6",
+ "gridcolor": "white",
+ "gridwidth": 2,
+ "linecolor": "white",
+ "showbackground": true,
+ "ticks": "",
+ "zerolinecolor": "white"
+ },
+ "zaxis": {
+ "backgroundcolor": "#E5ECF6",
+ "gridcolor": "white",
+ "gridwidth": 2,
+ "linecolor": "white",
+ "showbackground": true,
+ "ticks": "",
+ "zerolinecolor": "white"
+ }
+ },
+ "shapedefaults": {
+ "line": {
+ "color": "#2a3f5f"
+ }
+ },
+ "ternary": {
+ "aaxis": {
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": ""
+ },
+ "baxis": {
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": ""
+ },
+ "bgcolor": "#E5ECF6",
+ "caxis": {
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": ""
+ }
+ },
+ "title": {
+ "x": 0.05
+ },
+ "xaxis": {
+ "automargin": true,
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": "",
+ "title": {
+ "standoff": 15
+ },
+ "zerolinecolor": "white",
+ "zerolinewidth": 2
+ },
+ "yaxis": {
+ "automargin": true,
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": "",
+ "title": {
+ "standoff": 15
+ },
+ "zerolinecolor": "white",
+ "zerolinewidth": 2
+ }
+ }
+ },
+ "title": {
+ "font": {
+ "color": "#0b0b0b",
+ "size": 16
+ },
+ "text": "AI Experience token consumption — absent from the Genesys cost case
Claim-level usage · published meter rates · $1.00/token US list · consumption starts at each region's implementation month",
+ "x": 0.02,
+ "xanchor": "left"
+ },
+ "xaxis": {
+ "linecolor": "#c3c2b7",
+ "showgrid": false,
+ "tickfont": {
+ "color": "#898781"
+ },
+ "type": "category"
+ },
+ "yaxis": {
+ "gridcolor": "#e1e0d9",
+ "tickfont": {
+ "color": "#898781"
+ },
+ "tickformat": "$~s",
+ "zerolinecolor": "#c3c2b7",
+ "zerolinewidth": 1.5
+ }
+ }
+ }
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# ── Chart: token cost by meter per year (stacked) ────────────────────\n",
+ "TOKEN_LINE_COLOR = { # entity colors consistent with the capability palette\n",
+ " \"Agent Copilot [named]\": CAP_COLOR[\"Agent Copilot\"],\n",
+ " \"Speech & Text Analytics [named]\": CAP_COLOR[\"STA\"],\n",
+ " \"Predictive Routing\": CAP_COLOR[\"Predictive Routing\"],\n",
+ " \"Email AI (Auto-Respond)\": CAP_COLOR[\"Email\"],\n",
+ " \"AI Translate\": CAP_COLOR[\"Supervisor Copilot\"],\n",
+ " \"AI Summary & Insights\": \"#e87ba4\",\n",
+ "}\n",
+ "X = [str(y) for y in YEARS]\n",
+ "fig = go.Figure()\n",
+ "for line, color in TOKEN_LINE_COLOR.items():\n",
+ " vals = [tokens_long.query(\"cost_line == @line and year == @y\")[\"annual_cost\"].sum()\n",
+ " for y in YEARS]\n",
+ " if sum(vals) > 0:\n",
+ " fig.add_trace(bar(X, vals, line, color))\n",
+ "for i, y in enumerate(YEARS): # annotations anchor by category index, not label\n",
+ " total = token_total_by_year[y]\n",
+ " fig.add_annotation(x=i, y=total, text=f\"{money(total)}\", showarrow=False,\n",
+ " yshift=12, font=dict(size=12, color=INK))\n",
+ "fig.update_layout(barmode=\"stack\")\n",
+ "tei_layout(fig, \"AI Experience token consumption — absent from the Genesys cost case\",\n",
+ " \"Claim-level usage · published meter rates · $1.00/token US list · \"\n",
+ " \"consumption starts at each region's implementation month\")\n",
+ "fig.show()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7d197edc",
+ "metadata": {},
+ "source": [
+ "## §4 · AI implementation effort (missing cost #2)\n",
+ "\n",
+ "The deck's $2.4M professional services covers **base platform implementation only** — none of the\n",
+ "five AI capabilities are turn-key. Hours below are the **V2 corrected LoE**\n",
+ "(`docs/ctm_ai_labour_estimate_V2.md`; V1 was vendor-inflated ~4×): configuration, tuning cycles and\n",
+ "enablement at CTM scale, not custom development. Per V2 correction #1, Agent Copilot's line\n",
+ "includes email/chat Auto-Suggest — one implementation.\n",
+ "\n",
+ "**Model (deliberately simple — the activity-level engine is deferred):**\n",
+ "hours(feature) × blended rate, allocated to each feature's scoped regions by agent share, spread\n",
+ "uniformly from contract start to that region's implementation month; **KB readiness** is a\n",
+ "separately-flagged prerequisite project; **steady-state** is an absolute 500–900 h/yr\n",
+ "(tuning, KB refresh, drift correction) booked program-level in 2027–2028."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "id": "4a7bd418",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-07-07T12:22:43.923450Z",
+ "iopub.status.busy": "2026-07-07T12:22:43.923326Z",
+ "iopub.status.idle": "2026-07-07T12:22:43.927251Z",
+ "shell.execute_reply": "2026-07-07T12:22:43.926625Z"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "# ── CONFIG — AI implementation (V2 LoE) ──────────────────────────────\n",
+ "AI_IMPL_HOURS = { # (low, high) Y1 hours — ctm_ai_labour_estimate_V2.md\n",
+ " \"Agent Copilot\": (1_200, 1_800), # voice + digital incl. email Auto-Suggest, all languages\n",
+ " \"Email Auto-Respond\": (800, 1_400), # separate flow; needs system-of-record integration\n",
+ " \"STA\": (800, 1_200), # topics, programs, tuning × 7 languages\n",
+ " \"Supervisor Copilot\": (200, 400),\n",
+ " \"Predictive Routing\": (400, 700),\n",
+ " \"Cross-cutting\": (1_000, 1_800), # governance, PM, test environment, integration coordination\n",
+ "}\n",
+ "KB_READINESS_HOURS = (500, 1_500) # prerequisite project — flagged separately\n",
+ "STEADY_STATE_HOURS = (500, 900) # absolute h/yr, 2027-2028\n",
+ "HOURS_MODE = \"mid\" # \"low\" | \"mid\" | \"high\"\n",
+ "BLENDED_RATE = 225 # $/h — 175 offshore-heavy | 225 typical | 275 onshore\n",
+ "INCLUDE_KB_READINESS = True\n",
+ "\n",
+ "IMPL_FEATURE_REGIONS = { # which regions each impl workstream serves\n",
+ " \"Agent Copilot\": [\"NA\", \"ANZ\", \"EMEA\"] + ([\"ASIA\"] if COPILOT_INCLUDES_ASIA else []),\n",
+ " \"Email Auto-Respond\": REGIONS,\n",
+ " \"STA\": REGIONS,\n",
+ " \"Supervisor Copilot\": [\"NA\", \"ANZ\", \"EMEA\"], # deck claims $0 SupCopilot benefit in ASIA\n",
+ " \"Predictive Routing\": REGIONS,\n",
+ " \"Cross-cutting\": REGIONS,\n",
+ "}"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "id": "afdcda65",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-07-07T12:22:43.929433Z",
+ "iopub.status.busy": "2026-07-07T12:22:43.929332Z",
+ "iopub.status.idle": "2026-07-07T12:22:43.944980Z",
+ "shell.execute_reply": "2026-07-07T12:22:43.944243Z"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "AI implementation (mid, $225/h): Y1 LoE $1.3M + KB readiness $225K + steady-state $315K (2027-28)\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " hours | \n",
+ " cost | \n",
+ " 2026 | \n",
+ " 2027 | \n",
+ " 2028 | \n",
+ "
\n",
+ " \n",
+ " | workstream | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | Agent Copilot | \n",
+ " 1,500 | \n",
+ " 337,500 | \n",
+ " 207,888 | \n",
+ " 129,612 | \n",
+ " 0 | \n",
+ "
\n",
+ " \n",
+ " | Cross-cutting | \n",
+ " 1,400 | \n",
+ " 315,000 | \n",
+ " 178,711 | \n",
+ " 126,366 | \n",
+ " 9,923 | \n",
+ "
\n",
+ " \n",
+ " | Email Auto-Respond | \n",
+ " 1,100 | \n",
+ " 247,500 | \n",
+ " 169,530 | \n",
+ " 70,174 | \n",
+ " 7,796 | \n",
+ "
\n",
+ " \n",
+ " | KB readiness (prerequisite) | \n",
+ " 1,000 | \n",
+ " 225,000 | \n",
+ " 127,651 | \n",
+ " 90,261 | \n",
+ " 7,088 | \n",
+ "
\n",
+ " \n",
+ " | Predictive Routing | \n",
+ " 550 | \n",
+ " 123,750 | \n",
+ " 70,208 | \n",
+ " 49,644 | \n",
+ " 3,898 | \n",
+ "
\n",
+ " \n",
+ " | STA | \n",
+ " 1,000 | \n",
+ " 225,000 | \n",
+ " 127,651 | \n",
+ " 90,261 | \n",
+ " 7,088 | \n",
+ "
\n",
+ " \n",
+ " | Supervisor Copilot | \n",
+ " 300 | \n",
+ " 67,500 | \n",
+ " 41,578 | \n",
+ " 25,922 | \n",
+ " 0 | \n",
+ "
\n",
+ " \n",
+ " | Steady-state tuning (2027-28) | \n",
+ " 1,400 | \n",
+ " 315,000 | \n",
+ " 0 | \n",
+ " 157,500 | \n",
+ " 157,500 | \n",
+ "
\n",
+ " \n",
+ " | TOTAL | \n",
+ " 8,250 | \n",
+ " 1,856,250 | \n",
+ " 923,217 | \n",
+ " 739,741 | \n",
+ " 193,293 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " hours cost 2026 2027 2028\n",
+ "workstream \n",
+ "Agent Copilot 1,500 337,500 207,888 129,612 0\n",
+ "Cross-cutting 1,400 315,000 178,711 126,366 9,923\n",
+ "Email Auto-Respond 1,100 247,500 169,530 70,174 7,796\n",
+ "KB readiness (prerequisite) 1,000 225,000 127,651 90,261 7,088\n",
+ "Predictive Routing 550 123,750 70,208 49,644 3,898\n",
+ "STA 1,000 225,000 127,651 90,261 7,088\n",
+ "Supervisor Copilot 300 67,500 41,578 25,922 0\n",
+ "Steady-state tuning (2027-28) 1,400 315,000 0 157,500 157,500\n",
+ "TOTAL 8,250 1,856,250 923,217 739,741 193,293"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# ── V2 hours-range × rate model (swap point for the future LoE engine) ─\n",
+ "def hours_pick(rng, mode):\n",
+ " low, high = rng\n",
+ " return {\"low\": low, \"mid\": (low + high) / 2, \"high\": high}[mode]\n",
+ "\n",
+ "\n",
+ "def impl_year_fractions(impl_month):\n",
+ " # Spend spreads uniformly from contract start (month 0) to the impl month.\n",
+ " prev, fracs = 0, []\n",
+ " for yi in (1, 2, 3):\n",
+ " cur = min(12 * yi, impl_month)\n",
+ " fracs.append((cur - prev) / impl_month)\n",
+ " prev = cur\n",
+ " return fracs\n",
+ "\n",
+ "\n",
+ "def region_impl_month(feature, region):\n",
+ " if feature == \"Email Auto-Respond\" and region == \"NA\" and NA_EMAIL_EARLY:\n",
+ " return NA_EMAIL_IMPL_MONTH\n",
+ " return IMPL_MONTH[region]\n",
+ "\n",
+ "\n",
+ "def build_impl_costs(mode, rate, include_kb):\n",
+ " rows = []\n",
+ " workstreams = dict(AI_IMPL_HOURS)\n",
+ " if include_kb:\n",
+ " workstreams[\"KB readiness (prerequisite)\"] = KB_READINESS_HOURS\n",
+ " for feature, rng in workstreams.items():\n",
+ " regions = IMPL_FEATURE_REGIONS.get(feature, REGIONS)\n",
+ " scope_agents = sum(agents_by_region[r] for r in regions)\n",
+ " for r in regions:\n",
+ " hours = hours_pick(rng, mode) * agents_by_region[r] / scope_agents\n",
+ " fracs = impl_year_fractions(region_impl_month(feature, r))\n",
+ " rows.append({\"workstream\": feature, \"region\": r, \"hours\": hours,\n",
+ " \"cost\": hours * rate,\n",
+ " **{y: hours * rate * f for y, f in zip(YEARS, fracs)}})\n",
+ " df = pd.DataFrame(rows)\n",
+ " is_kb = df[\"workstream\"].str.startswith(\"KB\")\n",
+ " impl_y = {y: float(df.loc[~is_kb, y].sum()) for y in YEARS}\n",
+ " kb_y = {y: float(df.loc[is_kb, y].sum()) for y in YEARS}\n",
+ " steady = hours_pick(STEADY_STATE_HOURS, mode) * rate\n",
+ " steady_y = {2026: 0.0, 2027: steady, 2028: steady}\n",
+ " return df, impl_y, kb_y, steady_y\n",
+ "\n",
+ "\n",
+ "impl_detail, impl_by_year, kb_by_year, steady_by_year = build_impl_costs(\n",
+ " HOURS_MODE, BLENDED_RATE, INCLUDE_KB_READINESS)\n",
+ "\n",
+ "impl_summary = impl_detail.groupby(\"workstream\")[[\"hours\", \"cost\", *YEARS]].sum()\n",
+ "impl_summary.loc[\"Steady-state tuning (2027-28)\"] = [\n",
+ " hours_pick(STEADY_STATE_HOURS, HOURS_MODE) * 2,\n",
+ " sum(steady_by_year.values()), *[steady_by_year[y] for y in YEARS]]\n",
+ "impl_summary.loc[\"TOTAL\"] = impl_summary.sum()\n",
+ "print(f\"AI implementation ({HOURS_MODE}, ${BLENDED_RATE}/h): \"\n",
+ " f\"Y1 LoE {money(sum(impl_by_year.values()))}\"\n",
+ " + (f\" + KB readiness {money(sum(kb_by_year.values()))}\" if INCLUDE_KB_READINESS else \"\")\n",
+ " + f\" + steady-state {money(sum(steady_by_year.values()))} (2027-28)\")\n",
+ "display(impl_summary)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "id": "b0614dec",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-07-07T12:22:43.946728Z",
+ "iopub.status.busy": "2026-07-07T12:22:43.946619Z",
+ "iopub.status.idle": "2026-07-07T12:22:43.950400Z",
+ "shell.execute_reply": "2026-07-07T12:22:43.949590Z"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "AI implementation $1.3M ÷ benefit claim $15.0M = 8.8% → FLAG — under-modelled by the industry benchmark\n",
+ "Industry benchmark: 20-40% of the Y1 benefit claim goes to implementation.\n",
+ "Reading: V2 deliberately strips vendor-services inflation from V1 (which sat at ~31%).\n",
+ "If the corrected case still looks good at V2 hours, it is robust to the higher estimate;\n",
+ "§9 sweeps hours × rate to show exactly how much the conclusion depends on this line.\n"
+ ]
+ }
+ ],
+ "source": [
+ "# ── Smell test (V1 doc rule): AI impl should be ≥15% of the AI benefit claim ──\n",
+ "_impl_total = sum(impl_by_year.values())\n",
+ "_ratio = _impl_total / SLIDE_TOTALS[\"total_3yr\"]\n",
+ "_verdict = \"PASS\" if _ratio >= 0.15 else \"FLAG — under-modelled by the industry benchmark\"\n",
+ "print(f\"AI implementation {money(_impl_total)} ÷ benefit claim \"\n",
+ " f\"{money(SLIDE_TOTALS['total_3yr'])} = {_ratio:.1%} → {_verdict}\")\n",
+ "print(\"Industry benchmark: 20-40% of the Y1 benefit claim goes to implementation.\")\n",
+ "print(\"Reading: V2 deliberately strips vendor-services inflation from V1 (which sat at ~31%).\")\n",
+ "print(\"If the corrected case still looks good at V2 hours, it is robust to the higher estimate;\")\n",
+ "print(\"§9 sweeps hours × rate to show exactly how much the conclusion depends on this line.\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e98e70db",
+ "metadata": {},
+ "source": [
+ "## §5 · Corrected cost stack\n",
+ "\n",
+ "The pitched case vs the same case with the three missing costs added — and one *credit* the deck\n",
+ "also missed: the 12-month ramp means licences don't actually bill in 2026. Double-billing is\n",
+ "visible directly: in 2026–27 CTM pays the existing platforms **and** the Genesys programme."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "id": "30948da4",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-07-07T12:22:43.952985Z",
+ "iopub.status.busy": "2026-07-07T12:22:43.952792Z",
+ "iopub.status.idle": "2026-07-07T12:22:43.970944Z",
+ "shell.execute_reply": "2026-07-07T12:22:43.970160Z"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Corrected programme cost: {2026: '$10.8M', 2027: '$13.5M', 2028: '$7.8M'} → 3-yr $32.1M\n",
+ "As pitched (deck): {2026: '$6.9M', 2027: '$4.3M', 2028: '$4.3M'} → 3-yr $15.5M (deck says $15.4M — rounding)\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " 2026 | \n",
+ " 2027 | \n",
+ " 2028 | \n",
+ " 3-yr | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | CCaaS platform licences (ramp-adjusted) | \n",
+ " 0 | \n",
+ " 4,300,000 | \n",
+ " 4,300,000 | \n",
+ " 8,600,000 | \n",
+ "
\n",
+ " \n",
+ " | Base professional services + training | \n",
+ " 2,567,000 | \n",
+ " 0 | \n",
+ " 0 | \n",
+ " 2,567,000 | \n",
+ "
\n",
+ " \n",
+ " | Existing platform (term-contract run-off) | \n",
+ " 7,300,000 | \n",
+ " 7,300,000 | \n",
+ " 0 | \n",
+ " 14,600,000 | \n",
+ "
\n",
+ " \n",
+ " | AI token consumption | \n",
+ " 0 | \n",
+ " 1,176,183 | \n",
+ " 3,345,156 | \n",
+ " 4,521,339 | \n",
+ "
\n",
+ " \n",
+ " | AI implementation + KB readiness | \n",
+ " 923,217 | \n",
+ " 582,241 | \n",
+ " 35,793 | \n",
+ " 1,541,250 | \n",
+ "
\n",
+ " \n",
+ " | AI steady-state tuning | \n",
+ " 0 | \n",
+ " 157,500 | \n",
+ " 157,500 | \n",
+ " 315,000 | \n",
+ "
\n",
+ " \n",
+ " | TOTAL — corrected | \n",
+ " 10,790,217 | \n",
+ " 13,515,924 | \n",
+ " 7,838,449 | \n",
+ " 32,144,589 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " 2026 2027 2028 \\\n",
+ "CCaaS platform licences (ramp-adjusted) 0 4,300,000 4,300,000 \n",
+ "Base professional services + training 2,567,000 0 0 \n",
+ "Existing platform (term-contract run-off) 7,300,000 7,300,000 0 \n",
+ "AI token consumption 0 1,176,183 3,345,156 \n",
+ "AI implementation + KB readiness 923,217 582,241 35,793 \n",
+ "AI steady-state tuning 0 157,500 157,500 \n",
+ "TOTAL — corrected 10,790,217 13,515,924 7,838,449 \n",
+ "\n",
+ " 3-yr \n",
+ "CCaaS platform licences (ramp-adjusted) 8,600,000 \n",
+ "Base professional services + training 2,567,000 \n",
+ "Existing platform (term-contract run-off) 14,600,000 \n",
+ "AI token consumption 4,521,339 \n",
+ "AI implementation + KB readiness 1,541,250 \n",
+ "AI steady-state tuning 315,000 \n",
+ "TOTAL — corrected 32,144,589 "
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " 2026 | \n",
+ " 2027 | \n",
+ " 2028 | \n",
+ " 3-yr | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | CCaaS platform licences (as pitched, no ramp) | \n",
+ " 4,300,000 | \n",
+ " 4,300,000 | \n",
+ " 4,300,000 | \n",
+ " 12,900,000 | \n",
+ "
\n",
+ " \n",
+ " | Base professional services + training | \n",
+ " 2,567,000 | \n",
+ " 0 | \n",
+ " 0 | \n",
+ " 2,567,000 | \n",
+ "
\n",
+ " \n",
+ " | TOTAL — as pitched | \n",
+ " 6,867,000 | \n",
+ " 4,300,000 | \n",
+ " 4,300,000 | \n",
+ " 15,467,000 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " 2026 2027 2028 \\\n",
+ "CCaaS platform licences (as pitched, no ramp) 4,300,000 4,300,000 4,300,000 \n",
+ "Base professional services + training 2,567,000 0 0 \n",
+ "TOTAL — as pitched 6,867,000 4,300,000 4,300,000 \n",
+ "\n",
+ " 3-yr \n",
+ "CCaaS platform licences (as pitched, no ramp) 12,900,000 \n",
+ "Base professional services + training 2,567,000 \n",
+ "TOTAL — as pitched 15,467,000 "
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "ps_by_year = {2026: TCO_VERBATIM[\"prof_services_y1\"] + TCO_VERBATIM[\"training_y1\"],\n",
+ " 2027: 0.0, 2028: 0.0}\n",
+ "impl_kb_by_year = {y: impl_by_year[y] + kb_by_year[y] for y in YEARS}\n",
+ "\n",
+ "corrected_costs = pd.DataFrame({\n",
+ " \"CCaaS platform licences (ramp-adjusted)\": licence_by_year,\n",
+ " \"Base professional services + training\": ps_by_year,\n",
+ " \"Existing platform (term-contract run-off)\": current_by_year,\n",
+ " \"AI token consumption\": token_total_by_year,\n",
+ " \"AI implementation + KB readiness\": impl_kb_by_year,\n",
+ " \"AI steady-state tuning\": steady_by_year,\n",
+ "}).T[YEARS]\n",
+ "corrected_costs[\"3-yr\"] = corrected_costs.sum(axis=1)\n",
+ "corrected_total_by_year = {y: float(corrected_costs[y].sum()) for y in YEARS}\n",
+ "\n",
+ "pitched_costs = pd.DataFrame({\n",
+ " \"CCaaS platform licences (as pitched, no ramp)\": {y: TCO_VERBATIM[\"ccaas_annual\"] for y in YEARS},\n",
+ " \"Base professional services + training\": ps_by_year,\n",
+ "}).T[YEARS]\n",
+ "pitched_costs[\"3-yr\"] = pitched_costs.sum(axis=1)\n",
+ "pitched_total_by_year = {y: float(pitched_costs[y].sum()) for y in YEARS}\n",
+ "\n",
+ "kb_note = \"\" if INCLUDE_KB_READINESS else \" (KB readiness excluded)\"\n",
+ "print(f\"Corrected programme cost{kb_note}: \"\n",
+ " f\"{ {y: money(v) for y, v in corrected_total_by_year.items()} } \"\n",
+ " f\"→ 3-yr {money(sum(corrected_total_by_year.values()))}\")\n",
+ "print(f\"As pitched (deck): { {y: money(v) for y, v in pitched_total_by_year.items()} } \"\n",
+ " f\"→ 3-yr {money(sum(pitched_total_by_year.values()))} (deck says $15.4M — rounding)\")\n",
+ "display(pd.concat([corrected_costs,\n",
+ " pd.DataFrame(corrected_costs.sum()).T.set_axis([\"TOTAL — corrected\"])]))\n",
+ "display(pd.concat([pitched_costs,\n",
+ " pd.DataFrame(pitched_costs.sum()).T.set_axis([\"TOTAL — as pitched\"])]))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1d37d385",
+ "metadata": {},
+ "source": [
+ "## §6 · Figure 1 — Benefits over 3 years (verbatim Genesys)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "id": "897493af",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-07-07T12:22:43.973216Z",
+ "iopub.status.busy": "2026-07-07T12:22:43.973035Z",
+ "iopub.status.idle": "2026-07-07T12:22:44.069313Z",
+ "shell.execute_reply": "2026-07-07T12:22:44.068537Z"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.plotly.v1+json": {
+ "config": {
+ "plotlyServerURL": "https://plot.ly"
+ },
+ "data": [
+ {
+ "hovertemplate": "%{fullData.name}: %{y:$,.0f}",
+ "marker": {
+ "color": "#2a78d6",
+ "line": {
+ "color": "#fcfcfb",
+ "width": 2
+ }
+ },
+ "name": "Agent Copilot",
+ "type": "bar",
+ "x": [
+ "2026",
+ "2027",
+ "2028"
+ ],
+ "y": [
+ 0.0,
+ 1150000.0,
+ 6214000.0
+ ]
+ },
+ {
+ "hovertemplate": "%{fullData.name}: %{y:$,.0f}",
+ "marker": {
+ "color": "#1baf7a",
+ "line": {
+ "color": "#fcfcfb",
+ "width": 2
+ }
+ },
+ "name": "WFM",
+ "type": "bar",
+ "x": [
+ "2026",
+ "2027",
+ "2028"
+ ],
+ "y": [
+ 0.0,
+ 107692.30769230769,
+ 2893307.692307692
+ ]
+ },
+ {
+ "hovertemplate": "%{fullData.name}: %{y:$,.0f}",
+ "marker": {
+ "color": "#eda100",
+ "line": {
+ "color": "#fcfcfb",
+ "width": 2
+ }
+ },
+ "name": "Email",
+ "type": "bar",
+ "x": [
+ "2026",
+ "2027",
+ "2028"
+ ],
+ "y": [
+ 0.0,
+ 1082428.5714285714,
+ 1888571.4285714286
+ ]
+ },
+ {
+ "hovertemplate": "%{fullData.name}: %{y:$,.0f}",
+ "marker": {
+ "color": "#008300",
+ "line": {
+ "color": "#fcfcfb",
+ "width": 2
+ }
+ },
+ "name": "STA",
+ "type": "bar",
+ "x": [
+ "2026",
+ "2027",
+ "2028"
+ ],
+ "y": [
+ 0.0,
+ 134576.92307692306,
+ 679423.076923077
+ ]
+ },
+ {
+ "hovertemplate": "%{fullData.name}: %{y:$,.0f}",
+ "marker": {
+ "color": "#4a3aa7",
+ "line": {
+ "color": "#fcfcfb",
+ "width": 2
+ }
+ },
+ "name": "Predictive Routing",
+ "type": "bar",
+ "x": [
+ "2026",
+ "2027",
+ "2028"
+ ],
+ "y": [
+ 0.0,
+ 64980.769230769234,
+ 461019.23076923075
+ ]
+ },
+ {
+ "hovertemplate": "%{fullData.name}: %{y:$,.0f}",
+ "marker": {
+ "color": "#e34948",
+ "line": {
+ "color": "#fcfcfb",
+ "width": 2
+ }
+ },
+ "name": "Supervisor Copilot",
+ "type": "bar",
+ "x": [
+ "2026",
+ "2027",
+ "2028"
+ ],
+ "y": [
+ 0.0,
+ 74826.92307692308,
+ 292173.07692307694
+ ]
+ },
+ {
+ "hovertemplate": "%{fullData.name}: %{y:$,.0f}",
+ "line": {
+ "color": "#52514e",
+ "width": 2
+ },
+ "marker": {
+ "line": {
+ "color": "#fcfcfb",
+ "width": 2
+ },
+ "size": 8
+ },
+ "mode": "lines+markers",
+ "name": "Cumulative benefits",
+ "type": "scatter",
+ "x": [
+ "2026",
+ "2027",
+ "2028"
+ ],
+ "y": {
+ "bdata": "AAAAAAAAAAC/9Eu/dPJDQQAAAAA3sWxB",
+ "dtype": "f8"
+ }
+ }
+ ],
+ "layout": {
+ "annotations": [
+ {
+ "font": {
+ "color": "#0b0b0b",
+ "size": 12
+ },
+ "showarrow": false,
+ "text": "$0K",
+ "x": 0,
+ "y": 0.0,
+ "yshift": 12
+ },
+ {
+ "font": {
+ "color": "#0b0b0b",
+ "size": 12
+ },
+ "showarrow": false,
+ "text": "$2.6M",
+ "x": 1,
+ "y": 2614505.4945054944,
+ "yshift": 12
+ },
+ {
+ "font": {
+ "color": "#0b0b0b",
+ "size": 12
+ },
+ "showarrow": false,
+ "text": "$12.4M",
+ "x": 2,
+ "y": 12428494.505494505,
+ "yshift": 12
+ },
+ {
+ "font": {
+ "color": "#52514e",
+ "size": 12
+ },
+ "showarrow": false,
+ "text": "3-yr total $15.0M (verbatim)",
+ "x": 2,
+ "xshift": -40,
+ "y": 15043000.0,
+ "yshift": 16
+ }
+ ],
+ "bargap": 0.45,
+ "barmode": "stack",
+ "font": {
+ "color": "#52514e",
+ "family": "system-ui, -apple-system, \"Segoe UI\", sans-serif",
+ "size": 12
+ },
+ "height": 460,
+ "hovermode": "x unified",
+ "legend": {
+ "font": {
+ "color": "#52514e",
+ "size": 11
+ },
+ "orientation": "h",
+ "x": 0,
+ "y": -0.1,
+ "yanchor": "top"
+ },
+ "margin": {
+ "b": 80,
+ "l": 70,
+ "r": 30,
+ "t": 70
+ },
+ "paper_bgcolor": "#fcfcfb",
+ "plot_bgcolor": "#fcfcfb",
+ "template": {
+ "data": {
+ "bar": [
+ {
+ "error_x": {
+ "color": "#2a3f5f"
+ },
+ "error_y": {
+ "color": "#2a3f5f"
+ },
+ "marker": {
+ "line": {
+ "color": "#E5ECF6",
+ "width": 0.5
+ },
+ "pattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ }
+ },
+ "type": "bar"
+ }
+ ],
+ "barpolar": [
+ {
+ "marker": {
+ "line": {
+ "color": "#E5ECF6",
+ "width": 0.5
+ },
+ "pattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ }
+ },
+ "type": "barpolar"
+ }
+ ],
+ "carpet": [
+ {
+ "aaxis": {
+ "endlinecolor": "#2a3f5f",
+ "gridcolor": "white",
+ "linecolor": "white",
+ "minorgridcolor": "white",
+ "startlinecolor": "#2a3f5f"
+ },
+ "baxis": {
+ "endlinecolor": "#2a3f5f",
+ "gridcolor": "white",
+ "linecolor": "white",
+ "minorgridcolor": "white",
+ "startlinecolor": "#2a3f5f"
+ },
+ "type": "carpet"
+ }
+ ],
+ "choropleth": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "type": "choropleth"
+ }
+ ],
+ "contour": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "contour"
+ }
+ ],
+ "contourcarpet": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "type": "contourcarpet"
+ }
+ ],
+ "heatmap": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "heatmap"
+ }
+ ],
+ "histogram": [
+ {
+ "marker": {
+ "pattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ }
+ },
+ "type": "histogram"
+ }
+ ],
+ "histogram2d": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "histogram2d"
+ }
+ ],
+ "histogram2dcontour": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "histogram2dcontour"
+ }
+ ],
+ "mesh3d": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "type": "mesh3d"
+ }
+ ],
+ "parcoords": [
+ {
+ "line": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "parcoords"
+ }
+ ],
+ "pie": [
+ {
+ "automargin": true,
+ "type": "pie"
+ }
+ ],
+ "scatter": [
+ {
+ "fillpattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ },
+ "type": "scatter"
+ }
+ ],
+ "scatter3d": [
+ {
+ "line": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatter3d"
+ }
+ ],
+ "scattercarpet": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattercarpet"
+ }
+ ],
+ "scattergeo": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattergeo"
+ }
+ ],
+ "scattergl": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattergl"
+ }
+ ],
+ "scattermap": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattermap"
+ }
+ ],
+ "scattermapbox": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattermapbox"
+ }
+ ],
+ "scatterpolar": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatterpolar"
+ }
+ ],
+ "scatterpolargl": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatterpolargl"
+ }
+ ],
+ "scatterternary": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatterternary"
+ }
+ ],
+ "surface": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "surface"
+ }
+ ],
+ "table": [
+ {
+ "cells": {
+ "fill": {
+ "color": "#EBF0F8"
+ },
+ "line": {
+ "color": "white"
+ }
+ },
+ "header": {
+ "fill": {
+ "color": "#C8D4E3"
+ },
+ "line": {
+ "color": "white"
+ }
+ },
+ "type": "table"
+ }
+ ]
+ },
+ "layout": {
+ "annotationdefaults": {
+ "arrowcolor": "#2a3f5f",
+ "arrowhead": 0,
+ "arrowwidth": 1
+ },
+ "autotypenumbers": "strict",
+ "coloraxis": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "colorscale": {
+ "diverging": [
+ [
+ 0,
+ "#8e0152"
+ ],
+ [
+ 0.1,
+ "#c51b7d"
+ ],
+ [
+ 0.2,
+ "#de77ae"
+ ],
+ [
+ 0.3,
+ "#f1b6da"
+ ],
+ [
+ 0.4,
+ "#fde0ef"
+ ],
+ [
+ 0.5,
+ "#f7f7f7"
+ ],
+ [
+ 0.6,
+ "#e6f5d0"
+ ],
+ [
+ 0.7,
+ "#b8e186"
+ ],
+ [
+ 0.8,
+ "#7fbc41"
+ ],
+ [
+ 0.9,
+ "#4d9221"
+ ],
+ [
+ 1,
+ "#276419"
+ ]
+ ],
+ "sequential": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "sequentialminus": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ]
+ },
+ "colorway": [
+ "#636efa",
+ "#EF553B",
+ "#00cc96",
+ "#ab63fa",
+ "#FFA15A",
+ "#19d3f3",
+ "#FF6692",
+ "#B6E880",
+ "#FF97FF",
+ "#FECB52"
+ ],
+ "font": {
+ "color": "#2a3f5f"
+ },
+ "geo": {
+ "bgcolor": "white",
+ "lakecolor": "white",
+ "landcolor": "#E5ECF6",
+ "showlakes": true,
+ "showland": true,
+ "subunitcolor": "white"
+ },
+ "hoverlabel": {
+ "align": "left"
+ },
+ "hovermode": "closest",
+ "mapbox": {
+ "style": "light"
+ },
+ "paper_bgcolor": "white",
+ "plot_bgcolor": "#E5ECF6",
+ "polar": {
+ "angularaxis": {
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": ""
+ },
+ "bgcolor": "#E5ECF6",
+ "radialaxis": {
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": ""
+ }
+ },
+ "scene": {
+ "xaxis": {
+ "backgroundcolor": "#E5ECF6",
+ "gridcolor": "white",
+ "gridwidth": 2,
+ "linecolor": "white",
+ "showbackground": true,
+ "ticks": "",
+ "zerolinecolor": "white"
+ },
+ "yaxis": {
+ "backgroundcolor": "#E5ECF6",
+ "gridcolor": "white",
+ "gridwidth": 2,
+ "linecolor": "white",
+ "showbackground": true,
+ "ticks": "",
+ "zerolinecolor": "white"
+ },
+ "zaxis": {
+ "backgroundcolor": "#E5ECF6",
+ "gridcolor": "white",
+ "gridwidth": 2,
+ "linecolor": "white",
+ "showbackground": true,
+ "ticks": "",
+ "zerolinecolor": "white"
+ }
+ },
+ "shapedefaults": {
+ "line": {
+ "color": "#2a3f5f"
+ }
+ },
+ "ternary": {
+ "aaxis": {
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": ""
+ },
+ "baxis": {
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": ""
+ },
+ "bgcolor": "#E5ECF6",
+ "caxis": {
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": ""
+ }
+ },
+ "title": {
+ "x": 0.05
+ },
+ "xaxis": {
+ "automargin": true,
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": "",
+ "title": {
+ "standoff": 15
+ },
+ "zerolinecolor": "white",
+ "zerolinewidth": 2
+ },
+ "yaxis": {
+ "automargin": true,
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": "",
+ "title": {
+ "standoff": 15
+ },
+ "zerolinecolor": "white",
+ "zerolinewidth": 2
+ }
+ }
+ },
+ "title": {
+ "font": {
+ "color": "#0b0b0b",
+ "size": 16
+ },
+ "text": "Benefits over 3 years — verbatim Genesys (Appendix 4)
Phased by Genesys's own deployment schedule: benefits realize +3 months after regional go-live — $0 in 2026 · WFM = Workforce Forecast & Scheduling",
+ "x": 0.02,
+ "xanchor": "left"
+ },
+ "xaxis": {
+ "linecolor": "#c3c2b7",
+ "showgrid": false,
+ "tickfont": {
+ "color": "#898781"
+ },
+ "type": "category"
+ },
+ "yaxis": {
+ "gridcolor": "#e1e0d9",
+ "tickfont": {
+ "color": "#898781"
+ },
+ "tickformat": "$~s",
+ "zerolinecolor": "#c3c2b7",
+ "zerolinewidth": 1.5
+ }
+ }
+ }
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "fig = go.Figure()\n",
+ "for cap in CAPABILITIES:\n",
+ " vals = [benefits_long.query(\"capability == @cap and year == @y\")[\"benefit\"].sum()\n",
+ " for y in YEARS]\n",
+ " fig.add_trace(bar(X, vals, cap, CAP_COLOR[cap]))\n",
+ "cum = pd.Series([benefit_total_by_year[y] for y in YEARS]).cumsum()\n",
+ "fig.add_trace(cum_line(X, cum, \"Cumulative benefits\"))\n",
+ "for i, y in enumerate(YEARS):\n",
+ " fig.add_annotation(x=i, y=benefit_total_by_year[y],\n",
+ " text=f\"{money(benefit_total_by_year[y])}\",\n",
+ " showarrow=False, yshift=12, font=dict(size=12, color=INK))\n",
+ "fig.add_annotation(x=len(YEARS) - 1, y=float(cum.iloc[-1]),\n",
+ " text=f\"3-yr total {money(float(cum.iloc[-1]))} (verbatim)\",\n",
+ " showarrow=False, yshift=16, xshift=-40, font=dict(size=12, color=INK2))\n",
+ "fig.update_layout(barmode=\"stack\")\n",
+ "tei_layout(fig, \"Benefits over 3 years — verbatim Genesys (Appendix 4)\",\n",
+ " \"Phased by Genesys's own deployment schedule: benefits realize +3 months after \"\n",
+ " \"regional go-live — $0 in 2026 · WFM = Workforce Forecast & Scheduling\")\n",
+ "fig.show()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d62fd178",
+ "metadata": {},
+ "source": [
+ "## §7 · Figure 2 — Costs over 3 years, corrected\n",
+ "\n",
+ "The stack is the full programme cost. The dashed grey line is the deck's cumulative cost case\n",
+ "($15.4M) for reference — the gap between the lines is what the pitch left out (net of the ramp\n",
+ "credit it also left out)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "id": "f12111e3",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-07-07T12:22:44.072051Z",
+ "iopub.status.busy": "2026-07-07T12:22:44.071804Z",
+ "iopub.status.idle": "2026-07-07T12:22:44.104691Z",
+ "shell.execute_reply": "2026-07-07T12:22:44.103839Z"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.plotly.v1+json": {
+ "config": {
+ "plotlyServerURL": "https://plot.ly"
+ },
+ "data": [
+ {
+ "hovertemplate": "%{fullData.name}: %{y:$,.0f}",
+ "marker": {
+ "color": "#2a78d6",
+ "line": {
+ "color": "#fcfcfb",
+ "width": 2
+ }
+ },
+ "name": "CCaaS platform licences (ramp-adjusted)",
+ "type": "bar",
+ "x": [
+ "2026",
+ "2027",
+ "2028"
+ ],
+ "y": [
+ 0.0,
+ 4300000.0,
+ 4300000.0
+ ]
+ },
+ {
+ "hovertemplate": "%{fullData.name}: %{y:$,.0f}",
+ "marker": {
+ "color": "#1baf7a",
+ "line": {
+ "color": "#fcfcfb",
+ "width": 2
+ }
+ },
+ "name": "Base professional services + training",
+ "type": "bar",
+ "x": [
+ "2026",
+ "2027",
+ "2028"
+ ],
+ "y": [
+ 2567000.0,
+ 0.0,
+ 0.0
+ ]
+ },
+ {
+ "hovertemplate": "%{fullData.name}: %{y:$,.0f}",
+ "marker": {
+ "color": "#eda100",
+ "line": {
+ "color": "#fcfcfb",
+ "width": 2
+ }
+ },
+ "name": "Existing platform (term-contract run-off)",
+ "type": "bar",
+ "x": [
+ "2026",
+ "2027",
+ "2028"
+ ],
+ "y": [
+ 7300000.0,
+ 7300000.0,
+ 0.0
+ ]
+ },
+ {
+ "hovertemplate": "%{fullData.name}: %{y:$,.0f}",
+ "marker": {
+ "color": "#008300",
+ "line": {
+ "color": "#fcfcfb",
+ "width": 2
+ }
+ },
+ "name": "AI token consumption",
+ "type": "bar",
+ "x": [
+ "2026",
+ "2027",
+ "2028"
+ ],
+ "y": [
+ 0.0,
+ 1176183.0,
+ 3345156.0
+ ]
+ },
+ {
+ "hovertemplate": "%{fullData.name}: %{y:$,.0f}",
+ "marker": {
+ "color": "#4a3aa7",
+ "line": {
+ "color": "#fcfcfb",
+ "width": 2
+ }
+ },
+ "name": "AI implementation + KB readiness",
+ "type": "bar",
+ "x": [
+ "2026",
+ "2027",
+ "2028"
+ ],
+ "y": [
+ 923216.703288553,
+ 582240.770938251,
+ 35792.52577319588
+ ]
+ },
+ {
+ "hovertemplate": "%{fullData.name}: %{y:$,.0f}",
+ "marker": {
+ "color": "#e34948",
+ "line": {
+ "color": "#fcfcfb",
+ "width": 2
+ }
+ },
+ "name": "AI steady-state tuning",
+ "type": "bar",
+ "x": [
+ "2026",
+ "2027",
+ "2028"
+ ],
+ "y": [
+ 0.0,
+ 157500.0,
+ 157500.0
+ ]
+ },
+ {
+ "hovertemplate": "%{fullData.name}: %{y:$,.0f}",
+ "line": {
+ "color": "#52514e",
+ "width": 2
+ },
+ "marker": {
+ "line": {
+ "color": "#fcfcfb",
+ "width": 2
+ },
+ "size": 8
+ },
+ "mode": "lines+markers",
+ "name": "Cumulative — corrected",
+ "type": "scatter",
+ "x": [
+ "2026",
+ "2027",
+ "2028"
+ ],
+ "y": {
+ "bdata": "/1aBFqmUZEHYbpbHHS53QQAAANDMp35B",
+ "dtype": "f8"
+ }
+ },
+ {
+ "hovertemplate": "%{fullData.name}: %{y:$,.0f}",
+ "line": {
+ "color": "#c3c2b7",
+ "dash": "dash",
+ "width": 2
+ },
+ "marker": {
+ "line": {
+ "color": "#fcfcfb",
+ "width": 2
+ },
+ "size": 8
+ },
+ "mode": "lines+markers",
+ "name": "Cumulative — as pitched",
+ "type": "scatter",
+ "x": [
+ "2026",
+ "2027",
+ "2028"
+ ],
+ "y": {
+ "bdata": "AAAAAA4yWkEAAAAAo0xlQQAAAAA/gG1B",
+ "dtype": "f8"
+ }
+ }
+ ],
+ "layout": {
+ "annotations": [
+ {
+ "font": {
+ "color": "#0b0b0b",
+ "size": 12
+ },
+ "showarrow": false,
+ "text": "$10.8M",
+ "x": 0,
+ "y": 10790216.703288553,
+ "yshift": 12
+ },
+ {
+ "font": {
+ "color": "#0b0b0b",
+ "size": 12
+ },
+ "showarrow": false,
+ "text": "$13.5M",
+ "x": 1,
+ "y": 13515923.770938251,
+ "yshift": 12
+ },
+ {
+ "font": {
+ "color": "#0b0b0b",
+ "size": 12
+ },
+ "showarrow": false,
+ "text": "$7.8M",
+ "x": 2,
+ "y": 7838448.525773196,
+ "yshift": 12
+ },
+ {
+ "font": {
+ "color": "#52514e",
+ "size": 12
+ },
+ "showarrow": false,
+ "text": "3-yr $32.1M — $16.7M above the pitch",
+ "x": 2,
+ "xshift": -70,
+ "y": 32144589.0,
+ "yshift": 18
+ }
+ ],
+ "bargap": 0.45,
+ "barmode": "stack",
+ "font": {
+ "color": "#52514e",
+ "family": "system-ui, -apple-system, \"Segoe UI\", sans-serif",
+ "size": 12
+ },
+ "height": 500,
+ "hovermode": "x unified",
+ "legend": {
+ "font": {
+ "color": "#52514e",
+ "size": 11
+ },
+ "orientation": "h",
+ "x": 0,
+ "y": -0.1,
+ "yanchor": "top"
+ },
+ "margin": {
+ "b": 80,
+ "l": 70,
+ "r": 30,
+ "t": 70
+ },
+ "paper_bgcolor": "#fcfcfb",
+ "plot_bgcolor": "#fcfcfb",
+ "template": {
+ "data": {
+ "bar": [
+ {
+ "error_x": {
+ "color": "#2a3f5f"
+ },
+ "error_y": {
+ "color": "#2a3f5f"
+ },
+ "marker": {
+ "line": {
+ "color": "#E5ECF6",
+ "width": 0.5
+ },
+ "pattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ }
+ },
+ "type": "bar"
+ }
+ ],
+ "barpolar": [
+ {
+ "marker": {
+ "line": {
+ "color": "#E5ECF6",
+ "width": 0.5
+ },
+ "pattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ }
+ },
+ "type": "barpolar"
+ }
+ ],
+ "carpet": [
+ {
+ "aaxis": {
+ "endlinecolor": "#2a3f5f",
+ "gridcolor": "white",
+ "linecolor": "white",
+ "minorgridcolor": "white",
+ "startlinecolor": "#2a3f5f"
+ },
+ "baxis": {
+ "endlinecolor": "#2a3f5f",
+ "gridcolor": "white",
+ "linecolor": "white",
+ "minorgridcolor": "white",
+ "startlinecolor": "#2a3f5f"
+ },
+ "type": "carpet"
+ }
+ ],
+ "choropleth": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "type": "choropleth"
+ }
+ ],
+ "contour": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "contour"
+ }
+ ],
+ "contourcarpet": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "type": "contourcarpet"
+ }
+ ],
+ "heatmap": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "heatmap"
+ }
+ ],
+ "histogram": [
+ {
+ "marker": {
+ "pattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ }
+ },
+ "type": "histogram"
+ }
+ ],
+ "histogram2d": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "histogram2d"
+ }
+ ],
+ "histogram2dcontour": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "histogram2dcontour"
+ }
+ ],
+ "mesh3d": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "type": "mesh3d"
+ }
+ ],
+ "parcoords": [
+ {
+ "line": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "parcoords"
+ }
+ ],
+ "pie": [
+ {
+ "automargin": true,
+ "type": "pie"
+ }
+ ],
+ "scatter": [
+ {
+ "fillpattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ },
+ "type": "scatter"
+ }
+ ],
+ "scatter3d": [
+ {
+ "line": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatter3d"
+ }
+ ],
+ "scattercarpet": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattercarpet"
+ }
+ ],
+ "scattergeo": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattergeo"
+ }
+ ],
+ "scattergl": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattergl"
+ }
+ ],
+ "scattermap": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattermap"
+ }
+ ],
+ "scattermapbox": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattermapbox"
+ }
+ ],
+ "scatterpolar": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatterpolar"
+ }
+ ],
+ "scatterpolargl": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatterpolargl"
+ }
+ ],
+ "scatterternary": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatterternary"
+ }
+ ],
+ "surface": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "surface"
+ }
+ ],
+ "table": [
+ {
+ "cells": {
+ "fill": {
+ "color": "#EBF0F8"
+ },
+ "line": {
+ "color": "white"
+ }
+ },
+ "header": {
+ "fill": {
+ "color": "#C8D4E3"
+ },
+ "line": {
+ "color": "white"
+ }
+ },
+ "type": "table"
+ }
+ ]
+ },
+ "layout": {
+ "annotationdefaults": {
+ "arrowcolor": "#2a3f5f",
+ "arrowhead": 0,
+ "arrowwidth": 1
+ },
+ "autotypenumbers": "strict",
+ "coloraxis": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "colorscale": {
+ "diverging": [
+ [
+ 0,
+ "#8e0152"
+ ],
+ [
+ 0.1,
+ "#c51b7d"
+ ],
+ [
+ 0.2,
+ "#de77ae"
+ ],
+ [
+ 0.3,
+ "#f1b6da"
+ ],
+ [
+ 0.4,
+ "#fde0ef"
+ ],
+ [
+ 0.5,
+ "#f7f7f7"
+ ],
+ [
+ 0.6,
+ "#e6f5d0"
+ ],
+ [
+ 0.7,
+ "#b8e186"
+ ],
+ [
+ 0.8,
+ "#7fbc41"
+ ],
+ [
+ 0.9,
+ "#4d9221"
+ ],
+ [
+ 1,
+ "#276419"
+ ]
+ ],
+ "sequential": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "sequentialminus": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ]
+ },
+ "colorway": [
+ "#636efa",
+ "#EF553B",
+ "#00cc96",
+ "#ab63fa",
+ "#FFA15A",
+ "#19d3f3",
+ "#FF6692",
+ "#B6E880",
+ "#FF97FF",
+ "#FECB52"
+ ],
+ "font": {
+ "color": "#2a3f5f"
+ },
+ "geo": {
+ "bgcolor": "white",
+ "lakecolor": "white",
+ "landcolor": "#E5ECF6",
+ "showlakes": true,
+ "showland": true,
+ "subunitcolor": "white"
+ },
+ "hoverlabel": {
+ "align": "left"
+ },
+ "hovermode": "closest",
+ "mapbox": {
+ "style": "light"
+ },
+ "paper_bgcolor": "white",
+ "plot_bgcolor": "#E5ECF6",
+ "polar": {
+ "angularaxis": {
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": ""
+ },
+ "bgcolor": "#E5ECF6",
+ "radialaxis": {
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": ""
+ }
+ },
+ "scene": {
+ "xaxis": {
+ "backgroundcolor": "#E5ECF6",
+ "gridcolor": "white",
+ "gridwidth": 2,
+ "linecolor": "white",
+ "showbackground": true,
+ "ticks": "",
+ "zerolinecolor": "white"
+ },
+ "yaxis": {
+ "backgroundcolor": "#E5ECF6",
+ "gridcolor": "white",
+ "gridwidth": 2,
+ "linecolor": "white",
+ "showbackground": true,
+ "ticks": "",
+ "zerolinecolor": "white"
+ },
+ "zaxis": {
+ "backgroundcolor": "#E5ECF6",
+ "gridcolor": "white",
+ "gridwidth": 2,
+ "linecolor": "white",
+ "showbackground": true,
+ "ticks": "",
+ "zerolinecolor": "white"
+ }
+ },
+ "shapedefaults": {
+ "line": {
+ "color": "#2a3f5f"
+ }
+ },
+ "ternary": {
+ "aaxis": {
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": ""
+ },
+ "baxis": {
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": ""
+ },
+ "bgcolor": "#E5ECF6",
+ "caxis": {
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": ""
+ }
+ },
+ "title": {
+ "x": 0.05
+ },
+ "xaxis": {
+ "automargin": true,
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": "",
+ "title": {
+ "standoff": 15
+ },
+ "zerolinecolor": "white",
+ "zerolinewidth": 2
+ },
+ "yaxis": {
+ "automargin": true,
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": "",
+ "title": {
+ "standoff": 15
+ },
+ "zerolinecolor": "white",
+ "zerolinewidth": 2
+ }
+ }
+ },
+ "title": {
+ "font": {
+ "color": "#0b0b0b",
+ "size": 16
+ },
+ "text": "Programme cost over 3 years — with the missed costs
Existing platforms bill until 31 Dec 2027 term-contract end (double-billing) · licences ramp-free for 12 months · tokens + AI implementation added",
+ "x": 0.02,
+ "xanchor": "left"
+ },
+ "xaxis": {
+ "linecolor": "#c3c2b7",
+ "showgrid": false,
+ "tickfont": {
+ "color": "#898781"
+ },
+ "type": "category"
+ },
+ "yaxis": {
+ "gridcolor": "#e1e0d9",
+ "tickfont": {
+ "color": "#898781"
+ },
+ "tickformat": "$~s",
+ "zerolinecolor": "#c3c2b7",
+ "zerolinewidth": 1.5
+ }
+ }
+ }
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "fig = go.Figure()\n",
+ "for line in corrected_costs.index:\n",
+ " fig.add_trace(bar(X, [corrected_costs.loc[line, y] for y in YEARS], line, COST_COLOR[line]))\n",
+ "cum_corrected = pd.Series([corrected_total_by_year[y] for y in YEARS]).cumsum()\n",
+ "cum_pitched = pd.Series([pitched_total_by_year[y] for y in YEARS]).cumsum()\n",
+ "fig.add_trace(cum_line(X, cum_corrected, \"Cumulative — corrected\"))\n",
+ "fig.add_trace(cum_line(X, cum_pitched, \"Cumulative — as pitched\", color=CONTEXT, dash=\"dash\"))\n",
+ "for i, y in enumerate(YEARS):\n",
+ " fig.add_annotation(x=i, y=corrected_total_by_year[y],\n",
+ " text=f\"{money(corrected_total_by_year[y])}\",\n",
+ " showarrow=False, yshift=12, font=dict(size=12, color=INK))\n",
+ "delta = float(cum_corrected.iloc[-1] - cum_pitched.iloc[-1])\n",
+ "fig.add_annotation(x=len(YEARS) - 1, y=float(cum_corrected.iloc[-1]),\n",
+ " text=f\"3-yr {html_money(float(cum_corrected.iloc[-1]))} — \"\n",
+ " f\"{html_money(delta)} above the pitch\",\n",
+ " showarrow=False, yshift=18, xshift=-70, font=dict(size=12, color=INK2))\n",
+ "fig.update_layout(barmode=\"stack\")\n",
+ "tei_layout(fig, \"Programme cost over 3 years — with the missed costs\",\n",
+ " \"Existing platforms bill until 31 Dec 2027 term-contract end (double-billing) · \"\n",
+ " \"licences ramp-free for 12 months · tokens + AI implementation added\", height=500)\n",
+ "fig.show()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "id": "31448686",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-07-07T12:22:44.106991Z",
+ "iopub.status.busy": "2026-07-07T12:22:44.106665Z",
+ "iopub.status.idle": "2026-07-07T12:22:44.136989Z",
+ "shell.execute_reply": "2026-07-07T12:22:44.136239Z"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.plotly.v1+json": {
+ "config": {
+ "plotlyServerURL": "https://plot.ly"
+ },
+ "data": [
+ {
+ "hovertemplate": "%{fullData.name}: %{y:$,.0f}",
+ "marker": {
+ "color": "#c3c2b7",
+ "line": {
+ "color": "#fcfcfb",
+ "width": 2
+ }
+ },
+ "name": "As pitched (deck)",
+ "type": "bar",
+ "x": [
+ "2026",
+ "2027",
+ "2028"
+ ],
+ "y": [
+ 6867000.0,
+ 4300000.0,
+ 4300000.0
+ ]
+ },
+ {
+ "hovertemplate": "%{fullData.name}: %{y:$,.0f}",
+ "marker": {
+ "color": "#2a78d6",
+ "line": {
+ "color": "#fcfcfb",
+ "width": 2
+ }
+ },
+ "name": "Corrected",
+ "type": "bar",
+ "x": [
+ "2026",
+ "2027",
+ "2028"
+ ],
+ "y": [
+ 10790216.703288553,
+ 13515923.770938251,
+ 7838448.525773196
+ ]
+ }
+ ],
+ "layout": {
+ "annotations": [
+ {
+ "font": {
+ "color": "#0b0b0b",
+ "size": 12
+ },
+ "showarrow": false,
+ "text": "+$3.9M",
+ "x": 0,
+ "xshift": 16,
+ "y": 10790216.703288553,
+ "yshift": 12
+ },
+ {
+ "font": {
+ "color": "#0b0b0b",
+ "size": 12
+ },
+ "showarrow": false,
+ "text": "+$9.2M",
+ "x": 1,
+ "xshift": 16,
+ "y": 13515923.770938251,
+ "yshift": 12
+ },
+ {
+ "font": {
+ "color": "#0b0b0b",
+ "size": 12
+ },
+ "showarrow": false,
+ "text": "+$3.5M",
+ "x": 2,
+ "xshift": 16,
+ "y": 7838448.525773196,
+ "yshift": 12
+ }
+ ],
+ "bargap": 0.45,
+ "bargroupgap": 0.15,
+ "barmode": "group",
+ "font": {
+ "color": "#52514e",
+ "family": "system-ui, -apple-system, \"Segoe UI\", sans-serif",
+ "size": 12
+ },
+ "height": 400,
+ "hovermode": "x unified",
+ "legend": {
+ "font": {
+ "color": "#52514e",
+ "size": 11
+ },
+ "orientation": "h",
+ "x": 0,
+ "y": -0.1,
+ "yanchor": "top"
+ },
+ "margin": {
+ "b": 80,
+ "l": 70,
+ "r": 30,
+ "t": 70
+ },
+ "paper_bgcolor": "#fcfcfb",
+ "plot_bgcolor": "#fcfcfb",
+ "template": {
+ "data": {
+ "bar": [
+ {
+ "error_x": {
+ "color": "#2a3f5f"
+ },
+ "error_y": {
+ "color": "#2a3f5f"
+ },
+ "marker": {
+ "line": {
+ "color": "#E5ECF6",
+ "width": 0.5
+ },
+ "pattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ }
+ },
+ "type": "bar"
+ }
+ ],
+ "barpolar": [
+ {
+ "marker": {
+ "line": {
+ "color": "#E5ECF6",
+ "width": 0.5
+ },
+ "pattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ }
+ },
+ "type": "barpolar"
+ }
+ ],
+ "carpet": [
+ {
+ "aaxis": {
+ "endlinecolor": "#2a3f5f",
+ "gridcolor": "white",
+ "linecolor": "white",
+ "minorgridcolor": "white",
+ "startlinecolor": "#2a3f5f"
+ },
+ "baxis": {
+ "endlinecolor": "#2a3f5f",
+ "gridcolor": "white",
+ "linecolor": "white",
+ "minorgridcolor": "white",
+ "startlinecolor": "#2a3f5f"
+ },
+ "type": "carpet"
+ }
+ ],
+ "choropleth": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "type": "choropleth"
+ }
+ ],
+ "contour": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "contour"
+ }
+ ],
+ "contourcarpet": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "type": "contourcarpet"
+ }
+ ],
+ "heatmap": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "heatmap"
+ }
+ ],
+ "histogram": [
+ {
+ "marker": {
+ "pattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ }
+ },
+ "type": "histogram"
+ }
+ ],
+ "histogram2d": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "histogram2d"
+ }
+ ],
+ "histogram2dcontour": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "histogram2dcontour"
+ }
+ ],
+ "mesh3d": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "type": "mesh3d"
+ }
+ ],
+ "parcoords": [
+ {
+ "line": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "parcoords"
+ }
+ ],
+ "pie": [
+ {
+ "automargin": true,
+ "type": "pie"
+ }
+ ],
+ "scatter": [
+ {
+ "fillpattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ },
+ "type": "scatter"
+ }
+ ],
+ "scatter3d": [
+ {
+ "line": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatter3d"
+ }
+ ],
+ "scattercarpet": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattercarpet"
+ }
+ ],
+ "scattergeo": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattergeo"
+ }
+ ],
+ "scattergl": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattergl"
+ }
+ ],
+ "scattermap": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattermap"
+ }
+ ],
+ "scattermapbox": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattermapbox"
+ }
+ ],
+ "scatterpolar": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatterpolar"
+ }
+ ],
+ "scatterpolargl": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatterpolargl"
+ }
+ ],
+ "scatterternary": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatterternary"
+ }
+ ],
+ "surface": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "surface"
+ }
+ ],
+ "table": [
+ {
+ "cells": {
+ "fill": {
+ "color": "#EBF0F8"
+ },
+ "line": {
+ "color": "white"
+ }
+ },
+ "header": {
+ "fill": {
+ "color": "#C8D4E3"
+ },
+ "line": {
+ "color": "white"
+ }
+ },
+ "type": "table"
+ }
+ ]
+ },
+ "layout": {
+ "annotationdefaults": {
+ "arrowcolor": "#2a3f5f",
+ "arrowhead": 0,
+ "arrowwidth": 1
+ },
+ "autotypenumbers": "strict",
+ "coloraxis": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "colorscale": {
+ "diverging": [
+ [
+ 0,
+ "#8e0152"
+ ],
+ [
+ 0.1,
+ "#c51b7d"
+ ],
+ [
+ 0.2,
+ "#de77ae"
+ ],
+ [
+ 0.3,
+ "#f1b6da"
+ ],
+ [
+ 0.4,
+ "#fde0ef"
+ ],
+ [
+ 0.5,
+ "#f7f7f7"
+ ],
+ [
+ 0.6,
+ "#e6f5d0"
+ ],
+ [
+ 0.7,
+ "#b8e186"
+ ],
+ [
+ 0.8,
+ "#7fbc41"
+ ],
+ [
+ 0.9,
+ "#4d9221"
+ ],
+ [
+ 1,
+ "#276419"
+ ]
+ ],
+ "sequential": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "sequentialminus": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ]
+ },
+ "colorway": [
+ "#636efa",
+ "#EF553B",
+ "#00cc96",
+ "#ab63fa",
+ "#FFA15A",
+ "#19d3f3",
+ "#FF6692",
+ "#B6E880",
+ "#FF97FF",
+ "#FECB52"
+ ],
+ "font": {
+ "color": "#2a3f5f"
+ },
+ "geo": {
+ "bgcolor": "white",
+ "lakecolor": "white",
+ "landcolor": "#E5ECF6",
+ "showlakes": true,
+ "showland": true,
+ "subunitcolor": "white"
+ },
+ "hoverlabel": {
+ "align": "left"
+ },
+ "hovermode": "closest",
+ "mapbox": {
+ "style": "light"
+ },
+ "paper_bgcolor": "white",
+ "plot_bgcolor": "#E5ECF6",
+ "polar": {
+ "angularaxis": {
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": ""
+ },
+ "bgcolor": "#E5ECF6",
+ "radialaxis": {
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": ""
+ }
+ },
+ "scene": {
+ "xaxis": {
+ "backgroundcolor": "#E5ECF6",
+ "gridcolor": "white",
+ "gridwidth": 2,
+ "linecolor": "white",
+ "showbackground": true,
+ "ticks": "",
+ "zerolinecolor": "white"
+ },
+ "yaxis": {
+ "backgroundcolor": "#E5ECF6",
+ "gridcolor": "white",
+ "gridwidth": 2,
+ "linecolor": "white",
+ "showbackground": true,
+ "ticks": "",
+ "zerolinecolor": "white"
+ },
+ "zaxis": {
+ "backgroundcolor": "#E5ECF6",
+ "gridcolor": "white",
+ "gridwidth": 2,
+ "linecolor": "white",
+ "showbackground": true,
+ "ticks": "",
+ "zerolinecolor": "white"
+ }
+ },
+ "shapedefaults": {
+ "line": {
+ "color": "#2a3f5f"
+ }
+ },
+ "ternary": {
+ "aaxis": {
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": ""
+ },
+ "baxis": {
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": ""
+ },
+ "bgcolor": "#E5ECF6",
+ "caxis": {
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": ""
+ }
+ },
+ "title": {
+ "x": 0.05
+ },
+ "xaxis": {
+ "automargin": true,
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": "",
+ "title": {
+ "standoff": 15
+ },
+ "zerolinecolor": "white",
+ "zerolinewidth": 2
+ },
+ "yaxis": {
+ "automargin": true,
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": "",
+ "title": {
+ "standoff": 15
+ },
+ "zerolinecolor": "white",
+ "zerolinewidth": 2
+ }
+ }
+ },
+ "title": {
+ "font": {
+ "color": "#0b0b0b",
+ "size": 16
+ },
+ "text": "Cost case: as pitched vs corrected, by year
Delta labels = what each year's pitch understates",
+ "x": 0.02,
+ "xanchor": "left"
+ },
+ "xaxis": {
+ "linecolor": "#c3c2b7",
+ "showgrid": false,
+ "tickfont": {
+ "color": "#898781"
+ },
+ "type": "category"
+ },
+ "yaxis": {
+ "gridcolor": "#e1e0d9",
+ "tickfont": {
+ "color": "#898781"
+ },
+ "tickformat": "$~s",
+ "zerolinecolor": "#c3c2b7",
+ "zerolinewidth": 1.5
+ }
+ }
+ }
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "fig = go.Figure()\n",
+ "fig.add_trace(bar(X, [pitched_total_by_year[y] for y in YEARS], \"As pitched (deck)\", CONTEXT))\n",
+ "fig.add_trace(bar(X, [corrected_total_by_year[y] for y in YEARS], \"Corrected\", \"#2a78d6\"))\n",
+ "for i, y in enumerate(YEARS):\n",
+ " d = corrected_total_by_year[y] - pitched_total_by_year[y]\n",
+ " fig.add_annotation(x=i, y=corrected_total_by_year[y], xshift=16,\n",
+ " text=f\"{'+' if d >= 0 else '−'}{money(abs(d))}\",\n",
+ " showarrow=False, yshift=12, font=dict(size=12, color=INK))\n",
+ "fig.update_layout(barmode=\"group\", bargap=0.35, bargroupgap=0.15)\n",
+ "tei_layout(fig, \"Cost case: as pitched vs corrected, by year\",\n",
+ " \"Delta labels = what each year's pitch understates\", height=400)\n",
+ "fig.show()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b178185b",
+ "metadata": {},
+ "source": [
+ "## §8 · Figure 3 — Overall business case / ROI\n",
+ "\n",
+ "**Frame:** baseline-relative, against *do nothing* (keep paying $7.3M/yr).\n",
+ "Incremental cost = programme cost − $7.3M baseline; net = verbatim benefits − incremental cost.\n",
+ "This single frame captures both the 2026–27 **double-billing penalty** and the 2028\n",
+ "**cost-avoidance credit** once the old contracts terminate — no separate \"savings\" line needed.\n",
+ "The deck's implicit frame is the same, minus the three missing costs.\n",
+ "\n",
+ "*Not modelled: any early-termination fees, and migration costs beyond the PS/impl lines (§11).*"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "id": "4fdcfe24",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-07-07T12:22:44.139720Z",
+ "iopub.status.busy": "2026-07-07T12:22:44.139505Z",
+ "iopub.status.idle": "2026-07-07T12:22:44.145487Z",
+ "shell.execute_reply": "2026-07-07T12:22:44.144835Z"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "corrected: net by year {2026: '-$3.5M', 2027: '-$3.6M', 2028: '$11.9M'}\n",
+ "pitched: net by year {2026: '$433K', 2027: '$5.6M', 2028: '$15.4M'}\n"
+ ]
+ }
+ ],
+ "source": [
+ "BASELINE_ANNUAL = TCO_VERBATIM[\"current_annual\"]\n",
+ "\n",
+ "\n",
+ "def case_flows(total_cost_by_year):\n",
+ " inc = {y: total_cost_by_year[y] - BASELINE_ANNUAL for y in YEARS}\n",
+ " net = {y: benefit_total_by_year[y] - inc[y] for y in YEARS}\n",
+ " return inc, net\n",
+ "\n",
+ "\n",
+ "def case_kpis(inc, net):\n",
+ " net_list = [net[y] for y in YEARS]\n",
+ " inc_total = sum(inc.values())\n",
+ " pb = payback_years(net_list)\n",
+ " if pb is None:\n",
+ " pb_label = \"beyond 2028\"\n",
+ " elif pb == 0:\n",
+ " pb_label = \"immediate\"\n",
+ " else:\n",
+ " m = math.ceil(pb * 12)\n",
+ " pb_label = f\"{m} months (~{_MONTHS[(m - 1) % 12]} {2026 + (m - 1) // 12})\"\n",
+ " return {\n",
+ " \"3-yr benefits\": sum(benefit_total_by_year.values()),\n",
+ " \"3-yr incremental cost\": inc_total,\n",
+ " \"3-yr net\": sum(net_list),\n",
+ " \"ROI (net ÷ incremental cost)\":\n",
+ " f\"{sum(net_list) / inc_total:.0%}\" if inc_total > 0 else \"n/a — net cost saving\",\n",
+ " f\"NPV @ {DISCOUNT_RATE:.1%} (deck rate)\": npv(net_list, DISCOUNT_RATE),\n",
+ " \"NPV @ 8.0% (CTM treasury)\": npv(net_list, 0.08),\n",
+ " \"Payback\": pb_label,\n",
+ " }\n",
+ "\n",
+ "\n",
+ "inc_corrected, net_corrected = case_flows(corrected_total_by_year)\n",
+ "inc_pitched, net_pitched = case_flows(pitched_total_by_year)\n",
+ "kpi_corrected = case_kpis(inc_corrected, net_corrected)\n",
+ "kpi_pitched = case_kpis(inc_pitched, net_pitched)\n",
+ "print(f\"corrected: net by year { {y: money(v) for y, v in net_corrected.items()} }\")\n",
+ "print(f\"pitched: net by year { {y: money(v) for y, v in net_pitched.items()} }\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "id": "c43d1e56",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-07-07T12:22:44.147385Z",
+ "iopub.status.busy": "2026-07-07T12:22:44.147219Z",
+ "iopub.status.idle": "2026-07-07T12:22:44.177837Z",
+ "shell.execute_reply": "2026-07-07T12:22:44.176873Z"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.plotly.v1+json": {
+ "config": {
+ "plotlyServerURL": "https://plot.ly"
+ },
+ "data": [
+ {
+ "hovertemplate": "%{fullData.name}: %{y:$,.0f}",
+ "marker": {
+ "color": "#2a78d6",
+ "line": {
+ "color": "#fcfcfb",
+ "width": 2
+ }
+ },
+ "name": "Benefits (verbatim Genesys)",
+ "type": "bar",
+ "x": [
+ "2026",
+ "2027",
+ "2028"
+ ],
+ "y": [
+ 0.0,
+ 2614505.4945054944,
+ 12428494.505494505
+ ]
+ },
+ {
+ "hovertemplate": "%{fullData.name}: %{y:$,.0f}",
+ "marker": {
+ "color": "#e34948",
+ "line": {
+ "color": "#fcfcfb",
+ "width": 2
+ }
+ },
+ "name": "Incremental cost vs $7.3M/yr baseline",
+ "type": "bar",
+ "x": [
+ "2026",
+ "2027",
+ "2028"
+ ],
+ "y": [
+ -3490216.7032885533,
+ -6215923.770938251,
+ -538448.5257731955
+ ]
+ },
+ {
+ "hovertemplate": "%{fullData.name}: %{y:$,.0f}",
+ "line": {
+ "color": "#52514e",
+ "width": 2
+ },
+ "marker": {
+ "line": {
+ "color": "#fcfcfb",
+ "width": 2
+ },
+ "size": 8
+ },
+ "mode": "lines+markers",
+ "name": "Cumulative net",
+ "type": "scatter",
+ "x": [
+ "2026",
+ "2027",
+ "2028"
+ ],
+ "y": {
+ "bdata": "/FsFWtSgSsECwbO+bA1bwQAAAMDyTVJB",
+ "dtype": "f8"
+ }
+ }
+ ],
+ "layout": {
+ "annotations": [
+ {
+ "font": {
+ "color": "#0b0b0b",
+ "size": 12
+ },
+ "showarrow": false,
+ "text": "-$3.5M",
+ "x": 0,
+ "y": -3490216.7032885533,
+ "yshift": -14
+ },
+ {
+ "font": {
+ "color": "#0b0b0b",
+ "size": 12
+ },
+ "showarrow": false,
+ "text": "-$7.1M",
+ "x": 1,
+ "y": -7091634.97972131,
+ "yshift": -14
+ },
+ {
+ "font": {
+ "color": "#0b0b0b",
+ "size": 12
+ },
+ "showarrow": false,
+ "text": "$4.8M",
+ "x": 2,
+ "y": 4798411.0,
+ "yshift": 14
+ },
+ {
+ "align": "left",
+ "bgcolor": "#fcfcfb",
+ "bordercolor": "#e1e0d9",
+ "borderwidth": 1,
+ "font": {
+ "color": "#52514e",
+ "size": 12
+ },
+ "showarrow": false,
+ "text": "3-yr net $4.8M · ROI 47%
NPV@13.5% $2.3M · payback 32 months (~Aug 2028)",
+ "x": 0.01,
+ "xref": "paper",
+ "y": 0.98,
+ "yref": "paper"
+ }
+ ],
+ "bargap": 0.45,
+ "barmode": "relative",
+ "font": {
+ "color": "#52514e",
+ "family": "system-ui, -apple-system, \"Segoe UI\", sans-serif",
+ "size": 12
+ },
+ "height": 500,
+ "hovermode": "x unified",
+ "legend": {
+ "font": {
+ "color": "#52514e",
+ "size": 11
+ },
+ "orientation": "h",
+ "x": 0,
+ "y": -0.1,
+ "yanchor": "top"
+ },
+ "margin": {
+ "b": 80,
+ "l": 70,
+ "r": 30,
+ "t": 70
+ },
+ "paper_bgcolor": "#fcfcfb",
+ "plot_bgcolor": "#fcfcfb",
+ "template": {
+ "data": {
+ "bar": [
+ {
+ "error_x": {
+ "color": "#2a3f5f"
+ },
+ "error_y": {
+ "color": "#2a3f5f"
+ },
+ "marker": {
+ "line": {
+ "color": "#E5ECF6",
+ "width": 0.5
+ },
+ "pattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ }
+ },
+ "type": "bar"
+ }
+ ],
+ "barpolar": [
+ {
+ "marker": {
+ "line": {
+ "color": "#E5ECF6",
+ "width": 0.5
+ },
+ "pattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ }
+ },
+ "type": "barpolar"
+ }
+ ],
+ "carpet": [
+ {
+ "aaxis": {
+ "endlinecolor": "#2a3f5f",
+ "gridcolor": "white",
+ "linecolor": "white",
+ "minorgridcolor": "white",
+ "startlinecolor": "#2a3f5f"
+ },
+ "baxis": {
+ "endlinecolor": "#2a3f5f",
+ "gridcolor": "white",
+ "linecolor": "white",
+ "minorgridcolor": "white",
+ "startlinecolor": "#2a3f5f"
+ },
+ "type": "carpet"
+ }
+ ],
+ "choropleth": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "type": "choropleth"
+ }
+ ],
+ "contour": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "contour"
+ }
+ ],
+ "contourcarpet": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "type": "contourcarpet"
+ }
+ ],
+ "heatmap": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "heatmap"
+ }
+ ],
+ "histogram": [
+ {
+ "marker": {
+ "pattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ }
+ },
+ "type": "histogram"
+ }
+ ],
+ "histogram2d": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "histogram2d"
+ }
+ ],
+ "histogram2dcontour": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "histogram2dcontour"
+ }
+ ],
+ "mesh3d": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "type": "mesh3d"
+ }
+ ],
+ "parcoords": [
+ {
+ "line": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "parcoords"
+ }
+ ],
+ "pie": [
+ {
+ "automargin": true,
+ "type": "pie"
+ }
+ ],
+ "scatter": [
+ {
+ "fillpattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ },
+ "type": "scatter"
+ }
+ ],
+ "scatter3d": [
+ {
+ "line": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatter3d"
+ }
+ ],
+ "scattercarpet": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattercarpet"
+ }
+ ],
+ "scattergeo": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattergeo"
+ }
+ ],
+ "scattergl": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattergl"
+ }
+ ],
+ "scattermap": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattermap"
+ }
+ ],
+ "scattermapbox": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattermapbox"
+ }
+ ],
+ "scatterpolar": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatterpolar"
+ }
+ ],
+ "scatterpolargl": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatterpolargl"
+ }
+ ],
+ "scatterternary": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatterternary"
+ }
+ ],
+ "surface": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "surface"
+ }
+ ],
+ "table": [
+ {
+ "cells": {
+ "fill": {
+ "color": "#EBF0F8"
+ },
+ "line": {
+ "color": "white"
+ }
+ },
+ "header": {
+ "fill": {
+ "color": "#C8D4E3"
+ },
+ "line": {
+ "color": "white"
+ }
+ },
+ "type": "table"
+ }
+ ]
+ },
+ "layout": {
+ "annotationdefaults": {
+ "arrowcolor": "#2a3f5f",
+ "arrowhead": 0,
+ "arrowwidth": 1
+ },
+ "autotypenumbers": "strict",
+ "coloraxis": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "colorscale": {
+ "diverging": [
+ [
+ 0,
+ "#8e0152"
+ ],
+ [
+ 0.1,
+ "#c51b7d"
+ ],
+ [
+ 0.2,
+ "#de77ae"
+ ],
+ [
+ 0.3,
+ "#f1b6da"
+ ],
+ [
+ 0.4,
+ "#fde0ef"
+ ],
+ [
+ 0.5,
+ "#f7f7f7"
+ ],
+ [
+ 0.6,
+ "#e6f5d0"
+ ],
+ [
+ 0.7,
+ "#b8e186"
+ ],
+ [
+ 0.8,
+ "#7fbc41"
+ ],
+ [
+ 0.9,
+ "#4d9221"
+ ],
+ [
+ 1,
+ "#276419"
+ ]
+ ],
+ "sequential": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "sequentialminus": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ]
+ },
+ "colorway": [
+ "#636efa",
+ "#EF553B",
+ "#00cc96",
+ "#ab63fa",
+ "#FFA15A",
+ "#19d3f3",
+ "#FF6692",
+ "#B6E880",
+ "#FF97FF",
+ "#FECB52"
+ ],
+ "font": {
+ "color": "#2a3f5f"
+ },
+ "geo": {
+ "bgcolor": "white",
+ "lakecolor": "white",
+ "landcolor": "#E5ECF6",
+ "showlakes": true,
+ "showland": true,
+ "subunitcolor": "white"
+ },
+ "hoverlabel": {
+ "align": "left"
+ },
+ "hovermode": "closest",
+ "mapbox": {
+ "style": "light"
+ },
+ "paper_bgcolor": "white",
+ "plot_bgcolor": "#E5ECF6",
+ "polar": {
+ "angularaxis": {
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": ""
+ },
+ "bgcolor": "#E5ECF6",
+ "radialaxis": {
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": ""
+ }
+ },
+ "scene": {
+ "xaxis": {
+ "backgroundcolor": "#E5ECF6",
+ "gridcolor": "white",
+ "gridwidth": 2,
+ "linecolor": "white",
+ "showbackground": true,
+ "ticks": "",
+ "zerolinecolor": "white"
+ },
+ "yaxis": {
+ "backgroundcolor": "#E5ECF6",
+ "gridcolor": "white",
+ "gridwidth": 2,
+ "linecolor": "white",
+ "showbackground": true,
+ "ticks": "",
+ "zerolinecolor": "white"
+ },
+ "zaxis": {
+ "backgroundcolor": "#E5ECF6",
+ "gridcolor": "white",
+ "gridwidth": 2,
+ "linecolor": "white",
+ "showbackground": true,
+ "ticks": "",
+ "zerolinecolor": "white"
+ }
+ },
+ "shapedefaults": {
+ "line": {
+ "color": "#2a3f5f"
+ }
+ },
+ "ternary": {
+ "aaxis": {
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": ""
+ },
+ "baxis": {
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": ""
+ },
+ "bgcolor": "#E5ECF6",
+ "caxis": {
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": ""
+ }
+ },
+ "title": {
+ "x": 0.05
+ },
+ "xaxis": {
+ "automargin": true,
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": "",
+ "title": {
+ "standoff": 15
+ },
+ "zerolinecolor": "white",
+ "zerolinewidth": 2
+ },
+ "yaxis": {
+ "automargin": true,
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": "",
+ "title": {
+ "standoff": 15
+ },
+ "zerolinecolor": "white",
+ "zerolinewidth": 2
+ }
+ }
+ },
+ "title": {
+ "font": {
+ "color": "#0b0b0b",
+ "size": 16
+ },
+ "text": "Corrected business case — benefits vs incremental cost
Baseline = keep paying $7.3M/yr · double-billing hits 2026-27, cost avoidance and benefits land 2028",
+ "x": 0.02,
+ "xanchor": "left"
+ },
+ "xaxis": {
+ "linecolor": "#c3c2b7",
+ "showgrid": false,
+ "tickfont": {
+ "color": "#898781"
+ },
+ "type": "category"
+ },
+ "yaxis": {
+ "gridcolor": "#e1e0d9",
+ "tickfont": {
+ "color": "#898781"
+ },
+ "tickformat": "$~s",
+ "zerolinecolor": "#c3c2b7",
+ "zerolinewidth": 1.5
+ }
+ }
+ }
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "fig = go.Figure()\n",
+ "fig.add_trace(bar(X, [benefit_total_by_year[y] for y in YEARS],\n",
+ " \"Benefits (verbatim Genesys)\", \"#2a78d6\"))\n",
+ "fig.add_trace(bar(X, [-inc_corrected[y] for y in YEARS],\n",
+ " \"Incremental cost vs $7.3M/yr baseline\", \"#e34948\"))\n",
+ "cum_net = pd.Series([net_corrected[y] for y in YEARS]).cumsum()\n",
+ "fig.add_trace(cum_line(X, cum_net, \"Cumulative net\"))\n",
+ "for i, c in enumerate(cum_net):\n",
+ " fig.add_annotation(x=i, y=float(c), text=f\"{money(float(c))}\", showarrow=False,\n",
+ " yshift=14 if c >= 0 else -14, font=dict(size=12, color=INK))\n",
+ "fig.update_layout(barmode=\"relative\")\n",
+ "fig.add_annotation(\n",
+ " xref=\"paper\", yref=\"paper\", x=0.01, y=0.98, align=\"left\", showarrow=False,\n",
+ " font=dict(size=12, color=INK2), bgcolor=SURFACE, bordercolor=GRID, borderwidth=1,\n",
+ " text=(f\"3-yr net {html_money(kpi_corrected['3-yr net'])} · \"\n",
+ " f\"ROI {kpi_corrected['ROI (net ÷ incremental cost)']}
\"\n",
+ " f\"NPV@{DISCOUNT_RATE:.1%} {html_money(kpi_corrected[f'NPV @ {DISCOUNT_RATE:.1%} (deck rate)'])} · \"\n",
+ " f\"payback {kpi_corrected['Payback']}\"))\n",
+ "tei_layout(fig, \"Corrected business case — benefits vs incremental cost\",\n",
+ " \"Baseline = keep paying $7.3M/yr · double-billing hits 2026-27, \"\n",
+ " \"cost avoidance and benefits land 2028\", height=500)\n",
+ "fig.show()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "id": "f504f2dc",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-07-07T12:22:44.180248Z",
+ "iopub.status.busy": "2026-07-07T12:22:44.180036Z",
+ "iopub.status.idle": "2026-07-07T12:22:44.206953Z",
+ "shell.execute_reply": "2026-07-07T12:22:44.204790Z"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "The missed costs move the 3-yr case by $16.7M ($21.5M pitched → $4.8M corrected).\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " As pitched (deck) | \n",
+ " Corrected | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 3-yr benefits | \n",
+ " $15.0M | \n",
+ " $15.0M | \n",
+ "
\n",
+ " \n",
+ " | 3-yr incremental cost | \n",
+ " -$6.4M | \n",
+ " $10.2M | \n",
+ "
\n",
+ " \n",
+ " | 3-yr net | \n",
+ " $21.5M | \n",
+ " $4.8M | \n",
+ "
\n",
+ " \n",
+ " | ROI (net ÷ incremental cost) | \n",
+ " n/a — net cost saving | \n",
+ " 47% | \n",
+ "
\n",
+ " \n",
+ " | NPV @ 13.5% (deck rate) | \n",
+ " $15.3M | \n",
+ " $2.3M | \n",
+ "
\n",
+ " \n",
+ " | NPV @ 8.0% (CTM treasury) | \n",
+ " $17.5M | \n",
+ " $3.1M | \n",
+ "
\n",
+ " \n",
+ " | Payback | \n",
+ " immediate | \n",
+ " 32 months (~Aug 2028) | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " As pitched (deck) Corrected\n",
+ "3-yr benefits $15.0M $15.0M\n",
+ "3-yr incremental cost -$6.4M $10.2M\n",
+ "3-yr net $21.5M $4.8M\n",
+ "ROI (net ÷ incremental cost) n/a — net cost saving 47%\n",
+ "NPV @ 13.5% (deck rate) $15.3M $2.3M\n",
+ "NPV @ 8.0% (CTM treasury) $17.5M $3.1M\n",
+ "Payback immediate 32 months (~Aug 2028)"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "kpis = pd.DataFrame({\"As pitched (deck)\": kpi_pitched, \"Corrected\": kpi_corrected})\n",
+ "kpis_fmt = kpis.map(lambda v: money(v) if isinstance(v, (int, float)) else v)\n",
+ "delta_net = kpi_pitched[\"3-yr net\"] - kpi_corrected[\"3-yr net\"]\n",
+ "print(f\"The missed costs move the 3-yr case by {money(delta_net)} \"\n",
+ " f\"({money(kpi_pitched['3-yr net'])} pitched → {money(kpi_corrected['3-yr net'])} corrected).\")\n",
+ "display(kpis_fmt)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ab730edc",
+ "metadata": {},
+ "source": [
+ "## §9 · Sensitivity\n",
+ "\n",
+ "The two weakest inputs, swept: the **Email Auto-Respond token rate** (🔴 unpublished — the working\n",
+ "assumption is the only estimated meter in the token stack) and the **AI implementation LoE**\n",
+ "(V1 vs V2 disagreed by ~4×)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "id": "80c2a060",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-07-07T12:22:44.210569Z",
+ "iopub.status.busy": "2026-07-07T12:22:44.210085Z",
+ "iopub.status.idle": "2026-07-07T12:22:44.477936Z",
+ "shell.execute_reply": "2026-07-07T12:22:44.476537Z"
+ }
+ },
+ "outputs": [
+ {
+ "data": {
+ "application/vnd.plotly.v1+json": {
+ "config": {
+ "plotlyServerURL": "https://plot.ly"
+ },
+ "data": [
+ {
+ "colorscale": [
+ [
+ 0.0,
+ "#cde2fb"
+ ],
+ [
+ 0.08333333333333333,
+ "#b7d3f6"
+ ],
+ [
+ 0.16666666666666666,
+ "#9ec5f4"
+ ],
+ [
+ 0.25,
+ "#86b6ef"
+ ],
+ [
+ 0.3333333333333333,
+ "#6da7ec"
+ ],
+ [
+ 0.4166666666666667,
+ "#5598e7"
+ ],
+ [
+ 0.5,
+ "#3987e5"
+ ],
+ [
+ 0.5833333333333334,
+ "#2a78d6"
+ ],
+ [
+ 0.6666666666666666,
+ "#256abf"
+ ],
+ [
+ 0.75,
+ "#1c5cab"
+ ],
+ [
+ 0.8333333333333334,
+ "#184f95"
+ ],
+ [
+ 0.9166666666666666,
+ "#104281"
+ ],
+ [
+ 1.0,
+ "#0d366b"
+ ]
+ ],
+ "hovertemplate": "%{y} × %{x}: %{z:$,.0f}",
+ "showscale": false,
+ "type": "heatmap",
+ "x": [
+ "0.02 tokens/msg",
+ "0.05 tokens/msg",
+ "0.1 tokens/msg"
+ ],
+ "xgap": 2,
+ "y": [
+ "10.0% auto-respond",
+ "25.5% auto-respond",
+ "50.0% auto-respond"
+ ],
+ "ygap": 2,
+ "z": [
+ [
+ 20930.0,
+ 52301.0,
+ 104602.0
+ ],
+ [
+ 53389.0,
+ 133408.0,
+ 266749.0
+ ],
+ [
+ 104602.0,
+ 261505.0,
+ 523010.0
+ ]
+ ]
+ }
+ ],
+ "layout": {
+ "annotations": [
+ {
+ "font": {
+ "color": "#0b0b0b",
+ "size": 12
+ },
+ "showarrow": false,
+ "text": "$21K",
+ "x": 0,
+ "y": 0
+ },
+ {
+ "font": {
+ "color": "#0b0b0b",
+ "size": 12
+ },
+ "showarrow": false,
+ "text": "$52K",
+ "x": 1,
+ "y": 0
+ },
+ {
+ "font": {
+ "color": "#0b0b0b",
+ "size": 12
+ },
+ "showarrow": false,
+ "text": "$105K",
+ "x": 2,
+ "y": 0
+ },
+ {
+ "font": {
+ "color": "#0b0b0b",
+ "size": 12
+ },
+ "showarrow": false,
+ "text": "$53K",
+ "x": 0,
+ "y": 1
+ },
+ {
+ "font": {
+ "color": "#0b0b0b",
+ "size": 12
+ },
+ "showarrow": false,
+ "text": "$133K",
+ "x": 1,
+ "y": 1
+ },
+ {
+ "font": {
+ "color": "#0b0b0b",
+ "size": 12
+ },
+ "showarrow": false,
+ "text": "$267K",
+ "x": 2,
+ "y": 1
+ },
+ {
+ "font": {
+ "color": "#0b0b0b",
+ "size": 12
+ },
+ "showarrow": false,
+ "text": "$105K",
+ "x": 0,
+ "y": 2
+ },
+ {
+ "font": {
+ "color": "#0b0b0b",
+ "size": 12
+ },
+ "showarrow": false,
+ "text": "$262K",
+ "x": 1,
+ "y": 2
+ },
+ {
+ "font": {
+ "color": "#ffffff",
+ "size": 12
+ },
+ "showarrow": false,
+ "text": "$523K",
+ "x": 2,
+ "y": 2
+ }
+ ],
+ "bargap": 0.45,
+ "font": {
+ "color": "#52514e",
+ "family": "system-ui, -apple-system, \"Segoe UI\", sans-serif",
+ "size": 12
+ },
+ "height": 380,
+ "hovermode": "closest",
+ "legend": {
+ "font": {
+ "color": "#52514e",
+ "size": 11
+ },
+ "orientation": "h",
+ "x": 0,
+ "y": -0.1,
+ "yanchor": "top"
+ },
+ "margin": {
+ "b": 80,
+ "l": 70,
+ "r": 30,
+ "t": 70
+ },
+ "paper_bgcolor": "#fcfcfb",
+ "plot_bgcolor": "#fcfcfb",
+ "template": {
+ "data": {
+ "bar": [
+ {
+ "error_x": {
+ "color": "#2a3f5f"
+ },
+ "error_y": {
+ "color": "#2a3f5f"
+ },
+ "marker": {
+ "line": {
+ "color": "#E5ECF6",
+ "width": 0.5
+ },
+ "pattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ }
+ },
+ "type": "bar"
+ }
+ ],
+ "barpolar": [
+ {
+ "marker": {
+ "line": {
+ "color": "#E5ECF6",
+ "width": 0.5
+ },
+ "pattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ }
+ },
+ "type": "barpolar"
+ }
+ ],
+ "carpet": [
+ {
+ "aaxis": {
+ "endlinecolor": "#2a3f5f",
+ "gridcolor": "white",
+ "linecolor": "white",
+ "minorgridcolor": "white",
+ "startlinecolor": "#2a3f5f"
+ },
+ "baxis": {
+ "endlinecolor": "#2a3f5f",
+ "gridcolor": "white",
+ "linecolor": "white",
+ "minorgridcolor": "white",
+ "startlinecolor": "#2a3f5f"
+ },
+ "type": "carpet"
+ }
+ ],
+ "choropleth": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "type": "choropleth"
+ }
+ ],
+ "contour": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "contour"
+ }
+ ],
+ "contourcarpet": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "type": "contourcarpet"
+ }
+ ],
+ "heatmap": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "heatmap"
+ }
+ ],
+ "histogram": [
+ {
+ "marker": {
+ "pattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ }
+ },
+ "type": "histogram"
+ }
+ ],
+ "histogram2d": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "histogram2d"
+ }
+ ],
+ "histogram2dcontour": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "histogram2dcontour"
+ }
+ ],
+ "mesh3d": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "type": "mesh3d"
+ }
+ ],
+ "parcoords": [
+ {
+ "line": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "parcoords"
+ }
+ ],
+ "pie": [
+ {
+ "automargin": true,
+ "type": "pie"
+ }
+ ],
+ "scatter": [
+ {
+ "fillpattern": {
+ "fillmode": "overlay",
+ "size": 10,
+ "solidity": 0.2
+ },
+ "type": "scatter"
+ }
+ ],
+ "scatter3d": [
+ {
+ "line": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatter3d"
+ }
+ ],
+ "scattercarpet": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattercarpet"
+ }
+ ],
+ "scattergeo": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattergeo"
+ }
+ ],
+ "scattergl": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattergl"
+ }
+ ],
+ "scattermap": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattermap"
+ }
+ ],
+ "scattermapbox": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scattermapbox"
+ }
+ ],
+ "scatterpolar": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatterpolar"
+ }
+ ],
+ "scatterpolargl": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatterpolargl"
+ }
+ ],
+ "scatterternary": [
+ {
+ "marker": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "type": "scatterternary"
+ }
+ ],
+ "surface": [
+ {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ },
+ "colorscale": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "type": "surface"
+ }
+ ],
+ "table": [
+ {
+ "cells": {
+ "fill": {
+ "color": "#EBF0F8"
+ },
+ "line": {
+ "color": "white"
+ }
+ },
+ "header": {
+ "fill": {
+ "color": "#C8D4E3"
+ },
+ "line": {
+ "color": "white"
+ }
+ },
+ "type": "table"
+ }
+ ]
+ },
+ "layout": {
+ "annotationdefaults": {
+ "arrowcolor": "#2a3f5f",
+ "arrowhead": 0,
+ "arrowwidth": 1
+ },
+ "autotypenumbers": "strict",
+ "coloraxis": {
+ "colorbar": {
+ "outlinewidth": 0,
+ "ticks": ""
+ }
+ },
+ "colorscale": {
+ "diverging": [
+ [
+ 0,
+ "#8e0152"
+ ],
+ [
+ 0.1,
+ "#c51b7d"
+ ],
+ [
+ 0.2,
+ "#de77ae"
+ ],
+ [
+ 0.3,
+ "#f1b6da"
+ ],
+ [
+ 0.4,
+ "#fde0ef"
+ ],
+ [
+ 0.5,
+ "#f7f7f7"
+ ],
+ [
+ 0.6,
+ "#e6f5d0"
+ ],
+ [
+ 0.7,
+ "#b8e186"
+ ],
+ [
+ 0.8,
+ "#7fbc41"
+ ],
+ [
+ 0.9,
+ "#4d9221"
+ ],
+ [
+ 1,
+ "#276419"
+ ]
+ ],
+ "sequential": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ],
+ "sequentialminus": [
+ [
+ 0.0,
+ "#0d0887"
+ ],
+ [
+ 0.1111111111111111,
+ "#46039f"
+ ],
+ [
+ 0.2222222222222222,
+ "#7201a8"
+ ],
+ [
+ 0.3333333333333333,
+ "#9c179e"
+ ],
+ [
+ 0.4444444444444444,
+ "#bd3786"
+ ],
+ [
+ 0.5555555555555556,
+ "#d8576b"
+ ],
+ [
+ 0.6666666666666666,
+ "#ed7953"
+ ],
+ [
+ 0.7777777777777778,
+ "#fb9f3a"
+ ],
+ [
+ 0.8888888888888888,
+ "#fdca26"
+ ],
+ [
+ 1.0,
+ "#f0f921"
+ ]
+ ]
+ },
+ "colorway": [
+ "#636efa",
+ "#EF553B",
+ "#00cc96",
+ "#ab63fa",
+ "#FFA15A",
+ "#19d3f3",
+ "#FF6692",
+ "#B6E880",
+ "#FF97FF",
+ "#FECB52"
+ ],
+ "font": {
+ "color": "#2a3f5f"
+ },
+ "geo": {
+ "bgcolor": "white",
+ "lakecolor": "white",
+ "landcolor": "#E5ECF6",
+ "showlakes": true,
+ "showland": true,
+ "subunitcolor": "white"
+ },
+ "hoverlabel": {
+ "align": "left"
+ },
+ "hovermode": "closest",
+ "mapbox": {
+ "style": "light"
+ },
+ "paper_bgcolor": "white",
+ "plot_bgcolor": "#E5ECF6",
+ "polar": {
+ "angularaxis": {
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": ""
+ },
+ "bgcolor": "#E5ECF6",
+ "radialaxis": {
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": ""
+ }
+ },
+ "scene": {
+ "xaxis": {
+ "backgroundcolor": "#E5ECF6",
+ "gridcolor": "white",
+ "gridwidth": 2,
+ "linecolor": "white",
+ "showbackground": true,
+ "ticks": "",
+ "zerolinecolor": "white"
+ },
+ "yaxis": {
+ "backgroundcolor": "#E5ECF6",
+ "gridcolor": "white",
+ "gridwidth": 2,
+ "linecolor": "white",
+ "showbackground": true,
+ "ticks": "",
+ "zerolinecolor": "white"
+ },
+ "zaxis": {
+ "backgroundcolor": "#E5ECF6",
+ "gridcolor": "white",
+ "gridwidth": 2,
+ "linecolor": "white",
+ "showbackground": true,
+ "ticks": "",
+ "zerolinecolor": "white"
+ }
+ },
+ "shapedefaults": {
+ "line": {
+ "color": "#2a3f5f"
+ }
+ },
+ "ternary": {
+ "aaxis": {
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": ""
+ },
+ "baxis": {
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": ""
+ },
+ "bgcolor": "#E5ECF6",
+ "caxis": {
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": ""
+ }
+ },
+ "title": {
+ "x": 0.05
+ },
+ "xaxis": {
+ "automargin": true,
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": "",
+ "title": {
+ "standoff": 15
+ },
+ "zerolinecolor": "white",
+ "zerolinewidth": 2
+ },
+ "yaxis": {
+ "automargin": true,
+ "gridcolor": "white",
+ "linecolor": "white",
+ "ticks": "",
+ "title": {
+ "standoff": 15
+ },
+ "zerolinecolor": "white",
+ "zerolinewidth": 2
+ }
+ }
+ },
+ "title": {
+ "font": {
+ "color": "#0b0b0b",
+ "size": 16
+ },
+ "text": "Email Auto-Respond tokens — 3-yr cost under the unpublished rate
Deck claims a $2.9M 3-yr Email benefit; even the worst corner stays well below it",
+ "x": 0.02,
+ "xanchor": "left"
+ },
+ "xaxis": {
+ "linecolor": "#c3c2b7",
+ "showgrid": false,
+ "tickfont": {
+ "color": "#898781"
+ },
+ "type": "category"
+ },
+ "yaxis": {
+ "gridcolor": "#e1e0d9",
+ "showgrid": false,
+ "tickfont": {
+ "color": "#898781"
+ },
+ "zerolinecolor": "#c3c2b7",
+ "zerolinewidth": 1.5
+ }
+ }
+ }
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# ── 3-yr Email Auto-Respond token cost: tokens/msg × auto-respond rate ─\n",
+ "TOK_GRID = [0.02, 0.05, 0.10]\n",
+ "RATE_GRID = [0.10, 0.255, 0.50]\n",
+ "\n",
+ "\n",
+ "def email_token_cost_3yr(tokens_per_msg, respond_rate):\n",
+ " meter = dataclasses.replace(AUTORESPOND_METER, tokens_per_unit=tokens_per_msg,\n",
+ " units_per_token=1.0 / tokens_per_msg)\n",
+ " sc = dataclasses.replace(GENESYS_CLAIM, email_auto_respond_rate=respond_rate)\n",
+ " m = {**METERS, \"Email AI (Auto-Respond)\": meter}\n",
+ " return sum(\n",
+ " calculate_total_cost(sites, EMAIL_SCOPES, m, DEFAULT_PRICING, sc, YEAR_INDEX[y],\n",
+ " include_platform=False, rollout=EMAIL_TOKEN_ROLLOUT)\n",
+ " [\"annual_cost\"].sum()\n",
+ " for y in YEARS)\n",
+ "\n",
+ "\n",
+ "grid = [[email_token_cost_3yr(t, r) for t in TOK_GRID] for r in RATE_GRID]\n",
+ "zmax = max(max(row) for row in grid)\n",
+ "fig = go.Figure(go.Heatmap(\n",
+ " z=grid, x=[f\"{t} tokens/msg\" for t in TOK_GRID],\n",
+ " y=[f\"{r:.1%} auto-respond\" for r in RATE_GRID],\n",
+ " colorscale=[[i / (len(SEQ_BLUES) - 1), c] for i, c in enumerate(SEQ_BLUES)],\n",
+ " showscale=False, xgap=2, ygap=2,\n",
+ " hovertemplate=\"%{y} × %{x}: %{z:$,.0f}\"))\n",
+ "for i, r in enumerate(RATE_GRID):\n",
+ " for j, t in enumerate(TOK_GRID):\n",
+ " v = grid[i][j]\n",
+ " fig.add_annotation(x=j, y=i, text=f\"{money(v)}\", showarrow=False,\n",
+ " font=dict(size=12, color=\"#ffffff\" if v > 0.55 * zmax else INK))\n",
+ "tei_layout(fig, \"Email Auto-Respond tokens — 3-yr cost under the unpublished rate\",\n",
+ " \"Deck claims a $2.9M 3-yr Email benefit; even the worst corner stays well below it\",\n",
+ " height=380)\n",
+ "fig.update_layout(xaxis=dict(showgrid=False), yaxis=dict(showgrid=False, tickformat=None),\n",
+ " hovermode=\"closest\")\n",
+ "fig.show()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "id": "abd816ea",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-07-07T12:22:44.480803Z",
+ "iopub.status.busy": "2026-07-07T12:22:44.480601Z",
+ "iopub.status.idle": "2026-07-07T12:22:44.510856Z",
+ "shell.execute_reply": "2026-07-07T12:22:44.509842Z"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "3-yr corrected net under implementation-cost assumptions (V2 LoE range × blended rate):\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " $175/h | \n",
+ " $225/h | \n",
+ " $275/h | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | low hours | \n",
+ " 5,622,161 | \n",
+ " 5,327,161 | \n",
+ " 5,032,161 | \n",
+ "
\n",
+ " \n",
+ " | mid hours | \n",
+ " 5,210,911 | \n",
+ " 4,798,411 | \n",
+ " 4,385,911 | \n",
+ "
\n",
+ " \n",
+ " | high hours | \n",
+ " 4,799,661 | \n",
+ " 4,269,661 | \n",
+ " 3,739,661 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " $175/h $225/h $275/h\n",
+ "low hours 5,622,161 5,327,161 5,032,161\n",
+ "mid hours 5,210,911 4,798,411 4,385,911\n",
+ "high hours 4,799,661 4,269,661 3,739,661"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Spread across the whole grid: $1.9M — the conclusion is not hostage to the implementation estimate; tokens and double-billing dominate.\n"
+ ]
+ }
+ ],
+ "source": [
+ "# ── 3-yr corrected net across impl hours mode × blended rate ─────────\n",
+ "def corrected_net_3yr(mode, rate):\n",
+ " _, iby, kby, ssby = build_impl_costs(mode, rate, INCLUDE_KB_READINESS)\n",
+ " total = {y: licence_by_year[y] + ps_by_year[y] + current_by_year[y]\n",
+ " + token_total_by_year[y] + iby[y] + kby[y] + ssby[y] for y in YEARS}\n",
+ " _, net = case_flows(total)\n",
+ " return sum(net.values())\n",
+ "\n",
+ "\n",
+ "impl_sens = pd.DataFrame(\n",
+ " {f\"${r}/h\": {m: corrected_net_3yr(m, r) for m in (\"low\", \"mid\", \"high\")}\n",
+ " for r in (175, 225, 275)})\n",
+ "impl_sens.index = [f\"{m} hours\" for m in impl_sens.index]\n",
+ "print(\"3-yr corrected net under implementation-cost assumptions \"\n",
+ " \"(V2 LoE range × blended rate):\")\n",
+ "display(impl_sens)\n",
+ "spread = float(impl_sens.max().max() - impl_sens.min().min())\n",
+ "print(f\"Spread across the whole grid: {money(spread)} — the conclusion is not \"\n",
+ " f\"hostage to the implementation estimate; tokens and double-billing dominate.\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "cb6ad27e",
+ "metadata": {},
+ "source": [
+ "## §10 · Verification & assertions\n",
+ "\n",
+ "The next cell re-derives key numbers independently and **raises on any failure**, so\n",
+ "`jupyter nbconvert --execute` acts as a regression gate for this notebook. Config-dependent\n",
+ "checks are guarded so the cell stays valid after you edit the CONFIG cells."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "id": "7e241b27",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2026-07-07T12:22:44.513962Z",
+ "iopub.status.busy": "2026-07-07T12:22:44.513773Z",
+ "iopub.status.idle": "2026-07-07T12:22:44.539348Z",
+ "shell.execute_reply": "2026-07-07T12:22:44.537406Z"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "All assertions passed.\n",
+ " benefits 3-yr $15.0M · tokens 3-yr $4.5M · corrected net 3-yr $4.8M\n"
+ ]
+ }
+ ],
+ "source": [
+ "def _approx(got, want, tol=0.5):\n",
+ " assert abs(got - want) <= tol, f\"got {got:,.2f}, want {want:,.2f}\"\n",
+ "\n",
+ "\n",
+ "# §1 — contract mechanics\n",
+ "_approx(current_state[\"annual_cost\"].sum(), 7_300_000)\n",
+ "if RAMP_MONTHS == 12:\n",
+ " _approx(licence_by_year[2026], 0)\n",
+ " _approx(licence_by_year[2027], 4_300_000)\n",
+ "if all(row[\"contract_termination\"] == dt.date(2027, 12, 31) for _, row in current_state.iterrows()):\n",
+ " _approx(current_by_year[2026], 7_300_000)\n",
+ " _approx(current_by_year[2028], 0)\n",
+ "\n",
+ "# §2 — verbatim benefits reproduce the deck\n",
+ "_approx(benefit_total_by_year[2026], 0)\n",
+ "_approx(sum(benefit_total_by_year.values()), verbatim[\"three_yr\"].sum())\n",
+ "assert abs(sum(benefit_total_by_year.values()) - SLIDE_TOTALS[\"total_3yr\"]) <= _tol(15e6)\n",
+ "for r in REGIONS:\n",
+ " _approx(benefits_long.query(\"region == @r\")[\"benefit\"].sum(),\n",
+ " verbatim.query(\"region == @r\")[\"three_yr\"].sum())\n",
+ "\n",
+ "# §3 — token hand-checks (independent derivations)\n",
+ "sta = tokens_long.query(\"cost_line == 'Speech & Text Analytics [named]'\")\n",
+ "_named = {s.site_name: s.named_users for s in sites}\n",
+ "_sta_2028 = sum(_named[n] * 30 * TOKEN_ROLLOUT.live_months_in_year(n, 3) for n in ALL_SITES)\n",
+ "_approx(sta.query(\"year == 2028\")[\"annual_cost\"].sum(), _sta_2028)\n",
+ "if not COPILOT_INCLUDES_ASIA:\n",
+ " _approx(_sta_2028, 715_800) # NAM/AUZ/EMEA ×12mo + ASIA ×10mo, by hand\n",
+ " cp = tokens_long.query(\"cost_line == 'Agent Copilot [named]' and year == 2028\")\n",
+ " _approx(cp[\"annual_cost\"].sum(), 1_490 * 40 * 12) # = $715,200\n",
+ "assert (tokens_long.query(\"cost_line == 'AI Summary & Insights'\")[\"annual_cost\"] == 0).all(), \\\n",
+ " \"Rule 1: Copilot must cover AI Summary at Copilot sites\"\n",
+ "assert math.ceil(1_214_358 * DEFAULT_METERS[\"Predictive Routing\"].tokens_per_unit) == 71_433\n",
+ "_approx(tokens_long.query(\"year == 2026\")[\"annual_cost\"].sum(), 0) # nothing live in 2026\n",
+ "\n",
+ "# §4 — impl model reconciles with the V2 doc\n",
+ "if HOURS_MODE == \"mid\" and BLENDED_RATE == 225:\n",
+ " _approx(sum(impl_by_year.values()), 5_850 * 225) # $1,316,250 — V2's \"$1.3M\"\n",
+ " _approx(sum(steady_by_year.values()), 700 * 225 * 2)\n",
+ " if INCLUDE_KB_READINESS:\n",
+ " _approx(sum(kb_by_year.values()), 1_000 * 225)\n",
+ "\n",
+ "# §5 — cost stacks\n",
+ "_approx(sum(pitched_total_by_year.values()),\n",
+ " 3 * TCO_VERBATIM[\"ccaas_annual\"] + ps_by_year[2026]) # ≈ deck's $15.4M\n",
+ "_approx(sum(corrected_total_by_year.values()),\n",
+ " sum(licence_by_year.values()) + ps_by_year[2026] + sum(current_by_year.values())\n",
+ " + sum(token_total_by_year.values()) + sum(impl_kb_by_year.values())\n",
+ " + sum(steady_by_year.values()))\n",
+ "\n",
+ "# §8 — flows tie out\n",
+ "for y in YEARS:\n",
+ " _approx(net_corrected[y],\n",
+ " benefit_total_by_year[y] - (corrected_total_by_year[y] - BASELINE_ANNUAL))\n",
+ "\n",
+ "print(\"All assertions passed.\")\n",
+ "print(f\" benefits 3-yr {money(sum(benefit_total_by_year.values()))} · \"\n",
+ " f\"tokens 3-yr {money(sum(token_total_by_year.values()))} · \"\n",
+ " f\"corrected net 3-yr {money(kpi_corrected['3-yr net'])}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8c43881a",
+ "metadata": {},
+ "source": [
+ "## §11 · Risks, gaps & next steps\n",
+ "\n",
+ "**Findings to lead with**\n",
+ "- **Predictive Routing is net-negative standalone at claimed scope:** ~$1.8M/yr in tokens at 100%\n",
+ " eligibility vs a $470K/yr claimed benefit. `PR_ELIGIBILITY` is the honest knob — Genesys likely\n",
+ " intends PR on a queue subset; ask which queues.\n",
+ "- **2026 is a pure cost year** — $0 benefits under Genesys's own schedule, while CTM double-pays\n",
+ " existing platforms plus PS and early AI implementation.\n",
+ "- The ramp programme (12 licence-free months) is a real credit the deck also failed to model —\n",
+ " the correction is not one-directional.\n",
+ "\n",
+ "**Known gaps / data wanted**\n",
+ "- Non-NAM site volumes & AHTs are `tokencalc` placeholders (🟡) — all non-NA token figures inherit\n",
+ " that. Regional current-cost split is an agent-share allocation pending real contract data.\n",
+ "- Email Auto-Respond token rate is unpublished (🔴) — §9 bounds it; even the worst corner is small\n",
+ " relative to the Email benefit claim.\n",
+ "- Early-termination fees, co-term options, and migration costs beyond PS/impl are not modelled.\n",
+ "- Contract termination default (31 Dec 2027) zeroes current costs in 2028; if any region's term\n",
+ " runs longer, 2028 worsens — edit the per-region dates in §1.\n",
+ "- The smell test (§4) flags V2 implementation hours as below the 20–40% industry band — V2\n",
+ " deliberately strips vendor inflation; §9 shows the conclusion is robust across the whole\n",
+ " V2 range × rate grid.\n",
+ "- Deck internal rounding: cell-level benefits cross-foot to $15.04M vs the $15.0M headline\n",
+ " (and $13.72M vs $13.6M annual); tolerated, not \"fixed\".\n",
+ "\n",
+ "**Next steps**\n",
+ "- Replace §4's hours-range model with the activity-level `ImplementationEffort` engine sketched in\n",
+ " `docs/ctm_ai_labour_estimate.md` (dataclasses + `tokencalc/implementation.py` + tests).\n",
+ "- Confirm contracted token rates and regional pricing (EU/AU/APAC flagged TBD at $1.00 list).\n",
+ "- Feed real per-region current-platform contract values and termination dates into §1."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.13.7"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/studies/202512_GenesysCX/ctm-token-calculator/tests/test_cost_model.py b/studies/202512_GenesysCX/ctm-token-calculator/tests/test_cost_model.py
index e6c5f2c..ebd2d83 100644
--- a/studies/202512_GenesysCX/ctm-token-calculator/tests/test_cost_model.py
+++ b/studies/202512_GenesysCX/ctm-token-calculator/tests/test_cost_model.py
@@ -108,6 +108,50 @@ def test_consumption_tokens_rounded_up_monthly():
assert df.iloc[0]["annual_cost"] == pytest.approx(3 * 12 * 1.0)
+def test_predictive_routing_consumption():
+ """1,700 calls/mo ÷ 17 per token = 100 tokens/mo → $1,200/yr (year 2, no ramp)."""
+ site = SiteInput(
+ "Tiny", "US", agents=5, supervisors=0,
+ voice_volume_monthly=1_700, email_volume_monthly=0,
+ chat_volume_monthly=0, sms_volume_monthly=0,
+ voice_aht_seconds=300, email_aht_seconds=600,
+ chat_aht_seconds=480, voice_acw_seconds=60,
+ fully_loaded_agent_cost_annual=65_000,
+ fully_loaded_supervisor_cost_annual=95_000,
+ )
+ df = calculate_consumption_ai_cost(
+ [site], FeatureScope("Predictive Routing", ["Tiny"]),
+ DEFAULT_METERS["Predictive Routing"], "realistic", DEFAULT_PRICING, year=2,
+ )
+ assert df.iloc[0]["tokens_monthly"] == 100
+ assert df.iloc[0]["annual_cost"] == pytest.approx(1_200)
+
+
+def test_predictive_routing_eligibility_and_total_cost():
+ """eligibility_pct halves the routed volume; total_cost handles the scope."""
+ site = SiteInput(
+ "Tiny", "US", agents=5, supervisors=0,
+ voice_volume_monthly=1_700, email_volume_monthly=0,
+ chat_volume_monthly=0, sms_volume_monthly=0,
+ voice_aht_seconds=300, email_aht_seconds=600,
+ chat_aht_seconds=480, voice_acw_seconds=60,
+ fully_loaded_agent_cost_annual=65_000,
+ fully_loaded_supervisor_cost_annual=95_000,
+ )
+ scope = FeatureScope("Predictive Routing", ["Tiny"], eligibility_pct=0.5)
+ df = calculate_consumption_ai_cost(
+ [site], scope, DEFAULT_METERS["Predictive Routing"], "realistic",
+ DEFAULT_PRICING, year=2,
+ )
+ assert df.iloc[0]["tokens_monthly"] == 50
+ total = calculate_total_cost(
+ [site], [scope], DEFAULT_METERS, DEFAULT_PRICING, "realistic", 2,
+ include_platform=False,
+ )
+ pr_row = total[total["cost_line"] == "Predictive Routing"].iloc[0]
+ assert pr_row["annual_cost"] == pytest.approx(50 * 12 * 1.0)
+
+
def test_regional_pricing_not_hardcoded():
pricing = dict(DEFAULT_PRICING)
from tokencalc.meters import TokenPricing
diff --git a/studies/202512_GenesysCX/ctm-token-calculator/tokencalc/cost_model.py b/studies/202512_GenesysCX/ctm-token-calculator/tokencalc/cost_model.py
index 12f9868..a24e1d0 100644
--- a/studies/202512_GenesysCX/ctm-token-calculator/tokencalc/cost_model.py
+++ b/studies/202512_GenesysCX/ctm-token-calculator/tokencalc/cost_model.py
@@ -170,6 +170,11 @@ def _monthly_units(site: SiteInput, feature: str, scope: FeatureScope,
# can be used to scope to a subset of interactions (e.g. non-English only).
eligibility = scope.eligibility_pct if scope.eligibility_pct is not None else 1.0
return site.voice_volume_monthly * eligibility # translations
+ if feature == "Predictive Routing":
+ # Every predictively-routed voice interaction consumes the PR meter;
+ # eligibility_pct scopes to the share of volume on PR-enabled queues.
+ eligibility = scope.eligibility_pct if scope.eligibility_pct is not None else 1.0
+ return site.voice_volume_monthly * eligibility # routed interactions
raise KeyError(f"No consumption-volume mapping for feature {feature!r}")