Skip to content

ump-spec

Contract import ump_spec

Ump-spec is the contract in code. Every shape the tools exchange, every rule that decides how numbers combine, and every gate lives here.

It is pure: no I/O beyond reading its own packaged registry files, and no numpy or modelling library. The other packages build on it. Analysts usually meet it through babelfin, which keeps the ump-spec object on .spec.

  1. Install: pip install -e 'packages/ump-spec' from a checkout.
  2. Start an estimand from the registry and build a shown number.
from ump_spec.registries import registered_estimand
from ump_spec.rules import shown_number
roi = registered_estimand("E-MMM-ROI")
tv = shown_number(1.37, 1.26, 1.50, roi, label="TV ROI")
tv.interval.level, tv.plain_words
# (0.9, 'revenue per dollar, net from removing all spend, geo_set, versus zero spend,
# total including carryover, posterior median, 90% eti')
ModuleWhat it holds
ump_spec.modelsEvery exchanged shape: Estimand, ShownNumber, ModelManifest, TestResult, Calibration, DecisionRecord and more. validate_doc(kind, doc) checks a document against its kind.
ump_spec.rulesPure rule functions: shown_number, compare_estimands, align (gate G-EST-02), dependence_envelope (G-DEP-01), choose_ladder_step.
ump_spec.gatesThe gate catalogue and an evaluator that stands in for OPA/Rego: evaluate, summarize, raise_for_stops.
ump_spec.integrityCanonical JSON, content hashes, an RFC 9162 Merkle log with inclusion and consistency proofs, Ed25519 checkpoints.
ump_spec.events / ump_spec.errorsCloudEvents 1.0.2 envelopes, Standard Webhooks signing, and UmpError with RFC 9457 problem details.
ump_spec.adapterRequest and response models of the engine adapter contract.
from ump_spec.rules import compare_estimands
cmp = compare_estimands(registered_estimand("E-MMM-ROI"), registered_estimand("E-GEO-IROAS"))
cmp.combinable # False
cmp.mismatched # ['intervention', 'baseline', 'horizon', 'point_rule', 'interval']

combinable is true only when all seven fields and the scope are declared and equal. Undeclared fields count as mismatched.

from ump_spec.rules import choose_ladder_step
choose_ladder_step(["C", "D"]).step # 'spend_table'
step = choose_ladder_step(["A"], excluded=["full_model"]) # a model that failed the acceptance bar
step.step, step.warned, step.notes[0].gate # ('benchmarks', True, 'G-SCN-02')
from ump_spec import UmpError, validate_doc
from ump_spec.integrity import MerkleLog
try:
validate_doc("model_manifest", doc)
except UmpError as err:
err.code # 'SCHEMA_INVALID', every issue listed with its pointer
log = MerkleLog()
index, leaf = log.append_record("ump/decision/v1", {"q": "shift 10% to search"})
log.root(), log.inclusion_proof(index)
Command line
Terminal window
python -m ump_spec gates # list the gate catalogue
python -m ump_spec export-schemas schemas # regenerate the JSON Schemas
python -m ump_spec check model_manifest doc.json --strict # validate a document and run its gates

The committed JSON Schemas are generated from the models. A test fails when they drift.

Conventions
  • Value objects reject unknown fields. Artifact bodies keep them, so documents from other tools still load.
  • Thresholds that wait on the pre-rollout studies are typed Proposed, carrying their value and source, so a proposed number never reads as settled.
  • In an estimand, None or the string "undeclared" means the source did not say. Gate G-EST-01 fills it from the source-type default, marks it assumed and widens the interval.

The conformance suite is not yet published. The tests in this package are reference tests of this implementation only: passing them does not make a tool conformant, and they are not the suite that section 21 describes. Where the package and the spec disagree, the spec wins and the package has a bug.