L2

Pearl Layer 2 — Intervention

Canonical do-calculusP(Y | do(X)) — sever incoming edges to X, re-simulate, report ATE

do(·) parameter overrides

Outcomes to report

Backend call: POST /api/pearl/l2/do. Source: src/minerals/pearl_layers.py do_compare + mutilated_graph_for_do.

L2

How to read this output

The result has three sections: (1) intervention summary chips, (2) Mean ATE tiles per outcome, (3) one factual-vs-intervention line chart per outcome. Read them as 'what happens to Y if I force X = x going forward.'

do(param)the structural-parameter value pinned by graph surgery for this run

e.g. do(substitution_elasticity=0.8) → that parameter is held at 0.8 for the entire run, regardless of upstream causes

Mean ATE per outcometime-averaged Average Treatment Effect: (1/T)·Σ_t [Y_t(intervention) − Y_t(factual)]. Sign tells direction; magnitude tells size.

e.g. ATE(P) = −0.015 → on average, the intervention lowered price by 0.015 normalised units across the horizon

red ↑ / green ↓ tile colorsred = intervention raised the outcome (bad for price/shortage, good for supply); green = lowered it. Color is opinionated against price-rising effects.
factual line (gray) vs intervention line (indigo)per-year trajectories under the un-intervened SCM (factual) vs. the mutilated SCM with do(·) applied (intervention)

e.g. If the indigo line sits below the gray line for most of the horizon, the intervention reduced that outcome on average

ATE_t (per-year)the gap between intervention and factual at year t. Look at the chart: ATE_t at any year is the vertical distance between the two lines.

Key takeaway: L2 ATE is a forward-looking expectation: 'what would Y look like on average if we forced X = x from now on?' It is NOT 'what would have happened in some specific past' — that's L3.

L2

What L2 (Intervention) computes

Formal: P(Y | do(X = x)) — sever the structural equation for X, pin X = x, re-simulate

Graph surgery (mutilated SCM)

Original SCM:    X_t  =  f_X( parents(X)_t , noise_X_t )
After do(X=x):   X_t  :=  x                  (incoming edges to X removed)

All other structural equations of the model remain unchanged.

Example structural equation (substitution supply)

Q_sub_t  =  shock.export_restriction_t · Q_t
             · clamp( 0, sub_cap,
                      sub_elasticity · max(0, P_t/P_ref − 1) )

do(sub_elasticity = 0.8) replaces the 'sub_elasticity' literal in
the equation above — the rest of the ODE continues to run.

Average Treatment Effect per outcome

ATE_t (Y)   =  Y_t^(intervention)  −  Y_t^(factual)
mean ATE(Y) =  (1/T) · Σ_t ATE_t (Y)             [reported per outcome]

Caveat: L2 answers 'what would Y look like if we forced X = x going forward?'. It runs a fresh forward simulation under the intervened SCM — it does NOT condition on a specific past trajectory. For that, use L3.

Source: src/minerals/pearl_layers.py — do_compare(), mutilated_graph_for_do(); model in src/minerals/model.py

No L2 intervention run yet

Pick a scenario, enable parameters to intervene on, set their values, then click Run L2 Intervention. The engine performs graph surgery on the SCM and returns factual vs. intervened trajectories with per-outcome ATE.