feat: add GenesysCX study and fix Streamlit chart key collisions

- Add 202512_GenesysCX TEI study (config, seed data, notebooks, README)
  with NPV $10.8M / ROI 266% including AI-token cost line
- Add explicit `key` parameter to all chart wrappers in app/components
  to prevent StreamlitDuplicateElementId errors when the same figure
  type renders across Summary/Benefits/Costs tabs
- Render benefits bar and cost pie charts on their respective tabs
- Add benefits_vs_costs_by_year chart wrapper
This commit is contained in:
2026-06-10 14:26:49 -04:00
parent ecd164ee6d
commit 64fb83257d
34 changed files with 12902 additions and 39 deletions

View File

@@ -0,0 +1,140 @@
"""Cost engine — including the spec's acceptance numbers."""
from __future__ import annotations
import pytest
from tokencalc.cost_model import (
calculate_consumption_ai_cost,
calculate_per_user_ai_cost,
calculate_platform_license_cost,
calculate_total_cost,
)
from tokencalc.defaults import (
CONTRACTED_NAMED_USERS,
CTM_DEFAULT_FEATURE_SCOPES,
CTM_DEFAULT_SITES,
DEFAULT_METERS,
DEFAULT_PRICING,
)
from tokencalc.inputs import FeatureScope, SiteInput
from tokencalc.scenarios import get_scenario
ALL_SITES = [s.site_name for s in CTM_DEFAULT_SITES]
def _scope(feature, sites=None, **kw):
return FeatureScope(feature, sites or ALL_SITES, **kw)
def test_default_sites_match_contracted_users():
assert sum(s.named_users for s in CTM_DEFAULT_SITES) == CONTRACTED_NAMED_USERS
def test_sta_acceptance_number():
"""2,088 users × 30 tokens × 12 months × $1 = $751,680."""
df = calculate_per_user_ai_cost(
CTM_DEFAULT_SITES, _scope("Speech & Text Analytics"),
DEFAULT_METERS["Speech & Text Analytics"], DEFAULT_PRICING,
)
assert df["annual_cost"].sum() == pytest.approx(751_680)
def test_agent_copilot_acceptance_number():
"""2,088 users × 40 tokens × 12 months × $1 = $1,002,240."""
df = calculate_per_user_ai_cost(
CTM_DEFAULT_SITES, _scope("Agent Copilot"),
DEFAULT_METERS["Agent Copilot"], DEFAULT_PRICING,
)
assert df["annual_cost"].sum() == pytest.approx(1_002_240)
def test_per_user_not_active_before_phase():
df = calculate_per_user_ai_cost(
CTM_DEFAULT_SITES, _scope("AI Translate", phase=3),
DEFAULT_METERS["AI Translate"], DEFAULT_PRICING, year=2,
)
assert df["annual_cost"].sum() == 0
def test_copilot_covers_supervisor_summary():
"""Rule 1: AI Summary cost is zero at Copilot sites."""
scenario = get_scenario("realistic")
total = calculate_total_cost(
CTM_DEFAULT_SITES,
[
_scope("Agent Copilot"),
_scope("AI Summary & Insights"),
],
DEFAULT_METERS, DEFAULT_PRICING, scenario, year=1,
include_platform=False,
)
summary_row = total[total["cost_line"] == "AI Summary & Insights"].iloc[0]
assert summary_row["annual_cost"] == 0
# Without Copilot the same line costs real money.
total2 = calculate_total_cost(
CTM_DEFAULT_SITES,
[_scope("AI Summary & Insights")],
DEFAULT_METERS, DEFAULT_PRICING, scenario, year=1,
include_platform=False,
)
assert total2[total2["cost_line"] == "AI Summary & Insights"].iloc[0][
"annual_cost"
] > 0
def test_consumption_tokens_rounded_up_monthly():
"""Rule 2: ceil on monthly site token totals."""
site = SiteInput(
"Tiny", "US", agents=5, supervisors=0,
voice_volume_monthly=100, email_volume_monthly=0,
chat_volume_monthly=0, sms_volume_monthly=0,
voice_aht_seconds=300, email_aht_seconds=600,
chat_aht_seconds=480, voice_acw_seconds=60,
fully_loaded_agent_cost_annual=65_000,
fully_loaded_supervisor_cost_annual=95_000,
)
# realistic: 100 calls × 35% × 1.5 min = 52.5 min × (1/17) = 3.088
# tokens × 70% Y1 ramp applied to units → 36.75 min → 2.16 tokens → ceil 3
df = calculate_consumption_ai_cost(
[site], FeatureScope("Voice Bot", ["Tiny"]),
DEFAULT_METERS["Voice Bot"], "realistic", DEFAULT_PRICING, year=1,
)
assert df.iloc[0]["tokens_monthly"] == 3
assert df.iloc[0]["annual_cost"] == pytest.approx(3 * 12 * 1.0)
def test_regional_pricing_not_hardcoded():
pricing = dict(DEFAULT_PRICING)
from tokencalc.meters import TokenPricing
pricing["APAC"] = TokenPricing(region="APAC", list_rate_per_token=2.0)
apac_site = next(s for s in CTM_DEFAULT_SITES if s.region_pricing == "APAC")
df = calculate_per_user_ai_cost(
[apac_site], _scope("Speech & Text Analytics", [apac_site.site_name]),
DEFAULT_METERS["Speech & Text Analytics"], pricing,
)
expected = apac_site.named_users * 30 * 12 * 2.0
assert df["annual_cost"].sum() == pytest.approx(expected)
def test_year1_consumption_ramp_default_70pct():
sc = get_scenario("realistic")
assert sc.cost_realization(1) == pytest.approx(0.70)
assert sc.cost_realization(2) == 1.0
def test_platform_license_cost():
df = calculate_platform_license_cost(CTM_DEFAULT_SITES)
expected = CONTRACTED_NAMED_USERS * 111.28 * 12
assert df["annual_cost"].sum() == pytest.approx(expected)
def test_total_cost_default_scopes_runs_all_years():
for year in (1, 2, 3):
df = calculate_total_cost(
CTM_DEFAULT_SITES, CTM_DEFAULT_FEATURE_SCOPES,
DEFAULT_METERS, DEFAULT_PRICING, "realistic", year,
)
assert (df["annual_cost"] >= 0).all()
assert {"cost_line", "scope", "annual_cost", "confidence"} <= set(df.columns)