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"]) 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) _, _, benefit_rollout = a4.build_rollouts(SITES)
long = a4.benefits_by_year(benefit_rollout) long = a4.benefits_by_year(benefit_rollout)
by_year = long.groupby("year")["benefit"].sum() 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. # Scaling at the finest grain reproduces every verbatim 3-yr value exactly.
for (region, cap), (_, three_yr) in a4.VERBATIM_BENEFITS.items(): for (region, cap), (_, three_yr) in a4.VERBATIM_BENEFITS.items():
got = long.query("region == @region and capability == @cap")["benefit"].sum() 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, long = a4.token_costs_by_year(SITES, meters, DEFAULT_PRICING,
a4.claim_scenario(0.255), core, email, a4.claim_scenario(0.255), core, email,
token_ro, email_ro) 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]'") 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. # Agent Copilot 2028 (ASIA off): 1,490 users × 40 tokens × 12 months.
cp = long.query("cost_line == 'Agent Copilot [named]' and year == 2028") cp = long.query("cost_line == 'Agent Copilot [named]' and year == 2028")
assert cp["annual_cost"].sum() == pytest.approx(1_490 * 40 * 12) assert cp["annual_cost"].sum() == pytest.approx(1_490 * 40 * 12)
# Rule 1: Copilot covers AI Summary at Copilot sites. # Rule 1: Copilot covers AI Summary at Copilot sites.
assert (long.query("cost_line == 'AI Summary & Insights'")["annual_cost"] == 0).all() assert (long.query("cost_line == 'AI Summary & Insights'")["annual_cost"] == 0).all()
# Nothing is live in 2026. # Under the PM timeline NA goes live Oct 2026, so tokens DO accrue in
assert long.query("year == 2026")["annual_cost"].sum() == 0 # 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. # PR NAM steady-month tokens.
assert math.ceil( assert math.ceil(
1_214_358 * DEFAULT_METERS["Predictive Routing"].tokens_per_unit) == 71_433 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 assert ps[2028] == 0.0
# The deck's verbatim year-1 lump stays intact for the as-pitched frame. # 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} 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() 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[2027] == pytest.approx(410_918.40)
assert ms[2028] == 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) 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()) ben = mw.wfm_benefits_by_year(_default_benefit_rollout())
by_year = ben.groupby("year")["benefit"].sum() by_year = ben.groupby("year")["benefit"].sum()
assert by_year[2026] == 0.0 assert by_year[2026] == 0.0
# ANZ realizes Dec 2027 (1 of 13 live months lands in 2027). # PM timeline: ANZ realizes Sep 2027 → 4 of 16 live months in 2027
assert by_year[2027] == pytest.approx(1_400_000 / 13) # (Sep-Dec); ASIA realizes Jan 2028 → all in 2028.
assert by_year[2028] == pytest.approx(2_314_000 - 1_400_000 / 13) 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(): def test_runrate_saving_annual():
@@ -76,7 +77,7 @@ def test_contracted_frame_with_sow_and_managed_services():
man = a4.managed_services_by_year() man = a4.managed_services_by_year()
total = {y: cur[y] + lic[y] + ps[y] + man[y] for y in a4.YEARS} 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()) 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( runrate = mw.runrate_saving_annual(
a4.tco("ccaas_annual"), managed_annual=a4.MANAGED_SERVICES_ANNUAL) a4.tco("ccaas_annual"), managed_annual=a4.MANAGED_SERVICES_ANNUAL)
label = mw.runrate_breakeven_label(net, runrate) label = mw.runrate_breakeven_label(net, runrate)

View File

@@ -113,11 +113,11 @@ TCO_CONTRACTED: dict[str, float] = {
PS_MILESTONES: list[dict] = [ PS_MILESTONES: list[dict] = [
{"name": "SOW Effective Date", "date": dt.date(2026, 3, 15), {"name": "SOW Effective Date", "date": dt.date(2026, 3, 15),
"share": 0.30, "amount": 607_633.94}, "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}, "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}, "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}, "share": 0.20, "amount": 405_089.30},
] ]
PS_CONTRACTED_TOTAL = sum(m["amount"] for m in PS_MILESTONES) 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). #: NTT managed services — commences billing at MCX go-live (🟢 contractual).
#: Not in the deck's TCO at all; an ongoing run-rate cost thereafter. #: Not in the deck's TCO at all; an ongoing run-rate cost thereafter.
MANAGED_SERVICES_ANNUAL = 410_918.40 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: def tco(key: str) -> float:
"""Contracted value where one exists, else the deck's verbatim anchor.""" """Contracted value where one exists, else the deck's verbatim anchor."""
return TCO_CONTRACTED.get(key, TCO_VERBATIM[key]) return TCO_CONTRACTED.get(key, TCO_VERBATIM[key])
#: Genesys/Broadreach deployment schedule (slides 17-21), months from #: Deployment schedule per the current PM delivery timeline (Jul 2026),
#: Jan 2026 inclusive. Benefits realize IMPL + 3 months. #: months from Jan 2026 inclusive. UAT start is the implementation anchor:
IMPL_MONTH = {"NA": 18, "ANZ": 21, "EMEA": 24, "ASIA": 27} #: 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 BENEFIT_LAG_MONTHS = 3
REALIZE_MONTH = {r: m + BENEFIT_LAG_MONTHS for r, m in IMPL_MONTH.items()} 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-early exception retired under the compressed timeline
NA_EMAIL_IMPL_MONTH = 13 #: (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_RAMP_MONTHS = 6 # Genesys ramp programme (🟢 order form)
DEFAULT_TERMINATION = dt.date(2027, 12, 31) # current-platform term contracts 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( def managed_services_by_year(
annual: float = MANAGED_SERVICES_ANNUAL, start: dt.date = MCX_GO_LIVE annual: float = MANAGED_SERVICES_ANNUAL, start: dt.date = MCX_GO_LIVE
) -> dict[int, float]: ) -> 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 then run at the full annual rate — an ongoing cost with no end date
inside the model window.""" inside the model window."""
def _months(y: int) -> int: def _months(y: int) -> int: