docs: introduce Mercury Notebook Deliverable Pattern

This commit is contained in:
2026-07-08 13:43:12 -04:00
parent a991879061
commit c3260ae7b8
55 changed files with 12036 additions and 0 deletions

View File

@@ -0,0 +1,137 @@
"""Appendix-4 corrected business case — hand-check acceptance numbers."""
from __future__ import annotations
import datetime as dt
import math
import pytest
from tokencalc import appendix4 as a4
from tokencalc.defaults import CTM_DEFAULT_SITES, DEFAULT_METERS, DEFAULT_PRICING
SITES = list(CTM_DEFAULT_SITES)
def test_verbatim_crossfoots_to_slide_totals():
df = a4.verbatim_dataframe()
for r, expect in a4.SLIDE_TOTALS["regional_3yr"].items():
got = df.loc[df.region == r, "three_yr"].sum()
assert abs(got - expect) <= a4.crossfoot_tolerance(expect), r
for c, expect in a4.SLIDE_TOTALS["capability_3yr"].items():
got = df.loc[df.capability == c, "three_yr"].sum()
assert abs(got - expect) <= a4.crossfoot_tolerance(expect), c
assert abs(df["three_yr"].sum() - a4.SLIDE_TOTALS["total_3yr"]) <= \
a4.crossfoot_tolerance(a4.SLIDE_TOTALS["total_3yr"])
def test_benefits_phase_on_the_deck_schedule():
_, _, benefit_rollout = a4.build_rollouts(SITES)
long = a4.benefits_by_year(benefit_rollout)
by_year = long.groupby("year")["benefit"].sum()
assert by_year[2026] == 0.0, "2026 must be $0 under Genesys's own schedule"
# Scaling at the finest grain reproduces every verbatim 3-yr value exactly.
for (region, cap), (_, three_yr) in a4.VERBATIM_BENEFITS.items():
got = long.query("region == @region and capability == @cap")["benefit"].sum()
assert got == pytest.approx(three_yr)
def test_ramp_zeroes_year_one_licences():
assert a4.licence_costs_by_year(12) == {2026: 0.0, 2027: 4_300_000.0,
2028: 4_300_000.0}
assert a4.licence_costs_by_year(0)[2026] == 4_300_000.0
assert a4.licence_costs_by_year(18)[2027] == pytest.approx(4_300_000 * 6 / 12)
# The order form's ramp is 6 months — licences bill from July 2026.
assert a4.DEFAULT_RAMP_MONTHS == 6
assert a4.licence_costs_by_year() == {2026: 2_150_000.0, 2027: 4_300_000.0,
2028: 4_300_000.0}
def test_current_state_run_off():
cs = a4.current_state_inputs(SITES)
assert cs["annual_cost"].sum() == pytest.approx(7_300_000)
by_year = a4.current_costs_by_year(cs)
assert by_year == {2026: pytest.approx(7_300_000),
2027: pytest.approx(7_300_000), 2028: 0.0}
cs.loc["NA", "contract_termination"] = dt.date(2028, 6, 30)
assert a4.current_costs_by_year(cs)[2028] == pytest.approx(
cs.loc["NA", "annual_cost"] * 6 / 12)
def test_token_hand_checks():
token_ro, email_ro, _ = a4.build_rollouts(SITES)
core, email = a4.build_scopes(SITES, copilot_includes_asia=False)
meters = {**DEFAULT_METERS, "Email AI (Auto-Respond)": a4.autorespond_meter(0.05)}
long = a4.token_costs_by_year(SITES, meters, DEFAULT_PRICING,
a4.claim_scenario(0.255), core, email,
token_ro, email_ro)
# STA 2028: NAM/AUZ/EMEA × 12 months + ASIA × 10 months, by hand.
sta = long.query("cost_line == 'Speech & Text Analytics [named]'")
assert sta.query("year == 2028")["annual_cost"].sum() == pytest.approx(715_800)
# Agent Copilot 2028 (ASIA off): 1,490 users × 40 tokens × 12 months.
cp = long.query("cost_line == 'Agent Copilot [named]' and year == 2028")
assert cp["annual_cost"].sum() == pytest.approx(1_490 * 40 * 12)
# Rule 1: Copilot covers AI Summary at Copilot sites.
assert (long.query("cost_line == 'AI Summary & Insights'")["annual_cost"] == 0).all()
# Nothing is live in 2026.
assert long.query("year == 2026")["annual_cost"].sum() == 0
# PR NAM steady-month tokens.
assert math.ceil(
1_214_358 * DEFAULT_METERS["Predictive Routing"].tokens_per_unit) == 71_433
def test_impl_costs_reconcile_with_v2_doc():
_, impl_y, kb_y, steady_y = a4.build_impl_costs(SITES, "mid", 225.0,
include_kb=True)
assert sum(impl_y.values()) == pytest.approx(5_850 * 225) # V2's "$1.3M"
assert sum(kb_y.values()) == pytest.approx(1_000 * 225)
assert steady_y == {2026: 0.0, 2027: pytest.approx(700 * 225),
2028: pytest.approx(700 * 225)}
# Impl spend is fully booked by each region's implementation month.
assert a4.impl_year_fractions(18) == pytest.approx([12 / 18, 6 / 18, 0.0])
assert a4.impl_year_fractions(27) == pytest.approx([12 / 27, 12 / 27, 3 / 27])
def test_case_flows_and_kpis():
benefits = {2026: 0.0, 2027: 2_000_000.0, 2028: 12_000_000.0}
costs = {2026: 10_000_000.0, 2027: 13_000_000.0, 2028: 8_000_000.0}
inc, net = a4.case_flows(costs, benefits)
assert inc == {2026: pytest.approx(2_700_000),
2027: pytest.approx(5_700_000),
2028: pytest.approx(700_000)}
for y in a4.YEARS:
assert net[y] == pytest.approx(benefits[y] - inc[y])
kpis = a4.case_kpis(inc, net)
assert kpis["benefits_3yr"] == pytest.approx(sum(benefits.values()))
assert kpis["net_3yr"] == pytest.approx(sum(net.values()))
assert kpis["roi"] == pytest.approx(kpis["net_3yr"] / kpis["incremental_cost_3yr"])
assert kpis["discount_rate"] == 0.135
# Net cost saving → ROI undefined.
inc2 = {y: -1.0 for y in a4.YEARS}
net2 = {y: benefits[y] + 1.0 for y in a4.YEARS}
assert a4.case_kpis(inc2, net2)["roi"] is None
def test_contracted_overlays_verbatim():
assert a4.tco("ccaas_annual") == 3_200_000 # signed contract
assert a4.TCO_VERBATIM["ccaas_annual"] == 4_300_000 # deck record intact
assert a4.tco("current_annual") == a4.TCO_VERBATIM["current_annual"]
assert a4.licence_costs_by_year(12, a4.tco("ccaas_annual"))[2027] == 3_200_000
def test_sow_milestones_and_managed_services():
assert a4.PS_CONTRACTED_TOTAL == pytest.approx(2_025_446.48)
for m in a4.PS_MILESTONES: # amounts match the shares
assert m["amount"] == pytest.approx(m["share"] * a4.PS_CONTRACTED_TOTAL,
abs=0.01)
ps = a4.ps_costs_by_year(contracted=True) # 50/50 across 2026-27
assert ps[2026] == pytest.approx(607_633.94 + 405_089.30 + 167_000)
assert ps[2027] == pytest.approx(607_633.94 + 405_089.30)
assert ps[2028] == 0.0
# The deck's verbatim year-1 lump stays intact for the as-pitched frame.
assert a4.ps_costs_by_year() == {2026: 2_567_000, 2027: 0.0, 2028: 0.0}
# Managed services bill from the month after MCX go-live (Sep 30 → Oct).
ms = a4.managed_services_by_year()
assert ms[2026] == pytest.approx(410_918.40 * 3 / 12)
assert ms[2027] == pytest.approx(410_918.40)
assert ms[2028] == pytest.approx(410_918.40)

