Migrate Genesys CX Cloud TEI study to the pattern; retire Streamlit app

studies/202512_GenesysCX -> studies/202512_TEI_Genesys_CX_Cloud,
rebuilt as pattern Variant 4 (TEI composite reproduction):

- teicalc/ self-contained engine: Forrester's tables as the never-edited
  verbatim anchor (incl. the p.14 typo note and the $0 AI-token line),
  generic model/scenarios/staging carried over from the Amazon Connect
  study, ClientDrivers overlay (agents / weekly interactions / revenue,
  flat composite so no growth re-base) with ai_tokens_annual as a direct
  input for the token line the published study models at $0
- one deliverable notebook (business_case.ipynb): widget-pair sidebar
  drivers incl. the AI-token price, published-vs-overlay KPI columns,
  cash-flow/waterfall/scenario charts, verification gate, backstage JSON
  data appendix
- gate + tests reproduce the published totals within $2: NPV $10.8M /
  ROI 266% (engine $10,783,466 / 265.79%; payback 3.3 months, not
  headlined in the PDF); 29 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,
  PALLADIUM_GENESYSCX_* keys, ATHENA_EXPECTED reconciliation) deleted;
  git history preserves it

With the last legacy study migrated, the retirement lands too:
- app/ (Streamlit UI) and core/notebook_helpers deleted; nothing else
  imported them
- streamlit stripped from pyproject extras, requirements.txt, Makefile;
  .env.example reduced to the Athena keys; 00_setup.ipynb and
  core/bootstrap.py repointed at the pattern studies
- root README reworked: self-contained studies + slim core/ Athena
  toolkit (tei_client, calculations, export, cli)

All suites green: Genesys 29, Amazon Connect 27, CTM 55, template 7,
root 58.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-09 16:38:51 -04:00
parent a420af230b
commit e88449d15a
54 changed files with 8462 additions and 6427 deletions

View File

@@ -0,0 +1,7 @@
"""Make teicalc importable even without the study venv active (the normal
setup is ``pip install -e ".[dev]"`` into the study-local ``.venv/``)."""
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))

View File

