Files
palladium/studies/202512_GenesysCX/notebooks/01_business_case.ipynb
Robert Helewka 64fb83257d feat: add GenesysCX study and fix Streamlit chart key collisions
- Add 202512_GenesysCX TEI study (config, seed data, notebooks, README)
  with NPV $10.8M / ROI 266% including AI-token cost line
- Add explicit `key` parameter to all chart wrappers in app/components
  to prevent StreamlitDuplicateElementId errors when the same figure
  type renders across Summary/Benefits/Costs tabs
- Render benefits bar and cost pie charts on their respective tabs
- Add benefits_vs_costs_by_year chart wrapper
2026-06-10 14:26:49 -04:00

376 lines
13 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"cells": [
{
"cell_type": "markdown",
"id": "3da9de99",
"metadata": {},
"source": [
"# 01 · Business Case — Genesys CX Cloud TEI\n",
"\n",
"Working view of the live Athena tool, plus the **Genesys AI Experience token**\n",
"cost line the published study omits. Run `00_provision.ipynb` first.\n",
"\n",
"The published study models \\$0 AI consumption while three of its four benefits\n",
"(self-service uplift, agent efficiency, agent-assist sales) depend on AI\n",
"capabilities that Genesys bills via AI Experience tokens. Token pricing is\n",
"tiered and deal-specific, so the model keeps it simple — **one annual cost\n",
"value, entered from the Genesys quote**, exactly as Athena stores it. Quote\n",
"details (volume, unit price, tier) go in the field notes for the audit trail."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "89deae70",
"metadata": {},
"outputs": [],
"source": [
"import sys, pathlib # path shim: works on a fresh kernel\n",
"for _p in [pathlib.Path.cwd(), *pathlib.Path.cwd().parents]:\n",
" if (_p / \"pyproject.toml\").exists():\n",
" sys.path.insert(0, str(_p)); break\n",
"\n",
"import pandas as pd\n",
"from core.bootstrap import init\n",
"from core.notebook_helpers import charts\n",
"\n",
"pal = init(study=\"202512_GenesysCX\")\n",
"client, seed, config = pal.client, pal.seed_data, pal.config\n",
"\n",
"TOOL_ID = config.TOOL_PUBLIC_ID\n",
"assert TOOL_ID, \"No PALLADIUM_GENESYSCX_TOOL_PUBLIC_ID in .env — run 00_provision.ipynb first.\"\n",
"tool = client.get_tool(TOOL_ID)\n",
"print(f\"Tool: {tool.get('name')} ({TOOL_ID}) status={tool.get('status')}\")"
]
},
{
"cell_type": "markdown",
"id": "09afaf70",
"metadata": {},
"source": [
"## Current financial summary"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3d80c19a",
"metadata": {},
"outputs": [],
"source": [
"summary = client.calculate(TOOL_ID)\n",
"client.print_summary(TOOL_ID)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "392df0ba",
"metadata": {},
"outputs": [],
"source": [
"values = client.get_values(TOOL_ID)\n",
"benefit_rows = [v for v in values if v.get(\"table\") == \"benefits\"]\n",
"cost_rows = [v for v in values if v.get(\"table\") == \"costs\"]\n",
"\n",
"def value_table(rows, *, initial=False):\n",
" out = []\n",
" for v in rows:\n",
" yv = v.get(\"year_values\") or {}\n",
" rec = {\"field\": v.get(\"label\") or v[\"field_key\"]}\n",
" if initial:\n",
" rec[\"Initial\"] = v.get(\"initial\", 0.0)\n",
" rec.update({f\"Year {y}\": yv.get(str(y), 0.0) for y in (1, 2, 3)})\n",
" rec[\"risk_adj\"] = v.get(\"risk_adjustment\")\n",
" out.append(rec)\n",
" return pd.DataFrame(out)\n",
"\n",
"print(\"Benefits (nominal; Athena risk-adjusts at calculate time):\")\n",
"display(value_table(benefit_rows))\n",
"print(\"Costs (stored pre-multiplied by 1 + risk_adj):\")\n",
"display(value_table(cost_rows, initial=True))"
]
},
{
"cell_type": "markdown",
"id": "2383f761",
"metadata": {},
"source": [
"## Financial visualizations\n",
"\n",
"All figures are risk-adjusted and built from the live Athena values, using\n",
"the shared `core.notebook_helpers.charts` helpers (same ones the Streamlit\n",
"app uses)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ── Visual theme — plain values, edit freely (not confidential) ──\n",
"THEME = {\n",
" \"heading_font\": \"Helvetica Neue, Arial, sans-serif\", # chart titles\n",
" \"body_font\": \"Helvetica, Arial, sans-serif\", # axes, legends, labels\n",
" \"font_color\": \"#1F2937\", # hex\n",
"\n",
" # Circle-chart slice colours, applied in order a..j\n",
" \"pie_colors\": {\n",
" \"a\": \"#1565C0\",\n",
" \"b\": \"#2E7D32\",\n",
" \"c\": \"#C62828\",\n",
" \"d\": \"#F9A825\",\n",
" \"e\": \"#6A1B9A\",\n",
" \"f\": \"#00838F\",\n",
" \"g\": \"#EF6C00\",\n",
" \"h\": \"#5D4037\",\n",
" \"i\": \"#37474F\",\n",
" \"j\": \"#AD1457\",\n",
" },\n",
"\n",
" \"bar_green\": \"#2E7D32\", # benefits bars\n",
" \"bar_red\": \"#C62828\", # costs bars\n",
"}\n",
"\n",
"charts.apply_theme(**THEME)\n",
"print(\"Theme applied — re-run the chart cells below to restyle.\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "14d5942a",
"metadata": {},
"outputs": [],
"source": [
"# Cost breakdown — share of total three-year cost per line item\n",
"charts.cost_breakdown_pie(\n",
" cost_rows, title=\"Cost Breakdown — share of 3-year total (risk-adjusted)\"\n",
").show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d13f342e",
"metadata": {},
"outputs": [],
"source": [
"# Benefits — three-year risk-adjusted total per category\n",
"charts.benefits_bar(\n",
" benefit_rows, title=\"Benefits (Three-Year, Risk-Adjusted)\"\n",
").show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9ef6caee",
"metadata": {},
"outputs": [],
"source": [
"# Benefits vs costs, year by year (Initial = one-time Year-0 costs)\n",
"charts.benefits_vs_costs_by_year(benefit_rows, cost_rows).show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6f069df6",
"metadata": {},
"outputs": [],
"source": [
"# Cash flow with cumulative net benefits — the Forrester-style exhibit\n",
"def _yearly_breakdown(benefit_items, cost_items):\n",
" \"\"\"Risk-adjusted yearly rows + initial, computed from the live values.\"\"\"\n",
" initial = sum(float(c.get(\"initial\") or 0) for c in cost_items)\n",
" rows, cumulative = [], -initial\n",
" for y in (1, 2, 3):\n",
" b = sum(float((v.get(\"year_values\") or {}).get(str(y), 0) or 0)\n",
" * (1 - float(v.get(\"risk_adjustment\") or 0))\n",
" for v in benefit_items)\n",
" c = sum(float((v.get(\"year_values\") or {}).get(str(y), 0) or 0)\n",
" for v in cost_items)\n",
" cumulative += b - c\n",
" rows.append({\"year\": y, \"benefits\": b, \"costs\": c,\n",
" \"net\": b - c, \"cumulative_net\": cumulative})\n",
" return rows, initial\n",
"\n",
"yb, initial_cost = _yearly_breakdown(benefit_rows, cost_rows)\n",
"charts.cashflow_chart(yb, initial_cost=initial_cost).show()\n",
"pd.DataFrame(yb)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b1ff0315",
"metadata": {},
"outputs": [],
"source": [
"# Waterfall — Benefits PV down to NPV (from the Athena summary)\n",
"charts.waterfall([\n",
" (\"Benefits PV\", float(summary[\"total_benefits_pv\"])),\n",
" (\"Costs PV\", -float(summary[\"total_costs_pv\"])),\n",
" (\"NPV\", float(summary[\"net_present_value\"])),\n",
"], title=\"Benefits PV → Costs PV → NPV\").show()"
]
},
{
"cell_type": "markdown",
"id": "c78f77a9",
"metadata": {},
"source": [
"## Genesys AI Experience tokens — annual cost\n",
"\n",
"Enter the negotiated annual token cost from the Genesys quote. For sizing\n",
"context, the study's own drivers imply roughly **1,040,000** self-service\n",
"interactions/yr and **3,120,000** agent-assisted interactions/yr would draw\n",
"tokens — bring the actual figure from the quote, not a derivation."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "99b9c665",
"metadata": {},
"outputs": [],
"source": [
"# ── Deal inputs ──\n",
"AI_TOKEN_ANNUAL_COST = 0.0 # $/yr from the Genesys quote — 0 reproduces the published study\n",
"AI_TOKEN_QUOTE_NOTE = \"\" # e.g. \"Quote #1234: 4.2M tokens/yr @ $0.05, tier 2 commit\"\n",
"\n",
"print(f\"AI token line: ${AI_TOKEN_ANNUAL_COST:,.0f}/yr\")"
]
},
{
"cell_type": "markdown",
"id": "b7c6c4c7",
"metadata": {},
"source": [
"### Sensitivity — what the AI line does to NPV and ROI\n",
"\n",
"Computed locally from the current Athena summary: an annual cost `Δ` raises\n",
"costs PV by `Δ × 2.4869` (the 3-year, 10% annuity factor) and lowers NPV by\n",
"the same amount."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "09ceeea2",
"metadata": {},
"outputs": [],
"source": [
"ANNUITY = sum(1 / 1.10**n for n in (1, 2, 3)) # 2.4869\n",
"\n",
"base_benefits_pv = float(summary[\"total_benefits_pv\"])\n",
"base_costs_pv = float(summary[\"total_costs_pv\"])\n",
"\n",
"# Remove any token cost already stored, to get a clean zero-token base.\n",
"current_tokens = next(\n",
" (v for v in values if v[\"field_key\"] == \"genesys_ai_tokens\"), {})\n",
"current_annual = (current_tokens.get(\"year_values\") or {}).get(\"1\", 0.0)\n",
"base_costs_pv -= current_annual * ANNUITY\n",
"\n",
"sweep = [0, 100_000, 250_000, 500_000, 750_000, 1_000_000]\n",
"if AI_TOKEN_ANNUAL_COST and AI_TOKEN_ANNUAL_COST not in sweep:\n",
" sweep = sorted(sweep + [AI_TOKEN_ANNUAL_COST])\n",
"\n",
"rows = []\n",
"for ai_annual in sweep:\n",
" costs_pv = base_costs_pv + ai_annual * ANNUITY\n",
" npv = base_benefits_pv - costs_pv\n",
" rows.append({\n",
" \"AI cost/yr\": f\"${ai_annual:,.0f}\" + (\" ← your input\" if ai_annual == AI_TOKEN_ANNUAL_COST and ai_annual else \"\"),\n",
" \"Costs PV\": f\"${costs_pv:,.0f}\",\n",
" \"NPV\": f\"${npv:,.0f}\",\n",
" \"ROI\": f\"{npv / costs_pv * 100:,.0f}%\",\n",
" })\n",
"\n",
"display(pd.DataFrame(rows))"
]
},
{
"cell_type": "markdown",
"id": "6516270c",
"metadata": {},
"source": [
"## Push the AI token cost to Athena\n",
"\n",
"Writes the annual cost into `genesys_ai_tokens` (quote details in the notes),\n",
"recalculates server-side, and saves a version."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c6caacea",
"metadata": {},
"outputs": [],
"source": [
"PUSH = False # ← set True once AI_TOKEN_ANNUAL_COST is final\n",
"\n",
"if PUSH:\n",
" note = (\n",
" f\"AI Experience tokens: ${AI_TOKEN_ANNUAL_COST:,.0f}/yr. \"\n",
" + (f\"{AI_TOKEN_QUOTE_NOTE} \" if AI_TOKEN_QUOTE_NOTE else \"\")\n",
" + \"Line absent from the published Forrester study.\"\n",
" )\n",
" client.update_values(TOOL_ID, [{\n",
" \"field_key\": \"genesys_ai_tokens\",\n",
" \"year_values\": {\"1\": round(AI_TOKEN_ANNUAL_COST, 2),\n",
" \"2\": round(AI_TOKEN_ANNUAL_COST, 2),\n",
" \"3\": round(AI_TOKEN_ANNUAL_COST, 2)},\n",
" \"notes\": note,\n",
" }])\n",
" summary = client.calculate(TOOL_ID)\n",
" client.print_summary(TOOL_ID)\n",
" client.save_version(TOOL_ID, note=f\"AI token cost set: {note}\")\n",
" print(\"✅ Pushed, recalculated, and versioned. Re-run the visualization \"\n",
" \"cells above to refresh the charts.\")\n",
"else:\n",
" print(\"Dry run — set PUSH = True to write to Athena.\")"
]
},
{
"cell_type": "markdown",
"id": "c261ad48",
"metadata": {},
"source": [
"## Next steps\n",
"\n",
"- Adjust other drivers for the client (interaction volume, agent count,\n",
" self-service delta) via `client.update_values` or the Streamlit app\n",
" (`make app`), saving a version per scenario.\n",
"- Export charts for a deck: any figure object supports\n",
" `fig.write_image(\"chart.png\")` (needs `pip install kaleido`) or\n",
" `fig.write_html(\"chart.html\")`.\n",
"- Export for the report pipeline:\n",
" `python -m palladium export $PALLADIUM_GENESYSCX_TOOL_PUBLIC_ID -o exports/export.json`"
]
}
],
"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.12.7"
}
},
"nbformat": 4,
"nbformat_minor": 5
}