Skip to content

ump_spec.rules.governance

module ump-spec

Materiality (G-MAT-01), maturity score and stages (§25.5), Benjamini-Hochberg (§10), conflict z (EMA §6.6), evidence ageing / use-by (§9, EMA §7).

class

class MaterialityResult

Fields

NameTypeDefaultDescription
tierstr
reasonslist[str]
thresholds_proposedboolTrue
thresholdsdict[str, float]field(default_factory=dict)
Methods and properties (1)

init constructor

MaterialityResult(
self,
tier: str,
reasons: list[str],
thresholds_proposed: bool = True,
thresholds: dict[str, float] = dict(),
) -> None

Source: packages/ump-spec/src/ump_spec/rules/governance.py, line 20

class

class MaturityResult

Fields

NameTypeDefaultDescription
scorefloat
componentsdict[str, float]
stagestr
stage_namestr
flagsdict[str, bool]
weights_proposedboolTrue
Methods and properties (1)

init constructor

MaturityResult(
self,
score: float,
components: dict[str, float],
stage: str,
stage_name: str,
flags: dict[str, bool],
weights_proposed: bool = True,
) -> None

Source: packages/ump-spec/src/ump_spec/rules/governance.py, line 81

class

class CloseRerunResult

Spec §13 close-rerun verdict. procedure is abs_z for one quantity, holm for more.

Fields

NameTypeDefaultDescription
closebool
procedureLiteral['abs_z', 'holm']
zlist[float]
plist[float]
rejectedlist[bool]
z_maxfloat
alphafloat
kpi_closebool | NoneNone
Methods and properties (1)

init constructor

CloseRerunResult(
self,
close: bool,
procedure: Literal['abs_z', 'holm'],
z: list[float],
p: list[float],
rejected: list[bool],
z_max: float,
alpha: float,
kpi_close: bool | None = None,
) -> None

Source: packages/ump-spec/src/ump_spec/rules/governance.py, line 228

function

def materiality_tier(
inputs: MaterialityInputs | dict[str, Any],
thresholds: MaterialityThresholds | None = None,
) -> MaterialityResult

G-MAT-01 (computed). Thresholds are per account and proposed.

full if spend >= full threshold, or not reversible within a quarter, or a public claim, or EVOI > 10x the cost of a full review; standard if spend >= standard threshold or the decision feeds a client recommendation; else light. A decision below the standard threshold that takes more than a month to reverse is lifted to standard (light requires reversible within a month).

Source: packages/ump-spec/src/ump_spec/rules/governance.py, line 28

function

def maturity_score(
*,
refit_age_days: float | None,
tests_last_4q: int,
share_channels_calibrated: float,
share_spend_with_test_or_cal: float,
) -> tuple[float, dict[str, float]]

0..100 = 25 x freshness (1 if refit <= 90d, 0.5 if <= 180d, 0.2 if older, 0 if no model)

  • 25 x min(tests/4, 1) + 30 x share of channels calibrated + 20 x causal share of spend.

Source: packages/ump-spec/src/ump_spec/rules/governance.py, line 91

function

def maturity_stage(flags: dict[str, bool]) -> str

Stages are independent flags; the headline stage is the furthest one reached.

Source: packages/ump-spec/src/ump_spec/rules/governance.py, line 118

function

def assess_maturity(
*,
foundations: bool,
refit_age_days: float | None,
quarters_with_test_last_4: int,
tests_last_4q: int,
share_channels_calibrated: float,
share_spend_with_test_or_cal: float,
reallocated_with_decision: bool,
) -> MaturityResult

Source: packages/ump-spec/src/ump_spec/rules/governance.py, line 124

function

def benjamini_hochberg(pvalues: list[float], q: float = BH_Q_DEFAULT) -> list[bool]

Return a reject/flag decision per p-value (same order) controlling FDR at q.

Source: packages/ump-spec/src/ump_spec/rules/governance.py, line 162

function

def benjamini_yekutieli(pvalues: list[float], q: float = BH_Q_DEFAULT) -> list[bool]

Benjamini-Yekutieli step-up (FDR under arbitrary dependence): BH at q / c(m), with c(m) = 1 + 1/2 + … + 1/m. Spec §10 lets a calibration declare it when tests share geos or weeks.

Source: packages/ump-spec/src/ump_spec/rules/governance.py, line 179

function

def holm(pvalues: list[float], alpha: float = 0.05) -> list[bool]