View File

@@ -0,0 +1,237 @@
"""Benefit engine."""
from __future__ import annotations
import pytest
from tokencalc.benefit_model import (
calculate_acw_summarization_benefit,
calculate_email_ai_benefit,
calculate_total_benefit,
calculate_va_deflection_benefit,
)
from tokencalc.defaults import CTM_DEFAULT_FEATURE_SCOPES, CTM_DEFAULT_SITES
from tokencalc.inputs import WORKING_SECONDS_PER_YEAR, FeatureScope, SiteInput
from tokencalc.scenarios import BENEFIT_PARAMS
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,
)
# Auto-Suggest is not a separate line — it lives inside Agent Copilot.
lines = set(df["benefit_line"])
assert lines == {"Email Auto-Respond (displaced handling)"}
# 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
# ── Virtual Agent deflection tests ───────────────────────────────────────────
def test_va_bot_deflection_hand_check():
"""Voice Bot: 10,000 calls/mo × 12 × 35% bot_rate × 300s AHT
× 50% Y1 realization × realization_factor × $0.01/s.
realistic realization_factor = 0.70 × 0.80 × (1 0.05) = 0.532
"""
site = _small_site()
df = calculate_va_deflection_benefit(
[site],
FeatureScope("Voice Bot", ["Small"], deflection_target=0.35),
"realistic",
year=1,
params="realistic",
)
completion = BENEFIT_PARAMS["va_completion_rate"]["realistic"]
labour = BENEFIT_PARAMS["va_labour_realization"]["realistic"]
callback = BENEFIT_PARAMS["va_callback_discount"]["realistic"]
real_factor = completion * labour * (1.0 - callback)
expected = (
10_000 * 12 # annual calls
* 0.35 # bot deflection rate
* 300 # AHT seconds
* 0.50 # Y1 scenario realization
* real_factor # completion × labour × (1 callback)
* 0.01 # labour rate per second
)
assert df["annual_value"].sum() == pytest.approx(expected)
def test_va_agentic_deflection_uses_residual():
"""Agentic VA must operate on the residual (1 bot_rate) call pool,
not the full volume.
With bot_rate=0.35 and va_rate=0.15:
residual = 10,000 × (1 0.35) = 6,500 calls/mo
va_deflected = 6,500 × 0.15 = 975 calls/mo
"""
site = _small_site()
df = calculate_va_deflection_benefit(
[site],
FeatureScope("Agentic Virtual Agent", ["Small"], deflection_target=0.15),
"realistic",
year=1,
params="realistic",
)
completion = BENEFIT_PARAMS["va_completion_rate"]["realistic"]
labour = BENEFIT_PARAMS["va_labour_realization"]["realistic"]
callback = BENEFIT_PARAMS["va_callback_discount"]["realistic"]
real_factor = completion * labour * (1.0 - callback)
# realistic scenario: voice_bot_deflection = 0.35
bot_rate = 0.35
va_rate = 0.15
expected = (
10_000 * 12 # annual calls
* (1.0 - bot_rate) * va_rate # residual × va_rate (layered)
* 300 # AHT seconds
* 0.50 # Y1 scenario realization
* real_factor
* 0.01
)
assert df["annual_value"].sum() == pytest.approx(expected)
def test_va_no_double_count():
"""Combined bot + VA benefit must be less than the naive additive sum.
Naive (wrong): volume × (bot_rate + va_rate) × AHT × ...
Correct (layered): volume × (bot_rate + (1bot_rate)×va_rate) × AHT × ...
With bot=35%, va=15%:
naive total deflection = 50%
layered total deflection = 35% + 65%×15% = 44.75%
"""
site = _small_site()
bot_scope = FeatureScope("Voice Bot", ["Small"], deflection_target=0.35)
va_scope = FeatureScope("Agentic Virtual Agent", ["Small"], deflection_target=0.15)
bot_df = calculate_va_deflection_benefit([site], bot_scope, "realistic", year=1)
va_df = calculate_va_deflection_benefit([site], va_scope, "realistic", year=1)
combined = bot_df["annual_value"].sum() + va_df["annual_value"].sum()
# Naive additive (the old broken model): both on full volume
completion = BENEFIT_PARAMS["va_completion_rate"]["realistic"]
labour = BENEFIT_PARAMS["va_labour_realization"]["realistic"]
callback = BENEFIT_PARAMS["va_callback_discount"]["realistic"]
real_factor = completion * labour * (1.0 - callback)
naive = (
10_000 * 12 * (0.35 + 0.15) * 300 * 0.50 * real_factor * 0.01
)
assert combined < naive, (
f"Combined layered benefit ({combined:.2f}) should be less than "
f"naive additive ({naive:.2f}) — double-count not fixed"
)
# Also verify the exact layered total
layered_deflection = 0.35 + (1.0 - 0.35) * 0.15 # = 0.4475
expected_combined = (
10_000 * 12 * layered_deflection * 300 * 0.50 * real_factor * 0.01
)
assert combined == pytest.approx(expected_combined)
def test_va_claim_params_reproduce_no_haircut():
"""params='claim' must apply zero haircuts (all factors = 1.0),
reproducing the original Genesys ROI-doc assumption."""
site = _small_site()
df_claim = calculate_va_deflection_benefit(
[site],
FeatureScope("Voice Bot", ["Small"], deflection_target=0.35),
"realistic",
year=1,
params="claim",
)
df_realistic = calculate_va_deflection_benefit(
[site],
FeatureScope("Voice Bot", ["Small"], deflection_target=0.35),
"realistic",
year=1,
params="realistic",
)
# claim should be strictly higher (no haircuts applied)
assert df_claim["annual_value"].sum() > df_realistic["annual_value"].sum()
# claim realization_factor = 1.0 × 1.0 × (1 0.0) = 1.0
expected_claim = 10_000 * 12 * 0.35 * 300 * 0.50 * 1.0 * 0.01
assert df_claim["annual_value"].sum() == pytest.approx(expected_claim)

