- 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
108 lines
3.5 KiB
Python
108 lines
3.5 KiB
Python
"""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
|