Files
palladium/studies/202512_GenesysCX/ctm-token-calculator/tests/test_benefit_model.py
Robert Helewka 64fb83257d 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
2026-06-10 14:26:49 -04:00

108 lines
3.5 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.
"""Benefit engine."""
from __future__ import annotations
import pytest
from tokencalc.benefit_model import (
calculate_acw_summarization_benefit,
calculate_email_ai_benefit,
calculate_total_benefit,
)
from tokencalc.defaults import CTM_DEFAULT_FEATURE_SCOPES, CTM_DEFAULT_SITES
from tokencalc.inputs import WORKING_SECONDS_PER_YEAR, FeatureScope, SiteInput
ALL_SITES = [s.site_name for s in CTM_DEFAULT_SITES]
def _small_site() -> SiteInput:
return SiteInput(
"Small", "US", agents=10, supervisors=1,
voice_volume_monthly=10_000, email_volume_monthly=1_000,
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=74_880, # → $0.01/second exactly
fully_loaded_supervisor_cost_annual=95_000,
)
def test_acw_benefit_hand_check():
"""10,000 calls × 12 × 70% eligible × 60s ACW × 40% reduction ×
50% Y1 realization × $0.01/s = $10,080."""
site = _small_site()
assert site.agent_cost_per_second == pytest.approx(0.01)
df = calculate_acw_summarization_benefit(
[site], FeatureScope("Agent Copilot", ["Small"]), "realistic", year=1,
)
expected = 10_000 * 12 * 0.70 * 60 * 0.40 * 0.50 * 0.01
assert df["annual_value"].sum() == pytest.approx(expected)
def test_email_benefit_split():
site = _small_site()
df = calculate_email_ai_benefit(
[site], FeatureScope("Email AI (Auto-Respond)", ["Small"]),
"realistic", year=1,
)
lines = set(df["benefit_line"])
assert lines == {
"Email Auto-Respond (displaced handling)",
"Email Auto-Suggest (drafting time)",
}
# auto-respond: 1,000×12 × 20% × 600s × 50% × $0.01 = $7,200
respond = df[df["benefit_line"].str.contains("Respond")]["annual_value"].sum()
assert respond == pytest.approx(7_200)
def test_scenarios_produce_distinct_benefits():
totals = {
name: calculate_total_benefit(
CTM_DEFAULT_SITES, CTM_DEFAULT_FEATURE_SCOPES, name, year=2
)["annual_value"].sum()
for name in ("floor", "realistic", "stretch")
}
assert totals["floor"] < totals["realistic"] < totals["stretch"]
def test_claim_exceeds_realistic():
realistic = calculate_total_benefit(
CTM_DEFAULT_SITES, CTM_DEFAULT_FEATURE_SCOPES, "realistic", year=1,
params="realistic",
)["annual_value"].sum()
claim = calculate_total_benefit(
CTM_DEFAULT_SITES, CTM_DEFAULT_FEATURE_SCOPES, "realistic", year=1,
params="claim",
)["annual_value"].sum()
assert claim > realistic
def test_benefits_ramp_by_year():
by_year = [
calculate_total_benefit(
CTM_DEFAULT_SITES, CTM_DEFAULT_FEATURE_SCOPES, "realistic", year=y
)["annual_value"].sum()
for y in (1, 2, 3)
]
assert by_year[0] < by_year[1] < by_year[2]
def test_zero_volume_site_is_safe():
site = SiteInput(
"Empty", "US", agents=0, supervisors=0,
voice_volume_monthly=0, 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=0,
fully_loaded_agent_cost_annual=0,
fully_loaded_supervisor_cost_annual=0,
)
df = calculate_total_benefit(
[site], [FeatureScope("Agent Copilot", ["Empty"])], "realistic", year=1,
)
assert df["annual_value"].sum() == 0
def test_working_seconds_constant():
assert WORKING_SECONDS_PER_YEAR == 2_080 * 3_600