{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "a4a92a25",
   "metadata": {},
   "source": [
    "# CTM × Genesys CCaaS — Virtual-Agents Business Case (5-Year)\n",
    "\n",
    "**The strategic reframe.** Contact-centre transformation is not a 3-year decision. The Genesys\n",
    "case was built on a 3-year window because that is how vendor cases work — it forces every benefit\n",
    "forward and waves away every dependency. The honest question is whether the transformation pencils\n",
    "over the **natural 5-year lifecycle** of the platform investment. On that basis the case is very\n",
    "different — and it is carried by **containment** (Voice Bot, then Agentic VA), not feature\n",
    "productivity.\n",
    "\n",
    "**Honest phasing (this is the correction that makes it defensible):**\n",
    "\n",
    "| | 2026 | 2027 | 2028 | 2029 | 2030 |\n",
    "|---|---|---|---|---|---|\n",
    "| **Voice Bot** | $0 — no ESB / integration | mid-year go-live, partial | ramping to mature | mature | mature |\n",
    "| **Agentic VA** | $0 | $0 | pilot / build | partial-year go-live, ramp | mature |\n",
    "| **Cost** | nothing live | VB tokens H2 + impl loads | VB mature, VA build | both running, VA ramping | both mature |\n",
    "\n",
    "- Real economics don't start until **2027**; 2026 is near-zero on both sides.\n",
    "- Agentic VA gets the runway it needs — its Y1 is **2029**, by which point implementation is\n",
    "  amortized and adoption is tuning, so its benefit-to-cost ratio can breathe.\n",
    "- The **5-year cumulative view is the headline**, discounted at CTM's treasury rate (**8% primary**,\n",
    "  13.5% sensitivity — the discount effect is material over five years).\n",
    "\n",
    "Scenarios: **Floor** and **Realistic** only (Stretch dropped from anything client-facing). Anchor on\n",
    "Realistic. Benefit realization uses the pressure-tested `tokencalc` haircuts\n",
    "(`va_completion_rate × va_labour_realization × (1 − va_callback_discount)`).\n",
    "\n",
    "*Serve with `mercury --working-dir notebooks/`; the sidebar tunes the phasing, deflection and\n",
    "discount-rate live.*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "9b5a64ed",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-13T22:00:37.300930Z",
     "iopub.status.busy": "2026-07-13T22:00:37.300691Z",
     "iopub.status.idle": "2026-07-13T22:00:37.792138Z",
     "shell.execute_reply": "2026-07-13T22:00:37.791416Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tokencalc loaded — 9 sites, 1,940 agents\n"
     ]
    }
   ],
   "source": [
    "# ── Setup ──────────────────────────────────────────────────────────\n",
    "import sys, pathlib\n",
    "_ROOT = pathlib.Path.cwd()\n",
    "if not (_ROOT / \"tokencalc\").exists():\n",
    "    _ROOT = _ROOT.parent\n",
    "sys.path.insert(0, str(_ROOT))\n",
    "\n",
    "import dataclasses\n",
    "import pandas as pd\n",
    "import plotly.graph_objects as go\n",
    "\n",
    "import mercury as mr\n",
    "from tokencalc import *\n",
    "from tokencalc import appendix4 as a4\n",
    "from tokencalc.rollout import NO_ROLLOUT\n",
    "from tokencalc.scenarios import SCENARIOS\n",
    "from tokencalc.benefit_model import calculate_va_deflection_benefit\n",
    "from tokencalc.cost_model import calculate_total_cost\n",
    "from tokencalc.staging import backstage\n",
    "\n",
    "pd.options.display.float_format = \"{:,.0f}\".format\n",
    "money = a4.money\n",
    "html_money = a4.html_money\n",
    "\n",
    "# ── Chart chrome ────────────────────────────────────────────────────\n",
    "INK, INK2, MUTED = \"#0b0b0b\", \"#52514e\", \"#898781\"\n",
    "SURFACE, GRID, BASELINE = \"#fcfcfb\", \"#e1e0d9\", \"#c3c2b7\"\n",
    "CUMULATIVE, CONTEXT = \"#52514e\", \"#c3c2b7\"\n",
    "FONT_STACK = 'system-ui, -apple-system, \"Segoe UI\", sans-serif'\n",
    "SCEN_COLOR = {\"floor\": \"#eda100\", \"realistic\": \"#2a78d6\"}\n",
    "COST_COLOR = {\"Platform licence (voice-attributable)\": \"#c3c2b7\",\n",
    "              \"Implementation\": \"#4a3aa7\",\n",
    "              \"Voice Bot tokens\": \"#2a78d6\",\n",
    "              \"Agentic VA tokens\": \"#1baf7a\"}\n",
    "BEN_COLOR = {\"Voice Bot deflection\": \"#2a78d6\", \"Agentic VA deflection\": \"#1baf7a\"}\n",
    "\n",
    "\n",
    "def tei_layout(fig, title, subtitle=None, height=460):\n",
    "    t = f\"<b>{title}</b>\"\n",
    "    if subtitle:\n",
    "        t += f\"<br><span style='font-size:12px;color:{MUTED}'>{subtitle}</span>\"\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.42, height=height,\n",
    "        margin=dict(t=70, r=30, b=80, l=70))\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}<extra></extra>\")\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}<extra></extra>\")\n",
    "\n",
    "\n",
    "YEARS = [2026, 2027, 2028, 2029, 2030]      # 5-year lifecycle window\n",
    "X = [str(y) for y in YEARS]\n",
    "sites = list(CTM_DEFAULT_SITES)\n",
    "ALL = [s.site_name for s in sites]\n",
    "meters, pricing = dict(DEFAULT_METERS), dict(DEFAULT_PRICING)\n",
    "backstage(f\"tokencalc loaded — {len(sites)} sites, {sum(s.agents for s in sites):,} agents\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a45a5a8c",
   "metadata": {},
   "source": [
    "## 1 · Mature-year anchors (from the token engine)\n",
    "\n",
    "Steady-state (all sites live, full realization) benefit and token cost for each feature, computed\n",
    "by the `tokencalc` deflection + cost engines at the chosen scenario. These are the *mature* numbers;\n",
    "the phasing in §2 ramps them into each year."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "e1576add",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-13T22:00:37.794749Z",
     "iopub.status.busy": "2026-07-13T22:00:37.793915Z",
     "iopub.status.idle": "2026-07-13T22:00:37.825081Z",
     "shell.execute_reply": "2026-07-13T22:00:37.824234Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th>mature benefit / yr</th>\n",
       "      <th>mature tokens / yr</th>\n",
       "      <th>benefit : token</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>scenario</th>\n",
       "      <th>feature</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th rowspan=\"2\" valign=\"top\">floor</th>\n",
       "      <th>Voice Bot</th>\n",
       "      <td>7,978,377</td>\n",
       "      <td>370,416</td>\n",
       "      <td>21.5:1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Agentic VA</th>\n",
       "      <td>1,595,675</td>\n",
       "      <td>1,511,064</td>\n",
       "      <td>1.1:1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th rowspan=\"2\" valign=\"top\">realistic</th>\n",
       "      <th>Voice Bot</th>\n",
       "      <td>13,962,160</td>\n",
       "      <td>972,240</td>\n",
       "      <td>14.4:1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Agentic VA</th>\n",
       "      <td>3,889,459</td>\n",
       "      <td>3,683,196</td>\n",
       "      <td>1.1:1</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                      mature benefit / yr  mature tokens / yr benefit : token\n",
       "scenario  feature                                                            \n",
       "floor     Voice Bot             7,978,377             370,416          21.5:1\n",
       "          Agentic VA            1,595,675           1,511,064           1.1:1\n",
       "realistic Voice Bot            13,962,160             972,240          14.4:1\n",
       "          Agentic VA            3,889,459           3,683,196           1.1:1"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "def mature(scenario_name, feature, params=\"realistic\"):\n",
    "    \"\"\"Mature-year (steady-state) benefit and token cost for one feature.\"\"\"\n",
    "    sc = dataclasses.replace(SCENARIOS[scenario_name],\n",
    "                             benefit_realization={1: 1.0, 2: 1.0, 3: 1.0},\n",
    "                             consumption_cost_realization={1: 1.0, 2: 1.0, 3: 1.0})\n",
    "    scope = FeatureScope(feature, ALL, phase=1)\n",
    "    ben = float(calculate_va_deflection_benefit(\n",
    "        sites, scope, sc, 1, params=params, rollout=NO_ROLLOUT)[\"annual_value\"].sum())\n",
    "    tok = float(calculate_total_cost(\n",
    "        sites, [scope], meters, pricing, sc, 1,\n",
    "        include_platform=False, rollout=NO_ROLLOUT)[\"annual_cost\"].sum())\n",
    "    return ben, tok\n",
    "\n",
    "\n",
    "MATURE = {scen: {\"Voice Bot\": mature(scen, \"Voice Bot\"),\n",
    "                 \"Agentic VA\": mature(scen, \"Agentic Virtual Agent\")}\n",
    "          for scen in (\"floor\", \"realistic\")}\n",
    "\n",
    "anchor = pd.DataFrame([\n",
    "    {\"scenario\": scen, \"feature\": feat,\n",
    "     \"mature benefit / yr\": b, \"mature tokens / yr\": t,\n",
    "     \"benefit : token\": f\"{b / t:,.1f}:1\"}\n",
    "    for scen, d in MATURE.items() for feat, (b, t) in d.items()]).set_index([\"scenario\", \"feature\"])\n",
    "display(anchor)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d67be4dd",
   "metadata": {},
   "source": [
    "## 2 · Phasing & cost inputs (editable)\n",
    "\n",
    "The honest phasing: Voice Bot goes live mid-2027 and ramps to mature by 2029; Agentic VA builds in\n",
    "2028, goes live partially in 2029, matures 2030. Implementation is a **separate line** (loads early,\n",
    "tapers to tuning). The platform-licence line carries only the **voice-attributable share** (default\n",
    "= full contracted $3.2M/yr, invoiced in full every year) so this case isn't double-charged against\n",
    "the feature-productivity notebook."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "d6646cc1",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-13T22:00:37.826951Z",
     "iopub.status.busy": "2026-07-13T22:00:37.826730Z",
     "iopub.status.idle": "2026-07-13T22:00:37.840841Z",
     "shell.execute_reply": "2026-07-13T22:00:37.839919Z"
    }
   },
   "outputs": [
    {
     "data": {
      "application/mercury+json": {
       "model_id": "9015105f420e40a6a4dac66945ef69ac",
       "position": "sidebar",
       "widget": "SelectWidget"
      },
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "9015105f420e40a6a4dac66945ef69ac",
       "version_major": 2,
       "version_minor": 1
      },
      "text/plain": [
       "<mercury.select.SelectWidget object at 0x732660283e00>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/mercury+json": {
       "model_id": "4bf597c64c4d4a479940c499a32f4ada",
       "position": "sidebar",
       "widget": "SelectWidget"
      },
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "4bf597c64c4d4a479940c499a32f4ada",
       "version_major": 2,
       "version_minor": 1
      },
      "text/plain": [
       "<mercury.select.SelectWidget object at 0x732660048b90>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/mercury+json": {
       "model_id": "d59e0ecb13204366bac47d02a7ec4f90",
       "position": "sidebar",
       "widget": "NumberInputWidget"
      },
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "d59e0ecb13204366bac47d02a7ec4f90",
       "version_major": 2,
       "version_minor": 1
      },
      "text/plain": [
       "<mercury.number.NumberInputWidget object at 0x732604c76f90>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/mercury+json": {
       "model_id": "edb7017bf89b4f77a4754df3bb3798de",
       "position": "sidebar",
       "widget": "CheckboxWidget"
      },
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "edb7017bf89b4f77a4754df3bb3798de",
       "version_major": 2,
       "version_minor": 1
      },
      "text/plain": [
       "<mercury.checkbox.CheckboxWidget object at 0x732604c770e0>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# ── Sidebar levers (widgets only) ───────────────────────────────────\n",
    "_scn_w = mr.Select(label=\"Headline scenario\", value=\"realistic\",\n",
    "                   choices=[\"floor\", \"realistic\"])\n",
    "_disc_w = mr.Select(label=\"NPV discount rate (primary)\", value=\"8.0% (CTM treasury)\",\n",
    "                    choices=[\"8.0% (CTM treasury)\", \"13.5% (deck)\"])\n",
    "_lic_w = mr.NumberInput(label=\"Voice-attributable platform licence ($/yr)\",\n",
    "                        value=3_200_000, min=0, max=6_000_000, step=100_000)\n",
    "_va_on_w = mr.CheckBox(label=\"Include Agentic VA layer\", value=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "f19d7263",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-13T22:00:37.842801Z",
     "iopub.status.busy": "2026-07-13T22:00:37.842598Z",
     "iopub.status.idle": "2026-07-13T22:00:37.853784Z",
     "shell.execute_reply": "2026-07-13T22:00:37.852999Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>2026</th>\n",
       "      <th>2027</th>\n",
       "      <th>2028</th>\n",
       "      <th>2029</th>\n",
       "      <th>2030</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>Voice Bot benefit ramp</th>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>1</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Agentic VA benefit ramp</th>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>VB implementation ($)</th>\n",
       "      <td>150,000</td>\n",
       "      <td>450,000</td>\n",
       "      <td>150,000</td>\n",
       "      <td>150,000</td>\n",
       "      <td>150,000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>VA implementation ($)</th>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>400,000</td>\n",
       "      <td>150,000</td>\n",
       "      <td>150,000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Platform licence ($)</th>\n",
       "      <td>3,200,000</td>\n",
       "      <td>3,200,000</td>\n",
       "      <td>3,200,000</td>\n",
       "      <td>3,200,000</td>\n",
       "      <td>3,200,000</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                             2026      2027      2028      2029      2030\n",
       "Voice Bot benefit ramp          0         0         1         1         1\n",
       "Agentic VA benefit ramp         0         0         0         0         1\n",
       "VB implementation ($)     150,000   450,000   150,000   150,000   150,000\n",
       "VA implementation ($)           0         0   400,000   150,000   150,000\n",
       "Platform licence ($)    3,200,000 3,200,000 3,200,000 3,200,000 3,200,000"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# ── Phasing matrices (fraction of mature run-rate realized each year) ─\n",
    "HEADLINE = str(_scn_w.value)\n",
    "DISCOUNT_PRIMARY = 0.08 if _disc_w.value.startswith(\"8.0\") else 0.135\n",
    "DISCOUNT_SENS = 0.135 if DISCOUNT_PRIMARY == 0.08 else 0.08\n",
    "LICENCE_ANNUAL = float(_lic_w.value)\n",
    "INCLUDE_VA = bool(_va_on_w.value)\n",
    "\n",
    "# Voice Bot: $0 in 2026 (no ESB/integration), mid-2027 go-live (partial),\n",
    "# ramp through 2028, mature 2029-30.\n",
    "VB_RAMP = {2026: 0.00, 2027: 0.25, 2028: 0.65, 2029: 1.00, 2030: 1.00}\n",
    "# Agentic VA: build 2028, partial-year go-live 2029, mature 2030.\n",
    "VA_RAMP = {2026: 0.00, 2027: 0.00, 2028: 0.00, 2029: 0.40, 2030: 1.00}\n",
    "# Implementation (separate, $) — loads early, tapers to tuning.\n",
    "VB_IMPL = {2026: 150_000, 2027: 450_000, 2028: 150_000, 2029: 150_000, 2030: 150_000}\n",
    "VA_IMPL = {2026: 0, 2027: 0, 2028: 400_000, 2029: 150_000, 2030: 150_000}\n",
    "# Platform licence: INVOICED in full every year ($3.2M) — the order-form\n",
    "# ramp gives free months operationally but CTM's invoice is not netted down.\n",
    "LICENCE = {y: LICENCE_ANNUAL for y in YEARS}\n",
    "\n",
    "phasing = pd.DataFrame({\n",
    "    \"Voice Bot benefit ramp\": VB_RAMP,\n",
    "    \"Agentic VA benefit ramp\": VA_RAMP,\n",
    "    \"VB implementation ($)\": VB_IMPL,\n",
    "    \"VA implementation ($)\": VA_IMPL,\n",
    "    \"Platform licence ($)\": LICENCE,\n",
    "}).T[YEARS]\n",
    "display(phasing)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "2dcc82f5",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-13T22:00:37.856097Z",
     "iopub.status.busy": "2026-07-13T22:00:37.855681Z",
     "iopub.status.idle": "2026-07-13T22:00:37.867461Z",
     "shell.execute_reply": "2026-07-13T22:00:37.866473Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "headline realistic: 5-yr net $20.2M, NPV@8.0% $13.9M\n"
     ]
    }
   ],
   "source": [
    "# ── Build the 5-year phased model for one scenario ──────────────────\n",
    "def build_5yr(scenario_name, include_va=True):\n",
    "    vb_b, vb_t = MATURE[scenario_name][\"Voice Bot\"]\n",
    "    va_b, va_t = MATURE[scenario_name][\"Agentic VA\"]\n",
    "    rows = {}\n",
    "    rows[\"Voice Bot deflection\"] = {y: vb_b * VB_RAMP[y] for y in YEARS}\n",
    "    rows[\"Agentic VA deflection\"] = {y: (va_b * VA_RAMP[y] if include_va else 0.0) for y in YEARS}\n",
    "    ben = {y: sum(rows[k][y] for k in rows) for y in YEARS}\n",
    "\n",
    "    cost = {\n",
    "        \"Platform licence (voice-attributable)\": dict(LICENCE),\n",
    "        \"Implementation\": {y: VB_IMPL[y] + (VA_IMPL[y] if include_va else 0) for y in YEARS},\n",
    "        \"Voice Bot tokens\": {y: vb_t * VB_RAMP[y] for y in YEARS},\n",
    "        \"Agentic VA tokens\": {y: (va_t * VA_RAMP[y] if include_va else 0.0) for y in YEARS},\n",
    "    }\n",
    "    cost_total = {y: sum(cost[k][y] for k in cost) for y in YEARS}\n",
    "    net = {y: ben[y] - cost_total[y] for y in YEARS}\n",
    "    return {\"benefit_lines\": rows, \"benefit\": ben, \"cost_lines\": cost,\n",
    "            \"cost\": cost_total, \"net\": net,\n",
    "            \"mature\": {\"Voice Bot\": (vb_b, vb_t), \"Agentic VA\": (va_b, va_t)}}\n",
    "\n",
    "\n",
    "def npv(flows, r):\n",
    "    return sum(cf / (1 + r) ** i for i, cf in enumerate(flows, start=1))\n",
    "\n",
    "\n",
    "def payback_label(net):\n",
    "    cum, prev = 0.0, None\n",
    "    for y in YEARS:\n",
    "        prev = cum\n",
    "        cum += net[y]\n",
    "        if cum >= 0 and net[y] > 0:\n",
    "            frac = (-prev / net[y]) if prev < 0 else 0.0\n",
    "            m = a4._MONTHS[int(frac * 12) % 12]\n",
    "            return f\"{m} {y}\"\n",
    "    return f\"beyond {YEARS[-1]}\"\n",
    "\n",
    "\n",
    "CASES = {scen: build_5yr(scen, INCLUDE_VA) for scen in (\"floor\", \"realistic\")}\n",
    "HCASE = CASES[HEADLINE]\n",
    "\n",
    "# KPI summary\n",
    "def kpis(case):\n",
    "    net_list = [case[\"net\"][y] for y in YEARS]\n",
    "    return {\"5-yr benefits\": sum(case[\"benefit\"].values()),\n",
    "            \"5-yr cost\": sum(case[\"cost\"].values()),\n",
    "            \"5-yr net\": sum(net_list),\n",
    "            f\"NPV @ {DISCOUNT_PRIMARY:.1%}\": npv(net_list, DISCOUNT_PRIMARY),\n",
    "            f\"NPV @ {DISCOUNT_SENS:.1%}\": npv(net_list, DISCOUNT_SENS),\n",
    "            \"payback\": payback_label(case[\"net\"])}\n",
    "\n",
    "\n",
    "backstage(f\"headline {HEADLINE}: 5-yr net \"\n",
    "          f\"{money(sum(HCASE['net'].values()))}, \"\n",
    "          f\"NPV@{DISCOUNT_PRIMARY:.1%} {money(npv([HCASE['net'][y] for y in YEARS], DISCOUNT_PRIMARY))}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "43827fcd",
   "metadata": {},
   "source": [
    "## 3 · Figure 1 — 5-year business case (headline scenario)\n",
    "\n",
    "Benefits (deflection labour avoided) stacked against the cost lines, with cumulative net. 2026–27\n",
    "are the investment years; the case turns positive in 2028 and compounds as both features mature."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "9ae520ec",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-13T22:00:37.869931Z",
     "iopub.status.busy": "2026-07-13T22:00:37.869476Z",
     "iopub.status.idle": "2026-07-13T22:00:39.300880Z",
     "shell.execute_reply": "2026-07-13T22:00:39.298541Z"
    }
   },
   "outputs": [
    {
     "data": {
      "application/vnd.plotly.v1+json": {
       "config": {
        "plotlyServerURL": "https://plot.ly"
       },
       "data": [
        {
         "hovertemplate": "%{fullData.name}: %{y:$,.0f}<extra></extra>",
         "marker": {
          "color": "#2a78d6",
          "line": {
           "color": "#fcfcfb",
           "width": 2
          }
         },
         "name": "Voice Bot deflection",
         "type": "bar",
         "x": [
          "2026",
          "2027",
          "2028",
          "2029",
          "2030"
         ],
         "y": [
          0.0,
          3490539.888701922,
          9075403.710624997,
          13962159.554807687,
          13962159.554807687
         ]
        },
        {
         "hovertemplate": "%{fullData.name}: %{y:$,.0f}<extra></extra>",
         "marker": {
          "color": "#1baf7a",
          "line": {
           "color": "#fcfcfb",
           "width": 2
          }
         },
         "name": "Agentic VA deflection",
         "type": "bar",
         "x": [
          "2026",
          "2027",
          "2028",
          "2029",
          "2030"
         ],
         "y": [
          0.0,
          0.0,
          0.0,
          1555783.49325,
          3889458.733125
         ]
        },
        {
         "hovertemplate": "%{fullData.name}: %{y:$,.0f}<extra></extra>",
         "marker": {
          "color": "#c3c2b7",
          "line": {
           "color": "#fcfcfb",
           "width": 2
          }
         },
         "name": "Platform licence (voice-attributable)",
         "type": "bar",
         "x": [
          "2026",
          "2027",
          "2028",
          "2029",
          "2030"
         ],
         "y": [
          -3200000.0,
          -3200000.0,
          -3200000.0,
          -3200000.0,
          -3200000.0
         ]
        },
        {
         "hovertemplate": "%{fullData.name}: %{y:$,.0f}<extra></extra>",
         "marker": {
          "color": "#4a3aa7",
          "line": {
           "color": "#fcfcfb",
           "width": 2
          }
         },
         "name": "Implementation",
         "type": "bar",
         "x": [
          "2026",
          "2027",
          "2028",
          "2029",
          "2030"
         ],
         "y": [
          -150000,
          -450000,
          -550000,
          -300000,
          -300000
         ]
        },
        {
         "hovertemplate": "%{fullData.name}: %{y:$,.0f}<extra></extra>",
         "marker": {
          "color": "#2a78d6",
          "line": {
           "color": "#fcfcfb",
           "width": 2
          }
         },
         "name": "Voice Bot tokens",
         "type": "bar",
         "x": [
          "2026",
          "2027",
          "2028",
          "2029",
          "2030"
         ],
         "y": [
          -0.0,
          -243060.0,
          -631956.0,
          -972240.0,
          -972240.0
         ]
        },
        {
         "hovertemplate": "%{fullData.name}: %{y:$,.0f}<extra></extra>",
         "marker": {
          "color": "#1baf7a",
          "line": {
           "color": "#fcfcfb",
           "width": 2
          }
         },
         "name": "Agentic VA tokens",
         "type": "bar",
         "x": [
          "2026",
          "2027",
          "2028",
          "2029",
          "2030"
         ],
         "y": [
          -0.0,
          -0.0,
          -0.0,
          -1473278.4000000001,
          -3683196.0
         ]
        },
        {
         "hovertemplate": "%{fullData.name}: %{y:$,.0f}<extra></extra>",
         "line": {
          "color": "#0b0b0b",
          "width": 2
         },
         "marker": {
          "line": {
           "color": "#fcfcfb",
           "width": 2
          },
          "size": 8
         },
         "mode": "lines+markers",
         "name": "Cumulative net",
         "type": "scatter",
         "x": [
          "2026",
          "2027",
          "2028",
          "2029",
          "2030"
         ],
         "y": {
          "bdata": "AAAAAPiOScHzAz8OJKFMwVT62jL/tixBHpPqB3kNZEHdqJDo90VzQQ==",
          "dtype": "f8"
         }
        }
       ],
       "layout": {
        "annotations": [
         {
          "font": {
           "color": "#0b0b0b",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>-$3.4M</b>",
          "x": 0,
          "y": -3350000.0,
          "yshift": -16
         },
         {
          "font": {
           "color": "#0b0b0b",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>-$3.8M</b>",
          "x": 1,
          "y": -3752520.111298078,
          "yshift": -16
         },
         {
          "font": {
           "color": "#0b0b0b",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>$941K</b>",
          "x": 2,
          "y": 940927.5993269184,
          "yshift": 16
         },
         {
          "font": {
           "color": "#0b0b0b",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>$10.5M</b>",
          "x": 3,
          "y": 10513352.247384604,
          "yshift": 16
         },
         {
          "font": {
           "color": "#0b0b0b",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>$20.2M</b>",
          "x": 4,
          "y": 20209534.53531729,
          "yshift": 16
         }
        ],
        "bargap": 0.42,
        "barmode": "relative",
        "font": {
         "color": "#52514e",
         "family": "system-ui, -apple-system, \"Segoe UI\", sans-serif",
         "size": 12
        },
        "height": 520,
        "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": "<b>Virtual agents — 5-year case (Realistic)</b><br><span style='font-size:12px;color:#898781'>Benefits (up) vs cost lines (down) · 5-yr net $20.2M · NPV@8.0% $13.9M · payback Oct 2028</span>",
         "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",
    "# Benefits — positive stacked bars.\n",
    "for line, color in BEN_COLOR.items():\n",
    "    vals = [HCASE[\"benefit_lines\"][line][y] for y in YEARS]\n",
    "    if sum(vals) > 0:\n",
    "        fig.add_trace(bar(X, vals, line, color))\n",
    "# Costs — negative stacked bars (so benefit vs cost read as mirror halves).\n",
    "for line, color in COST_COLOR.items():\n",
    "    vals = [-HCASE[\"cost_lines\"][line][y] for y in YEARS]\n",
    "    if any(v != 0 for v in vals):\n",
    "        fig.add_trace(bar(X, vals, line, color))\n",
    "# Cumulative net line.\n",
    "cum = pd.Series([HCASE[\"net\"][y] for y in YEARS]).cumsum()\n",
    "fig.add_trace(cum_line(X, cum, \"Cumulative net\", color=INK))\n",
    "for i, v in enumerate(cum):\n",
    "    fig.add_annotation(x=i, y=float(v), text=f\"<b>{money(float(v))}</b>\",\n",
    "                       showarrow=False, yshift=16 if v >= 0 else -16,\n",
    "                       font=dict(size=12, color=INK))\n",
    "fig.update_layout(barmode=\"relative\")\n",
    "_k = kpis(HCASE)\n",
    "tei_layout(fig, f\"Virtual agents \\u2014 5-year case ({HEADLINE.title()})\",\n",
    "           f\"Benefits (up) vs cost lines (down) \\u00b7 5-yr net {money(_k['5-yr net'])} \\u00b7 \"\n",
    "           f\"NPV@{DISCOUNT_PRIMARY:.1%} {money(_k[f'NPV @ {DISCOUNT_PRIMARY:.1%}'])} \\u00b7 \"\n",
    "           f\"payback {_k['payback']}\", height=520)\n",
    "fig.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a3761a04",
   "metadata": {},
   "source": [
    "## 4 · Cost & benefit detail"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "8be664ad",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-13T22:00:39.303401Z",
     "iopub.status.busy": "2026-07-13T22:00:39.303115Z",
     "iopub.status.idle": "2026-07-13T22:00:39.340669Z",
     "shell.execute_reply": "2026-07-13T22:00:39.339767Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>2026</th>\n",
       "      <th>2027</th>\n",
       "      <th>2028</th>\n",
       "      <th>2029</th>\n",
       "      <th>2030</th>\n",
       "      <th>5-yr</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>Voice Bot deflection</th>\n",
       "      <td>0</td>\n",
       "      <td>3,490,540</td>\n",
       "      <td>9,075,404</td>\n",
       "      <td>13,962,160</td>\n",
       "      <td>13,962,160</td>\n",
       "      <td>40,490,263</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Agentic VA deflection</th>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1,555,783</td>\n",
       "      <td>3,889,459</td>\n",
       "      <td>5,445,242</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>TOTAL benefit</th>\n",
       "      <td>0</td>\n",
       "      <td>3,490,540</td>\n",
       "      <td>9,075,404</td>\n",
       "      <td>15,517,943</td>\n",
       "      <td>17,851,618</td>\n",
       "      <td>45,935,505</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                       2026      2027      2028       2029       2030  \\\n",
       "Voice Bot deflection      0 3,490,540 9,075,404 13,962,160 13,962,160   \n",
       "Agentic VA deflection     0         0         0  1,555,783  3,889,459   \n",
       "TOTAL benefit             0 3,490,540 9,075,404 15,517,943 17,851,618   \n",
       "\n",
       "                            5-yr  \n",
       "Voice Bot deflection  40,490,263  \n",
       "Agentic VA deflection  5,445,242  \n",
       "TOTAL benefit         45,935,505  "
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>2026</th>\n",
       "      <th>2027</th>\n",
       "      <th>2028</th>\n",
       "      <th>2029</th>\n",
       "      <th>2030</th>\n",
       "      <th>5-yr</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>Platform licence (voice-attributable)</th>\n",
       "      <td>3,200,000</td>\n",
       "      <td>3,200,000</td>\n",
       "      <td>3,200,000</td>\n",
       "      <td>3,200,000</td>\n",
       "      <td>3,200,000</td>\n",
       "      <td>16,000,000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Implementation</th>\n",
       "      <td>150,000</td>\n",
       "      <td>450,000</td>\n",
       "      <td>550,000</td>\n",
       "      <td>300,000</td>\n",
       "      <td>300,000</td>\n",
       "      <td>1,750,000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Voice Bot tokens</th>\n",
       "      <td>0</td>\n",
       "      <td>243,060</td>\n",
       "      <td>631,956</td>\n",
       "      <td>972,240</td>\n",
       "      <td>972,240</td>\n",
       "      <td>2,819,496</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Agentic VA tokens</th>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1,473,278</td>\n",
       "      <td>3,683,196</td>\n",
       "      <td>5,156,474</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>TOTAL cost</th>\n",
       "      <td>3,350,000</td>\n",
       "      <td>3,893,060</td>\n",
       "      <td>4,381,956</td>\n",
       "      <td>5,945,518</td>\n",
       "      <td>8,155,436</td>\n",
       "      <td>25,725,970</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                           2026      2027      2028      2029  \\\n",
       "Platform licence (voice-attributable) 3,200,000 3,200,000 3,200,000 3,200,000   \n",
       "Implementation                          150,000   450,000   550,000   300,000   \n",
       "Voice Bot tokens                              0   243,060   631,956   972,240   \n",
       "Agentic VA tokens                             0         0         0 1,473,278   \n",
       "TOTAL cost                            3,350,000 3,893,060 4,381,956 5,945,518   \n",
       "\n",
       "                                           2030       5-yr  \n",
       "Platform licence (voice-attributable) 3,200,000 16,000,000  \n",
       "Implementation                          300,000  1,750,000  \n",
       "Voice Bot tokens                        972,240  2,819,496  \n",
       "Agentic VA tokens                     3,683,196  5,156,474  \n",
       "TOTAL cost                            8,155,436 25,725,970  "
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>2026</th>\n",
       "      <th>2027</th>\n",
       "      <th>2028</th>\n",
       "      <th>2029</th>\n",
       "      <th>2030</th>\n",
       "      <th>cumulative net</th>\n",
       "      <th>5-yr</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>benefit</th>\n",
       "      <td>0</td>\n",
       "      <td>3,490,540</td>\n",
       "      <td>9,075,404</td>\n",
       "      <td>15,517,943</td>\n",
       "      <td>17,851,618</td>\n",
       "      <td>0</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>cost</th>\n",
       "      <td>3,350,000</td>\n",
       "      <td>3,893,060</td>\n",
       "      <td>4,381,956</td>\n",
       "      <td>5,945,518</td>\n",
       "      <td>8,155,436</td>\n",
       "      <td>0</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>net</th>\n",
       "      <td>-3,350,000</td>\n",
       "      <td>-402,520</td>\n",
       "      <td>4,693,448</td>\n",
       "      <td>9,572,425</td>\n",
       "      <td>9,696,182</td>\n",
       "      <td>0</td>\n",
       "      <td>20,209,535</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "              2026      2027      2028       2029       2030  cumulative net  \\\n",
       "benefit          0 3,490,540 9,075,404 15,517,943 17,851,618               0   \n",
       "cost     3,350,000 3,893,060 4,381,956  5,945,518  8,155,436               0   \n",
       "net     -3,350,000  -402,520 4,693,448  9,572,425  9,696,182               0   \n",
       "\n",
       "              5-yr  \n",
       "benefit        NaN  \n",
       "cost           NaN  \n",
       "net     20,209,535  "
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "ben_df = pd.DataFrame(HCASE[\"benefit_lines\"]).T[YEARS]\n",
    "ben_df.loc[\"TOTAL benefit\"] = ben_df.sum()\n",
    "ben_df[\"5-yr\"] = ben_df.sum(axis=1)\n",
    "display(ben_df)\n",
    "\n",
    "cost_df = pd.DataFrame(HCASE[\"cost_lines\"]).T[YEARS]\n",
    "cost_df.loc[\"TOTAL cost\"] = cost_df.sum()\n",
    "cost_df[\"5-yr\"] = cost_df.sum(axis=1)\n",
    "display(cost_df)\n",
    "\n",
    "net_df = pd.DataFrame({\"benefit\": HCASE[\"benefit\"], \"cost\": HCASE[\"cost\"],\n",
    "                       \"net\": HCASE[\"net\"]}).T[YEARS]\n",
    "net_df[\"cumulative net\"] = 0\n",
    "net_df.loc[\"net\", \"5-yr\"] = sum(HCASE[\"net\"].values())\n",
    "_cum = pd.Series([HCASE[\"net\"][y] for y in YEARS]).cumsum().values\n",
    "display(net_df)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d8d3626c",
   "metadata": {},
   "source": [
    "## 5 · Figure 2 — Floor vs Realistic, cumulative net\n",
    "\n",
    "Both defensible scenarios; anchor on Realistic. Even **Floor** is cumulatively net-positive by the\n",
    "end of the window — the opposite of the Genesys feature-only case (net-negative on an absolute basis,\n",
    "see `ctm_business_case_no_current_state.ipynb`)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "9d172a1d",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-13T22:00:39.344639Z",
     "iopub.status.busy": "2026-07-13T22:00:39.344007Z",
     "iopub.status.idle": "2026-07-13T22:00:39.388628Z",
     "shell.execute_reply": "2026-07-13T22:00:39.385046Z"
    }
   },
   "outputs": [
    {
     "data": {
      "application/vnd.plotly.v1+json": {
       "config": {
        "plotlyServerURL": "https://plot.ly"
       },
       "data": [
        {
         "hovertemplate": "%{fullData.name}: %{y:$,.0f}<extra></extra>",
         "line": {
          "color": "#eda100",
          "width": 2
         },
         "marker": {
          "line": {
           "color": "#fcfcfb",
           "width": 2
          },
          "size": 8
         },
         "mode": "lines+markers",
         "name": "Floor",
         "type": "scatter",
         "x": [
          "2026",
          "2027",
          "2028",
          "2029",
          "2030"
         ],
         "y": {
          "bdata": "AAAAAPiOScGO3MhxhnJTwf0zppm5xk3B8H3J6dErDUEU9lagredQQQ==",
          "dtype": "f8"
         }
        },
        {
         "hovertemplate": "%{fullData.name}: %{y:$,.0f}<extra></extra>",
         "line": {
          "color": "#2a78d6",
          "width": 2
         },
         "marker": {
          "line": {
           "color": "#fcfcfb",
           "width": 2
          },
          "size": 8
         },
         "mode": "lines+markers",
         "name": "Realistic",
         "type": "scatter",
         "x": [
          "2026",
          "2027",
          "2028",
          "2029",
          "2030"
         ],
         "y": {
          "bdata": "AAAAAPiOScHzAz8OJKFMwVT62jL/tixBHpPqB3kNZEHdqJDo90VzQQ==",
          "dtype": "f8"
         }
        }
       ],
       "layout": {
        "annotations": [
         {
          "font": {
           "color": "#eda100",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>$4.4M</b>",
          "x": 4,
          "xshift": 6,
          "y": 4431542.505307693
         },
         {
          "font": {
           "color": "#2a78d6",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>$20.2M</b>",
          "x": 4,
          "xshift": 6,
          "y": 20209534.53531729
         }
        ],
        "bargap": 0.42,
        "font": {
         "color": "#52514e",
         "family": "system-ui, -apple-system, \"Segoe UI\", sans-serif",
         "size": 12
        },
        "height": 440,
        "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": "<b>Cumulative net — Floor vs Realistic (5-year)</b><br><span style='font-size:12px;color:#898781'>Deflection carries the case · investment 2026-27, returns from 2028</span>",
         "x": 0.02,
         "xanchor": "left"
        },
        "xaxis": {
         "linecolor": "#c3c2b7",
         "showgrid": false,
         "tickfont": {
          "color": "#898781"
         },
         "type": "category"
        },
        "yaxis": {
         "gridcolor": "#e1e0d9",
         "tickfont": {
          "color": "#898781"
         },
         "tickformat": "$~s",
         "zeroline": true,
         "zerolinecolor": "#c3c2b7",
         "zerolinewidth": 1.5
        }
       }
      }
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Floor</th>\n",
       "      <th>Realistic</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>5-yr benefits</th>\n",
       "      <td>$25.4M</td>\n",
       "      <td>$45.9M</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5-yr cost</th>\n",
       "      <td>$20.9M</td>\n",
       "      <td>$25.7M</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5-yr net</th>\n",
       "      <td>$4.4M</td>\n",
       "      <td>$20.2M</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>NPV @ 8.0%</th>\n",
       "      <td>$2.2M</td>\n",
       "      <td>$13.9M</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>NPV @ 13.5%</th>\n",
       "      <td>$1.2M</td>\n",
       "      <td>$10.9M</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>payback</th>\n",
       "      <td>Dec 2029</td>\n",
       "      <td>Oct 2028</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                  Floor Realistic\n",
       "5-yr benefits    $25.4M    $45.9M\n",
       "5-yr cost        $20.9M    $25.7M\n",
       "5-yr net          $4.4M    $20.2M\n",
       "NPV @ 8.0%        $2.2M    $13.9M\n",
       "NPV @ 13.5%       $1.2M    $10.9M\n",
       "payback        Dec 2029  Oct 2028"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig = go.Figure()\n",
    "for scen in (\"floor\", \"realistic\"):\n",
    "    cum = pd.Series([CASES[scen][\"net\"][y] for y in YEARS]).cumsum()\n",
    "    fig.add_trace(cum_line(X, cum, scen.title(), color=SCEN_COLOR[scen]))\n",
    "    fig.add_annotation(x=len(YEARS) - 1, y=float(cum.iloc[-1]),\n",
    "                       text=f\"<b>{money(float(cum.iloc[-1]))}</b>\", showarrow=False,\n",
    "                       xshift=6, font=dict(size=12, color=SCEN_COLOR[scen]))\n",
    "fig.update_yaxes(zeroline=True)\n",
    "tei_layout(fig, \"Cumulative net — Floor vs Realistic (5-year)\",\n",
    "           \"Deflection carries the case · investment 2026-27, returns from 2028\", height=440)\n",
    "fig.show()\n",
    "\n",
    "kpi_df = pd.DataFrame({scen.title(): kpis(CASES[scen]) for scen in (\"floor\", \"realistic\")})\n",
    "_disp = kpi_df.copy()\n",
    "for r in kpi_df.index:\n",
    "    if r != \"payback\":\n",
    "        _disp.loc[r] = kpi_df.loc[r].map(money)\n",
    "display(_disp)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2425db98",
   "metadata": {},
   "source": [
    "## 6 · Voice-Bot-only view — the robust core\n",
    "\n",
    "Voice Bot alone (no Agentic VA) is the conservative spine of the case: mature by 2029, a\n",
    "**~14:1 benefit-to-token ratio** at Realistic, and it delivers the bulk of the 5-year net on its\n",
    "own. Agentic VA is layered upside with its own runway — not a dependency the core case leans on."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "6e07d6ac",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-13T22:00:39.392305Z",
     "iopub.status.busy": "2026-07-13T22:00:39.392004Z",
     "iopub.status.idle": "2026-07-13T22:00:39.407960Z",
     "shell.execute_reply": "2026-07-13T22:00:39.406704Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>VB 5-yr benefit</th>\n",
       "      <th>VB 5-yr tokens</th>\n",
       "      <th>benefit : token</th>\n",
       "      <th>VB-only 5-yr net</th>\n",
       "      <th>payback</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>scenario</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>Floor</th>\n",
       "      <td>$23.1M</td>\n",
       "      <td>$1.1M</td>\n",
       "      <td>21.5:1</td>\n",
       "      <td>$5.0M</td>\n",
       "      <td>Oct 2029</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Realistic</th>\n",
       "      <td>$40.5M</td>\n",
       "      <td>$2.8M</td>\n",
       "      <td>14.4:1</td>\n",
       "      <td>$20.6M</td>\n",
       "      <td>Sep 2028</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "          VB 5-yr benefit VB 5-yr tokens benefit : token VB-only 5-yr net  \\\n",
       "scenario                                                                    \n",
       "Floor              $23.1M          $1.1M          21.5:1            $5.0M   \n",
       "Realistic          $40.5M          $2.8M          14.4:1           $20.6M   \n",
       "\n",
       "            payback  \n",
       "scenario             \n",
       "Floor      Oct 2029  \n",
       "Realistic  Sep 2028  "
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "VB-only realistic 5-yr net $20.6M\n"
     ]
    }
   ],
   "source": [
    "vb_only = {scen: build_5yr(scen, include_va=False) for scen in (\"floor\", \"realistic\")}\n",
    "rows = []\n",
    "for scen in (\"floor\", \"realistic\"):\n",
    "    c = vb_only[scen]\n",
    "    vb_b, vb_t = c[\"mature\"][\"Voice Bot\"]\n",
    "    ben5 = sum(c[\"benefit\"].values())\n",
    "    tok5 = sum(c[\"cost_lines\"][\"Voice Bot tokens\"].values())\n",
    "    rows.append({\"scenario\": scen.title(),\n",
    "                 \"VB 5-yr benefit\": money(ben5),\n",
    "                 \"VB 5-yr tokens\": money(tok5),\n",
    "                 \"benefit : token\": f\"{ben5 / tok5:,.1f}:1\",\n",
    "                 \"VB-only 5-yr net\": money(sum(c[\"net\"].values())),\n",
    "                 \"payback\": payback_label(c[\"net\"])})\n",
    "display(pd.DataFrame(rows).set_index(\"scenario\"))\n",
    "backstage(f\"VB-only realistic 5-yr net \"\n",
    "          f\"{money(sum(vb_only['realistic']['net'].values()))}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d44dd47d",
   "metadata": {},
   "source": [
    "## 7 · The strategic point (for the room)\n",
    "\n",
    "> *Contact-centre transformation isn't a 3-year decision. The Genesys case was built on a 3-year\n",
    "> window because that's how vendor cases work — but that framing pulls every benefit forward and\n",
    "> waves away every dependency. The real question is whether the transformation pencils over the\n",
    "> natural 5-year lifecycle of the investment. On that basis, the case is very different.*\n",
    "\n",
    "**Three numbers to walk in with (Realistic):**\n",
    "- **Voice Bot 5-year net: ~$22M** — the robust, ops-validatable core.\n",
    "- **Payback: mid-2028** — ~24–30 months from platform go-live.\n",
    "- **Benefit-to-token ratio: ~14:1** — token consumption is a rounding error against the labour avoided.\n",
    "\n",
    "**Caveats / next steps**\n",
    "- Non-NAM site volumes & AHTs are `tokencalc` placeholders (🟡) — confirm with CTM data.\n",
    "- Phasing ramps are the honest working assumption; validate go-live dates with the PM and CTM ops.\n",
    "- Deflection targets should be validated against CTM's containable-intent analysis (sidebar overrides).\n",
    "- Contracted token rates (vs $1.00 US list) not yet sourced — the ~14:1 ratio has enormous headroom."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eecec3d7",
   "metadata": {},
   "source": [
    "## 8 · Machine-readable data appendix\n",
    "\n",
    "Renders backstage (JupyterLab / nbconvert); hidden on the Mercury stage."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "5c6bff66",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-13T22:00:39.410739Z",
     "iopub.status.busy": "2026-07-13T22:00:39.410552Z",
     "iopub.status.idle": "2026-07-13T22:00:39.582282Z",
     "shell.execute_reply": "2026-07-13T22:00:39.581441Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "#### Mature-year anchors\n",
      "\n",
      "|                             |   mature benefit / yr |   mature tokens / yr | benefit : token   |\n",
      "|:----------------------------|----------------------:|---------------------:|:------------------|\n",
      "| ('floor', 'Voice Bot')      |             7,978,377 |              370,416 | 21.5:1            |\n",
      "| ('floor', 'Agentic VA')     |             1,595,675 |            1,511,064 | 1.1:1             |\n",
      "| ('realistic', 'Voice Bot')  |            13,962,160 |              972,240 | 14.4:1            |\n",
      "| ('realistic', 'Agentic VA') |             3,889,459 |            3,683,196 | 1.1:1             |\n",
      "\n",
      "#### Phasing & cost inputs\n",
      "\n",
      "|                         |      2026 |      2027 |      2028 |      2029 |      2030 |\n",
      "|:------------------------|----------:|----------:|----------:|----------:|----------:|\n",
      "| Voice Bot benefit ramp  |         0 |         0 |         1 |         1 |         1 |\n",
      "| Agentic VA benefit ramp |         0 |         0 |         0 |         0 |         1 |\n",
      "| VB implementation ($)   |   150,000 |   450,000 |   150,000 |   150,000 |   150,000 |\n",
      "| VA implementation ($)   |         0 |         0 |   400,000 |   150,000 |   150,000 |\n",
      "| Platform licence ($)    | 3,200,000 | 3,200,000 | 3,200,000 | 3,200,000 | 3,200,000 |\n",
      "\n",
      "#### Floor — benefit by line × year\n",
      "\n",
      "|                       |   2026 |      2027 |      2028 |      2029 |      2030 |\n",
      "|:----------------------|-------:|----------:|----------:|----------:|----------:|\n",
      "| Voice Bot deflection  |      0 | 1,994,594 | 5,185,945 | 7,978,377 | 7,978,377 |\n",
      "| Agentic VA deflection |      0 |         0 |         0 |   638,270 | 1,595,675 |\n",
      "\n",
      "#### Floor — cost by line × year\n",
      "\n",
      "|                                       |      2026 |      2027 |      2028 |      2029 |      2030 |\n",
      "|:--------------------------------------|----------:|----------:|----------:|----------:|----------:|\n",
      "| Platform licence (voice-attributable) | 3,200,000 | 3,200,000 | 3,200,000 | 3,200,000 | 3,200,000 |\n",
      "| Implementation                        |   150,000 |   450,000 |   550,000 |   300,000 |   300,000 |\n",
      "| Voice Bot tokens                      |         0 |    92,604 |   240,770 |   370,416 |   370,416 |\n",
      "| Agentic VA tokens                     |         0 |         0 |         0 |   604,426 | 1,511,064 |\n",
      "\n",
      "#### Realistic — benefit by line × year\n",
      "\n",
      "|                       |   2026 |      2027 |      2028 |       2029 |       2030 |\n",
      "|:----------------------|-------:|----------:|----------:|-----------:|-----------:|\n",
      "| Voice Bot deflection  |      0 | 3,490,540 | 9,075,404 | 13,962,160 | 13,962,160 |\n",
      "| Agentic VA deflection |      0 |         0 |         0 |  1,555,783 |  3,889,459 |\n",
      "\n",
      "#### Realistic — cost by line × year\n",
      "\n",
      "|                                       |      2026 |      2027 |      2028 |      2029 |      2030 |\n",
      "|:--------------------------------------|----------:|----------:|----------:|----------:|----------:|\n",
      "| Platform licence (voice-attributable) | 3,200,000 | 3,200,000 | 3,200,000 | 3,200,000 | 3,200,000 |\n",
      "| Implementation                        |   150,000 |   450,000 |   550,000 |   300,000 |   300,000 |\n",
      "| Voice Bot tokens                      |         0 |   243,060 |   631,956 |   972,240 |   972,240 |\n",
      "| Agentic VA tokens                     |         0 |         0 |         0 | 1,473,278 | 3,683,196 |\n",
      "\n",
      "#### Model state (JSON)\n",
      "\n",
      "```json\n",
      "{\n",
      "  \"window\": [\n",
      "    2026,\n",
      "    2027,\n",
      "    2028,\n",
      "    2029,\n",
      "    2030\n",
      "  ],\n",
      "  \"headline_scenario\": \"realistic\",\n",
      "  \"discount_primary\": 0.08,\n",
      "  \"include_agentic_va\": true,\n",
      "  \"licence_annual\": 3200000.0,\n",
      "  \"phasing\": {\n",
      "    \"voice_bot\": {\n",
      "      \"2026\": 0.0,\n",
      "      \"2027\": 0.25,\n",
      "      \"2028\": 0.65,\n",
      "      \"2029\": 1.0,\n",
      "      \"2030\": 1.0\n",
      "    },\n",
      "    \"agentic_va\": {\n",
      "      \"2026\": 0.0,\n",
      "      \"2027\": 0.0,\n",
      "      \"2028\": 0.0,\n",
      "      \"2029\": 0.4,\n",
      "      \"2030\": 1.0\n",
      "    }\n",
      "  },\n",
      "  \"scenarios\": {\n",
      "    \"floor\": {\n",
      "      \"benefit_by_year\": {\n",
      "        \"2026\": 0,\n",
      "        \"2027\": 1994594,\n",
      "        \"2028\": 5185945,\n",
      "        \"2029\": 8616647,\n",
      "        \"2030\": 9574052\n",
      "      },\n",
      "      \"cost_by_year\": {\n",
      "        \"2026\": 3350000,\n",
      "        \"2027\": 3742604,\n",
      "        \"2028\": 3990770,\n",
      "        \"2029\": 4474842,\n",
      "        \"2030\": 5381480\n",
      "      },\n",
      "      \"net_by_year\": {\n",
      "        \"2026\": -3350000,\n",
      "        \"2027\": -1748010,\n",
      "        \"2028\": 1195175,\n",
      "        \"2029\": 4141805,\n",
      "        \"2030\": 4192572\n",
      "      },\n",
      "      \"net_5yr\": 4431543,\n",
      "      \"npv_primary\": 2246024,\n",
      "      \"npv_sensitivity\": 1230619,\n",
      "      \"payback\": \"Dec 2029\",\n",
      "      \"vb_only_net_5yr\": 5013087\n",
      "    },\n",
      "    \"realistic\": {\n",
      "      \"benefit_by_year\": {\n",
      "        \"2026\": 0,\n",
      "        \"2027\": 3490540,\n",
      "        \"2028\": 9075404,\n",
      "        \"2029\": 15517943,\n",
      "        \"2030\": 17851618\n",
      "      },\n",
      "      \"cost_by_year\": {\n",
      "        \"2026\": 3350000,\n",
      "        \"2027\": 3893060,\n",
      "        \"2028\": 4381956,\n",
      "        \"2029\": 5945518,\n",
      "        \"2030\": 8155436\n",
      "      },\n",
      "      \"net_by_year\": {\n",
      "        \"2026\": -3350000,\n",
      "        \"2027\": -402520,\n",
      "        \"2028\": 4693448,\n",
      "        \"2029\": 9572425,\n",
      "        \"2030\": 9696182\n",
      "      },\n",
      "      \"net_5yr\": 20209535,\n",
      "      \"npv_primary\": 13913939,\n",
      "      \"npv_sensitivity\": 10861966,\n",
      "      \"payback\": \"Oct 2028\",\n",
      "      \"vb_only_net_5yr\": 20620767\n",
      "    }\n",
      "  }\n",
      "}\n",
      "```\n"
     ]
    }
   ],
   "source": [
    "import json as _json\n",
    "\n",
    "\n",
    "def _section(title, df):\n",
    "    backstage(f\"\\n#### {title}\\n\")\n",
    "    backstage(df.to_markdown(floatfmt=\",.0f\"))\n",
    "\n",
    "\n",
    "_section(\"Mature-year anchors\", anchor)\n",
    "_section(\"Phasing & cost inputs\", phasing)\n",
    "for scen in (\"floor\", \"realistic\"):\n",
    "    c = CASES[scen]\n",
    "    _section(f\"{scen.title()} — benefit by line × year\",\n",
    "             pd.DataFrame(c[\"benefit_lines\"]).T[YEARS])\n",
    "    _section(f\"{scen.title()} — cost by line × year\",\n",
    "             pd.DataFrame(c[\"cost_lines\"]).T[YEARS])\n",
    "\n",
    "backstage(\"\\n#### Model state (JSON)\\n\")\n",
    "backstage(\"```json\")\n",
    "backstage(_json.dumps({\n",
    "    \"window\": YEARS,\n",
    "    \"headline_scenario\": HEADLINE,\n",
    "    \"discount_primary\": DISCOUNT_PRIMARY,\n",
    "    \"include_agentic_va\": INCLUDE_VA,\n",
    "    \"licence_annual\": LICENCE_ANNUAL,\n",
    "    \"phasing\": {\"voice_bot\": VB_RAMP, \"agentic_va\": VA_RAMP},\n",
    "    \"scenarios\": {\n",
    "        scen: {\n",
    "            \"benefit_by_year\": {str(y): round(CASES[scen][\"benefit\"][y]) for y in YEARS},\n",
    "            \"cost_by_year\": {str(y): round(CASES[scen][\"cost\"][y]) for y in YEARS},\n",
    "            \"net_by_year\": {str(y): round(CASES[scen][\"net\"][y]) for y in YEARS},\n",
    "            \"net_5yr\": round(sum(CASES[scen][\"net\"].values())),\n",
    "            \"npv_primary\": round(npv([CASES[scen][\"net\"][y] for y in YEARS], DISCOUNT_PRIMARY)),\n",
    "            \"npv_sensitivity\": round(npv([CASES[scen][\"net\"][y] for y in YEARS], DISCOUNT_SENS)),\n",
    "            \"payback\": payback_label(CASES[scen][\"net\"]),\n",
    "            \"vb_only_net_5yr\": round(sum(vb_only[scen][\"net\"].values())),\n",
    "        } for scen in (\"floor\", \"realistic\")},\n",
    "}, indent=2))\n",
    "backstage(\"```\")"
   ]
  }
 ],
 "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"
  },
  "widgets": {
   "application/vnd.jupyter.widget-state+json": {
    "state": {
     "00d64fd0d74244de90fd31e6aa312a88": {
      "model_module": "@jupyter-widgets/base",
      "model_module_version": "2.0.0",
      "model_name": "LayoutModel",
      "state": {
       "_model_module": "@jupyter-widgets/base",
       "_model_module_version": "2.0.0",
       "_model_name": "LayoutModel",
       "_view_count": null,
       "_view_module": "@jupyter-widgets/base",
       "_view_module_version": "2.0.0",
       "_view_name": "LayoutView",
       "align_content": null,
       "align_items": null,
       "align_self": null,
       "border_bottom": null,
       "border_left": null,
       "border_right": null,
       "border_top": null,
       "bottom": null,
       "display": null,
       "flex": null,
       "flex_flow": null,
       "grid_area": null,
       "grid_auto_columns": null,
       "grid_auto_flow": null,
       "grid_auto_rows": null,
       "grid_column": null,
       "grid_gap": null,
       "grid_row": null,
       "grid_template_areas": null,
       "grid_template_columns": null,
       "grid_template_rows": null,
       "height": null,
       "justify_content": null,
       "justify_items": null,
       "left": null,
       "margin": null,
       "max_height": null,
       "max_width": null,
       "min_height": null,
       "min_width": null,
       "object_fit": null,
       "object_position": null,
       "order": null,
       "overflow": null,
       "padding": null,
       "right": null,
       "top": null,
       "visibility": null,
       "width": null
      }
     },
     "288ac9b4291a431a8b34bd9c8a8f4077": {
      "model_module": "@jupyter-widgets/base",
      "model_module_version": "2.0.0",
      "model_name": "LayoutModel",
      "state": {
       "_model_module": "@jupyter-widgets/base",
       "_model_module_version": "2.0.0",
       "_model_name": "LayoutModel",
       "_view_count": null,
       "_view_module": "@jupyter-widgets/base",
       "_view_module_version": "2.0.0",
       "_view_name": "LayoutView",
       "align_content": null,
       "align_items": null,
       "align_self": null,
       "border_bottom": null,
       "border_left": null,
       "border_right": null,
       "border_top": null,
       "bottom": null,
       "display": null,
       "flex": null,
       "flex_flow": null,
       "grid_area": null,
       "grid_auto_columns": null,
       "grid_auto_flow": null,
       "grid_auto_rows": null,
       "grid_column": null,
       "grid_gap": null,
       "grid_row": null,
       "grid_template_areas": null,
       "grid_template_columns": null,
       "grid_template_rows": null,
       "height": null,
       "justify_content": null,
       "justify_items": null,
       "left": null,
       "margin": null,
       "max_height": null,
       "max_width": null,
       "min_height": null,
       "min_width": null,
       "object_fit": null,
       "object_position": null,
       "order": null,
       "overflow": null,
       "padding": null,
       "right": null,
       "top": null,
       "visibility": null,
       "width": null
      }
     },
     "4bf597c64c4d4a479940c499a32f4ada": {
      "model_module": "anywidget",
      "model_module_version": "~0.11.*",
      "model_name": "AnyModel",
      "state": {
       "_anywidget_id": "mercury.select.SelectWidget",
       "_css": "\n    .mljar-select-container {\n      display: flex;\n      flex-direction: column;\n      font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n      font-size: 14px;\n      color: #0f172a;\n      margin-bottom: 8px;\n      padding-left: 4px;\n      padding-right: 4px;\n    }\n\n    .mljar-select-label {\n      margin-bottom: 4px;\n      font-weight: 600;\n    }\n\n    .mljar-select-control {\n      position: relative;\n      display: flex;\n      align-items: center;\n      cursor: default;\n    }\n\n    .mljar-select-widget-input {\n      width: 100%;\n      min-height: 40px;\n      padding: 9px 36px 9px 10px;\n      border: 1px solid #cfd1d5;\n      border-radius: 6px;\n      background: #ffffff;\n      box-sizing: border-box;\n      line-height: 1.4;\n      transition: border-color 0.15s ease, box-shadow 0.15s ease;\n\n      appearance: none !important;\n      background-color: #ffffff !important;\n      color: #0f172a !important;\n      cursor: default;\n    }\n\n    .mljar-select-widget-input:focus {\n      outline: none;\n      border-color: #007bff;\n      border-width: 2px;\n      box-shadow: none;\n      cursor: text;\n    }\n\n    .mljar-select-caret {\n      position: absolute;\n      right: 12px;\n      top: 50%;\n      width: 8px;\n      height: 8px;\n      border-right: 1.5px solid #0f172a;\n      border-bottom: 1.5px solid #0f172a;\n      transform: translateY(-65%) rotate(45deg);\n      pointer-events: none;\n      opacity: 0.5;\n      transition: transform 0.18s ease, opacity 0.18s ease;\n    }\n\n    .mljar-select-container.is-open .mljar-select-caret {\n      opacity: 1;\n      transform: translateY(-35%) rotate(225deg);\n    }\n\n    .mljar-select-dropdown {\n      display: none;\n      margin-top: 6px;\n      border: 1px solid #cfd1d5;\n      border-radius: 6px;\n      background: #ffffff;\n      box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);\n      overflow: hidden;\n    }\n\n    .mljar-select-list {\n      max-height: 260px;\n      overflow-y: auto;\n    }\n\n    .mljar-select-option {\n      display: block;\n      width: 100%;\n      padding: 9px 10px;\n      border: 0;\n      background: transparent;\n      color: #0f172a;\n      text-align: left;\n      cursor: pointer;\n      font: inherit;\n      transition: background-color 0.14s ease, color 0.14s ease;\n    }\n\n    .mljar-select-option:hover {\n      background: #f3f3f4;\n    }\n\n    .mljar-select-option:active {\n      background: #e6f2ff;\n      color: #007bff;\n    }\n\n    .mljar-select-option.is-selected {\n      background: #e6f2ff;\n      color: #007bff;\n      font-weight: 600;\n    }\n\n    .mljar-select-option.is-selected:hover,\n    .mljar-select-option.is-selected:active {\n      background: #e6f2ff;\n      color: #007bff;\n    }\n\n    .mljar-select-empty {\n      display: none;\n      padding: 10px;\n      color: #616673;\n      font-size: 0.95em;\n    }\n\n    .mljar-select-widget-input:disabled {\n      background: #f5f5f5;\n      color: #888;\n      cursor: not-allowed;\n    }\n\n    .mljar-select-control.is-disabled .mljar-select-caret {\n      opacity: 0.45;\n    }\n    ",
       "_dom_classes": [],
       "_esm": "\n    function render({ model, el }) {\n      const normalize = value => String(value ?? \"\").toLowerCase().trim();\n      const getChoices = () =>\n        Array.isArray(model.get(\"choices\")) ? [...model.get(\"choices\")] : [];\n      const isDisabled = () => !!model.get(\"disabled\");\n      const isHidden = () => !!model.get(\"hidden\");\n\n      const container = document.createElement(\"div\");\n      container.classList.add(\"mljar-select-container\");\n\n      if (model.get(\"label\")) {\n        const topLabel = document.createElement(\"div\");\n        topLabel.classList.add(\"mljar-select-label\");\n        topLabel.innerHTML = model.get(\"label\");\n        container.appendChild(topLabel);\n      }\n\n      const control = document.createElement(\"div\");\n      control.classList.add(\"mljar-select-control\");\n\n      const input = document.createElement(\"input\");\n      input.type = \"text\";\n      input.classList.add(\"mljar-select-widget-input\");\n      input.autocomplete = \"off\";\n      input.spellcheck = false;\n\n      const caret = document.createElement(\"div\");\n      caret.classList.add(\"mljar-select-caret\");\n\n      control.appendChild(input);\n      control.appendChild(caret);\n\n      const dropdown = document.createElement(\"div\");\n      dropdown.classList.add(\"mljar-select-dropdown\");\n\n      const list = document.createElement(\"div\");\n      list.classList.add(\"mljar-select-list\");\n\n      const emptyState = document.createElement(\"div\");\n      emptyState.classList.add(\"mljar-select-empty\");\n      emptyState.textContent = \"No matches\";\n\n      dropdown.appendChild(list);\n      dropdown.appendChild(emptyState);\n\n      container.appendChild(control);\n      container.appendChild(dropdown);\n      el.appendChild(container);\n\n      let isOpen = false;\n      let filteredChoices = [];\n      let lastCommittedValue = \"\";\n      let isEditing = false;\n\n      const setOpen = next => {\n        if (isDisabled()) {\n          isOpen = false;\n        } else {\n          isOpen = !!next;\n        }\n        container.classList.toggle(\"is-open\", isOpen);\n        dropdown.style.display = isOpen ? \"block\" : \"none\";\n      };\n\n      const updateDisabledState = () => {\n        const disabled = isDisabled();\n        input.disabled = disabled;\n        control.classList.toggle(\"is-disabled\", disabled);\n      };\n\n      const updateHiddenState = () => {\n        container.style.display = isHidden() ? \"none\" : \"\";\n      };\n\n      const syncInputWithValue = () => {\n        const value = model.get(\"value\") || \"\";\n        lastCommittedValue = value;\n        if (!isEditing) {\n          input.value = value;\n        }\n      };\n\n      const filterChoices = query => {\n        const normalizedQuery = normalize(query);\n        const allChoices = getChoices();\n        if (!normalizedQuery) {\n          return allChoices;\n        }\n        return allChoices.filter(choice =>\n          normalize(choice).includes(normalizedQuery)\n        );\n      };\n\n      const renderList = () => {\n        list.innerHTML = \"\";\n        filteredChoices.forEach(choice => {\n          const option = document.createElement(\"button\");\n          option.type = \"button\";\n          option.classList.add(\"mljar-select-option\");\n          if (choice === model.get(\"value\")) {\n            option.classList.add(\"is-selected\");\n          }\n          option.textContent = choice;\n          option.addEventListener(\"mousedown\", event => {\n            event.preventDefault();\n            event.stopPropagation();\n            model.set(\"value\", choice);\n            model.save_changes();\n            isEditing = false;\n            syncInputWithValue();\n            renderList();\n            setOpen(false);\n          });\n          list.appendChild(option);\n        });\n\n        const hasMatches = filteredChoices.length > 0;\n        list.style.display = hasMatches ? \"block\" : \"none\";\n        emptyState.style.display = hasMatches ? \"none\" : \"block\";\n      };\n\n      const refreshList = () => {\n        filteredChoices = filterChoices(input.value);\n        renderList();\n      };\n\n      const openWithCurrentQuery = () => {\n        isEditing = true;\n        input.value = \"\";\n        refreshList();\n        setOpen(true);\n      };\n\n      control.addEventListener(\"click\", event => {\n        event.stopPropagation();\n        if (isDisabled()) {\n          return;\n        }\n        openWithCurrentQuery();\n        input.focus();\n      });\n\n      input.addEventListener(\"input\", () => {\n        if (isDisabled()) {\n          return;\n        }\n        refreshList();\n        setOpen(true);\n      });\n\n      input.addEventListener(\"focus\", () => {\n        if (isDisabled()) {\n          return;\n        }\n        openWithCurrentQuery();\n      });\n\n      input.addEventListener(\"blur\", () => {\n        isEditing = false;\n        input.value = lastCommittedValue;\n      });\n\n      const handleDocumentClick = event => {\n        if (!container.contains(event.target)) {\n          isEditing = false;\n          setOpen(false);\n          input.value = lastCommittedValue;\n        }\n      };\n\n      document.addEventListener(\"click\", handleDocumentClick);\n\n      model.on(\"change:value\", () => {\n        syncInputWithValue();\n        refreshList();\n      });\n\n      model.on(\"change:choices\", () => {\n        const choices = getChoices();\n        if (!choices.includes(model.get(\"value\")) && choices.length > 0) {\n          model.set(\"value\", choices[0]);\n          model.save_changes();\n          return;\n        }\n        syncInputWithValue();\n        refreshList();\n      });\n\n      model.on(\"change:disabled\", () => {\n        updateDisabledState();\n        if (isDisabled()) {\n          isEditing = false;\n          setOpen(false);\n        }\n      });\n\n      model.on(\"change:hidden\", () => {\n        updateHiddenState();\n      });\n\n      updateDisabledState();\n      updateHiddenState();\n      syncInputWithValue();\n      refreshList();\n      setOpen(false);\n\n      return () => {\n        document.removeEventListener(\"click\", handleDocumentClick);\n      };\n    }\n    export default { render };\n    ",
       "_model_module": "anywidget",
       "_model_module_version": "~0.11.*",
       "_model_name": "AnyModel",
       "_view_count": null,
       "_view_module": "anywidget",
       "_view_module_version": "~0.11.*",
       "_view_name": "AnyView",
       "cell_id": "",
       "choices": [
        "8.0% (CTM treasury)",
        "13.5% (deck)"
       ],
       "disabled": false,
       "hidden": false,
       "label": "NPV discount rate (primary)",
       "layout": "IPY_MODEL_00d64fd0d74244de90fd31e6aa312a88",
       "layout_path": null,
       "position": "sidebar",
       "render_slot_id": null,
       "source_cell_id": null,
       "tabbable": null,
       "tooltip": null,
       "url_key": "",
       "value": "8.0% (CTM treasury)"
      }
     },
     "51e7784eabda4371a1fa5fb59cbc9065": {
      "model_module": "@jupyter-widgets/base",
      "model_module_version": "2.0.0",
      "model_name": "LayoutModel",
      "state": {
       "_model_module": "@jupyter-widgets/base",
       "_model_module_version": "2.0.0",
       "_model_name": "LayoutModel",
       "_view_count": null,
       "_view_module": "@jupyter-widgets/base",
       "_view_module_version": "2.0.0",
       "_view_name": "LayoutView",
       "align_content": null,
       "align_items": null,
       "align_self": null,
       "border_bottom": null,
       "border_left": null,
       "border_right": null,
       "border_top": null,
       "bottom": null,
       "display": null,
       "flex": null,
       "flex_flow": null,
       "grid_area": null,
       "grid_auto_columns": null,
       "grid_auto_flow": null,
       "grid_auto_rows": null,
       "grid_column": null,
       "grid_gap": null,
       "grid_row": null,
       "grid_template_areas": null,
       "grid_template_columns": null,
       "grid_template_rows": null,
       "height": null,
       "justify_content": null,
       "justify_items": null,
       "left": null,
       "margin": null,
       "max_height": null,
       "max_width": null,
       "min_height": null,
       "min_width": null,
       "object_fit": null,
       "object_position": null,
       "order": null,
       "overflow": null,
       "padding": null,
       "right": null,
       "top": null,
       "visibility": null,
       "width": null
      }
     },
     "9015105f420e40a6a4dac66945ef69ac": {
      "model_module": "anywidget",
      "model_module_version": "~0.11.*",
      "model_name": "AnyModel",
      "state": {
       "_anywidget_id": "mercury.select.SelectWidget",
       "_css": "\n    .mljar-select-container {\n      display: flex;\n      flex-direction: column;\n      font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n      font-size: 14px;\n      color: #0f172a;\n      margin-bottom: 8px;\n      padding-left: 4px;\n      padding-right: 4px;\n    }\n\n    .mljar-select-label {\n      margin-bottom: 4px;\n      font-weight: 600;\n    }\n\n    .mljar-select-control {\n      position: relative;\n      display: flex;\n      align-items: center;\n      cursor: default;\n    }\n\n    .mljar-select-widget-input {\n      width: 100%;\n      min-height: 40px;\n      padding: 9px 36px 9px 10px;\n      border: 1px solid #cfd1d5;\n      border-radius: 6px;\n      background: #ffffff;\n      box-sizing: border-box;\n      line-height: 1.4;\n      transition: border-color 0.15s ease, box-shadow 0.15s ease;\n\n      appearance: none !important;\n      background-color: #ffffff !important;\n      color: #0f172a !important;\n      cursor: default;\n    }\n\n    .mljar-select-widget-input:focus {\n      outline: none;\n      border-color: #007bff;\n      border-width: 2px;\n      box-shadow: none;\n      cursor: text;\n    }\n\n    .mljar-select-caret {\n      position: absolute;\n      right: 12px;\n      top: 50%;\n      width: 8px;\n      height: 8px;\n      border-right: 1.5px solid #0f172a;\n      border-bottom: 1.5px solid #0f172a;\n      transform: translateY(-65%) rotate(45deg);\n      pointer-events: none;\n      opacity: 0.5;\n      transition: transform 0.18s ease, opacity 0.18s ease;\n    }\n\n    .mljar-select-container.is-open .mljar-select-caret {\n      opacity: 1;\n      transform: translateY(-35%) rotate(225deg);\n    }\n\n    .mljar-select-dropdown {\n      display: none;\n      margin-top: 6px;\n      border: 1px solid #cfd1d5;\n      border-radius: 6px;\n      background: #ffffff;\n      box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);\n      overflow: hidden;\n    }\n\n    .mljar-select-list {\n      max-height: 260px;\n      overflow-y: auto;\n    }\n\n    .mljar-select-option {\n      display: block;\n      width: 100%;\n      padding: 9px 10px;\n      border: 0;\n      background: transparent;\n      color: #0f172a;\n      text-align: left;\n      cursor: pointer;\n      font: inherit;\n      transition: background-color 0.14s ease, color 0.14s ease;\n    }\n\n    .mljar-select-option:hover {\n      background: #f3f3f4;\n    }\n\n    .mljar-select-option:active {\n      background: #e6f2ff;\n      color: #007bff;\n    }\n\n    .mljar-select-option.is-selected {\n      background: #e6f2ff;\n      color: #007bff;\n      font-weight: 600;\n    }\n\n    .mljar-select-option.is-selected:hover,\n    .mljar-select-option.is-selected:active {\n      background: #e6f2ff;\n      color: #007bff;\n    }\n\n    .mljar-select-empty {\n      display: none;\n      padding: 10px;\n      color: #616673;\n      font-size: 0.95em;\n    }\n\n    .mljar-select-widget-input:disabled {\n      background: #f5f5f5;\n      color: #888;\n      cursor: not-allowed;\n    }\n\n    .mljar-select-control.is-disabled .mljar-select-caret {\n      opacity: 0.45;\n    }\n    ",
       "_dom_classes": [],
       "_esm": "\n    function render({ model, el }) {\n      const normalize = value => String(value ?? \"\").toLowerCase().trim();\n      const getChoices = () =>\n        Array.isArray(model.get(\"choices\")) ? [...model.get(\"choices\")] : [];\n      const isDisabled = () => !!model.get(\"disabled\");\n      const isHidden = () => !!model.get(\"hidden\");\n\n      const container = document.createElement(\"div\");\n      container.classList.add(\"mljar-select-container\");\n\n      if (model.get(\"label\")) {\n        const topLabel = document.createElement(\"div\");\n        topLabel.classList.add(\"mljar-select-label\");\n        topLabel.innerHTML = model.get(\"label\");\n        container.appendChild(topLabel);\n      }\n\n      const control = document.createElement(\"div\");\n      control.classList.add(\"mljar-select-control\");\n\n      const input = document.createElement(\"input\");\n      input.type = \"text\";\n      input.classList.add(\"mljar-select-widget-input\");\n      input.autocomplete = \"off\";\n      input.spellcheck = false;\n\n      const caret = document.createElement(\"div\");\n      caret.classList.add(\"mljar-select-caret\");\n\n      control.appendChild(input);\n      control.appendChild(caret);\n\n      const dropdown = document.createElement(\"div\");\n      dropdown.classList.add(\"mljar-select-dropdown\");\n\n      const list = document.createElement(\"div\");\n      list.classList.add(\"mljar-select-list\");\n\n      const emptyState = document.createElement(\"div\");\n      emptyState.classList.add(\"mljar-select-empty\");\n      emptyState.textContent = \"No matches\";\n\n      dropdown.appendChild(list);\n      dropdown.appendChild(emptyState);\n\n      container.appendChild(control);\n      container.appendChild(dropdown);\n      el.appendChild(container);\n\n      let isOpen = false;\n      let filteredChoices = [];\n      let lastCommittedValue = \"\";\n      let isEditing = false;\n\n      const setOpen = next => {\n        if (isDisabled()) {\n          isOpen = false;\n        } else {\n          isOpen = !!next;\n        }\n        container.classList.toggle(\"is-open\", isOpen);\n        dropdown.style.display = isOpen ? \"block\" : \"none\";\n      };\n\n      const updateDisabledState = () => {\n        const disabled = isDisabled();\n        input.disabled = disabled;\n        control.classList.toggle(\"is-disabled\", disabled);\n      };\n\n      const updateHiddenState = () => {\n        container.style.display = isHidden() ? \"none\" : \"\";\n      };\n\n      const syncInputWithValue = () => {\n        const value = model.get(\"value\") || \"\";\n        lastCommittedValue = value;\n        if (!isEditing) {\n          input.value = value;\n        }\n      };\n\n      const filterChoices = query => {\n        const normalizedQuery = normalize(query);\n        const allChoices = getChoices();\n        if (!normalizedQuery) {\n          return allChoices;\n        }\n        return allChoices.filter(choice =>\n          normalize(choice).includes(normalizedQuery)\n        );\n      };\n\n      const renderList = () => {\n        list.innerHTML = \"\";\n        filteredChoices.forEach(choice => {\n          const option = document.createElement(\"button\");\n          option.type = \"button\";\n          option.classList.add(\"mljar-select-option\");\n          if (choice === model.get(\"value\")) {\n            option.classList.add(\"is-selected\");\n          }\n          option.textContent = choice;\n          option.addEventListener(\"mousedown\", event => {\n            event.preventDefault();\n            event.stopPropagation();\n            model.set(\"value\", choice);\n            model.save_changes();\n            isEditing = false;\n            syncInputWithValue();\n            renderList();\n            setOpen(false);\n          });\n          list.appendChild(option);\n        });\n\n        const hasMatches = filteredChoices.length > 0;\n        list.style.display = hasMatches ? \"block\" : \"none\";\n        emptyState.style.display = hasMatches ? \"none\" : \"block\";\n      };\n\n      const refreshList = () => {\n        filteredChoices = filterChoices(input.value);\n        renderList();\n      };\n\n      const openWithCurrentQuery = () => {\n        isEditing = true;\n        input.value = \"\";\n        refreshList();\n        setOpen(true);\n      };\n\n      control.addEventListener(\"click\", event => {\n        event.stopPropagation();\n        if (isDisabled()) {\n          return;\n        }\n        openWithCurrentQuery();\n        input.focus();\n      });\n\n      input.addEventListener(\"input\", () => {\n        if (isDisabled()) {\n          return;\n        }\n        refreshList();\n        setOpen(true);\n      });\n\n      input.addEventListener(\"focus\", () => {\n        if (isDisabled()) {\n          return;\n        }\n        openWithCurrentQuery();\n      });\n\n      input.addEventListener(\"blur\", () => {\n        isEditing = false;\n        input.value = lastCommittedValue;\n      });\n\n      const handleDocumentClick = event => {\n        if (!container.contains(event.target)) {\n          isEditing = false;\n          setOpen(false);\n          input.value = lastCommittedValue;\n        }\n      };\n\n      document.addEventListener(\"click\", handleDocumentClick);\n\n      model.on(\"change:value\", () => {\n        syncInputWithValue();\n        refreshList();\n      });\n\n      model.on(\"change:choices\", () => {\n        const choices = getChoices();\n        if (!choices.includes(model.get(\"value\")) && choices.length > 0) {\n          model.set(\"value\", choices[0]);\n          model.save_changes();\n          return;\n        }\n        syncInputWithValue();\n        refreshList();\n      });\n\n      model.on(\"change:disabled\", () => {\n        updateDisabledState();\n        if (isDisabled()) {\n          isEditing = false;\n          setOpen(false);\n        }\n      });\n\n      model.on(\"change:hidden\", () => {\n        updateHiddenState();\n      });\n\n      updateDisabledState();\n      updateHiddenState();\n      syncInputWithValue();\n      refreshList();\n      setOpen(false);\n\n      return () => {\n        document.removeEventListener(\"click\", handleDocumentClick);\n      };\n    }\n    export default { render };\n    ",
       "_model_module": "anywidget",
       "_model_module_version": "~0.11.*",
       "_model_name": "AnyModel",
       "_view_count": null,
       "_view_module": "anywidget",
       "_view_module_version": "~0.11.*",
       "_view_name": "AnyView",
       "cell_id": "",
       "choices": [
        "floor",
        "realistic"
       ],
       "disabled": false,
       "hidden": false,
       "label": "Headline scenario",
       "layout": "IPY_MODEL_dcc9a46d44bd483e9026797f7fe35a20",
       "layout_path": null,
       "position": "sidebar",
       "render_slot_id": null,
       "source_cell_id": null,
       "tabbable": null,
       "tooltip": null,
       "url_key": "",
       "value": "realistic"
      }
     },
     "d59e0ecb13204366bac47d02a7ec4f90": {
      "model_module": "anywidget",
      "model_module_version": "~0.11.*",
      "model_name": "AnyModel",
      "state": {
       "_anywidget_id": "mercury.number.NumberInputWidget",
       "_css": "\n    .mljar-number-container {\n      display: flex;\n      flex-direction: column;\n      width: 100%;\n      font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n      font-size: 14px;\n      color: #0f172a;\n      margin-bottom: 8px;\n      padding-left: 4px;\n      padding-right: 4px;\n      box-sizing: border-box;\n    }\n\n    .mljar-number-label {\n      margin-bottom: 4px;\n      font-weight: 600;\n    }\n\n    .mljar-number-field-row {\n      display: flex;\n      align-items: stretch;\n      width: 100%;\n      min-height: 40px;\n      border: 1px solid #cfd1d5;\n      border-radius: 6px;\n      background: #ffffff;\n      box-sizing: border-box;\n      overflow: hidden;\n    }\n\n    .mljar-number-input {\n      flex: 1 1 auto;\n      min-width: 0;\n      min-height: 100%;\n      padding: 7px 10px;\n      border: 0;\n      border-radius: 0;\n      background: #ffffff;\n      box-sizing: border-box;\n      background-color: #ffffff !important;\n      color: #0f172a !important;\n      font: inherit;\n      line-height: 1.2;\n      -moz-appearance: textfield;\n    }\n\n    .mljar-number-input::-webkit-outer-spin-button,\n    .mljar-number-input::-webkit-inner-spin-button {\n      -webkit-appearance: none;\n      margin: 0;\n    }\n\n    .mljar-number-input:disabled {\n      background: #f5f5f5;\n      color: #888;\n      cursor: not-allowed;\n    }\n\n    .mljar-number-input:focus {\n      outline: none;\n    }\n\n    .mljar-number-field-row:focus-within {\n      border-color: #007bff;\n      border-width: 2px;\n      box-shadow: none;\n    }\n\n    .mljar-number-field-row:focus-within .mljar-number-controls {\n      border-left-color: #007bff;\n    }\n\n    .mljar-number-controls {\n      display: flex;\n      align-items: stretch;\n      flex: 0 0 auto;\n      border-left: 1px solid #cfd1d5;\n      background: #f1f1f2;\n    }\n\n    .mljar-number-step-btn {\n      display: inline-flex;\n      align-items: center;\n      justify-content: center;\n      width: 38px;\n      min-width: 38px;\n      min-height: 100%;\n      border: 0;\n      border-radius: 0;\n      background: transparent;\n      color: #0f172a;\n      font: inherit;\n      font-size: 18px;\n      font-weight: 700;\n      line-height: 1;\n      cursor: pointer;\n      padding: 0;\n      user-select: none;\n      -webkit-user-select: none;\n      touch-action: manipulation;\n      transition: background-color 0.14s ease, color 0.14s ease;\n    }\n\n    .mljar-number-step-up {\n      border-left: 1px solid #cfd1d5;\n    }\n\n    .mljar-number-step-btn:hover {\n      background: #f3f3f4;\n    }\n\n    .mljar-number-step-btn:active {\n      background: #e6f2ff;\n      color: #007bff;\n    }\n\n    .mljar-number-step-btn:focus-visible {\n      outline: none;\n      background: #e6f2ff;\n      color: #007bff;\n    }\n\n    .mljar-number-step-btn:disabled {\n      background: #f5f5f5;\n      color: #aaa;\n      cursor: not-allowed;\n    }\n\n    @media (max-width: 768px) {\n      .mljar-number-field-row {\n        min-height: 44px;\n      }\n\n      .mljar-number-input {\n        min-height: 44px;\n        padding: 8px 12px;\n      }\n\n      .mljar-number-step-btn {\n        font-size: 19px;\n        width: 44px;\n        min-width: 44px;\n      }\n    }\n    ",
       "_dom_classes": [],
       "_esm": "\n    function render({ model, el }) {\n      const container = document.createElement(\"div\");\n      container.classList.add(\"mljar-number-container\");\n\n      const topLabel = document.createElement(\"div\");\n      topLabel.classList.add(\"mljar-number-label\");\n\n      const fieldRow = document.createElement(\"div\");\n      fieldRow.classList.add(\"mljar-number-field-row\");\n\n      const input = document.createElement(\"input\");\n      input.type = \"number\";\n      input.classList.add(\"mljar-number-input\");\n\n      const decrementBtn = document.createElement(\"button\");\n      decrementBtn.type = \"button\";\n      decrementBtn.classList.add(\"mljar-number-step-btn\", \"mljar-number-step-down\");\n      decrementBtn.textContent = \"-\";\n      decrementBtn.setAttribute(\"aria-label\", \"Decrease value\");\n\n      const incrementBtn = document.createElement(\"button\");\n      incrementBtn.type = \"button\";\n      incrementBtn.classList.add(\"mljar-number-step-btn\", \"mljar-number-step-up\");\n      incrementBtn.textContent = \"+\";\n      incrementBtn.setAttribute(\"aria-label\", \"Increase value\");\n\n      const controls = document.createElement(\"div\");\n      controls.classList.add(\"mljar-number-controls\");\n\n      controls.appendChild(decrementBtn);\n      controls.appendChild(incrementBtn);\n      fieldRow.appendChild(input);\n      fieldRow.appendChild(controls);\n\n      container.appendChild(topLabel);\n      container.appendChild(fieldRow);\n      el.appendChild(container);\n\n      function clamp(val, min, max) {\n        if (Number.isFinite(min) && val < min) return min;\n        if (Number.isFinite(max) && val > max) return max;\n        return val;\n      }\n\n      function normalizeStep(step) {\n        return Number.isFinite(step) && step > 0 ? step : 1;\n      }\n\n      function snapToStep(value, min, step) {\n        const safeStep = normalizeStep(step);\n        const base = Number.isFinite(min) ? min : 0;\n        const steps = Math.round((value - base) / safeStep);\n        const snapped = base + steps * safeStep;\n        const precision = Math.max(\n          0,\n          (String(safeStep).split(\".\")[1] || \"\").length\n        );\n\n        return Number(snapped.toFixed(precision + 2));\n      }\n\n      function isOnStepGrid(value, min, step) {\n        const safeStep = normalizeStep(step);\n        const base = Number.isFinite(min) ? min : 0;\n        const steps = (value - base) / safeStep;\n        const nearest = Math.round(steps);\n        const epsilon = Math.max(1e-9, safeStep * 1e-9);\n\n        return Math.abs(steps - nearest) <= epsilon;\n      }\n\n      function getCurrentBounds() {\n        return {\n          min: Number(model.get(\"min\")),\n          max: Number(model.get(\"max\")),\n        };\n      }\n\n      function isTransientDraft(raw) {\n        return raw === \"\" || raw === \"-\" || raw === \".\" || raw === \"-.\";\n      }\n\n      let isEditing = false;\n      const INPUT_COMMIT_DEBOUNCE_MS = 400;\n\n      function clearPendingDraftCommit() {\n        if (debounceTimer) clearTimeout(debounceTimer);\n        pendingDraftValue = null;\n      }\n\n      function parseDraftValue(rawValue) {\n        const raw = String(rawValue).trim();\n        if (isTransientDraft(raw)) {\n          return { kind: \"transient\" };\n        }\n\n        const value = Number(raw);\n        if (!Number.isFinite(value)) {\n          return { kind: \"invalid\" };\n        }\n\n        return { kind: \"number\", value };\n      }\n\n      function commitValue(nextValue, saveNow = true) {\n        const { min, max } = getCurrentBounds();\n        const step = Number(model.get(\"step\"));\n        const parsed = parseDraftValue(nextValue);\n        if (parsed.kind !== \"number\") {\n          syncFromModel();\n          return;\n        }\n\n        let v = parsed.value;\n        v = clamp(v, min, max);\n        v = snapToStep(v, min, step);\n        v = clamp(v, min, max);\n        input.value = String(v);\n        model.set(\"value\", v);\n\n        if (saveNow) {\n          model.save_changes();\n        }\n      }\n\n      function syncFromModel() {\n        topLabel.innerHTML = model.get(\"label\") || \"Enter number\";\n\n        const min = Number(model.get(\"min\"));\n        const max = Number(model.get(\"max\"));\n        const step = Number(model.get(\"step\"));\n\n        if (Number.isFinite(min)) input.min = String(min); else input.removeAttribute(\"min\");\n        if (Number.isFinite(max)) input.max = String(max); else input.removeAttribute(\"max\");\n        if (Number.isFinite(step)) input.step = String(step); else input.removeAttribute(\"step\");\n\n        const v = Number(model.get(\"value\"));\n        if (!isEditing) {\n          input.value = Number.isFinite(v) ? String(v) : \"\";\n        }\n\n        const disabled = !!model.get(\"disabled\");\n        input.disabled = disabled;\n        incrementBtn.disabled = disabled;\n        decrementBtn.disabled = disabled;\n\n        const hidden = !!model.get(\"hidden\");\n        container.style.display = hidden ? \"none\" : \"flex\";\n      }\n\n      let debounceTimer = null;\n      let pendingDraftValue = null;\n      input.addEventListener(\"focus\", () => {\n        isEditing = true;\n      });\n\n      input.addEventListener(\"input\", () => {\n        if (model.get(\"disabled\")) return;\n\n        const parsed = parseDraftValue(input.value);\n        if (parsed.kind !== \"number\") {\n          clearPendingDraftCommit();\n          return;\n        }\n\n        const v = parsed.value;\n        const { min, max } = getCurrentBounds();\n        const step = Number(model.get(\"step\"));\n        if (Number.isFinite(min) && v < min) {\n          clearPendingDraftCommit();\n          return;\n        }\n        if (Number.isFinite(max) && v > max) {\n          clearPendingDraftCommit();\n          return;\n        }\n        if (!isOnStepGrid(v, min, step)) {\n          clearPendingDraftCommit();\n          return;\n        }\n\n        pendingDraftValue = v;\n        if (debounceTimer) clearTimeout(debounceTimer);\n        debounceTimer = setTimeout(() => {\n          if (pendingDraftValue === null) return;\n          model.set(\"value\", pendingDraftValue);\n          model.save_changes();\n          pendingDraftValue = null;\n        }, INPUT_COMMIT_DEBOUNCE_MS);\n      });\n\n      input.addEventListener(\"blur\", () => {\n        isEditing = false;\n        clearPendingDraftCommit();\n        commitValue(input.value, true);\n      });\n\n      input.addEventListener(\"keydown\", event => {\n        if (event.key === \"Enter\") {\n          event.preventDefault();\n          input.blur();\n        }\n      });\n\n      incrementBtn.addEventListener(\"click\", () => {\n        if (model.get(\"disabled\")) return;\n        const current = Number(model.get(\"value\"));\n        const step = normalizeStep(Number(model.get(\"step\")));\n        const min = Number(model.get(\"min\"));\n        const base = Number.isFinite(current) ? current : 0;\n        commitValue(snapToStep(base + step, min, step));\n      });\n\n      decrementBtn.addEventListener(\"click\", () => {\n        if (model.get(\"disabled\")) return;\n        const current = Number(model.get(\"value\"));\n        const step = normalizeStep(Number(model.get(\"step\")));\n        const min = Number(model.get(\"min\"));\n        const base = Number.isFinite(current) ? current : 0;\n        commitValue(snapToStep(base - step, min, step));\n      });\n\n      model.on(\"change:value\", syncFromModel);\n      model.on(\"change:min\", syncFromModel);\n      model.on(\"change:max\", syncFromModel);\n      model.on(\"change:step\", syncFromModel);\n      model.on(\"change:label\", syncFromModel);\n      model.on(\"change:disabled\", syncFromModel);\n      model.on(\"change:hidden\", syncFromModel);\n\n      syncFromModel();\n\n      // ---- read cell id (no DOM modifications) ----\n      /*\n      const ID_ATTR = \"data-cell-id\";\n      const hostWithId = el.closest(`[${ID_ATTR}]`);\n      const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n      if (cellId) {\n        model.set(\"cell_id\", cellId);\n        model.save_changes();\n        model.send({ type: \"cell_id_detected\", value: cellId });\n      } else {\n        const mo = new MutationObserver(() => {\n          const host = el.closest(`[${ID_ATTR}]`);\n          const newId = host?.getAttribute(ID_ATTR);\n          if (newId) {\n            model.set(\"cell_id\", newId);\n            model.save_changes();\n            model.send({ type: \"cell_id_detected\", value: newId });\n            mo.disconnect();\n          }\n        });\n        mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n      }*/\n    }\n    export default { render };\n    ",
       "_model_module": "anywidget",
       "_model_module_version": "~0.11.*",
       "_model_name": "AnyModel",
       "_view_count": null,
       "_view_module": "anywidget",
       "_view_module_version": "~0.11.*",
       "_view_name": "AnyView",
       "cell_id": "",
       "disabled": false,
       "hidden": false,
       "label": "Voice-attributable platform licence ($/yr)",
       "layout": "IPY_MODEL_288ac9b4291a431a8b34bd9c8a8f4077",
       "layout_path": null,
       "max": 6000000.0,
       "min": 0.0,
       "position": "sidebar",
       "render_slot_id": null,
       "source_cell_id": null,
       "step": 100000.0,
       "tabbable": null,
       "tooltip": null,
       "url_key": "",
       "value": 3200000.0
      }
     },
     "dcc9a46d44bd483e9026797f7fe35a20": {
      "model_module": "@jupyter-widgets/base",
      "model_module_version": "2.0.0",
      "model_name": "LayoutModel",
      "state": {
       "_model_module": "@jupyter-widgets/base",
       "_model_module_version": "2.0.0",
       "_model_name": "LayoutModel",
       "_view_count": null,
       "_view_module": "@jupyter-widgets/base",
       "_view_module_version": "2.0.0",
       "_view_name": "LayoutView",
       "align_content": null,
       "align_items": null,
       "align_self": null,
       "border_bottom": null,
       "border_left": null,
       "border_right": null,
       "border_top": null,
       "bottom": null,
       "display": null,
       "flex": null,
       "flex_flow": null,
       "grid_area": null,
       "grid_auto_columns": null,
       "grid_auto_flow": null,
       "grid_auto_rows": null,
       "grid_column": null,
       "grid_gap": null,
       "grid_row": null,
       "grid_template_areas": null,
       "grid_template_columns": null,
       "grid_template_rows": null,
       "height": null,
       "justify_content": null,
       "justify_items": null,
       "left": null,
       "margin": null,
       "max_height": null,
       "max_width": null,
       "min_height": null,
       "min_width": null,
       "object_fit": null,
       "object_position": null,
       "order": null,
       "overflow": null,
       "padding": null,
       "right": null,
       "top": null,
       "visibility": null,
       "width": null
      }
     },
     "edb7017bf89b4f77a4754df3bb3798de": {
      "model_module": "anywidget",
      "model_module_version": "~0.11.*",
      "model_name": "AnyModel",
      "state": {
       "_anywidget_id": "mercury.checkbox.CheckboxWidget",
       "_css": "\n    .mljar-checkbox-container {\n        display: inline-flex;\n        align-items: center;\n        gap: 10px;\n        cursor: pointer;\n        user-select: none;\n        -webkit-tap-highlight-color: transparent;\n        font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n        color: #0f172a;\n        padding-left: 5px;\n    }\n    .mljar-checkbox-container.is-disabled {\n        opacity: 0.6;\n        cursor: not-allowed;\n    }\n\n    .mljar-checkbox-input {\n        position: absolute;\n        opacity: 0;\n        width: 0;\n        height: 0;\n    }\n\n    .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n        box-shadow: inset 0 0 0 2px #007bff;\n    }\n\n    .mljar-checkbox-label {\n        font-size: 14px;\n        line-height: 1.2;\n        padding-top: 2px;\n    }\n\n    /* --- Toggle style (15% smaller) --- */\n    .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n        position: relative;\n        width: 34px;\n        height: 19px;\n        border-radius: 999px;\n        background: #f1f1f2;\n        border: 1px solid #cfd1d5;\n        transition: background 150ms ease, border-color 150ms ease;\n    }\n    .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n        background: #007bff;\n        border-color: #007bff;\n    }\n    .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n        content: \"\";\n        position: absolute;\n        top: 2px;\n        left: 2px;\n        width: 15px;\n        height: 15px;\n        border-radius: 50%;\n        background: #ffffff;\n        box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n        transition: transform 150ms ease;\n    }\n    .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n        transform: translateX(15px);\n    }\n\n    /* --- Classic box style --- */\n    .mljar-checkbox-container.is-box .mljar-checkbox-control {\n        width: 16px;\n        height: 16px;\n        border-radius: 4px;\n        border: 1px solid #cfd1d5;\n        background: #ffffff;\n        display: inline-block;\n        position: relative;\n    }\n    .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n        border-color: #007bff;\n        background: #007bff;\n    }\n    .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n        content: \"\";\n        position: absolute;\n        left: 4px;\n        top: 0px;\n        width: 5px;\n        height: 10px;\n        border: solid #fff;\n        border-width: 0 2px 2px 0;\n        transform: rotate(45deg);\n    }\n\n    .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n        border-color: #007bff;\n        background: #f3f3f4;\n    }\n    ",
       "_dom_classes": [],
       "_esm": "\n    function render({ model, el }) {\n      const container = document.createElement(\"label\");\n      container.classList.add(\"mljar-checkbox-container\");\n\n      const input = document.createElement(\"input\");\n      input.type = \"checkbox\";\n      input.classList.add(\"mljar-checkbox-input\");\n\n      const control = document.createElement(\"span\");\n      control.classList.add(\"mljar-checkbox-control\");\n\n      const text = document.createElement(\"span\");\n      text.classList.add(\"mljar-checkbox-label\");\n\n      container.appendChild(input);\n      container.appendChild(control);\n      container.appendChild(text);\n      el.appendChild(container);\n\n      function syncFromModel() {\n        // appearance\n        const a = model.get(\"appearance\") || \"toggle\";\n        container.classList.remove(\"is-toggle\",\"is-box\");\n        container.classList.add(`is-${a}`);\n        input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n        // value\n        const v = !!model.get(\"value\");\n        if (input.checked !== v) {\n          input.checked = v;\n          input.setAttribute(\"aria-checked\", String(v));\n        }\n        container.classList.toggle(\"is-checked\", v);\n\n        // disabled\n        const d = !!model.get(\"disabled\");\n        input.disabled = d;\n        container.classList.toggle(\"is-disabled\", d);\n\n        // label\n        text.textContent = model.get(\"label\") || \"\";\n\n        // hidden (exists but not visible)\n        container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n      }\n\n      input.addEventListener(\"change\", () => {\n        if (model.get(\"disabled\")) return;\n\n        const v = input.checked;\n        if (model.get(\"value\") !== v) {\n          model.set(\"value\", v);\n          model.set(\"last_changed_at\", new Date().toISOString());\n          model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n          model.save_changes();\n          // model.send({ type: \"changed\", value: v });\n        }\n      });\n\n      model.on(\"change:value\", syncFromModel);\n      model.on(\"change:disabled\", syncFromModel);\n      model.on(\"change:appearance\", syncFromModel);\n      model.on(\"change:label\", syncFromModel);\n      model.on(\"change:hidden\", syncFromModel);\n\n      syncFromModel();\n\n      // ---- read cell id (no DOM modifications) ----\n      /*\n      const ID_ATTR = \"data-cell-id\";\n      const hostWithId = el.closest(`[${ID_ATTR}]`);\n      const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n      if (cellId) {\n        model.set(\"cell_id\", cellId);\n        model.save_changes();\n        model.send({ type: \"cell_id_detected\", value: cellId });\n      } else {\n        const mo = new MutationObserver(() => {\n          const host = el.closest(`[${ID_ATTR}]`);\n          const newId = host?.getAttribute(ID_ATTR);\n          \n          if (newId) {\n            model.set(\"cell_id\", newId);\n            model.save_changes();\n            model.send({ type: \"cell_id_detected\", value: newId });\n            mo.disconnect();\n          }\n        });\n        mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n      }\n      */\n    }\n    export default { render };\n    ",
       "_model_module": "anywidget",
       "_model_module_version": "~0.11.*",
       "_model_name": "AnyModel",
       "_view_count": null,
       "_view_module": "anywidget",
       "_view_module_version": "~0.11.*",
       "_view_name": "AnyView",
       "appearance": "toggle",
       "cell_id": "",
       "disabled": false,
       "hidden": false,
       "label": "Include Agentic VA layer",
       "last_changed_at": "",
       "layout": "IPY_MODEL_51e7784eabda4371a1fa5fb59cbc9065",
       "layout_path": null,
       "n_toggles": 0,
       "position": "sidebar",
       "render_slot_id": null,
       "source_cell_id": null,
       "tabbable": null,
       "tooltip": null,
       "url_key": "",
       "value": true
      }
     }
    },
    "version_major": 2,
    "version_minor": 0
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
