studies/202602_AmazonConnect -> studies/202602_TEI_Amazon_Connect, rebuilt as pattern Variant 4 (TEI composite reproduction): - teicalc/ self-contained engine (stdlib-only): Forrester's tables as the never-edited verbatim anchor, NPV/ROI/payback + risk adjustment transplanted from core/calculations, ClientDrivers overlay (contacts/ agents/fixed driver map, growth re-base, identity at composite scale), scenario stress with core-identical semantics - one deliverable notebook (business_case.ipynb): widget-pair sidebar drivers, published-vs-overlay KPI columns, cash-flow/waterfall/scenario charts, verification gate, backstage JSON data appendix - gate + tests reproduce the published totals within PDF rounding: NPV $78.7M / ROI 342% / payback <6 months (engine $78,713,492 / 342.48% / 0.7 months); 27 study tests, headless nbconvert green, stage simulation leak-free, exports carry the appendix - old Athena workflow (00_provision..04_export, config.py, seed_data.py) deleted; git history preserves it; root test fixture repointed to teicalc.anchor - docs: study README rewritten; root README points new studies at template/MercuryNotebook; pattern doc stale ctm-token-calculator paths now cite studies/202607_CTM_GenesysCX; Variant 4 cites this study as its realized reference Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
108 lines
4.4 KiB
Python
108 lines
4.4 KiB
Python
"""
|
||
Client overlay — Variant 4's personalization layer.
|
||
|
||
The verbatim anchor is Forrester's *composite organization* (2,000 agents,
|
||
20M contacts, 30% growth). This module rescales that composite to a client's
|
||
size: a 🟡 **first-order linear rescale**, answering "what does the composite
|
||
look like at your scale?", not "what is your TEI?".
|
||
|
||
Each verbatim row is tied to the driver that dominates its derivation in the
|
||
PDF (see ``BENEFIT_DRIVERS``/``COST_DRIVERS``); rows scale linearly with
|
||
their driver, project-based costs stay fixed. The client's growth rate
|
||
re-bases the composite's Y1→Y3 trajectory (which embeds 30% YoY).
|
||
|
||
``overlay_rows(COMPOSITE)`` is the identity — it reproduces the verbatim
|
||
numbers exactly, so headless widget defaults form the published-study
|
||
reproduction the gate expects. The anchor is never mutated: every function
|
||
deep-copies.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from copy import deepcopy
|
||
from dataclasses import dataclass
|
||
|
||
from .anchor import ASSUMPTIONS, BENEFITS_VERBATIM, COSTS_VERBATIM
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class ClientDrivers:
|
||
"""Client inputs; defaults are the Forrester composite (identity overlay)."""
|
||
|
||
agents_fte: int = ASSUMPTIONS["agents_fte"] # 2,000 (+200 supervisors at 10:1)
|
||
annual_contacts_y1: int = ASSUMPTIONS["annual_contacts_y1"] # 20M
|
||
growth_rate: float = ASSUMPTIONS["growth_rate"] # 0.30 YoY
|
||
discount_rate: float = ASSUMPTIONS["discount_rate"] # 0.10
|
||
|
||
|
||
COMPOSITE = ClientDrivers()
|
||
|
||
|
||
#: 🟡 Which driver each verbatim row scales with, per its PDF derivation.
|
||
BENEFIT_DRIVERS: dict[str, str] = {
|
||
"ai_contact_resolution": "contacts", # AHT × volume → contact-driven
|
||
"ai_content_sentiment": "contacts", # per-call summaries/QA → contact-driven
|
||
"ai_forecasting_supervision": "agents", # FTE optimization + supervisor span
|
||
"data_driven_profit_lift": "contacts", # 🔴 proxy — revenue-driven in the PDF;
|
||
# outbound volume is the nearest linear driver
|
||
"legacy_solution_savings": "agents", # $/agent-month licences (supervisors follow 10:1)
|
||
}
|
||
COST_DRIVERS: dict[str, str] = {
|
||
"amazon_connect_usage": "contacts", # per-minute/per-message consumption
|
||
"implementation_migration": "fixed", # project-based — does not scale
|
||
"ongoing_management": "fixed", # small fixed team
|
||
}
|
||
|
||
|
||
def scale_factor(driver: str, d: ClientDrivers) -> float:
|
||
"""Linear size ratio vs the composite for one driver kind."""
|
||
if driver == "contacts":
|
||
return d.annual_contacts_y1 / ASSUMPTIONS["annual_contacts_y1"]
|
||
if driver == "agents":
|
||
return d.agents_fte / ASSUMPTIONS["agents_fte"]
|
||
if driver == "fixed":
|
||
return 1.0
|
||
raise KeyError(f"Unknown driver: {driver!r}")
|
||
|
||
|
||
def growth_multiplier(year_index: int, growth_rate: float) -> float:
|
||
"""
|
||
Re-base the composite's Y1→Y3 trajectory on the client's growth.
|
||
|
||
The verbatim year values already embed the composite's 30% YoY growth;
|
||
dividing it out and compounding the client's rate preserves the
|
||
composite's *shape* while adopting the client's slope. Year 1 → 1.0.
|
||
"""
|
||
composite_g = ASSUMPTIONS["growth_rate"]
|
||
return ((1.0 + growth_rate) / (1.0 + composite_g)) ** (year_index - 1)
|
||
|
||
|
||
def overlay_rows(d: ClientDrivers = COMPOSITE) -> tuple[list[dict], list[dict]]:
|
||
"""
|
||
Deep-copied (benefits, costs) rows rescaled to the client's drivers.
|
||
|
||
Non-fixed rows: ``year_values[n] ×= scale_factor × growth_multiplier(n)``.
|
||
Fixed rows keep their year values and ``initial`` unchanged (no growth
|
||
re-base either — they are project/team costs, not volume costs).
|
||
Risk factors, labels, and notes are untouched.
|
||
"""
|
||
|
||
def _apply(rows: list[dict], drivers: dict[str, str]) -> list[dict]:
|
||
out = []
|
||
for raw in rows:
|
||
row = deepcopy(raw)
|
||
driver = drivers[row["field_key"]]
|
||
if driver != "fixed":
|
||
s = scale_factor(driver, d)
|
||
row["year_values"] = {
|
||
k: float(v) * s * growth_multiplier(int(k), d.growth_rate)
|
||
for k, v in row["year_values"].items()
|
||
}
|
||
if row.get("initial"):
|
||
row["initial"] = float(row["initial"]) * s
|
||
out.append(row)
|
||
return out
|
||
|
||
return (_apply(BENEFITS_VERBATIM, BENEFIT_DRIVERS),
|
||
_apply(COSTS_VERBATIM, COST_DRIVERS))
|