Files
palladium/studies/202602_AmazonConnect/notebooks/03_business_case.ipynb
Robert Helewka a2420ed692 refactor: restructure repo into core/app modules with per-study folders
Reorganize Palladium codebase into a modular architecture with `core/`
shared logic and `app/` Streamlit UI, separating per-study assets into
`studies/YYYYMM_<Vendor>/` folders containing notebooks, seed data, and
configuration. Update README to reflect new structure, add `.gitignore`
entries for `.env` and study exports, and refresh component documentation.
2026-05-20 22:28:12 -04:00

222 lines
6.1 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"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,
"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,
"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",
"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,
"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,
"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",
"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,
"metadata": {},
"outputs": [],
"source": [
"charts.cashflow_chart(\n",
" summary['yearly_breakdown'],\n",
" initial_cost=summary.get('initial_costs', 0),\n",
").show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Waterfall: Benefits PV → Costs PV → NPV"
]
},
{
"cell_type": "code",
"execution_count": null,
"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",
"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,
"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,
"metadata": {},
"outputs": [],
"source": [
"charts.scenario_comparison(scenario_summaries).show()"
]
},
{
"cell_type": "markdown",
"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,
"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",
"metadata": {},
"source": [
"Continue with [`04_export.ipynb`](04_export.ipynb) →"
]
}
],
"metadata": {
"kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"},
"language_info": {"name": "python", "version": "3.11"}
},
"nbformat": 4,
"nbformat_minor": 5
}