docs: introduce Mercury Notebook Deliverable Pattern

This commit is contained in:
2026-07-08 13:43:12 -04:00
parent a991879061
commit c3260ae7b8
55 changed files with 12036 additions and 0 deletions

View File

@@ -0,0 +1,166 @@
"""
Study engine — the single source of truth for every number in the notebooks.
Replace the toy domain below with the study's real model; the *structure*
is the pattern:
- ``ANCHOR_VERBATIM`` — the client/vendor source record, never edited.
- ``ANCHOR_CONTRACTED`` — signed values layered over it; ``anchor()`` reads
through, so the source anchor survives for as-pitched comparisons.
- ``*_by_year`` schedules keyed by calendar year.
- A baseline-relative case frame (``case_flows`` / ``case_kpis``).
The notebooks hold no math — they call this module and render.
"""
from __future__ import annotations
import math
# ── Timeline ─────────────────────────────────────────────────────────
YEARS = [2026, 2027, 2028] # model window; contract starts Jan of YEARS[0]
_MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
def month_label(m: int) -> str:
"""Calendar label for a 1-indexed month from Jan of YEARS[0]."""
return f"{_MONTHS[(m - 1) % 12]} {YEARS[0] + (m - 1) // 12}"
# ── Anchors: verbatim source record + contracted overlay ─────────────
#: The vendor's pitch / the client's source deck — VERBATIM, do not edit.
ANCHOR_VERBATIM: dict[str, float] = {
"baseline_annual": 1_000_000, # do-nothing run-rate
"platform_annual": 600_000, # pitched platform run-rate
"services_y1": 250_000, # pitched one-off services, year 1
"benefit_3yr": 900_000, # claimed 3-yr benefit
"npv_discount_rate": 0.10,
}
#: Signed values where they differ from the pitch — 🟢 contractual.
ANCHOR_CONTRACTED: dict[str, float] = {
"platform_annual": 500_000, # signed run-rate (pitch said $600K)
}
def anchor(key: str) -> float:
"""Contracted value where one exists, else the verbatim anchor."""
return ANCHOR_CONTRACTED.get(key, ANCHOR_VERBATIM[key])
DEFAULT_RAMP_MONTHS = 6 # platform billing starts month 7
DEFAULT_TERMINATION_YEAR = 2027 # existing platform bills through this year
REALIZE_MONTH = 18 # benefits realize from month 19
# ── Cost & benefit schedules (calendar-year keyed) ───────────────────
def platform_costs_by_year(
ramp_months: int = DEFAULT_RAMP_MONTHS, annual: float | None = None
) -> dict[int, float]:
"""Ramp programme: billing starts in calendar month ramp_months + 1."""
rate = ANCHOR_VERBATIM["platform_annual"] if annual is None else annual
out = {}
for yi, y in enumerate(YEARS, start=1):
start, end = 12 * (yi - 1) + 1, 12 * yi
months = max(0, end - max(start, ramp_months + 1) + 1)
out[y] = rate * months / 12
return out
def current_costs_by_year(
termination_year: int = DEFAULT_TERMINATION_YEAR, annual: float | None = None
) -> dict[int, float]:
"""Existing-platform run-off (the double-billing line)."""
rate = ANCHOR_VERBATIM["baseline_annual"] if annual is None else annual
return {y: (rate if y <= termination_year else 0.0) for y in YEARS}
def services_by_year() -> dict[int, float]:
"""One-off services — verbatim, year 1 only."""
return {y: (ANCHOR_VERBATIM["services_y1"] if y == YEARS[0] else 0.0)
for y in YEARS}
def benefits_by_year(realize_month: int = REALIZE_MONTH) -> dict[int, float]:
"""Phase the claimed 3-yr benefit across its live months."""
live = {y: max(0, 12 * yi - max(12 * (yi - 1), realize_month))
for yi, y in enumerate(YEARS, start=1)}
total = sum(live.values())
return {y: anchor("benefit_3yr") * m / total if total else 0.0
for y, m in live.items()}
# ── Business case (baseline-relative frame) ──────────────────────────
def case_flows(
total_cost_by_year: dict[int, float],
benefit_by_year: dict[int, float],
baseline_annual: float | None = None,
) -> tuple[dict[int, float], dict[int, float]]:
"""(incremental cost, net) vs the do-nothing baseline."""
base = ANCHOR_VERBATIM["baseline_annual"] if baseline_annual is None \
else baseline_annual
inc = {y: total_cost_by_year[y] - base for y in YEARS}
net = {y: benefit_by_year[y] - inc[y] for y in YEARS}
return inc, net
def npv(flows: list[float], rate: float) -> float:
"""NPV with the first flow discounted one full year."""
return sum(v / (1 + rate) ** i for i, v in enumerate(flows, start=1))
def payback_label(net_by_year: dict[int, float]) -> str:
cum = 0.0
for i, y in enumerate(YEARS):
step = net_by_year[y]
if cum + step >= 0:
if i == 0 and step >= 0:
return "immediate"
frac = (-cum / step) if step > 0 else 0.0
m = math.ceil((i + frac) * 12)
return f"{m} months (~{month_label(m)})"
cum += step
return f"beyond {YEARS[-1]}"
def case_kpis(
inc: dict[int, float],
net: dict[int, float],
discount_rate: float | None = None,
) -> dict:
"""KPIs for one cost frame. Benefits are recoverable as net + inc."""
rate = ANCHOR_VERBATIM["npv_discount_rate"] if discount_rate is None \
else discount_rate
net_list = [net[y] for y in YEARS]
inc_total = sum(inc.values())
net_total = sum(net_list)
return {
"benefits_3yr": net_total + inc_total,
"incremental_cost_3yr": inc_total,
"net_3yr": net_total,
"roi": (net_total / inc_total) if inc_total > 0 else None,
"npv": npv(net_list, rate),
"discount_rate": rate,
"payback": payback_label(net),
}
# ── Display helpers ──────────────────────────────────────────────────
def money(v: float) -> str:
sign, a = ("-" if v < 0 else ""), abs(v)
return f"{sign}${a/1e6:,.1f}M" if a >= 1e6 else f"{sign}${a/1e3:,.0f}K"
def html_money(v: float) -> str:
"""Plotly text with two or more bare ``$`` triggers MathJax math mode —
annotations holding several amounts must use the HTML entity instead."""
return money(v).replace("$", "&#36;")