View File

@@ -0,0 +1,117 @@
"""Business case maths + exports."""
from __future__ import annotations
import pytest
from tokencalc.business_case import build_business_case, npv, payback_years
from tokencalc.defaults import (
CTM_DEFAULT_FEATURE_SCOPES,
CTM_DEFAULT_SITES,
CTM_DEFAULT_TAKEOUTS,
DEFAULT_METERS,
DEFAULT_PRICING,
)
from tokencalc.exports import (
export_excel,
scenario_state_from_json,
scenario_state_to_json,
)
def test_npv_hand_check():
"""100/yr for 3 years @ 8%: 92.593 + 85.734 + 79.383 = 257.710."""
assert npv([100, 100, 100], 0.08) == pytest.approx(257.710, abs=0.001)
def test_payback_interpolation():
# -100 in Y1, +200 in Y2 → breakeven halfway through Y2 = 1.5 years
assert payback_years([-100, 200, 0]) == pytest.approx(1.5)
assert payback_years([-100, -100, -100]) is None
assert payback_years([50, 50, 50]) == pytest.approx(0.0)
def _case(scenario="realistic", **kw):
return build_business_case(
CTM_DEFAULT_SITES, CTM_DEFAULT_FEATURE_SCOPES, DEFAULT_METERS,
DEFAULT_PRICING, CTM_DEFAULT_TAKEOUTS, scenario, **kw,
)
def test_business_case_shape():
case = _case()
assert set(case) == {
"cost_by_year", "benefit_by_year", "takeouts_by_year",
"net_by_year", "cumulative_net", "npv",
"payback_period_years", "roi_3yr",
}
for key in ("cost_by_year", "benefit_by_year", "net_by_year"):
assert {"Y1", "Y2", "Y3"} <= set(case[key].columns)
def test_net_consistency():
"""NET row must equal benefits + takeouts costs, per year."""
case = _case()
nb = case["net_by_year"].set_index("line")
for y in ("Y1", "Y2", "Y3"):
assert nb.loc["NET", y] == pytest.approx(
nb.loc["TOTAL BENEFITS", y]
+ nb.loc["TOTAL TAKEOUTS", y]
- nb.loc["TOTAL COSTS", y]
)
# cumulative is a running sum of NET
assert nb.loc["Cumulative net", "Y3"] == pytest.approx(
sum(nb.loc["NET", y] for y in ("Y1", "Y2", "Y3"))
)
def test_npv_matches_net_rows():
case = _case()
nb = case["net_by_year"].set_index("line")
net = [nb.loc["NET", y] for y in ("Y1", "Y2", "Y3")]
assert case["npv"] == pytest.approx(npv(net, 0.08))
def test_three_scenarios_distinct():
npvs = {s: _case(s)["npv"] for s in ("floor", "realistic", "stretch")}
assert len({round(v) for v in npvs.values()}) == 3
assert npvs["floor"] < npvs["realistic"] < npvs["stretch"]
def test_implementation_amortization():
base = _case()
with_impl = _case(implementation_cost=900_000)
nb, nb2 = (
c["net_by_year"].set_index("line") for c in (base, with_impl)
)
for y in ("Y1", "Y2", "Y3"):
assert nb2.loc["TOTAL COSTS", y] == pytest.approx(
nb.loc["TOTAL COSTS", y] + 300_000
)
def test_excel_export_readable(tmp_path):
case = _case()
path = export_excel(
{
"Business Case": case["net_by_year"],
"Costs": case["cost_by_year"],
"Benefits": case["benefit_by_year"],
},
tmp_path / "ctm.xlsx",
)
import openpyxl
wb = openpyxl.load_workbook(path)
assert set(wb.sheetnames) == {"Business Case", "Costs", "Benefits"}
def test_scenario_json_roundtrip(tmp_path):
p = tmp_path / "state.json"
scenario_state_to_json(
CTM_DEFAULT_SITES, CTM_DEFAULT_TAKEOUTS, CTM_DEFAULT_FEATURE_SCOPES, p
)
sites, takeouts, scopes, _rollout = scenario_state_from_json(p)
assert [s.site_name for s in sites] == [s.site_name for s in CTM_DEFAULT_SITES]
assert takeouts[0].annual_cost == CTM_DEFAULT_TAKEOUTS[0].annual_cost
assert scopes[0].adoption_curve == CTM_DEFAULT_FEATURE_SCOPES[0].adoption_curve