@@ -0,0 +1,104 @@
"""The verbatim anchor is Forrester's published record — pinned value by
value, and proven immutable under every engine code path."""
from copy import deepcopy
from teicalc import (
ASSUMPTIONS,
BENEFITS_VERBATIM,
COMPOSITE,
COSTS_VERBATIM,
PUBLISHED,
ClientDrivers,
apply_scenario,
compute_summary,
overlay_rows,
)
def _row(rows, key):
return next(r for r in rows if r["field_key"] == key)
def test_benefit_rows_verbatim():
assert [r["field_key"] for r in BENEFITS_VERBATIM] == [
"legacy_retirement",
"self_service_savings",
"agent_efficiency",
"agent_assist_sales",
]
expected = {
"legacy_retirement": ({"1": 680_000, "2": 930_000, "3": 930_000}, 0.05),
"self_service_savings": ({"1": 2_329_600, "2": 2_329_600, "3": 2_329_600}, 0.15),
"agent_efficiency": ({"1": 2_912_000, "2": 2_912_000, "3": 2_912_000}, 0.10),
"agent_assist_sales": ({"1": 600_000, "2": 600_000, "3": 600_000}, 0.05),
}
for key, (years, rf) in expected.items():
row = _row(BENEFITS_VERBATIM, key)
assert row["year_values"] == years
assert row["risk_adjustment"] == rf
assert row["table"] == "benefits"
def test_cost_rows_verbatim():
expected = {
"cx_cloud_licenses": ({"1": 840_000, "2": 840_000, "3": 840_000}, 0.05, 0),
"implementation": ({"1": 0, "2": 0, "3": 0}, 0.10, 1_190_000),
"ongoing_management": ({"1": 202_800, "2": 202_800, "3": 202_800}, 0.10, 0),
"genesys_ai_tokens": ({"1": 0, "2": 0, "3": 0}, 0.0, 0),
}
for key, (years, rf, initial) in expected.items():
row = _row(COSTS_VERBATIM, key)
assert row["year_values"] == years
assert row["risk_adjustment"] == rf
assert row["initial"] == initial
assert row["table"] == "costs"
def test_ai_token_line_is_anchored_at_zero():
"""The published study models $0 AI consumption — the study's blind spot,
preserved verbatim so the reproduction matches the published totals."""
row = _row(COSTS_VERBATIM, "genesys_ai_tokens")
assert all(v == 0 for v in row["year_values"].values())
assert row["initial"] == 0 and row["risk_adjustment"] == 0.0
assert "NOT in the published study" in row["notes"]
def test_assumptions_and_published():
assert ASSUMPTIONS["annual_revenue"] == 2_500_000_000
assert ASSUMPTIONS["agents_fte"] == 600
assert ASSUMPTIONS["concurrent_licenses"] == 400
assert ASSUMPTIONS["weekly_interactions"] == 80_000
assert ASSUMPTIONS["discount_rate"] == 0.10
assert ASSUMPTIONS["analysis_years"] == 3
assert PUBLISHED["benefits_pv"] == 14_840_638
assert PUBLISHED["costs_pv"] == 4_057_170
assert PUBLISHED["npv"] == 10_783_468
assert PUBLISHED["roi_pct"] == 266
assert "payback" not in str(sorted(PUBLISHED)) # study doesn't headline one
# The composite drivers ARE the anchor assumptions (tokens at $0).
assert COMPOSITE.agents_fte == ASSUMPTIONS["agents_fte"]
assert COMPOSITE.weekly_interactions == ASSUMPTIONS["weekly_interactions"]
assert COMPOSITE.annual_revenue == ASSUMPTIONS["annual_revenue"]
assert COMPOSITE.ai_tokens_annual == 0.0
assert COMPOSITE.discount_rate == ASSUMPTIONS["discount_rate"]
def test_anchor_is_never_mutated():
"""Exercise every engine code path, then prove the record unchanged."""
ben_snap = deepcopy(BENEFITS_VERBATIM)
cost_snap = deepcopy(COSTS_VERBATIM)
overlay_rows()
overlay_rows(ClientDrivers(agents_fte=137, weekly_interactions=5_000,
annual_revenue=9e9, ai_tokens_annual=450_000))
for scenario in ("conservative", "moderate", "aggressive"):
apply_scenario(BENEFITS_VERBATIM, scenario)
apply_scenario(COSTS_VERBATIM, scenario)
compute_summary(BENEFITS_VERBATIM, COSTS_VERBATIM, 0.10)
compute_summary(BENEFITS_VERBATIM, COSTS_VERBATIM, 0.08)
assert BENEFITS_VERBATIM == ben_snap
assert COSTS_VERBATIM == cost_snap

View File

