- 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
141 lines
4.8 KiB
Python
141 lines
4.8 KiB
Python
"""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)
|