View File

@@ -0,0 +1,188 @@
"""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 [named]"),
DEFAULT_METERS["Speech & Text Analytics [named]"], 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 [named]"),
DEFAULT_METERS["Agent Copilot [named]"], DEFAULT_PRICING,
)
assert df["annual_cost"].sum() == pytest.approx(1_002_240)
def test_ai_translate_not_active_before_phase():
"""AI Translate (consumption meter) produces zero cost before its phase."""
scenario = get_scenario("realistic")
apac_sites = [s.site_name for s in CTM_DEFAULT_SITES if s.region_pricing == "APAC"]
df = calculate_consumption_ai_cost(
CTM_DEFAULT_SITES,
_scope("AI Translate", apac_sites, phase=3),
DEFAULT_METERS["AI Translate"], scenario, 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 [named]"),
_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_predictive_routing_consumption():
"""1,700 calls/mo ÷ 17 per token = 100 tokens/mo → $1,200/yr (year 2, no ramp)."""
site = SiteInput(
"Tiny", "US", agents=5, supervisors=0,
voice_volume_monthly=1_700, 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,
)
df = calculate_consumption_ai_cost(
[site], FeatureScope("Predictive Routing", ["Tiny"]),
DEFAULT_METERS["Predictive Routing"], "realistic", DEFAULT_PRICING, year=2,
)
assert df.iloc[0]["tokens_monthly"] == 100
assert df.iloc[0]["annual_cost"] == pytest.approx(1_200)
def test_predictive_routing_eligibility_and_total_cost():
"""eligibility_pct halves the routed volume; total_cost handles the scope."""
site = SiteInput(
"Tiny", "US", agents=5, supervisors=0,
voice_volume_monthly=1_700, 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,
)
scope = FeatureScope("Predictive Routing", ["Tiny"], eligibility_pct=0.5)
df = calculate_consumption_ai_cost(
[site], scope, DEFAULT_METERS["Predictive Routing"], "realistic",
DEFAULT_PRICING, year=2,
)
assert df.iloc[0]["tokens_monthly"] == 50
total = calculate_total_cost(
[site], [scope], DEFAULT_METERS, DEFAULT_PRICING, "realistic", 2,
include_platform=False,
)
pr_row = total[total["cost_line"] == "Predictive Routing"].iloc[0]
assert pr_row["annual_cost"] == pytest.approx(50 * 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 [named]", [apac_site.site_name]),
DEFAULT_METERS["Speech & Text Analytics [named]"], 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)

View File

@@ -0,0 +1,92 @@
"""Meter catalogue integrity."""
from __future__ import annotations
import pytest
from tokencalc.defaults import DEFAULT_METERS, DEFAULT_PRICING
from tokencalc.meters import Confidence, MeterType, TokenMeter, TokenPricing
def test_all_spec_meters_present():
expected = {
# Voice / Bot
"Voice Bot", "Digital Bot",
# Virtual Agent
"Virtual Agent (legacy)", "Agentic Virtual Agent",
# Agent Copilot (named + concurrent)
"Agent Copilot [named]", "Agent Copilot [concurrent]",
# AI Quality / Analytics
"AI Scoring", "AI Summary & Insights",
# Speech & Text Analytics (named + concurrent)
"Speech & Text Analytics [named]", "Speech & Text Analytics [concurrent]",
# Routing
"Predictive Routing",
# Messaging
"Direct Messaging", "Social Listening", "Social Responses",
# Language
"AI Translate",
# Genesys Cloud Copilot
"Genesys Cloud Copilot",
# Email AI (rate TBD; Auto-Suggest is inside Agent Copilot)
"Email AI (Auto-Respond)",
}
assert expected == set(DEFAULT_METERS)
def test_confirmed_rates():
m = DEFAULT_METERS
assert m["Voice Bot"].units_per_token == 17
assert m["Voice Bot"].tokens_per_unit == pytest.approx(0.0588, abs=1e-3)
assert m["Digital Bot"].units_per_token == 51
assert m["Agentic Virtual Agent"].tokens_per_unit == 1.2
assert m["AI Summary & Insights"].tokens_per_unit == 0.02
assert m["Direct Messaging"].units_per_token == 400
# Named variants
assert m["Speech & Text Analytics [named]"].tokens_per_unit == 30
assert m["Speech & Text Analytics [concurrent]"].tokens_per_unit == 45
assert m["Agent Copilot [named]"].tokens_per_unit == 40
assert m["Agent Copilot [concurrent]"].tokens_per_unit == 60
# AI Translate is now a confirmed consumption meter
assert m["AI Translate"].tokens_per_unit == 0.5
assert m["AI Translate"].units_per_token == 2
assert m["AI Translate"].confidence is Confidence.CONFIRMED
# New meters
assert m["AI Scoring"].units_per_token == 20
assert m["Predictive Routing"].units_per_token == 17
assert m["Genesys Cloud Copilot"].units_per_token == 20
def test_unknown_meters_flagged():
unknown = {f for f, m in DEFAULT_METERS.items() if m.confidence is Confidence.UNKNOWN}
assert unknown == {"Email AI (Auto-Respond)"}
assert Confidence.UNKNOWN.icon == "🔴"
assert Confidence.CONFIRMED.icon == "🟢"
def test_inverse_consistency_validated():
with pytest.raises(ValueError, match="not inverses"):
TokenMeter(
feature="Bad", meter_type=MeterType.PER_MINUTE,
units_per_token=10, tokens_per_unit=0.5,
confidence=Confidence.ESTIMATED, notes="",
)
def test_every_confirmed_meter_has_source_url():
for m in DEFAULT_METERS.values():
if m.confidence is Confidence.CONFIRMED:
assert m.source_url, f"{m.feature} missing source URL"
def test_pricing_effective_rate():
p = TokenPricing(region="US", list_rate_per_token=1.0,
contracted_rate_per_token=0.85)
assert p.effective_rate(use_contracted=False) == 1.0
assert p.effective_rate(use_contracted=True) == 0.85
# no contracted rate → falls back to list
assert DEFAULT_PRICING["US"].effective_rate(use_contracted=True) == 1.0
def test_all_regions_priced():
assert set(DEFAULT_PRICING) == {"US", "EU", "AU", "APAC"}

View File

@@ -0,0 +1,92 @@
"""Migration + WFM (no-AI) scenario — hand-check acceptance numbers."""
from __future__ import annotations
import pytest
from tokencalc import appendix4 as a4
from tokencalc import migration_wfm as mw
from tokencalc.defaults import CTM_DEFAULT_SITES
SITES = list(CTM_DEFAULT_SITES)
def _default_benefit_rollout():
_, _, benefit_rollout = a4.build_rollouts(SITES)
return benefit_rollout
def test_wfm_scope_and_verbatim_total():
ben = mw.wfm_benefits_by_year(_default_benefit_rollout())
assert set(ben["capability"]) == {"WFM"}
assert set(ben["region"]) == set(mw.DEFAULT_WFM_REGIONS)
assert "EMEA" not in set(ben["region"]), "EMEA WFM is out of scope"
# NA $0 (migration) + ANZ $1.4M + ASIA $914K — verbatim, exact.
assert ben["benefit"].sum() == pytest.approx(2_314_000)
def test_wfm_phasing_on_deck_schedule():
ben = mw.wfm_benefits_by_year(_default_benefit_rollout())
by_year = ben.groupby("year")["benefit"].sum()
assert by_year[2026] == 0.0
# ANZ realizes Dec 2027 (1 of 13 live months lands in 2027).
assert by_year[2027] == pytest.approx(1_400_000 / 13)
assert by_year[2028] == pytest.approx(2_314_000 - 1_400_000 / 13)
def test_runrate_saving_annual():
# (7.3M 4.3M) licence + 1.3M ANZ + 1.6M ASIA + 0 NA = 5.9M.
assert mw.wfm_annual_runrate() == pytest.approx(2_900_000)
assert mw.runrate_saving_annual() == pytest.approx(5_900_000)
assert mw.runrate_saving_annual(regions=["NA"]) == pytest.approx(3_000_000)
assert mw.runrate_saving_annual(licence_annual=4_800_000,
regions=[]) == pytest.approx(2_500_000)
# Contracted frame: managed services stay in the run-rate forever.
assert mw.runrate_saving_annual(
a4.tco("ccaas_annual"), managed_annual=a4.MANAGED_SERVICES_ANNUAL
) == pytest.approx(6_589_081.60)
def _default_wfm_benefits_by_year():
ben = mw.wfm_benefits_by_year(_default_benefit_rollout())
return {y: float(ben.loc[ben.year == y, "benefit"].sum()) for y in a4.YEARS}
def test_breakeven_extrapolates_past_window():
# Deck frame: deck licence rate (6-month ramp), verbatim PS lump,
# no managed services.
cs = a4.current_state_inputs(SITES)
cur = a4.current_costs_by_year(cs)
lic = a4.licence_costs_by_year()
ps = a4.ps_costs_by_year()
total = {y: cur[y] + lic[y] + ps[y] for y in a4.YEARS}
inc, net = a4.case_flows(total, _default_wfm_benefits_by_year())
assert sum(net.values()) == pytest.approx(-3_703_000, abs=1_000)
label = mw.runrate_breakeven_label(net, mw.runrate_saving_annual())
assert label == "44 months (~Aug 2029, extrapolated)"
def test_contracted_frame_with_sow_and_managed_services():
# Contracted frame: signed licence rate, SOW PS milestones, managed
# services from MCX go-live — the case the notebook leads with.
cs = a4.current_state_inputs(SITES)
cur = a4.current_costs_by_year(cs)
lic = a4.licence_costs_by_year(annual=a4.tco("ccaas_annual"))
ps = a4.ps_costs_by_year(contracted=True)
man = a4.managed_services_by_year()
total = {y: cur[y] + lic[y] + ps[y] + man[y] for y in a4.YEARS}
inc, net = a4.case_flows(total, _default_wfm_benefits_by_year())
assert sum(net.values()) == pytest.approx(-1_503_013, abs=1_000)
runrate = mw.runrate_saving_annual(
a4.tco("ccaas_annual"), managed_annual=a4.MANAGED_SERVICES_ANNUAL)
label = mw.runrate_breakeven_label(net, runrate)
assert label == "39 months (~Mar 2029, extrapolated)"
def test_breakeven_defers_in_window_and_guards_zero_runrate():
positive = {2026: 1_000_000.0, 2027: 0.0, 2028: 0.0}
assert mw.runrate_breakeven_label(positive, 5_900_000) == \
a4.payback_label(positive)
negative = {2026: -1_000_000.0, 2027: 0.0, 2028: 0.0}
assert mw.runrate_breakeven_label(negative, 0.0) == \
"never at current run-rate"

View File

@@ -0,0 +1,15 @@
"""Stage/backstage detection — Mercury kernels carry MERCURY_CONFIG_DIR."""
from tokencalc 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 == ""