CTM Calculators: Add PS billing milestones, Adjust Genesys RAMP
This commit is contained in:
@@ -41,6 +41,10 @@ def test_ramp_zeroes_year_one_licences():
|
||||
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():
|
||||
@@ -113,3 +117,21 @@ def test_contracted_overlays_verbatim():
|
||||
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)
|
||||
|
||||
@@ -47,11 +47,9 @@ def test_email_benefit_split():
|
||||
[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)",
|
||||
"Email Auto-Suggest (drafting time)",
|
||||
}
|
||||
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)
|
||||
|
||||
@@ -28,8 +28,8 @@ def test_all_spec_meters_present():
|
||||
"AI Translate",
|
||||
# Genesys Cloud Copilot
|
||||
"Genesys Cloud Copilot",
|
||||
# Email AI (rates TBD)
|
||||
"Email AI (Auto-Suggest)", "Email AI (Auto-Respond)",
|
||||
# Email AI (rate TBD; Auto-Suggest is inside Agent Copilot)
|
||||
"Email AI (Auto-Respond)",
|
||||
}
|
||||
assert expected == set(DEFAULT_METERS)
|
||||
|
||||
@@ -59,9 +59,7 @@ def test_confirmed_rates():
|
||||
|
||||
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-Suggest)", "Email AI (Auto-Respond)",
|
||||
}
|
||||
assert unknown == {"Email AI (Auto-Respond)"}
|
||||
assert Confidence.UNKNOWN.icon == "🔴"
|
||||
assert Confidence.CONFIRMED.icon == "🟢"
|
||||
|
||||
|
||||
@@ -41,20 +41,46 @@ def test_runrate_saving_annual():
|
||||
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}
|
||||
ben = mw.wfm_benefits_by_year(_default_benefit_rollout())
|
||||
ben_y = {y: float(ben.loc[ben.year == y, "benefit"].sum()) for y in a4.YEARS}
|
||||
inc, net = a4.case_flows(total, ben_y)
|
||||
assert sum(net.values()) == pytest.approx(-1_553_000, abs=1_000)
|
||||
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 == "40 months (~Apr 2029, extrapolated)"
|
||||
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():
|
||||
|
||||
Reference in New Issue
Block a user