How it works · the data model

The pipeline, as a data model.

How evidence becomes a governed product — the entities the platform actually runs on, and how they relate. Each entity links to the working surface that implements it. This is the model, not a diagram of intent.

Evidence → decision insight

Four stages — Sources, Decision control, Execution, Products — expressed as entities and relationships.

1 · SourcesSOURCE
2 · Decision controlDOMAIN · ASSUMPTION · SCENARIO
3 · ExecutionMODEL · RUN
4 · Outputprovenance.json in artifact
Layer 1 · Live — governed inputs Layer 2 · Roadmap — run audit trail
erDiagram
  SOURCE ||--o{ ASSUMPTION : feeds
  DOMAIN ||--o{ ASSUMPTION : governs
  SCENARIO }o--o{ ASSUMPTION : "selects bands"
  MODEL }o--o{ ASSUMPTION : consumes
  RUN }o--|| MODEL : executes
  RUN }o--|| SCENARIO : under
  RUN ||--o{ OUTPUT : produces
  SOURCE { string provider string type date accessed }
  DOMAIN { string name string steward string cadence }
  ASSUMPTION { string key string value string unit string source float confidence date valid_to }
  SCENARIO { string name string bands string review_status }
  MODEL { string repo string sector string status }
  RUN { string model_version string assumptions_commit string scenario string who datetime at }
  OUTPUT { string artifact string provenance_json }
ERD renders client-side. If blank, the diagram script was blocked — the entities are in the table below.

Read it as the pipeline: a governed ASSUMPTION (sourced, confidence-scored, governed by a DOMAIN steward) is banded by a SCENARIO and consumed by a MODEL. Stages 1–2 are live today. Stage 3 (RUN) is Layer 2 — a lightweight audit record of every execution, pinning the assumptions_commit, scenario, who and when. Stage 4 needs no separate registry: every OUTPUT artifact carries a provenance.json that embeds those same fields.

How models & assumptions meet — the promotion path

A model is born bottom-up (it defines its own assumptions) and matures top-down (it consumes governed ones). Maturity is one journey: an assumption goes local → governed → consumed. Which way you build depends on whether the registry already covers your domain.

← bottom-up · the model defines the assumptions the registry serves the model · top-down →
LOCAL
Assumptions live inside the model — improvised, model-shaped. The model is the source of the spec. You build the model, then discover the assumption case.
liquid_fuels_model provider-ready · source_repo_ref: null
promote →
GOVERNED
Promoted into the registry — owned, sourced, reviewed. Divergences are flagged, not overridden. The alignment doc is literally the promotion list.
gas_mass_balance governed locally · mid-promotion
consume →
CONSUMED
The model reads governed assumptions live. Top-down, integrate-once-consume-many. You know the assumption case; you build the model to fit it.
freight_market_sizing reads the API · 5 live domains · 550+ governed fields
The registry plays both ends: a sink that captures what new bottom-up models need, and a source that serves mature top-down models. Maturing a model means moving it from feeding the sink to consuming from the source.

Coverage decides what’s even possible: a thin or empty domain forces bottom-up — the model becomes the thing that defines what the registry should hold; a rich governed domain lets you go top-down from day one.

Live example · Boegoebaai Rail Corridor

This is what CONSUMED looks like in practice

A 25-year railway feasibility model pulling 12 governed assumptions across 4 domains at runtime. Every input is sourced, confidence-scored, and owned. The model never touches a spreadsheet cell directly — it reads the registry.

Governed assumptions — 4 domains
rail / boegoebaai.capex.total_capex rail / boegoebaai.judgements.tariff_escalation rail / boegoebaai.judgements.cost_of_debt rail / boegoebaai.judgements.debt_ratio financial / assets.pipeline.wacc macro / gdp_growth macro / interest_rate mining / iron_ore.base_production_mpta + 4 more fields
Model — railway_feasibility.py
25-year DCF 3 scenarios FCFF @ WACC
Reads the registry at runtime.
No hardcoded assumptions.
SHA stamped on every run.
Output — scenario results
NPV (Rbn) IRR Payback year Excel (live refresh)
Carries assumptions_commit.
Reproducible in 6 months.

Governed inputs consumed by this model

AssumptionDomainValue (medium)UnitConfidenceSource
CapEx — total project rail R 79.0 bn ZAR V2 base case
Tariff escalation rail 4.6% pa V2 base case rail tariff escalation
Debt ratio rail 70% gearing V2 base case gearing
Cost of debt rail 7.0% pa nominal V2 base case cost of debt
Asset life rail 27 years years Engineering estimate
WACC — pipeline assets financial 12.0% pa Governed hurdle rate
GDP growth — SA base macro 2.2% pa Macro committee
Iron ore — SA base production mining 37.7 MTPA MTPA DMRE / Kumba

Model output — three scenarios, same governed inputs

High scenario
+R 3.3 bn
IRR 12.5% — clears WACC hurdle
Payback 2039 · vol. ceiling 50 MTPA
Tariff esc. 5.5% pa
Medium scenario
−R 16.6 bn
IRR 9.3% — below WACC
Payback 2040 · vol. ceiling 40 MTPA
Tariff esc. 4.6% pa
Low scenario
−R 40.7 bn
IRR 5.1% — not viable
Payback 2044 · vol. ceiling 30 MTPA
Tariff esc. 3.5% pa

The 1% difference between high and medium tariff escalation (5.5% vs 4.6%) moves NPV by R20bn. That one governed assumption is the swing factor. If it lives in a spreadsheet cell, two analysts will disagree on it — silently. In the registry, there is one value, one owner, and one source document.

provenance.json — stamped on every run
  "model": "railway_feasibility.py",
  "assumptions_commit": "loading…",
  "scenario": "medium",
  "wacc": 0.12,
  "domains_consumed": ["rail", "financial", "macro", "mining"],
  "fields_consumed": 12,
  "run_at": "loading…"

Where each entity lives — go use it

The model is not a picture. Every entity is a running surface in this intranet.

EntityWhat it isStatusOpen it
DOMAIN13 governed domains, each with a named steward and cadence. 5 live, 4 wiring pending.LiveOperating manual →
ASSUMPTION550+ governed fields — sourced, confidence-scored, valid_to set, banded by scenario.LiveThe registry →
SCENARIO6 named scenarios (3 reviewed, 3 draft). Every model run picks one; bands resolve to concrete values.LiveIn the registry →
SOURCEProvider, source_date and confidence on every assumption. No separate table — lives on the field.LiveDownload CSV →
MODELSector decision models in external repos — they read the API at runtime.ExternalDecision tools →
RUNAudit record of every model execution — assumptions_commit, scenario, who, when. Closes the governance loop.Layer 2In the roadmap
Output provenanceprovenance.json written alongside every artifact — embeds run_id, assumptions_commit, scenario, model_version. No separate registry needed.Layer 2Template →