253 lines
6.7 KiB
Plaintext
253 lines
6.7 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "4173501f",
|
|
"metadata": {},
|
|
"source": [
|
|
"# 03 — Business Case\n",
|
|
"\n",
|
|
"Combine the benefits and costs into the consolidated TEI summary,\n",
|
|
"render the Cash Flow chart, and run scenario analysis. This notebook\n",
|
|
"should reproduce the headline numbers from the PDF Financial Summary:\n",
|
|
"\n",
|
|
"* **NPV $78.7M • ROI 342% • Payback <6 months**"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "3cc4b453",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import sys\n",
|
|
"from pathlib import Path\n",
|
|
"\n",
|
|
"ROOT = Path.cwd().resolve()\n",
|
|
"while ROOT != ROOT.parent and not (ROOT / 'core').is_dir():\n",
|
|
" ROOT = ROOT.parent\n",
|
|
"if str(ROOT) not in sys.path:\n",
|
|
" sys.path.insert(0, str(ROOT))\n",
|
|
"STUDY = ROOT / 'studies' / '202602_AmazonConnect'\n",
|
|
"if str(STUDY) not in sys.path:\n",
|
|
" sys.path.insert(0, str(STUDY))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "62c56628",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import config\n",
|
|
"import seed_data\n",
|
|
"from core.export import build_report_data\n",
|
|
"from core.export.report_data import _compute_summary\n",
|
|
"from core.notebook_helpers import charts, display, tables"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "11fdc7c3",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Local summary (no Athena round-trip)\n",
|
|
"\n",
|
|
"Compute the moderate-case TEI summary directly from `seed_data` so the\n",
|
|
"notebook produces results even before the Athena tool is provisioned."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "7d295b06",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"summary = _compute_summary(\n",
|
|
" seed_data.BENEFITS,\n",
|
|
" seed_data.COSTS,\n",
|
|
" config.DISCOUNT_RATE,\n",
|
|
" config.ANALYSIS_YEARS,\n",
|
|
")\n",
|
|
"# `_compute_summary` returns roi_pct; expose it as `roi` for kpi_cards.\n",
|
|
"summary['roi'] = summary.get('roi_pct')\n",
|
|
"display.kpi_cards(summary, title='Forrester composite — moderate case')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "c3fc75fb",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"df_cash = tables.cashflow_table(summary)\n",
|
|
"df_cash.style.format({c: '${:,.0f}' for c in df_cash.columns if c != 'Year'})"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "dd58e4c8",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Cash flow chart\n",
|
|
"\n",
|
|
"Mirrors the chart on PDF page 25: stacked benefits/costs by year +\n",
|
|
"cumulative-net line."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "5d293439",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"charts.cashflow_chart(\n",
|
|
" summary['yearly_breakdown'],\n",
|
|
" initial_cost=summary.get('initial_costs', 0),\n",
|
|
").show()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "3b4b7b39",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Waterfall: Benefits PV → Costs PV → NPV"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "04012e0f",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"charts.waterfall([\n",
|
|
" ('Benefits PV', summary['total_benefits_pv']),\n",
|
|
" ('Costs PV', -summary['total_costs_pv']),\n",
|
|
" ('NPV', summary['npv']),\n",
|
|
"]).show()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "b48db610",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Scenario analysis\n",
|
|
"\n",
|
|
"Apply the default Palladium multipliers (see `core.calculations.SCENARIOS`):\n",
|
|
"\n",
|
|
"* **Conservative** — 80% adoption, +10pp risk on benefits / -10pp on costs\n",
|
|
"* **Moderate** — base case (= the published Forrester study)\n",
|
|
"* **Aggressive** — 115% adoption, -5pp risk on benefits / +5pp on costs"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "1fb9aa20",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from core.calculations import apply_scenario\n",
|
|
"import pandas as pd\n",
|
|
"\n",
|
|
"scenario_summaries = {}\n",
|
|
"for name in ('conservative', 'moderate', 'aggressive'):\n",
|
|
" sb = apply_scenario(seed_data.BENEFITS, name, table='benefits')\n",
|
|
" sc = apply_scenario(seed_data.COSTS, name, table='costs')\n",
|
|
" scenario_summaries[name] = _compute_summary(sb, sc, config.DISCOUNT_RATE, config.ANALYSIS_YEARS)\n",
|
|
"\n",
|
|
"scen_df = pd.DataFrame([\n",
|
|
" {\n",
|
|
" 'Scenario': k,\n",
|
|
" 'Benefits PV': v['total_benefits_pv'],\n",
|
|
" 'Costs PV': v['total_costs_pv'],\n",
|
|
" 'NPV': v['npv'],\n",
|
|
" 'ROI %': v['roi_pct'],\n",
|
|
" 'Payback (mo)': round(v['payback_months'], 1) if v['payback_months'] is not None else None,\n",
|
|
" }\n",
|
|
" for k, v in scenario_summaries.items()\n",
|
|
"])\n",
|
|
"scen_df.style.format({\n",
|
|
" 'Benefits PV': '${:,.0f}', 'Costs PV': '${:,.0f}', 'NPV': '${:,.0f}', 'ROI %': '{:,.0f}%'\n",
|
|
"})"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "0ff81b9d",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"charts.scenario_comparison(scenario_summaries).show()"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "270745bf",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Cross-check vs Athena (optional)\n",
|
|
"\n",
|
|
"When `TOOL_PUBLIC_ID` is set, ask Athena to recalculate the summary on\n",
|
|
"the server side and confirm it matches our local computation."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "c8239dbd",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"if config.TOOL_PUBLIC_ID:\n",
|
|
" from core.tei_client import TEIClient\n",
|
|
"\n",
|
|
" client = TEIClient()\n",
|
|
" client.calculate(config.TOOL_PUBLIC_ID)\n",
|
|
" server_summary = client.get_summary(config.TOOL_PUBLIC_ID)\n",
|
|
" display.kpi_cards(server_summary, title='Athena server-side summary')\n",
|
|
"else:\n",
|
|
" display.alert('Set TOOL_PUBLIC_ID to compare Athena vs local.', 'info')"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "794848f5",
|
|
"metadata": {},
|
|
"source": [
|
|
"Continue with [`04_export.ipynb`](04_export.ipynb) →"
|
|
]
|
|
}
|
|
],
|
|
"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
|
|
}
|