- 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
376 lines
13 KiB
Plaintext
376 lines
13 KiB
Plaintext
{
|
||
"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
|
||
} |