{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "a79bef3a",
   "metadata": {},
   "source": [
    "# CTM × Genesys CCaaS — Migration + WFM (No-AI) Case\n",
    "\n",
    "**Thesis:** strip every AI capability out of the business case and price the platform move\n",
    "alone: migrate all four regions to Genesys Cloud CX, move NA off its existing WFM-like tool\n",
    "onto Genesys WFM, and implement WFM for APAC (**ANZ + ASIA**). Migration and WFM are\n",
    "**included in the base implementation price** — the verbatim $2.4M professional services +\n",
    "$167K training. The only benefits kept are the deck's verbatim **WFM** lines for the regions\n",
    "in WFM scope; every other benefit (Agent Copilot, Email, STA, Predictive Routing, Supervisor\n",
    "Copilot) is AI-driven and out.\n",
    "\n",
    "| Layer | Source | Treatment |\n",
    "|---|---|---|\n",
    "| WFM benefits | *Appendix 4 — CCaaS Platform Benefit Calculations (Consolidated).pptx*, slides 12–15 | **Verbatim**, scoped to NA/ANZ/ASIA, phased on Genesys's own deployment schedule |\n",
    "| CCaaS licences | Same deck (TCO slides 5–6) | $4.3M/yr run-rate, 12-month ramp credit |\n",
    "| Base PS + training | Same deck | Verbatim $2.4M + $167K, year 1 — **includes migration + WFM** |\n",
    "| Existing platform run-off | Term contracts to 31 Dec 2027, per-region | Double-billing until termination |\n",
    "| AI tokens / AI implementation | — | **Removed** — see `ctm_business_case_corrected.ipynb` |\n",
    "\n",
    "Companion to the Corrected Business Case notebook — same engine (`tokencalc`), same\n",
    "baseline-relative frame, same 2026–2028 window, so the two cases are directly comparable.\n",
    "Confidence legend: 🟢 confirmed (published/contractual) · 🟡 estimated (working assumption) · 🔴 unknown.\n",
    "\n",
    "*This notebook is the deliverable: serve it interactively with `mercury --working-dir notebooks/`, tune the 🟡 inputs live for the client, then export an LLM-readable report source with `python scripts/export_report.py`.*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "38d0b7d9",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-08T00:44:23.355201Z",
     "iopub.status.busy": "2026-07-08T00:44:23.355012Z",
     "iopub.status.idle": "2026-07-08T00:44:23.806304Z",
     "shell.execute_reply": "2026-07-08T00:44:23.805596Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tokencalc loaded — 9 sites, 2,088 named users (contracted: 2,088)\n"
     ]
    }
   ],
   "source": [
    "# ── Setup ──────────────────────────────────────────────────────────\n",
    "import sys, pathlib\n",
    "_ROOT = pathlib.Path.cwd()\n",
    "if not (_ROOT / \"tokencalc\").exists():           # notebook lives in notebooks/\n",
    "    _ROOT = _ROOT.parent\n",
    "sys.path.insert(0, str(_ROOT))\n",
    "\n",
    "import datetime as dt\n",
    "\n",
    "import pandas as pd\n",
    "import plotly.graph_objects as go\n",
    "\n",
    "import mercury as mr\n",
    "\n",
    "from tokencalc import *\n",
    "# Single source of truth — all math lives in the library; only\n",
    "# presentation (and Mercury input widgets) lives here.\n",
    "from tokencalc.appendix4 import (\n",
    "    YEARS, YEAR_INDEX, REGIONS,\n",
    "    VERBATIM_BENEFITS, TCO_VERBATIM, IMPL_MONTH, REALIZE_MONTH,\n",
    "    DEFAULT_RAMP_MONTHS,\n",
    "    build_rollouts, case_flows, case_kpis, crossfoot_tolerance,\n",
    "    current_costs_by_year, current_state_inputs, licence_costs_by_year,\n",
    "    money, html_money, month_label, ps_costs_by_year,\n",
    "    region_agents, region_site_names,\n",
    ")\n",
    "from tokencalc.migration_wfm import (\n",
    "    DEFAULT_WFM_REGIONS, runrate_breakeven_label, runrate_saving_annual,\n",
    "    wfm_annual_runrate, wfm_benefits_by_year,\n",
    ")\n",
    "from tokencalc.business_case import npv\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 region colors — color follows the entity across every figure.\n",
    "REGION_COLOR = {\"NA\": \"#2a78d6\", \"ANZ\": \"#1baf7a\", \"EMEA\": \"#4a3aa7\", \"ASIA\": \"#eda100\"}\n",
    "COST_COLOR = {\n",
    "    \"CCaaS platform licences (ramp-adjusted)\": \"#2a78d6\",\n",
    "    \"Base PS + training (incl. migration + WFM)\": \"#1baf7a\",\n",
    "    \"Existing platform (term-contract run-off)\": \"#eda100\",\n",
    "}\n",
    "DIVERGING = [[0.0, \"#e34948\"], [0.5, \"#f0efe9\"], [1.0, \"#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",
    "X = [str(y) for y in YEARS]\n",
    "\n",
    "print(f\"tokencalc loaded — {len(CTM_DEFAULT_SITES)} sites, \"\n",
    "      f\"{sum(s.named_users for s in CTM_DEFAULT_SITES):,} named users \"\n",
    "      f\"(contracted: {CONTRACTED_NAMED_USERS:,})\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "104afa86",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-08T00:44:23.809895Z",
     "iopub.status.busy": "2026-07-08T00:44:23.809693Z",
     "iopub.status.idle": "2026-07-08T00:44:23.820997Z",
     "shell.execute_reply": "2026-07-08T00:44:23.820428Z"
    }
   },
   "outputs": [
    {
     "data": {
      "application/mercury+json": {
       "model_id": "e6f9520844b84d66a15de199f5ecf700",
       "position": "sidebar",
       "widget": "MarkdownWidget"
      },
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "e6f9520844b84d66a15de199f5ecf700",
       "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, \"Current state & contracts\"),\n",
    "    (2, \"Scenario scope & licences\"),\n",
    "    (3, \"WFM benefits\"),\n",
    "    (4, \"Cost stack\"),\n",
    "    (5, \"Business case\"),\n",
    "    (6, \"Sensitivity\"),\n",
    "    (7, \"Verification & assertions\"),\n",
    "    (8, \"Risks & notes\"),\n",
    "    (9, \"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\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cecdb4b9",
   "metadata": {},
   "source": [
    "<a id=\"section-1\"></a>\n",
    "\n",
    "## 1 · Current state & contract inputs — data collection\n",
    "\n",
    "Verbatim anchors (Appendix 4, slides 5–6): current solution costs **$7.3M/yr globally**\n",
    "(ANZ, APAC, EMEA, NA — $22M over 3 years); CCaaS is **$4.3M/yr** with **$2.4M professional\n",
    "services + $167K training in year 1**.\n",
    "\n",
    "The same three contract facts as the corrected notebook, collected as editable inputs:\n",
    "\n",
    "1. **Per-region current cost** — seeded as $7.3M × (region agents ÷ total agents). 🟡 Allocation\n",
    "   estimate; replace with real regional contract values as they arrive. If NA's existing\n",
    "   WFM-like tool bills separately from the platform, fold it into NA's number here — migrating\n",
    "   off it is part of this scenario.\n",
    "2. **Contract termination dates** — the current platforms are **term contracts**; CTM pays until\n",
    "   termination (default **31 Dec 2027**) regardless of when Genesys goes live → double-billing.\n",
    "3. **Genesys ramp programme** — `RAMP_MONTHS = 12`: licences are free for the first 12 months,\n",
    "   so the licence run-rate bills from month 13."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "f8863db7",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-08T00:44:23.823579Z",
     "iopub.status.busy": "2026-07-08T00:44:23.823469Z",
     "iopub.status.idle": "2026-07-08T00:44:23.858704Z",
     "shell.execute_reply": "2026-07-08T00:44:23.858017Z"
    }
   },
   "outputs": [
    {
     "data": {
      "application/mercury+json": {
       "model_id": "de213f8c9e7242dca519f335951528dd",
       "position": "inline",
       "widget": "NumberInputWidget"
      },
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "de213f8c9e7242dca519f335951528dd",
       "version_major": 2,
       "version_minor": 1
      },
      "text/plain": [
       "<mercury.number.NumberInputWidget object at 0x7c44104f3e00>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/mercury+json": {
       "model_id": "7a150d2b059b4c769da78ec1301ce34d",
       "position": "inline",
       "widget": "NumberInputWidget"
      },
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "7a150d2b059b4c769da78ec1301ce34d",
       "version_major": 2,
       "version_minor": 1
      },
      "text/plain": [
       "<mercury.number.NumberInputWidget object at 0x7c44102b9590>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/mercury+json": {
       "model_id": "82431bad9263463187c3a824326eedcf",
       "position": "inline",
       "widget": "DateInputWidget"
      },
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "82431bad9263463187c3a824326eedcf",
       "version_major": 2,
       "version_minor": 1
      },
      "text/plain": [
       "<mercury.date.DateInputWidget object at 0x7c44104f3cb0>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/mercury+json": {
       "model_id": "ff2cafef5fb24fb4b60e4972527894f7",
       "position": "inline",
       "widget": "NumberInputWidget"
      },
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "ff2cafef5fb24fb4b60e4972527894f7",
       "version_major": 2,
       "version_minor": 1
      },
      "text/plain": [
       "<mercury.number.NumberInputWidget object at 0x7c44102b9d10>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/mercury+json": {
       "model_id": "9b301ed2ca294f05902a6855e5e41920",
       "position": "inline",
       "widget": "DateInputWidget"
      },
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "9b301ed2ca294f05902a6855e5e41920",
       "version_major": 2,
       "version_minor": 1
      },
      "text/plain": [
       "<mercury.date.DateInputWidget object at 0x7c44102b9f90>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/mercury+json": {
       "model_id": "8d3bd1c968c9418fbb2937303f3fbb38",
       "position": "inline",
       "widget": "NumberInputWidget"
      },
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "8d3bd1c968c9418fbb2937303f3fbb38",
       "version_major": 2,
       "version_minor": 1
      },
      "text/plain": [
       "<mercury.number.NumberInputWidget object at 0x7c43b268f5c0>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/mercury+json": {
       "model_id": "72b80b7fd621417ca15b3ac7d058b757",
       "position": "inline",
       "widget": "DateInputWidget"
      },
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "72b80b7fd621417ca15b3ac7d058b757",
       "version_major": 2,
       "version_minor": 1
      },
      "text/plain": [
       "<mercury.date.DateInputWidget object at 0x7c44102ba350>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/mercury+json": {
       "model_id": "56ad595a5e4a45c9ab13fb8ca1822d82",
       "position": "inline",
       "widget": "NumberInputWidget"
      },
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "56ad595a5e4a45c9ab13fb8ca1822d82",
       "version_major": 2,
       "version_minor": 1
      },
      "text/plain": [
       "<mercury.number.NumberInputWidget object at 0x7c43b268f6f0>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/mercury+json": {
       "model_id": "701f9b3f6fa747f5abd97876777785df",
       "position": "inline",
       "widget": "DateInputWidget"
      },
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "701f9b3f6fa747f5abd97876777785df",
       "version_major": 2,
       "version_minor": 1
      },
      "text/plain": [
       "<mercury.date.DateInputWidget object at 0x7c43b268f820>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "current-state total $7.3M/yr (seed: $7.3M; deck 3-yr $22.0M — deck rounding)\n",
      "existing-platform run-off by year: {2026: '$7.3M', 2027: '$7.3M', 2028: '$0K'}\n"
     ]
    },
    {
     "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>agents</th>\n",
       "      <th>share</th>\n",
       "      <th>annual_cost</th>\n",
       "      <th>contract_termination</th>\n",
       "      <th>confidence</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>region</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>NA</th>\n",
       "      <td>890</td>\n",
       "      <td>0</td>\n",
       "      <td>3,348,969</td>\n",
       "      <td>2027-12-31</td>\n",
       "      <td>🟡 agent-share allocation of the verbatim total</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>ANZ</th>\n",
       "      <td>180</td>\n",
       "      <td>0</td>\n",
       "      <td>677,320</td>\n",
       "      <td>2027-12-31</td>\n",
       "      <td>🟡 agent-share allocation of the verbatim total</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>EMEA</th>\n",
       "      <td>320</td>\n",
       "      <td>0</td>\n",
       "      <td>1,204,124</td>\n",
       "      <td>2027-12-31</td>\n",
       "      <td>🟡 agent-share allocation of the verbatim total</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>ASIA</th>\n",
       "      <td>550</td>\n",
       "      <td>0</td>\n",
       "      <td>2,069,588</td>\n",
       "      <td>2027-12-31</td>\n",
       "      <td>🟡 agent-share allocation of the verbatim total</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "        agents  share  annual_cost contract_termination  \\\n",
       "region                                                    \n",
       "NA         890      0    3,348,969           2027-12-31   \n",
       "ANZ        180      0      677,320           2027-12-31   \n",
       "EMEA       320      0    1,204,124           2027-12-31   \n",
       "ASIA       550      0    2,069,588           2027-12-31   \n",
       "\n",
       "                                            confidence  \n",
       "region                                                  \n",
       "NA      🟡 agent-share allocation of the verbatim total  \n",
       "ANZ     🟡 agent-share allocation of the verbatim total  \n",
       "EMEA    🟡 agent-share allocation of the verbatim total  \n",
       "ASIA    🟡 agent-share allocation of the verbatim total  "
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# ── Contract inputs (model: tokencalc.appendix4) ─────────────────────\n",
    "# ⚙ Interactive when served with Mercury; headless runs (nbconvert /\n",
    "#   regression gate) use the widget defaults below.\n",
    "RAMP_MONTHS = int(mr.NumberInput(\n",
    "    label=\"Genesys ramp — licence-free months\", value=DEFAULT_RAMP_MONTHS,\n",
    "    min=0, max=24, step=1, position=\"inline\").value)\n",
    "\n",
    "sites = list(CTM_DEFAULT_SITES)\n",
    "REGION_SITES = region_site_names(sites)\n",
    "agents_by_region = region_agents(sites)\n",
    "\n",
    "# ── Per-region current-state inputs (EDIT as real data arrives) ──────\n",
    "# Seeded as $7.3M × agent share; annual_cost and contract_termination\n",
    "# are the editable inputs.\n",
    "_seed = current_state_inputs(sites)\n",
    "assert abs(_seed[\"annual_cost\"].sum() - TCO_VERBATIM[\"current_annual\"]) < 1\n",
    "\n",
    "current_state = _seed.copy()\n",
    "for _r in REGIONS:\n",
    "    current_state.loc[_r, \"annual_cost\"] = float(mr.NumberInput(\n",
    "        label=f\"{_r} — current platform cost ($/yr)\",\n",
    "        value=round(float(_seed.loc[_r, \"annual_cost\"])),\n",
    "        min=0, max=8_000_000, step=10_000, position=\"inline\").value)\n",
    "    current_state.loc[_r, \"contract_termination\"] = dt.date.fromisoformat(\n",
    "        mr.DateInput(label=f\"{_r} — contract termination\",\n",
    "                     value=_seed.loc[_r, \"contract_termination\"].isoformat(),\n",
    "                     position=\"inline\").value)\n",
    "\n",
    "current_by_year = current_costs_by_year(current_state)\n",
    "\n",
    "print(f\"current-state total {money(current_state['annual_cost'].sum())}/yr \"\n",
    "      f\"(seed: {money(TCO_VERBATIM['current_annual'])}; deck 3-yr \"\n",
    "      f\"{money(TCO_VERBATIM['current_3yr'])} — deck rounding)\")\n",
    "print(f\"existing-platform run-off by year: \"\n",
    "      f\"{ {y: money(v) for y, v in current_by_year.items()} }\")\n",
    "display(current_state)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2caa14c2",
   "metadata": {},
   "source": [
    "<a id=\"section-2\"></a>\n",
    "\n",
    "## 2 · Scenario scope — what the base price includes\n",
    "\n",
    "- **Genesys Cloud CX migration** for all four regions.\n",
    "- **NA — WFM migration**: users move off the existing WFM-like tool onto Genesys WFM. The deck\n",
    "  claims **$0** WFM benefit for NA (\"has similar feature\") — kept verbatim.\n",
    "- **APAC (ANZ + ASIA) — WFM implementation**: net-new WFM capability.\n",
    "- **EMEA — no WFM** in this scenario; the deck's $687K 3-yr EMEA WFM line is dropped\n",
    "  (upside option — toggle it in the sidebar to see the effect).\n",
    "- **Base implementation price covers all of the above**: the verbatim $2.4M PS + $167K\n",
    "  training in year 1. No extra cost lines for migration or WFM.\n",
    "- 🟡 **Licence run-rate assumption**: the $4.3M/yr CCaaS quote includes the WFM/WEM SKUs,\n",
    "  including the new APAC seats. If WEM turns out to be an add-on, raise the run-rate in\n",
    "  the sidebar."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "8bafeb91",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-08T00:44:23.860570Z",
     "iopub.status.busy": "2026-07-08T00:44:23.860456Z",
     "iopub.status.idle": "2026-07-08T00:44:23.871856Z",
     "shell.execute_reply": "2026-07-08T00:44:23.871102Z"
    }
   },
   "outputs": [
    {
     "data": {
      "application/mercury+json": {
       "model_id": "e99ec85877354dd5973cb641103db205",
       "position": "sidebar",
       "widget": "NumberInputWidget"
      },
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "e99ec85877354dd5973cb641103db205",
       "version_major": 2,
       "version_minor": 1
      },
      "text/plain": [
       "<mercury.number.NumberInputWidget object at 0x7c43b26c2b10>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/mercury+json": {
       "model_id": "f6e29bda007a4096a6624a90eb2a7190",
       "position": "sidebar",
       "widget": "CheckboxWidget"
      },
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "f6e29bda007a4096a6624a90eb2a7190",
       "version_major": 2,
       "version_minor": 1
      },
      "text/plain": [
       "<mercury.checkbox.CheckboxWidget object at 0x7c43b26ba510>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/mercury+json": {
       "model_id": "e502bd1367354f3b8cd8e6889a4f4d50",
       "position": "sidebar",
       "widget": "CheckboxWidget"
      },
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "e502bd1367354f3b8cd8e6889a4f4d50",
       "version_major": 2,
       "version_minor": 1
      },
      "text/plain": [
       "<mercury.checkbox.CheckboxWidget object at 0x7c44102baad0>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/mercury+json": {
       "model_id": "7ec342097b35498a8385688f0ab16f13",
       "position": "sidebar",
       "widget": "CheckboxWidget"
      },
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "7ec342097b35498a8385688f0ab16f13",
       "version_major": 2,
       "version_minor": 1
      },
      "text/plain": [
       "<mercury.checkbox.CheckboxWidget object at 0x7c44102ba990>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/mercury+json": {
       "model_id": "f01f7a6671d84072bfb17c43a9844009",
       "position": "sidebar",
       "widget": "CheckboxWidget"
      },
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "f01f7a6671d84072bfb17c43a9844009",
       "version_major": 2,
       "version_minor": 1
      },
      "text/plain": [
       "<mercury.checkbox.CheckboxWidget object at 0x7c43b268fce0>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# ── CONFIG — scenario scope (Mercury sidebar — widgets only, no output) ─\n",
    "LICENCE_ANNUAL = float(mr.NumberInput(\n",
    "    label=\"Genesys licence run-rate ($/yr)\",\n",
    "    value=round(TCO_VERBATIM[\"ccaas_annual\"]),\n",
    "    min=0, max=10_000_000, step=100_000).value)\n",
    "_wfm_na = bool(mr.CheckBox(label=\"WFM — NA (migrate existing users)\", value=True).value)\n",
    "_wfm_anz = bool(mr.CheckBox(label=\"WFM — ANZ (implement)\", value=True).value)\n",
    "_wfm_asia = bool(mr.CheckBox(label=\"WFM — ASIA (implement)\", value=True).value)\n",
    "_wfm_emea = bool(mr.CheckBox(label=\"WFM — EMEA (upside option)\", value=False).value)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "6d971716",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-08T00:44:23.873648Z",
     "iopub.status.busy": "2026-07-08T00:44:23.873485Z",
     "iopub.status.idle": "2026-07-08T00:44:23.887618Z",
     "shell.execute_reply": "2026-07-08T00:44:23.886793Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WFM scope: NA, ANZ, ASIA · licence run-rate $4.3M/yr · ramp 12 months\n",
      "ramp-adjusted licences by year: {2026: '$0K', 2027: '$4.3M', 2028: '$4.3M'}\n",
      "base PS + training (incl. migration + WFM): $2.6M, year 1 only\n"
     ]
    },
    {
     "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>WFM treatment</th>\n",
       "      <th>in scope</th>\n",
       "      <th>verbatim annual</th>\n",
       "      <th>verbatim 3-yr</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>region</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>NA</th>\n",
       "      <td>migrate existing users</td>\n",
       "      <td>✓</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>ANZ</th>\n",
       "      <td>implement (APAC)</td>\n",
       "      <td>✓</td>\n",
       "      <td>1300000</td>\n",
       "      <td>1400000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>EMEA</th>\n",
       "      <td>implement (upside option)</td>\n",
       "      <td>—</td>\n",
       "      <td>824000</td>\n",
       "      <td>687000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>ASIA</th>\n",
       "      <td>implement (APAC)</td>\n",
       "      <td>✓</td>\n",
       "      <td>1600000</td>\n",
       "      <td>914000</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                    WFM treatment in scope  verbatim annual  verbatim 3-yr\n",
       "region                                                                    \n",
       "NA         migrate existing users        ✓                0              0\n",
       "ANZ              implement (APAC)        ✓          1300000        1400000\n",
       "EMEA    implement (upside option)        —           824000         687000\n",
       "ASIA             implement (APAC)        ✓          1600000         914000"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# ── Scope derivation + licence schedule ──────────────────────────────\n",
    "_wfm_on = {\"NA\": _wfm_na, \"ANZ\": _wfm_anz, \"EMEA\": _wfm_emea, \"ASIA\": _wfm_asia}\n",
    "WFM_REGIONS = [r for r in REGIONS if _wfm_on[r]]\n",
    "\n",
    "licence_by_year = licence_costs_by_year(RAMP_MONTHS, LICENCE_ANNUAL)\n",
    "ps_by_year = ps_costs_by_year()   # verbatim $2.4M PS + $167K training, year 1\n",
    "\n",
    "_treat = {\"NA\": \"migrate existing users\", \"ANZ\": \"implement (APAC)\",\n",
    "          \"ASIA\": \"implement (APAC)\", \"EMEA\": \"implement (upside option)\"}\n",
    "wfm_scope_table = pd.DataFrame([\n",
    "    {\"region\": r, \"WFM treatment\": _treat[r],\n",
    "     \"in scope\": \"✓\" if r in WFM_REGIONS else \"—\",\n",
    "     \"verbatim annual\": VERBATIM_BENEFITS[(r, \"WFM\")][0],\n",
    "     \"verbatim 3-yr\": VERBATIM_BENEFITS[(r, \"WFM\")][1]}\n",
    "    for r in REGIONS]).set_index(\"region\")\n",
    "\n",
    "print(f\"WFM scope: {', '.join(WFM_REGIONS) or '(none)'} · \"\n",
    "      f\"licence run-rate {money(LICENCE_ANNUAL)}/yr · ramp {RAMP_MONTHS} months\")\n",
    "print(f\"ramp-adjusted licences by year: \"\n",
    "      f\"{ {y: money(v) for y, v in licence_by_year.items()} }\")\n",
    "print(f\"base PS + training (incl. migration + WFM): {money(ps_by_year[2026])}, year 1 only\")\n",
    "display(wfm_scope_table)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "95b71218",
   "metadata": {},
   "source": [
    "<a id=\"section-3\"></a>\n",
    "\n",
    "## 3 · WFM benefits — verbatim, scoped, schedule-phased\n",
    "\n",
    "Only the deck's WFM lines survive the no-AI cut, and only for the regions in WFM scope.\n",
    "They phase on the same Genesys deployment schedule as the corrected notebook (benefits\n",
    "realize **implementation + 3 months**, inclusive): ANZ realizes Dec 2027, ASIA Jun 2028 —\n",
    "so most of the WFM value lands at the very edge of the 3-year window.\n",
    "\n",
    "*Deck quirk, kept verbatim: ASIA's WFM **annual** value ($1.6M) exceeds its **3-yr** value\n",
    "($914K) — the deck's own late-realization arithmetic. The 3-yr figure is what phases here;\n",
    "the annual figure feeds the post-window run-rate in section 5.*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "12673034",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-08T00:44:23.889872Z",
     "iopub.status.busy": "2026-07-08T00:44:23.889765Z",
     "iopub.status.idle": "2026-07-08T00:44:23.917709Z",
     "shell.execute_reply": "2026-07-08T00:44:23.916976Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WFM benefits by year: {2026: '$0K', 2027: '$108K', 2028: '$2.2M'} → 3-yr $2.3M\n"
     ]
    },
    {
     "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>year</th>\n",
       "      <th>2026</th>\n",
       "      <th>2027</th>\n",
       "      <th>2028</th>\n",
       "      <th>TOTAL</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>region</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>NA</th>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>ANZ</th>\n",
       "      <td>0</td>\n",
       "      <td>107,692</td>\n",
       "      <td>1,292,308</td>\n",
       "      <td>1,400,000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>ASIA</th>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>914,000</td>\n",
       "      <td>914,000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>TOTAL</th>\n",
       "      <td>0</td>\n",
       "      <td>107,692</td>\n",
       "      <td>2,206,308</td>\n",
       "      <td>2,314,000</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "year    2026    2027      2028     TOTAL\n",
       "region                                  \n",
       "NA         0       0         0         0\n",
       "ANZ        0 107,692 1,292,308 1,400,000\n",
       "ASIA       0       0   914,000   914,000\n",
       "TOTAL      0 107,692 2,206,308 2,314,000"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# ── WFM slice of the verbatim benefits (tokencalc.migration_wfm) ─────\n",
    "_, _, BENEFIT_ROLLOUT = build_rollouts(sites, ramp_months=RAMP_MONTHS)\n",
    "wfm_long = wfm_benefits_by_year(BENEFIT_ROLLOUT, WFM_REGIONS)\n",
    "ben_by_year = {y: float(wfm_long.loc[wfm_long.year == y, \"benefit\"].sum())\n",
    "               for y in YEARS}\n",
    "\n",
    "print(f\"WFM benefits by year: { {y: money(v) for y, v in ben_by_year.items()} } \"\n",
    "      f\"→ 3-yr {money(sum(ben_by_year.values()))}\")\n",
    "if wfm_long.empty:\n",
    "    print(\"No WFM regions in scope — pure licence-swap case.\")\n",
    "else:\n",
    "    display(wfm_long.pivot_table(index=\"region\", columns=\"year\", values=\"benefit\",\n",
    "                                 aggfunc=\"sum\", margins=True, margins_name=\"TOTAL\")\n",
    "            .reindex([*[r for r in REGIONS if r in WFM_REGIONS], \"TOTAL\"]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "3cbefa13",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-08T00:44:23.921193Z",
     "iopub.status.busy": "2026-07-08T00:44:23.920886Z",
     "iopub.status.idle": "2026-07-08T00:44:25.049614Z",
     "shell.execute_reply": "2026-07-08T00:44:25.048799Z"
    }
   },
   "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": "NA WFM",
         "type": "bar",
         "x": [
          "2026",
          "2027",
          "2028"
         ],
         "y": [
          0.0,
          0.0,
          0.0
         ]
        },
        {
         "hovertemplate": "%{fullData.name}: %{y:$,.0f}<extra></extra>",
         "marker": {
          "color": "#1baf7a",
          "line": {
           "color": "#fcfcfb",
           "width": 2
          }
         },
         "name": "ANZ WFM",
         "type": "bar",
         "x": [
          "2026",
          "2027",
          "2028"
         ],
         "y": [
          0.0,
          107692.30769230769,
          1292307.6923076923
         ]
        },
        {
         "hovertemplate": "%{fullData.name}: %{y:$,.0f}<extra></extra>",
         "marker": {
          "color": "#eda100",
          "line": {
           "color": "#fcfcfb",
           "width": 2
          }
         },
         "name": "ASIA WFM",
         "type": "bar",
         "x": [
          "2026",
          "2027",
          "2028"
         ],
         "y": [
          0.0,
          0.0,
          914000.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 WFM benefits",
         "type": "scatter",
         "x": [
          "2026",
          "2027",
          "2028"
         ],
         "y": {
          "bdata": "AAAAAAAAAADsxE7sxEr6QP////+Hp0FB",
          "dtype": "f8"
         }
        }
       ],
       "layout": {
        "annotations": [
         {
          "font": {
           "color": "#0b0b0b",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>$0K</b>",
          "x": 0,
          "y": 0.0,
          "yshift": 12
         },
         {
          "font": {
           "color": "#0b0b0b",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>$108K</b>",
          "x": 1,
          "y": 107692.30769230769,
          "yshift": 12
         },
         {
          "font": {
           "color": "#0b0b0b",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>$2.2M</b>",
          "x": 2,
          "y": 2206307.692307692,
          "yshift": 12
         }
        ],
        "bargap": 0.45,
        "barmode": "stack",
        "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>WFM benefits — verbatim, scoped, schedule-phased</b><br><span style='font-size:12px;color:#898781'>NA claims $0 (migration — deck: 'has similar feature') · ANZ realizes Dec 2027, ASIA Jun 2028 — value lands at the edge of the window</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 [r for r in REGIONS if r in WFM_REGIONS]:\n",
    "    _v = [float(wfm_long.query(\"region == @_r and year == @y\")[\"benefit\"].sum())\n",
    "          for y in YEARS]\n",
    "    fig.add_trace(bar(X, _v, f\"{_r} WFM\", REGION_COLOR[_r]))\n",
    "cum_ben = pd.Series([ben_by_year[y] for y in YEARS]).cumsum()\n",
    "fig.add_trace(cum_line(X, cum_ben, \"Cumulative WFM benefits\"))\n",
    "for i, y in enumerate(YEARS):\n",
    "    fig.add_annotation(x=i, y=ben_by_year[y], text=f\"<b>{money(ben_by_year[y])}</b>\",\n",
    "                       showarrow=False, yshift=12, font=dict(size=12, color=INK))\n",
    "fig.update_layout(barmode=\"stack\")\n",
    "tei_layout(fig, \"WFM benefits — verbatim, scoped, schedule-phased\",\n",
    "           \"NA claims $0 (migration — deck: 'has similar feature') · ANZ realizes \"\n",
    "           \"Dec 2027, ASIA Jun 2028 — value lands at the edge of the window\", height=420)\n",
    "fig.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "059bd0f9",
   "metadata": {},
   "source": [
    "<a id=\"section-4\"></a>\n",
    "\n",
    "## 4 · Cost stack — licences, base PS, double-billing\n",
    "\n",
    "Three lines only. The AI token-consumption and AI implementation lines are gone —\n",
    "that is the point of this scenario. What remains is the deck's own cost case plus the\n",
    "two contract mechanics it ignored: the **ramp credit** (licences bill from month 13)\n",
    "and the **term-contract run-off** (double-billing until 31 Dec 2027)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "ccde902f",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-08T00:44:25.052873Z",
     "iopub.status.busy": "2026-07-08T00:44:25.052680Z",
     "iopub.status.idle": "2026-07-08T00:44:25.064092Z",
     "shell.execute_reply": "2026-07-08T00:44:25.063634Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Migration programme cost: {2026: '$9.9M', 2027: '$11.6M', 2028: '$4.3M'} → 3-yr $25.8M\n",
      "Do nothing: $7.3M/yr → 3-yr $21.9M\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>2026</th>\n",
       "      <th>2027</th>\n",
       "      <th>2028</th>\n",
       "      <th>3-yr</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>CCaaS platform licences (ramp-adjusted)</th>\n",
       "      <td>0</td>\n",
       "      <td>4,300,000</td>\n",
       "      <td>4,300,000</td>\n",
       "      <td>8,600,000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Base PS + training (incl. migration + WFM)</th>\n",
       "      <td>2,567,000</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>2,567,000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Existing platform (term-contract run-off)</th>\n",
       "      <td>7,300,001</td>\n",
       "      <td>7,300,001</td>\n",
       "      <td>0</td>\n",
       "      <td>14,600,002</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>TOTAL</th>\n",
       "      <td>9,867,001</td>\n",
       "      <td>11,600,001</td>\n",
       "      <td>4,300,000</td>\n",
       "      <td>25,767,002</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                                2026       2027      2028  \\\n",
       "CCaaS platform licences (ramp-adjusted)            0  4,300,000 4,300,000   \n",
       "Base PS + training (incl. migration + WFM) 2,567,000          0         0   \n",
       "Existing platform (term-contract run-off)  7,300,001  7,300,001         0   \n",
       "TOTAL                                      9,867,001 11,600,001 4,300,000   \n",
       "\n",
       "                                                 3-yr  \n",
       "CCaaS platform licences (ramp-adjusted)     8,600,000  \n",
       "Base PS + training (incl. migration + WFM)  2,567,000  \n",
       "Existing platform (term-contract run-off)  14,600,002  \n",
       "TOTAL                                      25,767,002  "
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "migration_costs = pd.DataFrame({\n",
    "    \"CCaaS platform licences (ramp-adjusted)\": licence_by_year,\n",
    "    \"Base PS + training (incl. migration + WFM)\": ps_by_year,\n",
    "    \"Existing platform (term-contract run-off)\": current_by_year,\n",
    "}).T[YEARS]\n",
    "migration_costs[\"3-yr\"] = migration_costs.sum(axis=1)\n",
    "total_by_year = {y: float(migration_costs[y].sum()) for y in YEARS}\n",
    "\n",
    "BASELINE_ANNUAL = TCO_VERBATIM[\"current_annual\"]\n",
    "donothing_by_year = {y: BASELINE_ANNUAL for y in YEARS}\n",
    "\n",
    "print(f\"Migration programme cost: { {y: money(v) for y, v in total_by_year.items()} } \"\n",
    "      f\"→ 3-yr {money(sum(total_by_year.values()))}\")\n",
    "print(f\"Do nothing: {money(BASELINE_ANNUAL)}/yr → 3-yr {money(3 * BASELINE_ANNUAL)}\")\n",
    "display(pd.concat([migration_costs,\n",
    "                   pd.DataFrame(migration_costs.sum()).T.set_axis([\"TOTAL\"])]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "c058e795",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-08T00:44:25.066052Z",
     "iopub.status.busy": "2026-07-08T00:44:25.065901Z",
     "iopub.status.idle": "2026-07-08T00:44:25.091401Z",
     "shell.execute_reply": "2026-07-08T00:44:25.090725Z"
    }
   },
   "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": "CCaaS platform licences (ramp-adjusted)",
         "type": "bar",
         "x": [
          "2026",
          "2027",
          "2028"
         ],
         "y": [
          0.0,
          4300000.0,
          4300000.0
         ]
        },
        {
         "hovertemplate": "%{fullData.name}: %{y:$,.0f}<extra></extra>",
         "marker": {
          "color": "#1baf7a",
          "line": {
           "color": "#fcfcfb",
           "width": 2
          }
         },
         "name": "Base PS + training (incl. migration + WFM)",
         "type": "bar",
         "x": [
          "2026",
          "2027",
          "2028"
         ],
         "y": [
          2567000.0,
          0.0,
          0.0
         ]
        },
        {
         "hovertemplate": "%{fullData.name}: %{y:$,.0f}<extra></extra>",
         "marker": {
          "color": "#eda100",
          "line": {
           "color": "#fcfcfb",
           "width": 2
          }
         },
         "name": "Existing platform (term-contract run-off)",
         "type": "bar",
         "x": [
          "2026",
          "2027",
          "2028"
         ],
         "y": [
          7300001.0,
          7300001.0,
          0.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 — migration programme",
         "type": "scatter",
         "x": [
          "2026",
          "2027",
          "2028"
         ],
         "y": {
          "bdata": "AAAAIN/RYkEAAACg93h0QQAAAKDFknhB",
          "dtype": "f8"
         }
        },
        {
         "hovertemplate": "%{fullData.name}: %{y:$,.0f}<extra></extra>",
         "line": {
          "color": "#c3c2b7",
          "dash": "dash",
          "width": 2
         },
         "marker": {
          "line": {
           "color": "#fcfcfb",
           "width": 2
          },
          "size": 8
         },
         "mode": "lines+markers",
         "name": "Cumulative — do nothing",
         "type": "scatter",
         "x": [
          "2026",
          "2027",
          "2028"
         ],
         "y": {
          "bdata": "oGNvAEDH3gDgKk4B",
          "dtype": "i4"
         }
        }
       ],
       "layout": {
        "annotations": [
         {
          "font": {
           "color": "#0b0b0b",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>$9.9M</b>",
          "x": 0,
          "y": 9867001.0,
          "yshift": 12
         },
         {
          "font": {
           "color": "#0b0b0b",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>$11.6M</b>",
          "x": 1,
          "y": 11600001.0,
          "yshift": 12
         },
         {
          "font": {
           "color": "#0b0b0b",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>$4.3M</b>",
          "x": 2,
          "y": 4300000.0,
          "yshift": 12
         },
         {
          "font": {
           "color": "#52514e",
           "size": 12
          },
          "showarrow": false,
          "text": "3-yr <b>&#36;25.8M</b> — &#36;3.9M above doing nothing",
          "x": 2,
          "xshift": -70,
          "y": 25767002.0,
          "yshift": 18
         }
        ],
        "bargap": 0.45,
        "barmode": "stack",
        "font": {
         "color": "#52514e",
         "family": "system-ui, -apple-system, \"Segoe UI\", sans-serif",
         "size": 12
        },
        "height": 500,
        "hovermode": "x unified",
        "legend": {
         "font": {
          "color": "#52514e",
          "size": 11
         },
         "orientation": "h",
         "x": 0,
         "y": -0.1,
         "yanchor": "top"
        },
        "margin": {
         "b": 80,
         "l": 70,
         "r": 30,
         "t": 70
        },
        "paper_bgcolor": "#fcfcfb",
        "plot_bgcolor": "#fcfcfb",
        "template": {
         "data": {
          "bar": [
           {
            "error_x": {
             "color": "#2a3f5f"
            },
            "error_y": {
             "color": "#2a3f5f"
            },
            "marker": {
             "line": {
              "color": "#E5ECF6",
              "width": 0.5
             },
             "pattern": {
              "fillmode": "overlay",
              "size": 10,
              "solidity": 0.2
             }
            },
            "type": "bar"
           }
          ],
          "barpolar": [
           {
            "marker": {
             "line": {
              "color": "#E5ECF6",
              "width": 0.5
             },
             "pattern": {
              "fillmode": "overlay",
              "size": 10,
              "solidity": 0.2
             }
            },
            "type": "barpolar"
           }
          ],
          "carpet": [
           {
            "aaxis": {
             "endlinecolor": "#2a3f5f",
             "gridcolor": "white",
             "linecolor": "white",
             "minorgridcolor": "white",
             "startlinecolor": "#2a3f5f"
            },
            "baxis": {
             "endlinecolor": "#2a3f5f",
             "gridcolor": "white",
             "linecolor": "white",
             "minorgridcolor": "white",
             "startlinecolor": "#2a3f5f"
            },
            "type": "carpet"
           }
          ],
          "choropleth": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "type": "choropleth"
           }
          ],
          "contour": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "colorscale": [
             [
              0.0,
              "#0d0887"
             ],
             [
              0.1111111111111111,
              "#46039f"
             ],
             [
              0.2222222222222222,
              "#7201a8"
             ],
             [
              0.3333333333333333,
              "#9c179e"
             ],
             [
              0.4444444444444444,
              "#bd3786"
             ],
             [
              0.5555555555555556,
              "#d8576b"
             ],
             [
              0.6666666666666666,
              "#ed7953"
             ],
             [
              0.7777777777777778,
              "#fb9f3a"
             ],
             [
              0.8888888888888888,
              "#fdca26"
             ],
             [
              1.0,
              "#f0f921"
             ]
            ],
            "type": "contour"
           }
          ],
          "contourcarpet": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "type": "contourcarpet"
           }
          ],
          "heatmap": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "colorscale": [
             [
              0.0,
              "#0d0887"
             ],
             [
              0.1111111111111111,
              "#46039f"
             ],
             [
              0.2222222222222222,
              "#7201a8"
             ],
             [
              0.3333333333333333,
              "#9c179e"
             ],
             [
              0.4444444444444444,
              "#bd3786"
             ],
             [
              0.5555555555555556,
              "#d8576b"
             ],
             [
              0.6666666666666666,
              "#ed7953"
             ],
             [
              0.7777777777777778,
              "#fb9f3a"
             ],
             [
              0.8888888888888888,
              "#fdca26"
             ],
             [
              1.0,
              "#f0f921"
             ]
            ],
            "type": "heatmap"
           }
          ],
          "histogram": [
           {
            "marker": {
             "pattern": {
              "fillmode": "overlay",
              "size": 10,
              "solidity": 0.2
             }
            },
            "type": "histogram"
           }
          ],
          "histogram2d": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "colorscale": [
             [
              0.0,
              "#0d0887"
             ],
             [
              0.1111111111111111,
              "#46039f"
             ],
             [
              0.2222222222222222,
              "#7201a8"
             ],
             [
              0.3333333333333333,
              "#9c179e"
             ],
             [
              0.4444444444444444,
              "#bd3786"
             ],
             [
              0.5555555555555556,
              "#d8576b"
             ],
             [
              0.6666666666666666,
              "#ed7953"
             ],
             [
              0.7777777777777778,
              "#fb9f3a"
             ],
             [
              0.8888888888888888,
              "#fdca26"
             ],
             [
              1.0,
              "#f0f921"
             ]
            ],
            "type": "histogram2d"
           }
          ],
          "histogram2dcontour": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "colorscale": [
             [
              0.0,
              "#0d0887"
             ],
             [
              0.1111111111111111,
              "#46039f"
             ],
             [
              0.2222222222222222,
              "#7201a8"
             ],
             [
              0.3333333333333333,
              "#9c179e"
             ],
             [
              0.4444444444444444,
              "#bd3786"
             ],
             [
              0.5555555555555556,
              "#d8576b"
             ],
             [
              0.6666666666666666,
              "#ed7953"
             ],
             [
              0.7777777777777778,
              "#fb9f3a"
             ],
             [
              0.8888888888888888,
              "#fdca26"
             ],
             [
              1.0,
              "#f0f921"
             ]
            ],
            "type": "histogram2dcontour"
           }
          ],
          "mesh3d": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "type": "mesh3d"
           }
          ],
          "parcoords": [
           {
            "line": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "parcoords"
           }
          ],
          "pie": [
           {
            "automargin": true,
            "type": "pie"
           }
          ],
          "scatter": [
           {
            "fillpattern": {
             "fillmode": "overlay",
             "size": 10,
             "solidity": 0.2
            },
            "type": "scatter"
           }
          ],
          "scatter3d": [
           {
            "line": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scatter3d"
           }
          ],
          "scattercarpet": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scattercarpet"
           }
          ],
          "scattergeo": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scattergeo"
           }
          ],
          "scattergl": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scattergl"
           }
          ],
          "scattermap": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scattermap"
           }
          ],
          "scattermapbox": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scattermapbox"
           }
          ],
          "scatterpolar": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scatterpolar"
           }
          ],
          "scatterpolargl": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scatterpolargl"
           }
          ],
          "scatterternary": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scatterternary"
           }
          ],
          "surface": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "colorscale": [
             [
              0.0,
              "#0d0887"
             ],
             [
              0.1111111111111111,
              "#46039f"
             ],
             [
              0.2222222222222222,
              "#7201a8"
             ],
             [
              0.3333333333333333,
              "#9c179e"
             ],
             [
              0.4444444444444444,
              "#bd3786"
             ],
             [
              0.5555555555555556,
              "#d8576b"
             ],
             [
              0.6666666666666666,
              "#ed7953"
             ],
             [
              0.7777777777777778,
              "#fb9f3a"
             ],
             [
              0.8888888888888888,
              "#fdca26"
             ],
             [
              1.0,
              "#f0f921"
             ]
            ],
            "type": "surface"
           }
          ],
          "table": [
           {
            "cells": {
             "fill": {
              "color": "#EBF0F8"
             },
             "line": {
              "color": "white"
             }
            },
            "header": {
             "fill": {
              "color": "#C8D4E3"
             },
             "line": {
              "color": "white"
             }
            },
            "type": "table"
           }
          ]
         },
         "layout": {
          "annotationdefaults": {
           "arrowcolor": "#2a3f5f",
           "arrowhead": 0,
           "arrowwidth": 1
          },
          "autotypenumbers": "strict",
          "coloraxis": {
           "colorbar": {
            "outlinewidth": 0,
            "ticks": ""
           }
          },
          "colorscale": {
           "diverging": [
            [
             0,
             "#8e0152"
            ],
            [
             0.1,
             "#c51b7d"
            ],
            [
             0.2,
             "#de77ae"
            ],
            [
             0.3,
             "#f1b6da"
            ],
            [
             0.4,
             "#fde0ef"
            ],
            [
             0.5,
             "#f7f7f7"
            ],
            [
             0.6,
             "#e6f5d0"
            ],
            [
             0.7,
             "#b8e186"
            ],
            [
             0.8,
             "#7fbc41"
            ],
            [
             0.9,
             "#4d9221"
            ],
            [
             1,
             "#276419"
            ]
           ],
           "sequential": [
            [
             0.0,
             "#0d0887"
            ],
            [
             0.1111111111111111,
             "#46039f"
            ],
            [
             0.2222222222222222,
             "#7201a8"
            ],
            [
             0.3333333333333333,
             "#9c179e"
            ],
            [
             0.4444444444444444,
             "#bd3786"
            ],
            [
             0.5555555555555556,
             "#d8576b"
            ],
            [
             0.6666666666666666,
             "#ed7953"
            ],
            [
             0.7777777777777778,
             "#fb9f3a"
            ],
            [
             0.8888888888888888,
             "#fdca26"
            ],
            [
             1.0,
             "#f0f921"
            ]
           ],
           "sequentialminus": [
            [
             0.0,
             "#0d0887"
            ],
            [
             0.1111111111111111,
             "#46039f"
            ],
            [
             0.2222222222222222,
             "#7201a8"
            ],
            [
             0.3333333333333333,
             "#9c179e"
            ],
            [
             0.4444444444444444,
             "#bd3786"
            ],
            [
             0.5555555555555556,
             "#d8576b"
            ],
            [
             0.6666666666666666,
             "#ed7953"
            ],
            [
             0.7777777777777778,
             "#fb9f3a"
            ],
            [
             0.8888888888888888,
             "#fdca26"
            ],
            [
             1.0,
             "#f0f921"
            ]
           ]
          },
          "colorway": [
           "#636efa",
           "#EF553B",
           "#00cc96",
           "#ab63fa",
           "#FFA15A",
           "#19d3f3",
           "#FF6692",
           "#B6E880",
           "#FF97FF",
           "#FECB52"
          ],
          "font": {
           "color": "#2a3f5f"
          },
          "geo": {
           "bgcolor": "white",
           "lakecolor": "white",
           "landcolor": "#E5ECF6",
           "showlakes": true,
           "showland": true,
           "subunitcolor": "white"
          },
          "hoverlabel": {
           "align": "left"
          },
          "hovermode": "closest",
          "mapbox": {
           "style": "light"
          },
          "paper_bgcolor": "white",
          "plot_bgcolor": "#E5ECF6",
          "polar": {
           "angularaxis": {
            "gridcolor": "white",
            "linecolor": "white",
            "ticks": ""
           },
           "bgcolor": "#E5ECF6",
           "radialaxis": {
            "gridcolor": "white",
            "linecolor": "white",
            "ticks": ""
           }
          },
          "scene": {
           "xaxis": {
            "backgroundcolor": "#E5ECF6",
            "gridcolor": "white",
            "gridwidth": 2,
            "linecolor": "white",
            "showbackground": true,
            "ticks": "",
            "zerolinecolor": "white"
           },
           "yaxis": {
            "backgroundcolor": "#E5ECF6",
            "gridcolor": "white",
            "gridwidth": 2,
            "linecolor": "white",
            "showbackground": true,
            "ticks": "",
            "zerolinecolor": "white"
           },
           "zaxis": {
            "backgroundcolor": "#E5ECF6",
            "gridcolor": "white",
            "gridwidth": 2,
            "linecolor": "white",
            "showbackground": true,
            "ticks": "",
            "zerolinecolor": "white"
           }
          },
          "shapedefaults": {
           "line": {
            "color": "#2a3f5f"
           }
          },
          "ternary": {
           "aaxis": {
            "gridcolor": "white",
            "linecolor": "white",
            "ticks": ""
           },
           "baxis": {
            "gridcolor": "white",
            "linecolor": "white",
            "ticks": ""
           },
           "bgcolor": "#E5ECF6",
           "caxis": {
            "gridcolor": "white",
            "linecolor": "white",
            "ticks": ""
           }
          },
          "title": {
           "x": 0.05
          },
          "xaxis": {
           "automargin": true,
           "gridcolor": "white",
           "linecolor": "white",
           "ticks": "",
           "title": {
            "standoff": 15
           },
           "zerolinecolor": "white",
           "zerolinewidth": 2
          },
          "yaxis": {
           "automargin": true,
           "gridcolor": "white",
           "linecolor": "white",
           "ticks": "",
           "title": {
            "standoff": 15
           },
           "zerolinecolor": "white",
           "zerolinewidth": 2
          }
         }
        },
        "title": {
         "font": {
          "color": "#0b0b0b",
          "size": 16
         },
         "text": "<b>Migration programme cost vs doing nothing</b><br><span style='font-size:12px;color:#898781'>Double-billing hits 2026-27 while existing term contracts run off · licences ramp-free for 12 months, then bill at the run-rate</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 line in migration_costs.index:\n",
    "    fig.add_trace(bar(X, [migration_costs.loc[line, y] for y in YEARS],\n",
    "                      line, COST_COLOR[line]))\n",
    "cum_mig = pd.Series([total_by_year[y] for y in YEARS]).cumsum()\n",
    "cum_nothing = pd.Series([donothing_by_year[y] for y in YEARS]).cumsum()\n",
    "fig.add_trace(cum_line(X, cum_mig, \"Cumulative — migration programme\"))\n",
    "fig.add_trace(cum_line(X, cum_nothing, \"Cumulative — do nothing\", color=CONTEXT, dash=\"dash\"))\n",
    "for i, y in enumerate(YEARS):\n",
    "    fig.add_annotation(x=i, y=total_by_year[y], text=f\"<b>{money(total_by_year[y])}</b>\",\n",
    "                       showarrow=False, yshift=12, font=dict(size=12, color=INK))\n",
    "_delta = float(cum_mig.iloc[-1] - cum_nothing.iloc[-1])\n",
    "fig.add_annotation(x=len(YEARS) - 1, y=float(cum_mig.iloc[-1]),\n",
    "                   text=f\"3-yr <b>{html_money(float(cum_mig.iloc[-1]))}</b> — \"\n",
    "                        f\"{html_money(_delta)} above doing nothing\",\n",
    "                   showarrow=False, yshift=18, xshift=-70, font=dict(size=12, color=INK2))\n",
    "fig.update_layout(barmode=\"stack\")\n",
    "tei_layout(fig, \"Migration programme cost vs doing nothing\",\n",
    "           \"Double-billing hits 2026-27 while existing term contracts run off · \"\n",
    "           \"licences ramp-free for 12 months, then bill at the run-rate\", height=500)\n",
    "fig.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f24b1255",
   "metadata": {},
   "source": [
    "<a id=\"section-5\"></a>\n",
    "\n",
    "## 5 · Business case vs do nothing\n",
    "\n",
    "**Frame:** identical to the corrected notebook — baseline-relative, against *do nothing*\n",
    "(keep paying $7.3M/yr). Incremental cost = programme cost − baseline; net = scoped WFM\n",
    "benefits − incremental cost. The 2026–27 double-billing penalty and the 2028 cost-avoidance\n",
    "credit both fall out of this one frame.\n",
    "\n",
    "Inside the deck's own 2026–2028 window the migration-only case is **net-negative** — the\n",
    "licence saving and WFM value arrive too late to cover the PS outlay and double-billing.\n",
    "The **run-rate breakeven** KPI extrapolates past the window: once contracts are terminated\n",
    "and the ramp is over, the steady-state saving is (baseline − licence run-rate) + scoped\n",
    "WFM annual values.\n",
    "\n",
    "*Not modelled: early-termination fees, and migration costs beyond the base PS line.*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "d8293152",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-08T00:44:25.093535Z",
     "iopub.status.busy": "2026-07-08T00:44:25.093403Z",
     "iopub.status.idle": "2026-07-08T00:44:25.098467Z",
     "shell.execute_reply": "2026-07-08T00:44:25.097960Z"
    }
   },
   "outputs": [
    {
     "data": {
      "application/mercury+json": {
       "model_id": "418f2449106245368a83b6674d042595",
       "position": "sidebar",
       "widget": "SelectWidget"
      },
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "418f2449106245368a83b6674d042595",
       "version_major": 2,
       "version_minor": 1
      },
      "text/plain": [
       "<mercury.select.SelectWidget object at 0x7c436f8692b0>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# ── NPV rate (Mercury sidebar — widgets only, no output) ────────────\n",
    "_rate_pick = mr.Select(label=\"NPV discount rate\", value=\"13.5% (deck)\",\n",
    "                       choices=[\"13.5% (deck)\", \"8.0% (CTM treasury)\"]).value\n",
    "DISCOUNT_RATE = (TCO_VERBATIM[\"npv_discount_rate\"]\n",
    "                 if _rate_pick.startswith(\"13.5\") else 0.08)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "c89973af",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-08T00:44:25.100178Z",
     "iopub.status.busy": "2026-07-08T00:44:25.100038Z",
     "iopub.status.idle": "2026-07-08T00:44:25.104456Z",
     "shell.execute_reply": "2026-07-08T00:44:25.103875Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "net by year: {2026: '-$2.6M', 2027: '-$4.2M', 2028: '$5.2M'} → 3-yr -$1.6M\n",
      "steady-state run-rate saving $5.9M/yr ($3.0M licences + $2.9M WFM annuals) → breakeven 40 months (~Apr 2029, extrapolated)\n"
     ]
    }
   ],
   "source": [
    "# Baseline-relative frame (tokencalc.appendix4.case_flows / case_kpis):\n",
    "# baseline = do nothing = keep paying $7.3M/yr.\n",
    "inc, net_by = case_flows(total_by_year, ben_by_year)\n",
    "kpi = case_kpis(inc, net_by, DISCOUNT_RATE)\n",
    "\n",
    "# Steady-state run-rate once contracts end and the ramp is over\n",
    "# (tokencalc.migration_wfm) — fills the end-2028 deficit.\n",
    "RUNRATE_SAVING = runrate_saving_annual(LICENCE_ANNUAL, WFM_REGIONS)\n",
    "BREAKEVEN = runrate_breakeven_label(net_by, RUNRATE_SAVING)\n",
    "\n",
    "\n",
    "def fmt_roi(k):\n",
    "    return f\"{k['roi']:.0%}\" if k[\"roi\"] is not None else \"n/a — net cost saving\"\n",
    "\n",
    "\n",
    "print(f\"net by year: { {y: money(v) for y, v in net_by.items()} } \"\n",
    "      f\"→ 3-yr {money(kpi['net_3yr'])}\")\n",
    "print(f\"steady-state run-rate saving {money(RUNRATE_SAVING)}/yr \"\n",
    "      f\"({money(BASELINE_ANNUAL - LICENCE_ANNUAL)} licences + \"\n",
    "      f\"{money(wfm_annual_runrate(WFM_REGIONS))} WFM annuals) → breakeven {BREAKEVEN}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "9d135a19",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-08T00:44:25.106291Z",
     "iopub.status.busy": "2026-07-08T00:44:25.106186Z",
     "iopub.status.idle": "2026-07-08T00:44:25.128479Z",
     "shell.execute_reply": "2026-07-08T00:44:25.127819Z"
    }
   },
   "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": "WFM benefits (verbatim, scoped)",
         "type": "bar",
         "x": [
          "2026",
          "2027",
          "2028"
         ],
         "y": [
          0.0,
          107692.30769230769,
          2206307.692307692
         ]
        },
        {
         "hovertemplate": "%{fullData.name}: %{y:$,.0f}<extra></extra>",
         "marker": {
          "color": "#e34948",
          "line": {
           "color": "#fcfcfb",
           "width": 2
          }
         },
         "name": "Incremental cost vs $7.3M/yr baseline",
         "type": "bar",
         "x": [
          "2026",
          "2027",
          "2028"
         ],
         "y": [
          -2567001.0,
          -4300001.0,
          3000000.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": [
          "2026",
          "2027",
          "2028"
         ],
         "y": {
          "bdata": "AAAAgKyVQ8HsxE5s48hZwQAAAABqsjfB",
          "dtype": "f8"
         }
        }
       ],
       "layout": {
        "annotations": [
         {
          "font": {
           "color": "#0b0b0b",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>-$2.6M</b>",
          "x": 0,
          "y": -2567001.0,
          "yshift": -14
         },
         {
          "font": {
           "color": "#0b0b0b",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>-$6.8M</b>",
          "x": 1,
          "y": -6759309.692307692,
          "yshift": -14
         },
         {
          "font": {
           "color": "#0b0b0b",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>-$1.6M</b>",
          "x": 2,
          "y": -1553002.0,
          "yshift": -14
         },
         {
          "align": "left",
          "bgcolor": "#fcfcfb",
          "bordercolor": "#e1e0d9",
          "borderwidth": 1,
          "font": {
           "color": "#52514e",
           "size": 12
          },
          "showarrow": false,
          "text": "3-yr net <b>-&#36;1.6M</b> · ROI <b>-40%</b><br>NPV@13.5% <b>-&#36;2.0M</b> · payback <b>beyond 2028</b><br>run-rate <b>&#36;5.9M/yr</b> post-window → breakeven <b>40 months (~Apr 2029, extrapolated)</b>",
          "x": 0.01,
          "xref": "paper",
          "y": 0.98,
          "yref": "paper"
         }
        ],
        "bargap": 0.45,
        "barmode": "relative",
        "font": {
         "color": "#52514e",
         "family": "system-ui, -apple-system, \"Segoe UI\", sans-serif",
         "size": 12
        },
        "height": 500,
        "hovermode": "x unified",
        "legend": {
         "font": {
          "color": "#52514e",
          "size": 11
         },
         "orientation": "h",
         "x": 0,
         "y": -0.1,
         "yanchor": "top"
        },
        "margin": {
         "b": 80,
         "l": 70,
         "r": 30,
         "t": 70
        },
        "paper_bgcolor": "#fcfcfb",
        "plot_bgcolor": "#fcfcfb",
        "template": {
         "data": {
          "bar": [
           {
            "error_x": {
             "color": "#2a3f5f"
            },
            "error_y": {
             "color": "#2a3f5f"
            },
            "marker": {
             "line": {
              "color": "#E5ECF6",
              "width": 0.5
             },
             "pattern": {
              "fillmode": "overlay",
              "size": 10,
              "solidity": 0.2
             }
            },
            "type": "bar"
           }
          ],
          "barpolar": [
           {
            "marker": {
             "line": {
              "color": "#E5ECF6",
              "width": 0.5
             },
             "pattern": {
              "fillmode": "overlay",
              "size": 10,
              "solidity": 0.2
             }
            },
            "type": "barpolar"
           }
          ],
          "carpet": [
           {
            "aaxis": {
             "endlinecolor": "#2a3f5f",
             "gridcolor": "white",
             "linecolor": "white",
             "minorgridcolor": "white",
             "startlinecolor": "#2a3f5f"
            },
            "baxis": {
             "endlinecolor": "#2a3f5f",
             "gridcolor": "white",
             "linecolor": "white",
             "minorgridcolor": "white",
             "startlinecolor": "#2a3f5f"
            },
            "type": "carpet"
           }
          ],
          "choropleth": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "type": "choropleth"
           }
          ],
          "contour": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "colorscale": [
             [
              0.0,
              "#0d0887"
             ],
             [
              0.1111111111111111,
              "#46039f"
             ],
             [
              0.2222222222222222,
              "#7201a8"
             ],
             [
              0.3333333333333333,
              "#9c179e"
             ],
             [
              0.4444444444444444,
              "#bd3786"
             ],
             [
              0.5555555555555556,
              "#d8576b"
             ],
             [
              0.6666666666666666,
              "#ed7953"
             ],
             [
              0.7777777777777778,
              "#fb9f3a"
             ],
             [
              0.8888888888888888,
              "#fdca26"
             ],
             [
              1.0,
              "#f0f921"
             ]
            ],
            "type": "contour"
           }
          ],
          "contourcarpet": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "type": "contourcarpet"
           }
          ],
          "heatmap": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "colorscale": [
             [
              0.0,
              "#0d0887"
             ],
             [
              0.1111111111111111,
              "#46039f"
             ],
             [
              0.2222222222222222,
              "#7201a8"
             ],
             [
              0.3333333333333333,
              "#9c179e"
             ],
             [
              0.4444444444444444,
              "#bd3786"
             ],
             [
              0.5555555555555556,
              "#d8576b"
             ],
             [
              0.6666666666666666,
              "#ed7953"
             ],
             [
              0.7777777777777778,
              "#fb9f3a"
             ],
             [
              0.8888888888888888,
              "#fdca26"
             ],
             [
              1.0,
              "#f0f921"
             ]
            ],
            "type": "heatmap"
           }
          ],
          "histogram": [
           {
            "marker": {
             "pattern": {
              "fillmode": "overlay",
              "size": 10,
              "solidity": 0.2
             }
            },
            "type": "histogram"
           }
          ],
          "histogram2d": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "colorscale": [
             [
              0.0,
              "#0d0887"
             ],
             [
              0.1111111111111111,
              "#46039f"
             ],
             [
              0.2222222222222222,
              "#7201a8"
             ],
             [
              0.3333333333333333,
              "#9c179e"
             ],
             [
              0.4444444444444444,
              "#bd3786"
             ],
             [
              0.5555555555555556,
              "#d8576b"
             ],
             [
              0.6666666666666666,
              "#ed7953"
             ],
             [
              0.7777777777777778,
              "#fb9f3a"
             ],
             [
              0.8888888888888888,
              "#fdca26"
             ],
             [
              1.0,
              "#f0f921"
             ]
            ],
            "type": "histogram2d"
           }
          ],
          "histogram2dcontour": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "colorscale": [
             [
              0.0,
              "#0d0887"
             ],
             [
              0.1111111111111111,
              "#46039f"
             ],
             [
              0.2222222222222222,
              "#7201a8"
             ],
             [
              0.3333333333333333,
              "#9c179e"
             ],
             [
              0.4444444444444444,
              "#bd3786"
             ],
             [
              0.5555555555555556,
              "#d8576b"
             ],
             [
              0.6666666666666666,
              "#ed7953"
             ],
             [
              0.7777777777777778,
              "#fb9f3a"
             ],
             [
              0.8888888888888888,
              "#fdca26"
             ],
             [
              1.0,
              "#f0f921"
             ]
            ],
            "type": "histogram2dcontour"
           }
          ],
          "mesh3d": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "type": "mesh3d"
           }
          ],
          "parcoords": [
           {
            "line": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "parcoords"
           }
          ],
          "pie": [
           {
            "automargin": true,
            "type": "pie"
           }
          ],
          "scatter": [
           {
            "fillpattern": {
             "fillmode": "overlay",
             "size": 10,
             "solidity": 0.2
            },
            "type": "scatter"
           }
          ],
          "scatter3d": [
           {
            "line": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scatter3d"
           }
          ],
          "scattercarpet": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scattercarpet"
           }
          ],
          "scattergeo": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scattergeo"
           }
          ],
          "scattergl": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scattergl"
           }
          ],
          "scattermap": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scattermap"
           }
          ],
          "scattermapbox": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scattermapbox"
           }
          ],
          "scatterpolar": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scatterpolar"
           }
          ],
          "scatterpolargl": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scatterpolargl"
           }
          ],
          "scatterternary": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scatterternary"
           }
          ],
          "surface": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "colorscale": [
             [
              0.0,
              "#0d0887"
             ],
             [
              0.1111111111111111,
              "#46039f"
             ],
             [
              0.2222222222222222,
              "#7201a8"
             ],
             [
              0.3333333333333333,
              "#9c179e"
             ],
             [
              0.4444444444444444,
              "#bd3786"
             ],
             [
              0.5555555555555556,
              "#d8576b"
             ],
             [
              0.6666666666666666,
              "#ed7953"
             ],
             [
              0.7777777777777778,
              "#fb9f3a"
             ],
             [
              0.8888888888888888,
              "#fdca26"
             ],
             [
              1.0,
              "#f0f921"
             ]
            ],
            "type": "surface"
           }
          ],
          "table": [
           {
            "cells": {
             "fill": {
              "color": "#EBF0F8"
             },
             "line": {
              "color": "white"
             }
            },
            "header": {
             "fill": {
              "color": "#C8D4E3"
             },
             "line": {
              "color": "white"
             }
            },
            "type": "table"
           }
          ]
         },
         "layout": {
          "annotationdefaults": {
           "arrowcolor": "#2a3f5f",
           "arrowhead": 0,
           "arrowwidth": 1
          },
          "autotypenumbers": "strict",
          "coloraxis": {
           "colorbar": {
            "outlinewidth": 0,
            "ticks": ""
           }
          },
          "colorscale": {
           "diverging": [
            [
             0,
             "#8e0152"
            ],
            [
             0.1,
             "#c51b7d"
            ],
            [
             0.2,
             "#de77ae"
            ],
            [
             0.3,
             "#f1b6da"
            ],
            [
             0.4,
             "#fde0ef"
            ],
            [
             0.5,
             "#f7f7f7"
            ],
            [
             0.6,
             "#e6f5d0"
            ],
            [
             0.7,
             "#b8e186"
            ],
            [
             0.8,
             "#7fbc41"
            ],
            [
             0.9,
             "#4d9221"
            ],
            [
             1,
             "#276419"
            ]
           ],
           "sequential": [
            [
             0.0,
             "#0d0887"
            ],
            [
             0.1111111111111111,
             "#46039f"
            ],
            [
             0.2222222222222222,
             "#7201a8"
            ],
            [
             0.3333333333333333,
             "#9c179e"
            ],
            [
             0.4444444444444444,
             "#bd3786"
            ],
            [
             0.5555555555555556,
             "#d8576b"
            ],
            [
             0.6666666666666666,
             "#ed7953"
            ],
            [
             0.7777777777777778,
             "#fb9f3a"
            ],
            [
             0.8888888888888888,
             "#fdca26"
            ],
            [
             1.0,
             "#f0f921"
            ]
           ],
           "sequentialminus": [
            [
             0.0,
             "#0d0887"
            ],
            [
             0.1111111111111111,
             "#46039f"
            ],
            [
             0.2222222222222222,
             "#7201a8"
            ],
            [
             0.3333333333333333,
             "#9c179e"
            ],
            [
             0.4444444444444444,
             "#bd3786"
            ],
            [
             0.5555555555555556,
             "#d8576b"
            ],
            [
             0.6666666666666666,
             "#ed7953"
            ],
            [
             0.7777777777777778,
             "#fb9f3a"
            ],
            [
             0.8888888888888888,
             "#fdca26"
            ],
            [
             1.0,
             "#f0f921"
            ]
           ]
          },
          "colorway": [
           "#636efa",
           "#EF553B",
           "#00cc96",
           "#ab63fa",
           "#FFA15A",
           "#19d3f3",
           "#FF6692",
           "#B6E880",
           "#FF97FF",
           "#FECB52"
          ],
          "font": {
           "color": "#2a3f5f"
          },
          "geo": {
           "bgcolor": "white",
           "lakecolor": "white",
           "landcolor": "#E5ECF6",
           "showlakes": true,
           "showland": true,
           "subunitcolor": "white"
          },
          "hoverlabel": {
           "align": "left"
          },
          "hovermode": "closest",
          "mapbox": {
           "style": "light"
          },
          "paper_bgcolor": "white",
          "plot_bgcolor": "#E5ECF6",
          "polar": {
           "angularaxis": {
            "gridcolor": "white",
            "linecolor": "white",
            "ticks": ""
           },
           "bgcolor": "#E5ECF6",
           "radialaxis": {
            "gridcolor": "white",
            "linecolor": "white",
            "ticks": ""
           }
          },
          "scene": {
           "xaxis": {
            "backgroundcolor": "#E5ECF6",
            "gridcolor": "white",
            "gridwidth": 2,
            "linecolor": "white",
            "showbackground": true,
            "ticks": "",
            "zerolinecolor": "white"
           },
           "yaxis": {
            "backgroundcolor": "#E5ECF6",
            "gridcolor": "white",
            "gridwidth": 2,
            "linecolor": "white",
            "showbackground": true,
            "ticks": "",
            "zerolinecolor": "white"
           },
           "zaxis": {
            "backgroundcolor": "#E5ECF6",
            "gridcolor": "white",
            "gridwidth": 2,
            "linecolor": "white",
            "showbackground": true,
            "ticks": "",
            "zerolinecolor": "white"
           }
          },
          "shapedefaults": {
           "line": {
            "color": "#2a3f5f"
           }
          },
          "ternary": {
           "aaxis": {
            "gridcolor": "white",
            "linecolor": "white",
            "ticks": ""
           },
           "baxis": {
            "gridcolor": "white",
            "linecolor": "white",
            "ticks": ""
           },
           "bgcolor": "#E5ECF6",
           "caxis": {
            "gridcolor": "white",
            "linecolor": "white",
            "ticks": ""
           }
          },
          "title": {
           "x": 0.05
          },
          "xaxis": {
           "automargin": true,
           "gridcolor": "white",
           "linecolor": "white",
           "ticks": "",
           "title": {
            "standoff": 15
           },
           "zerolinecolor": "white",
           "zerolinewidth": 2
          },
          "yaxis": {
           "automargin": true,
           "gridcolor": "white",
           "linecolor": "white",
           "ticks": "",
           "title": {
            "standoff": 15
           },
           "zerolinecolor": "white",
           "zerolinewidth": 2
          }
         }
        },
        "title": {
         "font": {
          "color": "#0b0b0b",
          "size": 16
         },
         "text": "<b>Migration + WFM — benefits vs incremental cost</b><br><span style='font-size:12px;color:#898781'>Baseline = keep paying $7.3M/yr · the platform swap alone does not pay back inside the deck's own 3-year window</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",
    "fig.add_trace(bar(X, [ben_by_year[y] for y in YEARS],\n",
    "                  \"WFM benefits (verbatim, scoped)\", \"#1baf7a\"))\n",
    "fig.add_trace(bar(X, [-inc[y] for y in YEARS],\n",
    "                  \"Incremental cost vs $7.3M/yr baseline\", \"#e34948\"))\n",
    "cum_net = pd.Series([net_by[y] for y in YEARS]).cumsum()\n",
    "fig.add_trace(cum_line(X, cum_net, \"Cumulative net\"))\n",
    "for i, c in enumerate(cum_net):\n",
    "    fig.add_annotation(x=i, y=float(c), text=f\"<b>{money(float(c))}</b>\", showarrow=False,\n",
    "                       yshift=14 if c >= 0 else -14, font=dict(size=12, color=INK))\n",
    "fig.update_layout(barmode=\"relative\")\n",
    "fig.add_annotation(\n",
    "    xref=\"paper\", yref=\"paper\", x=0.01, y=0.98, align=\"left\", showarrow=False,\n",
    "    font=dict(size=12, color=INK2), bgcolor=SURFACE, bordercolor=GRID, borderwidth=1,\n",
    "    text=(f\"3-yr net <b>{html_money(kpi['net_3yr'])}</b> · \"\n",
    "          f\"ROI <b>{fmt_roi(kpi)}</b><br>\"\n",
    "          f\"NPV@{DISCOUNT_RATE:.1%} <b>{html_money(kpi['npv'])}</b> · \"\n",
    "          f\"payback <b>{kpi['payback']}</b><br>\"\n",
    "          f\"run-rate <b>{html_money(RUNRATE_SAVING)}/yr</b> post-window → \"\n",
    "          f\"breakeven <b>{BREAKEVEN}</b>\"))\n",
    "tei_layout(fig, \"Migration + WFM — benefits vs incremental cost\",\n",
    "           \"Baseline = keep paying $7.3M/yr · the platform swap alone does not pay \"\n",
    "           \"back inside the deck's own 3-year window\", height=500)\n",
    "fig.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "7ca576f1",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-08T00:44:25.130433Z",
     "iopub.status.busy": "2026-07-08T00:44:25.130314Z",
     "iopub.status.idle": "2026-07-08T00:44:25.140333Z",
     "shell.execute_reply": "2026-07-08T00:44:25.139593Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The migration stands on cost avoidance alone — for the with-AI case in the same frame, open the Corrected Business Case notebook.\n"
     ]
    },
    {
     "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>Migration + WFM (no AI)</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>3-yr WFM benefits (verbatim, scoped)</th>\n",
       "      <td>$2.3M</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3-yr incremental cost</th>\n",
       "      <td>$3.9M</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3-yr net</th>\n",
       "      <td>-$1.6M</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>ROI (net ÷ incremental cost)</th>\n",
       "      <td>-40%</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>NPV @ 13.5% (deck rate)</th>\n",
       "      <td>-$2.0M</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>NPV @ 8.0% (CTM treasury)</th>\n",
       "      <td>-$1.8M</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Payback (2026–28 window)</th>\n",
       "      <td>beyond 2028</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Steady-state run-rate saving</th>\n",
       "      <td>$5.9M/yr</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Breakeven (run-rate extrapolation)</th>\n",
       "      <td>40 months (~Apr 2029, extrapolated)</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                                  Migration + WFM (no AI)\n",
       "3-yr WFM benefits (verbatim, scoped)                                $2.3M\n",
       "3-yr incremental cost                                               $3.9M\n",
       "3-yr net                                                           -$1.6M\n",
       "ROI (net ÷ incremental cost)                                         -40%\n",
       "NPV @ 13.5% (deck rate)                                            -$2.0M\n",
       "NPV @ 8.0% (CTM treasury)                                          -$1.8M\n",
       "Payback (2026–28 window)                                      beyond 2028\n",
       "Steady-state run-rate saving                                     $5.9M/yr\n",
       "Breakeven (run-rate extrapolation)    40 months (~Apr 2029, extrapolated)"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "kpis_fmt = pd.DataFrame({\"Migration + WFM (no AI)\": {\n",
    "    \"3-yr WFM benefits (verbatim, scoped)\": money(kpi[\"benefits_3yr\"]),\n",
    "    \"3-yr incremental cost\": money(kpi[\"incremental_cost_3yr\"]),\n",
    "    \"3-yr net\": money(kpi[\"net_3yr\"]),\n",
    "    \"ROI (net ÷ incremental cost)\": fmt_roi(kpi),\n",
    "    f\"NPV @ {kpi['discount_rate']:.1%} (deck rate)\": money(kpi[\"npv\"]),\n",
    "    \"NPV @ 8.0% (CTM treasury)\": money(npv([net_by[y] for y in YEARS], 0.08)),\n",
    "    \"Payback (2026–28 window)\": kpi[\"payback\"],\n",
    "    \"Steady-state run-rate saving\": f\"{money(RUNRATE_SAVING)}/yr\",\n",
    "    \"Breakeven (run-rate extrapolation)\": BREAKEVEN,\n",
    "}})\n",
    "print(\"The migration stands on cost avoidance alone — for the with-AI case in the \"\n",
    "      \"same frame, open the Corrected Business Case notebook.\")\n",
    "display(kpis_fmt)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a7329ce6",
   "metadata": {},
   "source": [
    "<a id=\"section-6\"></a>\n",
    "\n",
    "## 6 · Sensitivity — ramp × licence run-rate\n",
    "\n",
    "The two commercial levers Genesys controls directly, swept against the 3-yr net:\n",
    "the **ramp programme length** (licence-free months) and the **licence run-rate**\n",
    "(🟡 whether WFM/WEM SKUs are really inside $4.3M/yr). Contract **termination dates**\n",
    "are the other big lever — edit the per-region dates in section 1 and the whole\n",
    "notebook recomputes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "4ab17e24",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-08T00:44:25.142421Z",
     "iopub.status.busy": "2026-07-08T00:44:25.142284Z",
     "iopub.status.idle": "2026-07-08T00:44:25.179491Z",
     "shell.execute_reply": "2026-07-08T00:44:25.178617Z"
    }
   },
   "outputs": [
    {
     "data": {
      "application/vnd.plotly.v1+json": {
       "config": {
        "plotlyServerURL": "https://plot.ly"
       },
       "data": [
        {
         "colorscale": [
          [
           0.0,
           "#e34948"
          ],
          [
           0.5,
           "#f0efe9"
          ],
          [
           1.0,
           "#2a78d6"
          ]
         ],
         "hovertemplate": "%{y} × %{x}: %{z:$,.0f}<extra></extra>",
         "showscale": false,
         "type": "heatmap",
         "x": [
          "$3.8M/yr",
          "$4.3M/yr",
          "$4.8M/yr"
         ],
         "xgap": 2,
         "y": [
          "0-mo ramp",
          "6-mo ramp",
          "12-mo ramp",
          "18-mo ramp",
          "24-mo ramp"
         ],
         "ygap": 2,
         "z": [
          [
           -4353002.0,
           -5853002.0,
           -7353002.0
          ],
          [
           -2453002.0000000005,
           -3703002.0000000005,
           -4953002.0
          ],
          [
           -553002.0000000005,
           -1553002.0000000005,
           -2553002.0
          ],
          [
           1346997.9999999998,
           596997.9999999998,
           -153002.00000000047
          ],
          [
           3246997.9999999995,
           2746997.9999999995,
           2246997.9999999995
          ]
         ],
         "zmid": 0
        }
       ],
       "layout": {
        "annotations": [
         {
          "font": {
           "color": "#0b0b0b",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>-$4.4M</b>",
          "x": 0,
          "y": 0
         },
         {
          "font": {
           "color": "#ffffff",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>-$5.9M</b>",
          "x": 1,
          "y": 0
         },
         {
          "font": {
           "color": "#ffffff",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>-$7.4M</b>",
          "x": 2,
          "y": 0
         },
         {
          "font": {
           "color": "#0b0b0b",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>-$2.5M</b>",
          "x": 0,
          "y": 1
         },
         {
          "font": {
           "color": "#0b0b0b",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>-$3.7M</b>",
          "x": 1,
          "y": 1
         },
         {
          "font": {
           "color": "#ffffff",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>-$5.0M</b>",
          "x": 2,
          "y": 1
         },
         {
          "font": {
           "color": "#0b0b0b",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>-$553K</b>",
          "x": 0,
          "y": 2
         },
         {
          "font": {
           "color": "#0b0b0b",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>-$1.6M</b>",
          "x": 1,
          "y": 2
         },
         {
          "font": {
           "color": "#0b0b0b",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>-$2.6M</b>",
          "x": 2,
          "y": 2
         },
         {
          "font": {
           "color": "#0b0b0b",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>$1.3M</b>",
          "x": 0,
          "y": 3
         },
         {
          "font": {
           "color": "#0b0b0b",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>$597K</b>",
          "x": 1,
          "y": 3
         },
         {
          "font": {
           "color": "#0b0b0b",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>-$153K</b>",
          "x": 2,
          "y": 3
         },
         {
          "font": {
           "color": "#0b0b0b",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>$3.2M</b>",
          "x": 0,
          "y": 4
         },
         {
          "font": {
           "color": "#0b0b0b",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>$2.7M</b>",
          "x": 1,
          "y": 4
         },
         {
          "font": {
           "color": "#0b0b0b",
           "size": 12
          },
          "showarrow": false,
          "text": "<b>$2.2M</b>",
          "x": 2,
          "y": 4
         }
        ],
        "bargap": 0.45,
        "font": {
         "color": "#52514e",
         "family": "system-ui, -apple-system, \"Segoe UI\", sans-serif",
         "size": 12
        },
        "height": 400,
        "hovermode": "closest",
        "legend": {
         "font": {
          "color": "#52514e",
          "size": 11
         },
         "orientation": "h",
         "x": 0,
         "y": -0.1,
         "yanchor": "top"
        },
        "margin": {
         "b": 80,
         "l": 70,
         "r": 30,
         "t": 70
        },
        "paper_bgcolor": "#fcfcfb",
        "plot_bgcolor": "#fcfcfb",
        "template": {
         "data": {
          "bar": [
           {
            "error_x": {
             "color": "#2a3f5f"
            },
            "error_y": {
             "color": "#2a3f5f"
            },
            "marker": {
             "line": {
              "color": "#E5ECF6",
              "width": 0.5
             },
             "pattern": {
              "fillmode": "overlay",
              "size": 10,
              "solidity": 0.2
             }
            },
            "type": "bar"
           }
          ],
          "barpolar": [
           {
            "marker": {
             "line": {
              "color": "#E5ECF6",
              "width": 0.5
             },
             "pattern": {
              "fillmode": "overlay",
              "size": 10,
              "solidity": 0.2
             }
            },
            "type": "barpolar"
           }
          ],
          "carpet": [
           {
            "aaxis": {
             "endlinecolor": "#2a3f5f",
             "gridcolor": "white",
             "linecolor": "white",
             "minorgridcolor": "white",
             "startlinecolor": "#2a3f5f"
            },
            "baxis": {
             "endlinecolor": "#2a3f5f",
             "gridcolor": "white",
             "linecolor": "white",
             "minorgridcolor": "white",
             "startlinecolor": "#2a3f5f"
            },
            "type": "carpet"
           }
          ],
          "choropleth": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "type": "choropleth"
           }
          ],
          "contour": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "colorscale": [
             [
              0.0,
              "#0d0887"
             ],
             [
              0.1111111111111111,
              "#46039f"
             ],
             [
              0.2222222222222222,
              "#7201a8"
             ],
             [
              0.3333333333333333,
              "#9c179e"
             ],
             [
              0.4444444444444444,
              "#bd3786"
             ],
             [
              0.5555555555555556,
              "#d8576b"
             ],
             [
              0.6666666666666666,
              "#ed7953"
             ],
             [
              0.7777777777777778,
              "#fb9f3a"
             ],
             [
              0.8888888888888888,
              "#fdca26"
             ],
             [
              1.0,
              "#f0f921"
             ]
            ],
            "type": "contour"
           }
          ],
          "contourcarpet": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "type": "contourcarpet"
           }
          ],
          "heatmap": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "colorscale": [
             [
              0.0,
              "#0d0887"
             ],
             [
              0.1111111111111111,
              "#46039f"
             ],
             [
              0.2222222222222222,
              "#7201a8"
             ],
             [
              0.3333333333333333,
              "#9c179e"
             ],
             [
              0.4444444444444444,
              "#bd3786"
             ],
             [
              0.5555555555555556,
              "#d8576b"
             ],
             [
              0.6666666666666666,
              "#ed7953"
             ],
             [
              0.7777777777777778,
              "#fb9f3a"
             ],
             [
              0.8888888888888888,
              "#fdca26"
             ],
             [
              1.0,
              "#f0f921"
             ]
            ],
            "type": "heatmap"
           }
          ],
          "histogram": [
           {
            "marker": {
             "pattern": {
              "fillmode": "overlay",
              "size": 10,
              "solidity": 0.2
             }
            },
            "type": "histogram"
           }
          ],
          "histogram2d": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "colorscale": [
             [
              0.0,
              "#0d0887"
             ],
             [
              0.1111111111111111,
              "#46039f"
             ],
             [
              0.2222222222222222,
              "#7201a8"
             ],
             [
              0.3333333333333333,
              "#9c179e"
             ],
             [
              0.4444444444444444,
              "#bd3786"
             ],
             [
              0.5555555555555556,
              "#d8576b"
             ],
             [
              0.6666666666666666,
              "#ed7953"
             ],
             [
              0.7777777777777778,
              "#fb9f3a"
             ],
             [
              0.8888888888888888,
              "#fdca26"
             ],
             [
              1.0,
              "#f0f921"
             ]
            ],
            "type": "histogram2d"
           }
          ],
          "histogram2dcontour": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "colorscale": [
             [
              0.0,
              "#0d0887"
             ],
             [
              0.1111111111111111,
              "#46039f"
             ],
             [
              0.2222222222222222,
              "#7201a8"
             ],
             [
              0.3333333333333333,
              "#9c179e"
             ],
             [
              0.4444444444444444,
              "#bd3786"
             ],
             [
              0.5555555555555556,
              "#d8576b"
             ],
             [
              0.6666666666666666,
              "#ed7953"
             ],
             [
              0.7777777777777778,
              "#fb9f3a"
             ],
             [
              0.8888888888888888,
              "#fdca26"
             ],
             [
              1.0,
              "#f0f921"
             ]
            ],
            "type": "histogram2dcontour"
           }
          ],
          "mesh3d": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "type": "mesh3d"
           }
          ],
          "parcoords": [
           {
            "line": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "parcoords"
           }
          ],
          "pie": [
           {
            "automargin": true,
            "type": "pie"
           }
          ],
          "scatter": [
           {
            "fillpattern": {
             "fillmode": "overlay",
             "size": 10,
             "solidity": 0.2
            },
            "type": "scatter"
           }
          ],
          "scatter3d": [
           {
            "line": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scatter3d"
           }
          ],
          "scattercarpet": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scattercarpet"
           }
          ],
          "scattergeo": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scattergeo"
           }
          ],
          "scattergl": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scattergl"
           }
          ],
          "scattermap": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scattermap"
           }
          ],
          "scattermapbox": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scattermapbox"
           }
          ],
          "scatterpolar": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scatterpolar"
           }
          ],
          "scatterpolargl": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scatterpolargl"
           }
          ],
          "scatterternary": [
           {
            "marker": {
             "colorbar": {
              "outlinewidth": 0,
              "ticks": ""
             }
            },
            "type": "scatterternary"
           }
          ],
          "surface": [
           {
            "colorbar": {
             "outlinewidth": 0,
             "ticks": ""
            },
            "colorscale": [
             [
              0.0,
              "#0d0887"
             ],
             [
              0.1111111111111111,
              "#46039f"
             ],
             [
              0.2222222222222222,
              "#7201a8"
             ],
             [
              0.3333333333333333,
              "#9c179e"
             ],
             [
              0.4444444444444444,
              "#bd3786"
             ],
             [
              0.5555555555555556,
              "#d8576b"
             ],
             [
              0.6666666666666666,
              "#ed7953"
             ],
             [
              0.7777777777777778,
              "#fb9f3a"
             ],
             [
              0.8888888888888888,
              "#fdca26"
             ],
             [
              1.0,
              "#f0f921"
             ]
            ],
            "type": "surface"
           }
          ],
          "table": [
           {
            "cells": {
             "fill": {
              "color": "#EBF0F8"
             },
             "line": {
              "color": "white"
             }
            },
            "header": {
             "fill": {
              "color": "#C8D4E3"
             },
             "line": {
              "color": "white"
             }
            },
            "type": "table"
           }
          ]
         },
         "layout": {
          "annotationdefaults": {
           "arrowcolor": "#2a3f5f",
           "arrowhead": 0,
           "arrowwidth": 1
          },
          "autotypenumbers": "strict",
          "coloraxis": {
           "colorbar": {
            "outlinewidth": 0,
            "ticks": ""
           }
          },
          "colorscale": {
           "diverging": [
            [
             0,
             "#8e0152"
            ],
            [
             0.1,
             "#c51b7d"
            ],
            [
             0.2,
             "#de77ae"
            ],
            [
             0.3,
             "#f1b6da"
            ],
            [
             0.4,
             "#fde0ef"
            ],
            [
             0.5,
             "#f7f7f7"
            ],
            [
             0.6,
             "#e6f5d0"
            ],
            [
             0.7,
             "#b8e186"
            ],
            [
             0.8,
             "#7fbc41"
            ],
            [
             0.9,
             "#4d9221"
            ],
            [
             1,
             "#276419"
            ]
           ],
           "sequential": [
            [
             0.0,
             "#0d0887"
            ],
            [
             0.1111111111111111,
             "#46039f"
            ],
            [
             0.2222222222222222,
             "#7201a8"
            ],
            [
             0.3333333333333333,
             "#9c179e"
            ],
            [
             0.4444444444444444,
             "#bd3786"
            ],
            [
             0.5555555555555556,
             "#d8576b"
            ],
            [
             0.6666666666666666,
             "#ed7953"
            ],
            [
             0.7777777777777778,
             "#fb9f3a"
            ],
            [
             0.8888888888888888,
             "#fdca26"
            ],
            [
             1.0,
             "#f0f921"
            ]
           ],
           "sequentialminus": [
            [
             0.0,
             "#0d0887"
            ],
            [
             0.1111111111111111,
             "#46039f"
            ],
            [
             0.2222222222222222,
             "#7201a8"
            ],
            [
             0.3333333333333333,
             "#9c179e"
            ],
            [
             0.4444444444444444,
             "#bd3786"
            ],
            [
             0.5555555555555556,
             "#d8576b"
            ],
            [
             0.6666666666666666,
             "#ed7953"
            ],
            [
             0.7777777777777778,
             "#fb9f3a"
            ],
            [
             0.8888888888888888,
             "#fdca26"
            ],
            [
             1.0,
             "#f0f921"
            ]
           ]
          },
          "colorway": [
           "#636efa",
           "#EF553B",
           "#00cc96",
           "#ab63fa",
           "#FFA15A",
           "#19d3f3",
           "#FF6692",
           "#B6E880",
           "#FF97FF",
           "#FECB52"
          ],
          "font": {
           "color": "#2a3f5f"
          },
          "geo": {
           "bgcolor": "white",
           "lakecolor": "white",
           "landcolor": "#E5ECF6",
           "showlakes": true,
           "showland": true,
           "subunitcolor": "white"
          },
          "hoverlabel": {
           "align": "left"
          },
          "hovermode": "closest",
          "mapbox": {
           "style": "light"
          },
          "paper_bgcolor": "white",
          "plot_bgcolor": "#E5ECF6",
          "polar": {
           "angularaxis": {
            "gridcolor": "white",
            "linecolor": "white",
            "ticks": ""
           },
           "bgcolor": "#E5ECF6",
           "radialaxis": {
            "gridcolor": "white",
            "linecolor": "white",
            "ticks": ""
           }
          },
          "scene": {
           "xaxis": {
            "backgroundcolor": "#E5ECF6",
            "gridcolor": "white",
            "gridwidth": 2,
            "linecolor": "white",
            "showbackground": true,
            "ticks": "",
            "zerolinecolor": "white"
           },
           "yaxis": {
            "backgroundcolor": "#E5ECF6",
            "gridcolor": "white",
            "gridwidth": 2,
            "linecolor": "white",
            "showbackground": true,
            "ticks": "",
            "zerolinecolor": "white"
           },
           "zaxis": {
            "backgroundcolor": "#E5ECF6",
            "gridcolor": "white",
            "gridwidth": 2,
            "linecolor": "white",
            "showbackground": true,
            "ticks": "",
            "zerolinecolor": "white"
           }
          },
          "shapedefaults": {
           "line": {
            "color": "#2a3f5f"
           }
          },
          "ternary": {
           "aaxis": {
            "gridcolor": "white",
            "linecolor": "white",
            "ticks": ""
           },
           "baxis": {
            "gridcolor": "white",
            "linecolor": "white",
            "ticks": ""
           },
           "bgcolor": "#E5ECF6",
           "caxis": {
            "gridcolor": "white",
            "linecolor": "white",
            "ticks": ""
           }
          },
          "title": {
           "x": 0.05
          },
          "xaxis": {
           "automargin": true,
           "gridcolor": "white",
           "linecolor": "white",
           "ticks": "",
           "title": {
            "standoff": 15
           },
           "zerolinecolor": "white",
           "zerolinewidth": 2
          },
          "yaxis": {
           "automargin": true,
           "gridcolor": "white",
           "linecolor": "white",
           "ticks": "",
           "title": {
            "standoff": 15
           },
           "zerolinecolor": "white",
           "zerolinewidth": 2
          }
         }
        },
        "title": {
         "font": {
          "color": "#0b0b0b",
          "size": 16
         },
         "text": "<b>3-yr net across ramp length × licence run-rate</b><br><span style='font-size:12px;color:#898781'>Diverging scale centered at $0 · WFM scope and contract dates held at current inputs</span>",
         "x": 0.02,
         "xanchor": "left"
        },
        "xaxis": {
         "linecolor": "#c3c2b7",
         "showgrid": false,
         "tickfont": {
          "color": "#898781"
         },
         "type": "category"
        },
        "yaxis": {
         "gridcolor": "#e1e0d9",
         "showgrid": false,
         "tickfont": {
          "color": "#898781"
         },
         "zerolinecolor": "#c3c2b7",
         "zerolinewidth": 1.5
        }
       }
      }
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Current corner (12-mo ramp × $4.3M/yr): -$1.6M — no corner of the grid turns the 3-yr window positive on its own.\n"
     ]
    }
   ],
   "source": [
    "# ── 3-yr net across ramp months × licence run-rate ───────────────────\n",
    "RAMP_GRID = [0, 6, 12, 18, 24]\n",
    "LIC_GRID = [3_800_000, 4_300_000, 4_800_000]\n",
    "\n",
    "\n",
    "def net_3yr(ramp_months, licence_annual):\n",
    "    lby = licence_costs_by_year(ramp_months, licence_annual)\n",
    "    t = {y: current_by_year[y] + lby[y] + ps_by_year[y] for y in YEARS}\n",
    "    _, n = case_flows(t, ben_by_year)\n",
    "    return sum(n.values())\n",
    "\n",
    "\n",
    "z = [[net_3yr(rm, lc) for lc in LIC_GRID] for rm in RAMP_GRID]\n",
    "_zabs = max(abs(v) for row in z for v in row)\n",
    "fig = go.Figure(go.Heatmap(\n",
    "    z=z, x=[f\"{money(lc)}/yr\" for lc in LIC_GRID],\n",
    "    y=[f\"{rm}-mo ramp\" for rm in RAMP_GRID],\n",
    "    zmid=0, colorscale=DIVERGING, showscale=False, xgap=2, ygap=2,\n",
    "    hovertemplate=\"%{y} × %{x}: %{z:$,.0f}<extra></extra>\"))\n",
    "for i, rm in enumerate(RAMP_GRID):\n",
    "    for j, lc in enumerate(LIC_GRID):\n",
    "        v = z[i][j]\n",
    "        fig.add_annotation(x=j, y=i, text=f\"<b>{money(v)}</b>\", showarrow=False,\n",
    "                           font=dict(size=12,\n",
    "                                     color=\"#ffffff\" if abs(v) > 0.65 * _zabs else INK))\n",
    "tei_layout(fig, \"3-yr net across ramp length × licence run-rate\",\n",
    "           \"Diverging scale centered at $0 · WFM scope and contract dates held at \"\n",
    "           \"current inputs\", height=400)\n",
    "fig.update_layout(xaxis=dict(showgrid=False), yaxis=dict(showgrid=False, tickformat=None),\n",
    "                  hovermode=\"closest\")\n",
    "fig.show()\n",
    "print(f\"Current corner ({RAMP_MONTHS}-mo ramp × {money(LICENCE_ANNUAL)}/yr): \"\n",
    "      f\"{money(net_3yr(RAMP_MONTHS, LICENCE_ANNUAL))} — no corner of the grid turns \"\n",
    "      f\"the 3-yr window positive on its own.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "13d4932f",
   "metadata": {},
   "source": [
    "<a id=\"section-7\"></a>\n",
    "\n",
    "## 7 · Verification & assertions\n",
    "\n",
    "The next cell re-derives key numbers independently and **raises on any failure**, so\n",
    "`jupyter nbconvert --execute` acts as a regression gate for this notebook. Engine pins\n",
    "use explicit default arguments so the gate tests `tokencalc`, not the current widget\n",
    "state; live-state checks are guarded so the cell stays valid after you edit the inputs."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "939417e4",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-08T00:44:25.182334Z",
     "iopub.status.busy": "2026-07-08T00:44:25.182142Z",
     "iopub.status.idle": "2026-07-08T00:44:25.195425Z",
     "shell.execute_reply": "2026-07-08T00:44:25.194878Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "All assertions passed.\n",
      "  WFM benefits 3-yr $2.3M · programme cost 3-yr $25.8M · net 3-yr -$1.6M · breakeven 40 months (~Apr 2029, extrapolated)\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",
    "# Section 1 — contract mechanics (engine pins, explicit defaults)\n",
    "_seed_check = current_state_inputs(sites)\n",
    "_approx(_seed_check[\"annual_cost\"].sum(), 7_300_000)\n",
    "_l12 = licence_costs_by_year(12)\n",
    "_approx(_l12[2026], 0)\n",
    "_approx(_l12[2027], 4_300_000)\n",
    "_contracts_at_default = (\n",
    "    (current_state[\"annual_cost\"] - _seed_check[\"annual_cost\"]).abs().max() < 1\n",
    "    and all(t == dt.date(2027, 12, 31) for t in current_state[\"contract_termination\"])\n",
    ")\n",
    "if _contracts_at_default:\n",
    "    # widget seeds are whole dollars → per-region rounding of ≤ $0.50\n",
    "    _approx(current_by_year[2026], 7_300_000, tol=len(REGIONS))\n",
    "    _approx(current_by_year[2028], 0)\n",
    "\n",
    "# Sections 2-3 — WFM scope & phasing at engine defaults\n",
    "_, _, _rollout_d = build_rollouts(sites)\n",
    "_wfm_d = wfm_benefits_by_year(_rollout_d)          # NA + ANZ + ASIA\n",
    "assert set(_wfm_d[\"region\"]) == set(DEFAULT_WFM_REGIONS)\n",
    "_approx(_wfm_d[\"benefit\"].sum(), 2_314_000)        # $0 NA + $1.4M ANZ + $914K ASIA\n",
    "_by_d = _wfm_d.groupby(\"year\")[\"benefit\"].sum()\n",
    "_approx(_by_d[2026], 0)\n",
    "_approx(_by_d[2027], 1_400_000 / 13, tol=1)        # ANZ: 1 of 13 live months in 2027\n",
    "_approx(wfm_annual_runrate(), 2_900_000)\n",
    "_approx(runrate_saving_annual(), 5_900_000)\n",
    "\n",
    "# Section 5 — default-flow pins (pure engine defaults, no widget state)\n",
    "_cur_d = current_costs_by_year(_seed_check)\n",
    "_ps_d = ps_costs_by_year()\n",
    "_tot_d = {y: _cur_d[y] + _l12[y] + _ps_d[y] for y in YEARS}\n",
    "_ben_d = {y: float(_by_d.get(y, 0.0)) for y in YEARS}\n",
    "_inc_d, _net_d = case_flows(_tot_d, _ben_d)\n",
    "_approx(sum(_net_d.values()), -1_553_000, tol=1)   # $2.314M WFM − $3.867M incremental\n",
    "assert runrate_breakeven_label(_net_d, runrate_saving_annual()) == \\\n",
    "    \"40 months (~Apr 2029, extrapolated)\"\n",
    "\n",
    "# Live state — stack and flows tie out at whatever the widgets say\n",
    "_approx(sum(total_by_year.values()),\n",
    "        sum(licence_by_year.values()) + ps_by_year[2026] + sum(current_by_year.values()))\n",
    "for y in YEARS:\n",
    "    _approx(net_by[y], ben_by_year[y] - (total_by_year[y] - BASELINE_ANNUAL))\n",
    "\n",
    "print(\"All assertions passed.\")\n",
    "print(f\"  WFM benefits 3-yr {money(sum(ben_by_year.values()))} · \"\n",
    "      f\"programme cost 3-yr {money(sum(total_by_year.values()))} · \"\n",
    "      f\"net 3-yr {money(kpi['net_3yr'])} · breakeven {BREAKEVEN}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "67ac8561",
   "metadata": {},
   "source": [
    "<a id=\"section-8\"></a>\n",
    "\n",
    "## 8 · Risks, notes & next steps\n",
    "\n",
    "**Findings to lead with**\n",
    "- **The platform swap alone does not carry the case in the deck's own window**: net −$1.6M\n",
    "  by end-2028 at defaults. At the $5.9M/yr steady-state run-rate it turns ~April 2029 —\n",
    "  a real but slow cost-avoidance story. The AI capabilities are what pull payback inside\n",
    "  the window (see the Corrected Business Case notebook).\n",
    "- **2026 is a pure cost year** here too — PS outlay plus double-billing, zero benefits.\n",
    "- The ramp credit and the term-contract run-off cut in opposite directions; both are\n",
    "  contract facts the deck's cost case omitted.\n",
    "\n",
    "**Known gaps / assumptions**\n",
    "- 🟡 Licence run-rate assumes WFM/WEM SKUs (incl. new APAC seats) are inside $4.3M/yr —\n",
    "  if WEM is an add-on, raise the sidebar run-rate; section 6 shows the sensitivity.\n",
    "- 🟡 Regional current-cost split is an agent-share allocation pending real contract data;\n",
    "  if NA's existing WFM tool bills separately, fold it into NA's section-1 input.\n",
    "- Deck quirk kept verbatim: ASIA WFM annual ($1.6M) exceeds its 3-yr value ($914K); the\n",
    "  annual value feeds the run-rate extrapolation, the 3-yr value phases in-window.\n",
    "- EMEA WFM ($687K 3-yr) is an upside option — sidebar toggle.\n",
    "- Not modelled: early-termination fees, co-term options, migration costs beyond the base\n",
    "  PS line, and any benefit realization lag beyond Genesys's own schedule.\n",
    "\n",
    "**Next steps**\n",
    "- Feed real per-region contract values and termination dates into section 1.\n",
    "- Confirm in the order form that WFM/WEM SKUs and the APAC seats are inside the run-rate.\n",
    "- Pressure-test the base-price claim: does $2.4M PS really include the NA WFM migration\n",
    "  and two net-new WFM implementations?"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b8ee762f",
   "metadata": {},
   "source": [
    "<a id=\"section-9\"></a>\n",
    "\n",
    "## 9 · Machine-readable data appendix\n",
    "\n",
    "Every number behind the figures above, emitted as markdown tables plus a JSON block, so an\n",
    "LLM can draft the client report or presentation directly from the HTML/markdown export\n",
    "(`python scripts/export_report.py`). Plotly figures export as JavaScript an LLM cannot\n",
    "read — this section carries the data. The tables reflect the **current** input state, so a\n",
    "client-customized session exports a client-customized appendix."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "32ebed57",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-08T00:44:25.197188Z",
     "iopub.status.busy": "2026-07-08T00:44:25.197065Z",
     "iopub.status.idle": "2026-07-08T00:44:25.218630Z",
     "shell.execute_reply": "2026-07-08T00:44:25.218002Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "#### Current-state contracts by region (inputs)\n",
      "\n",
      "| region   |   agents |   annual_cost | contract_termination   | confidence                                      |\n",
      "|:---------|---------:|--------------:|:-----------------------|:------------------------------------------------|\n",
      "| NA       |      890 |     3,348,969 | 2027-12-31             | 🟡 agent-share allocation of the verbatim total |\n",
      "| ANZ      |      180 |       677,320 | 2027-12-31             | 🟡 agent-share allocation of the verbatim total |\n",
      "| EMEA     |      320 |     1,204,124 | 2027-12-31             | 🟡 agent-share allocation of the verbatim total |\n",
      "| ASIA     |      550 |     2,069,588 | 2027-12-31             | 🟡 agent-share allocation of the verbatim total |\n",
      "\n",
      "#### WFM scope — treatment and verbatim values ($)\n",
      "\n",
      "| region   | WFM treatment             | in scope   |   verbatim annual |   verbatim 3-yr |\n",
      "|:---------|:--------------------------|:-----------|------------------:|----------------:|\n",
      "| NA       | migrate existing users    | ✓          |                 0 |               0 |\n",
      "| ANZ      | implement (APAC)          | ✓          |           1300000 |         1400000 |\n",
      "| EMEA     | implement (upside option) | —          |            824000 |          687000 |\n",
      "| ASIA     | implement (APAC)          | ✓          |           1600000 |          914000 |\n",
      "\n",
      "#### WFM benefits by region × year (schedule-phased, $)\n",
      "\n",
      "| region   |   2026 |    2027 |      2028 |\n",
      "|:---------|-------:|--------:|----------:|\n",
      "| ANZ      |      0 | 107,692 | 1,292,308 |\n",
      "| ASIA     |      0 |       0 |   914,000 |\n",
      "| NA       |      0 |       0 |         0 |\n",
      "\n",
      "#### Migration programme cost stack ($)\n",
      "\n",
      "|                                            |      2026 |      2027 |      2028 |       3-yr |\n",
      "|:-------------------------------------------|----------:|----------:|----------:|-----------:|\n",
      "| CCaaS platform licences (ramp-adjusted)    |         0 | 4,300,000 | 4,300,000 |  8,600,000 |\n",
      "| Base PS + training (incl. migration + WFM) | 2,567,000 |         0 |         0 |  2,567,000 |\n",
      "| Existing platform (term-contract run-off)  | 7,300,001 | 7,300,001 |         0 | 14,600,002 |\n",
      "\n",
      "#### Business-case flows vs do-nothing baseline ($)\n",
      "\n",
      "|                  |       2026 |       2027 |       2028 |\n",
      "|:-----------------|-----------:|-----------:|-----------:|\n",
      "| programme cost   |  9,867,001 | 11,600,001 |  4,300,000 |\n",
      "| incremental cost |  2,567,001 |  4,300,001 | -3,000,000 |\n",
      "| net              | -2,567,001 | -4,192,309 |  5,206,308 |\n",
      "\n",
      "#### KPIs — migration + WFM (no AI)\n",
      "\n",
      "|                                      | Migration + WFM (no AI)             |\n",
      "|:-------------------------------------|:------------------------------------|\n",
      "| 3-yr WFM benefits (verbatim, scoped) | $2.3M                               |\n",
      "| 3-yr incremental cost                | $3.9M                               |\n",
      "| 3-yr net                             | -$1.6M                              |\n",
      "| ROI (net ÷ incremental cost)         | -40%                                |\n",
      "| NPV @ 13.5% (deck rate)              | -$2.0M                              |\n",
      "| NPV @ 8.0% (CTM treasury)            | -$1.8M                              |\n",
      "| Payback (2026–28 window)             | beyond 2028                         |\n",
      "| Steady-state run-rate saving         | $5.9M/yr                            |\n",
      "| Breakeven (run-rate extrapolation)   | 40 months (~Apr 2029, extrapolated) |\n",
      "\n",
      "#### Model state (JSON)\n",
      "\n",
      "```json\n",
      "{\n",
      "  \"scenario\": \"Genesys Cloud CX migration + WFM (NA migrate, APAC implement) \\u2014 no AI\",\n",
      "  \"wfm_benefits_by_year\": {\n",
      "    \"2026\": 0,\n",
      "    \"2027\": 107692,\n",
      "    \"2028\": 2206308\n",
      "  },\n",
      "  \"cost_by_year\": {\n",
      "    \"2026\": 9867001,\n",
      "    \"2027\": 11600001,\n",
      "    \"2028\": 4300000\n",
      "  },\n",
      "  \"net_by_year\": {\n",
      "    \"2026\": -2567001,\n",
      "    \"2027\": -4192309,\n",
      "    \"2028\": 5206308\n",
      "  },\n",
      "  \"kpis\": {\n",
      "    \"benefits_3yr\": 2314000,\n",
      "    \"incremental_cost_3yr\": 3867002,\n",
      "    \"net_3yr\": -1553002,\n",
      "    \"roi\": -0.4016,\n",
      "    \"npv\": -1955248,\n",
      "    \"discount_rate\": 0.135,\n",
      "    \"payback\": \"beyond 2028\"\n",
      "  },\n",
      "  \"runrate_saving_annual\": 5900000,\n",
      "  \"breakeven\": \"40 months (~Apr 2029, extrapolated)\",\n",
      "  \"assumptions\": {\n",
      "    \"ramp_months\": 12,\n",
      "    \"licence_annual\": 4300000,\n",
      "    \"licence_includes_wfm_skus\": true,\n",
      "    \"wfm_regions\": [\n",
      "      \"NA\",\n",
      "      \"ANZ\",\n",
      "      \"ASIA\"\n",
      "    ],\n",
      "    \"migration_and_wfm_in_base_price\": true,\n",
      "    \"discount_rate\": 0.135,\n",
      "    \"baseline_annual\": 7300000,\n",
      "    \"current_state_by_region\": {\n",
      "      \"NA\": {\n",
      "        \"annual_cost\": 3348969,\n",
      "        \"contract_termination\": \"2027-12-31\"\n",
      "      },\n",
      "      \"ANZ\": {\n",
      "        \"annual_cost\": 677320,\n",
      "        \"contract_termination\": \"2027-12-31\"\n",
      "      },\n",
      "      \"EMEA\": {\n",
      "        \"annual_cost\": 1204124,\n",
      "        \"contract_termination\": \"2027-12-31\"\n",
      "      },\n",
      "      \"ASIA\": {\n",
      "        \"annual_cost\": 2069588,\n",
      "        \"contract_termination\": \"2027-12-31\"\n",
      "      }\n",
      "    }\n",
      "  }\n",
      "}\n",
      "```\n"
     ]
    }
   ],
   "source": [
    "# ── Data appendix — LLM-readable dump of every model output ──────────\n",
    "import json as _json\n",
    "\n",
    "\n",
    "def _section(title, df, **kw):\n",
    "    print(f\"\\n#### {title}\\n\")\n",
    "    print(df.to_markdown(floatfmt=\",.0f\", **kw))\n",
    "\n",
    "\n",
    "_section(\"Current-state contracts by region (inputs)\",\n",
    "         current_state.reset_index().drop(columns=[\"share\"]), index=False)\n",
    "_section(\"WFM scope — treatment and verbatim values ($)\", wfm_scope_table)\n",
    "if not wfm_long.empty:\n",
    "    _section(\"WFM benefits by region × year (schedule-phased, $)\",\n",
    "             wfm_long.pivot_table(index=\"region\", columns=\"year\",\n",
    "                                  values=\"benefit\", aggfunc=\"sum\"))\n",
    "_section(\"Migration programme cost stack ($)\", migration_costs)\n",
    "_section(\"Business-case flows vs do-nothing baseline ($)\",\n",
    "         pd.DataFrame({\"programme cost\": total_by_year,\n",
    "                       \"incremental cost\": inc, \"net\": net_by}).T[YEARS])\n",
    "_section(\"KPIs — migration + WFM (no AI)\", kpis_fmt)\n",
    "\n",
    "\n",
    "def _jval(v):\n",
    "    if isinstance(v, float):\n",
    "        return round(v, 4) if abs(v) < 10 else round(v)\n",
    "    return v\n",
    "\n",
    "\n",
    "print(\"\\n#### Model state (JSON)\\n\")\n",
    "print(\"```json\")\n",
    "print(_json.dumps({\n",
    "    \"scenario\": \"Genesys Cloud CX migration + WFM (NA migrate, APAC implement) — no AI\",\n",
    "    \"wfm_benefits_by_year\": {str(y): round(ben_by_year[y]) for y in YEARS},\n",
    "    \"cost_by_year\": {str(y): round(total_by_year[y]) for y in YEARS},\n",
    "    \"net_by_year\": {str(y): round(net_by[y]) for y in YEARS},\n",
    "    \"kpis\": {k: _jval(v) for k, v in kpi.items()},\n",
    "    \"runrate_saving_annual\": round(RUNRATE_SAVING),\n",
    "    \"breakeven\": BREAKEVEN,\n",
    "    \"assumptions\": {\n",
    "        \"ramp_months\": RAMP_MONTHS,\n",
    "        \"licence_annual\": round(LICENCE_ANNUAL),\n",
    "        \"licence_includes_wfm_skus\": True,\n",
    "        \"wfm_regions\": WFM_REGIONS,\n",
    "        \"migration_and_wfm_in_base_price\": True,\n",
    "        \"discount_rate\": DISCOUNT_RATE,\n",
    "        \"baseline_annual\": round(BASELINE_ANNUAL),\n",
    "        \"current_state_by_region\": {\n",
    "            r: {\"annual_cost\": round(float(current_state.loc[r, \"annual_cost\"])),\n",
    "                \"contract_termination\": current_state.loc[r, \"contract_termination\"].isoformat()}\n",
    "            for r in REGIONS},\n",
    "    },\n",
    "}, indent=2))\n",
    "print(\"```\")"
   ]
  }
 ],
 "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": {
     "2b937636abf64a27aea2780988f790c2": {
      "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
      }
     },
     "36ab088e1ab642748d8c30c5a34559ad": {
      "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
      }
     },
     "4049cd4af46c4c19acf3535b2a553f56": {
      "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
      }
     },
     "418f2449106245368a83b6674d042595": {
      "model_module": "anywidget",
      "model_module_version": "~0.11.*",
      "model_name": "AnyModel",
      "state": {
       "_anywidget_id": "mercury.select.SelectWidget",
       "_css": "\n    .mljar-select-container {\n      display: flex;\n      flex-direction: column;\n      font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n      font-size: 14px;\n      color: #0f172a;\n      margin-bottom: 8px;\n      padding-left: 4px;\n      padding-right: 4px;\n    }\n\n    .mljar-select-label {\n      margin-bottom: 4px;\n      font-weight: 600;\n    }\n\n    .mljar-select-control {\n      position: relative;\n      display: flex;\n      align-items: center;\n      cursor: default;\n    }\n\n    .mljar-select-widget-input {\n      width: 100%;\n      min-height: 40px;\n      padding: 9px 36px 9px 10px;\n      border: 1px solid #cfd1d5;\n      border-radius: 6px;\n      background: #ffffff;\n      box-sizing: border-box;\n      line-height: 1.4;\n      transition: border-color 0.15s ease, box-shadow 0.15s ease;\n\n      appearance: none !important;\n      background-color: #ffffff !important;\n      color: #0f172a !important;\n      cursor: default;\n    }\n\n    .mljar-select-widget-input:focus {\n      outline: none;\n      border-color: #007bff;\n      border-width: 2px;\n      box-shadow: none;\n      cursor: text;\n    }\n\n    .mljar-select-caret {\n      position: absolute;\n      right: 12px;\n      top: 50%;\n      width: 8px;\n      height: 8px;\n      border-right: 1.5px solid #0f172a;\n      border-bottom: 1.5px solid #0f172a;\n      transform: translateY(-65%) rotate(45deg);\n      pointer-events: none;\n      opacity: 0.5;\n      transition: transform 0.18s ease, opacity 0.18s ease;\n    }\n\n    .mljar-select-container.is-open .mljar-select-caret {\n      opacity: 1;\n      transform: translateY(-35%) rotate(225deg);\n    }\n\n    .mljar-select-dropdown {\n      display: none;\n      margin-top: 6px;\n      border: 1px solid #cfd1d5;\n      border-radius: 6px;\n      background: #ffffff;\n      box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);\n      overflow: hidden;\n    }\n\n    .mljar-select-list {\n      max-height: 260px;\n      overflow-y: auto;\n    }\n\n    .mljar-select-option {\n      display: block;\n      width: 100%;\n      padding: 9px 10px;\n      border: 0;\n      background: transparent;\n      color: #0f172a;\n      text-align: left;\n      cursor: pointer;\n      font: inherit;\n      transition: background-color 0.14s ease, color 0.14s ease;\n    }\n\n    .mljar-select-option:hover {\n      background: #f3f3f4;\n    }\n\n    .mljar-select-option:active {\n      background: #e6f2ff;\n      color: #007bff;\n    }\n\n    .mljar-select-option.is-selected {\n      background: #e6f2ff;\n      color: #007bff;\n      font-weight: 600;\n    }\n\n    .mljar-select-option.is-selected:hover,\n    .mljar-select-option.is-selected:active {\n      background: #e6f2ff;\n      color: #007bff;\n    }\n\n    .mljar-select-empty {\n      display: none;\n      padding: 10px;\n      color: #616673;\n      font-size: 0.95em;\n    }\n\n    .mljar-select-widget-input:disabled {\n      background: #f5f5f5;\n      color: #888;\n      cursor: not-allowed;\n    }\n\n    .mljar-select-control.is-disabled .mljar-select-caret {\n      opacity: 0.45;\n    }\n    ",
       "_dom_classes": [],
       "_esm": "\n    function render({ model, el }) {\n      const normalize = value => String(value ?? \"\").toLowerCase().trim();\n      const getChoices = () =>\n        Array.isArray(model.get(\"choices\")) ? [...model.get(\"choices\")] : [];\n      const isDisabled = () => !!model.get(\"disabled\");\n      const isHidden = () => !!model.get(\"hidden\");\n\n      const container = document.createElement(\"div\");\n      container.classList.add(\"mljar-select-container\");\n\n      if (model.get(\"label\")) {\n        const topLabel = document.createElement(\"div\");\n        topLabel.classList.add(\"mljar-select-label\");\n        topLabel.innerHTML = model.get(\"label\");\n        container.appendChild(topLabel);\n      }\n\n      const control = document.createElement(\"div\");\n      control.classList.add(\"mljar-select-control\");\n\n      const input = document.createElement(\"input\");\n      input.type = \"text\";\n      input.classList.add(\"mljar-select-widget-input\");\n      input.autocomplete = \"off\";\n      input.spellcheck = false;\n\n      const caret = document.createElement(\"div\");\n      caret.classList.add(\"mljar-select-caret\");\n\n      control.appendChild(input);\n      control.appendChild(caret);\n\n      const dropdown = document.createElement(\"div\");\n      dropdown.classList.add(\"mljar-select-dropdown\");\n\n      const list = document.createElement(\"div\");\n      list.classList.add(\"mljar-select-list\");\n\n      const emptyState = document.createElement(\"div\");\n      emptyState.classList.add(\"mljar-select-empty\");\n      emptyState.textContent = \"No matches\";\n\n      dropdown.appendChild(list);\n      dropdown.appendChild(emptyState);\n\n      container.appendChild(control);\n      container.appendChild(dropdown);\n      el.appendChild(container);\n\n      let isOpen = false;\n      let filteredChoices = [];\n      let lastCommittedValue = \"\";\n      let isEditing = false;\n\n      const setOpen = next => {\n        if (isDisabled()) {\n          isOpen = false;\n        } else {\n          isOpen = !!next;\n        }\n        container.classList.toggle(\"is-open\", isOpen);\n        dropdown.style.display = isOpen ? \"block\" : \"none\";\n      };\n\n      const updateDisabledState = () => {\n        const disabled = isDisabled();\n        input.disabled = disabled;\n        control.classList.toggle(\"is-disabled\", disabled);\n      };\n\n      const updateHiddenState = () => {\n        container.style.display = isHidden() ? \"none\" : \"\";\n      };\n\n      const syncInputWithValue = () => {\n        const value = model.get(\"value\") || \"\";\n        lastCommittedValue = value;\n        if (!isEditing) {\n          input.value = value;\n        }\n      };\n\n      const filterChoices = query => {\n        const normalizedQuery = normalize(query);\n        const allChoices = getChoices();\n        if (!normalizedQuery) {\n          return allChoices;\n        }\n        return allChoices.filter(choice =>\n          normalize(choice).includes(normalizedQuery)\n        );\n      };\n\n      const renderList = () => {\n        list.innerHTML = \"\";\n        filteredChoices.forEach(choice => {\n          const option = document.createElement(\"button\");\n          option.type = \"button\";\n          option.classList.add(\"mljar-select-option\");\n          if (choice === model.get(\"value\")) {\n            option.classList.add(\"is-selected\");\n          }\n          option.textContent = choice;\n          option.addEventListener(\"mousedown\", event => {\n            event.preventDefault();\n            event.stopPropagation();\n            model.set(\"value\", choice);\n            model.save_changes();\n            isEditing = false;\n            syncInputWithValue();\n            renderList();\n            setOpen(false);\n          });\n          list.appendChild(option);\n        });\n\n        const hasMatches = filteredChoices.length > 0;\n        list.style.display = hasMatches ? \"block\" : \"none\";\n        emptyState.style.display = hasMatches ? \"none\" : \"block\";\n      };\n\n      const refreshList = () => {\n        filteredChoices = filterChoices(input.value);\n        renderList();\n      };\n\n      const openWithCurrentQuery = () => {\n        isEditing = true;\n        input.value = \"\";\n        refreshList();\n        setOpen(true);\n      };\n\n      control.addEventListener(\"click\", event => {\n        event.stopPropagation();\n        if (isDisabled()) {\n          return;\n        }\n        openWithCurrentQuery();\n        input.focus();\n      });\n\n      input.addEventListener(\"input\", () => {\n        if (isDisabled()) {\n          return;\n        }\n        refreshList();\n        setOpen(true);\n      });\n\n      input.addEventListener(\"focus\", () => {\n        if (isDisabled()) {\n          return;\n        }\n        openWithCurrentQuery();\n      });\n\n      input.addEventListener(\"blur\", () => {\n        isEditing = false;\n        input.value = lastCommittedValue;\n      });\n\n      const handleDocumentClick = event => {\n        if (!container.contains(event.target)) {\n          isEditing = false;\n          setOpen(false);\n          input.value = lastCommittedValue;\n        }\n      };\n\n      document.addEventListener(\"click\", handleDocumentClick);\n\n      model.on(\"change:value\", () => {\n        syncInputWithValue();\n        refreshList();\n      });\n\n      model.on(\"change:choices\", () => {\n        const choices = getChoices();\n        if (!choices.includes(model.get(\"value\")) && choices.length > 0) {\n          model.set(\"value\", choices[0]);\n          model.save_changes();\n          return;\n        }\n        syncInputWithValue();\n        refreshList();\n      });\n\n      model.on(\"change:disabled\", () => {\n        updateDisabledState();\n        if (isDisabled()) {\n          isEditing = false;\n          setOpen(false);\n        }\n      });\n\n      model.on(\"change:hidden\", () => {\n        updateHiddenState();\n      });\n\n      updateDisabledState();\n      updateHiddenState();\n      syncInputWithValue();\n      refreshList();\n      setOpen(false);\n\n      return () => {\n        document.removeEventListener(\"click\", handleDocumentClick);\n      };\n    }\n    export default { render };\n    ",
       "_model_module": "anywidget",
       "_model_module_version": "~0.11.*",
       "_model_name": "AnyModel",
       "_view_count": null,
       "_view_module": "anywidget",
       "_view_module_version": "~0.11.*",
       "_view_name": "AnyView",
       "cell_id": "",
       "choices": [
        "13.5% (deck)",
        "8.0% (CTM treasury)"
       ],
       "disabled": false,
       "hidden": false,
       "label": "NPV discount rate",
       "layout": "IPY_MODEL_97775efd267643e2a6cacf19f74d8881",
       "layout_path": null,
       "position": "sidebar",
       "render_slot_id": null,
       "source_cell_id": null,
       "tabbable": null,
       "tooltip": null,
       "url_key": "",
       "value": "13.5% (deck)"
      }
     },
     "4642350575194490b7d9ed5c8f4e956f": {
      "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
      }
     },
     "56ad595a5e4a45c9ab13fb8ca1822d82": {
      "model_module": "anywidget",
      "model_module_version": "~0.11.*",
      "model_name": "AnyModel",
      "state": {
       "_anywidget_id": "mercury.number.NumberInputWidget",
       "_css": "\n    .mljar-number-container {\n      display: flex;\n      flex-direction: column;\n      width: 100%;\n      font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n      font-size: 14px;\n      color: #0f172a;\n      margin-bottom: 8px;\n      padding-left: 4px;\n      padding-right: 4px;\n      box-sizing: border-box;\n    }\n\n    .mljar-number-label {\n      margin-bottom: 4px;\n      font-weight: 600;\n    }\n\n    .mljar-number-field-row {\n      display: flex;\n      align-items: stretch;\n      width: 100%;\n      min-height: 40px;\n      border: 1px solid #cfd1d5;\n      border-radius: 6px;\n      background: #ffffff;\n      box-sizing: border-box;\n      overflow: hidden;\n    }\n\n    .mljar-number-input {\n      flex: 1 1 auto;\n      min-width: 0;\n      min-height: 100%;\n      padding: 7px 10px;\n      border: 0;\n      border-radius: 0;\n      background: #ffffff;\n      box-sizing: border-box;\n      background-color: #ffffff !important;\n      color: #0f172a !important;\n      font: inherit;\n      line-height: 1.2;\n      -moz-appearance: textfield;\n    }\n\n    .mljar-number-input::-webkit-outer-spin-button,\n    .mljar-number-input::-webkit-inner-spin-button {\n      -webkit-appearance: none;\n      margin: 0;\n    }\n\n    .mljar-number-input:disabled {\n      background: #f5f5f5;\n      color: #888;\n      cursor: not-allowed;\n    }\n\n    .mljar-number-input:focus {\n      outline: none;\n    }\n\n    .mljar-number-field-row:focus-within {\n      border-color: #007bff;\n      border-width: 2px;\n      box-shadow: none;\n    }\n\n    .mljar-number-field-row:focus-within .mljar-number-controls {\n      border-left-color: #007bff;\n    }\n\n    .mljar-number-controls {\n      display: flex;\n      align-items: stretch;\n      flex: 0 0 auto;\n      border-left: 1px solid #cfd1d5;\n      background: #f1f1f2;\n    }\n\n    .mljar-number-step-btn {\n      display: inline-flex;\n      align-items: center;\n      justify-content: center;\n      width: 38px;\n      min-width: 38px;\n      min-height: 100%;\n      border: 0;\n      border-radius: 0;\n      background: transparent;\n      color: #0f172a;\n      font: inherit;\n      font-size: 18px;\n      font-weight: 700;\n      line-height: 1;\n      cursor: pointer;\n      padding: 0;\n      user-select: none;\n      -webkit-user-select: none;\n      touch-action: manipulation;\n      transition: background-color 0.14s ease, color 0.14s ease;\n    }\n\n    .mljar-number-step-up {\n      border-left: 1px solid #cfd1d5;\n    }\n\n    .mljar-number-step-btn:hover {\n      background: #f3f3f4;\n    }\n\n    .mljar-number-step-btn:active {\n      background: #e6f2ff;\n      color: #007bff;\n    }\n\n    .mljar-number-step-btn:focus-visible {\n      outline: none;\n      background: #e6f2ff;\n      color: #007bff;\n    }\n\n    .mljar-number-step-btn:disabled {\n      background: #f5f5f5;\n      color: #aaa;\n      cursor: not-allowed;\n    }\n\n    @media (max-width: 768px) {\n      .mljar-number-field-row {\n        min-height: 44px;\n      }\n\n      .mljar-number-input {\n        min-height: 44px;\n        padding: 8px 12px;\n      }\n\n      .mljar-number-step-btn {\n        font-size: 19px;\n        width: 44px;\n        min-width: 44px;\n      }\n    }\n    ",
       "_dom_classes": [],
       "_esm": "\n    function render({ model, el }) {\n      const container = document.createElement(\"div\");\n      container.classList.add(\"mljar-number-container\");\n\n      const topLabel = document.createElement(\"div\");\n      topLabel.classList.add(\"mljar-number-label\");\n\n      const fieldRow = document.createElement(\"div\");\n      fieldRow.classList.add(\"mljar-number-field-row\");\n\n      const input = document.createElement(\"input\");\n      input.type = \"number\";\n      input.classList.add(\"mljar-number-input\");\n\n      const decrementBtn = document.createElement(\"button\");\n      decrementBtn.type = \"button\";\n      decrementBtn.classList.add(\"mljar-number-step-btn\", \"mljar-number-step-down\");\n      decrementBtn.textContent = \"-\";\n      decrementBtn.setAttribute(\"aria-label\", \"Decrease value\");\n\n      const incrementBtn = document.createElement(\"button\");\n      incrementBtn.type = \"button\";\n      incrementBtn.classList.add(\"mljar-number-step-btn\", \"mljar-number-step-up\");\n      incrementBtn.textContent = \"+\";\n      incrementBtn.setAttribute(\"aria-label\", \"Increase value\");\n\n      const controls = document.createElement(\"div\");\n      controls.classList.add(\"mljar-number-controls\");\n\n      controls.appendChild(decrementBtn);\n      controls.appendChild(incrementBtn);\n      fieldRow.appendChild(input);\n      fieldRow.appendChild(controls);\n\n      container.appendChild(topLabel);\n      container.appendChild(fieldRow);\n      el.appendChild(container);\n\n      function clamp(val, min, max) {\n        if (Number.isFinite(min) && val < min) return min;\n        if (Number.isFinite(max) && val > max) return max;\n        return val;\n      }\n\n      function normalizeStep(step) {\n        return Number.isFinite(step) && step > 0 ? step : 1;\n      }\n\n      function snapToStep(value, min, step) {\n        const safeStep = normalizeStep(step);\n        const base = Number.isFinite(min) ? min : 0;\n        const steps = Math.round((value - base) / safeStep);\n        const snapped = base + steps * safeStep;\n        const precision = Math.max(\n          0,\n          (String(safeStep).split(\".\")[1] || \"\").length\n        );\n\n        return Number(snapped.toFixed(precision + 2));\n      }\n\n      function isOnStepGrid(value, min, step) {\n        const safeStep = normalizeStep(step);\n        const base = Number.isFinite(min) ? min : 0;\n        const steps = (value - base) / safeStep;\n        const nearest = Math.round(steps);\n        const epsilon = Math.max(1e-9, safeStep * 1e-9);\n\n        return Math.abs(steps - nearest) <= epsilon;\n      }\n\n      function getCurrentBounds() {\n        return {\n          min: Number(model.get(\"min\")),\n          max: Number(model.get(\"max\")),\n        };\n      }\n\n      function isTransientDraft(raw) {\n        return raw === \"\" || raw === \"-\" || raw === \".\" || raw === \"-.\";\n      }\n\n      let isEditing = false;\n      const INPUT_COMMIT_DEBOUNCE_MS = 400;\n\n      function clearPendingDraftCommit() {\n        if (debounceTimer) clearTimeout(debounceTimer);\n        pendingDraftValue = null;\n      }\n\n      function parseDraftValue(rawValue) {\n        const raw = String(rawValue).trim();\n        if (isTransientDraft(raw)) {\n          return { kind: \"transient\" };\n        }\n\n        const value = Number(raw);\n        if (!Number.isFinite(value)) {\n          return { kind: \"invalid\" };\n        }\n\n        return { kind: \"number\", value };\n      }\n\n      function commitValue(nextValue, saveNow = true) {\n        const { min, max } = getCurrentBounds();\n        const step = Number(model.get(\"step\"));\n        const parsed = parseDraftValue(nextValue);\n        if (parsed.kind !== \"number\") {\n          syncFromModel();\n          return;\n        }\n\n        let v = parsed.value;\n        v = clamp(v, min, max);\n        v = snapToStep(v, min, step);\n        v = clamp(v, min, max);\n        input.value = String(v);\n        model.set(\"value\", v);\n\n        if (saveNow) {\n          model.save_changes();\n        }\n      }\n\n      function syncFromModel() {\n        topLabel.innerHTML = model.get(\"label\") || \"Enter number\";\n\n        const min = Number(model.get(\"min\"));\n        const max = Number(model.get(\"max\"));\n        const step = Number(model.get(\"step\"));\n\n        if (Number.isFinite(min)) input.min = String(min); else input.removeAttribute(\"min\");\n        if (Number.isFinite(max)) input.max = String(max); else input.removeAttribute(\"max\");\n        if (Number.isFinite(step)) input.step = String(step); else input.removeAttribute(\"step\");\n\n        const v = Number(model.get(\"value\"));\n        if (!isEditing) {\n          input.value = Number.isFinite(v) ? String(v) : \"\";\n        }\n\n        const disabled = !!model.get(\"disabled\");\n        input.disabled = disabled;\n        incrementBtn.disabled = disabled;\n        decrementBtn.disabled = disabled;\n\n        const hidden = !!model.get(\"hidden\");\n        container.style.display = hidden ? \"none\" : \"flex\";\n      }\n\n      let debounceTimer = null;\n      let pendingDraftValue = null;\n      input.addEventListener(\"focus\", () => {\n        isEditing = true;\n      });\n\n      input.addEventListener(\"input\", () => {\n        if (model.get(\"disabled\")) return;\n\n        const parsed = parseDraftValue(input.value);\n        if (parsed.kind !== \"number\") {\n          clearPendingDraftCommit();\n          return;\n        }\n\n        const v = parsed.value;\n        const { min, max } = getCurrentBounds();\n        const step = Number(model.get(\"step\"));\n        if (Number.isFinite(min) && v < min) {\n          clearPendingDraftCommit();\n          return;\n        }\n        if (Number.isFinite(max) && v > max) {\n          clearPendingDraftCommit();\n          return;\n        }\n        if (!isOnStepGrid(v, min, step)) {\n          clearPendingDraftCommit();\n          return;\n        }\n\n        pendingDraftValue = v;\n        if (debounceTimer) clearTimeout(debounceTimer);\n        debounceTimer = setTimeout(() => {\n          if (pendingDraftValue === null) return;\n          model.set(\"value\", pendingDraftValue);\n          model.save_changes();\n          pendingDraftValue = null;\n        }, INPUT_COMMIT_DEBOUNCE_MS);\n      });\n\n      input.addEventListener(\"blur\", () => {\n        isEditing = false;\n        clearPendingDraftCommit();\n        commitValue(input.value, true);\n      });\n\n      input.addEventListener(\"keydown\", event => {\n        if (event.key === \"Enter\") {\n          event.preventDefault();\n          input.blur();\n        }\n      });\n\n      incrementBtn.addEventListener(\"click\", () => {\n        if (model.get(\"disabled\")) return;\n        const current = Number(model.get(\"value\"));\n        const step = normalizeStep(Number(model.get(\"step\")));\n        const min = Number(model.get(\"min\"));\n        const base = Number.isFinite(current) ? current : 0;\n        commitValue(snapToStep(base + step, min, step));\n      });\n\n      decrementBtn.addEventListener(\"click\", () => {\n        if (model.get(\"disabled\")) return;\n        const current = Number(model.get(\"value\"));\n        const step = normalizeStep(Number(model.get(\"step\")));\n        const min = Number(model.get(\"min\"));\n        const base = Number.isFinite(current) ? current : 0;\n        commitValue(snapToStep(base - step, min, step));\n      });\n\n      model.on(\"change:value\", syncFromModel);\n      model.on(\"change:min\", syncFromModel);\n      model.on(\"change:max\", syncFromModel);\n      model.on(\"change:step\", syncFromModel);\n      model.on(\"change:label\", syncFromModel);\n      model.on(\"change:disabled\", syncFromModel);\n      model.on(\"change:hidden\", syncFromModel);\n\n      syncFromModel();\n\n      // ---- read cell id (no DOM modifications) ----\n      /*\n      const ID_ATTR = \"data-cell-id\";\n      const hostWithId = el.closest(`[${ID_ATTR}]`);\n      const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n      if (cellId) {\n        model.set(\"cell_id\", cellId);\n        model.save_changes();\n        model.send({ type: \"cell_id_detected\", value: cellId });\n      } else {\n        const mo = new MutationObserver(() => {\n          const host = el.closest(`[${ID_ATTR}]`);\n          const newId = host?.getAttribute(ID_ATTR);\n          if (newId) {\n            model.set(\"cell_id\", newId);\n            model.save_changes();\n            model.send({ type: \"cell_id_detected\", value: newId });\n            mo.disconnect();\n          }\n        });\n        mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n      }*/\n    }\n    export default { render };\n    ",
       "_model_module": "anywidget",
       "_model_module_version": "~0.11.*",
       "_model_name": "AnyModel",
       "_view_count": null,
       "_view_module": "anywidget",
       "_view_module_version": "~0.11.*",
       "_view_name": "AnyView",
       "cell_id": "",
       "disabled": false,
       "hidden": false,
       "label": "ASIA — current platform cost ($/yr)",
       "layout": "IPY_MODEL_a9f8ba71821a4c59be71d4f6f20d5cb1",
       "layout_path": null,
       "max": 8000000.0,
       "min": 0.0,
       "position": "inline",
       "render_slot_id": null,
       "source_cell_id": null,
       "step": 10000.0,
       "tabbable": null,
       "tooltip": null,
       "url_key": "",
       "value": 2069588.0
      }
     },
     "58d70d2b4fef4c8f87e02d5e6a5ced5d": {
      "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
      }
     },
     "60230c9272ad41139f7b8cdc16ad0d87": {
      "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
      }
     },
     "69363ef728004eb587b95ad5eeb8995b": {
      "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
      }
     },
     "701f9b3f6fa747f5abd97876777785df": {
      "model_module": "anywidget",
      "model_module_version": "~0.11.*",
      "model_name": "AnyModel",
      "state": {
       "_anywidget_id": "mercury.date.DateInputWidget",
       "_css": "\n    .mljar-date-container {\n      display: flex;\n      flex-direction: column;\n      width: 100%;\n      font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n      font-size: 14px;\n      color: #0f172a;\n      margin-bottom: 8px;\n      padding-left: 4px;\n      padding-right: 4px;\n      box-sizing: border-box;\n    }\n\n    .mljar-date-label {\n      margin-bottom: 4px;\n      font-weight: 600;\n    }\n\n    .mljar-date-input {\n      width: 100%;\n      min-height: 40px;\n      padding: 9px 10px;\n      border: 1px solid #cfd1d5;\n      border-radius: 6px;\n      background: #ffffff;\n      color: #0f172a;\n      box-sizing: border-box;\n      line-height: 1.4;\n    }\n\n    .mljar-date-input:disabled {\n      background: #f5f5f5;\n      color: #888;\n      cursor: not-allowed;\n    }\n\n    .mljar-date-input:focus {\n      outline: none;\n      border-color: #007bff;\n      border-width: 2px;\n      box-shadow: none;\n    }\n\n    @media (max-width: 768px) {\n      .mljar-date-input {\n        min-height: 44px;\n        padding: 10px 12px;\n      }\n    }\n    ",
       "_dom_classes": [],
       "_esm": "\n    function render({ model, el }) {\n      const container = document.createElement(\"div\");\n      container.classList.add(\"mljar-date-container\");\n\n      const topLabel = document.createElement(\"div\");\n      topLabel.classList.add(\"mljar-date-label\");\n\n      const input = document.createElement(\"input\");\n      input.type = \"date\";\n      input.classList.add(\"mljar-date-input\");\n\n      container.appendChild(topLabel);\n      container.appendChild(input);\n      el.appendChild(container);\n\n      function syncFromModel() {\n        topLabel.innerHTML = model.get(\"label\") || \"Date\";\n        input.value = model.get(\"value\") || \"\";\n\n        const min = model.get(\"min\") || \"\";\n        const max = model.get(\"max\") || \"\";\n        if (min) input.min = min; else input.removeAttribute(\"min\");\n        if (max) input.max = max; else input.removeAttribute(\"max\");\n\n        input.disabled = !!model.get(\"disabled\");\n        container.style.display = model.get(\"hidden\") ? \"none\" : \"flex\";\n      }\n\n      input.addEventListener(\"change\", () => {\n        if (model.get(\"disabled\")) return;\n        model.set(\"value\", input.value);\n        model.save_changes();\n      });\n\n      model.on(\"change:value\", syncFromModel);\n      model.on(\"change:min\", syncFromModel);\n      model.on(\"change:max\", syncFromModel);\n      model.on(\"change:label\", syncFromModel);\n      model.on(\"change:disabled\", syncFromModel);\n      model.on(\"change:hidden\", syncFromModel);\n\n      syncFromModel();\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": "ASIA — contract termination",
       "layout": "IPY_MODEL_b4137f1980b14440abee145f02a3f3ec",
       "layout_path": null,
       "max": "",
       "min": "",
       "position": "inline",
       "render_slot_id": null,
       "source_cell_id": null,
       "tabbable": null,
       "tooltip": null,
       "url_key": "",
       "value": "2027-12-31"
      }
     },
     "72b80b7fd621417ca15b3ac7d058b757": {
      "model_module": "anywidget",
      "model_module_version": "~0.11.*",
      "model_name": "AnyModel",
      "state": {
       "_anywidget_id": "mercury.date.DateInputWidget",
       "_css": "\n    .mljar-date-container {\n      display: flex;\n      flex-direction: column;\n      width: 100%;\n      font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n      font-size: 14px;\n      color: #0f172a;\n      margin-bottom: 8px;\n      padding-left: 4px;\n      padding-right: 4px;\n      box-sizing: border-box;\n    }\n\n    .mljar-date-label {\n      margin-bottom: 4px;\n      font-weight: 600;\n    }\n\n    .mljar-date-input {\n      width: 100%;\n      min-height: 40px;\n      padding: 9px 10px;\n      border: 1px solid #cfd1d5;\n      border-radius: 6px;\n      background: #ffffff;\n      color: #0f172a;\n      box-sizing: border-box;\n      line-height: 1.4;\n    }\n\n    .mljar-date-input:disabled {\n      background: #f5f5f5;\n      color: #888;\n      cursor: not-allowed;\n    }\n\n    .mljar-date-input:focus {\n      outline: none;\n      border-color: #007bff;\n      border-width: 2px;\n      box-shadow: none;\n    }\n\n    @media (max-width: 768px) {\n      .mljar-date-input {\n        min-height: 44px;\n        padding: 10px 12px;\n      }\n    }\n    ",
       "_dom_classes": [],
       "_esm": "\n    function render({ model, el }) {\n      const container = document.createElement(\"div\");\n      container.classList.add(\"mljar-date-container\");\n\n      const topLabel = document.createElement(\"div\");\n      topLabel.classList.add(\"mljar-date-label\");\n\n      const input = document.createElement(\"input\");\n      input.type = \"date\";\n      input.classList.add(\"mljar-date-input\");\n\n      container.appendChild(topLabel);\n      container.appendChild(input);\n      el.appendChild(container);\n\n      function syncFromModel() {\n        topLabel.innerHTML = model.get(\"label\") || \"Date\";\n        input.value = model.get(\"value\") || \"\";\n\n        const min = model.get(\"min\") || \"\";\n        const max = model.get(\"max\") || \"\";\n        if (min) input.min = min; else input.removeAttribute(\"min\");\n        if (max) input.max = max; else input.removeAttribute(\"max\");\n\n        input.disabled = !!model.get(\"disabled\");\n        container.style.display = model.get(\"hidden\") ? \"none\" : \"flex\";\n      }\n\n      input.addEventListener(\"change\", () => {\n        if (model.get(\"disabled\")) return;\n        model.set(\"value\", input.value);\n        model.save_changes();\n      });\n\n      model.on(\"change:value\", syncFromModel);\n      model.on(\"change:min\", syncFromModel);\n      model.on(\"change:max\", syncFromModel);\n      model.on(\"change:label\", syncFromModel);\n      model.on(\"change:disabled\", syncFromModel);\n      model.on(\"change:hidden\", syncFromModel);\n\n      syncFromModel();\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": "EMEA — contract termination",
       "layout": "IPY_MODEL_bcd839aaf93d4435a34035babc729daf",
       "layout_path": null,
       "max": "",
       "min": "",
       "position": "inline",
       "render_slot_id": null,
       "source_cell_id": null,
       "tabbable": null,
       "tooltip": null,
       "url_key": "",
       "value": "2027-12-31"
      }
     },
     "7742525da3ea413e97d2cfc40e6b73d4": {
      "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
      }
     },
     "7a150d2b059b4c769da78ec1301ce34d": {
      "model_module": "anywidget",
      "model_module_version": "~0.11.*",
      "model_name": "AnyModel",
      "state": {
       "_anywidget_id": "mercury.number.NumberInputWidget",
       "_css": "\n    .mljar-number-container {\n      display: flex;\n      flex-direction: column;\n      width: 100%;\n      font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n      font-size: 14px;\n      color: #0f172a;\n      margin-bottom: 8px;\n      padding-left: 4px;\n      padding-right: 4px;\n      box-sizing: border-box;\n    }\n\n    .mljar-number-label {\n      margin-bottom: 4px;\n      font-weight: 600;\n    }\n\n    .mljar-number-field-row {\n      display: flex;\n      align-items: stretch;\n      width: 100%;\n      min-height: 40px;\n      border: 1px solid #cfd1d5;\n      border-radius: 6px;\n      background: #ffffff;\n      box-sizing: border-box;\n      overflow: hidden;\n    }\n\n    .mljar-number-input {\n      flex: 1 1 auto;\n      min-width: 0;\n      min-height: 100%;\n      padding: 7px 10px;\n      border: 0;\n      border-radius: 0;\n      background: #ffffff;\n      box-sizing: border-box;\n      background-color: #ffffff !important;\n      color: #0f172a !important;\n      font: inherit;\n      line-height: 1.2;\n      -moz-appearance: textfield;\n    }\n\n    .mljar-number-input::-webkit-outer-spin-button,\n    .mljar-number-input::-webkit-inner-spin-button {\n      -webkit-appearance: none;\n      margin: 0;\n    }\n\n    .mljar-number-input:disabled {\n      background: #f5f5f5;\n      color: #888;\n      cursor: not-allowed;\n    }\n\n    .mljar-number-input:focus {\n      outline: none;\n    }\n\n    .mljar-number-field-row:focus-within {\n      border-color: #007bff;\n      border-width: 2px;\n      box-shadow: none;\n    }\n\n    .mljar-number-field-row:focus-within .mljar-number-controls {\n      border-left-color: #007bff;\n    }\n\n    .mljar-number-controls {\n      display: flex;\n      align-items: stretch;\n      flex: 0 0 auto;\n      border-left: 1px solid #cfd1d5;\n      background: #f1f1f2;\n    }\n\n    .mljar-number-step-btn {\n      display: inline-flex;\n      align-items: center;\n      justify-content: center;\n      width: 38px;\n      min-width: 38px;\n      min-height: 100%;\n      border: 0;\n      border-radius: 0;\n      background: transparent;\n      color: #0f172a;\n      font: inherit;\n      font-size: 18px;\n      font-weight: 700;\n      line-height: 1;\n      cursor: pointer;\n      padding: 0;\n      user-select: none;\n      -webkit-user-select: none;\n      touch-action: manipulation;\n      transition: background-color 0.14s ease, color 0.14s ease;\n    }\n\n    .mljar-number-step-up {\n      border-left: 1px solid #cfd1d5;\n    }\n\n    .mljar-number-step-btn:hover {\n      background: #f3f3f4;\n    }\n\n    .mljar-number-step-btn:active {\n      background: #e6f2ff;\n      color: #007bff;\n    }\n\n    .mljar-number-step-btn:focus-visible {\n      outline: none;\n      background: #e6f2ff;\n      color: #007bff;\n    }\n\n    .mljar-number-step-btn:disabled {\n      background: #f5f5f5;\n      color: #aaa;\n      cursor: not-allowed;\n    }\n\n    @media (max-width: 768px) {\n      .mljar-number-field-row {\n        min-height: 44px;\n      }\n\n      .mljar-number-input {\n        min-height: 44px;\n        padding: 8px 12px;\n      }\n\n      .mljar-number-step-btn {\n        font-size: 19px;\n        width: 44px;\n        min-width: 44px;\n      }\n    }\n    ",
       "_dom_classes": [],
       "_esm": "\n    function render({ model, el }) {\n      const container = document.createElement(\"div\");\n      container.classList.add(\"mljar-number-container\");\n\n      const topLabel = document.createElement(\"div\");\n      topLabel.classList.add(\"mljar-number-label\");\n\n      const fieldRow = document.createElement(\"div\");\n      fieldRow.classList.add(\"mljar-number-field-row\");\n\n      const input = document.createElement(\"input\");\n      input.type = \"number\";\n      input.classList.add(\"mljar-number-input\");\n\n      const decrementBtn = document.createElement(\"button\");\n      decrementBtn.type = \"button\";\n      decrementBtn.classList.add(\"mljar-number-step-btn\", \"mljar-number-step-down\");\n      decrementBtn.textContent = \"-\";\n      decrementBtn.setAttribute(\"aria-label\", \"Decrease value\");\n\n      const incrementBtn = document.createElement(\"button\");\n      incrementBtn.type = \"button\";\n      incrementBtn.classList.add(\"mljar-number-step-btn\", \"mljar-number-step-up\");\n      incrementBtn.textContent = \"+\";\n      incrementBtn.setAttribute(\"aria-label\", \"Increase value\");\n\n      const controls = document.createElement(\"div\");\n      controls.classList.add(\"mljar-number-controls\");\n\n      controls.appendChild(decrementBtn);\n      controls.appendChild(incrementBtn);\n      fieldRow.appendChild(input);\n      fieldRow.appendChild(controls);\n\n      container.appendChild(topLabel);\n      container.appendChild(fieldRow);\n      el.appendChild(container);\n\n      function clamp(val, min, max) {\n        if (Number.isFinite(min) && val < min) return min;\n        if (Number.isFinite(max) && val > max) return max;\n        return val;\n      }\n\n      function normalizeStep(step) {\n        return Number.isFinite(step) && step > 0 ? step : 1;\n      }\n\n      function snapToStep(value, min, step) {\n        const safeStep = normalizeStep(step);\n        const base = Number.isFinite(min) ? min : 0;\n        const steps = Math.round((value - base) / safeStep);\n        const snapped = base + steps * safeStep;\n        const precision = Math.max(\n          0,\n          (String(safeStep).split(\".\")[1] || \"\").length\n        );\n\n        return Number(snapped.toFixed(precision + 2));\n      }\n\n      function isOnStepGrid(value, min, step) {\n        const safeStep = normalizeStep(step);\n        const base = Number.isFinite(min) ? min : 0;\n        const steps = (value - base) / safeStep;\n        const nearest = Math.round(steps);\n        const epsilon = Math.max(1e-9, safeStep * 1e-9);\n\n        return Math.abs(steps - nearest) <= epsilon;\n      }\n\n      function getCurrentBounds() {\n        return {\n          min: Number(model.get(\"min\")),\n          max: Number(model.get(\"max\")),\n        };\n      }\n\n      function isTransientDraft(raw) {\n        return raw === \"\" || raw === \"-\" || raw === \".\" || raw === \"-.\";\n      }\n\n      let isEditing = false;\n      const INPUT_COMMIT_DEBOUNCE_MS = 400;\n\n      function clearPendingDraftCommit() {\n        if (debounceTimer) clearTimeout(debounceTimer);\n        pendingDraftValue = null;\n      }\n\n      function parseDraftValue(rawValue) {\n        const raw = String(rawValue).trim();\n        if (isTransientDraft(raw)) {\n          return { kind: \"transient\" };\n        }\n\n        const value = Number(raw);\n        if (!Number.isFinite(value)) {\n          return { kind: \"invalid\" };\n        }\n\n        return { kind: \"number\", value };\n      }\n\n      function commitValue(nextValue, saveNow = true) {\n        const { min, max } = getCurrentBounds();\n        const step = Number(model.get(\"step\"));\n        const parsed = parseDraftValue(nextValue);\n        if (parsed.kind !== \"number\") {\n          syncFromModel();\n          return;\n        }\n\n        let v = parsed.value;\n        v = clamp(v, min, max);\n        v = snapToStep(v, min, step);\n        v = clamp(v, min, max);\n        input.value = String(v);\n        model.set(\"value\", v);\n\n        if (saveNow) {\n          model.save_changes();\n        }\n      }\n\n      function syncFromModel() {\n        topLabel.innerHTML = model.get(\"label\") || \"Enter number\";\n\n        const min = Number(model.get(\"min\"));\n        const max = Number(model.get(\"max\"));\n        const step = Number(model.get(\"step\"));\n\n        if (Number.isFinite(min)) input.min = String(min); else input.removeAttribute(\"min\");\n        if (Number.isFinite(max)) input.max = String(max); else input.removeAttribute(\"max\");\n        if (Number.isFinite(step)) input.step = String(step); else input.removeAttribute(\"step\");\n\n        const v = Number(model.get(\"value\"));\n        if (!isEditing) {\n          input.value = Number.isFinite(v) ? String(v) : \"\";\n        }\n\n        const disabled = !!model.get(\"disabled\");\n        input.disabled = disabled;\n        incrementBtn.disabled = disabled;\n        decrementBtn.disabled = disabled;\n\n        const hidden = !!model.get(\"hidden\");\n        container.style.display = hidden ? \"none\" : \"flex\";\n      }\n\n      let debounceTimer = null;\n      let pendingDraftValue = null;\n      input.addEventListener(\"focus\", () => {\n        isEditing = true;\n      });\n\n      input.addEventListener(\"input\", () => {\n        if (model.get(\"disabled\")) return;\n\n        const parsed = parseDraftValue(input.value);\n        if (parsed.kind !== \"number\") {\n          clearPendingDraftCommit();\n          return;\n        }\n\n        const v = parsed.value;\n        const { min, max } = getCurrentBounds();\n        const step = Number(model.get(\"step\"));\n        if (Number.isFinite(min) && v < min) {\n          clearPendingDraftCommit();\n          return;\n        }\n        if (Number.isFinite(max) && v > max) {\n          clearPendingDraftCommit();\n          return;\n        }\n        if (!isOnStepGrid(v, min, step)) {\n          clearPendingDraftCommit();\n          return;\n        }\n\n        pendingDraftValue = v;\n        if (debounceTimer) clearTimeout(debounceTimer);\n        debounceTimer = setTimeout(() => {\n          if (pendingDraftValue === null) return;\n          model.set(\"value\", pendingDraftValue);\n          model.save_changes();\n          pendingDraftValue = null;\n        }, INPUT_COMMIT_DEBOUNCE_MS);\n      });\n\n      input.addEventListener(\"blur\", () => {\n        isEditing = false;\n        clearPendingDraftCommit();\n        commitValue(input.value, true);\n      });\n\n      input.addEventListener(\"keydown\", event => {\n        if (event.key === \"Enter\") {\n          event.preventDefault();\n          input.blur();\n        }\n      });\n\n      incrementBtn.addEventListener(\"click\", () => {\n        if (model.get(\"disabled\")) return;\n        const current = Number(model.get(\"value\"));\n        const step = normalizeStep(Number(model.get(\"step\")));\n        const min = Number(model.get(\"min\"));\n        const base = Number.isFinite(current) ? current : 0;\n        commitValue(snapToStep(base + step, min, step));\n      });\n\n      decrementBtn.addEventListener(\"click\", () => {\n        if (model.get(\"disabled\")) return;\n        const current = Number(model.get(\"value\"));\n        const step = normalizeStep(Number(model.get(\"step\")));\n        const min = Number(model.get(\"min\"));\n        const base = Number.isFinite(current) ? current : 0;\n        commitValue(snapToStep(base - step, min, step));\n      });\n\n      model.on(\"change:value\", syncFromModel);\n      model.on(\"change:min\", syncFromModel);\n      model.on(\"change:max\", syncFromModel);\n      model.on(\"change:step\", syncFromModel);\n      model.on(\"change:label\", syncFromModel);\n      model.on(\"change:disabled\", syncFromModel);\n      model.on(\"change:hidden\", syncFromModel);\n\n      syncFromModel();\n\n      // ---- read cell id (no DOM modifications) ----\n      /*\n      const ID_ATTR = \"data-cell-id\";\n      const hostWithId = el.closest(`[${ID_ATTR}]`);\n      const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n      if (cellId) {\n        model.set(\"cell_id\", cellId);\n        model.save_changes();\n        model.send({ type: \"cell_id_detected\", value: cellId });\n      } else {\n        const mo = new MutationObserver(() => {\n          const host = el.closest(`[${ID_ATTR}]`);\n          const newId = host?.getAttribute(ID_ATTR);\n          if (newId) {\n            model.set(\"cell_id\", newId);\n            model.save_changes();\n            model.send({ type: \"cell_id_detected\", value: newId });\n            mo.disconnect();\n          }\n        });\n        mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n      }*/\n    }\n    export default { render };\n    ",
       "_model_module": "anywidget",
       "_model_module_version": "~0.11.*",
       "_model_name": "AnyModel",
       "_view_count": null,
       "_view_module": "anywidget",
       "_view_module_version": "~0.11.*",
       "_view_name": "AnyView",
       "cell_id": "",
       "disabled": false,
       "hidden": false,
       "label": "NA — current platform cost ($/yr)",
       "layout": "IPY_MODEL_4642350575194490b7d9ed5c8f4e956f",
       "layout_path": null,
       "max": 8000000.0,
       "min": 0.0,
       "position": "inline",
       "render_slot_id": null,
       "source_cell_id": null,
       "step": 10000.0,
       "tabbable": null,
       "tooltip": null,
       "url_key": "",
       "value": 3348969.0
      }
     },
     "7ec342097b35498a8385688f0ab16f13": {
      "model_module": "anywidget",
      "model_module_version": "~0.11.*",
      "model_name": "AnyModel",
      "state": {
       "_anywidget_id": "mercury.checkbox.CheckboxWidget",
       "_css": "\n    .mljar-checkbox-container {\n        display: inline-flex;\n        align-items: center;\n        gap: 10px;\n        cursor: pointer;\n        user-select: none;\n        -webkit-tap-highlight-color: transparent;\n        font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n        color: #0f172a;\n        padding-left: 5px;\n    }\n    .mljar-checkbox-container.is-disabled {\n        opacity: 0.6;\n        cursor: not-allowed;\n    }\n\n    .mljar-checkbox-input {\n        position: absolute;\n        opacity: 0;\n        width: 0;\n        height: 0;\n    }\n\n    .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n        box-shadow: inset 0 0 0 2px #007bff;\n    }\n\n    .mljar-checkbox-label {\n        font-size: 14px;\n        line-height: 1.2;\n        padding-top: 2px;\n    }\n\n    /* --- Toggle style (15% smaller) --- */\n    .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n        position: relative;\n        width: 34px;\n        height: 19px;\n        border-radius: 999px;\n        background: #f1f1f2;\n        border: 1px solid #cfd1d5;\n        transition: background 150ms ease, border-color 150ms ease;\n    }\n    .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n        background: #007bff;\n        border-color: #007bff;\n    }\n    .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n        content: \"\";\n        position: absolute;\n        top: 2px;\n        left: 2px;\n        width: 15px;\n        height: 15px;\n        border-radius: 50%;\n        background: #ffffff;\n        box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n        transition: transform 150ms ease;\n    }\n    .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n        transform: translateX(15px);\n    }\n\n    /* --- Classic box style --- */\n    .mljar-checkbox-container.is-box .mljar-checkbox-control {\n        width: 16px;\n        height: 16px;\n        border-radius: 4px;\n        border: 1px solid #cfd1d5;\n        background: #ffffff;\n        display: inline-block;\n        position: relative;\n    }\n    .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n        border-color: #007bff;\n        background: #007bff;\n    }\n    .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n        content: \"\";\n        position: absolute;\n        left: 4px;\n        top: 0px;\n        width: 5px;\n        height: 10px;\n        border: solid #fff;\n        border-width: 0 2px 2px 0;\n        transform: rotate(45deg);\n    }\n\n    .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n        border-color: #007bff;\n        background: #f3f3f4;\n    }\n    ",
       "_dom_classes": [],
       "_esm": "\n    function render({ model, el }) {\n      const container = document.createElement(\"label\");\n      container.classList.add(\"mljar-checkbox-container\");\n\n      const input = document.createElement(\"input\");\n      input.type = \"checkbox\";\n      input.classList.add(\"mljar-checkbox-input\");\n\n      const control = document.createElement(\"span\");\n      control.classList.add(\"mljar-checkbox-control\");\n\n      const text = document.createElement(\"span\");\n      text.classList.add(\"mljar-checkbox-label\");\n\n      container.appendChild(input);\n      container.appendChild(control);\n      container.appendChild(text);\n      el.appendChild(container);\n\n      function syncFromModel() {\n        // appearance\n        const a = model.get(\"appearance\") || \"toggle\";\n        container.classList.remove(\"is-toggle\",\"is-box\");\n        container.classList.add(`is-${a}`);\n        input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n        // value\n        const v = !!model.get(\"value\");\n        if (input.checked !== v) {\n          input.checked = v;\n          input.setAttribute(\"aria-checked\", String(v));\n        }\n        container.classList.toggle(\"is-checked\", v);\n\n        // disabled\n        const d = !!model.get(\"disabled\");\n        input.disabled = d;\n        container.classList.toggle(\"is-disabled\", d);\n\n        // label\n        text.textContent = model.get(\"label\") || \"\";\n\n        // hidden (exists but not visible)\n        container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n      }\n\n      input.addEventListener(\"change\", () => {\n        if (model.get(\"disabled\")) return;\n\n        const v = input.checked;\n        if (model.get(\"value\") !== v) {\n          model.set(\"value\", v);\n          model.set(\"last_changed_at\", new Date().toISOString());\n          model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n          model.save_changes();\n          // model.send({ type: \"changed\", value: v });\n        }\n      });\n\n      model.on(\"change:value\", syncFromModel);\n      model.on(\"change:disabled\", syncFromModel);\n      model.on(\"change:appearance\", syncFromModel);\n      model.on(\"change:label\", syncFromModel);\n      model.on(\"change:hidden\", syncFromModel);\n\n      syncFromModel();\n\n      // ---- read cell id (no DOM modifications) ----\n      /*\n      const ID_ATTR = \"data-cell-id\";\n      const hostWithId = el.closest(`[${ID_ATTR}]`);\n      const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n      if (cellId) {\n        model.set(\"cell_id\", cellId);\n        model.save_changes();\n        model.send({ type: \"cell_id_detected\", value: cellId });\n      } else {\n        const mo = new MutationObserver(() => {\n          const host = el.closest(`[${ID_ATTR}]`);\n          const newId = host?.getAttribute(ID_ATTR);\n          \n          if (newId) {\n            model.set(\"cell_id\", newId);\n            model.save_changes();\n            model.send({ type: \"cell_id_detected\", value: newId });\n            mo.disconnect();\n          }\n        });\n        mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n      }\n      */\n    }\n    export default { render };\n    ",
       "_model_module": "anywidget",
       "_model_module_version": "~0.11.*",
       "_model_name": "AnyModel",
       "_view_count": null,
       "_view_module": "anywidget",
       "_view_module_version": "~0.11.*",
       "_view_name": "AnyView",
       "appearance": "toggle",
       "cell_id": "",
       "disabled": false,
       "hidden": false,
       "label": "WFM — ASIA (implement)",
       "last_changed_at": "",
       "layout": "IPY_MODEL_69363ef728004eb587b95ad5eeb8995b",
       "layout_path": null,
       "n_toggles": 0,
       "position": "sidebar",
       "render_slot_id": null,
       "source_cell_id": null,
       "tabbable": null,
       "tooltip": null,
       "url_key": "",
       "value": true
      }
     },
     "82431bad9263463187c3a824326eedcf": {
      "model_module": "anywidget",
      "model_module_version": "~0.11.*",
      "model_name": "AnyModel",
      "state": {
       "_anywidget_id": "mercury.date.DateInputWidget",
       "_css": "\n    .mljar-date-container {\n      display: flex;\n      flex-direction: column;\n      width: 100%;\n      font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n      font-size: 14px;\n      color: #0f172a;\n      margin-bottom: 8px;\n      padding-left: 4px;\n      padding-right: 4px;\n      box-sizing: border-box;\n    }\n\n    .mljar-date-label {\n      margin-bottom: 4px;\n      font-weight: 600;\n    }\n\n    .mljar-date-input {\n      width: 100%;\n      min-height: 40px;\n      padding: 9px 10px;\n      border: 1px solid #cfd1d5;\n      border-radius: 6px;\n      background: #ffffff;\n      color: #0f172a;\n      box-sizing: border-box;\n      line-height: 1.4;\n    }\n\n    .mljar-date-input:disabled {\n      background: #f5f5f5;\n      color: #888;\n      cursor: not-allowed;\n    }\n\n    .mljar-date-input:focus {\n      outline: none;\n      border-color: #007bff;\n      border-width: 2px;\n      box-shadow: none;\n    }\n\n    @media (max-width: 768px) {\n      .mljar-date-input {\n        min-height: 44px;\n        padding: 10px 12px;\n      }\n    }\n    ",
       "_dom_classes": [],
       "_esm": "\n    function render({ model, el }) {\n      const container = document.createElement(\"div\");\n      container.classList.add(\"mljar-date-container\");\n\n      const topLabel = document.createElement(\"div\");\n      topLabel.classList.add(\"mljar-date-label\");\n\n      const input = document.createElement(\"input\");\n      input.type = \"date\";\n      input.classList.add(\"mljar-date-input\");\n\n      container.appendChild(topLabel);\n      container.appendChild(input);\n      el.appendChild(container);\n\n      function syncFromModel() {\n        topLabel.innerHTML = model.get(\"label\") || \"Date\";\n        input.value = model.get(\"value\") || \"\";\n\n        const min = model.get(\"min\") || \"\";\n        const max = model.get(\"max\") || \"\";\n        if (min) input.min = min; else input.removeAttribute(\"min\");\n        if (max) input.max = max; else input.removeAttribute(\"max\");\n\n        input.disabled = !!model.get(\"disabled\");\n        container.style.display = model.get(\"hidden\") ? \"none\" : \"flex\";\n      }\n\n      input.addEventListener(\"change\", () => {\n        if (model.get(\"disabled\")) return;\n        model.set(\"value\", input.value);\n        model.save_changes();\n      });\n\n      model.on(\"change:value\", syncFromModel);\n      model.on(\"change:min\", syncFromModel);\n      model.on(\"change:max\", syncFromModel);\n      model.on(\"change:label\", syncFromModel);\n      model.on(\"change:disabled\", syncFromModel);\n      model.on(\"change:hidden\", syncFromModel);\n\n      syncFromModel();\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": "NA — contract termination",
       "layout": "IPY_MODEL_58d70d2b4fef4c8f87e02d5e6a5ced5d",
       "layout_path": null,
       "max": "",
       "min": "",
       "position": "inline",
       "render_slot_id": null,
       "source_cell_id": null,
       "tabbable": null,
       "tooltip": null,
       "url_key": "",
       "value": "2027-12-31"
      }
     },
     "8d3bd1c968c9418fbb2937303f3fbb38": {
      "model_module": "anywidget",
      "model_module_version": "~0.11.*",
      "model_name": "AnyModel",
      "state": {
       "_anywidget_id": "mercury.number.NumberInputWidget",
       "_css": "\n    .mljar-number-container {\n      display: flex;\n      flex-direction: column;\n      width: 100%;\n      font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n      font-size: 14px;\n      color: #0f172a;\n      margin-bottom: 8px;\n      padding-left: 4px;\n      padding-right: 4px;\n      box-sizing: border-box;\n    }\n\n    .mljar-number-label {\n      margin-bottom: 4px;\n      font-weight: 600;\n    }\n\n    .mljar-number-field-row {\n      display: flex;\n      align-items: stretch;\n      width: 100%;\n      min-height: 40px;\n      border: 1px solid #cfd1d5;\n      border-radius: 6px;\n      background: #ffffff;\n      box-sizing: border-box;\n      overflow: hidden;\n    }\n\n    .mljar-number-input {\n      flex: 1 1 auto;\n      min-width: 0;\n      min-height: 100%;\n      padding: 7px 10px;\n      border: 0;\n      border-radius: 0;\n      background: #ffffff;\n      box-sizing: border-box;\n      background-color: #ffffff !important;\n      color: #0f172a !important;\n      font: inherit;\n      line-height: 1.2;\n      -moz-appearance: textfield;\n    }\n\n    .mljar-number-input::-webkit-outer-spin-button,\n    .mljar-number-input::-webkit-inner-spin-button {\n      -webkit-appearance: none;\n      margin: 0;\n    }\n\n    .mljar-number-input:disabled {\n      background: #f5f5f5;\n      color: #888;\n      cursor: not-allowed;\n    }\n\n    .mljar-number-input:focus {\n      outline: none;\n    }\n\n    .mljar-number-field-row:focus-within {\n      border-color: #007bff;\n      border-width: 2px;\n      box-shadow: none;\n    }\n\n    .mljar-number-field-row:focus-within .mljar-number-controls {\n      border-left-color: #007bff;\n    }\n\n    .mljar-number-controls {\n      display: flex;\n      align-items: stretch;\n      flex: 0 0 auto;\n      border-left: 1px solid #cfd1d5;\n      background: #f1f1f2;\n    }\n\n    .mljar-number-step-btn {\n      display: inline-flex;\n      align-items: center;\n      justify-content: center;\n      width: 38px;\n      min-width: 38px;\n      min-height: 100%;\n      border: 0;\n      border-radius: 0;\n      background: transparent;\n      color: #0f172a;\n      font: inherit;\n      font-size: 18px;\n      font-weight: 700;\n      line-height: 1;\n      cursor: pointer;\n      padding: 0;\n      user-select: none;\n      -webkit-user-select: none;\n      touch-action: manipulation;\n      transition: background-color 0.14s ease, color 0.14s ease;\n    }\n\n    .mljar-number-step-up {\n      border-left: 1px solid #cfd1d5;\n    }\n\n    .mljar-number-step-btn:hover {\n      background: #f3f3f4;\n    }\n\n    .mljar-number-step-btn:active {\n      background: #e6f2ff;\n      color: #007bff;\n    }\n\n    .mljar-number-step-btn:focus-visible {\n      outline: none;\n      background: #e6f2ff;\n      color: #007bff;\n    }\n\n    .mljar-number-step-btn:disabled {\n      background: #f5f5f5;\n      color: #aaa;\n      cursor: not-allowed;\n    }\n\n    @media (max-width: 768px) {\n      .mljar-number-field-row {\n        min-height: 44px;\n      }\n\n      .mljar-number-input {\n        min-height: 44px;\n        padding: 8px 12px;\n      }\n\n      .mljar-number-step-btn {\n        font-size: 19px;\n        width: 44px;\n        min-width: 44px;\n      }\n    }\n    ",
       "_dom_classes": [],
       "_esm": "\n    function render({ model, el }) {\n      const container = document.createElement(\"div\");\n      container.classList.add(\"mljar-number-container\");\n\n      const topLabel = document.createElement(\"div\");\n      topLabel.classList.add(\"mljar-number-label\");\n\n      const fieldRow = document.createElement(\"div\");\n      fieldRow.classList.add(\"mljar-number-field-row\");\n\n      const input = document.createElement(\"input\");\n      input.type = \"number\";\n      input.classList.add(\"mljar-number-input\");\n\n      const decrementBtn = document.createElement(\"button\");\n      decrementBtn.type = \"button\";\n      decrementBtn.classList.add(\"mljar-number-step-btn\", \"mljar-number-step-down\");\n      decrementBtn.textContent = \"-\";\n      decrementBtn.setAttribute(\"aria-label\", \"Decrease value\");\n\n      const incrementBtn = document.createElement(\"button\");\n      incrementBtn.type = \"button\";\n      incrementBtn.classList.add(\"mljar-number-step-btn\", \"mljar-number-step-up\");\n      incrementBtn.textContent = \"+\";\n      incrementBtn.setAttribute(\"aria-label\", \"Increase value\");\n\n      const controls = document.createElement(\"div\");\n      controls.classList.add(\"mljar-number-controls\");\n\n      controls.appendChild(decrementBtn);\n      controls.appendChild(incrementBtn);\n      fieldRow.appendChild(input);\n      fieldRow.appendChild(controls);\n\n      container.appendChild(topLabel);\n      container.appendChild(fieldRow);\n      el.appendChild(container);\n\n      function clamp(val, min, max) {\n        if (Number.isFinite(min) && val < min) return min;\n        if (Number.isFinite(max) && val > max) return max;\n        return val;\n      }\n\n      function normalizeStep(step) {\n        return Number.isFinite(step) && step > 0 ? step : 1;\n      }\n\n      function snapToStep(value, min, step) {\n        const safeStep = normalizeStep(step);\n        const base = Number.isFinite(min) ? min : 0;\n        const steps = Math.round((value - base) / safeStep);\n        const snapped = base + steps * safeStep;\n        const precision = Math.max(\n          0,\n          (String(safeStep).split(\".\")[1] || \"\").length\n        );\n\n        return Number(snapped.toFixed(precision + 2));\n      }\n\n      function isOnStepGrid(value, min, step) {\n        const safeStep = normalizeStep(step);\n        const base = Number.isFinite(min) ? min : 0;\n        const steps = (value - base) / safeStep;\n        const nearest = Math.round(steps);\n        const epsilon = Math.max(1e-9, safeStep * 1e-9);\n\n        return Math.abs(steps - nearest) <= epsilon;\n      }\n\n      function getCurrentBounds() {\n        return {\n          min: Number(model.get(\"min\")),\n          max: Number(model.get(\"max\")),\n        };\n      }\n\n      function isTransientDraft(raw) {\n        return raw === \"\" || raw === \"-\" || raw === \".\" || raw === \"-.\";\n      }\n\n      let isEditing = false;\n      const INPUT_COMMIT_DEBOUNCE_MS = 400;\n\n      function clearPendingDraftCommit() {\n        if (debounceTimer) clearTimeout(debounceTimer);\n        pendingDraftValue = null;\n      }\n\n      function parseDraftValue(rawValue) {\n        const raw = String(rawValue).trim();\n        if (isTransientDraft(raw)) {\n          return { kind: \"transient\" };\n        }\n\n        const value = Number(raw);\n        if (!Number.isFinite(value)) {\n          return { kind: \"invalid\" };\n        }\n\n        return { kind: \"number\", value };\n      }\n\n      function commitValue(nextValue, saveNow = true) {\n        const { min, max } = getCurrentBounds();\n        const step = Number(model.get(\"step\"));\n        const parsed = parseDraftValue(nextValue);\n        if (parsed.kind !== \"number\") {\n          syncFromModel();\n          return;\n        }\n\n        let v = parsed.value;\n        v = clamp(v, min, max);\n        v = snapToStep(v, min, step);\n        v = clamp(v, min, max);\n        input.value = String(v);\n        model.set(\"value\", v);\n\n        if (saveNow) {\n          model.save_changes();\n        }\n      }\n\n      function syncFromModel() {\n        topLabel.innerHTML = model.get(\"label\") || \"Enter number\";\n\n        const min = Number(model.get(\"min\"));\n        const max = Number(model.get(\"max\"));\n        const step = Number(model.get(\"step\"));\n\n        if (Number.isFinite(min)) input.min = String(min); else input.removeAttribute(\"min\");\n        if (Number.isFinite(max)) input.max = String(max); else input.removeAttribute(\"max\");\n        if (Number.isFinite(step)) input.step = String(step); else input.removeAttribute(\"step\");\n\n        const v = Number(model.get(\"value\"));\n        if (!isEditing) {\n          input.value = Number.isFinite(v) ? String(v) : \"\";\n        }\n\n        const disabled = !!model.get(\"disabled\");\n        input.disabled = disabled;\n        incrementBtn.disabled = disabled;\n        decrementBtn.disabled = disabled;\n\n        const hidden = !!model.get(\"hidden\");\n        container.style.display = hidden ? \"none\" : \"flex\";\n      }\n\n      let debounceTimer = null;\n      let pendingDraftValue = null;\n      input.addEventListener(\"focus\", () => {\n        isEditing = true;\n      });\n\n      input.addEventListener(\"input\", () => {\n        if (model.get(\"disabled\")) return;\n\n        const parsed = parseDraftValue(input.value);\n        if (parsed.kind !== \"number\") {\n          clearPendingDraftCommit();\n          return;\n        }\n\n        const v = parsed.value;\n        const { min, max } = getCurrentBounds();\n        const step = Number(model.get(\"step\"));\n        if (Number.isFinite(min) && v < min) {\n          clearPendingDraftCommit();\n          return;\n        }\n        if (Number.isFinite(max) && v > max) {\n          clearPendingDraftCommit();\n          return;\n        }\n        if (!isOnStepGrid(v, min, step)) {\n          clearPendingDraftCommit();\n          return;\n        }\n\n        pendingDraftValue = v;\n        if (debounceTimer) clearTimeout(debounceTimer);\n        debounceTimer = setTimeout(() => {\n          if (pendingDraftValue === null) return;\n          model.set(\"value\", pendingDraftValue);\n          model.save_changes();\n          pendingDraftValue = null;\n        }, INPUT_COMMIT_DEBOUNCE_MS);\n      });\n\n      input.addEventListener(\"blur\", () => {\n        isEditing = false;\n        clearPendingDraftCommit();\n        commitValue(input.value, true);\n      });\n\n      input.addEventListener(\"keydown\", event => {\n        if (event.key === \"Enter\") {\n          event.preventDefault();\n          input.blur();\n        }\n      });\n\n      incrementBtn.addEventListener(\"click\", () => {\n        if (model.get(\"disabled\")) return;\n        const current = Number(model.get(\"value\"));\n        const step = normalizeStep(Number(model.get(\"step\")));\n        const min = Number(model.get(\"min\"));\n        const base = Number.isFinite(current) ? current : 0;\n        commitValue(snapToStep(base + step, min, step));\n      });\n\n      decrementBtn.addEventListener(\"click\", () => {\n        if (model.get(\"disabled\")) return;\n        const current = Number(model.get(\"value\"));\n        const step = normalizeStep(Number(model.get(\"step\")));\n        const min = Number(model.get(\"min\"));\n        const base = Number.isFinite(current) ? current : 0;\n        commitValue(snapToStep(base - step, min, step));\n      });\n\n      model.on(\"change:value\", syncFromModel);\n      model.on(\"change:min\", syncFromModel);\n      model.on(\"change:max\", syncFromModel);\n      model.on(\"change:step\", syncFromModel);\n      model.on(\"change:label\", syncFromModel);\n      model.on(\"change:disabled\", syncFromModel);\n      model.on(\"change:hidden\", syncFromModel);\n\n      syncFromModel();\n\n      // ---- read cell id (no DOM modifications) ----\n      /*\n      const ID_ATTR = \"data-cell-id\";\n      const hostWithId = el.closest(`[${ID_ATTR}]`);\n      const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n      if (cellId) {\n        model.set(\"cell_id\", cellId);\n        model.save_changes();\n        model.send({ type: \"cell_id_detected\", value: cellId });\n      } else {\n        const mo = new MutationObserver(() => {\n          const host = el.closest(`[${ID_ATTR}]`);\n          const newId = host?.getAttribute(ID_ATTR);\n          if (newId) {\n            model.set(\"cell_id\", newId);\n            model.save_changes();\n            model.send({ type: \"cell_id_detected\", value: newId });\n            mo.disconnect();\n          }\n        });\n        mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n      }*/\n    }\n    export default { render };\n    ",
       "_model_module": "anywidget",
       "_model_module_version": "~0.11.*",
       "_model_name": "AnyModel",
       "_view_count": null,
       "_view_module": "anywidget",
       "_view_module_version": "~0.11.*",
       "_view_name": "AnyView",
       "cell_id": "",
       "disabled": false,
       "hidden": false,
       "label": "EMEA — current platform cost ($/yr)",
       "layout": "IPY_MODEL_cf077822220240e1b1f2a9b78a5b314e",
       "layout_path": null,
       "max": 8000000.0,
       "min": 0.0,
       "position": "inline",
       "render_slot_id": null,
       "source_cell_id": null,
       "step": 10000.0,
       "tabbable": null,
       "tooltip": null,
       "url_key": "",
       "value": 1204124.0
      }
     },
     "97775efd267643e2a6cacf19f74d8881": {
      "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
      }
     },
     "9b301ed2ca294f05902a6855e5e41920": {
      "model_module": "anywidget",
      "model_module_version": "~0.11.*",
      "model_name": "AnyModel",
      "state": {
       "_anywidget_id": "mercury.date.DateInputWidget",
       "_css": "\n    .mljar-date-container {\n      display: flex;\n      flex-direction: column;\n      width: 100%;\n      font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n      font-size: 14px;\n      color: #0f172a;\n      margin-bottom: 8px;\n      padding-left: 4px;\n      padding-right: 4px;\n      box-sizing: border-box;\n    }\n\n    .mljar-date-label {\n      margin-bottom: 4px;\n      font-weight: 600;\n    }\n\n    .mljar-date-input {\n      width: 100%;\n      min-height: 40px;\n      padding: 9px 10px;\n      border: 1px solid #cfd1d5;\n      border-radius: 6px;\n      background: #ffffff;\n      color: #0f172a;\n      box-sizing: border-box;\n      line-height: 1.4;\n    }\n\n    .mljar-date-input:disabled {\n      background: #f5f5f5;\n      color: #888;\n      cursor: not-allowed;\n    }\n\n    .mljar-date-input:focus {\n      outline: none;\n      border-color: #007bff;\n      border-width: 2px;\n      box-shadow: none;\n    }\n\n    @media (max-width: 768px) {\n      .mljar-date-input {\n        min-height: 44px;\n        padding: 10px 12px;\n      }\n    }\n    ",
       "_dom_classes": [],
       "_esm": "\n    function render({ model, el }) {\n      const container = document.createElement(\"div\");\n      container.classList.add(\"mljar-date-container\");\n\n      const topLabel = document.createElement(\"div\");\n      topLabel.classList.add(\"mljar-date-label\");\n\n      const input = document.createElement(\"input\");\n      input.type = \"date\";\n      input.classList.add(\"mljar-date-input\");\n\n      container.appendChild(topLabel);\n      container.appendChild(input);\n      el.appendChild(container);\n\n      function syncFromModel() {\n        topLabel.innerHTML = model.get(\"label\") || \"Date\";\n        input.value = model.get(\"value\") || \"\";\n\n        const min = model.get(\"min\") || \"\";\n        const max = model.get(\"max\") || \"\";\n        if (min) input.min = min; else input.removeAttribute(\"min\");\n        if (max) input.max = max; else input.removeAttribute(\"max\");\n\n        input.disabled = !!model.get(\"disabled\");\n        container.style.display = model.get(\"hidden\") ? \"none\" : \"flex\";\n      }\n\n      input.addEventListener(\"change\", () => {\n        if (model.get(\"disabled\")) return;\n        model.set(\"value\", input.value);\n        model.save_changes();\n      });\n\n      model.on(\"change:value\", syncFromModel);\n      model.on(\"change:min\", syncFromModel);\n      model.on(\"change:max\", syncFromModel);\n      model.on(\"change:label\", syncFromModel);\n      model.on(\"change:disabled\", syncFromModel);\n      model.on(\"change:hidden\", syncFromModel);\n\n      syncFromModel();\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": "ANZ — contract termination",
       "layout": "IPY_MODEL_b81ddaa7f50f48f3b7d51ca3c677cd6d",
       "layout_path": null,
       "max": "",
       "min": "",
       "position": "inline",
       "render_slot_id": null,
       "source_cell_id": null,
       "tabbable": null,
       "tooltip": null,
       "url_key": "",
       "value": "2027-12-31"
      }
     },
     "a9f8ba71821a4c59be71d4f6f20d5cb1": {
      "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
      }
     },
     "b4137f1980b14440abee145f02a3f3ec": {
      "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
      }
     },
     "b81ddaa7f50f48f3b7d51ca3c677cd6d": {
      "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
      }
     },
     "bcd839aaf93d4435a34035babc729daf": {
      "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
      }
     },
     "c0c824e7a641458cadcc23a9d93b5d15": {
      "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
      }
     },
     "cf077822220240e1b1f2a9b78a5b314e": {
      "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
      }
     },
     "de213f8c9e7242dca519f335951528dd": {
      "model_module": "anywidget",
      "model_module_version": "~0.11.*",
      "model_name": "AnyModel",
      "state": {
       "_anywidget_id": "mercury.number.NumberInputWidget",
       "_css": "\n    .mljar-number-container {\n      display: flex;\n      flex-direction: column;\n      width: 100%;\n      font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n      font-size: 14px;\n      color: #0f172a;\n      margin-bottom: 8px;\n      padding-left: 4px;\n      padding-right: 4px;\n      box-sizing: border-box;\n    }\n\n    .mljar-number-label {\n      margin-bottom: 4px;\n      font-weight: 600;\n    }\n\n    .mljar-number-field-row {\n      display: flex;\n      align-items: stretch;\n      width: 100%;\n      min-height: 40px;\n      border: 1px solid #cfd1d5;\n      border-radius: 6px;\n      background: #ffffff;\n      box-sizing: border-box;\n      overflow: hidden;\n    }\n\n    .mljar-number-input {\n      flex: 1 1 auto;\n      min-width: 0;\n      min-height: 100%;\n      padding: 7px 10px;\n      border: 0;\n      border-radius: 0;\n      background: #ffffff;\n      box-sizing: border-box;\n      background-color: #ffffff !important;\n      color: #0f172a !important;\n      font: inherit;\n      line-height: 1.2;\n      -moz-appearance: textfield;\n    }\n\n    .mljar-number-input::-webkit-outer-spin-button,\n    .mljar-number-input::-webkit-inner-spin-button {\n      -webkit-appearance: none;\n      margin: 0;\n    }\n\n    .mljar-number-input:disabled {\n      background: #f5f5f5;\n      color: #888;\n      cursor: not-allowed;\n    }\n\n    .mljar-number-input:focus {\n      outline: none;\n    }\n\n    .mljar-number-field-row:focus-within {\n      border-color: #007bff;\n      border-width: 2px;\n      box-shadow: none;\n    }\n\n    .mljar-number-field-row:focus-within .mljar-number-controls {\n      border-left-color: #007bff;\n    }\n\n    .mljar-number-controls {\n      display: flex;\n      align-items: stretch;\n      flex: 0 0 auto;\n      border-left: 1px solid #cfd1d5;\n      background: #f1f1f2;\n    }\n\n    .mljar-number-step-btn {\n      display: inline-flex;\n      align-items: center;\n      justify-content: center;\n      width: 38px;\n      min-width: 38px;\n      min-height: 100%;\n      border: 0;\n      border-radius: 0;\n      background: transparent;\n      color: #0f172a;\n      font: inherit;\n      font-size: 18px;\n      font-weight: 700;\n      line-height: 1;\n      cursor: pointer;\n      padding: 0;\n      user-select: none;\n      -webkit-user-select: none;\n      touch-action: manipulation;\n      transition: background-color 0.14s ease, color 0.14s ease;\n    }\n\n    .mljar-number-step-up {\n      border-left: 1px solid #cfd1d5;\n    }\n\n    .mljar-number-step-btn:hover {\n      background: #f3f3f4;\n    }\n\n    .mljar-number-step-btn:active {\n      background: #e6f2ff;\n      color: #007bff;\n    }\n\n    .mljar-number-step-btn:focus-visible {\n      outline: none;\n      background: #e6f2ff;\n      color: #007bff;\n    }\n\n    .mljar-number-step-btn:disabled {\n      background: #f5f5f5;\n      color: #aaa;\n      cursor: not-allowed;\n    }\n\n    @media (max-width: 768px) {\n      .mljar-number-field-row {\n        min-height: 44px;\n      }\n\n      .mljar-number-input {\n        min-height: 44px;\n        padding: 8px 12px;\n      }\n\n      .mljar-number-step-btn {\n        font-size: 19px;\n        width: 44px;\n        min-width: 44px;\n      }\n    }\n    ",
       "_dom_classes": [],
       "_esm": "\n    function render({ model, el }) {\n      const container = document.createElement(\"div\");\n      container.classList.add(\"mljar-number-container\");\n\n      const topLabel = document.createElement(\"div\");\n      topLabel.classList.add(\"mljar-number-label\");\n\n      const fieldRow = document.createElement(\"div\");\n      fieldRow.classList.add(\"mljar-number-field-row\");\n\n      const input = document.createElement(\"input\");\n      input.type = \"number\";\n      input.classList.add(\"mljar-number-input\");\n\n      const decrementBtn = document.createElement(\"button\");\n      decrementBtn.type = \"button\";\n      decrementBtn.classList.add(\"mljar-number-step-btn\", \"mljar-number-step-down\");\n      decrementBtn.textContent = \"-\";\n      decrementBtn.setAttribute(\"aria-label\", \"Decrease value\");\n\n      const incrementBtn = document.createElement(\"button\");\n      incrementBtn.type = \"button\";\n      incrementBtn.classList.add(\"mljar-number-step-btn\", \"mljar-number-step-up\");\n      incrementBtn.textContent = \"+\";\n      incrementBtn.setAttribute(\"aria-label\", \"Increase value\");\n\n      const controls = document.createElement(\"div\");\n      controls.classList.add(\"mljar-number-controls\");\n\n      controls.appendChild(decrementBtn);\n      controls.appendChild(incrementBtn);\n      fieldRow.appendChild(input);\n      fieldRow.appendChild(controls);\n\n      container.appendChild(topLabel);\n      container.appendChild(fieldRow);\n      el.appendChild(container);\n\n      function clamp(val, min, max) {\n        if (Number.isFinite(min) && val < min) return min;\n        if (Number.isFinite(max) && val > max) return max;\n        return val;\n      }\n\n      function normalizeStep(step) {\n        return Number.isFinite(step) && step > 0 ? step : 1;\n      }\n\n      function snapToStep(value, min, step) {\n        const safeStep = normalizeStep(step);\n        const base = Number.isFinite(min) ? min : 0;\n        const steps = Math.round((value - base) / safeStep);\n        const snapped = base + steps * safeStep;\n        const precision = Math.max(\n          0,\n          (String(safeStep).split(\".\")[1] || \"\").length\n        );\n\n        return Number(snapped.toFixed(precision + 2));\n      }\n\n      function isOnStepGrid(value, min, step) {\n        const safeStep = normalizeStep(step);\n        const base = Number.isFinite(min) ? min : 0;\n        const steps = (value - base) / safeStep;\n        const nearest = Math.round(steps);\n        const epsilon = Math.max(1e-9, safeStep * 1e-9);\n\n        return Math.abs(steps - nearest) <= epsilon;\n      }\n\n      function getCurrentBounds() {\n        return {\n          min: Number(model.get(\"min\")),\n          max: Number(model.get(\"max\")),\n        };\n      }\n\n      function isTransientDraft(raw) {\n        return raw === \"\" || raw === \"-\" || raw === \".\" || raw === \"-.\";\n      }\n\n      let isEditing = false;\n      const INPUT_COMMIT_DEBOUNCE_MS = 400;\n\n      function clearPendingDraftCommit() {\n        if (debounceTimer) clearTimeout(debounceTimer);\n        pendingDraftValue = null;\n      }\n\n      function parseDraftValue(rawValue) {\n        const raw = String(rawValue).trim();\n        if (isTransientDraft(raw)) {\n          return { kind: \"transient\" };\n        }\n\n        const value = Number(raw);\n        if (!Number.isFinite(value)) {\n          return { kind: \"invalid\" };\n        }\n\n        return { kind: \"number\", value };\n      }\n\n      function commitValue(nextValue, saveNow = true) {\n        const { min, max } = getCurrentBounds();\n        const step = Number(model.get(\"step\"));\n        const parsed = parseDraftValue(nextValue);\n        if (parsed.kind !== \"number\") {\n          syncFromModel();\n          return;\n        }\n\n        let v = parsed.value;\n        v = clamp(v, min, max);\n        v = snapToStep(v, min, step);\n        v = clamp(v, min, max);\n        input.value = String(v);\n        model.set(\"value\", v);\n\n        if (saveNow) {\n          model.save_changes();\n        }\n      }\n\n      function syncFromModel() {\n        topLabel.innerHTML = model.get(\"label\") || \"Enter number\";\n\n        const min = Number(model.get(\"min\"));\n        const max = Number(model.get(\"max\"));\n        const step = Number(model.get(\"step\"));\n\n        if (Number.isFinite(min)) input.min = String(min); else input.removeAttribute(\"min\");\n        if (Number.isFinite(max)) input.max = String(max); else input.removeAttribute(\"max\");\n        if (Number.isFinite(step)) input.step = String(step); else input.removeAttribute(\"step\");\n\n        const v = Number(model.get(\"value\"));\n        if (!isEditing) {\n          input.value = Number.isFinite(v) ? String(v) : \"\";\n        }\n\n        const disabled = !!model.get(\"disabled\");\n        input.disabled = disabled;\n        incrementBtn.disabled = disabled;\n        decrementBtn.disabled = disabled;\n\n        const hidden = !!model.get(\"hidden\");\n        container.style.display = hidden ? \"none\" : \"flex\";\n      }\n\n      let debounceTimer = null;\n      let pendingDraftValue = null;\n      input.addEventListener(\"focus\", () => {\n        isEditing = true;\n      });\n\n      input.addEventListener(\"input\", () => {\n        if (model.get(\"disabled\")) return;\n\n        const parsed = parseDraftValue(input.value);\n        if (parsed.kind !== \"number\") {\n          clearPendingDraftCommit();\n          return;\n        }\n\n        const v = parsed.value;\n        const { min, max } = getCurrentBounds();\n        const step = Number(model.get(\"step\"));\n        if (Number.isFinite(min) && v < min) {\n          clearPendingDraftCommit();\n          return;\n        }\n        if (Number.isFinite(max) && v > max) {\n          clearPendingDraftCommit();\n          return;\n        }\n        if (!isOnStepGrid(v, min, step)) {\n          clearPendingDraftCommit();\n          return;\n        }\n\n        pendingDraftValue = v;\n        if (debounceTimer) clearTimeout(debounceTimer);\n        debounceTimer = setTimeout(() => {\n          if (pendingDraftValue === null) return;\n          model.set(\"value\", pendingDraftValue);\n          model.save_changes();\n          pendingDraftValue = null;\n        }, INPUT_COMMIT_DEBOUNCE_MS);\n      });\n\n      input.addEventListener(\"blur\", () => {\n        isEditing = false;\n        clearPendingDraftCommit();\n        commitValue(input.value, true);\n      });\n\n      input.addEventListener(\"keydown\", event => {\n        if (event.key === \"Enter\") {\n          event.preventDefault();\n          input.blur();\n        }\n      });\n\n      incrementBtn.addEventListener(\"click\", () => {\n        if (model.get(\"disabled\")) return;\n        const current = Number(model.get(\"value\"));\n        const step = normalizeStep(Number(model.get(\"step\")));\n        const min = Number(model.get(\"min\"));\n        const base = Number.isFinite(current) ? current : 0;\n        commitValue(snapToStep(base + step, min, step));\n      });\n\n      decrementBtn.addEventListener(\"click\", () => {\n        if (model.get(\"disabled\")) return;\n        const current = Number(model.get(\"value\"));\n        const step = normalizeStep(Number(model.get(\"step\")));\n        const min = Number(model.get(\"min\"));\n        const base = Number.isFinite(current) ? current : 0;\n        commitValue(snapToStep(base - step, min, step));\n      });\n\n      model.on(\"change:value\", syncFromModel);\n      model.on(\"change:min\", syncFromModel);\n      model.on(\"change:max\", syncFromModel);\n      model.on(\"change:step\", syncFromModel);\n      model.on(\"change:label\", syncFromModel);\n      model.on(\"change:disabled\", syncFromModel);\n      model.on(\"change:hidden\", syncFromModel);\n\n      syncFromModel();\n\n      // ---- read cell id (no DOM modifications) ----\n      /*\n      const ID_ATTR = \"data-cell-id\";\n      const hostWithId = el.closest(`[${ID_ATTR}]`);\n      const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n      if (cellId) {\n        model.set(\"cell_id\", cellId);\n        model.save_changes();\n        model.send({ type: \"cell_id_detected\", value: cellId });\n      } else {\n        const mo = new MutationObserver(() => {\n          const host = el.closest(`[${ID_ATTR}]`);\n          const newId = host?.getAttribute(ID_ATTR);\n          if (newId) {\n            model.set(\"cell_id\", newId);\n            model.save_changes();\n            model.send({ type: \"cell_id_detected\", value: newId });\n            mo.disconnect();\n          }\n        });\n        mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n      }*/\n    }\n    export default { render };\n    ",
       "_model_module": "anywidget",
       "_model_module_version": "~0.11.*",
       "_model_name": "AnyModel",
       "_view_count": null,
       "_view_module": "anywidget",
       "_view_module_version": "~0.11.*",
       "_view_name": "AnyView",
       "cell_id": "",
       "disabled": false,
       "hidden": false,
       "label": "Genesys ramp — licence-free months",
       "layout": "IPY_MODEL_ec9035f9afa4494ab4e48f2284a23d4c",
       "layout_path": null,
       "max": 24.0,
       "min": 0.0,
       "position": "inline",
       "render_slot_id": null,
       "source_cell_id": null,
       "step": 1.0,
       "tabbable": null,
       "tooltip": null,
       "url_key": "",
       "value": 12.0
      }
     },
     "e502bd1367354f3b8cd8e6889a4f4d50": {
      "model_module": "anywidget",
      "model_module_version": "~0.11.*",
      "model_name": "AnyModel",
      "state": {
       "_anywidget_id": "mercury.checkbox.CheckboxWidget",
       "_css": "\n    .mljar-checkbox-container {\n        display: inline-flex;\n        align-items: center;\n        gap: 10px;\n        cursor: pointer;\n        user-select: none;\n        -webkit-tap-highlight-color: transparent;\n        font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n        color: #0f172a;\n        padding-left: 5px;\n    }\n    .mljar-checkbox-container.is-disabled {\n        opacity: 0.6;\n        cursor: not-allowed;\n    }\n\n    .mljar-checkbox-input {\n        position: absolute;\n        opacity: 0;\n        width: 0;\n        height: 0;\n    }\n\n    .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n        box-shadow: inset 0 0 0 2px #007bff;\n    }\n\n    .mljar-checkbox-label {\n        font-size: 14px;\n        line-height: 1.2;\n        padding-top: 2px;\n    }\n\n    /* --- Toggle style (15% smaller) --- */\n    .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n        position: relative;\n        width: 34px;\n        height: 19px;\n        border-radius: 999px;\n        background: #f1f1f2;\n        border: 1px solid #cfd1d5;\n        transition: background 150ms ease, border-color 150ms ease;\n    }\n    .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n        background: #007bff;\n        border-color: #007bff;\n    }\n    .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n        content: \"\";\n        position: absolute;\n        top: 2px;\n        left: 2px;\n        width: 15px;\n        height: 15px;\n        border-radius: 50%;\n        background: #ffffff;\n        box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n        transition: transform 150ms ease;\n    }\n    .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n        transform: translateX(15px);\n    }\n\n    /* --- Classic box style --- */\n    .mljar-checkbox-container.is-box .mljar-checkbox-control {\n        width: 16px;\n        height: 16px;\n        border-radius: 4px;\n        border: 1px solid #cfd1d5;\n        background: #ffffff;\n        display: inline-block;\n        position: relative;\n    }\n    .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n        border-color: #007bff;\n        background: #007bff;\n    }\n    .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n        content: \"\";\n        position: absolute;\n        left: 4px;\n        top: 0px;\n        width: 5px;\n        height: 10px;\n        border: solid #fff;\n        border-width: 0 2px 2px 0;\n        transform: rotate(45deg);\n    }\n\n    .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n        border-color: #007bff;\n        background: #f3f3f4;\n    }\n    ",
       "_dom_classes": [],
       "_esm": "\n    function render({ model, el }) {\n      const container = document.createElement(\"label\");\n      container.classList.add(\"mljar-checkbox-container\");\n\n      const input = document.createElement(\"input\");\n      input.type = \"checkbox\";\n      input.classList.add(\"mljar-checkbox-input\");\n\n      const control = document.createElement(\"span\");\n      control.classList.add(\"mljar-checkbox-control\");\n\n      const text = document.createElement(\"span\");\n      text.classList.add(\"mljar-checkbox-label\");\n\n      container.appendChild(input);\n      container.appendChild(control);\n      container.appendChild(text);\n      el.appendChild(container);\n\n      function syncFromModel() {\n        // appearance\n        const a = model.get(\"appearance\") || \"toggle\";\n        container.classList.remove(\"is-toggle\",\"is-box\");\n        container.classList.add(`is-${a}`);\n        input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n        // value\n        const v = !!model.get(\"value\");\n        if (input.checked !== v) {\n          input.checked = v;\n          input.setAttribute(\"aria-checked\", String(v));\n        }\n        container.classList.toggle(\"is-checked\", v);\n\n        // disabled\n        const d = !!model.get(\"disabled\");\n        input.disabled = d;\n        container.classList.toggle(\"is-disabled\", d);\n\n        // label\n        text.textContent = model.get(\"label\") || \"\";\n\n        // hidden (exists but not visible)\n        container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n      }\n\n      input.addEventListener(\"change\", () => {\n        if (model.get(\"disabled\")) return;\n\n        const v = input.checked;\n        if (model.get(\"value\") !== v) {\n          model.set(\"value\", v);\n          model.set(\"last_changed_at\", new Date().toISOString());\n          model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n          model.save_changes();\n          // model.send({ type: \"changed\", value: v });\n        }\n      });\n\n      model.on(\"change:value\", syncFromModel);\n      model.on(\"change:disabled\", syncFromModel);\n      model.on(\"change:appearance\", syncFromModel);\n      model.on(\"change:label\", syncFromModel);\n      model.on(\"change:hidden\", syncFromModel);\n\n      syncFromModel();\n\n      // ---- read cell id (no DOM modifications) ----\n      /*\n      const ID_ATTR = \"data-cell-id\";\n      const hostWithId = el.closest(`[${ID_ATTR}]`);\n      const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n      if (cellId) {\n        model.set(\"cell_id\", cellId);\n        model.save_changes();\n        model.send({ type: \"cell_id_detected\", value: cellId });\n      } else {\n        const mo = new MutationObserver(() => {\n          const host = el.closest(`[${ID_ATTR}]`);\n          const newId = host?.getAttribute(ID_ATTR);\n          \n          if (newId) {\n            model.set(\"cell_id\", newId);\n            model.save_changes();\n            model.send({ type: \"cell_id_detected\", value: newId });\n            mo.disconnect();\n          }\n        });\n        mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n      }\n      */\n    }\n    export default { render };\n    ",
       "_model_module": "anywidget",
       "_model_module_version": "~0.11.*",
       "_model_name": "AnyModel",
       "_view_count": null,
       "_view_module": "anywidget",
       "_view_module_version": "~0.11.*",
       "_view_name": "AnyView",
       "appearance": "toggle",
       "cell_id": "",
       "disabled": false,
       "hidden": false,
       "label": "WFM — ANZ (implement)",
       "last_changed_at": "",
       "layout": "IPY_MODEL_7742525da3ea413e97d2cfc40e6b73d4",
       "layout_path": null,
       "n_toggles": 0,
       "position": "sidebar",
       "render_slot_id": null,
       "source_cell_id": null,
       "tabbable": null,
       "tooltip": null,
       "url_key": "",
       "value": true
      }
     },
     "e6f9520844b84d66a15de199f5ecf700": {
      "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_60230c9272ad41139f7b8cdc16ad0d87",
       "layout_path": null,
       "placeholder": "​",
       "position": "sidebar",
       "render_slot_id": null,
       "source_cell_id": null,
       "style": "IPY_MODEL_36ab088e1ab642748d8c30c5a34559ad",
       "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'});\">Current state &amp; contracts</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'});\">Scenario scope &amp; licences</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'});\">WFM 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'});\">Cost stack</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'});\">Sensitivity</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'});\">Risks &amp; notes</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-9');if(!el){document.querySelectorAll('h1,h2').forEach(function(h){if(!el&&h.textContent.trim().indexOf('9 ')===0){el=h;}});}if(el)el.scrollIntoView({behavior:'smooth',block:'start'});\">Data appendix</a></li></ol></p></div>"
      }
     },
     "e99ec85877354dd5973cb641103db205": {
      "model_module": "anywidget",
      "model_module_version": "~0.11.*",
      "model_name": "AnyModel",
      "state": {
       "_anywidget_id": "mercury.number.NumberInputWidget",
       "_css": "\n    .mljar-number-container {\n      display: flex;\n      flex-direction: column;\n      width: 100%;\n      font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n      font-size: 14px;\n      color: #0f172a;\n      margin-bottom: 8px;\n      padding-left: 4px;\n      padding-right: 4px;\n      box-sizing: border-box;\n    }\n\n    .mljar-number-label {\n      margin-bottom: 4px;\n      font-weight: 600;\n    }\n\n    .mljar-number-field-row {\n      display: flex;\n      align-items: stretch;\n      width: 100%;\n      min-height: 40px;\n      border: 1px solid #cfd1d5;\n      border-radius: 6px;\n      background: #ffffff;\n      box-sizing: border-box;\n      overflow: hidden;\n    }\n\n    .mljar-number-input {\n      flex: 1 1 auto;\n      min-width: 0;\n      min-height: 100%;\n      padding: 7px 10px;\n      border: 0;\n      border-radius: 0;\n      background: #ffffff;\n      box-sizing: border-box;\n      background-color: #ffffff !important;\n      color: #0f172a !important;\n      font: inherit;\n      line-height: 1.2;\n      -moz-appearance: textfield;\n    }\n\n    .mljar-number-input::-webkit-outer-spin-button,\n    .mljar-number-input::-webkit-inner-spin-button {\n      -webkit-appearance: none;\n      margin: 0;\n    }\n\n    .mljar-number-input:disabled {\n      background: #f5f5f5;\n      color: #888;\n      cursor: not-allowed;\n    }\n\n    .mljar-number-input:focus {\n      outline: none;\n    }\n\n    .mljar-number-field-row:focus-within {\n      border-color: #007bff;\n      border-width: 2px;\n      box-shadow: none;\n    }\n\n    .mljar-number-field-row:focus-within .mljar-number-controls {\n      border-left-color: #007bff;\n    }\n\n    .mljar-number-controls {\n      display: flex;\n      align-items: stretch;\n      flex: 0 0 auto;\n      border-left: 1px solid #cfd1d5;\n      background: #f1f1f2;\n    }\n\n    .mljar-number-step-btn {\n      display: inline-flex;\n      align-items: center;\n      justify-content: center;\n      width: 38px;\n      min-width: 38px;\n      min-height: 100%;\n      border: 0;\n      border-radius: 0;\n      background: transparent;\n      color: #0f172a;\n      font: inherit;\n      font-size: 18px;\n      font-weight: 700;\n      line-height: 1;\n      cursor: pointer;\n      padding: 0;\n      user-select: none;\n      -webkit-user-select: none;\n      touch-action: manipulation;\n      transition: background-color 0.14s ease, color 0.14s ease;\n    }\n\n    .mljar-number-step-up {\n      border-left: 1px solid #cfd1d5;\n    }\n\n    .mljar-number-step-btn:hover {\n      background: #f3f3f4;\n    }\n\n    .mljar-number-step-btn:active {\n      background: #e6f2ff;\n      color: #007bff;\n    }\n\n    .mljar-number-step-btn:focus-visible {\n      outline: none;\n      background: #e6f2ff;\n      color: #007bff;\n    }\n\n    .mljar-number-step-btn:disabled {\n      background: #f5f5f5;\n      color: #aaa;\n      cursor: not-allowed;\n    }\n\n    @media (max-width: 768px) {\n      .mljar-number-field-row {\n        min-height: 44px;\n      }\n\n      .mljar-number-input {\n        min-height: 44px;\n        padding: 8px 12px;\n      }\n\n      .mljar-number-step-btn {\n        font-size: 19px;\n        width: 44px;\n        min-width: 44px;\n      }\n    }\n    ",
       "_dom_classes": [],
       "_esm": "\n    function render({ model, el }) {\n      const container = document.createElement(\"div\");\n      container.classList.add(\"mljar-number-container\");\n\n      const topLabel = document.createElement(\"div\");\n      topLabel.classList.add(\"mljar-number-label\");\n\n      const fieldRow = document.createElement(\"div\");\n      fieldRow.classList.add(\"mljar-number-field-row\");\n\n      const input = document.createElement(\"input\");\n      input.type = \"number\";\n      input.classList.add(\"mljar-number-input\");\n\n      const decrementBtn = document.createElement(\"button\");\n      decrementBtn.type = \"button\";\n      decrementBtn.classList.add(\"mljar-number-step-btn\", \"mljar-number-step-down\");\n      decrementBtn.textContent = \"-\";\n      decrementBtn.setAttribute(\"aria-label\", \"Decrease value\");\n\n      const incrementBtn = document.createElement(\"button\");\n      incrementBtn.type = \"button\";\n      incrementBtn.classList.add(\"mljar-number-step-btn\", \"mljar-number-step-up\");\n      incrementBtn.textContent = \"+\";\n      incrementBtn.setAttribute(\"aria-label\", \"Increase value\");\n\n      const controls = document.createElement(\"div\");\n      controls.classList.add(\"mljar-number-controls\");\n\n      controls.appendChild(decrementBtn);\n      controls.appendChild(incrementBtn);\n      fieldRow.appendChild(input);\n      fieldRow.appendChild(controls);\n\n      container.appendChild(topLabel);\n      container.appendChild(fieldRow);\n      el.appendChild(container);\n\n      function clamp(val, min, max) {\n        if (Number.isFinite(min) && val < min) return min;\n        if (Number.isFinite(max) && val > max) return max;\n        return val;\n      }\n\n      function normalizeStep(step) {\n        return Number.isFinite(step) && step > 0 ? step : 1;\n      }\n\n      function snapToStep(value, min, step) {\n        const safeStep = normalizeStep(step);\n        const base = Number.isFinite(min) ? min : 0;\n        const steps = Math.round((value - base) / safeStep);\n        const snapped = base + steps * safeStep;\n        const precision = Math.max(\n          0,\n          (String(safeStep).split(\".\")[1] || \"\").length\n        );\n\n        return Number(snapped.toFixed(precision + 2));\n      }\n\n      function isOnStepGrid(value, min, step) {\n        const safeStep = normalizeStep(step);\n        const base = Number.isFinite(min) ? min : 0;\n        const steps = (value - base) / safeStep;\n        const nearest = Math.round(steps);\n        const epsilon = Math.max(1e-9, safeStep * 1e-9);\n\n        return Math.abs(steps - nearest) <= epsilon;\n      }\n\n      function getCurrentBounds() {\n        return {\n          min: Number(model.get(\"min\")),\n          max: Number(model.get(\"max\")),\n        };\n      }\n\n      function isTransientDraft(raw) {\n        return raw === \"\" || raw === \"-\" || raw === \".\" || raw === \"-.\";\n      }\n\n      let isEditing = false;\n      const INPUT_COMMIT_DEBOUNCE_MS = 400;\n\n      function clearPendingDraftCommit() {\n        if (debounceTimer) clearTimeout(debounceTimer);\n        pendingDraftValue = null;\n      }\n\n      function parseDraftValue(rawValue) {\n        const raw = String(rawValue).trim();\n        if (isTransientDraft(raw)) {\n          return { kind: \"transient\" };\n        }\n\n        const value = Number(raw);\n        if (!Number.isFinite(value)) {\n          return { kind: \"invalid\" };\n        }\n\n        return { kind: \"number\", value };\n      }\n\n      function commitValue(nextValue, saveNow = true) {\n        const { min, max } = getCurrentBounds();\n        const step = Number(model.get(\"step\"));\n        const parsed = parseDraftValue(nextValue);\n        if (parsed.kind !== \"number\") {\n          syncFromModel();\n          return;\n        }\n\n        let v = parsed.value;\n        v = clamp(v, min, max);\n        v = snapToStep(v, min, step);\n        v = clamp(v, min, max);\n        input.value = String(v);\n        model.set(\"value\", v);\n\n        if (saveNow) {\n          model.save_changes();\n        }\n      }\n\n      function syncFromModel() {\n        topLabel.innerHTML = model.get(\"label\") || \"Enter number\";\n\n        const min = Number(model.get(\"min\"));\n        const max = Number(model.get(\"max\"));\n        const step = Number(model.get(\"step\"));\n\n        if (Number.isFinite(min)) input.min = String(min); else input.removeAttribute(\"min\");\n        if (Number.isFinite(max)) input.max = String(max); else input.removeAttribute(\"max\");\n        if (Number.isFinite(step)) input.step = String(step); else input.removeAttribute(\"step\");\n\n        const v = Number(model.get(\"value\"));\n        if (!isEditing) {\n          input.value = Number.isFinite(v) ? String(v) : \"\";\n        }\n\n        const disabled = !!model.get(\"disabled\");\n        input.disabled = disabled;\n        incrementBtn.disabled = disabled;\n        decrementBtn.disabled = disabled;\n\n        const hidden = !!model.get(\"hidden\");\n        container.style.display = hidden ? \"none\" : \"flex\";\n      }\n\n      let debounceTimer = null;\n      let pendingDraftValue = null;\n      input.addEventListener(\"focus\", () => {\n        isEditing = true;\n      });\n\n      input.addEventListener(\"input\", () => {\n        if (model.get(\"disabled\")) return;\n\n        const parsed = parseDraftValue(input.value);\n        if (parsed.kind !== \"number\") {\n          clearPendingDraftCommit();\n          return;\n        }\n\n        const v = parsed.value;\n        const { min, max } = getCurrentBounds();\n        const step = Number(model.get(\"step\"));\n        if (Number.isFinite(min) && v < min) {\n          clearPendingDraftCommit();\n          return;\n        }\n        if (Number.isFinite(max) && v > max) {\n          clearPendingDraftCommit();\n          return;\n        }\n        if (!isOnStepGrid(v, min, step)) {\n          clearPendingDraftCommit();\n          return;\n        }\n\n        pendingDraftValue = v;\n        if (debounceTimer) clearTimeout(debounceTimer);\n        debounceTimer = setTimeout(() => {\n          if (pendingDraftValue === null) return;\n          model.set(\"value\", pendingDraftValue);\n          model.save_changes();\n          pendingDraftValue = null;\n        }, INPUT_COMMIT_DEBOUNCE_MS);\n      });\n\n      input.addEventListener(\"blur\", () => {\n        isEditing = false;\n        clearPendingDraftCommit();\n        commitValue(input.value, true);\n      });\n\n      input.addEventListener(\"keydown\", event => {\n        if (event.key === \"Enter\") {\n          event.preventDefault();\n          input.blur();\n        }\n      });\n\n      incrementBtn.addEventListener(\"click\", () => {\n        if (model.get(\"disabled\")) return;\n        const current = Number(model.get(\"value\"));\n        const step = normalizeStep(Number(model.get(\"step\")));\n        const min = Number(model.get(\"min\"));\n        const base = Number.isFinite(current) ? current : 0;\n        commitValue(snapToStep(base + step, min, step));\n      });\n\n      decrementBtn.addEventListener(\"click\", () => {\n        if (model.get(\"disabled\")) return;\n        const current = Number(model.get(\"value\"));\n        const step = normalizeStep(Number(model.get(\"step\")));\n        const min = Number(model.get(\"min\"));\n        const base = Number.isFinite(current) ? current : 0;\n        commitValue(snapToStep(base - step, min, step));\n      });\n\n      model.on(\"change:value\", syncFromModel);\n      model.on(\"change:min\", syncFromModel);\n      model.on(\"change:max\", syncFromModel);\n      model.on(\"change:step\", syncFromModel);\n      model.on(\"change:label\", syncFromModel);\n      model.on(\"change:disabled\", syncFromModel);\n      model.on(\"change:hidden\", syncFromModel);\n\n      syncFromModel();\n\n      // ---- read cell id (no DOM modifications) ----\n      /*\n      const ID_ATTR = \"data-cell-id\";\n      const hostWithId = el.closest(`[${ID_ATTR}]`);\n      const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n      if (cellId) {\n        model.set(\"cell_id\", cellId);\n        model.save_changes();\n        model.send({ type: \"cell_id_detected\", value: cellId });\n      } else {\n        const mo = new MutationObserver(() => {\n          const host = el.closest(`[${ID_ATTR}]`);\n          const newId = host?.getAttribute(ID_ATTR);\n          if (newId) {\n            model.set(\"cell_id\", newId);\n            model.save_changes();\n            model.send({ type: \"cell_id_detected\", value: newId });\n            mo.disconnect();\n          }\n        });\n        mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n      }*/\n    }\n    export default { render };\n    ",
       "_model_module": "anywidget",
       "_model_module_version": "~0.11.*",
       "_model_name": "AnyModel",
       "_view_count": null,
       "_view_module": "anywidget",
       "_view_module_version": "~0.11.*",
       "_view_name": "AnyView",
       "cell_id": "",
       "disabled": false,
       "hidden": false,
       "label": "Genesys licence run-rate ($/yr)",
       "layout": "IPY_MODEL_ffebc83a901849f08ca62e76b4b899b8",
       "layout_path": null,
       "max": 10000000.0,
       "min": 0.0,
       "position": "sidebar",
       "render_slot_id": null,
       "source_cell_id": null,
       "step": 100000.0,
       "tabbable": null,
       "tooltip": null,
       "url_key": "",
       "value": 4300000.0
      }
     },
     "ec9035f9afa4494ab4e48f2284a23d4c": {
      "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
      }
     },
     "f01f7a6671d84072bfb17c43a9844009": {
      "model_module": "anywidget",
      "model_module_version": "~0.11.*",
      "model_name": "AnyModel",
      "state": {
       "_anywidget_id": "mercury.checkbox.CheckboxWidget",
       "_css": "\n    .mljar-checkbox-container {\n        display: inline-flex;\n        align-items: center;\n        gap: 10px;\n        cursor: pointer;\n        user-select: none;\n        -webkit-tap-highlight-color: transparent;\n        font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n        color: #0f172a;\n        padding-left: 5px;\n    }\n    .mljar-checkbox-container.is-disabled {\n        opacity: 0.6;\n        cursor: not-allowed;\n    }\n\n    .mljar-checkbox-input {\n        position: absolute;\n        opacity: 0;\n        width: 0;\n        height: 0;\n    }\n\n    .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n        box-shadow: inset 0 0 0 2px #007bff;\n    }\n\n    .mljar-checkbox-label {\n        font-size: 14px;\n        line-height: 1.2;\n        padding-top: 2px;\n    }\n\n    /* --- Toggle style (15% smaller) --- */\n    .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n        position: relative;\n        width: 34px;\n        height: 19px;\n        border-radius: 999px;\n        background: #f1f1f2;\n        border: 1px solid #cfd1d5;\n        transition: background 150ms ease, border-color 150ms ease;\n    }\n    .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n        background: #007bff;\n        border-color: #007bff;\n    }\n    .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n        content: \"\";\n        position: absolute;\n        top: 2px;\n        left: 2px;\n        width: 15px;\n        height: 15px;\n        border-radius: 50%;\n        background: #ffffff;\n        box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n        transition: transform 150ms ease;\n    }\n    .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n        transform: translateX(15px);\n    }\n\n    /* --- Classic box style --- */\n    .mljar-checkbox-container.is-box .mljar-checkbox-control {\n        width: 16px;\n        height: 16px;\n        border-radius: 4px;\n        border: 1px solid #cfd1d5;\n        background: #ffffff;\n        display: inline-block;\n        position: relative;\n    }\n    .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n        border-color: #007bff;\n        background: #007bff;\n    }\n    .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n        content: \"\";\n        position: absolute;\n        left: 4px;\n        top: 0px;\n        width: 5px;\n        height: 10px;\n        border: solid #fff;\n        border-width: 0 2px 2px 0;\n        transform: rotate(45deg);\n    }\n\n    .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n        border-color: #007bff;\n        background: #f3f3f4;\n    }\n    ",
       "_dom_classes": [],
       "_esm": "\n    function render({ model, el }) {\n      const container = document.createElement(\"label\");\n      container.classList.add(\"mljar-checkbox-container\");\n\n      const input = document.createElement(\"input\");\n      input.type = \"checkbox\";\n      input.classList.add(\"mljar-checkbox-input\");\n\n      const control = document.createElement(\"span\");\n      control.classList.add(\"mljar-checkbox-control\");\n\n      const text = document.createElement(\"span\");\n      text.classList.add(\"mljar-checkbox-label\");\n\n      container.appendChild(input);\n      container.appendChild(control);\n      container.appendChild(text);\n      el.appendChild(container);\n\n      function syncFromModel() {\n        // appearance\n        const a = model.get(\"appearance\") || \"toggle\";\n        container.classList.remove(\"is-toggle\",\"is-box\");\n        container.classList.add(`is-${a}`);\n        input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n        // value\n        const v = !!model.get(\"value\");\n        if (input.checked !== v) {\n          input.checked = v;\n          input.setAttribute(\"aria-checked\", String(v));\n        }\n        container.classList.toggle(\"is-checked\", v);\n\n        // disabled\n        const d = !!model.get(\"disabled\");\n        input.disabled = d;\n        container.classList.toggle(\"is-disabled\", d);\n\n        // label\n        text.textContent = model.get(\"label\") || \"\";\n\n        // hidden (exists but not visible)\n        container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n      }\n\n      input.addEventListener(\"change\", () => {\n        if (model.get(\"disabled\")) return;\n\n        const v = input.checked;\n        if (model.get(\"value\") !== v) {\n          model.set(\"value\", v);\n          model.set(\"last_changed_at\", new Date().toISOString());\n          model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n          model.save_changes();\n          // model.send({ type: \"changed\", value: v });\n        }\n      });\n\n      model.on(\"change:value\", syncFromModel);\n      model.on(\"change:disabled\", syncFromModel);\n      model.on(\"change:appearance\", syncFromModel);\n      model.on(\"change:label\", syncFromModel);\n      model.on(\"change:hidden\", syncFromModel);\n\n      syncFromModel();\n\n      // ---- read cell id (no DOM modifications) ----\n      /*\n      const ID_ATTR = \"data-cell-id\";\n      const hostWithId = el.closest(`[${ID_ATTR}]`);\n      const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n      if (cellId) {\n        model.set(\"cell_id\", cellId);\n        model.save_changes();\n        model.send({ type: \"cell_id_detected\", value: cellId });\n      } else {\n        const mo = new MutationObserver(() => {\n          const host = el.closest(`[${ID_ATTR}]`);\n          const newId = host?.getAttribute(ID_ATTR);\n          \n          if (newId) {\n            model.set(\"cell_id\", newId);\n            model.save_changes();\n            model.send({ type: \"cell_id_detected\", value: newId });\n            mo.disconnect();\n          }\n        });\n        mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n      }\n      */\n    }\n    export default { render };\n    ",
       "_model_module": "anywidget",
       "_model_module_version": "~0.11.*",
       "_model_name": "AnyModel",
       "_view_count": null,
       "_view_module": "anywidget",
       "_view_module_version": "~0.11.*",
       "_view_name": "AnyView",
       "appearance": "toggle",
       "cell_id": "",
       "disabled": false,
       "hidden": false,
       "label": "WFM — EMEA (upside option)",
       "last_changed_at": "",
       "layout": "IPY_MODEL_4049cd4af46c4c19acf3535b2a553f56",
       "layout_path": null,
       "n_toggles": 0,
       "position": "sidebar",
       "render_slot_id": null,
       "source_cell_id": null,
       "tabbable": null,
       "tooltip": null,
       "url_key": "",
       "value": false
      }
     },
     "f6e29bda007a4096a6624a90eb2a7190": {
      "model_module": "anywidget",
      "model_module_version": "~0.11.*",
      "model_name": "AnyModel",
      "state": {
       "_anywidget_id": "mercury.checkbox.CheckboxWidget",
       "_css": "\n    .mljar-checkbox-container {\n        display: inline-flex;\n        align-items: center;\n        gap: 10px;\n        cursor: pointer;\n        user-select: none;\n        -webkit-tap-highlight-color: transparent;\n        font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n        color: #0f172a;\n        padding-left: 5px;\n    }\n    .mljar-checkbox-container.is-disabled {\n        opacity: 0.6;\n        cursor: not-allowed;\n    }\n\n    .mljar-checkbox-input {\n        position: absolute;\n        opacity: 0;\n        width: 0;\n        height: 0;\n    }\n\n    .mljar-checkbox-input:focus-visible + .mljar-checkbox-control {\n        box-shadow: inset 0 0 0 2px #007bff;\n    }\n\n    .mljar-checkbox-label {\n        font-size: 14px;\n        line-height: 1.2;\n        padding-top: 2px;\n    }\n\n    /* --- Toggle style (15% smaller) --- */\n    .mljar-checkbox-container.is-toggle .mljar-checkbox-control {\n        position: relative;\n        width: 34px;\n        height: 19px;\n        border-radius: 999px;\n        background: #f1f1f2;\n        border: 1px solid #cfd1d5;\n        transition: background 150ms ease, border-color 150ms ease;\n    }\n    .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control {\n        background: #007bff;\n        border-color: #007bff;\n    }\n    .mljar-checkbox-container.is-toggle .mljar-checkbox-control::after {\n        content: \"\";\n        position: absolute;\n        top: 2px;\n        left: 2px;\n        width: 15px;\n        height: 15px;\n        border-radius: 50%;\n        background: #ffffff;\n        box-shadow: 0 1px 2px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.04);\n        transition: transform 150ms ease;\n    }\n    .mljar-checkbox-container.is-toggle.is-checked .mljar-checkbox-control::after {\n        transform: translateX(15px);\n    }\n\n    /* --- Classic box style --- */\n    .mljar-checkbox-container.is-box .mljar-checkbox-control {\n        width: 16px;\n        height: 16px;\n        border-radius: 4px;\n        border: 1px solid #cfd1d5;\n        background: #ffffff;\n        display: inline-block;\n        position: relative;\n    }\n    .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control {\n        border-color: #007bff;\n        background: #007bff;\n    }\n    .mljar-checkbox-container.is-box.is-checked .mljar-checkbox-control::after {\n        content: \"\";\n        position: absolute;\n        left: 4px;\n        top: 0px;\n        width: 5px;\n        height: 10px;\n        border: solid #fff;\n        border-width: 0 2px 2px 0;\n        transform: rotate(45deg);\n    }\n\n    .mljar-checkbox-container:not(.is-disabled):hover .mljar-checkbox-control {\n        border-color: #007bff;\n        background: #f3f3f4;\n    }\n    ",
       "_dom_classes": [],
       "_esm": "\n    function render({ model, el }) {\n      const container = document.createElement(\"label\");\n      container.classList.add(\"mljar-checkbox-container\");\n\n      const input = document.createElement(\"input\");\n      input.type = \"checkbox\";\n      input.classList.add(\"mljar-checkbox-input\");\n\n      const control = document.createElement(\"span\");\n      control.classList.add(\"mljar-checkbox-control\");\n\n      const text = document.createElement(\"span\");\n      text.classList.add(\"mljar-checkbox-label\");\n\n      container.appendChild(input);\n      container.appendChild(control);\n      container.appendChild(text);\n      el.appendChild(container);\n\n      function syncFromModel() {\n        // appearance\n        const a = model.get(\"appearance\") || \"toggle\";\n        container.classList.remove(\"is-toggle\",\"is-box\");\n        container.classList.add(`is-${a}`);\n        input.setAttribute(\"role\", a === \"toggle\" ? \"switch\" : \"checkbox\");\n\n        // value\n        const v = !!model.get(\"value\");\n        if (input.checked !== v) {\n          input.checked = v;\n          input.setAttribute(\"aria-checked\", String(v));\n        }\n        container.classList.toggle(\"is-checked\", v);\n\n        // disabled\n        const d = !!model.get(\"disabled\");\n        input.disabled = d;\n        container.classList.toggle(\"is-disabled\", d);\n\n        // label\n        text.textContent = model.get(\"label\") || \"\";\n\n        // hidden (exists but not visible)\n        container.style.display = model.get(\"hidden\") ? \"none\" : \"inline-flex\";\n      }\n\n      input.addEventListener(\"change\", () => {\n        if (model.get(\"disabled\")) return;\n\n        const v = input.checked;\n        if (model.get(\"value\") !== v) {\n          model.set(\"value\", v);\n          model.set(\"last_changed_at\", new Date().toISOString());\n          model.set(\"n_toggles\", (model.get(\"n_toggles\") || 0) + 1);\n          model.save_changes();\n          // model.send({ type: \"changed\", value: v });\n        }\n      });\n\n      model.on(\"change:value\", syncFromModel);\n      model.on(\"change:disabled\", syncFromModel);\n      model.on(\"change:appearance\", syncFromModel);\n      model.on(\"change:label\", syncFromModel);\n      model.on(\"change:hidden\", syncFromModel);\n\n      syncFromModel();\n\n      // ---- read cell id (no DOM modifications) ----\n      /*\n      const ID_ATTR = \"data-cell-id\";\n      const hostWithId = el.closest(`[${ID_ATTR}]`);\n      const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n      if (cellId) {\n        model.set(\"cell_id\", cellId);\n        model.save_changes();\n        model.send({ type: \"cell_id_detected\", value: cellId });\n      } else {\n        const mo = new MutationObserver(() => {\n          const host = el.closest(`[${ID_ATTR}]`);\n          const newId = host?.getAttribute(ID_ATTR);\n          \n          if (newId) {\n            model.set(\"cell_id\", newId);\n            model.save_changes();\n            model.send({ type: \"cell_id_detected\", value: newId });\n            mo.disconnect();\n          }\n        });\n        mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n      }\n      */\n    }\n    export default { render };\n    ",
       "_model_module": "anywidget",
       "_model_module_version": "~0.11.*",
       "_model_name": "AnyModel",
       "_view_count": null,
       "_view_module": "anywidget",
       "_view_module_version": "~0.11.*",
       "_view_name": "AnyView",
       "appearance": "toggle",
       "cell_id": "",
       "disabled": false,
       "hidden": false,
       "label": "WFM — NA (migrate existing users)",
       "last_changed_at": "",
       "layout": "IPY_MODEL_2b937636abf64a27aea2780988f790c2",
       "layout_path": null,
       "n_toggles": 0,
       "position": "sidebar",
       "render_slot_id": null,
       "source_cell_id": null,
       "tabbable": null,
       "tooltip": null,
       "url_key": "",
       "value": true
      }
     },
     "ff2cafef5fb24fb4b60e4972527894f7": {
      "model_module": "anywidget",
      "model_module_version": "~0.11.*",
      "model_name": "AnyModel",
      "state": {
       "_anywidget_id": "mercury.number.NumberInputWidget",
       "_css": "\n    .mljar-number-container {\n      display: flex;\n      flex-direction: column;\n      width: 100%;\n      font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;\n      font-size: 14px;\n      color: #0f172a;\n      margin-bottom: 8px;\n      padding-left: 4px;\n      padding-right: 4px;\n      box-sizing: border-box;\n    }\n\n    .mljar-number-label {\n      margin-bottom: 4px;\n      font-weight: 600;\n    }\n\n    .mljar-number-field-row {\n      display: flex;\n      align-items: stretch;\n      width: 100%;\n      min-height: 40px;\n      border: 1px solid #cfd1d5;\n      border-radius: 6px;\n      background: #ffffff;\n      box-sizing: border-box;\n      overflow: hidden;\n    }\n\n    .mljar-number-input {\n      flex: 1 1 auto;\n      min-width: 0;\n      min-height: 100%;\n      padding: 7px 10px;\n      border: 0;\n      border-radius: 0;\n      background: #ffffff;\n      box-sizing: border-box;\n      background-color: #ffffff !important;\n      color: #0f172a !important;\n      font: inherit;\n      line-height: 1.2;\n      -moz-appearance: textfield;\n    }\n\n    .mljar-number-input::-webkit-outer-spin-button,\n    .mljar-number-input::-webkit-inner-spin-button {\n      -webkit-appearance: none;\n      margin: 0;\n    }\n\n    .mljar-number-input:disabled {\n      background: #f5f5f5;\n      color: #888;\n      cursor: not-allowed;\n    }\n\n    .mljar-number-input:focus {\n      outline: none;\n    }\n\n    .mljar-number-field-row:focus-within {\n      border-color: #007bff;\n      border-width: 2px;\n      box-shadow: none;\n    }\n\n    .mljar-number-field-row:focus-within .mljar-number-controls {\n      border-left-color: #007bff;\n    }\n\n    .mljar-number-controls {\n      display: flex;\n      align-items: stretch;\n      flex: 0 0 auto;\n      border-left: 1px solid #cfd1d5;\n      background: #f1f1f2;\n    }\n\n    .mljar-number-step-btn {\n      display: inline-flex;\n      align-items: center;\n      justify-content: center;\n      width: 38px;\n      min-width: 38px;\n      min-height: 100%;\n      border: 0;\n      border-radius: 0;\n      background: transparent;\n      color: #0f172a;\n      font: inherit;\n      font-size: 18px;\n      font-weight: 700;\n      line-height: 1;\n      cursor: pointer;\n      padding: 0;\n      user-select: none;\n      -webkit-user-select: none;\n      touch-action: manipulation;\n      transition: background-color 0.14s ease, color 0.14s ease;\n    }\n\n    .mljar-number-step-up {\n      border-left: 1px solid #cfd1d5;\n    }\n\n    .mljar-number-step-btn:hover {\n      background: #f3f3f4;\n    }\n\n    .mljar-number-step-btn:active {\n      background: #e6f2ff;\n      color: #007bff;\n    }\n\n    .mljar-number-step-btn:focus-visible {\n      outline: none;\n      background: #e6f2ff;\n      color: #007bff;\n    }\n\n    .mljar-number-step-btn:disabled {\n      background: #f5f5f5;\n      color: #aaa;\n      cursor: not-allowed;\n    }\n\n    @media (max-width: 768px) {\n      .mljar-number-field-row {\n        min-height: 44px;\n      }\n\n      .mljar-number-input {\n        min-height: 44px;\n        padding: 8px 12px;\n      }\n\n      .mljar-number-step-btn {\n        font-size: 19px;\n        width: 44px;\n        min-width: 44px;\n      }\n    }\n    ",
       "_dom_classes": [],
       "_esm": "\n    function render({ model, el }) {\n      const container = document.createElement(\"div\");\n      container.classList.add(\"mljar-number-container\");\n\n      const topLabel = document.createElement(\"div\");\n      topLabel.classList.add(\"mljar-number-label\");\n\n      const fieldRow = document.createElement(\"div\");\n      fieldRow.classList.add(\"mljar-number-field-row\");\n\n      const input = document.createElement(\"input\");\n      input.type = \"number\";\n      input.classList.add(\"mljar-number-input\");\n\n      const decrementBtn = document.createElement(\"button\");\n      decrementBtn.type = \"button\";\n      decrementBtn.classList.add(\"mljar-number-step-btn\", \"mljar-number-step-down\");\n      decrementBtn.textContent = \"-\";\n      decrementBtn.setAttribute(\"aria-label\", \"Decrease value\");\n\n      const incrementBtn = document.createElement(\"button\");\n      incrementBtn.type = \"button\";\n      incrementBtn.classList.add(\"mljar-number-step-btn\", \"mljar-number-step-up\");\n      incrementBtn.textContent = \"+\";\n      incrementBtn.setAttribute(\"aria-label\", \"Increase value\");\n\n      const controls = document.createElement(\"div\");\n      controls.classList.add(\"mljar-number-controls\");\n\n      controls.appendChild(decrementBtn);\n      controls.appendChild(incrementBtn);\n      fieldRow.appendChild(input);\n      fieldRow.appendChild(controls);\n\n      container.appendChild(topLabel);\n      container.appendChild(fieldRow);\n      el.appendChild(container);\n\n      function clamp(val, min, max) {\n        if (Number.isFinite(min) && val < min) return min;\n        if (Number.isFinite(max) && val > max) return max;\n        return val;\n      }\n\n      function normalizeStep(step) {\n        return Number.isFinite(step) && step > 0 ? step : 1;\n      }\n\n      function snapToStep(value, min, step) {\n        const safeStep = normalizeStep(step);\n        const base = Number.isFinite(min) ? min : 0;\n        const steps = Math.round((value - base) / safeStep);\n        const snapped = base + steps * safeStep;\n        const precision = Math.max(\n          0,\n          (String(safeStep).split(\".\")[1] || \"\").length\n        );\n\n        return Number(snapped.toFixed(precision + 2));\n      }\n\n      function isOnStepGrid(value, min, step) {\n        const safeStep = normalizeStep(step);\n        const base = Number.isFinite(min) ? min : 0;\n        const steps = (value - base) / safeStep;\n        const nearest = Math.round(steps);\n        const epsilon = Math.max(1e-9, safeStep * 1e-9);\n\n        return Math.abs(steps - nearest) <= epsilon;\n      }\n\n      function getCurrentBounds() {\n        return {\n          min: Number(model.get(\"min\")),\n          max: Number(model.get(\"max\")),\n        };\n      }\n\n      function isTransientDraft(raw) {\n        return raw === \"\" || raw === \"-\" || raw === \".\" || raw === \"-.\";\n      }\n\n      let isEditing = false;\n      const INPUT_COMMIT_DEBOUNCE_MS = 400;\n\n      function clearPendingDraftCommit() {\n        if (debounceTimer) clearTimeout(debounceTimer);\n        pendingDraftValue = null;\n      }\n\n      function parseDraftValue(rawValue) {\n        const raw = String(rawValue).trim();\n        if (isTransientDraft(raw)) {\n          return { kind: \"transient\" };\n        }\n\n        const value = Number(raw);\n        if (!Number.isFinite(value)) {\n          return { kind: \"invalid\" };\n        }\n\n        return { kind: \"number\", value };\n      }\n\n      function commitValue(nextValue, saveNow = true) {\n        const { min, max } = getCurrentBounds();\n        const step = Number(model.get(\"step\"));\n        const parsed = parseDraftValue(nextValue);\n        if (parsed.kind !== \"number\") {\n          syncFromModel();\n          return;\n        }\n\n        let v = parsed.value;\n        v = clamp(v, min, max);\n        v = snapToStep(v, min, step);\n        v = clamp(v, min, max);\n        input.value = String(v);\n        model.set(\"value\", v);\n\n        if (saveNow) {\n          model.save_changes();\n        }\n      }\n\n      function syncFromModel() {\n        topLabel.innerHTML = model.get(\"label\") || \"Enter number\";\n\n        const min = Number(model.get(\"min\"));\n        const max = Number(model.get(\"max\"));\n        const step = Number(model.get(\"step\"));\n\n        if (Number.isFinite(min)) input.min = String(min); else input.removeAttribute(\"min\");\n        if (Number.isFinite(max)) input.max = String(max); else input.removeAttribute(\"max\");\n        if (Number.isFinite(step)) input.step = String(step); else input.removeAttribute(\"step\");\n\n        const v = Number(model.get(\"value\"));\n        if (!isEditing) {\n          input.value = Number.isFinite(v) ? String(v) : \"\";\n        }\n\n        const disabled = !!model.get(\"disabled\");\n        input.disabled = disabled;\n        incrementBtn.disabled = disabled;\n        decrementBtn.disabled = disabled;\n\n        const hidden = !!model.get(\"hidden\");\n        container.style.display = hidden ? \"none\" : \"flex\";\n      }\n\n      let debounceTimer = null;\n      let pendingDraftValue = null;\n      input.addEventListener(\"focus\", () => {\n        isEditing = true;\n      });\n\n      input.addEventListener(\"input\", () => {\n        if (model.get(\"disabled\")) return;\n\n        const parsed = parseDraftValue(input.value);\n        if (parsed.kind !== \"number\") {\n          clearPendingDraftCommit();\n          return;\n        }\n\n        const v = parsed.value;\n        const { min, max } = getCurrentBounds();\n        const step = Number(model.get(\"step\"));\n        if (Number.isFinite(min) && v < min) {\n          clearPendingDraftCommit();\n          return;\n        }\n        if (Number.isFinite(max) && v > max) {\n          clearPendingDraftCommit();\n          return;\n        }\n        if (!isOnStepGrid(v, min, step)) {\n          clearPendingDraftCommit();\n          return;\n        }\n\n        pendingDraftValue = v;\n        if (debounceTimer) clearTimeout(debounceTimer);\n        debounceTimer = setTimeout(() => {\n          if (pendingDraftValue === null) return;\n          model.set(\"value\", pendingDraftValue);\n          model.save_changes();\n          pendingDraftValue = null;\n        }, INPUT_COMMIT_DEBOUNCE_MS);\n      });\n\n      input.addEventListener(\"blur\", () => {\n        isEditing = false;\n        clearPendingDraftCommit();\n        commitValue(input.value, true);\n      });\n\n      input.addEventListener(\"keydown\", event => {\n        if (event.key === \"Enter\") {\n          event.preventDefault();\n          input.blur();\n        }\n      });\n\n      incrementBtn.addEventListener(\"click\", () => {\n        if (model.get(\"disabled\")) return;\n        const current = Number(model.get(\"value\"));\n        const step = normalizeStep(Number(model.get(\"step\")));\n        const min = Number(model.get(\"min\"));\n        const base = Number.isFinite(current) ? current : 0;\n        commitValue(snapToStep(base + step, min, step));\n      });\n\n      decrementBtn.addEventListener(\"click\", () => {\n        if (model.get(\"disabled\")) return;\n        const current = Number(model.get(\"value\"));\n        const step = normalizeStep(Number(model.get(\"step\")));\n        const min = Number(model.get(\"min\"));\n        const base = Number.isFinite(current) ? current : 0;\n        commitValue(snapToStep(base - step, min, step));\n      });\n\n      model.on(\"change:value\", syncFromModel);\n      model.on(\"change:min\", syncFromModel);\n      model.on(\"change:max\", syncFromModel);\n      model.on(\"change:step\", syncFromModel);\n      model.on(\"change:label\", syncFromModel);\n      model.on(\"change:disabled\", syncFromModel);\n      model.on(\"change:hidden\", syncFromModel);\n\n      syncFromModel();\n\n      // ---- read cell id (no DOM modifications) ----\n      /*\n      const ID_ATTR = \"data-cell-id\";\n      const hostWithId = el.closest(`[${ID_ATTR}]`);\n      const cellId = hostWithId ? hostWithId.getAttribute(ID_ATTR) : null;\n\n      if (cellId) {\n        model.set(\"cell_id\", cellId);\n        model.save_changes();\n        model.send({ type: \"cell_id_detected\", value: cellId });\n      } else {\n        const mo = new MutationObserver(() => {\n          const host = el.closest(`[${ID_ATTR}]`);\n          const newId = host?.getAttribute(ID_ATTR);\n          if (newId) {\n            model.set(\"cell_id\", newId);\n            model.save_changes();\n            model.send({ type: \"cell_id_detected\", value: newId });\n            mo.disconnect();\n          }\n        });\n        mo.observe(document.body, { attributes: true, subtree: true, attributeFilter: [ID_ATTR] });\n      }*/\n    }\n    export default { render };\n    ",
       "_model_module": "anywidget",
       "_model_module_version": "~0.11.*",
       "_model_name": "AnyModel",
       "_view_count": null,
       "_view_module": "anywidget",
       "_view_module_version": "~0.11.*",
       "_view_name": "AnyView",
       "cell_id": "",
       "disabled": false,
       "hidden": false,
       "label": "ANZ — current platform cost ($/yr)",
       "layout": "IPY_MODEL_c0c824e7a641458cadcc23a9d93b5d15",
       "layout_path": null,
       "max": 8000000.0,
       "min": 0.0,
       "position": "inline",
       "render_slot_id": null,
       "source_cell_id": null,
       "step": 10000.0,
       "tabbable": null,
       "tooltip": null,
       "url_key": "",
       "value": 677320.0
      }
     },
     "ffebc83a901849f08ca62e76b4b899b8": {
      "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
}