@@ -0,0 +1,122 @@
"""Engine pins — every number hand-checked before pinning.
RA_benefit = v×(1rf), RA_cost = v×(1+rf), PV = Σ RA_n/(1.1)^n, initial
undiscounted. The composite reproduction lands within $2 of the published
Financial Summary (benefits PV $1.19 low, costs PV $0.40 high) — pinned
both engine-exact (±$1) and against PUBLISHED (±$5). Forrester does not
headline a payback for this study; the engine computes 3.3 months.
"""
import pytest
from teicalc import (
BENEFITS_VERBATIM,
COSTS_VERBATIM,
PUBLISHED,
X_LABELS,
YEAR_INDEX,
YEARS,
by_calendar,
compute_summary,
discount_factor,
money,
npv,
payback_label,
payback_months,
payback_years,
roi_pct,
)
# Hand-checked risk-adjusted PVs per row (see module docstring).
ROW_PVS = {
"legacy_retirement": 1_981_224.64,
"self_service_savings": 4_924_364.84,
"agent_efficiency": 6_517_541.70,
"agent_assist_sales": 1_417_505.63,
"cx_cloud_licenses": 2_193_403.46,
"implementation": 1_309_000.00,
"ongoing_management": 554_766.94,
"genesys_ai_tokens": 0.00,
}
@pytest.fixture(scope="module")
def composite():
return compute_summary(BENEFITS_VERBATIM, COSTS_VERBATIM, 0.10)
def test_calendar_mapping():
assert YEARS == [2026, 2027, 2028]
assert YEAR_INDEX == {2026: 1, 2027: 2, 2028: 3}
assert X_LABELS == ["Initial", "2026", "2027", "2028"]
assert by_calendar({"1": 10, "2": 20, "3": 30}) == {2026: 10, 2027: 20, 2028: 30}
def test_primitives():
assert discount_factor(0, 0.10) == 1.0
assert discount_factor(1, 0.10) == pytest.approx(1 / 1.1)
assert npv([110], 0.10) == pytest.approx(100)
assert npv([110], 0.10, initial=-50) == pytest.approx(50)
assert roi_pct(14_840_638, 4_057_170) == pytest.approx(265.79, abs=0.1)
assert roi_pct(100, 0) == 0.0
assert money(10_783_466) == "$10.8M"
assert money(-250_000) == "-$250K"
def test_payback_edges():
assert payback_years(0, [100]) == 0.0
assert payback_years(500, []) is None
assert payback_years(500, [-100, 200]) is None
assert payback_years(300, [-100, 400]) == pytest.approx(2.0)
assert payback_months(100, [1_200]) == pytest.approx(1.0)
assert payback_label(None) == "beyond 2028"
assert payback_label(0.0) == "immediate"
assert payback_label(3.3337) == "3.3 months (~Apr 2026)"
assert payback_label(14.2) == "14.2 months (~Mar 2027)"
def test_per_row_pvs(composite):
rows = composite["rows"]["benefits"] + composite["rows"]["costs"]
assert len(rows) == 8
for row in rows:
assert row["pv"] == pytest.approx(ROW_PVS[row["field_key"]], abs=1)
def test_composite_totals_engine_exact(composite):
assert composite["benefits_pv"] == pytest.approx(14_840_636.81, abs=1)
assert composite["costs_pv"] == pytest.approx(4_057_170.40, abs=1)
assert composite["npv"] == pytest.approx(10_783_466.42, abs=1)
assert composite["roi_pct"] == pytest.approx(265.7879, abs=0.01)
assert composite["payback_months"] == pytest.approx(3.3337, abs=0.001)
assert composite["initial_costs"] == pytest.approx(1_309_000, abs=0.01)
def test_composite_reproduces_published(composite):
assert composite["benefits_pv"] == pytest.approx(PUBLISHED["benefits_pv"], abs=5)
assert composite["costs_pv"] == pytest.approx(PUBLISHED["costs_pv"], abs=5)
assert composite["npv"] == pytest.approx(PUBLISHED["npv"], abs=5)
assert round(composite["roi_pct"]) == PUBLISHED["roi_pct"]
assert composite["payback_label"] == "3.3 months (~Apr 2026)"
def test_yearly_schedules(composite):
assert composite["benefits_by_year"][2026] == pytest.approx(5_816_960.00, abs=0.01)
assert composite["benefits_by_year"][2027] == pytest.approx(6_054_460.00, abs=0.01)
assert composite["benefits_by_year"][2028] == pytest.approx(6_054_460.00, abs=0.01)
for y in YEARS:
assert composite["costs_by_year"][y] == pytest.approx(1_105_080.00, abs=0.01)
assert composite["cumulative_net_by_year"][2026] == pytest.approx(3_402_880.00, abs=0.01)
assert composite["cumulative_net_by_year"][2028] == pytest.approx(13_301_640.00, abs=0.01)
def test_cross_foots(composite):
assert composite["npv"] == pytest.approx(
composite["benefits_pv"] - composite["costs_pv"], abs=0.01)
for y in YEARS:
assert composite["net_by_year"][y] == pytest.approx(
composite["benefits_by_year"][y] - composite["costs_by_year"][y], abs=0.01)
assert composite["cumulative_net_by_year"][2028] == pytest.approx(
sum(composite["net_by_year"].values()) - composite["initial_costs"], abs=0.01)
for table, total in (("benefits", "benefits_pv"), ("costs", "costs_pv")):
assert sum(r["pv"] for r in composite["rows"][table]) == pytest.approx(
composite[total], abs=0.01)

View File

