Files
palladium/studies/202602_TEI_Amazon_Connect/teicalc/overlay.py
Robert Helewka a420af230b Migrate Amazon Connect TEI study to the Mercury Notebook Pattern
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>
2026-07-09 14:29:46 -04:00

108 lines
4.4 KiB
Python
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.
"""
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))