CTM Calculators: Add PS billing milestones, Adjust Genesys RAMP
This commit is contained in:
@@ -107,6 +107,26 @@ TCO_CONTRACTED: dict[str, float] = {
|
||||
"ccaas_annual": 3_200_000, # signed licence run-rate (deck pitched $4.3M/yr)
|
||||
}
|
||||
|
||||
#: NTT professional services — SOW billing milestones (🟢 contractual).
|
||||
#: The deck's verbatim anchor books $2.4M PS in year 1; the signed SOW
|
||||
#: bills $2,025,446.48 in four milestones split 50/50 across 2026-27.
|
||||
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),
|
||||
"share": 0.20, "amount": 405_089.30},
|
||||
{"name": "Start of client UAT (last region)", "date": dt.date(2027, 6, 30),
|
||||
"share": 0.30, "amount": 607_633.94},
|
||||
{"name": "Completion of last go-live migration", "date": dt.date(2027, 9, 30),
|
||||
"share": 0.20, "amount": 405_089.30},
|
||||
]
|
||||
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)
|
||||
|
||||
|
||||
def tco(key: str) -> float:
|
||||
"""Contracted value where one exists, else the deck's verbatim anchor."""
|
||||
@@ -120,7 +140,7 @@ 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
|
||||
|
||||
DEFAULT_RAMP_MONTHS = 12 # Genesys ramp programme
|
||||
DEFAULT_RAMP_MONTHS = 6 # Genesys ramp programme (🟢 order form)
|
||||
DEFAULT_TERMINATION = dt.date(2027, 12, 31) # current-platform term contracts
|
||||
|
||||
# ── Region ⇄ site mapping ────────────────────────────────────────────
|
||||
@@ -275,12 +295,42 @@ def licence_costs_by_year(
|
||||
for y in YEARS}
|
||||
|
||||
|
||||
def ps_costs_by_year() -> dict[int, float]:
|
||||
"""Base professional services + training — verbatim, year 1 only."""
|
||||
return {2026: TCO_VERBATIM["prof_services_y1"] + TCO_VERBATIM["training_y1"],
|
||||
def ps_costs_by_year(contracted: bool = False) -> dict[int, float]:
|
||||
"""Base professional services + training.
|
||||
|
||||
Verbatim: the deck's $2.4M PS lump plus training, all in year 1.
|
||||
Contracted: PS phased on the SOW billing milestones (50/50 across
|
||||
2026-27, $2.03M total); training stays the verbatim year-1 line —
|
||||
the SOW milestones don't itemize it separately.
|
||||
"""
|
||||
training = TCO_VERBATIM["training_y1"]
|
||||
if contracted:
|
||||
ps = {y: 0.0 for y in YEARS}
|
||||
for m in PS_MILESTONES:
|
||||
ps[m["date"].year] += m["amount"]
|
||||
return {y: ps[y] + (training if y == 2026 else 0.0) for y in YEARS}
|
||||
return {2026: TCO_VERBATIM["prof_services_y1"] + training,
|
||||
2027: 0.0, 2028: 0.0}
|
||||
|
||||
|
||||
def ps_milestones_dataframe() -> pd.DataFrame:
|
||||
"""The SOW billing milestones as a display table."""
|
||||
return pd.DataFrame(PS_MILESTONES)
|
||||
|
||||
|
||||
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),
|
||||
then run at the full annual rate — an ongoing cost with no end date
|
||||
inside the model window."""
|
||||
def _months(y: int) -> int:
|
||||
if y < start.year:
|
||||
return 0
|
||||
return 12 if y > start.year else 12 - start.month
|
||||
return {y: annual * _months(y) / 12 for y in YEARS}
|
||||
|
||||
|
||||
# ── Token consumption (missing cost #1) ──────────────────────────────
|
||||
|
||||
|
||||
@@ -293,7 +343,6 @@ def claim_scenario(email_auto_respond_rate: float = 0.255) -> Scenario:
|
||||
voice_summarization_eligibility=0.0,
|
||||
voice_knowledge_eligibility=0.0, # unused by the Appendix-4 scope set
|
||||
email_auto_respond_rate=email_auto_respond_rate,
|
||||
email_auto_suggest_acceptance=0.0, # Auto-Suggest is inside Copilot (V2 #1)
|
||||
consumption_cost_realization={1: 1.0, 2: 1.0, 3: 1.0},
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user