@@ -0,0 +1,95 @@
"""Client-overlay pins — identity at the composite, linear per-driver
scaling, the direct AI-token input, and copy semantics."""
import dataclasses
import pytest
from teicalc import (
BENEFIT_DRIVERS,
BENEFITS_VERBATIM,
COMPOSITE,
COST_DRIVERS,
COSTS_VERBATIM,
ClientDrivers,
compute_summary,
overlay_rows,
scale_factor,
)
def _row(rows, key):
return next(r for r in rows if r["field_key"] == key)
def test_identity_at_composite():
"""overlay_rows(COMPOSITE) reproduces the verbatim study to the cent."""
ob, oc = overlay_rows(COMPOSITE)
got = compute_summary(ob, oc, 0.10)
want = compute_summary(BENEFITS_VERBATIM, COSTS_VERBATIM, 0.10)
assert got["benefits_pv"] == pytest.approx(want["benefits_pv"], abs=0.01)
assert got["costs_pv"] == pytest.approx(want["costs_pv"], abs=0.01)
assert got["npv"] == pytest.approx(want["npv"], abs=0.01)
def test_driver_map_covers_every_row():
assert set(BENEFIT_DRIVERS) == {r["field_key"] for r in BENEFITS_VERBATIM}
assert set(COST_DRIVERS) == {r["field_key"] for r in COSTS_VERBATIM}
def test_scale_factor():
d = ClientDrivers(agents_fte=300, weekly_interactions=160_000,
annual_revenue=5_000_000_000)
assert scale_factor("agents", d) == pytest.approx(0.5)
assert scale_factor("interactions", d) == pytest.approx(2.0)
assert scale_factor("revenue", d) == pytest.approx(2.0)
assert scale_factor("fixed", d) == 1.0
with pytest.raises(KeyError):
scale_factor("contacts", d)
def test_half_agents_halves_agent_rows_only():
ob, oc = overlay_rows(ClientDrivers(agents_fte=300))
assert _row(ob, "legacy_retirement")["year_values"]["1"] == pytest.approx(340_000)
assert _row(oc, "cx_cloud_licenses")["year_values"]["1"] == pytest.approx(420_000)
# Interaction-, revenue-driven, and fixed rows unmoved.
assert _row(ob, "self_service_savings")["year_values"]["1"] == pytest.approx(2_329_600)
assert _row(ob, "agent_assist_sales")["year_values"]["1"] == pytest.approx(600_000)
assert _row(oc, "implementation")["initial"] == 1_190_000
def test_double_interactions_doubles_volume_rows_only():
ob, oc = overlay_rows(ClientDrivers(weekly_interactions=160_000))
assert _row(ob, "self_service_savings")["year_values"]["1"] == pytest.approx(4_659_200)
assert _row(ob, "agent_efficiency")["year_values"]["1"] == pytest.approx(5_824_000)
assert _row(ob, "legacy_retirement")["year_values"]["1"] == pytest.approx(680_000)
assert _row(oc, "cx_cloud_licenses")["year_values"]["1"] == pytest.approx(840_000)
def test_double_revenue_doubles_agent_assist_only():
ob, _ = overlay_rows(ClientDrivers(annual_revenue=5_000_000_000))
assert _row(ob, "agent_assist_sales")["year_values"]["1"] == pytest.approx(1_200_000)
assert _row(ob, "self_service_savings")["year_values"]["1"] == pytest.approx(2_329_600)
def test_ai_tokens_direct_input():
"""The token line takes the negotiated annual figure directly (rf 0.0),
adding annual × Σ1/1.1ⁿ = 250,000 × 2.48685… ≈ $621,713 to costs PV."""
_, oc = overlay_rows(ClientDrivers(ai_tokens_annual=250_000))
tokens = _row(oc, "genesys_ai_tokens")
assert tokens["year_values"] == {"1": 250_000.0, "2": 250_000.0, "3": 250_000.0}
base = compute_summary(BENEFITS_VERBATIM, COSTS_VERBATIM, 0.10)
ob, oc = overlay_rows(ClientDrivers(ai_tokens_annual=250_000))
got = compute_summary(ob, oc, 0.10)
assert got["costs_pv"] - base["costs_pv"] == pytest.approx(621_713.00, abs=1)
assert got["benefits_pv"] == pytest.approx(base["benefits_pv"], abs=0.01)
def test_drivers_frozen_and_rows_are_copies():
with pytest.raises(dataclasses.FrozenInstanceError):
COMPOSITE.agents_fte = 1 # type: ignore[misc]
ob, oc = overlay_rows(COMPOSITE)
ob[0]["year_values"]["1"] = -1
oc[0]["year_values"]["1"] = -1
assert BENEFITS_VERBATIM[0]["year_values"]["1"] == 680_000
assert COSTS_VERBATIM[0]["year_values"]["1"] == 840_000