Holm step-down (family-wise error at alpha): reject the i-th smallest p while p <= alpha / (m - i + 1); stop at the first that fails. Same order as the input.

Source: packages/ump-spec/src/ump_spec/rules/governance.py, line 189

function

def threshold_set() -> dict[str, Any]

The threshold set registry: {version, thresholds: {group: {name: {value, proposed, status, spec_ref}}}}` (`registries/threshold_set.yaml; every value proposed).

Source: packages/ump-spec/src/ump_spec/rules/governance.py, line 206

function

def threshold_set_version() -> str

Version string to record as threshold_set_version (spec §14).

Source: packages/ump-spec/src/ump_spec/rules/governance.py, line 215

function

def threshold_value(group: str, name: str) -> Any

One threshold’s value; raises KeyError for an unknown group or name.

Source: packages/ump-spec/src/ump_spec/rules/governance.py, line 220

function

def close_rerun(
theta_1: Sequence[float],
theta_2: Sequence[float],
mcse_1: Sequence[float],
mcse_2: Sequence[float],
*,
kpi_difference: float | None = None,
materiality_threshold: float | None = None,
z_max: float | None = None,
alpha: float | None = None,
) -> CloseRerunResult

Spec §13: z = |theta_1 - theta_2| / sqrt(MCSE_1^2 + MCSE_2^2) per compared quantity, with MCSE from Vehtari et al. (2021), method mean (the caller supplies it). One quantity: close when z <= 2. Two or more: two-sided normal p-values through Holm at alpha 0.05; close unless Holm rejects. With kpi_difference and materiality_threshold the recommended allocation’s expected KPI must also differ by less than 1% of the threshold. Defaults come from the threshold set (all proposed).

Source: packages/ump-spec/src/ump_spec/rules/governance.py, line 242

function

def conflict_z(
tau_hat: float,
se_exp: float,
mu0: float,
sigma0: float,
) -> tuple[float, float]

EMA §6.6: z = |tau_hat - mu0| / sqrt(sigma0^2 + se_exp^2); returns (z, two-sided p).

Source: packages/ump-spec/src/ump_spec/rules/governance.py, line 289

function

def precision_weighted_update(
mu0: float,
sigma0: float,
y: float,
sigma_e: float,
) -> tuple[float, float]

EMA Eq. 2 (G-CAL-05 fallback): posterior mean and sd of a normal-normal update.

Source: packages/ump-spec/src/ump_spec/rules/governance.py, line 296

function

def variance_inflation(age_weeks: float, half_life_weeks: float) -> float

sigma_eff^2 / sigma_post^2 = exp(lambda t), lambda = ln 2 / h.

Source: packages/ump-spec/src/ump_spec/rules/governance.py, line 312

function

def use_by_date(
measured: date,
half_life_weeks: float,
k: float = USE_BY_K_DEFAULT,
) -> date

Date at which variance inflation reaches k (t = h log2 k).

Source: packages/ump-spec/src/ump_spec/rules/governance.py, line 317

function

def carryover_floor_weeks(alpha: float, threshold: float = 0.05) -> int

EMA Eq. 17: k_min = ceil(ln(threshold)/ln(alpha)), clamped to [1, 26].

Source: packages/ump-spec/src/ump_spec/rules/governance.py, line 323

NameTypeValueDescription
MATURITY_WEIGHTSdict[str, float]{'fresh_model': 25, 'tests': 25, 'calibrated': 30, 'causal_share': 20}§25.5 weights: placeholders for the measurement lead to set (treat as proposed).
MATURITY_WEIGHTS_PROPOSEDTrue
STAGE_NAMESdict[str, str]{'none': 'Not started', 'foundations': 'Foundations', 'model_live': 'Model li...
STAGE_ORDERtuple[str, ...]('foundations', 'model_live', 'experimenting', 'calibrated', 'reallocating')
BH_Q_DEFAULT0.1§10 proposed.
USE_BY_K_DEFAULT2.0POC choice (proposed): use-by is when the effective variance reaches k x the posterior variance.
COLD_START_HALF_LIFE_WEEKSdict[str, float]{'digital': 39.0, 'broadcast': 91.0}Cold-start ageing half-lives in weeks, chosen so use_by (k=2) lands at about 9 months for digital and 21 months for broadcast/OOH, the midpoints of the EMA defaults 6-12 and 18-24 months.