""" Migration + WFM scenario — the no-AI business case. Current state → Genesys Cloud CX migration, NA users migrated onto Genesys WFM, WFM implemented for APAC (ANZ + ASIA). Migration and WFM are included in the base implementation price (the verbatim PS + training), so the only cost lines are the existing-platform run-off, the licence ramp, and base PS — no token consumption, no AI implementation labour. The only benefits kept are the deck's verbatim WFM lines for the regions in WFM scope. Single source of truth behind ``notebooks/ctm_migration_wfm.ipynb`` (served with Mercury) — the presentation layer holds no math. All primitives come from :mod:`tokencalc.appendix4`; this module only scopes and extrapolates. """ from __future__ import annotations import math import pandas as pd from . import appendix4 as a4 #: WFM scope in this scenario. NA is a migration off its existing #: WFM-like tool (verbatim benefit $0 — "has similar feature"); ANZ and #: ASIA are new implementations ("APAC"); EMEA is out of scope, so the #: deck's EMEA WFM benefit line is dropped. DEFAULT_WFM_REGIONS = ["NA", "ANZ", "ASIA"] def wfm_benefits_by_year( benefit_rollout, regions: list[str] | None = None ) -> pd.DataFrame: """Verbatim WFM benefits for the scoped regions, phased on the deck's deployment schedule (realize = impl + 3 months, inclusive). Long DataFrame: region, capability, year, benefit — the WFM slice of :func:`tokencalc.appendix4.benefits_by_year`. """ scope = DEFAULT_WFM_REGIONS if regions is None else regions df = a4.benefits_by_year(benefit_rollout) return df[(df["capability"] == "WFM") & (df["region"].isin(scope))].reset_index(drop=True) def wfm_annual_runrate(regions: list[str] | None = None) -> float: """Sum of the verbatim WFM *annual* values for the scoped regions.""" scope = DEFAULT_WFM_REGIONS if regions is None else regions return float(sum(annual for (r, c), (annual, _t) in a4.VERBATIM_BENEFITS.items() if c == "WFM" and r in scope)) def runrate_saving_annual( licence_annual: float | None = None, regions: list[str] | None = None, baseline_annual: float | None = None, managed_annual: float = 0.0, ) -> float: """Steady-state annual saving once term contracts end and the ramp is over: (baseline − licence run-rate − managed services) + scoped WFM annual values. Defaults are the deck frame (deck licence rate, no managed services); the contracted frame passes ``a4.MANAGED_SERVICES_ANNUAL``. """ lic = a4.TCO_VERBATIM["ccaas_annual"] if licence_annual is None else licence_annual base = a4.TCO_VERBATIM["current_annual"] if baseline_annual is None else baseline_annual return (base - lic - managed_annual) + wfm_annual_runrate(regions) def runrate_breakeven_label( net_by_year: dict[int, float], runrate_annual: float ) -> str: """Payback label, extrapolated past the model window at a run-rate. Inside 2026-28 this defers to :func:`tokencalc.appendix4.payback_label`; a deficit at end-2028 fills at ``runrate_annual`` per year. """ deficit = -sum(net_by_year[y] for y in a4.YEARS) if deficit <= 0: return a4.payback_label(net_by_year) if runrate_annual <= 0: return "never at current run-rate" m = 12 * len(a4.YEARS) + math.ceil(12 * deficit / runrate_annual) return f"{m} months (~{a4.month_label(m)}, extrapolated)"