View File

@@ -0,0 +1,74 @@
"""Scenario pins — hand-checked composite results per scenario, clamp
behaviour, and copy semantics."""
import pytest
from teicalc import (
BENEFITS_VERBATIM,
COSTS_VERBATIM,
SCENARIOS,
apply_scenario,
compute_summary,
)
def _summary(scenario):
return compute_summary(
apply_scenario(BENEFITS_VERBATIM, scenario),
apply_scenario(COSTS_VERBATIM, scenario),
0.10,
)
def test_scenario_definitions():
assert SCENARIOS == {
"conservative": {"adoption": 0.80, "risk_delta": 0.10},
"moderate": {"adoption": 1.00, "risk_delta": 0.00},
"aggressive": {"adoption": 1.15, "risk_delta": -0.05},
}
def test_moderate_is_identity():
got = _summary("moderate")
want = compute_summary(BENEFITS_VERBATIM, COSTS_VERBATIM, 0.10)
assert got["benefits_pv"] == pytest.approx(want["benefits_pv"], abs=0.01)
assert got["costs_pv"] == pytest.approx(want["costs_pv"], abs=0.01)
def test_conservative_pins():
s = _summary("conservative")
assert s["benefits_pv"] == pytest.approx(10_543_493.91, abs=1)
assert s["costs_pv"] == pytest.approx(3_026_631.40, abs=1)
assert s["npv"] == pytest.approx(7_516_862.51, abs=1)
assert s["roi_pct"] == pytest.approx(248.36, abs=0.01)
assert s["payback_months"] == pytest.approx(3.464, abs=0.001)
def test_aggressive_pins():
s = _summary("aggressive")
assert s["benefits_pv"] == pytest.approx(18_021_962.25, abs=1)
assert s["costs_pv"] == pytest.approx(4_883_285.09, abs=1)
assert s["npv"] == pytest.approx(13_138_677.16, abs=1)
assert s["roi_pct"] == pytest.approx(269.05, abs=0.01)
assert s["payback_months"] == pytest.approx(3.294, abs=0.001)
def test_risk_delta_clamps_at_zero():
"""Conservative subtracts 0.10 from cost risk; every cost rf clamps to 0
(licenses 0.05, implementation 0.10, ongoing 0.10, tokens 0.0)."""
rows = apply_scenario(COSTS_VERBATIM, "conservative")
assert all(r["risk_adjustment"] == 0.0 for r in rows)
impl = next(r for r in rows if r["field_key"] == "implementation")
assert impl["initial"] == pytest.approx(1_190_000 * 0.80) # adoption scales initial
def test_unknown_scenario_raises():
with pytest.raises(KeyError):
apply_scenario(BENEFITS_VERBATIM, "wildly_optimistic")
def test_inputs_not_mutated():
apply_scenario(BENEFITS_VERBATIM, "aggressive")
apply_scenario(COSTS_VERBATIM, "conservative")
assert BENEFITS_VERBATIM[0]["year_values"]["1"] == 680_000
assert COSTS_VERBATIM[1]["initial"] == 1_190_000

View File

@@ -0,0 +1,15 @@
"""Stage/backstage detection — Mercury kernels carry MERCURY_CONFIG_DIR."""
from teicalc import staging
def test_backstage_prints_only_off_stage(monkeypatch, capsys):
monkeypatch.delenv("MERCURY_CONFIG_DIR", raising=False)
assert not staging.on_stage()
staging.backstage("visible")
assert capsys.readouterr().out == "visible\n"
monkeypatch.setenv("MERCURY_CONFIG_DIR", "/tmp/app")
assert staging.on_stage()
staging.backstage("hidden")
assert capsys.readouterr().out == ""