{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "cell-0",
   "metadata": {},
   "source": [
    "# Genesys CX Cloud TEI — Business Case\n",
    "\n",
    "Reproduction of Forrester's *The Total Economic Impact™ Of CX Cloud — Cost\n",
    "Savings And Business Benefits Enabled By Genesys And Salesforce* (December\n",
    "2025, commissioned by Genesys and Salesforce) — and a live personalization\n",
    "of it. The published composite organization is the **verbatim anchor**\n",
    "(never edited); the verification gate proves this notebook reproduces the\n",
    "published **$10.8M NPV · 266% ROI**; the client drivers then rescale the\n",
    "composite to your organization.\n",
    "\n",
    "One line deserves special attention: the published study models **$0 of\n",
    "Genesys AI Experience token consumption**, even though three of its four\n",
    "benefits depend on AI capabilities that Genesys bills via tokens. That line\n",
    "is anchored at $0 (so the reproduction matches the PDF) and exposed as a\n",
    "direct 🔴 input — price it from the Genesys quote and watch the case move.\n",
    "\n",
    "**This notebook is the deliverable** — served interactively with Mercury,\n",
    "exported via nbconvert as the report source (Mercury Notebook Pattern,\n",
    "Variant 4).\n",
    "\n",
    "| Layer | What it is | Confidence |\n",
    "|---|---|---|\n",
    "| Verbatim anchor | Forrester's composite tables, unedited | 🟢 published |\n",
    "| Client overlay | first-order linear rescale by your drivers | 🟡 estimated |\n",
    "| AI-token line | negotiated annual quote figure, direct input | 🔴 unknown until quoted |\n",
    "| Scenario | adoption × risk stress | 🟡 estimated |\n",
    "\n",
    "Confidence legend: 🟢 confirmed/published · 🟡 estimated (stated assumption) · 🔴 unknown (flagged)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "cell-1",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-09T19:15:18.686288Z",
     "iopub.status.busy": "2026-07-09T19:15:18.686041Z",
     "iopub.status.idle": "2026-07-09T19:15:19.083373Z",
     "shell.execute_reply": "2026-07-09T19:15:19.082724Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "teicalc loaded — window 2026–2028 · published NPV $10.8M · ROI 266% · AI-token line anchored at $0\n"
     ]
    }
   ],
   "source": [
    "# ── Setup ──────────────────────────────────────────────────────────\n",
    "import sys, pathlib\n",
    "_ROOT = pathlib.Path.cwd()\n",
    "if not (_ROOT / \"teicalc\").exists():             # notebook lives in notebooks/\n",
    "    _ROOT = _ROOT.parent\n",
    "sys.path.insert(0, str(_ROOT))\n",
    "\n",
    "import pandas as pd\n",
    "import plotly.graph_objects as go\n",
    "\n",
    "import mercury as mr\n",
    "\n",
    "# Single source of truth — all math lives in the study package; only\n",
    "# presentation (and Mercury input widgets) lives here.\n",
    "from teicalc import (\n",
    "    ASSUMPTIONS, BENEFITS_VERBATIM, COSTS_VERBATIM, PUBLISHED,\n",
    "    YEARS, X_LABELS,\n",
    "    BENEFIT_DRIVERS, COST_DRIVERS, COMPOSITE, ClientDrivers,\n",
    "    SCENARIOS, apply_scenario, compute_summary,\n",
    "    money, html_money, overlay_rows,\n",
    ")\n",
    "from teicalc.staging import backstage\n",
    "\n",
    "pd.options.display.float_format = \"{:,.0f}\".format\n",
    "\n",
    "# ── Chart chrome (dataviz reference palette, light surface) ─────────\n",
    "INK, INK2, MUTED = \"#0b0b0b\", \"#52514e\", \"#898781\"\n",
    "SURFACE, GRID, BASELINE = \"#fcfcfb\", \"#e1e0d9\", \"#c3c2b7\"\n",
    "CUMULATIVE, CONTEXT = \"#52514e\", \"#c3c2b7\"   # neutral line; de-emphasized context series\n",
    "FONT_STACK = 'system-ui, -apple-system, \"Segoe UI\", sans-serif'\n",
    "\n",
    "# Fixed row colors — color follows the entity across every figure.\n",
    "BENEFIT_COLOR = {\n",
    "    \"legacy_retirement\": \"#2a78d6\",       # blue\n",
    "    \"self_service_savings\": \"#1baf7a\",    # aqua\n",
    "    \"agent_efficiency\": \"#4a3aa7\",        # violet\n",
    "    \"agent_assist_sales\": \"#eda100\",      # yellow\n",
    "}\n",
    "COST_COLOR = {\n",
    "    \"cx_cloud_licenses\": \"#2a78d6\",       # blue\n",
    "    \"implementation\": \"#eda100\",          # yellow\n",
    "    \"ongoing_management\": \"#4a3aa7\",      # violet\n",
    "    \"genesys_ai_tokens\": \"#e34948\",       # red — the flagged $0 line\n",
    "}\n",
    "BEN_TOTAL, COST_TOTAL, NPV_COLOR = \"#1baf7a\", \"#e34948\", \"#2a78d6\"\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.45, height=height,\n",
    "        margin=dict(t=70, r=30, b=80, l=70),\n",
    "    )\n",
    "    return fig\n",
    "\n",
    "\n",
    "def bar(x, y, name, color):\n",
    "    return go.Bar(x=x, y=y, name=name,\n",
    "                  marker=dict(color=color, line=dict(width=2, color=SURFACE)),\n",
    "                  hovertemplate=\"%{fullData.name}: %{y:$,.0f}<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",
    "backstage(f\"teicalc loaded — window {YEARS[0]}–{YEARS[-1]} · published \"\n",
    "          f\"NPV {money(PUBLISHED['npv'])} · ROI {PUBLISHED['roi_pct']}% · \"\n",
    "          f\"AI-token line anchored at $0\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "cell-2",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-09T19:15:19.085680Z",
     "iopub.status.busy": "2026-07-09T19:15:19.085417Z",
     "iopub.status.idle": "2026-07-09T19:15:19.097643Z",
     "shell.execute_reply": "2026-07-09T19:15:19.096879Z"
    }
   },
   "outputs": [
    {
     "data": {
      "application/mercury+json": {
       "model_id": "c7bf126b79ee49b2bb7140ec4fb10b96",
       "position": "sidebar",
       "widget": "MarkdownWidget"
      },
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "c7bf126b79ee49b2bb7140ec4fb10b96",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "MarkdownWidget(value='<div style=\"font-family: ui-sans-serif, system-ui, -apple-system, \\'Segoe UI\\', Roboto, …"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# ── Jump-to-section ToC (Mercury sidebar — widgets only, no output) ──\n",
    "# Fragment links don't scroll inside Mercury's app shell (SPA base URL),\n",
    "# so each entry scrolls via JS: anchor id first, heading-text fallback.\n",
    "_TOC = [\n",
    "    (1, \"Composite organization\"),\n",
    "    (2, \"Client inputs\"),\n",
    "    (3, \"Benefits\"),\n",
    "    (4, \"Costs & the $0 AI line\"),\n",
    "    (5, \"Business case\"),\n",
    "    (6, \"Scenarios\"),\n",
    "    (7, \"Verification & assertions\"),\n",
    "    (8, \"Data appendix\"),\n",
    "]\n",
    "# NB: no raw \"<\" allowed inside the handler — python-markdown escapes the\n",
    "# whole tag if the attribute text looks like malformed HTML.\n",
    "_JS = (\"var el=document.getElementById('section-{n}');\"\n",
    "       \"if(!el){{document.querySelectorAll('h1,h2').forEach(function(h){{\"\n",
    "       \"if(!el&&h.textContent.trim().indexOf('{n} ')===0){{el=h;}}}});}}\"\n",
    "       \"if(el)el.scrollIntoView({{behavior:'smooth',block:'start'}});\")\n",
    "_items = \"\".join(\n",
    "    f'<li style=\"margin:2px 0\"><a style=\"cursor:pointer;text-decoration:underline\"'\n",
    "    f' onclick=\"{_JS.format(n=n)}\">{label}</a></li>'\n",
    "    for n, label in _TOC)\n",
    "_toc = mr.Markdown(\n",
    "    text=(f'<b>Jump to section</b>'\n",
    "          f'<ol style=\"padding-left:1.2em;margin:6px 0\">{_items}</ol>'),\n",
    "    position=\"sidebar\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-3",
   "metadata": {},
   "source": [
    "<a id=\"section-1\"></a>\n",
    "## 1 · The Forrester composite (verbatim anchor 🟢)\n",
    "\n",
    "Forrester's composite organization: a **global supply company with $2.5B\n",
    "revenue and 10,000 employees**, running **600 CX agents** (400 concurrent\n",
    "licenses) who handle **80,000 weekly interactions** averaging 12 minutes.\n",
    "\n",
    "TEI methodology, carried verbatim into the engine: benefits are\n",
    "risk-adjusted **down** (×(1−rf)), costs **up** (×(1+rf)); the initial\n",
    "investment sits at time 0 undiscounted; year flows discount at end-of-year\n",
    "(10%, 3 years). Two source footnotes preserved in the anchor: the p.14\n",
    "Total Costs table's $1,304,600 implementation figure is a typo (the detail\n",
    "table and cash-flow analysis give $1,309,000), and the study models **$0\n",
    "AI-token consumption** (§4).\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "cell-4",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-09T19:15:19.100446Z",
     "iopub.status.busy": "2026-07-09T19:15:19.100167Z",
     "iopub.status.idle": "2026-07-09T19:15:19.119649Z",
     "shell.execute_reply": "2026-07-09T19:15:19.118932Z"
    }
   },
   "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>Value 🟢</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Assumption</th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>annual revenue</th>\n",
       "      <td>$2,500,000,000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>employees</th>\n",
       "      <td>10,000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>agents fte</th>\n",
       "      <td>600</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>concurrent licenses</th>\n",
       "      <td>400</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>weekly interactions</th>\n",
       "      <td>80,000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>interaction minutes</th>\n",
       "      <td>12 min</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>self service rate before</th>\n",
       "      <td>15%</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>self service rate after</th>\n",
       "      <td>25%</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>mttr saved minutes</th>\n",
       "      <td>2 min</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>agent hourly rate</th>\n",
       "      <td>$28/hr</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>agent annual salary</th>\n",
       "      <td>$58,240</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>revenue impacted</th>\n",
       "      <td>$500,000,000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>revenue lift</th>\n",
       "      <td>1.5%</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>gross margin</th>\n",
       "      <td>8%</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>discount rate</th>\n",
       "      <td>10%</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>analysis years</th>\n",
       "      <td>3 years</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                 Value 🟢\n",
       "Assumption                              \n",
       "annual revenue            $2,500,000,000\n",
       "employees                         10,000\n",
       "agents fte                           600\n",
       "concurrent licenses                  400\n",
       "weekly interactions               80,000\n",
       "interaction minutes               12 min\n",
       "self service rate before             15%\n",
       "self service rate after              25%\n",
       "mttr saved minutes                 2 min\n",
       "agent hourly rate                 $28/hr\n",
       "agent annual salary              $58,240\n",
       "revenue impacted            $500,000,000\n",
       "revenue lift                        1.5%\n",
       "gross margin                          8%\n",
       "discount rate                        10%\n",
       "analysis years                   3 years"
      ]
     },
     "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>Published 🟢</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Metric</th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>Benefits PV (risk-adjusted)</th>\n",
       "      <td>$14,840,638</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Costs PV (risk-adjusted)</th>\n",
       "      <td>$4,057,170</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>NPV</th>\n",
       "      <td>$10,783,468</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>ROI</th>\n",
       "      <td>266%</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Payback</th>\n",
       "      <td>not headlined (engine: 3.3 months)</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                                    Published 🟢\n",
       "Metric                                                         \n",
       "Benefits PV (risk-adjusted)                         $14,840,638\n",
       "Costs PV (risk-adjusted)                             $4,057,170\n",
       "NPV                                                 $10,783,468\n",
       "ROI                                                        266%\n",
       "Payback                      not headlined (engine: 3.3 months)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "engine reproduction Δ vs PDF: benefits -1.19 · costs +0.40 · npv -1.58\n"
     ]
    }
   ],
   "source": [
    "# ── Composite assumptions & published financial summary (🟢) ─────────\n",
    "composite = compute_summary(BENEFITS_VERBATIM, COSTS_VERBATIM,\n",
    "                            PUBLISHED[\"discount_rate\"])\n",
    "\n",
    "_fmt = {\n",
    "    \"annual_revenue\": \"${:,}\", \"employees\": \"{:,}\", \"agents_fte\": \"{:,}\",\n",
    "    \"concurrent_licenses\": \"{:,}\", \"weekly_interactions\": \"{:,}\",\n",
    "    \"interaction_minutes\": \"{} min\", \"self_service_rate_before\": \"{:.0%}\",\n",
    "    \"self_service_rate_after\": \"{:.0%}\", \"mttr_saved_minutes\": \"{} min\",\n",
    "    \"agent_hourly_rate\": \"${}/hr\", \"agent_annual_salary\": \"${:,}\",\n",
    "    \"revenue_impacted\": \"${:,}\", \"revenue_lift\": \"{:.1%}\",\n",
    "    \"gross_margin\": \"{:.0%}\", \"discount_rate\": \"{:.0%}\",\n",
    "    \"analysis_years\": \"{} years\",\n",
    "}\n",
    "assumptions_df = pd.DataFrame(\n",
    "    [{\"Assumption\": k.replace(\"_\", \" \"), \"Value 🟢\": _fmt[k].format(v)}\n",
    "     for k, v in ASSUMPTIONS.items()])\n",
    "display(assumptions_df.set_index(\"Assumption\"))\n",
    "\n",
    "published_df = pd.DataFrame([\n",
    "    {\"Metric\": \"Benefits PV (risk-adjusted)\", \"Published 🟢\": f\"${PUBLISHED['benefits_pv']:,}\"},\n",
    "    {\"Metric\": \"Costs PV (risk-adjusted)\",    \"Published 🟢\": f\"${PUBLISHED['costs_pv']:,}\"},\n",
    "    {\"Metric\": \"NPV\",                          \"Published 🟢\": f\"${PUBLISHED['npv']:,}\"},\n",
    "    {\"Metric\": \"ROI\",                          \"Published 🟢\": f\"{PUBLISHED['roi_pct']}%\"},\n",
    "    {\"Metric\": \"Payback\",                      \"Published 🟢\": \"not headlined (engine: 3.3 months)\"},\n",
    "])\n",
    "display(published_df.set_index(\"Metric\"))\n",
    "\n",
    "backstage(f\"engine reproduction Δ vs PDF: \"\n",
    "          f\"benefits {composite['benefits_pv'] - PUBLISHED['benefits_pv']:+,.2f} · \"\n",
    "          f\"costs {composite['costs_pv'] - PUBLISHED['costs_pv']:+,.2f} · \"\n",
    "          f\"npv {composite['npv'] - PUBLISHED['npv']:+,.2f}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-5",
   "metadata": {},
   "source": [
    "<a id=\"section-2\"></a>\n",
    "## 2 · Client inputs (overlay 🟡)\n",
    "\n",
    "The overlay is a **first-order linear rescale** of Forrester's composite —\n",
    "it answers *\"what does the composite look like at your size?\"*, not *\"what\n",
    "is your TEI?\"*. Each published row scales with the driver that dominates\n",
    "its derivation in the PDF; project-based costs stay fixed. This composite's\n",
    "trajectory is flat (Y2 = Y3), so there is no growth re-base.\n",
    "\n",
    "| Published row | Scales with | Confidence |\n",
    "|---|---|---|\n",
    "| Retirement of legacy systems | agents | 🟡 seat-scoped platform costs |\n",
    "| Self-service savings | interactions | 🟡 |\n",
    "| CX agent efficiency gains | interactions | 🟡 |\n",
    "| Incremental sales from agent assist | revenue | 🟡 |\n",
    "| CX Cloud licenses | agents | 🟡 |\n",
    "| Implementation & deployment | fixed | 🟡 project-based |\n",
    "| Ongoing management | fixed | 🟡 |\n",
    "| **Genesys AI Experience tokens** | **direct $/yr input** | 🔴 $0 until quoted |\n",
    "\n",
    "*Change any input in the sidebar — every table, figure and KPI below\n",
    "recomputes. The assertions in §7 hold at any setting.*\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "cell-6",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-09T19:15:19.121610Z",
     "iopub.status.busy": "2026-07-09T19:15:19.121442Z",
     "iopub.status.idle": "2026-07-09T19:15:19.139328Z",
     "shell.execute_reply": "2026-07-09T19:15:19.138672Z"
    }
   },
   "outputs": [
    {
     "data": {
      "application/mercury+json": {
       "model_id": "b2751bf42368472ebf73f08317e9cde9",
       "position": "sidebar",
       "widget": "NumberInputWidget"
      },
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "b2751bf42368472ebf73f08317e9cde9",
       "version_major": 2,
       "version_minor": 1
      },
      "text/plain": [
       "<mercury.number.NumberInputWidget object at 0x7888c41e12b0>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/mercury+json": {
       "model_id": "2247afa5bfb04ad8905333e3f64caf67",
       "position": "sidebar",
       "widget": "NumberInputWidget"
      },
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "2247afa5bfb04ad8905333e3f64caf67",
       "version_major": 2,
       "version_minor": 1
      },
      "text/plain": [
       "<mercury.number.NumberInputWidget object at 0x7888c41d9310>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/mercury+json": {
       "model_id": "55da27f443ca4021895933ad5d96f23b",
       "position": "sidebar",
       "widget": "NumberInputWidget"
      },
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "55da27f443ca4021895933ad5d96f23b",
       "version_major": 2,
       "version_minor": 1
      },
      "text/plain": [
       "<mercury.number.NumberInputWidget object at 0x7888c41d8f50>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/mercury+json": {
       "model_id": "9173bebf5a34432e8e4819f906db4add",
       "position": "sidebar",
       "widget": "NumberInputWidget"
      },
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "9173bebf5a34432e8e4819f906db4add",
       "version_major": 2,
       "version_minor": 1
      },
      "text/plain": [
       "<mercury.number.NumberInputWidget object at 0x78886eafd940>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/mercury+json": {
       "model_id": "76c10bd18e9c466daec54721bc92b435",
       "position": "sidebar",
       "widget": "SelectWidget"
      },
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "76c10bd18e9c466daec54721bc92b435",
       "version_major": 2,
       "version_minor": 1
      },
      "text/plain": [
       "<mercury.select.SelectWidget object at 0x7888c41e16a0>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/mercury+json": {
       "model_id": "ce038c3719d240a1a635401ac4a6208e",
       "position": "sidebar",
       "widget": "SelectWidget"
      },
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "ce038c3719d240a1a635401ac4a6208e",
       "version_major": 2,
       "version_minor": 1
      },
      "text/plain": [
       "<mercury.select.SelectWidget object at 0x7888c41d9950>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# ── Client drivers (Mercury sidebar — widgets only, NO other output) ─\n",
    "# NB: Mercury re-executes only cells BELOW a changed widget's cell, so\n",
    "# this cell constructs widgets ONLY — .value is read downstream.\n",
    "_agents_w = mr.NumberInput(label=\"CX agents (FTE) — composite 600\",\n",
    "                           value=600, min=25, max=20_000, step=25)\n",
    "_interactions_w = mr.NumberInput(label=\"Weekly interactions — composite 80,000\",\n",
    "                                 value=80_000, min=1_000, max=2_000_000,\n",
    "                                 step=5_000)\n",
    "_revenue_w = mr.NumberInput(label=\"Annual revenue ($) — composite 2.5B\",\n",
    "                            value=2_500_000_000, min=100_000_000,\n",
    "                            max=100_000_000_000, step=100_000_000)\n",
    "_tokens_w = mr.NumberInput(label=\"Genesys AI tokens ($/yr) — study modeled $0\",\n",
    "                           value=0, min=0, max=5_000_000, step=25_000)\n",
    "_discount_w = mr.Select(label=\"Discount rate\", value=\"10% (Forrester)\",\n",
    "                        choices=[\"8%\", \"10% (Forrester)\", \"12%\"])\n",
    "_scenario_w = mr.Select(label=\"Scenario\", value=\"moderate\",\n",
    "                        choices=[\"conservative\", \"moderate\", \"aggressive\"])\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "cell-7",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-09T19:15:19.141205Z",
     "iopub.status.busy": "2026-07-09T19:15:19.141026Z",
     "iopub.status.idle": "2026-07-09T19:15:19.147273Z",
     "shell.execute_reply": "2026-07-09T19:15:19.146674Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Client frame: 600 agents · 80,000 weekly interactions · moderate scenario → NPV $10.8M · ROI 266% · payback 3.3 months (~Apr 2026)\n",
      "scale factors — agents 1.00× · interactions 1.00× · revenue 1.00×\n"
     ]
    }
   ],
   "source": [
    "# ── Client overlay state (re-runs on any change to the widgets above) ─\n",
    "AGENTS_FTE = int(_agents_w.value)\n",
    "WEEKLY_INTERACTIONS = int(_interactions_w.value)\n",
    "ANNUAL_REVENUE = float(_revenue_w.value)\n",
    "AI_TOKENS_ANNUAL = float(_tokens_w.value)\n",
    "DISCOUNT_RATE = {\"8%\": 0.08, \"10% (Forrester)\": 0.10,\n",
    "                 \"12%\": 0.12}[str(_discount_w.value)]\n",
    "SCENARIO = str(_scenario_w.value)\n",
    "\n",
    "DRIVERS = ClientDrivers(agents_fte=AGENTS_FTE,\n",
    "                        weekly_interactions=WEEKLY_INTERACTIONS,\n",
    "                        annual_revenue=ANNUAL_REVENUE,\n",
    "                        ai_tokens_annual=AI_TOKENS_ANNUAL,\n",
    "                        discount_rate=DISCOUNT_RATE)\n",
    "overlay_benefits, overlay_costs = overlay_rows(DRIVERS)\n",
    "client_benefits = apply_scenario(overlay_benefits, SCENARIO)\n",
    "client_costs = apply_scenario(overlay_costs, SCENARIO)\n",
    "client = compute_summary(client_benefits, client_costs, DISCOUNT_RATE)\n",
    "\n",
    "_at_default = (DRIVERS == COMPOSITE and SCENARIO == \"moderate\")\n",
    "\n",
    "_tok = f\" · AI tokens {money(AI_TOKENS_ANNUAL)}/yr\" if AI_TOKENS_ANNUAL else \"\"\n",
    "print(f\"Client frame: {AGENTS_FTE:,} agents · {WEEKLY_INTERACTIONS:,} weekly \"\n",
    "      f\"interactions{_tok} · {SCENARIO} scenario → NPV {money(client['npv'])} · \"\n",
    "      f\"ROI {client['roi_pct']:.0f}% · payback {client['payback_label']}\")\n",
    "backstage(f\"scale factors — agents {AGENTS_FTE / ASSUMPTIONS['agents_fte']:.2f}× · \"\n",
    "          f\"interactions {WEEKLY_INTERACTIONS / ASSUMPTIONS['weekly_interactions']:.2f}× · \"\n",
    "          f\"revenue {ANNUAL_REVENUE / ASSUMPTIONS['annual_revenue']:.2f}×\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-8",
   "metadata": {},
   "source": [
    "<a id=\"section-3\"></a>\n",
    "## 3 · Benefits\n",
    "\n",
    "Four benefit streams (Forrester refs A–D), risk-adjusted down 5–15%.\n",
    "Agent efficiency and self-service deflection carry ~three-quarters of the\n",
    "total; legacy retirement ramps as the old telephony sunsets mid-year-1;\n",
    "the profile is otherwise flat.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "cell-9",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-09T19:15:19.149355Z",
     "iopub.status.busy": "2026-07-09T19:15:19.149198Z",
     "iopub.status.idle": "2026-07-09T19:15:19.161479Z",
     "shell.execute_reply": "2026-07-09T19:15:19.160698Z"
    }
   },
   "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>Driver</th>\n",
       "      <th>Risk adj</th>\n",
       "      <th>2026</th>\n",
       "      <th>2027</th>\n",
       "      <th>2028</th>\n",
       "      <th>3-yr RA</th>\n",
       "      <th>PV</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Benefit</th>\n",
       "      <th></th>\n",
       "      <th></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>Retirement of legacy systems with CX Cloud adoption</th>\n",
       "      <td>agents</td>\n",
       "      <td>-5%</td>\n",
       "      <td>646,000</td>\n",
       "      <td>883,500</td>\n",
       "      <td>883,500</td>\n",
       "      <td>2,413,000</td>\n",
       "      <td>1,981,225</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Cost savings from reallocated workers and avoided seasonal hires with increased customer self-service</th>\n",
       "      <td>interactions</td>\n",
       "      <td>-15%</td>\n",
       "      <td>1,980,160</td>\n",
       "      <td>1,980,160</td>\n",
       "      <td>1,980,160</td>\n",
       "      <td>5,940,480</td>\n",
       "      <td>4,924,365</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>CX agent efficiency gains</th>\n",
       "      <td>interactions</td>\n",
       "      <td>-10%</td>\n",
       "      <td>2,620,800</td>\n",
       "      <td>2,620,800</td>\n",
       "      <td>2,620,800</td>\n",
       "      <td>7,862,400</td>\n",
       "      <td>6,517,542</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Incremental sales from agent assist capabilities</th>\n",
       "      <td>revenue</td>\n",
       "      <td>-5%</td>\n",
       "      <td>570,000</td>\n",
       "      <td>570,000</td>\n",
       "      <td>570,000</td>\n",
       "      <td>1,710,000</td>\n",
       "      <td>1,417,506</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>TOTAL</th>\n",
       "      <td></td>\n",
       "      <td></td>\n",
       "      <td>5,816,960</td>\n",
       "      <td>6,054,460</td>\n",
       "      <td>6,054,460</td>\n",
       "      <td>17,925,880</td>\n",
       "      <td>14,840,637</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                                          Driver Risk adj  \\\n",
       "Benefit                                                                     \n",
       "Retirement of legacy systems with CX Cloud adop...        agents      -5%   \n",
       "Cost savings from reallocated workers and avoid...  interactions     -15%   \n",
       "CX agent efficiency gains                           interactions     -10%   \n",
       "Incremental sales from agent assist capabilities         revenue      -5%   \n",
       "TOTAL                                                                       \n",
       "\n",
       "                                                        2026      2027  \\\n",
       "Benefit                                                                  \n",
       "Retirement of legacy systems with CX Cloud adop...   646,000   883,500   \n",
       "Cost savings from reallocated workers and avoid... 1,980,160 1,980,160   \n",
       "CX agent efficiency gains                          2,620,800 2,620,800   \n",
       "Incremental sales from agent assist capabilities     570,000   570,000   \n",
       "TOTAL                                              5,816,960 6,054,460   \n",
       "\n",
       "                                                        2028    3-yr RA  \\\n",
       "Benefit                                                                   \n",
       "Retirement of legacy systems with CX Cloud adop...   883,500  2,413,000   \n",
       "Cost savings from reallocated workers and avoid... 1,980,160  5,940,480   \n",
       "CX agent efficiency gains                          2,620,800  7,862,400   \n",
       "Incremental sales from agent assist capabilities     570,000  1,710,000   \n",
       "TOTAL                                              6,054,460 17,925,880   \n",
       "\n",
       "                                                           PV  \n",
       "Benefit                                                        \n",
       "Retirement of legacy systems with CX Cloud adop...  1,981,225  \n",
       "Cost savings from reallocated workers and avoid...  4,924,365  \n",
       "CX agent efficiency gains                           6,517,542  \n",
       "Incremental sales from agent assist capabilities    1,417,506  \n",
       "TOTAL                                              14,840,637  "
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# ── Benefits table — client overlay, risk-adjusted ───────────────────\n",
    "_ben_rows = client[\"rows\"][\"benefits\"]\n",
    "benefits_df = pd.DataFrame([{\n",
    "    \"Benefit\": r[\"label\"],\n",
    "    \"Driver\": BENEFIT_DRIVERS[r[\"field_key\"]],\n",
    "    \"Risk adj\": f\"-{r['risk_adjustment']:.0%}\",\n",
    "    **{str(y): r[\"ra_by_year\"][y] for y in YEARS},\n",
    "    \"3-yr RA\": r[\"three_yr_ra\"],\n",
    "    \"PV\": r[\"pv\"],\n",
    "} for r in _ben_rows]).set_index(\"Benefit\")\n",
    "benefits_df.loc[\"TOTAL\"] = [\"\", \"\"] + [client[\"benefits_by_year\"][y] for y in YEARS] \\\n",
    "    + [sum(r[\"three_yr_ra\"] for r in _ben_rows), client[\"benefits_pv\"]]\n",
    "if not _at_default:      # published composite beside the overlay for reference\n",
    "    benefits_df[\"Composite PV 🟢\"] = \\\n",
    "        [r[\"pv\"] for r in composite[\"rows\"][\"benefits\"]] + [composite[\"benefits_pv\"]]\n",
    "display(benefits_df)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "cell-10",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-09T19:15:19.163518Z",
     "iopub.status.busy": "2026-07-09T19:15:19.163257Z",
     "iopub.status.idle": "2026-07-09T19:15:20.430977Z",
     "shell.execute_reply": "2026-07-09T19:15:20.429934Z"
    }
   },
   "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": "Retirement of legacy systems with CX Cloud adoption",
         "type": "bar",
         "x": [
          "2026",
          "2027",
          "2028"
         ],
         "y": [
          646000.0,
          883500.0,
          883500.0
         ]
        },
        {
         "hovertemplate": "%{fullData.name}: %{y:$,.0f}<extra></extra>",
         "marker": {
          "color": "#1baf7a",
          "line": {
           "color": "#fcfcfb",
           "width": 2
          }
         },
         "name": "Cost savings from reallocated workers and avoided seasonal hires with increased customer self-service",
         "type": "bar",
         "x": [
          "2026",
          "2027",
          "2028"
         ],
         "y": [
          1980160.0,
          1980160.0,
          1980160.0
         ]
        },
        {
         "hovertemplate": "%{fullData.name}: %{y:$,.0f}<extra></extra>",
         "marker": {
          "color": "#4a3aa7",
          "line": {
           "color": "#fcfcfb",
           "width": 2
          }
         },
         "name": "CX agent efficiency gains",
         "type": "bar",
         "x": [
          "2026",
          "2027",
          "2028"
         ],
         "y": [
          2620800.0,
          2620800.0,
          2620800.0
         ]
        },
        {
         "hovertemplate": "%{fullData.name}: %{y:$,.0f}<extra></extra>",
         "marker": {
          "color": "#eda100",
          "line": {
           "color": "#fcfcfb",
           "width": 2
          }
         },
         "name": "Incremental sales from agent assist capabilities",
         "type": "bar",
         "x": [
          "2026",
          "2027",
          "2028"
         ],
         "y": [
          570000.0,
          570000.0,
          570000.0
         ]
        }
       ],
       "layout": {
        "bargap": 0.45,
        "barmode": "stack",
        "font": {
         "color": "#52514e",
         "family": "system-ui, -apple-system, \"Segoe UI\", sans-serif",
         "size": 12
        },
        "height": 480,
        "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>Benefits by year (risk-adjusted)</b><br><span style='font-size:12px;color:#898781'>stacked by benefit stream — client overlay</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",
    "_x = [str(y) for y in YEARS]\n",
    "for r in client[\"rows\"][\"benefits\"]:\n",
    "    fig.add_trace(bar(_x, [r[\"ra_by_year\"][y] for y in YEARS],\n",
    "                      r[\"label\"], BENEFIT_COLOR[r[\"field_key\"]]))\n",
    "if not _at_default:      # composite yearly total as de-emphasized context\n",
    "    fig.add_trace(cum_line(_x, [composite[\"benefits_by_year\"][y] for y in YEARS],\n",
    "                           \"Forrester composite total\", CONTEXT, dash=\"dot\"))\n",
    "fig.update_layout(barmode=\"stack\")\n",
    "tei_layout(fig, \"Benefits by year (risk-adjusted)\",\n",
    "           subtitle=\"stacked by benefit stream — client overlay\", height=480)\n",
    "fig.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-11",
   "metadata": {},
   "source": [
    "<a id=\"section-4\"></a>\n",
    "## 4 · Costs — and the $0 AI line (🔴)\n",
    "\n",
    "Three published cost lines, risk-adjusted **up** 5–10%: seat licenses,\n",
    "a 10-week implementation (the only time-0 outlay: $1.19M nominal → $1.31M\n",
    "risk-adjusted), and a small ops team.\n",
    "\n",
    "The fourth line is the one the study *doesn't* price: **Genesys AI\n",
    "Experience token consumption**. Forrester models it at $0, yet the\n",
    "self-service uplift (B), agent efficiency (C), and agent assist (D)\n",
    "benefits all run on AI capabilities that Genesys bills via tokens. The\n",
    "anchor keeps the $0 verbatim so the reproduction matches the PDF; the\n",
    "sidebar's token input prices it for a real client from the Genesys quote.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "cell-12",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-09T19:15:20.435367Z",
     "iopub.status.busy": "2026-07-09T19:15:20.435032Z",
     "iopub.status.idle": "2026-07-09T19:15:20.449985Z",
     "shell.execute_reply": "2026-07-09T19:15:20.449281Z"
    }
   },
   "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>Driver</th>\n",
       "      <th>Risk adj</th>\n",
       "      <th>Initial</th>\n",
       "      <th>2026</th>\n",
       "      <th>2027</th>\n",
       "      <th>2028</th>\n",
       "      <th>PV</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Cost</th>\n",
       "      <th></th>\n",
       "      <th></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>CX Cloud solution costs (licenses)</th>\n",
       "      <td>agents</td>\n",
       "      <td>+5%</td>\n",
       "      <td>0</td>\n",
       "      <td>882,000</td>\n",
       "      <td>882,000</td>\n",
       "      <td>882,000</td>\n",
       "      <td>2,193,403</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Implementation and deployment cost</th>\n",
       "      <td>fixed</td>\n",
       "      <td>+10%</td>\n",
       "      <td>1,309,000</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1,309,000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Ongoing management costs</th>\n",
       "      <td>fixed</td>\n",
       "      <td>+10%</td>\n",
       "      <td>0</td>\n",
       "      <td>223,080</td>\n",
       "      <td>223,080</td>\n",
       "      <td>223,080</td>\n",
       "      <td>554,767</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Genesys AI Experience token consumption</th>\n",
       "      <td>ai_tokens</td>\n",
       "      <td>+0%</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>TOTAL</th>\n",
       "      <td></td>\n",
       "      <td></td>\n",
       "      <td>1,309,000</td>\n",
       "      <td>1,105,080</td>\n",
       "      <td>1,105,080</td>\n",
       "      <td>1,105,080</td>\n",
       "      <td>4,057,170</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                            Driver Risk adj   Initial  \\\n",
       "Cost                                                                    \n",
       "CX Cloud solution costs (licenses)          agents      +5%         0   \n",
       "Implementation and deployment cost           fixed     +10% 1,309,000   \n",
       "Ongoing management costs                     fixed     +10%         0   \n",
       "Genesys AI Experience token consumption  ai_tokens      +0%         0   \n",
       "TOTAL                                                       1,309,000   \n",
       "\n",
       "                                             2026      2027      2028  \\\n",
       "Cost                                                                    \n",
       "CX Cloud solution costs (licenses)        882,000   882,000   882,000   \n",
       "Implementation and deployment cost              0         0         0   \n",
       "Ongoing management costs                  223,080   223,080   223,080   \n",
       "Genesys AI Experience token consumption         0         0         0   \n",
       "TOTAL                                   1,105,080 1,105,080 1,105,080   \n",
       "\n",
       "                                               PV  \n",
       "Cost                                               \n",
       "CX Cloud solution costs (licenses)      2,193,403  \n",
       "Implementation and deployment cost      1,309,000  \n",
       "Ongoing management costs                  554,767  \n",
       "Genesys AI Experience token consumption         0  \n",
       "TOTAL                                   4,057,170  "
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# ── Costs table — client overlay, risk-adjusted ──────────────────────\n",
    "_cost_rows = client[\"rows\"][\"costs\"]\n",
    "costs_df = pd.DataFrame([{\n",
    "    \"Cost\": r[\"label\"],\n",
    "    \"Driver\": COST_DRIVERS[r[\"field_key\"]],\n",
    "    \"Risk adj\": f\"+{r['risk_adjustment']:.0%}\",\n",
    "    \"Initial\": r[\"initial_ra\"],\n",
    "    **{str(y): r[\"ra_by_year\"][y] for y in YEARS},\n",
    "    \"PV\": r[\"pv\"],\n",
    "} for r in _cost_rows]).set_index(\"Cost\")\n",
    "costs_df.loc[\"TOTAL\"] = [\"\", \"\", client[\"initial_costs\"]] \\\n",
    "    + [client[\"costs_by_year\"][y] for y in YEARS] + [client[\"costs_pv\"]]\n",
    "if not _at_default:\n",
    "    costs_df[\"Composite PV 🟢\"] = \\\n",
    "        [r[\"pv\"] for r in composite[\"rows\"][\"costs\"]] + [composite[\"costs_pv\"]]\n",
    "display(costs_df)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "cell-13",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-09T19:15:20.458332Z",
     "iopub.status.busy": "2026-07-09T19:15:20.458028Z",
     "iopub.status.idle": "2026-07-09T19:15:20.482508Z",
     "shell.execute_reply": "2026-07-09T19:15:20.481680Z"
    }
   },
   "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": "CX Cloud solution costs (licenses)",
         "type": "bar",
         "x": [
          "Initial",
          "2026",
          "2027",
          "2028"
         ],
         "y": [
          0.0,
          882000.0,
          882000.0,
          882000.0
         ]
        },
        {
         "hovertemplate": "%{fullData.name}: %{y:$,.0f}<extra></extra>",
         "marker": {
          "color": "#eda100",
          "line": {
           "color": "#fcfcfb",
           "width": 2
          }
         },
         "name": "Implementation and deployment cost",
         "type": "bar",
         "x": [
          "Initial",
          "2026",
          "2027",
          "2028"
         ],
         "y": [
          1309000.0,
          0.0,
          0.0,
          0.0
         ]
        },
        {
         "hovertemplate": "%{fullData.name}: %{y:$,.0f}<extra></extra>",
         "marker": {
          "color": "#4a3aa7",
          "line": {
           "color": "#fcfcfb",
           "width": 2
          }
         },
         "name": "Ongoing management costs",
         "type": "bar",
         "x": [
          "Initial",
          "2026",
          "2027",
          "2028"
         ],
         "y": [
          0.0,
          223080.00000000003,
          223080.00000000003,
          223080.00000000003
         ]
        },
        {
         "hovertemplate": "%{fullData.name}: %{y:$,.0f}<extra></extra>",
         "marker": {
          "color": "#e34948",
          "line": {
           "color": "#fcfcfb",
           "width": 2
          }
         },
         "name": "Genesys AI Experience token consumption",
         "type": "bar",
         "x": [
          "Initial",
          "2026",
          "2027",
          "2028"
         ],
         "y": [
          0.0,
          0.0,
          0.0,
          0.0
         ]
        }
       ],
       "layout": {
        "bargap": 0.45,
        "barmode": "stack",
        "font": {
         "color": "#52514e",
         "family": "system-ui, -apple-system, \"Segoe UI\", sans-serif",
         "size": 12
        },
        "height": 460,
        "hovermode": "x unified",
        "legend": {
         "font": {
          "color": "#52514e",
          "size": 11
         },
         "orientation": "h",
         "x": 0,
         "y": -0.1,
         "yanchor": "top"
        },
        "margin": {
         "b": 80,
         "l": 70,
         "r": 30,
         "t": 70
        },
        "paper_bgcolor": "#fcfcfb",
        "plot_bgcolor": "#fcfcfb",
        "template": {
         "data": {
          "bar": [
           {
            "error_x": {
             "color": "#2a3f5f"
            },
            "error_y": {
             "color": "#2a3f5f"
            },
            "marker": {
             "line": {
              "color": "#E5ECF6",
              "width": 0.5
             },
             "pattern": {
              "fillmode": "overlay",
              "size": 10,
              "solidity": 0.2
             }
            },
            "type": "bar"
           }
          ],
          "barpolar": [
           {
            "marker": {
             "line": {
              "color": "#E5ECF6",
              "width": 0.5
             },
             "pattern": {
              "fillmode": "overlay",
              "size": 10,
              "solidity": 0.2
             }
            },
            "type": "barpolar"
           }
          ],
          "carpet": [
           {
            "aaxis": {
             "endlinecolor": "#2a3f5f",
             "gridcolor": "white",
             "linecolor": "white",
             "minorgridcolor": "white",
             "startlinecolor": "#2a3f5f"
            },
            "baxis": {
             "endlinecolor": "#2a3f5f",
             "gridcolor": "white",
             "linecolor": "white",
             "minorgridcolor": "white",
             "startlinecolor": "#2a3f5f"
            },
            "type": "carpet"
           }
          ],
          "choropleth": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "type": "choropleth"
           }
          ],
          "contour": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "colorscale": [
             [
              0.0,
              "#0d0887"
             ],
             [
              0.1111111111111111,
              "#46039f"
             ],
             [
              0.2222222222222222,
              "#7201a8"
             ],
             [
              0.3333333333333333,
              "#9c179e"
             ],
             [
              0.4444444444444444,
              "#bd3786"
             ],
             [
              0.5555555555555556,
              "#d8576b"
             ],
             [
              0.6666666666666666,
              "#ed7953"
             ],
             [
              0.7777777777777778,
              "#fb9f3a"
             ],
             [
              0.8888888888888888,
              "#fdca26"
             ],
             [
              1.0,
              "#f0f921"
             ]
            ],
            "type": "contour"
           }
          ],
          "contourcarpet": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "type": "contourcarpet"
           }
          ],
          "heatmap": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "colorscale": [
             [
              0.0,
              "#0d0887"
             ],
             [
              0.1111111111111111,
              "#46039f"
             ],
             [
              0.2222222222222222,
              "#7201a8"
             ],
             [
              0.3333333333333333,
              "#9c179e"
             ],
             [
              0.4444444444444444,
              "#bd3786"
             ],
             [
              0.5555555555555556,
              "#d8576b"
             ],
             [
              0.6666666666666666,
              "#ed7953"
             ],
             [
              0.7777777777777778,
              "#fb9f3a"
             ],
             [
              0.8888888888888888,
              "#fdca26"
             ],
             [
              1.0,
              "#f0f921"
             ]
            ],
            "type": "heatmap"
           }
          ],
          "histogram": [
           {
            "marker": {
             "pattern": {
              "fillmode": "overlay",
              "size": 10,
              "solidity": 0.2
             }
            },
            "type": "histogram"
           }
          ],
          "histogram2d": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "colorscale": [
             [
              0.0,
              "#0d0887"
             ],
             [
              0.1111111111111111,
              "#46039f"
             ],
             [
              0.2222222222222222,
              "#7201a8"
             ],
             [
              0.3333333333333333,
              "#9c179e"
             ],
             [
              0.4444444444444444,
              "#bd3786"
             ],
             [
              0.5555555555555556,
              "#d8576b"
             ],
             [
              0.6666666666666666,
              "#ed7953"
             ],
             [
              0.7777777777777778,
              "#fb9f3a"
             ],
             [
              0.8888888888888888,
              "#fdca26"
             ],
             [
              1.0,
              "#f0f921"
             ]
            ],
            "type": "histogram2d"
           }
          ],
          "histogram2dcontour": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "colorscale": [
             [
              0.0,
              "#0d0887"
             ],
             [
              0.1111111111111111,
              "#46039f"
             ],
             [
              0.2222222222222222,
              "#7201a8"
             ],
             [
              0.3333333333333333,
              "#9c179e"
             ],
             [
              0.4444444444444444,
              "#bd3786"
             ],
             [
              0.5555555555555556,
              "#d8576b"
             ],
             [
              0.6666666666666666,
              "#ed7953"
             ],
             [
              0.7777777777777778,
              "#fb9f3a"
             ],
             [
              0.8888888888888888,
              "#fdca26"
             ],
             [
              1.0,
              "#f0f921"
             ]
            ],
            "type": "histogram2dcontour"
           }
          ],
          "mesh3d": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "type": "mesh3d"
           }
          ],
          "parcoords": [
           {
            "line": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "parcoords"
           }
          ],
          "pie": [
           {
            "automargin": true,
            "type": "pie"
           }
          ],
          "scatter": [
           {
            "fillpattern": {
             "fillmode": "overlay",
             "size": 10,
             "solidity": 0.2
            },
            "type": "scatter"
           }
          ],
          "scatter3d": [
           {
            "line": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scatter3d"
           }
          ],
          "scattercarpet": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scattercarpet"
           }
          ],
          "scattergeo": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scattergeo"
           }
          ],
          "scattergl": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scattergl"
           }
          ],
          "scattermap": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scattermap"
           }
          ],
          "scattermapbox": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scattermapbox"
           }
          ],
          "scatterpolar": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scatterpolar"
           }
          ],
          "scatterpolargl": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scatterpolargl"
           }
          ],
          "scatterternary": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scatterternary"
           }
          ],
          "surface": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "colorscale": [
             [
              0.0,
              "#0d0887"
             ],
             [
              0.1111111111111111,
              "#46039f"
             ],
             [
              0.2222222222222222,
              "#7201a8"
             ],
             [
              0.3333333333333333,
              "#9c179e"
             ],
             [
              0.4444444444444444,
              "#bd3786"
             ],
             [
              0.5555555555555556,
              "#d8576b"
             ],
             [
              0.6666666666666666,
              "#ed7953"
             ],
             [
              0.7777777777777778,
              "#fb9f3a"
             ],
             [
              0.8888888888888888,
              "#fdca26"
             ],
             [
              1.0,
              "#f0f921"
             ]
            ],
            "type": "surface"
           }
          ],
          "table": [
           {
            "cells": {
             "fill": {
              "color": "#EBF0F8"
             },
             "line": {
              "color": "white"
             }
            },
            "header": {
             "fill": {
              "color": "#C8D4E3"
             },
             "line": {
              "color": "white"
             }
            },
            "type": "table"
           }
          ]
         },
         "layout": {
          "annotationdefaults": {
           "arrowcolor": "#2a3f5f",
           "arrowhead": 0,
           "arrowwidth": 1
          },
          "autotypenumbers": "strict",
          "coloraxis": {
           "colorbar": {
            "outlinewidth": 0,
            "ticks": ""
           }
          },
          "colorscale": {
           "diverging": [
            [
             0,
             "#8e0152"
            ],
            [
             0.1,
             "#c51b7d"
            ],
            [
             0.2,
             "#de77ae"
            ],
            [
             0.3,
             "#f1b6da"
            ],
            [
             0.4,
             "#fde0ef"
            ],
            [
             0.5,
             "#f7f7f7"
            ],
            [
             0.6,
             "#e6f5d0"
            ],
            [
             0.7,
             "#b8e186"
            ],
            [
             0.8,
             "#7fbc41"
            ],
            [
             0.9,
             "#4d9221"
            ],
            [
             1,
             "#276419"
            ]
           ],
           "sequential": [
            [
             0.0,
             "#0d0887"
            ],
            [
             0.1111111111111111,
             "#46039f"
            ],
            [
             0.2222222222222222,
             "#7201a8"
            ],
            [
             0.3333333333333333,
             "#9c179e"
            ],
            [
             0.4444444444444444,
             "#bd3786"
            ],
            [
             0.5555555555555556,
             "#d8576b"
            ],
            [
             0.6666666666666666,
             "#ed7953"
            ],
            [
             0.7777777777777778,
             "#fb9f3a"
            ],
            [
             0.8888888888888888,
             "#fdca26"
            ],
            [
             1.0,
             "#f0f921"
            ]
           ],
           "sequentialminus": [
            [
             0.0,
             "#0d0887"
            ],
            [
             0.1111111111111111,
             "#46039f"
            ],
            [
             0.2222222222222222,
             "#7201a8"
            ],
            [
             0.3333333333333333,
             "#9c179e"
            ],
            [
             0.4444444444444444,
             "#bd3786"
            ],
            [
             0.5555555555555556,
             "#d8576b"
            ],
            [
             0.6666666666666666,
             "#ed7953"
            ],
            [
             0.7777777777777778,
             "#fb9f3a"
            ],
            [
             0.8888888888888888,
             "#fdca26"
            ],
            [
             1.0,
             "#f0f921"
            ]
           ]
          },
          "colorway": [
           "#636efa",
           "#EF553B",
           "#00cc96",
           "#ab63fa",
           "#FFA15A",
           "#19d3f3",
           "#FF6692",
           "#B6E880",
           "#FF97FF",
           "#FECB52"
          ],
          "font": {
           "color": "#2a3f5f"
          },
          "geo": {
           "bgcolor": "white",
           "lakecolor": "white",
           "landcolor": "#E5ECF6",
           "showlakes": true,
           "showland": true,
           "subunitcolor": "white"
          },
          "hoverlabel": {
           "align": "left"
          },
          "hovermode": "closest",
          "mapbox": {
           "style": "light"
          },
          "paper_bgcolor": "white",
          "plot_bgcolor": "#E5ECF6",
          "polar": {
           "angularaxis": {
            "gridcolor": "white",
            "linecolor": "white",
            "ticks": ""
           },
           "bgcolor": "#E5ECF6",
           "radialaxis": {
            "gridcolor": "white",
            "linecolor": "white",
            "ticks": ""
           }
          },
          "scene": {
           "xaxis": {
            "backgroundcolor": "#E5ECF6",
            "gridcolor": "white",
            "gridwidth": 2,
            "linecolor": "white",
            "showbackground": true,
            "ticks": "",
            "zerolinecolor": "white"
           },
           "yaxis": {
            "backgroundcolor": "#E5ECF6",
            "gridcolor": "white",
            "gridwidth": 2,
            "linecolor": "white",
            "showbackground": true,
            "ticks": "",
            "zerolinecolor": "white"
           },
           "zaxis": {
            "backgroundcolor": "#E5ECF6",
            "gridcolor": "white",
            "gridwidth": 2,
            "linecolor": "white",
            "showbackground": true,
            "ticks": "",
            "zerolinecolor": "white"
           }
          },
          "shapedefaults": {
           "line": {
            "color": "#2a3f5f"
           }
          },
          "ternary": {
           "aaxis": {
            "gridcolor": "white",
            "linecolor": "white",
            "ticks": ""
           },
           "baxis": {
            "gridcolor": "white",
            "linecolor": "white",
            "ticks": ""
           },
           "bgcolor": "#E5ECF6",
           "caxis": {
            "gridcolor": "white",
            "linecolor": "white",
            "ticks": ""
           }
          },
          "title": {
           "x": 0.05
          },
          "xaxis": {
           "automargin": true,
           "gridcolor": "white",
           "linecolor": "white",
           "ticks": "",
           "title": {
            "standoff": 15
           },
           "zerolinecolor": "white",
           "zerolinewidth": 2
          },
          "yaxis": {
           "automargin": true,
           "gridcolor": "white",
           "linecolor": "white",
           "ticks": "",
           "title": {
            "standoff": 15
           },
           "zerolinecolor": "white",
           "zerolinewidth": 2
          }
         }
        },
        "title": {
         "font": {
          "color": "#0b0b0b",
          "size": 16
         },
         "text": "<b>Costs by year (risk-adjusted)</b><br><span style='font-size:12px;color:#898781'>Initial = undiscounted time-0 outlay; the red AI-token segment is $0 until priced</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",
    "for r in client[\"rows\"][\"costs\"]:\n",
    "    fig.add_trace(bar(X_LABELS,\n",
    "                      [r[\"initial_ra\"]] + [r[\"ra_by_year\"][y] for y in YEARS],\n",
    "                      r[\"label\"], COST_COLOR[r[\"field_key\"]]))\n",
    "fig.update_layout(barmode=\"stack\")\n",
    "tei_layout(fig, \"Costs by year (risk-adjusted)\",\n",
    "           subtitle=\"Initial = undiscounted time-0 outlay; the red AI-token \"\n",
    "                    \"segment is $0 until priced\")\n",
    "fig.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-14",
   "metadata": {},
   "source": [
    "<a id=\"section-5\"></a>\n",
    "## 5 · Business case\n",
    "\n",
    "The left column is Forrester's published Financial Summary, verbatim. The\n",
    "right column is the client overlay at the sidebar's drivers. At the\n",
    "defaults the engine reproduces the published totals to within $2 — the\n",
    "gate in §7 enforces it. Forrester does not headline a payback for this\n",
    "study; the engine computes **3.3 months** from the cash-flow table\n",
    "($1.31M initial vs ~$4.7M year-1 net).\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "cell-15",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-09T19:15:20.484624Z",
     "iopub.status.busy": "2026-07-09T19:15:20.484408Z",
     "iopub.status.idle": "2026-07-09T19:15:20.493816Z",
     "shell.execute_reply": "2026-07-09T19:15:20.493019Z"
    }
   },
   "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>Forrester composite (published 🟢)</th>\n",
       "      <th>Client overlay (🟡)</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>Benefits PV</th>\n",
       "      <td>$14,840,638</td>\n",
       "      <td>$14,840,637</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Costs PV</th>\n",
       "      <td>$4,057,170</td>\n",
       "      <td>$4,057,170</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>NPV</th>\n",
       "      <td>$10,783,468</td>\n",
       "      <td>$10,783,466</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>ROI</th>\n",
       "      <td>266%</td>\n",
       "      <td>266%</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Payback</th>\n",
       "      <td>not headlined</td>\n",
       "      <td>3.3 months (~Apr 2026)</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Discount rate</th>\n",
       "      <td>10%</td>\n",
       "      <td>10%</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "              Forrester composite (published 🟢)      Client overlay (🟡)\n",
       "Benefits PV                         $14,840,638             $14,840,637\n",
       "Costs PV                             $4,057,170              $4,057,170\n",
       "NPV                                 $10,783,468             $10,783,466\n",
       "ROI                                        266%                    266%\n",
       "Payback                           not headlined  3.3 months (~Apr 2026)\n",
       "Discount rate                               10%                     10%"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "engine composite NPV $10,783,466.42 vs published $10,783,468 (Δ -1.58)\n"
     ]
    }
   ],
   "source": [
    "# ── KPIs — published composite beside the client overlay ─────────────\n",
    "kpis_fmt = pd.DataFrame({\n",
    "    \"Forrester composite (published 🟢)\": {\n",
    "        \"Benefits PV\": f\"${PUBLISHED['benefits_pv']:,}\",\n",
    "        \"Costs PV\": f\"${PUBLISHED['costs_pv']:,}\",\n",
    "        \"NPV\": f\"${PUBLISHED['npv']:,}\",\n",
    "        \"ROI\": f\"{PUBLISHED['roi_pct']}%\",\n",
    "        \"Payback\": \"not headlined\",\n",
    "        \"Discount rate\": f\"{PUBLISHED['discount_rate']:.0%}\",\n",
    "    },\n",
    "    \"Client overlay (🟡)\": {\n",
    "        \"Benefits PV\": f\"${client['benefits_pv']:,.0f}\",\n",
    "        \"Costs PV\": f\"${client['costs_pv']:,.0f}\",\n",
    "        \"NPV\": f\"${client['npv']:,.0f}\",\n",
    "        \"ROI\": f\"{client['roi_pct']:.0f}%\",\n",
    "        \"Payback\": client[\"payback_label\"],\n",
    "        \"Discount rate\": f\"{DISCOUNT_RATE:.0%}\",\n",
    "    },\n",
    "})\n",
    "display(kpis_fmt)\n",
    "backstage(f\"engine composite NPV ${composite['npv']:,.2f} vs published \"\n",
    "          f\"${PUBLISHED['npv']:,} (Δ {composite['npv'] - PUBLISHED['npv']:+,.2f})\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "cell-16",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-09T19:15:20.496223Z",
     "iopub.status.busy": "2026-07-09T19:15:20.495912Z",
     "iopub.status.idle": "2026-07-09T19:15:20.525205Z",
     "shell.execute_reply": "2026-07-09T19:15:20.524534Z"
    }
   },
   "outputs": [
    {
     "data": {
      "application/vnd.plotly.v1+json": {
       "config": {
        "plotlyServerURL": "https://plot.ly"
       },
       "data": [
        {
         "hovertemplate": "%{fullData.name}: %{y:$,.0f}<extra></extra>",
         "marker": {
          "color": "#1baf7a",
          "line": {
           "color": "#fcfcfb",
           "width": 2
          }
         },
         "name": "Benefits (risk-adjusted)",
         "type": "bar",
         "x": [
          "Initial",
          "2026",
          "2027",
          "2028"
         ],
         "y": [
          0,
          5816960.0,
          6054460.0,
          6054460.0
         ]
        },
        {
         "hovertemplate": "%{fullData.name}: %{y:$,.0f}<extra></extra>",
         "marker": {
          "color": "#e34948",
          "line": {
           "color": "#fcfcfb",
           "width": 2
          }
         },
         "name": "Costs (risk-adjusted)",
         "type": "bar",
         "x": [
          "Initial",
          "2026",
          "2027",
          "2028"
         ],
         "y": [
          -1309000.0,
          -1105080.0,
          -1105080.0,
          -1105080.0
         ]
        },
        {
         "hovertemplate": "%{fullData.name}: %{y:$,.0f}<extra></extra>",
         "line": {
          "color": "#52514e",
          "width": 2
         },
         "marker": {
          "line": {
           "color": "#fcfcfb",
           "width": 2
          },
          "size": 8
         },
         "mode": "lines+markers",
         "name": "Cumulative net",
         "type": "scatter",
         "x": [
          "Initial",
          "2026",
          "2027",
          "2028"
         ],
         "y": [
          -1309000.0,
          3402880.0,
          8352260.0,
          13301640.0
         ]
        }
       ],
       "layout": {
        "annotations": [
         {
          "align": "left",
          "bgcolor": "#fcfcfb",
          "bordercolor": "#e1e0d9",
          "borderwidth": 1,
          "font": {
           "color": "#0b0b0b",
           "size": 12
          },
          "showarrow": false,
          "text": "NPV &#36;10.8M · ROI 266% · payback 3.3 months (~Apr 2026)",
          "x": 0.02,
          "xref": "paper",
          "y": 0.98,
          "yref": "paper"
         }
        ],
        "bargap": 0.45,
        "barmode": "relative",
        "font": {
         "color": "#52514e",
         "family": "system-ui, -apple-system, \"Segoe UI\", sans-serif",
         "size": 12
        },
        "height": 460,
        "hovermode": "x unified",
        "legend": {
         "font": {
          "color": "#52514e",
          "size": 11
         },
         "orientation": "h",
         "x": 0,
         "y": -0.1,
         "yanchor": "top"
        },
        "margin": {
         "b": 80,
         "l": 70,
         "r": 30,
         "t": 70
        },
        "paper_bgcolor": "#fcfcfb",
        "plot_bgcolor": "#fcfcfb",
        "template": {
         "data": {
          "bar": [
           {
            "error_x": {
             "color": "#2a3f5f"
            },
            "error_y": {
             "color": "#2a3f5f"
            },
            "marker": {
             "line": {
              "color": "#E5ECF6",
              "width": 0.5
             },
             "pattern": {
              "fillmode": "overlay",
              "size": 10,
              "solidity": 0.2
             }
            },
            "type": "bar"
           }
          ],
          "barpolar": [
           {
            "marker": {
             "line": {
              "color": "#E5ECF6",
              "width": 0.5
             },
             "pattern": {
              "fillmode": "overlay",
              "size": 10,
              "solidity": 0.2
             }
            },
            "type": "barpolar"
           }
          ],
          "carpet": [
           {
            "aaxis": {
             "endlinecolor": "#2a3f5f",
             "gridcolor": "white",
             "linecolor": "white",
             "minorgridcolor": "white",
             "startlinecolor": "#2a3f5f"
            },
            "baxis": {
             "endlinecolor": "#2a3f5f",
             "gridcolor": "white",
             "linecolor": "white",
             "minorgridcolor": "white",
             "startlinecolor": "#2a3f5f"
            },
            "type": "carpet"
           }
          ],
          "choropleth": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "type": "choropleth"
           }
          ],
          "contour": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "colorscale": [
             [
              0.0,
              "#0d0887"
             ],
             [
              0.1111111111111111,
              "#46039f"
             ],
             [
              0.2222222222222222,
              "#7201a8"
             ],
             [
              0.3333333333333333,
              "#9c179e"
             ],
             [
              0.4444444444444444,
              "#bd3786"
             ],
             [
              0.5555555555555556,
              "#d8576b"
             ],
             [
              0.6666666666666666,
              "#ed7953"
             ],
             [
              0.7777777777777778,
              "#fb9f3a"
             ],
             [
              0.8888888888888888,
              "#fdca26"
             ],
             [
              1.0,
              "#f0f921"
             ]
            ],
            "type": "contour"
           }
          ],
          "contourcarpet": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "type": "contourcarpet"
           }
          ],
          "heatmap": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "colorscale": [
             [
              0.0,
              "#0d0887"
             ],
             [
              0.1111111111111111,
              "#46039f"
             ],
             [
              0.2222222222222222,
              "#7201a8"
             ],
             [
              0.3333333333333333,
              "#9c179e"
             ],
             [
              0.4444444444444444,
              "#bd3786"
             ],
             [
              0.5555555555555556,
              "#d8576b"
             ],
             [
              0.6666666666666666,
              "#ed7953"
             ],
             [
              0.7777777777777778,
              "#fb9f3a"
             ],
             [
              0.8888888888888888,
              "#fdca26"
             ],
             [
              1.0,
              "#f0f921"
             ]
            ],
            "type": "heatmap"
           }
          ],
          "histogram": [
           {
            "marker": {
             "pattern": {
              "fillmode": "overlay",
              "size": 10,
              "solidity": 0.2
             }
            },
            "type": "histogram"
           }
          ],
          "histogram2d": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "colorscale": [
             [
              0.0,
              "#0d0887"
             ],
             [
              0.1111111111111111,
              "#46039f"
             ],
             [
              0.2222222222222222,
              "#7201a8"
             ],
             [
              0.3333333333333333,
              "#9c179e"
             ],
             [
              0.4444444444444444,
              "#bd3786"
             ],
             [
              0.5555555555555556,
              "#d8576b"
             ],
             [
              0.6666666666666666,
              "#ed7953"
             ],
             [
              0.7777777777777778,
              "#fb9f3a"
             ],
             [
              0.8888888888888888,
              "#fdca26"
             ],
             [
              1.0,
              "#f0f921"
             ]
            ],
            "type": "histogram2d"
           }
          ],
          "histogram2dcontour": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "colorscale": [
             [
              0.0,
              "#0d0887"
             ],
             [
              0.1111111111111111,
              "#46039f"
             ],
             [
              0.2222222222222222,
              "#7201a8"
             ],
             [
              0.3333333333333333,
              "#9c179e"
             ],
             [
              0.4444444444444444,
              "#bd3786"
             ],
             [
              0.5555555555555556,
              "#d8576b"
             ],
             [
              0.6666666666666666,
              "#ed7953"
             ],
             [
              0.7777777777777778,
              "#fb9f3a"
             ],
             [
              0.8888888888888888,
              "#fdca26"
             ],
             [
              1.0,
              "#f0f921"
             ]
            ],
            "type": "histogram2dcontour"
           }
          ],
          "mesh3d": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "type": "mesh3d"
           }
          ],
          "parcoords": [
           {
            "line": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "parcoords"
           }
          ],
          "pie": [
           {
            "automargin": true,
            "type": "pie"
           }
          ],
          "scatter": [
           {
            "fillpattern": {
             "fillmode": "overlay",
             "size": 10,
             "solidity": 0.2
            },
            "type": "scatter"
           }
          ],
          "scatter3d": [
           {
            "line": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scatter3d"
           }
          ],
          "scattercarpet": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scattercarpet"
           }
          ],
          "scattergeo": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scattergeo"
           }
          ],
          "scattergl": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scattergl"
           }
          ],
          "scattermap": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scattermap"
           }
          ],
          "scattermapbox": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scattermapbox"
           }
          ],
          "scatterpolar": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scatterpolar"
           }
          ],
          "scatterpolargl": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scatterpolargl"
           }
          ],
          "scatterternary": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scatterternary"
           }
          ],
          "surface": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "colorscale": [
             [
              0.0,
              "#0d0887"
             ],
             [
              0.1111111111111111,
              "#46039f"
             ],
             [
              0.2222222222222222,
              "#7201a8"
             ],
             [
              0.3333333333333333,
              "#9c179e"
             ],
             [
              0.4444444444444444,
              "#bd3786"
             ],
             [
              0.5555555555555556,
              "#d8576b"
             ],
             [
              0.6666666666666666,
              "#ed7953"
             ],
             [
              0.7777777777777778,
              "#fb9f3a"
             ],
             [
              0.8888888888888888,
              "#fdca26"
             ],
             [
              1.0,
              "#f0f921"
             ]
            ],
            "type": "surface"
           }
          ],
          "table": [
           {
            "cells": {
             "fill": {
              "color": "#EBF0F8"
             },
             "line": {
              "color": "white"
             }
            },
            "header": {
             "fill": {
              "color": "#C8D4E3"
             },
             "line": {
              "color": "white"
             }
            },
            "type": "table"
           }
          ]
         },
         "layout": {
          "annotationdefaults": {
           "arrowcolor": "#2a3f5f",
           "arrowhead": 0,
           "arrowwidth": 1
          },
          "autotypenumbers": "strict",
          "coloraxis": {
           "colorbar": {
            "outlinewidth": 0,
            "ticks": ""
           }
          },
          "colorscale": {
           "diverging": [
            [
             0,
             "#8e0152"
            ],
            [
             0.1,
             "#c51b7d"
            ],
            [
             0.2,
             "#de77ae"
            ],
            [
             0.3,
             "#f1b6da"
            ],
            [
             0.4,
             "#fde0ef"
            ],
            [
             0.5,
             "#f7f7f7"
            ],
            [
             0.6,
             "#e6f5d0"
            ],
            [
             0.7,
             "#b8e186"
            ],
            [
             0.8,
             "#7fbc41"
            ],
            [
             0.9,
             "#4d9221"
            ],
            [
             1,
             "#276419"
            ]
           ],
           "sequential": [
            [
             0.0,
             "#0d0887"
            ],
            [
             0.1111111111111111,
             "#46039f"
            ],
            [
             0.2222222222222222,
             "#7201a8"
            ],
            [
             0.3333333333333333,
             "#9c179e"
            ],
            [
             0.4444444444444444,
             "#bd3786"
            ],
            [
             0.5555555555555556,
             "#d8576b"
            ],
            [
             0.6666666666666666,
             "#ed7953"
            ],
            [
             0.7777777777777778,
             "#fb9f3a"
            ],
            [
             0.8888888888888888,
             "#fdca26"
            ],
            [
             1.0,
             "#f0f921"
            ]
           ],
           "sequentialminus": [
            [
             0.0,
             "#0d0887"
            ],
            [
             0.1111111111111111,
             "#46039f"
            ],
            [
             0.2222222222222222,
             "#7201a8"
            ],
            [
             0.3333333333333333,
             "#9c179e"
            ],
            [
             0.4444444444444444,
             "#bd3786"
            ],
            [
             0.5555555555555556,
             "#d8576b"
            ],
            [
             0.6666666666666666,
             "#ed7953"
            ],
            [
             0.7777777777777778,
             "#fb9f3a"
            ],
            [
             0.8888888888888888,
             "#fdca26"
            ],
            [
             1.0,
             "#f0f921"
            ]
           ]
          },
          "colorway": [
           "#636efa",
           "#EF553B",
           "#00cc96",
           "#ab63fa",
           "#FFA15A",
           "#19d3f3",
           "#FF6692",
           "#B6E880",
           "#FF97FF",
           "#FECB52"
          ],
          "font": {
           "color": "#2a3f5f"
          },
          "geo": {
           "bgcolor": "white",
           "lakecolor": "white",
           "landcolor": "#E5ECF6",
           "showlakes": true,
           "showland": true,
           "subunitcolor": "white"
          },
          "hoverlabel": {
           "align": "left"
          },
          "hovermode": "closest",
          "mapbox": {
           "style": "light"
          },
          "paper_bgcolor": "white",
          "plot_bgcolor": "#E5ECF6",
          "polar": {
           "angularaxis": {
            "gridcolor": "white",
            "linecolor": "white",
            "ticks": ""
           },
           "bgcolor": "#E5ECF6",
           "radialaxis": {
            "gridcolor": "white",
            "linecolor": "white",
            "ticks": ""
           }
          },
          "scene": {
           "xaxis": {
            "backgroundcolor": "#E5ECF6",
            "gridcolor": "white",
            "gridwidth": 2,
            "linecolor": "white",
            "showbackground": true,
            "ticks": "",
            "zerolinecolor": "white"
           },
           "yaxis": {
            "backgroundcolor": "#E5ECF6",
            "gridcolor": "white",
            "gridwidth": 2,
            "linecolor": "white",
            "showbackground": true,
            "ticks": "",
            "zerolinecolor": "white"
           },
           "zaxis": {
            "backgroundcolor": "#E5ECF6",
            "gridcolor": "white",
            "gridwidth": 2,
            "linecolor": "white",
            "showbackground": true,
            "ticks": "",
            "zerolinecolor": "white"
           }
          },
          "shapedefaults": {
           "line": {
            "color": "#2a3f5f"
           }
          },
          "ternary": {
           "aaxis": {
            "gridcolor": "white",
            "linecolor": "white",
            "ticks": ""
           },
           "baxis": {
            "gridcolor": "white",
            "linecolor": "white",
            "ticks": ""
           },
           "bgcolor": "#E5ECF6",
           "caxis": {
            "gridcolor": "white",
            "linecolor": "white",
            "ticks": ""
           }
          },
          "title": {
           "x": 0.05
          },
          "xaxis": {
           "automargin": true,
           "gridcolor": "white",
           "linecolor": "white",
           "ticks": "",
           "title": {
            "standoff": 15
           },
           "zerolinecolor": "white",
           "zerolinewidth": 2
          },
          "yaxis": {
           "automargin": true,
           "gridcolor": "white",
           "linecolor": "white",
           "ticks": "",
           "title": {
            "standoff": 15
           },
           "zerolinecolor": "white",
           "zerolinewidth": 2
          }
         }
        },
        "title": {
         "font": {
          "color": "#0b0b0b",
          "size": 16
         },
         "text": "<b>Cash flow — risk-adjusted</b><br><span style='font-size:12px;color:#898781'>benefits up, costs down; initial outlay undiscounted at time 0</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": [
    "# ── Cash flow — mirrors the PDF's Cash Flow Chart ────────────────────\n",
    "fig = go.Figure()\n",
    "fig.add_trace(bar(X_LABELS,\n",
    "                  [0] + [client[\"benefits_by_year\"][y] for y in YEARS],\n",
    "                  \"Benefits (risk-adjusted)\", BEN_TOTAL))\n",
    "fig.add_trace(bar(X_LABELS,\n",
    "                  [-client[\"initial_costs\"]] + [-client[\"costs_by_year\"][y] for y in YEARS],\n",
    "                  \"Costs (risk-adjusted)\", COST_TOTAL))\n",
    "fig.add_trace(cum_line(X_LABELS,\n",
    "                       [-client[\"initial_costs\"]] + [client[\"cumulative_net_by_year\"][y] for y in YEARS],\n",
    "                       \"Cumulative net\"))\n",
    "fig.update_layout(barmode=\"relative\")\n",
    "fig.add_annotation(\n",
    "    x=0.02, y=0.98, xref=\"paper\", yref=\"paper\", showarrow=False, align=\"left\",\n",
    "    text=(f\"NPV {html_money(client['npv'])} · ROI {client['roi_pct']:.0f}% · \"\n",
    "          f\"payback {client['payback_label']}\"),\n",
    "    font=dict(size=12, color=INK), bgcolor=SURFACE,\n",
    "    bordercolor=GRID, borderwidth=1)\n",
    "tei_layout(fig, \"Cash flow — risk-adjusted\",\n",
    "           subtitle=\"benefits up, costs down; initial outlay undiscounted at time 0\")\n",
    "fig.show()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "cell-17",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-09T19:15:20.527870Z",
     "iopub.status.busy": "2026-07-09T19:15:20.527655Z",
     "iopub.status.idle": "2026-07-09T19:15:20.551901Z",
     "shell.execute_reply": "2026-07-09T19:15:20.551086Z"
    }
   },
   "outputs": [
    {
     "data": {
      "application/vnd.plotly.v1+json": {
       "config": {
        "plotlyServerURL": "https://plot.ly"
       },
       "data": [
        {
         "connector": {
          "line": {
           "color": "#e1e0d9"
          }
         },
         "decreasing": {
          "marker": {
           "color": "#e34948"
          }
         },
         "increasing": {
          "marker": {
           "color": "#1baf7a"
          }
         },
         "measure": [
          "relative",
          "relative",
          "total"
         ],
         "text": [
          "&#36;14.8M",
          "-&#36;4.1M",
          "&#36;10.8M"
         ],
         "textposition": "outside",
         "totals": {
          "marker": {
           "color": "#2a78d6"
          }
         },
         "type": "waterfall",
         "x": [
          "Benefits PV",
          "Costs PV",
          "NPV"
         ],
         "y": [
          14840636.814425241,
          -4057170.398196844,
          0
         ]
        }
       ],
       "layout": {
        "bargap": 0.45,
        "font": {
         "color": "#52514e",
         "family": "system-ui, -apple-system, \"Segoe UI\", sans-serif",
         "size": 12
        },
        "height": 420,
        "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",
        "showlegend": false,
        "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>Present value walk — client overlay</b><br><span style='font-size:12px;color:#898781'>discounted at 10%, 3 years</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(go.Waterfall(\n",
    "    x=[\"Benefits PV\", \"Costs PV\", \"NPV\"],\n",
    "    measure=[\"relative\", \"relative\", \"total\"],\n",
    "    y=[client[\"benefits_pv\"], -client[\"costs_pv\"], 0],\n",
    "    text=[html_money(client[\"benefits_pv\"]), html_money(-client[\"costs_pv\"]),\n",
    "          html_money(client[\"npv\"])],\n",
    "    textposition=\"outside\",\n",
    "    connector=dict(line=dict(color=GRID)),\n",
    "    increasing=dict(marker=dict(color=BEN_TOTAL)),\n",
    "    decreasing=dict(marker=dict(color=COST_TOTAL)),\n",
    "    totals=dict(marker=dict(color=NPV_COLOR)),\n",
    "))\n",
    "fig.update_layout(showlegend=False)\n",
    "tei_layout(fig, \"Present value walk — client overlay\",\n",
    "           subtitle=f\"discounted at {'{:.0%}'.format(DISCOUNT_RATE)}, 3 years\",\n",
    "           height=420)\n",
    "fig.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-18",
   "metadata": {},
   "source": [
    "<a id=\"section-6\"></a>\n",
    "## 6 · Scenarios (🟡)\n",
    "\n",
    "Scenarios stress the overlay on two levers: **adoption** scales every\n",
    "nominal value (including the initial outlay), and **risk delta** widens or\n",
    "narrows the TEI risk adjustments — added to benefit risk, subtracted from\n",
    "cost risk, clamped at zero.\n",
    "\n",
    "One counterintuitive consequence, worth stating: the **conservative**\n",
    "scenario *lowers* costs PV as well as benefits — 80% adoption shrinks\n",
    "every cost line and the clamp zeroes all this study's cost paddings (5–10%\n",
    "risk factors, all under the 10% delta). The case direction is still\n",
    "conservative: NPV and ROI both fall.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "cell-19",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-09T19:15:20.554518Z",
     "iopub.status.busy": "2026-07-09T19:15:20.554277Z",
     "iopub.status.idle": "2026-07-09T19:15:20.564822Z",
     "shell.execute_reply": "2026-07-09T19:15:20.564005Z"
    }
   },
   "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>Adoption</th>\n",
       "      <th>Risk Δ</th>\n",
       "      <th>Benefits PV</th>\n",
       "      <th>Costs PV</th>\n",
       "      <th>NPV</th>\n",
       "      <th>ROI %</th>\n",
       "      <th>Payback (months)</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",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>conservative</th>\n",
       "      <td>80%</td>\n",
       "      <td>+10%</td>\n",
       "      <td>10,543,494</td>\n",
       "      <td>3,026,631</td>\n",
       "      <td>7,516,863</td>\n",
       "      <td>248</td>\n",
       "      <td>3</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>moderate</th>\n",
       "      <td>100%</td>\n",
       "      <td>+0%</td>\n",
       "      <td>14,840,637</td>\n",
       "      <td>4,057,170</td>\n",
       "      <td>10,783,466</td>\n",
       "      <td>266</td>\n",
       "      <td>3</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>aggressive</th>\n",
       "      <td>115%</td>\n",
       "      <td>-5%</td>\n",
       "      <td>18,021,962</td>\n",
       "      <td>4,883,285</td>\n",
       "      <td>13,138,677</td>\n",
       "      <td>269</td>\n",
       "      <td>3</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "             Adoption Risk Δ  Benefits PV  Costs PV        NPV  ROI %  \\\n",
       "Scenario                                                                \n",
       "conservative      80%   +10%   10,543,494 3,026,631  7,516,863    248   \n",
       "moderate         100%    +0%   14,840,637 4,057,170 10,783,466    266   \n",
       "aggressive       115%    -5%   18,021,962 4,883,285 13,138,677    269   \n",
       "\n",
       "              Payback (months)  \n",
       "Scenario                        \n",
       "conservative                 3  \n",
       "moderate                     3  \n",
       "aggressive                   3  "
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# ── Scenario sweep over the client overlay ───────────────────────────\n",
    "scen_summaries = {\n",
    "    s: compute_summary(apply_scenario(overlay_benefits, s),\n",
    "                       apply_scenario(overlay_costs, s), DISCOUNT_RATE)\n",
    "    for s in SCENARIOS\n",
    "}\n",
    "scen_df = pd.DataFrame([{\n",
    "    \"Scenario\": s,\n",
    "    \"Adoption\": f\"{SCENARIOS[s]['adoption']:.0%}\",\n",
    "    \"Risk Δ\": f\"{SCENARIOS[s]['risk_delta']:+.0%}\",\n",
    "    \"Benefits PV\": r[\"benefits_pv\"],\n",
    "    \"Costs PV\": r[\"costs_pv\"],\n",
    "    \"NPV\": r[\"npv\"],\n",
    "    \"ROI %\": round(r[\"roi_pct\"], 1),\n",
    "    \"Payback (months)\": round(r[\"payback_months\"], 2)\n",
    "        if r[\"payback_months\"] is not None else float(\"nan\"),\n",
    "} for s, r in scen_summaries.items()]).set_index(\"Scenario\")\n",
    "display(scen_df)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "cell-20",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-09T19:15:20.567700Z",
     "iopub.status.busy": "2026-07-09T19:15:20.567440Z",
     "iopub.status.idle": "2026-07-09T19:15:20.587490Z",
     "shell.execute_reply": "2026-07-09T19:15:20.586666Z"
    }
   },
   "outputs": [
    {
     "data": {
      "application/vnd.plotly.v1+json": {
       "config": {
        "plotlyServerURL": "https://plot.ly"
       },
       "data": [
        {
         "hovertemplate": "%{fullData.name}: %{y:$,.0f}<extra></extra>",
         "marker": {
          "color": "#1baf7a",
          "line": {
           "color": "#fcfcfb",
           "width": 2
          }
         },
         "name": "Benefits PV",
         "type": "bar",
         "x": [
          "conservative",
          "moderate",
          "aggressive"
         ],
         "y": [
          10543493.914350111,
          14840636.814425241,
          18021962.2539444
         ]
        },
        {
         "hovertemplate": "%{fullData.name}: %{y:$,.0f}<extra></extra>",
         "marker": {
          "color": "#e34948",
          "line": {
           "color": "#fcfcfb",
           "width": 2
          }
         },
         "name": "Costs PV",
         "type": "bar",
         "x": [
          "conservative",
          "moderate",
          "aggressive"
         ],
         "y": [
          3026631.4049586775,
          4057170.398196844,
          4883285.090157775
         ]
        },
        {
         "hovertemplate": "%{fullData.name}: %{y:$,.0f}<extra></extra>",
         "marker": {
          "color": "#2a78d6",
          "line": {
           "color": "#fcfcfb",
           "width": 2
          }
         },
         "name": "NPV",
         "type": "bar",
         "x": [
          "conservative",
          "moderate",
          "aggressive"
         ],
         "y": [
          7516862.509391434,
          10783466.416228397,
          13138677.163786625
         ]
        }
       ],
       "layout": {
        "bargap": 0.45,
        "barmode": "group",
        "font": {
         "color": "#52514e",
         "family": "system-ui, -apple-system, \"Segoe UI\", sans-serif",
         "size": 12
        },
        "height": 420,
        "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>Scenario comparison — client overlay</b><br><span style='font-size:12px;color:#898781'>adoption × risk-delta stress on the same drivers</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",
    "_scen = list(scen_summaries)\n",
    "for _name, _key, _color in [(\"Benefits PV\", \"benefits_pv\", BEN_TOTAL),\n",
    "                            (\"Costs PV\", \"costs_pv\", COST_TOTAL),\n",
    "                            (\"NPV\", \"npv\", NPV_COLOR)]:\n",
    "    fig.add_trace(bar(_scen, [scen_summaries[s][_key] for s in _scen],\n",
    "                      _name, _color))\n",
    "fig.update_layout(barmode=\"group\")\n",
    "tei_layout(fig, \"Scenario comparison — client overlay\",\n",
    "           subtitle=\"adoption × risk-delta stress on the same drivers\",\n",
    "           height=420)\n",
    "fig.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-21",
   "metadata": {},
   "source": [
    "<a id=\"section-7\"></a>\n",
    "## 7 · Verification & assertions\n",
    "\n",
    "The gate re-derives the case from the engine and asserts: the verbatim\n",
    "anchor is intact (AI-token line at $0 included); the engine reproduces\n",
    "Forrester's published totals within $5; the overlay is the identity at\n",
    "composite scale; and the structural identities hold at **any** widget\n",
    "setting. It must pass in a headless `nbconvert --execute` run — that is\n",
    "this study's regression check.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "cell-22",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-09T19:15:20.590288Z",
     "iopub.status.busy": "2026-07-09T19:15:20.590029Z",
     "iopub.status.idle": "2026-07-09T19:15:20.607459Z",
     "shell.execute_reply": "2026-07-09T19:15:20.606722Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "All assertions passed.\n",
      "  reproduction Δ vs PDF: benefits -1.19 · costs +0.40 · npv -1.58\n"
     ]
    }
   ],
   "source": [
    "def _approx(got, want, tol=0.5):\n",
    "    assert abs(got - want) <= tol, f\"got {got:,.2f}, want {want:,.2f}\"\n",
    "\n",
    "\n",
    "# ── Anchor integrity — the verbatim record is intact (unconditional) ─\n",
    "_approx(BENEFITS_VERBATIM[0][\"year_values\"][\"1\"], 680_000)\n",
    "_approx(COSTS_VERBATIM[1][\"initial\"], 1_190_000)\n",
    "_tok = next(r for r in COSTS_VERBATIM if r[\"field_key\"] == \"genesys_ai_tokens\")\n",
    "assert all(v == 0 for v in _tok[\"year_values\"].values())          # the $0 AI line, verbatim\n",
    "assert ASSUMPTIONS[\"agents_fte\"] == 600\n",
    "assert ASSUMPTIONS[\"weekly_interactions\"] == 80_000\n",
    "assert (COMPOSITE.agents_fte, COMPOSITE.ai_tokens_annual) == (600, 0.0)\n",
    "\n",
    "# ── Published reproduction — engine defaults, explicit args ──────────\n",
    "_c = compute_summary(BENEFITS_VERBATIM, COSTS_VERBATIM, 0.10)\n",
    "_approx(_c[\"benefits_pv\"], PUBLISHED[\"benefits_pv\"], tol=5)       # Δ −1.19 (PDF rounding)\n",
    "_approx(_c[\"costs_pv\"], PUBLISHED[\"costs_pv\"], tol=5)             # Δ +0.40\n",
    "_approx(_c[\"npv\"], PUBLISHED[\"npv\"], tol=5)                       # Δ −1.58\n",
    "assert round(_c[\"roi_pct\"]) == PUBLISHED[\"roi_pct\"]               # 265.79 → 266\n",
    "_approx(_c[\"payback_months\"], 3.33, tol=0.01)                     # not headlined in the PDF\n",
    "_approx(_c[\"initial_costs\"], 1_309_000)\n",
    "_approx(_c[\"benefits_by_year\"][2026], 5_816_960, tol=1)\n",
    "_approx(_c[\"costs_by_year\"][2028], 1_105_080, tol=1)\n",
    "\n",
    "# ── Overlay identity + scaling behaviour (explicit args) ─────────────\n",
    "_ob, _oc = overlay_rows(COMPOSITE)\n",
    "_id = compute_summary(_ob, _oc, 0.10)\n",
    "_approx(_id[\"benefits_pv\"], _c[\"benefits_pv\"], tol=0.01)          # identity at composite\n",
    "_hb, _hc = overlay_rows(ClientDrivers(agents_fte=300))\n",
    "_approx(next(r for r in _hb if r[\"field_key\"] == \"legacy_retirement\")\n",
    "        [\"year_values\"][\"1\"], 340_000)                            # agents-driven halves\n",
    "_approx(next(r for r in _hb if r[\"field_key\"] == \"self_service_savings\")\n",
    "        [\"year_values\"][\"1\"], 2_329_600)                          # interactions-driven unmoved\n",
    "_tb, _tc = overlay_rows(ClientDrivers(ai_tokens_annual=250_000))\n",
    "_t = compute_summary(_tb, _tc, 0.10)\n",
    "_approx(_t[\"costs_pv\"] - _c[\"costs_pv\"], 621_713, tol=1)          # tokens priced → PV moves\n",
    "\n",
    "# ── Scenario pin (explicit args) ─────────────────────────────────────\n",
    "_s = compute_summary(apply_scenario(BENEFITS_VERBATIM, \"conservative\"),\n",
    "                     apply_scenario(COSTS_VERBATIM, \"conservative\"), 0.10)\n",
    "_approx(_s[\"npv\"], 7_516_862.51, tol=1)\n",
    "\n",
    "# ── Structural ties — hold at ANY widget state (unconditional) ───────\n",
    "_approx(client[\"npv\"], client[\"benefits_pv\"] - client[\"costs_pv\"], tol=0.01)\n",
    "_approx(client[\"roi_pct\"], client[\"npv\"] / client[\"costs_pv\"] * 100, tol=0.01)\n",
    "for _y in YEARS:\n",
    "    _approx(client[\"net_by_year\"][_y],\n",
    "            client[\"benefits_by_year\"][_y] - client[\"costs_by_year\"][_y], tol=0.01)\n",
    "_approx(client[\"cumulative_net_by_year\"][YEARS[-1]],\n",
    "        sum(client[\"net_by_year\"].values()) - client[\"initial_costs\"], tol=0.01)\n",
    "_approx(sum(r[\"pv\"] for r in client[\"rows\"][\"benefits\"]), client[\"benefits_pv\"], tol=0.01)\n",
    "_approx(sum(r[\"pv\"] for r in client[\"rows\"][\"costs\"]), client[\"costs_pv\"], tol=0.01)\n",
    "\n",
    "# ── Live state — only when the sidebar sits at the composite defaults ─\n",
    "if _at_default:\n",
    "    _approx(client[\"benefits_pv\"], 14_840_636.81, tol=1)          # engine-exact\n",
    "    _approx(client[\"npv\"], PUBLISHED[\"npv\"], tol=5)\n",
    "    _approx(client[\"payback_months\"], 3.33, tol=0.01)\n",
    "\n",
    "backstage(\"All assertions passed.\")\n",
    "backstage(f\"  reproduction Δ vs PDF: benefits \"\n",
    "          f\"{_c['benefits_pv'] - PUBLISHED['benefits_pv']:+,.2f} · \"\n",
    "          f\"costs {_c['costs_pv'] - PUBLISHED['costs_pv']:+,.2f} · \"\n",
    "          f\"npv {_c['npv'] - PUBLISHED['npv']:+,.2f}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-23",
   "metadata": {},
   "source": [
    "<a id=\"section-8\"></a>\n",
    "## 8 · Data appendix — for the machines\n",
    "\n",
    "Everything below renders **backstage only** (JupyterLab / nbconvert\n",
    "exports): markdown tables plus one JSON block of model state. Carried in\n",
    "the exports, this is the payload a downstream LLM — or, on the roadmap,\n",
    "Athena as the study repository — consumes directly. On the Mercury stage\n",
    "it stays hidden.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "cell-24",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-09T19:15:20.610247Z",
     "iopub.status.busy": "2026-07-09T19:15:20.609930Z",
     "iopub.status.idle": "2026-07-09T19:15:20.632690Z",
     "shell.execute_reply": "2026-07-09T19:15:20.631995Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "#### Composite organization (verbatim assumptions 🟢)\n",
      "\n",
      "| Assumption               | Value 🟢       |\n",
      "|:-------------------------|:---------------|\n",
      "| annual revenue           | $2,500,000,000 |\n",
      "| employees                | 10,000         |\n",
      "| agents fte               | 600            |\n",
      "| concurrent licenses      | 400            |\n",
      "| weekly interactions      | 80,000         |\n",
      "| interaction minutes      | 12 min         |\n",
      "| self service rate before | 15%            |\n",
      "| self service rate after  | 25%            |\n",
      "| mttr saved minutes       | 2 min          |\n",
      "| agent hourly rate        | $28/hr         |\n",
      "| agent annual salary      | $58,240        |\n",
      "| revenue impacted         | $500,000,000   |\n",
      "| revenue lift             | 1.5%           |\n",
      "| gross margin             | 8%             |\n",
      "| discount rate            | 10%            |\n",
      "| analysis years           | 3 years        |\n",
      "\n",
      "#### Benefits — client overlay (risk-adjusted $)\n",
      "\n",
      "| Benefit                                                                                               | Driver       | Risk adj   |      2026 |      2027 |      2028 |    3-yr RA |         PV |\n",
      "|:------------------------------------------------------------------------------------------------------|:-------------|:-----------|----------:|----------:|----------:|-----------:|-----------:|\n",
      "| Retirement of legacy systems with CX Cloud adoption                                                   | agents       | -5%        |   646,000 |   883,500 |   883,500 |  2,413,000 |  1,981,225 |\n",
      "| Cost savings from reallocated workers and avoided seasonal hires with increased customer self-service | interactions | -15%       | 1,980,160 | 1,980,160 | 1,980,160 |  5,940,480 |  4,924,365 |\n",
      "| CX agent efficiency gains                                                                             | interactions | -10%       | 2,620,800 | 2,620,800 | 2,620,800 |  7,862,400 |  6,517,542 |\n",
      "| Incremental sales from agent assist capabilities                                                      | revenue      | -5%        |   570,000 |   570,000 |   570,000 |  1,710,000 |  1,417,506 |\n",
      "| TOTAL                                                                                                 |              |            | 5,816,960 | 6,054,460 | 6,054,460 | 17,925,880 | 14,840,637 |\n",
      "\n",
      "#### Costs — client overlay (risk-adjusted $)\n",
      "\n",
      "| Cost                                    | Driver    | Risk adj   |   Initial |      2026 |      2027 |      2028 |        PV |\n",
      "|:----------------------------------------|:----------|:-----------|----------:|----------:|----------:|----------:|----------:|\n",
      "| CX Cloud solution costs (licenses)      | agents    | +5%        |         0 |   882,000 |   882,000 |   882,000 | 2,193,403 |\n",
      "| Implementation and deployment cost      | fixed     | +10%       | 1,309,000 |         0 |         0 |         0 | 1,309,000 |\n",
      "| Ongoing management costs                | fixed     | +10%       |         0 |   223,080 |   223,080 |   223,080 |   554,767 |\n",
      "| Genesys AI Experience token consumption | ai_tokens | +0%        |         0 |         0 |         0 |         0 |         0 |\n",
      "| TOTAL                                   |           |            | 1,309,000 | 1,105,080 | 1,105,080 | 1,105,080 | 4,057,170 |\n",
      "\n",
      "#### KPIs — published composite vs client overlay\n",
      "\n",
      "|               | Forrester composite (published 🟢)   | Client overlay (🟡)    |\n",
      "|:--------------|:-------------------------------------|:-----------------------|\n",
      "| Benefits PV   | $14,840,638                          | $14,840,637            |\n",
      "| Costs PV      | $4,057,170                           | $4,057,170             |\n",
      "| NPV           | $10,783,468                          | $10,783,466            |\n",
      "| ROI           | 266%                                 | 266%                   |\n",
      "| Payback       | not headlined                        | 3.3 months (~Apr 2026) |\n",
      "| Discount rate | 10%                                  | 10%                    |\n",
      "\n",
      "#### Scenarios (client overlay)\n",
      "\n",
      "| Scenario     | Adoption   | Risk Δ   |   Benefits PV |   Costs PV |        NPV |   ROI % |   Payback (months) |\n",
      "|:-------------|:-----------|:---------|--------------:|-----------:|-----------:|--------:|-------------------:|\n",
      "| conservative | 80%        | +10%     |    10,543,494 |  3,026,631 |  7,516,863 |     248 |                  3 |\n",
      "| moderate     | 100%       | +0%      |    14,840,637 |  4,057,170 | 10,783,466 |     266 |                  3 |\n",
      "| aggressive   | 115%       | -5%      |    18,021,962 |  4,883,285 | 13,138,677 |     269 |                  3 |\n",
      "\n",
      "#### Model state (JSON)\n",
      "\n",
      "```json\n",
      "{\n",
      "  \"study\": \"202512_TEI_Genesys_CX_Cloud\",\n",
      "  \"source\": \"Forrester TEI of CX Cloud, by Genesys and Salesforce (Dec 2025, commissioned by Genesys and Salesforce)\",\n",
      "  \"published\": {\n",
      "    \"benefits_pv\": 14840638,\n",
      "    \"costs_pv\": 4057170,\n",
      "    \"npv\": 10783468,\n",
      "    \"roi_pct\": 266,\n",
      "    \"discount_rate\": 0.1,\n",
      "    \"analysis_years\": 3\n",
      "  },\n",
      "  \"reproduction\": {\n",
      "    \"benefits_pv\": 14840636.81,\n",
      "    \"costs_pv\": 4057170.4,\n",
      "    \"npv\": 10783466.42,\n",
      "    \"roi_pct\": 265.79,\n",
      "    \"payback_months\": 3.33,\n",
      "    \"payback_note\": \"Forrester does not headline a payback for this study\"\n",
      "  },\n",
      "  \"client\": {\n",
      "    \"drivers\": {\n",
      "      \"agents_fte\": 600,\n",
      "      \"weekly_interactions\": 80000,\n",
      "      \"annual_revenue\": 2500000000.0,\n",
      "      \"ai_tokens_annual\": 0.0,\n",
      "      \"discount_rate\": 0.1,\n",
      "      \"scenario\": \"moderate\"\n",
      "    },\n",
      "    \"benefits_by_year\": {\n",
      "      \"2026\": 5816960,\n",
      "      \"2027\": 6054460,\n",
      "      \"2028\": 6054460\n",
      "    },\n",
      "    \"costs_by_year\": {\n",
      "      \"2026\": 1105080,\n",
      "      \"2027\": 1105080,\n",
      "      \"2028\": 1105080\n",
      "    },\n",
      "    \"net_by_year\": {\n",
      "      \"2026\": 4711880,\n",
      "      \"2027\": 4949380,\n",
      "      \"2028\": 4949380\n",
      "    },\n",
      "    \"cumulative_net_by_year\": {\n",
      "      \"2026\": 3402880,\n",
      "      \"2027\": 8352260,\n",
      "      \"2028\": 13301640\n",
      "    },\n",
      "    \"initial_costs\": 1309000,\n",
      "    \"kpis\": {\n",
      "      \"benefits_pv\": 14840637,\n",
      "      \"costs_pv\": 4057170,\n",
      "      \"npv\": 10783466,\n",
      "      \"roi_pct\": 265.79,\n",
      "      \"payback_months\": 3.33,\n",
      "      \"payback_label\": \"3.3 months (~Apr 2026)\"\n",
      "    },\n",
      "    \"scenarios\": {\n",
      "      \"conservative\": {\n",
      "        \"benefits_pv\": 10543494,\n",
      "        \"costs_pv\": 3026631,\n",
      "        \"npv\": 7516863,\n",
      "        \"roi_pct\": 248.36\n",
      "      },\n",
      "      \"moderate\": {\n",
      "        \"benefits_pv\": 14840637,\n",
      "        \"costs_pv\": 4057170,\n",
      "        \"npv\": 10783466,\n",
      "        \"roi_pct\": 265.79\n",
      "      },\n",
      "      \"aggressive\": {\n",
      "        \"benefits_pv\": 18021962,\n",
      "        \"costs_pv\": 4883285,\n",
      "        \"npv\": 13138677,\n",
      "        \"roi_pct\": 269.05\n",
      "      }\n",
      "    }\n",
      "  },\n",
      "  \"driver_map\": {\n",
      "    \"benefits\": {\n",
      "      \"legacy_retirement\": \"agents\",\n",
      "      \"self_service_savings\": \"interactions\",\n",
      "      \"agent_efficiency\": \"interactions\",\n",
      "      \"agent_assist_sales\": \"revenue\"\n",
      "    },\n",
      "    \"costs\": {\n",
      "      \"cx_cloud_licenses\": \"agents\",\n",
      "      \"implementation\": \"fixed\",\n",
      "      \"ongoing_management\": \"fixed\",\n",
      "      \"genesys_ai_tokens\": \"ai_tokens\"\n",
      "    }\n",
      "  },\n",
      "  \"ai_token_note\": \"Published study models $0 Genesys AI Experience token consumption; benefits B/C/D depend on token-billed AI capabilities. Price via drivers.ai_tokens_annual.\"\n",
      "}\n",
      "```\n"
     ]
    }
   ],
   "source": [
    "# ── Data appendix — LLM-readable dump of every model output ──────────\n",
    "# Renders backstage only (JupyterLab / nbconvert exports) — hidden on\n",
    "# the Mercury stage, where the narrative and figures carry the story.\n",
    "import json as _json\n",
    "\n",
    "\n",
    "def _section(title, df, **kw):\n",
    "    backstage(f\"\\n#### {title}\\n\")\n",
    "    backstage(df.to_markdown(floatfmt=\",.0f\", **kw))\n",
    "\n",
    "\n",
    "_section(\"Composite organization (verbatim assumptions 🟢)\",\n",
    "         assumptions_df, index=False)\n",
    "_section(\"Benefits — client overlay (risk-adjusted $)\", benefits_df)\n",
    "_section(\"Costs — client overlay (risk-adjusted $)\", costs_df)\n",
    "_section(\"KPIs — published composite vs client overlay\", kpis_fmt)\n",
    "_section(\"Scenarios (client overlay)\", scen_df)\n",
    "\n",
    "backstage(\"\\n#### Model state (JSON)\\n\")\n",
    "backstage(\"```json\")\n",
    "backstage(_json.dumps({\n",
    "    \"study\": \"202512_TEI_Genesys_CX_Cloud\",\n",
    "    \"source\": (\"Forrester TEI of CX Cloud, by Genesys and Salesforce \"\n",
    "               \"(Dec 2025, commissioned by Genesys and Salesforce)\"),\n",
    "    \"published\": PUBLISHED,\n",
    "    \"reproduction\": {\n",
    "        \"benefits_pv\": round(composite[\"benefits_pv\"], 2),\n",
    "        \"costs_pv\": round(composite[\"costs_pv\"], 2),\n",
    "        \"npv\": round(composite[\"npv\"], 2),\n",
    "        \"roi_pct\": round(composite[\"roi_pct\"], 2),\n",
    "        \"payback_months\": round(composite[\"payback_months\"], 2),\n",
    "        \"payback_note\": \"Forrester does not headline a payback for this study\",\n",
    "    },\n",
    "    \"client\": {\n",
    "        \"drivers\": {\n",
    "            \"agents_fte\": AGENTS_FTE,\n",
    "            \"weekly_interactions\": WEEKLY_INTERACTIONS,\n",
    "            \"annual_revenue\": ANNUAL_REVENUE,\n",
    "            \"ai_tokens_annual\": AI_TOKENS_ANNUAL,\n",
    "            \"discount_rate\": DISCOUNT_RATE,\n",
    "            \"scenario\": SCENARIO,\n",
    "        },\n",
    "        \"benefits_by_year\": {str(y): round(client[\"benefits_by_year\"][y]) for y in YEARS},\n",
    "        \"costs_by_year\": {str(y): round(client[\"costs_by_year\"][y]) for y in YEARS},\n",
    "        \"net_by_year\": {str(y): round(client[\"net_by_year\"][y]) for y in YEARS},\n",
    "        \"cumulative_net_by_year\": {str(y): round(client[\"cumulative_net_by_year\"][y]) for y in YEARS},\n",
    "        \"initial_costs\": round(client[\"initial_costs\"]),\n",
    "        \"kpis\": {\n",
    "            \"benefits_pv\": round(client[\"benefits_pv\"]),\n",
    "            \"costs_pv\": round(client[\"costs_pv\"]),\n",
    "            \"npv\": round(client[\"npv\"]),\n",
    "            \"roi_pct\": round(client[\"roi_pct\"], 2),\n",
    "            \"payback_months\": round(client[\"payback_months\"], 2)\n",
    "                if client[\"payback_months\"] is not None else None,\n",
    "            \"payback_label\": client[\"payback_label\"],\n",
    "        },\n",
    "        \"scenarios\": {\n",
    "            s: {\"benefits_pv\": round(r[\"benefits_pv\"]),\n",
    "                \"costs_pv\": round(r[\"costs_pv\"]),\n",
    "                \"npv\": round(r[\"npv\"]),\n",
    "                \"roi_pct\": round(r[\"roi_pct\"], 2)}\n",
    "            for s, r in scen_summaries.items()\n",
    "        },\n",
    "    },\n",
    "    \"driver_map\": {\"benefits\": BENEFIT_DRIVERS, \"costs\": COST_DRIVERS},\n",
    "    \"ai_token_note\": (\"Published study models $0 Genesys AI Experience token \"\n",
    "                      \"consumption; benefits B/C/D depend on token-billed AI \"\n",
    "                      \"capabilities. Price via drivers.ai_tokens_annual.\"),\n",
    "}, indent=2))\n",
    "backstage(\"```\")\n"
   ]
  }
 ],
 "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": {
     "14c2d1e5a0644752a2731aca143939b2": {
      "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
      }
     },
     "2247afa5bfb04ad8905333e3f64caf67": {
      "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      padding-left: 4px;\n      padding-right: 4px;\n      box-sizing: border-box;\n    }\n\n    .mljar-number-label {\n      padding-top: 6px;\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      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": "Weekly interactions — composite 80,000",
       "layout": "IPY_MODEL_e2772f6f5be849508a98ba9fd5de0dd1",
       "layout_path": null,
       "max": 2000000.0,
       "min": 1000.0,
       "position": "sidebar",
       "render_slot_id": null,
       "source_cell_id": null,
       "step": 5000.0,
       "tabbable": null,
       "tooltip": null,
       "url_key": "",
       "value": 80000.0
      }
     },
     "2b2aa32e5ae340dfab0c7f0de2791359": {
      "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
      }
     },
     "55da27f443ca4021895933ad5d96f23b": {
      "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      padding-left: 4px;\n      padding-right: 4px;\n      box-sizing: border-box;\n    }\n\n    .mljar-number-label {\n      padding-top: 6px;\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      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": "Annual revenue ($) — composite 2.5B",
       "layout": "IPY_MODEL_14c2d1e5a0644752a2731aca143939b2",
       "layout_path": null,
       "max": 100000000000.0,
       "min": 100000000.0,
       "position": "sidebar",
       "render_slot_id": null,
       "source_cell_id": null,
       "step": 100000000.0,
       "tabbable": null,
       "tooltip": null,
       "url_key": "",
       "value": 2500000000.0
      }
     },
     "6367ab66c77b4dcba88690962f8f466b": {
      "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
      }
     },
     "76c10bd18e9c466daec54721bc92b435": {
      "model_module": "anywidget",
      "model_module_version": "~0.11.*",
      "model_name": "AnyModel",
      "state": {
       "_anywidget_id": "mercury.select.SelectWidget",
       "_css": "\n    .mljar-select-container {\n      position: relative;\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      padding-left: 4px;\n      padding-right: 4px;\n      overflow: visible;\n    }\n\n    .mljar-select-label {\n      padding-top: 6px;\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      overflow: visible;\n    }\n\n    .mljar-select-container.is-open {\n      z-index: 20;\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      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: auto;\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      position: fixed;\n      z-index: 10000;\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      el.appendChild(container);\n\n      let isOpen = false;\n      let filteredChoices = [];\n      let lastCommittedValue = \"\";\n      let isEditing = false;\n      document.body.appendChild(dropdown);\n\n      const updateDropdownPosition = () => {\n        if (!isOpen) {\n          return;\n        }\n        const rect = control.getBoundingClientRect();\n        dropdown.style.top = `${rect.bottom + 6}px`;\n        dropdown.style.left = `${rect.left}px`;\n        dropdown.style.width = `${rect.width}px`;\n      };\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        if (isOpen) {\n          updateDropdownPosition();\n        }\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      const closeDropdown = () => {\n        isEditing = false;\n        setOpen(false);\n        input.value = lastCommittedValue;\n      };\n\n      control.addEventListener(\"click\", event => {\n        event.stopPropagation();\n        if (isDisabled()) {\n          return;\n        }\n        if (event.target === caret && isOpen) {\n          closeDropdown();\n          input.blur();\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) && !dropdown.contains(event.target)) {\n          closeDropdown();\n        }\n      };\n\n      document.addEventListener(\"click\", handleDocumentClick);\n      window.addEventListener(\"resize\", updateDropdownPosition);\n      document.addEventListener(\"scroll\", updateDropdownPosition, true);\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          closeDropdown();\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        dropdown.remove();\n        document.removeEventListener(\"click\", handleDocumentClick);\n        window.removeEventListener(\"resize\", updateDropdownPosition);\n        document.removeEventListener(\"scroll\", updateDropdownPosition, true);\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%",
        "10% (Forrester)",
        "12%"
       ],
       "disabled": false,
       "hidden": false,
       "label": "Discount rate",
       "layout": "IPY_MODEL_fdb25a02906540a49bcd2fb7791aba21",
       "layout_path": null,
       "position": "sidebar",
       "render_slot_id": null,
       "source_cell_id": null,
       "tabbable": null,
       "tooltip": null,
       "url_key": "",
       "value": "10% (Forrester)"
      }
     },
     "9173bebf5a34432e8e4819f906db4add": {
      "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      padding-left: 4px;\n      padding-right: 4px;\n      box-sizing: border-box;\n    }\n\n    .mljar-number-label {\n      padding-top: 6px;\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      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": "Genesys AI tokens ($/yr) — study modeled $0",
       "layout": "IPY_MODEL_2b2aa32e5ae340dfab0c7f0de2791359",
       "layout_path": null,
       "max": 5000000.0,
       "min": 0.0,
       "position": "sidebar",
       "render_slot_id": null,
       "source_cell_id": null,
       "step": 25000.0,
       "tabbable": null,
       "tooltip": null,
       "url_key": "",
       "value": 0.0
      }
     },
     "979e73c2734f41dca452ea6800cad0b8": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "HTMLStyleModel",
      "state": {
       "_model_module": "@jupyter-widgets/controls",
       "_model_module_version": "2.0.0",
       "_model_name": "HTMLStyleModel",
       "_view_count": null,
       "_view_module": "@jupyter-widgets/base",
       "_view_module_version": "2.0.0",
       "_view_name": "StyleView",
       "background": null,
       "description_width": "",
       "font_size": null,
       "text_color": null
      }
     },
     "ac40d1aeb52245df84aad900dc195c77": {
      "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
      }
     },
     "b01d658cd1294c5382cf6c64284d4781": {
      "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
      }
     },
     "b2751bf42368472ebf73f08317e9cde9": {
      "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      padding-left: 4px;\n      padding-right: 4px;\n      box-sizing: border-box;\n    }\n\n    .mljar-number-label {\n      padding-top: 6px;\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      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": "CX agents (FTE) — composite 600",
       "layout": "IPY_MODEL_6367ab66c77b4dcba88690962f8f466b",
       "layout_path": null,
       "max": 20000.0,
       "min": 25.0,
       "position": "sidebar",
       "render_slot_id": null,
       "source_cell_id": null,
       "step": 25.0,
       "tabbable": null,
       "tooltip": null,
       "url_key": "",
       "value": 600.0
      }
     },
     "c7bf126b79ee49b2bb7140ec4fb10b96": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "HTMLModel",
      "state": {
       "_dom_classes": [],
       "_model_module": "@jupyter-widgets/controls",
       "_model_module_version": "2.0.0",
       "_model_name": "HTMLModel",
       "_view_count": null,
       "_view_module": "@jupyter-widgets/controls",
       "_view_module_version": "2.0.0",
       "_view_name": "HTMLView",
       "cell_id": "",
       "description": "",
       "description_allow_html": false,
       "layout": "IPY_MODEL_ac40d1aeb52245df84aad900dc195c77",
       "layout_path": null,
       "placeholder": "​",
       "position": "sidebar",
       "render_slot_id": null,
       "source_cell_id": null,
       "style": "IPY_MODEL_979e73c2734f41dca452ea6800cad0b8",
       "tabbable": null,
       "tooltip": null,
       "value": "<div style=\"font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; font-size: 14px; font-weight: normal; line-height: 1.65; color: #0f172a; word-break: break-word;\"><p style=\"margin: 0 0 1em;\"><b>Jump to section</b><ol style=\"padding-left:1.2em;margin:6px 0; margin: 0 0 1em; padding-left: 1.4em;\"><li style=\"margin:2px 0\"><a style=\"cursor:pointer;text-decoration:underline; color: #007bff; text-decoration: underline; text-underline-offset: 0.14em;\" onclick=\"var el=document.getElementById('section-1');if(!el){document.querySelectorAll('h1,h2').forEach(function(h){if(!el&&h.textContent.trim().indexOf('1 ')===0){el=h;}});}if(el)el.scrollIntoView({behavior:'smooth',block:'start'});\">Composite organization</a></li><li style=\"margin:2px 0\"><a style=\"cursor:pointer;text-decoration:underline; color: #007bff; text-decoration: underline; text-underline-offset: 0.14em;\" onclick=\"var el=document.getElementById('section-2');if(!el){document.querySelectorAll('h1,h2').forEach(function(h){if(!el&&h.textContent.trim().indexOf('2 ')===0){el=h;}});}if(el)el.scrollIntoView({behavior:'smooth',block:'start'});\">Client inputs</a></li><li style=\"margin:2px 0\"><a style=\"cursor:pointer;text-decoration:underline; color: #007bff; text-decoration: underline; text-underline-offset: 0.14em;\" onclick=\"var el=document.getElementById('section-3');if(!el){document.querySelectorAll('h1,h2').forEach(function(h){if(!el&&h.textContent.trim().indexOf('3 ')===0){el=h;}});}if(el)el.scrollIntoView({behavior:'smooth',block:'start'});\">Benefits</a></li><li style=\"margin:2px 0\"><a style=\"cursor:pointer;text-decoration:underline; color: #007bff; text-decoration: underline; text-underline-offset: 0.14em;\" onclick=\"var el=document.getElementById('section-4');if(!el){document.querySelectorAll('h1,h2').forEach(function(h){if(!el&&h.textContent.trim().indexOf('4 ')===0){el=h;}});}if(el)el.scrollIntoView({behavior:'smooth',block:'start'});\">Costs &amp; the $0 AI line</a></li><li style=\"margin:2px 0\"><a style=\"cursor:pointer;text-decoration:underline; color: #007bff; text-decoration: underline; text-underline-offset: 0.14em;\" onclick=\"var el=document.getElementById('section-5');if(!el){document.querySelectorAll('h1,h2').forEach(function(h){if(!el&&h.textContent.trim().indexOf('5 ')===0){el=h;}});}if(el)el.scrollIntoView({behavior:'smooth',block:'start'});\">Business case</a></li><li style=\"margin:2px 0\"><a style=\"cursor:pointer;text-decoration:underline; color: #007bff; text-decoration: underline; text-underline-offset: 0.14em;\" onclick=\"var el=document.getElementById('section-6');if(!el){document.querySelectorAll('h1,h2').forEach(function(h){if(!el&&h.textContent.trim().indexOf('6 ')===0){el=h;}});}if(el)el.scrollIntoView({behavior:'smooth',block:'start'});\">Scenarios</a></li><li style=\"margin:2px 0\"><a style=\"cursor:pointer;text-decoration:underline; color: #007bff; text-decoration: underline; text-underline-offset: 0.14em;\" onclick=\"var el=document.getElementById('section-7');if(!el){document.querySelectorAll('h1,h2').forEach(function(h){if(!el&&h.textContent.trim().indexOf('7 ')===0){el=h;}});}if(el)el.scrollIntoView({behavior:'smooth',block:'start'});\">Verification &amp; assertions</a></li><li style=\"margin:2px 0\"><a style=\"cursor:pointer;text-decoration:underline; color: #007bff; text-decoration: underline; text-underline-offset: 0.14em;\" onclick=\"var el=document.getElementById('section-8');if(!el){document.querySelectorAll('h1,h2').forEach(function(h){if(!el&&h.textContent.trim().indexOf('8 ')===0){el=h;}});}if(el)el.scrollIntoView({behavior:'smooth',block:'start'});\">Data appendix</a></li></ol></p></div>"
      }
     },
     "ce038c3719d240a1a635401ac4a6208e": {
      "model_module": "anywidget",
      "model_module_version": "~0.11.*",
      "model_name": "AnyModel",
      "state": {
       "_anywidget_id": "mercury.select.SelectWidget",
       "_css": "\n    .mljar-select-container {\n      position: relative;\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      padding-left: 4px;\n      padding-right: 4px;\n      overflow: visible;\n    }\n\n    .mljar-select-label {\n      padding-top: 6px;\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      overflow: visible;\n    }\n\n    .mljar-select-container.is-open {\n      z-index: 20;\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      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: auto;\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      position: fixed;\n      z-index: 10000;\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      el.appendChild(container);\n\n      let isOpen = false;\n      let filteredChoices = [];\n      let lastCommittedValue = \"\";\n      let isEditing = false;\n      document.body.appendChild(dropdown);\n\n      const updateDropdownPosition = () => {\n        if (!isOpen) {\n          return;\n        }\n        const rect = control.getBoundingClientRect();\n        dropdown.style.top = `${rect.bottom + 6}px`;\n        dropdown.style.left = `${rect.left}px`;\n        dropdown.style.width = `${rect.width}px`;\n      };\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        if (isOpen) {\n          updateDropdownPosition();\n        }\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      const closeDropdown = () => {\n        isEditing = false;\n        setOpen(false);\n        input.value = lastCommittedValue;\n      };\n\n      control.addEventListener(\"click\", event => {\n        event.stopPropagation();\n        if (isDisabled()) {\n          return;\n        }\n        if (event.target === caret && isOpen) {\n          closeDropdown();\n          input.blur();\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) && !dropdown.contains(event.target)) {\n          closeDropdown();\n        }\n      };\n\n      document.addEventListener(\"click\", handleDocumentClick);\n      window.addEventListener(\"resize\", updateDropdownPosition);\n      document.addEventListener(\"scroll\", updateDropdownPosition, true);\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          closeDropdown();\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        dropdown.remove();\n        document.removeEventListener(\"click\", handleDocumentClick);\n        window.removeEventListener(\"resize\", updateDropdownPosition);\n        document.removeEventListener(\"scroll\", updateDropdownPosition, true);\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": [
        "conservative",
        "moderate",
        "aggressive"
       ],
       "disabled": false,
       "hidden": false,
       "label": "Scenario",
       "layout": "IPY_MODEL_b01d658cd1294c5382cf6c64284d4781",
       "layout_path": null,
       "position": "sidebar",
       "render_slot_id": null,
       "source_cell_id": null,
       "tabbable": null,
       "tooltip": null,
       "url_key": "",
       "value": "moderate"
      }
     },
     "e2772f6f5be849508a98ba9fd5de0dd1": {
      "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
      }
     },
     "fdb25a02906540a49bcd2fb7791aba21": {
      "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
      }
     }
    },
    "version_major": 2,
    "version_minor": 0
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
