New notebooks: Scenario without current spend, VA scenario

This commit is contained in:
2026-07-13 16:32:18 -04:00
parent e88449d15a
commit cbbc9ba839
8 changed files with 19078 additions and 3964 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -25,11 +25,12 @@ def test_verbatim_crossfoots_to_slide_totals():
a4.crossfoot_tolerance(a4.SLIDE_TOTALS["total_3yr"])
def test_benefits_phase_on_the_deck_schedule():
def test_benefits_phase_on_the_pm_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"
# Earliest region (NA) realizes Jan 2027 → 2026 still $0.
assert by_year[2026] == 0.0, "2026 must be $0 (NA realizes Jan 2027)"
# 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()
@@ -65,16 +66,18 @@ def test_token_hand_checks():
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 2028: every site live all 12 months under the compressed PM
# timeline (APAC live from Nov 2027) → full-year STA for all sites.
sta = long.query("cost_line == 'Speech & Text Analytics [named]'")
assert sta.query("year == 2028")["annual_cost"].sum() == pytest.approx(715_800)
assert sta.query("year == 2028")["annual_cost"].sum() == pytest.approx(751_680)
# 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
# Under the PM timeline NA goes live Oct 2026, so tokens DO accrue in
# 2026 (NA only — three live months); no longer a $0 year.
assert long.query("year == 2026")["annual_cost"].sum() == pytest.approx(424_350)
# PR NAM steady-month tokens.
assert math.ceil(
1_214_358 * DEFAULT_METERS["Predictive Routing"].tokens_per_unit) == 71_433
@@ -130,8 +133,8 @@ def test_sow_milestones_and_managed_services():
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).
# Managed services bill from the month after MCX go-live (Oct 16 → Nov).
ms = a4.managed_services_by_year()
assert ms[2026] == pytest.approx(410_918.40 * 3 / 12)
assert ms[2026] == pytest.approx(410_918.40 * 2 / 12)
assert ms[2027] == pytest.approx(410_918.40)
assert ms[2028] == pytest.approx(410_918.40)

View File

@@ -25,13 +25,14 @@ def test_wfm_scope_and_verbatim_total():
assert ben["benefit"].sum() == pytest.approx(2_314_000)
def test_wfm_phasing_on_deck_schedule():
def test_wfm_phasing_on_pm_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)
# PM timeline: ANZ realizes Sep 2027 → 4 of 16 live months in 2027
# (Sep-Dec); ASIA realizes Jan 2028 → all in 2028.
assert by_year[2027] == pytest.approx(1_400_000 * 4 / 16)
assert by_year[2028] == pytest.approx(2_314_000 - 1_400_000 * 4 / 16)
def test_runrate_saving_annual():
@@ -76,7 +77,7 @@ def test_contracted_frame_with_sow_and_managed_services():
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)
assert sum(net.values()) == pytest.approx(-1_468_770, 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)

View File

@@ -113,11 +113,11 @@ TCO_CONTRACTED: dict[str, float] = {
PS_MILESTONES: list[dict] = [
{"name": "SOW Effective Date", "date": dt.date(2026, 3, 15),
"share": 0.30, "amount": 607_633.94},
{"name": "Start of client UAT (first region)", "date": dt.date(2026, 9, 30),
{"name": "Start of client UAT (first region, NA)", "date": dt.date(2026, 10, 16),
"share": 0.20, "amount": 405_089.30},
{"name": "Start of client UAT (last region)", "date": dt.date(2027, 6, 30),
{"name": "Start of client UAT (last region, APAC)", "date": dt.date(2027, 10, 1),
"share": 0.30, "amount": 607_633.94},
{"name": "Completion of last go-live migration", "date": dt.date(2027, 9, 30),
{"name": "Completion of last go-live migration", "date": dt.date(2027, 11, 1),
"share": 0.20, "amount": 405_089.30},
]
PS_CONTRACTED_TOTAL = sum(m["amount"] for m in PS_MILESTONES)
@@ -125,20 +125,23 @@ PS_CONTRACTED_TOTAL = sum(m["amount"] for m in PS_MILESTONES)
#: NTT managed services — commences billing at MCX go-live (🟢 contractual).
#: Not in the deck's TCO at all; an ongoing run-rate cost thereafter.
MANAGED_SERVICES_ANNUAL = 410_918.40
MCX_GO_LIVE = dt.date(2026, 9, 30)
MCX_GO_LIVE = dt.date(2026, 10, 16)
def tco(key: str) -> float:
"""Contracted value where one exists, else the deck's verbatim anchor."""
return TCO_CONTRACTED.get(key, TCO_VERBATIM[key])
#: Genesys/Broadreach deployment schedule (slides 17-21), months from
#: Jan 2026 inclusive. Benefits realize IMPL + 3 months.
IMPL_MONTH = {"NA": 18, "ANZ": 21, "EMEA": 24, "ASIA": 27}
#: Deployment schedule per the current PM delivery timeline (Jul 2026),
#: months from Jan 2026 inclusive. UAT start is the implementation anchor:
#: NA Oct 2026 (m10), EMEA Feb 2027 (m14), ANZ Jun 2027 (m18),
#: APAC Oct 2027 (m22). Benefits realize IMPL + 3 months.
IMPL_MONTH = {"NA": 10, "EMEA": 14, "ANZ": 18, "ASIA": 22}
BENEFIT_LAG_MONTHS = 3
REALIZE_MONTH = {r: m + BENEFIT_LAG_MONTHS for r, m in IMPL_MONTH.items()}
#: NA Gantt exception: Email implemented Jan 2027, realizes Apr 2027.
NA_EMAIL_IMPL_MONTH = 13
#: NA-Email-early exception retired under the compressed timeline
#: (NA implements Oct 2026; Email cannot realize before NA itself).
NA_EMAIL_IMPL_MONTH = IMPL_MONTH["NA"]
DEFAULT_RAMP_MONTHS = 6 # Genesys ramp programme (🟢 order form)
DEFAULT_TERMINATION = dt.date(2027, 12, 31) # current-platform term contracts
@@ -321,7 +324,7 @@ def ps_milestones_dataframe() -> pd.DataFrame:
def managed_services_by_year(
annual: float = MANAGED_SERVICES_ANNUAL, start: dt.date = MCX_GO_LIVE
) -> dict[int, float]:
"""Managed services bill from the month after go-live (Sep 30 → Oct),
"""Managed services bill from the month after go-live (Oct 16 → Nov),
then run at the full annual rate — an ongoing cost with no end date
inside the model window."""
def _months(y: int) -> int: