diff --git a/.github/workflows/site-provenance.yml b/.github/workflows/site-provenance.yml index c9fa565..2dd5ec9 100644 --- a/.github/workflows/site-provenance.yml +++ b/.github/workflows/site-provenance.yml @@ -12,7 +12,7 @@ on: jobs: charts: - name: validation/index.html matches its sources + name: model validation pages match their chart sources runs-on: ubuntu-latest timeout-minutes: 5 steps: @@ -22,6 +22,8 @@ jobs: python-version: "3.13" - name: Charts are regenerable from committed data run: python3 validation/figures/make_charts.py --check + - name: OLG showcase stats table matches the committed transition data + run: python3 olg/figures/make_showcase.py --check - name: Economy page matches committed data and forecasts run: python3 economy/build.py --check - name: Homepage hero fan matches the current forecast artifact diff --git a/assets/make_current_outlooks.py b/assets/make_current_outlooks.py index 1d3c0ce..f0c4ba7 100644 --- a/assets/make_current_outlooks.py +++ b/assets/make_current_outlooks.py @@ -47,7 +47,7 @@ def ymap(v): f'', f'', f'', - 'real GDPconsumption', + ] for i in (0, 4, 8, 12, 16, 20): out.append( @@ -55,7 +55,10 @@ def ymap(v): ) out += [ "", - "
Latest official baseline available on 21 July 2026. Levels are indexed to 2026Q1 so the two series can share one honest scale. Source: OBR March 2026 detailed forecast tables; committed values in papers/obr-macro/figures/current_outlook.csv.
", + '
' + 'Real GDP' + 'Household consumption' + '
', "", ] return "\n".join(out) diff --git a/assets/make_hero.py b/assets/make_hero.py index f3ab212..7155f6f 100644 --- a/assets/make_hero.py +++ b/assets/make_hero.py @@ -123,12 +123,14 @@ def band(lo, hi): f'' ) - out.append( - f'{esc(quarters[0])}' - ) - out.append( - f'{esc(quarters[-1])}' - ) + # Year labels along the x-axis: the first quarter's year at the left edge, + # then each subsequent year at its Q1 boundary. + for i, q in enumerate(quarters): + year, qtr = q.split("Q") + if i == 0 or qtr == "1": + out.append( + f'{esc(year)}' + ) # No in-SVG caption: the
next to this figure says the same thing # at real body size. Below 1000px the viewBox scales to roughly half, which # would render these 11px labels at ~6px, so style.css hides .hf-tick there diff --git a/connect/index.html b/connect/index.html index b52085a..beabe23 100644 --- a/connect/index.html +++ b/connect/index.html @@ -19,7 +19,7 @@ - + @@ -276,7 +281,7 @@ Home Economy Models - Track record + Forecasts Use @@ -295,20 +300,26 @@

Run a model your way.

- Run hosted models through MCP, install the shared CLI, or use the - Python packages. The public endpoint needs no PolicyEngine account or - API key. (Some clients gate custom connectors by plan — details per client below.) + Three routes to the same models. The public endpoint needs no + PolicyEngine account or API key — though some clients gate custom + connectors by plan.

+ +
-

No code? You don't need any of this to read the results: - browse the economy dashboards or check the - forecast track record.

-

Score a reform? Score a reform → - walks through pricing a tax or benefit change with the hosted models — no - setup needed.

+

No code? Browse the economy + dashboards or the forecast track record — + no setup at all. To price a tax or benefit change with the hosted models, + score a reform →.

@@ -324,41 +335,41 @@

Connect in three steps.

- 1 + 1

Copy the server URL

https://policyengine--policyengine-macro-mcp-serve.modal.run/mcp
- 2 + 2

Add a Claude connector

On claude.ai or in the Claude Desktop app, open Settings → Connectors, choose “Add custom connector”, and paste the URL.

- 3 + 3

Ask a policy question

-

Try a US rate shock, a UK forecast, or a household reform in either country — copy an example prompt below.

+

A US rate shock, a UK forecast, a household reform — copy an example prompt below.

Example prompts
    -
  • frbus_shock
    What happens to US GDP and unemployment if the Fed raises rates by 100 basis points?
  • -
  • score_reform
    Score raising the UK basic rate of income tax to 21p.
  • -
  • forecast_uk
    What is the UK GDP and inflation outlook for the next four quarters?
  • -
  • hank_shock
    How does a 25 basis point US rate cut hit households across the wealth distribution?
  • -
  • calculate_household
    How much tax would a single 35-year-old earning $50,000 pay in the US?
  • +
  • frbus_shockWhat happens to US GDP and unemployment if the Fed raises rates by 100 basis points?
  • +
  • score_reformScore raising the UK basic rate of income tax to 21p.
  • +
  • forecast_ukWhat is the UK GDP and inflation outlook for the next four quarters?
  • +
  • hank_shockHow does a 25 basis point US rate cut hit households across the wealth distribution?
  • +
  • calculate_householdHow much tax would a single 35-year-old earning $50,000 pay in the US?
-

Not sure which tool fits a question? Ask the model to call recommend_model or list_model_capabilities — the server describes what each tool answers.

+

Not sure which tool fits? Ask the model to call recommend_model or list_model_capabilities — the server describes what each tool answers.

Terminal setup @@ -372,24 +383,20 @@

Connect in three steps.

gemini mcp add --transport http policyengine-macro https://policyengine--policyengine-macro-mcp-serve.modal.run/mcp
-
-

- hosted - No account or API key required. Capacity is shared on a fair-use - basis (a few concurrent runs). The first household or population - call in a fresh container can take around 20 seconds while the - tax-benefit models load — other tools respond faster — and forecast - tools take longer: a UK forecast runs a couple of minutes on - first call. -

-

- local - OG-UK long-run scoring (score_reform with - model='og') and dynamic_reform_impact - run locally only — they take tens of minutes, and the hosted - server will tell you so and point to the local CLI. Everything - else on this page is available hosted. -

+
+

access + No account or API key. Capacity is shared on a fair-use basis + — a few concurrent runs.

+

cold start + The first household or population call in a fresh container + takes around 20 seconds while the tax-benefit models load; a UK + forecast runs a couple of minutes on first call. Other tools + respond faster.

+

local only + OG-UK long-run scoring (score_reform with + model='og') and dynamic_reform_impact take + tens of minutes — the hosted server points you to the local CLI. + Everything else here runs hosted.

@@ -402,10 +409,10 @@

Run the shared CLI.

pip install "policyengine-macro[models] @ git+https://github.com/PolicyEngine/macro#subdirectory=integration"
-
US rate shockpe-macro frbus-shock --var rffintay_aerr --shock 1.0
-
UK forecastpe-macro forecast
-
US HANK shockpe-macro hank-shock --kind monetary --size -0.0025
-
Household calculationpe-macro household --country us --people '[{"age":35,"employment_income":50000}]'
+
US rate shock
pe-macro frbus-shock --var rffintay_aerr --shock 1.0
+
UK forecast
pe-macro forecast
+
US HANK shock
pe-macro hank-shock --kind monetary --size -0.0025
+
Household calculation
pe-macro household --country us --people '[{"age":35,"employment_income":50000}]'

View every CLI command →

@@ -428,32 +435,32 @@

Use Python directly.

hosted

Near-term UK fiscal effects

Trace tax and spending changes against the OBR baseline.

- Open OBR setup + Open OBR setup
@@ -463,15 +470,12 @@

Use Python directly.

- + + diff --git a/contact/index.html b/contact/index.html index 3a59029..40fe021 100644 --- a/contact/index.html +++ b/contact/index.html @@ -18,7 +18,7 @@ - + @@ -31,7 +31,7 @@ Home Economy Models - Track record + Forecasts Use @@ -74,13 +74,10 @@

Get in touch.

- + diff --git a/economy/build.py b/economy/build.py index 0d18d94..f6150b2 100644 --- a/economy/build.py +++ b/economy/build.py @@ -785,15 +785,14 @@ def next_open(fc: dict, variable: str, last_observed: str) -> tuple[str, dict]: u_period, u_fc = next_open(okun["forecast"], "unemployment", u_now["period"]) def rng(fc: dict) -> str: - return f"68% range {fmt(fc['lo68'])}%–{fmt(fc['hi68'])}%" + return (f'
68% range ' + f"{fmt(fc['lo68'])}%–{fmt(fc['hi68'])}%") caption = ( - "UK headline indicators: latest official outturn and the model " - "near-term forecast. Outturn vintages as of " - f"{max(gdp['vintage'], cpi['vintage'], unemployment['vintage'])}; " - f"GDP and CPI from the boe-svar forecast generated {forecast['generated']} " - f"(conditioned on data through {forecast['data_edge']}); unemployment " - f"from the {okun['round_id']} Okun-satellite round." + "ONS outturns (as of " + f"{max(gdp['vintage'], cpi['vintage'], unemployment['vintage'])}) beside " + 'archived forecast rounds. ' + 'Full horizon →' ) return "\n".join( [ @@ -806,14 +805,13 @@ def rng(fc: dict) -> str: " ", f' Real GDP growth, y/y' f'{fmt(g_now["value"])}% {g_now["period"]}' - f'{fmt(g_fc["median"])}% {g_period} · {rng(g_fc)}', + f'{fmt(g_fc["median"])}% {g_period}{rng(g_fc)}', f' CPI inflation, y/y' f'{fmt(c_now["value"])}% {c_now["period"]}' - f'{fmt(c_fc["median"])}% {c_period} · {rng(c_fc)}', + f'{fmt(c_fc["median"])}% {c_period}{rng(c_fc)}', f' Unemployment rate' f'{fmt(u_now["value"])}% {u_now["period"]}' - f'{fmt(u_fc["median"])}% {u_period} · {rng(u_fc)}' - ' · Okun satellite', + f'{fmt(u_fc["median"])}% {u_period}{rng(u_fc)}', " ", " ", " ", @@ -837,9 +835,10 @@ def home_us_now() -> str: u_base = baseline_next_open(baseline, u_now["period"]) caption = ( - "US headline indicators beside the FRB/US April 2026 LONGBASE " - "baseline. Outturn vintages as of " - f"{max(gdp['vintage'], cpi['vintage'], unemployment['vintage'])}." + "FRED outturns (as of " + f"{max(gdp['vintage'], cpi['vintage'], unemployment['vintage'])}) beside " + "the FRB/US LONGBASE conditioning baseline — not a forecast. " + 'Full sources →' ) return "\n".join( [ diff --git a/economy/index.html b/economy/index.html index c7a5033..b6cae6b 100644 --- a/economy/index.html +++ b/economy/index.html @@ -18,7 +18,7 @@ - + @@ -32,7 +32,7 @@ Home Economy Models - Track record + Forecasts Use @@ -59,10 +59,8 @@

The UK economy, with every number ti
Economy - - UKUnited Kingdom - USUnited States - + UK + US
At a glance @@ -120,11 +118,6 @@

What has happened, and what the model sees next.

-

- “Current” means the newest observation in the committed store—not the - browser clock. GDP growth is derived from the ONS chained-volume level. - Forecast ranges are predictive intervals, not promises. -

@@ -156,7 +149,7 @@

One forecast, with the uncertainty left in.

Research note · 25 July 2026 UK CPI fell to 2.8% in 2026Q2—and the model expects it back above 3% by Q4. Read the CPI note → - All notes + All notes

@@ -292,14 +285,11 @@

Upcoming and recent releases.

- + diff --git a/economy/trends/index.html b/economy/trends/index.html index 2d27bde..6e7eaf1 100644 --- a/economy/trends/index.html +++ b/economy/trends/index.html @@ -18,7 +18,7 @@ - + @@ -32,7 +32,7 @@ Home Economy Models - Track record + Forecasts Use @@ -60,10 +60,8 @@

UK economic trends.

Trends - - UKUnited Kingdom - USUnited States - + UK + US
@@ -128,10 +126,7 @@

UK CPI fell to 2.8% in 2026Q2. The model expects inflation back above 3% by Q4.

Read the CPI note → -

- “Latest” means the newest observation in the committed store—not the - browser clock. GDP growth is derived from the ONS chained-volume level. -

+ @@ -139,13 +134,10 @@ - + diff --git a/economy/us/index.html b/economy/us/index.html index 4ed929f..57ba803 100644 --- a/economy/us/index.html +++ b/economy/us/index.html @@ -18,7 +18,7 @@ - + @@ -32,7 +32,7 @@ Home Economy Models - Track record + Forecasts Use @@ -65,10 +65,8 @@

The US economy, tied to its observat
Economy - - UKUnited Kingdom - USUnited States - + UK + US
At a glance @@ -156,10 +154,6 @@

What has happened in the US economy.

-

- “Current” means the newest observation in the committed store—not the - browser clock. GDP and CPI growth are derived from official level series. -

@@ -272,14 +266,11 @@

Freshness you can verify.

- + diff --git a/economy/us/trends/index.html b/economy/us/trends/index.html index eb642b4..059b1fc 100644 --- a/economy/us/trends/index.html +++ b/economy/us/trends/index.html @@ -18,7 +18,7 @@ - + @@ -32,7 +32,7 @@ Home Economy Models - Track record + Forecasts Use @@ -61,10 +61,8 @@

US economic trends.

Trends - - UKUnited Kingdom - USUnited States - + UK + US
@@ -121,10 +119,7 @@

Three signals, shown over time.

-

- “Latest” means the newest observation in the committed store—not the - browser clock. GDP and CPI growth are derived from official level series. -

+ @@ -132,13 +127,10 @@

Three signals, shown over time.

- + diff --git a/forecasts/index.html b/forecasts/index.html index 6ddeefa..b9824a2 100644 --- a/forecasts/index.html +++ b/forecasts/index.html @@ -19,7 +19,7 @@ - + + + + + + + + + + +
+
+
+

model 03 — Federal Reserve macroeconomic model · frb-us · US · hosted

+

+ Test US monetary and fiscal shocks. +

+

+ Trace how a funds-rate or fiscal shock affects US output, inflation, + and unemployment quarter by quarter using the April 2026 baseline. +

+
+
+ + + +
+
+ code +

Ask the server, or install it.

+
+
+

+ Five steps from a hosted one-liner to the full 284-variable model in + Python; each step's code sits alongside its narrative. +

+
+ +
+
+
+
1

Ask the hosted server

+

Three MCP tools

+

The quickest route is the hosted MCP server, with three tools: frbus_list_variables (the shockable levers, with units), frbus_shock (solve and return impulse responses), and frbus_summary (vintage and validation provenance).

+

Speed

+

A solve takes about 3 seconds cold and well under a second warm — the fastest member of the suite.

+

CLI mirrors

+

The same three tools are on the CLI, no server round-trip required.

+ +
+ +
+
2

Two traps

+

Units are per-lever

+

rffintay_aerr is in percentage points (1.0 = a 100bp tightening), but spending levers such as egfe_aerr are in log points of quarterly growth, not billions of dollars — a dollar-sized number there diverges the solver.

+

Each rule reads its own add-error

+

rffintay_aerr works only under inertial_taylor; under taylor it is rejected with a pointer to rfftay_aerr rather than silently returning all-zero responses.

+ +
+ +
+
3

Choose the policy rule

+

Usually the point of the exercise

+

frbus_shock takes a policy_rule: inertial_taylor (the default, the LONGBASE rule and the one the validation numbers use), taylor, or fixed_funds_rate, which holds the funds rate on its baseline path so there is no endogenous monetary offset.

+

Why it matters

+

A four-quarter federal-purchases shock of 0.01 log points peaks higher on real GDP with the funds rate fixed — and the price-level response is larger still.

+ +
+ +
+
4

Install the package

+

When to install

+

For the full 284-variable model, the Board's own demo scripts, or anything the three tools do not expose — separate from the rest of the suite, with its own Python API.

+

No data step

+

The Board's raw materials are vendored unmodified, so there is no data-download step, but you clone rather than pip-install from an index.

+ +
+ +
+
5

Run a 100bp shock in Python

+

The workflow

+

Load LONGBASE, set the Board's demo fiscal configuration, add-factor with init_trac so the baseline reproduces LONGBASE exactly, add the shock, solve.

+

+ a runnable copy of exactly this is + examples/monetary_policy_shock.py. The API mirrors the + essentials of pyfrbus's Frbus class — + Frbus(path), .init_trac(), + .solve(), .exogenize() — so the Board's + demo scripts port across with minimal change. +

+

Series you will reach for

+
+ + + + + + + + + + +
Frequently used frb-us series
codevariablerole
rffintay_aerrTaylor-rule policy-rate error termshock instrument — +1 for a 100bp tightening
dfpdbt / dfpsrpFiscal policy switchesexogenous — set the debt / surplus closure
xgdpReal GDPendogenous — headline output
lurUnemployment rateendogenous — rate, pp
picxfeCore PCE inflationendogenous — rate, pp
rffFederal funds rateendogenous under the rule, pp
+ +
+
+ + +
+
+ +
+ + + + + + + + diff --git a/frb-us/index.html b/frb-us/index.html index 98b2f7c..6ced3f1 100644 --- a/frb-us/index.html +++ b/frb-us/index.html @@ -19,7 +19,7 @@ - + + + + + + + + + + + + +
+
+
+

model 03 — Federal Reserve macroeconomic model · frb-us · US · hosted

+

+ Test US monetary and fiscal shocks. +

+

+ Trace how a funds-rate or fiscal shock affects US output, inflation, + and unemployment quarter by quarter using the April 2026 baseline. +

+
+
+ + + +
+
+ methodology +

Parse, add-factor, solve by Newton.

+
+
+

+ The five stages of the frb-us pipeline, from the Board's published + artefacts to an impulse response. Each step's formal structure follows + its narrative; equations render locally (no CDN). The full derivations + are in the working paper. +

+
+ +
+
+
+
1

Parse the Board's model file

+
+ + + One source of truth: the Board's model.xml + + model.xml + variables, equations, + coefficients, endo/exo + public domain + + parse + + normalise + rewrite EViews lags, + substitute coefficients, + flip to residual form + + + 284 equations + behavioural + identity, + each with aerr + trac + VAR ("standard") set + nothing re-typed or re-specified — the XML declares the model's semantics + +
click to expand
+
+

The published artefacts

+

The Federal Reserve Board publishes FRB/US whole: the equation system as model.xml, the LONGBASE historical and projection database, the documentation, and a reference Python solver, pyfrbus — all in the public domain.

+

One source of truth

+

The parse stage reads model.xml directly: variables, equations, per-equation coefficients, and the endogenous / exogenous classification for the VAR-expectations ("standard") equation set. Nothing is re-typed or re-specified — the XML file is the single source of truth for the model's semantics. That yields 284 endogenous variables and their equations, classified into behavioural and identity blocks exactly as the Board's file declares them.

+

Residual form

+

Each equation is normalised — EViews lag and difference notation rewritten, coefficient references substituted by their published values — flipped to residual form \(F_i(\cdot) = 0\), and augmented with its additive shock term (_aerr) and a tracking residual (_trac), so each equation solves \(F_i + \mathrm{aerr}_i + \mathrm{trac}_i = 0\).

+ +
+ +
+
2

Load LONGBASE, then add-factor

+
+ + + Add-factoring pins the baseline + + LONGBASE.TXT + April 2026 vintage + history + projection + quarterly PeriodIndex + + + init_trac + set each equation's + tracking residual, + per quarter + + solve + + = LONGBASE + reproduced to + machine precision + + shocks are then imposed on top of the add-factored baseline + +
click to expand
+
+

The baseline database

+

The load stage brings LONGBASE.TXT — the April 2026 vintage — into a pandas frame on a quarterly PeriodIndex. This is the Board's own historical and projection database, and it conditions everything that follows: every simulation is read as a deviation from this baseline.

+

Tracking residuals (init_trac)

+

init_trac sets each equation's tracking residual so that solving the model reproduces the database exactly. This is the Fed's own device, used the Fed's own way — the direct analogue of the OBR emulator's anchoring. Shocks are then imposed on top of the add-factored baseline.

+ +
+ +
+
3

Solve by damped Newton

+
+ + + Simultaneous within the quarter, recursive across quarters + + one quarter: damped Newton + analytic sparse Jacobian (sympy) + sparse LU solve (SuperLU) + damp: halve the step to stay in-domain + check convergence, else iterate + + + Newton iteration + + converged + + t + + t+1 + + t+2 + each quarter starts from + the previous solution + +
click to expand
+
+

Period by period

+

Under VAR expectations every expectations term depends only on current and lagged observables, so the model is simultaneous within each quarter but recursive across quarters. The solver therefore works period by period, starting each quarter from the previous quarter's solution.

+

Analytic sparse Jacobian

+

Each quarter, a damped Newton iteration solves the full simultaneous system with an analytic sparse Jacobian — differentiated symbolically with sympy, factorised with scipy.sparse.linalg.splu. Damping backtracks the step only to stay inside the domain of definition (no logs of non-positive arguments, no NaNs). Defaults are \(\mathrm{xtol} = 10^{-8}\), \(\mathrm{rtol} = 5\times 10^{-4}\).

+

One block, not many

+

The reference implementation decomposes each period into recursive and simultaneous blocks; this one solves the whole period at once. Both approaches solve the same \(F(x) = 0\), which is why the two agree to solver tolerance rather than to some negotiated threshold — see how far to trust it.

+ +
+ +
+
4

Policy rules

+
+ + + 0/1 switches select which rule sets the funds rate + + inertial_taylor + default, LONGBASE rule + shock: rffintay_aerr + + taylor + same bracket, no inertia + shock: rfftay_aerr + + fixed_funds_rate + baseline path held + no monetary offset + + + + + funds rate rff — one rule active per run + each rule reads its own add-error; the wrong one is rejected, not zeroed + +
click to expand
+
+

A modular funds rate

+

Monetary policy is modular: exogenous 0/1 switches in the model file select which rule sets the funds rate. Three are exposed through frbus_shock: inertial_taylor (the default, the LONGBASE rule and the one the validation numbers use), taylor, and fixed_funds_rate, which holds the funds rate on its baseline path so there is no endogenous monetary offset.

+

The rule choice is the exercise

+

The choice is usually the economic point of the exercise. Run the same government-purchases shock (1% of GDP) under both settings and the multiplier moves from 0.72 in year one under the inertial Taylor rule to 0.99 in year two with the funds rate fixed — and the price-level response is larger still without the monetary offset. The overview places both inside the published cross-model ranges.

+

Each rule reads its own error term

+

Each policy rule reads its own add-error, so rffintay_aerr works only under inertial_taylor; asking for it under taylor is rejected with a pointer to rfftay_aerr rather than silently returning all-zero responses — one of the two traps the code page walks through before you shock anything.

+ +
+ +
+
5

Shock and compare

+
+ + + Impulse response = shocked path minus baseline path + + baseline solve + add-factored LONGBASE + + shocked solve + aerr += shock, same window + + + + difference + sim − baseline + + + 100bp IRF + GDP −0.55% + lur +0.26pp + picxfe −0.034pp + shock units are per-lever — pp for the policy-rate error, log points of growth for spending + +
click to expand
+
+

Shocks are error-term additions

+

A shock is an addition to an equation's error term — rffintay_aerr for the policy rule, for instance. Solve shocked and baseline paths over the same window and read the difference. Shock sizes are per-lever and not interchangeable — percentage points for the policy-rate error, log points of quarterly growth for the spending levers; the code page spells out the units trap.

+

What comes out

+

The headline 100bp tightening lands where the Board's documentation says it should: the funds rate up 1.000pp on impact, real GDP bottoming at −0.55% in 2027Q4, unemployment peaking +0.26pp, core inflation down 0.034pp at its trough. The same four numbers are the simulation-properties row of the validation tables.

+

What stays out of scope

+

The pipeline covers the VAR-expectations model only: the model-consistent-expectations variants, forward guidance, and the reference implementation's mcontrol trajectory-targeting routine are not implemented, and there is no bridge from a PolicyEngine reform to model variables — see the known limits.

+ +
+
+ +
+
+ + + + +
+ + + + + + + + diff --git a/frb-us/validation/index.html b/frb-us/validation/index.html new file mode 100644 index 0000000..f0e54b5 --- /dev/null +++ b/frb-us/validation/index.html @@ -0,0 +1,278 @@ + + + + + +FRB/US validation — PolicyEngine Macro + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+

model 03 — Federal Reserve macroeconomic model · frb-us · US · hosted

+

+ Test US monetary and fiscal shocks. +

+

+ Trace how a funds-rate or fiscal shock affects US output, inflation, + and unemployment quarter by quarter using the April 2026 baseline. +

+
+
+ + + +
+
+ how far to trust it +

Machine precision on the baseline; the Fed's own noise floor on shocks.

+
+
+

+ This is a replication with a published anchor — the + sharpest-tested member of the suite, because for once the ground truth + is exact rather than statistical. Three invariants carry the weight, + all enforced in continuous integration, including four like-for-like + scenarios gated against vendor-generated references. +

+
+
+
+ tracking invariant + 5.6e−17 + vs LONGBASE, all 284 endos × 20 quarters; CI gate 1e−8 +
+
+ shock vs pyfrbus 1.0.0 + 6.0e−9 + the committed reference; CI gate 1e−6 +
+
+ shock vs pyfrbus 1.1.1 + 1.4e−8 + the Board's April 2026 LONGBASE vintage used here +
+
+ the Fed vs the Fed + 1.3e−8 + pyfrbus 1.1.1 against 1.0.0 — the yardstick +
+
+
+

+ The last figure frames the other three. The Board's two releases ship + a byte-identical model.xml and LONGBASE and + differ only in how the Newton routine reuses its LU factorisation — + yet they disagree with each other by + 1.3×10−8, as much as this + implementation disagrees with either. Agreement therefore sits at the + reference implementation's own numerical noise, not at a tolerance + anyone chose. All three residuals concentrate in the same near-zero + expectational-gap series (wpsn, zgap05). +

+
+ +frb-us: residuals against the Fed’s pyfrbus, log scale +Horizontal bar chart on a base-10 logarithmic axis of maximum absolute residuals; shorter is closer. ours vs LONGBASE (tracking invariant): 5.6e-17; pyfrbus 1.1.1 vs LONGBASE: 1.1e-08; ours vs pyfrbus 1.0.0 (shock): 6.0e-09; ours vs pyfrbus 1.1.1 (shock): 1.4e-08; pyfrbus 1.1.1 vs 1.0.0 — the Fed’s own two releases: 1.3e-08. The framing comparison is the last row: the Federal Reserve's own two pyfrbus releases disagree with each other by as much as this implementation disagrees with either, so our agreement sits at the scale of the reference implementation's own numerical noise rather than at a chosen tolerance. + +1e-18 + +1e-16 + +1e-14 + +1e-12 + +1e-10 + +1e-8 +ours vs LONGBASE (tracking invariant) + +5.6e-17 +pyfrbus 1.1.1 vs LONGBASE + +1.1e-08 +ours vs pyfrbus 1.0.0 (shock) + +6.0e-09 +ours vs pyfrbus 1.1.1 (shock) + +1.4e-08 +pyfrbus 1.1.1 vs 1.0.0 — the Fed’s own two releases + +1.3e-08 + +
Maximum absolute residuals, log scale — lower is closer. The bottom bar is the framing one: the Fed's own two releases disagree by as much as we disagree with either. On the tracking invariant, pyfrbus 1.1.1 reproduces LONGBASE to 1.1×10−8 against this implementation's 5.6×10−17. Source: papers/frb-us validation tables.
+
+
+ + + + + + + + + + + + + + + + + + + +
Validation results against the Federal Reserve Board's own artefacts
testwhat it checksresult
Tracking invariantAfter init_trac, solving the baseline must reproduce LONGBASE for all 284 endogenous variables over 2026Q1–2030Q4.Max absolute error 5.6e−17 — machine precision, against a 1e−8 gate. The Board's own pyfrbus 1.1.1 reproduces LONGBASE to 1.1e−8 on the same test.
Cross-validationAn identical 100bp rffintay_aerr shock run in both implementations, compared across all 284 variables and all 20 quarters.6.0e−9 against pyfrbus 1.0.0 and 1.4e−8 against 1.1.1 — where the two Board releases differ from each other by 1.3e−8.
Simulation propertiesA 100bp tightening must move the right things in the right direction by the right order of magnitude.rff +1.000pp on impact, GDP trough −0.55%, unemployment peak +0.26pp, core inflation trough −0.034pp — consistent with the Board's documented VAR-expectations properties.
+

+ Multipliers. Government-purchases and personal-tax + shocks of 1% of GDP land inside every published range they are + compared against — the last three rows below. +

+
+ + + + + + + + + + +
frb-us against the Fed's pyfrbus and published multiplier ranges
QuantityOursPublishedReading
Tracking invariant, max abs. error5.6×10−17CI gate <10−8; pyfrbus 1.1.1 gives 1.1×10−8machine precision
Shock responses vs pyfrbus 1.0.06.0×10−9at the reference's noise floor
Shock responses vs pyfrbus 1.1.11.4×10−81.1.1 vs 1.0.0: 1.3×10−8the Fed's releases differ as much as we do
Gov. purchases multiplier, yr 1, inertial Taylor rule0.720.7–1.0 (Coenen et al. 2012); Ramey (JEP 2019): "a surprisingly narrow range of 0.6 to 1"inside range
Gov. purchases multiplier, yr 2, fixed funds rate0.99"roughly one" pegged; 1.1–1.2 with accommodationmatches the Board's characterisation
Personal tax cut multiplier, yrs 1–20.22 → 0.320.2–0.4; CBO central ≈0.3inside both ranges
+

+ Uncertainty engine. The implementation now supports + seeded stochastic simulations that jointly resample the official + stochastic-equation residual vector, preserving contemporaneous + dependence and reporting failed replications. Model and LONGBASE + packages are also verified against separate SHA-256 provenance gates. + Published probability results still require a reviewed simulation + design; model-consistent expectations remain unavailable. +

+
+

+ The committed reference stays pinned to pyfrbus 1.0.0 so the gate has a + fixed anchor; the 1.1.1 comparison is a recorded cross-check. CI goes + beyond re-checking a committed CSV: a dedicated job re-runs the Board's + pyfrbus from source in a throwaway environment and gates against that + freshly generated solution, and a weekly scheduled run repeats the suite + across Python 3.10–3.12 so dependency drift surfaces without a pull + request. Full tables in + VALIDATION.md + and the working paper. +

+

+ This is an independent implementation built from the Federal Reserve + Board's published model equations and data. It is not produced, + maintained, or endorsed by the Federal Reserve, and its results + should not be presented as official Federal Reserve estimates. +

+
+ +
+
+ limits +

Known limitations.

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Known limits of the frb-us implementation
limitdetail
VAR expectations onlyThe model-consistent-expectations (MCE) equation variants are not implemented — Frbus(path, mce=...) raises NotImplementedError — so anything that turns on agents anticipating announced future policy, forward guidance most obviously, is out of scope.
No mcontrolThe reference implementation's trajectory-targeting routine is out of scope. Stochastic simulation is implemented — stochsim_bands() produces seeded residual-bootstrap percentile bands — but the hosted frbus_shock path returns a single deterministic projection, so bands are not available over MCP.
Hosted, but only as raw shocksThe hosted tools and their pe-macro mirrors expose one lever, one shock size, a policy rule and a headline set of responses — not the full 284-variable model. Anything beyond that (the Board's demo scripts, exogenize, arbitrary variable sets) means installing the package and using its Python API.
No PolicyEngine reform bridgeReforms enter as shocks to model variables, not as statute. There is no equivalent of the OBR emulator's static-costing bridge from a PolicyEngine reform dict — score_reform refuses model="frbus" outright and points at frbus_shock — so US reform scoring stops at pe-microsim's static answer. Hosting did not change this.
Approximate published comparisonsThe 100bp funds-rate responses in the Board's 2014 FEDS Note are read off charts, so those comparisons are approximate by nature: our output-gap trough of −0.50pp against the Note's roughly −0.4pp reflects vintage and shock-design differences, not a claimed match.
VintageThe April 2026 model.xml and LONGBASE as published by the Board. Later Board vintages have not been adopted.
+
+
+ +
+ + + + + + diff --git a/index.html b/index.html index 2259ea7..dfb65ce 100644 --- a/index.html +++ b/index.html @@ -19,7 +19,7 @@ - + @@ -33,7 +33,7 @@ Home Economy Models - Track record + Forecasts Use @@ -45,12 +45,12 @@

Open economic models · public evidence

- Open economic models, -
with the evidence attached. + Open UK and US economic models, +
scored in public.

- Run open UK and US policy and macro models. Inspect the assumptions, - preserved data, validation, and forecast record behind every result. + Run the models yourself. Every result carries its assumptions, + official data vintage, validation, and forecast record.

Run a hosted model @@ -76,12 +76,14 @@

-2026Q2 -2029Q2 +2026 +2027 +2028 +2029
- Current UK GDP growth forecast. Explore the model. + Current UK GDP growth forecast. Explore the model →

@@ -93,38 +95,30 @@

00 — at a glance

Latest outturns beside the model's near-term view.

-
- - -
-
-
+
+
+

United Kingdom

- + - - - + + +
UK headline indicators: latest official outturn and the model near-term forecast. Outturn vintages as of 2026-07-26; GDP and CPI from the boe-svar forecast generated 2026-07-29 (conditioned on data through 2026Q1); unemployment from the 2026-07-28 Okun-satellite round.ONS outturns (as of 2026-07-26) beside archived forecast rounds. Full horizon →
IndicatorLatest outturnModel near-term forecast
Real GDP growth, y/y0.9% 2026Q11.1% 2026Q2 · 68% range 0.6%–1.7%
CPI inflation, y/y2.8% 2026Q22.5% 2026Q3 · 68% range 1.8%–3.3%
Unemployment rate5.0% 2026Q15.0% 2026Q2 · 68% range 4.9%–5.0% · Okun satellite
Real GDP growth, y/y0.9% 2026Q11.1% 2026Q2
68% range 0.6%–1.7%
CPI inflation, y/y2.8% 2026Q22.5% 2026Q3
68% range 1.8%–3.3%
Unemployment rate5.0% 2026Q15.0% 2026Q2
68% range 4.9%–5.0%
-

- Outturns are ONS series (ABMI, D7G7, MGSX) from the committed vintage - store; forecasts come from the archived - boe-svar round. Full sources and the complete - horizon are on the economy page. -

-
- +
+

United States

- + @@ -134,28 +128,8 @@

Latest outturns beside the model's near-term view.

US headline indicators beside the FRB/US April 2026 LONGBASE baseline. Outturn vintages as of 2026-07-27.FRED outturns (as of 2026-07-27) beside the FRB/US LONGBASE conditioning baseline — not a forecast. Full sources →
IndicatorLatest outturnLONGBASE baseline path
Real GDP growth, y/y2.7% 2026Q12.5% 2026Q2
-

- Outturns are FRED series (GDPC1 from the BEA, CPIAUCSL and UNRATE from - the BLS) from the committed vintage store; the right column is the - FRB/US LONGBASE conditioning baseline, not a forecast. Full sources - are on the US economy page. -

+
-
- @@ -187,13 +161,6 @@

Check forecasts against what happened

Explore all six models or compare their assumptions side by side.

-
-
No account neededPublic hosted tools, no API key
-
Point-in-time dataEvery official vintage preserved as published
-
Scored in publicForecasts archived before outturns exist
-
Failures publishedUnder-performance reported beside wins
-
-
02 — validation @@ -205,7 +172,7 @@

What the evidence shows—and what it doesn't.

Replication, forecast performance, statutory correctness, and calibration are different kinds of evidence—and where no independent check exists, we say so. - Compare the evidence → + Compare the evidence →

obr-macroAnchored replication

Tracks the March 2026 EFO to 0.15% GDP MAPE with published anchors; free-running error is 5.75%.

@@ -243,14 +210,11 @@

Run hosted models—or use the code directly.

- + diff --git a/models/index.html b/models/index.html index 42e20f5..0041f7e 100644 --- a/models/index.html +++ b/models/index.html @@ -4,16 +4,16 @@ Models — PolicyEngine Macro - + - + - + @@ -26,7 +26,7 @@ Home Economy Models - Track record + Forecasts Use @@ -45,28 +45,125 @@

Choose the model that fits the quest

Six open UK and US models cover household policy, fiscal and monetary shocks, near-term forecasts, and long-run structural change.

-
@@ -87,7 +184,7 @@

Choose the model that fits the quest

Six model classes, side by side.

-
+
@@ -96,7 +193,7 @@

Six model classes, side by side.

- + @@ -104,13 +201,6 @@

Six model classes, side by side.

The six models compared across question class, method, country, vintage, verification and run surface
OBR macroeconometric
obr-macro
Bank of England structural VAR
boe-svar
Federal Reserve model
frb-us
US heterogeneous-agent NK
us-hank
Overlapping generations
psl-og
PolicyEngine microsimulation
pe-microsim
MethodStructural macroeconometric emulator — Gauss–Seidel over 372 equations per quarterBayesian structural VAR — posterior sampling with zero + sign restrictions naming the shocksLarge-scale macroeconometric model — 284 equations solved under VAR expectationsTwo-asset HANK (Auclert et al. 2021) — first-order sequence-space impulse responsesDynamic general equilibrium (OLG) — steady-state root-find and transition-path iterationStatic tax-benefit microsimulation — direct rule evaluation, no behavioural response
CountryUKUKUSUSUK (OG-UK)UK & US
Data vintageOBR Economic and Fiscal Outlook, March 2026ONS, BoE and FRED series — coefficients estimated through 2025Q1; conditioned through the 2026Q1 data edgeThe Fed's model.xml and LONGBASE database, April 2026The published Econometrica 2021 calibration; no data vintageONS, OBR and BoE national accounts as calibration targets; OG-UK 0.3.2 (from GitHub)UK & US statute; enhanced FRS 2023–24 microdata for UK population runs
Evidence class (numbers →)Validated for selected scenarios; published anchor is not out-of-sample validationValidated replication for selected outputsValidated software replication; substantive comparisons are approximateValidated replication of published results; not a forecaster; distributional outputs are first-order approximationsResearch prototype; calibrated counterfactual with no ground truthDeterministic rule calculation; population estimates add survey uncertainty
Evidence class (numbers →)Validated for selected scenarios; published anchor is not out-of-sample validationValidated replication for selected outputsValidated software replication; substantive comparisons are approximateValidated replication of published results; not a forecaster; distributional outputs are first-order approximationsResearch prototype; calibrated counterfactual with no ground truthDeterministic rule calculation; population estimates add survey uncertainty
Reform scoringYes — shocks to exogenous model variables; static-costing bridge from PolicyEngine reformsNo — the baseline/conditioning member: it reads the economy reforms are scored againstNo — shock experiments only (funds-rate or fiscal shocks under VAR expectations); score_reform refuses model="frbus", as no PolicyEngine reform bridge existsNo — stylized shocks only (monetary, fiscal spending, productivity); the labor tax is endogenous, so score_reform refuses model="hank"Yes — a PolicyEngine policy translated into estimated tax functionsYes — a PolicyEngine parameter + a new value, applied directly to households
Out of scopeArbitrary reform incidence or borrowing through the current adapterThe causal effect of a specific statutory reformPolicyEngine reform effects, anticipated-policy paths, or MCE scenariosForecasts, detailed tax reforms, or nonlinear/state-dependent dynamicsA short-run forecast or independently validated reform effectGDP, inflation, interest rates, or macro feedback
Run surfaceHosted (CLI, MCP, Python API)Hosted (CLI, MCP, Python API)Hosted (CLI, MCP, Python API) — raw shocks onlyHosted (CLI, MCP, Python API) — stylized shocks onlyLocal onlyHosted
-
- Comparison rule. These models use different horizons, - baselines, mechanisms, and output definitions. Related results should - be interpreted together, not added, averaged, or ranked. The CLI and - JSON result schema retain units, time basis, provenance, and an explicit - comparability label for this reason. -

Compare two models

@@ -119,6 +209,123 @@

Compare two models

+ +
+ +
+
+ validation +

How each model is validated.

+
+
+

+ The six models support different kinds of checks; the gradient below + groups them into three classes — open a model's validation page for the + full evidence. In plain English: the OBR emulator tracks the published + forecast within 0.15% on GDP, the SVAR's frozen-edge test put 14 of 14 + outturns inside its 68% bands (single-origin, suggestive), and frb-us + reproduces the Fed's database to machine precision. +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ModelVerification classWhat it is checked againstHeadline result
obr-macroReplication with a published anchorMarch 2026 EFO; HMRC ready reckoner; ONS outturnsAnchored GDP MAPE 0.15%, consumption 0.25%
boe-svarReplication with a published anchorBrignone & Piffer (2025); ONS outturnsFEVD shares 42.1% / 49.5% vs the paper's ~40% / ~50%
frb-usReplication with a published anchorThe Fed's LONGBASE and pyfrbus; published multiplier rangesTracks LONGBASE to machine precision (5.6×10−17)
us-hankReplication with a published anchorAuclert et al. (2021), via the authors' toolkit18 gated tests: targets, market clearing, shock responses
pe-microsimChecked against implemented legislationImplemented UK and US legislation, rule by ruleHousehold maths exact; population adds survey error
psl-ogCalibrated counterfactual — no ground truthONS/OBR aggregates as targets, not a validation setTargets met by construction; no ground truth
+
+

The same questions, answered by other shops.

+

+ Imposed matches are anchors, not validation — the stronger evidence is + out-of-sample forecasts, independent costings, and solver residuals. + Below, published institutional answers sit beside ours. +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
QuestionOursTheirsHow comparable
Long-run effect of a 1pp basic-rate rise (psl-og)Not yet produced — open validation workOBR Working Paper 22, Table 5.1: GDP/person −0.1%, labour supply −0.2%, productivity +0.1%Future benchmark — same scenario; the target we will be judged against, not a match claimed today
Cost of cutting the basic rate 1p, 2026–27 (obr-macro + pe)£6.46bn staticHMRC ready reckoner (June 2025): £6.9bn, rising to £8.2bn by 2028–29Direct — −6.4% in year one; HMRC's figure embeds behavioural response
Government-purchases multiplier, year one (frb-us)0.72 (inertial Taylor rule); 0.99 in year two with the funds rate fixedRamey (JEP 2019): the literature sits "in a surprisingly narrow range of 0.6 to 1"Direct — both estimates fall inside the survey range
Income-tax multiplier (frb-us, US)0.22 → 0.32 over years 1–2OBR's published UK assumption: 0.3 for income tax and NICsLoose — different country and model class
Forecast error, 2024Q3 onward (boe-svar)RMSE 0.32pp (GDP growth, CPI), frozen at 2024Q2; the 2025 CPI hump missed by up to 0.6ppOBR FER (July 2025): one-year GDP growth under by 0.4pp on average; BoE MPR (Aug 2024): late-2025 CPI ~2.4% modal vs a 3.8% outturn (~1.4pp miss)Adjusted — different statistics, matched vintages; full diagnostics: boe-svar validation
+
+
+
+
+
+ evidence guides +

Read the source literature and replication notes.

+

Working papers and replication reports for each model. The full library, with abstracts and PDFs, lives on the Papers page.

+
+
@@ -126,14 +333,11 @@

Compare two models

- + + diff --git a/notes/2026-07-25-cpi-2026q2/index.html b/notes/2026-07-25-cpi-2026q2/index.html index 66413f5..7352cdc 100644 --- a/notes/2026-07-25-cpi-2026q2/index.html +++ b/notes/2026-07-25-cpi-2026q2/index.html @@ -19,7 +19,7 @@ - + + + + + + + + + + +
+
+
+

model 01 — OBR macroeconometric model · obr-macro · UK · hosted

+

+ UK fiscal reform, quarter by quarter. +

+

+ Run selected UK tax and spending scenarios and trace GDP, consumption, + and investment over 3–5 years against the March 2026 EFO baseline. + Borrowing is not yet returned by the PolicyEngine Macro adapter. +

+
+
+ + + +
+
+ code +

Shock a variable, read the deviation.

+
+
+

+ Four steps, from hosted tools to a local run: each step's commands + appear alongside as you read. +

+
+ +
+
+
+
1

Start hosted: the MCP server

+

+ The quickest route is the hosted MCP server — + no install, no credentials. obr_shock shocks one + variable in model units and reads the per-quarter GDP deviation; + list_reform_variables lists the shockable levers with + units; score_reform with model="obr" + scores a PolicyEngine reform end to end through the static-costing + bridge. +

+ +
+ +
+
2

Or from the CLI

+

+ The pe-macro CLI mirrors the hosted tools one for one: + shock a variable, list the levers, or score a reform from the + terminal. +

+ +
+ +
+
3

Install and run locally

+

+ Locally, the package installs straight from GitHub — the equation + listing, the March 2026 EFO forecast tables, and the ONS snapshot + ship inside it, so there is no data step. A reform is one call: + pick an exogenous variable, apply a shock, read the deviation from + baseline. +

+

+ Two solves, so about a minute. + python -m obr_macro.reform_analysis runs the five + bundled scenarios — a £5bn spending rise, a 5pp corporation-tax cut + and rise, £10bn public real investment (~£3bn nominal + CGIPS per quarter), and a £10bn austerity cut — with + charts landing in outputs/. +

+ +
+ +
+
4

Pick a lever

+

+ Reforms enter through the 253 exogenous inputs; results come out of + the 383 the model solves — the test is simply whether a variable + has its own equation. The most useful levers and readouts are in + the table alongside. +

+

+ The full glossary — all 636 codes with descriptions, ONS series and + equations — is the model package's variable and equation references. +

+ +
+
+ +
+
+
+ + + + + + + + diff --git a/obr/index.html b/obr/index.html index df1e3af..8cffed9 100644 --- a/obr/index.html +++ b/obr/index.html @@ -19,7 +19,7 @@ - + + + + + + + + + + + + +
+
+
+

model 01 — OBR macroeconometric model · obr-macro · UK · hosted

+

+ UK fiscal reform, quarter by quarter. +

+

+ Run selected UK tax and spending scenarios and trace GDP, consumption, + and investment over 3–5 years against the March 2026 EFO baseline. + Borrowing is not yet returned by the PolicyEngine Macro adapter. +

+
+
+ + + +
+
+ methodology +

Transpile, solve, swap the closure.

+
+
+

+ The pipeline in five steps: the OBR's published EViews equation + listing is rewritten in Python, initialised from official data, + solved quarter by quarter until the equations agree, anchored so the + baseline reproduces the published EFO, and shocked against an + identical baseline so the difference is the policy effect. +

+
+ +
+
+
+
1

Transpile the published listing

+
+ + + From EViews source to open Python + + EViews listing + dlog(X) + X(-1) @elem + error-correction terms + needs an EViews licence + + + transpiler + line by line + + + Python expressions + log(X) - log(X[-1]) + indexed array reads + compiled once per solve + no licence needed + same equations, identities, and technical relationships — in open code + +
click to expand
+
+

From EViews to Python

+

The OBR publishes its full equation listing, but as EViews source that few can run. The transpiler rewrites every EViews line — dlog(), @elem, lags like X(-1), error-correction terms — into an equivalent Python expression, compiled once per solve.

+

What comes out

+

The result is the model itself in open code: the same behavioural equations, identities, and technical relationships the official forecaster relies on, runnable without an EViews licence.

+ +
+ +
+
2

Initialise from official data

+
+ + + Where the starting values come from + + OBR detailed forecast tables + March 2026 EFO + + vendored ONS snapshot + 348 series, ships in the package + + + + model history: 199 of 261 unprinted endogenous variables matched by ONS code + fiscal and financial sub-blocks get real balancing data + +
click to expand
+
+

Loading history

+

History loads from the OBR's detailed forecast tables. Of the 261 endogenous variables the listing referenced but the outlook did not print, 199 had ONS series codes; a vendored 348-series ONS snapshot supplies them.

+

Why it matters

+

That snapshot gives the circular fiscal and financial sub-blocks real balancing data rather than guessed starting values.

+ +
+ +
+
3

Solve by Gauss–Seidel iteration

+
+ + + Simultaneous within a quarter, sequential across quarters + + within a quarter + cycle through the equations + update each in turn + repeat to convergence + + + converged + + Q1 + + + Q2 + + + Q3 … + quarters solve sequentially forward + 12-quarter anchored solve: ~30–40 s single-threaded; a reform run is two solves + +
click to expand
+
+

Simultaneous within a quarter

+

The equations depend on each other, so each quarter is solved simultaneously: cycle through the equations, update, repeat to convergence. Quarters then solve sequentially forward.

+

Runtime

+

A twelve-quarter anchored solve takes roughly 30–40 seconds single-threaded; a reform run is two such solves.

+ +
+ +
+
4

Anchor to the published EFO

+
+ + + Add-factors pull the baseline onto the published EFO + + fit window 2024Q1–2025Q4 + + + + add-factors + EFO + raw + + + + fit add-factors + hold constant, project to 2027Q4 + to 2031Q1 + +
click to expand
+
+

Add-factors, the OBR's own device

+

Add-factors — the residuals reconciling each behavioural equation to data — are set so the baseline reproduces the published EFO. This is the OBR's own device, used the OBR's own way.

+

Forecasting with held add-factors

+

For forecasting rather than one-off shocks, the emulator fits add-factors over a base window of 2024Q1–2025Q4, holds them constant, and projects to 2027Q4, with exogenous inputs extrapolated (seasonal cash series at their trailing annual mean). Scenario solves run twelve quarters, 2025Q1–2027Q4.

+

Horizon limits

+

The anchored horizon extends to 2031Q1, where GDP is reproduced to 0.29%. Anchored unemployment (LFSUR) is unreliable beyond 2027Q4, drifting to 0.9% against the EFO's 4.1% by 2031Q1.

+ +
+ +
+
5

Swap the closure, shock, compare

+
+ + + Two identical solves; the difference is the policy effect + + closure swap + GDPM endogenised + DINV = residual + + + + shocked + baseline + + policy effect + + shock applied + + reported result = shocked path − baseline path + identical structure isolates the policy from the forecast + +
click to expand
+
+

Closure swap

+

The GDP identity is switched off in the OBR file, so for a shock GDPM is swapped in and inventories (DINV) become the residual — output responds to the policy instead of being fixed.

+

Shock against an identical baseline

+

The emulator solves shocked and baseline paths with identical structure and reports the difference, isolating the policy effect from the underlying forecast.

+ +
+
+ +
+
+ + + +
+ + + + + + + + diff --git a/obr/validation/index.html b/obr/validation/index.html new file mode 100644 index 0000000..cc1d39d --- /dev/null +++ b/obr/validation/index.html @@ -0,0 +1,522 @@ + + + + + +OBR emulator validation — PolicyEngine Macro + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+

model 01 — OBR macroeconometric model · obr-macro · UK · hosted

+

+ UK fiscal reform, quarter by quarter. +

+

+ Run selected UK tax and spending scenarios and trace GDP, consumption, + and investment over 3–5 years against the March 2026 EFO baseline. + Borrowing is not yet returned by the PolicyEngine Macro adapter. +

+
+
+ + + +
+
+ how far to trust it +

Excellent anchored. Weak free-running. Both published.

+
+
+

+ Three configurations, three very different levels of trust — errors + are MAPE (mean absolute percentage error) against the published EFO path. +

+
+
+
+ anchored — real GDP + 0.15% MAPE + vs published EFO; CI fails at 1% +
+
+ anchored — consumption + 0.25% MAPE + vs published EFO; CI fails at 1% +
+
+ held-add-factor forecast + 0.37% MAPE + real GDP, 2026Q1–2027Q4; 6 of 8 computed in band +
+
+ free-running + 5.75% MAPE + real GDP; 4 of 11 computed in band — report-only +
+
+
+
+ + + + + + + + + + + + + + + + + + + +
The three emulator configurations and how each one scores
configurationwhat it ishow it scores
AnchoredAdd-factors on, matched to the March 2026 EFO. The configuration every reform score uses.GDP 0.15% MAPE, consumption 0.25% over 2025Q1–2027Q4; horizon extends to 2031Q1, where GDP is reproduced to 0.29% (anchored unemployment unreliable beyond 2027Q4). Hard-gated in CI, with finiteness, a 0.5%-of-GDP expenditure-identity check, and structural sign invariants.
Held add-factorsAdd-factors fitted 2024Q1–2025Q4, held flat, projected to 2027Q4. Scored over 2026Q1–2027Q4 on the March 2026 baseline (caveats in the notes below).GDP 0.37%, consumption 0.33%; 6 of 8 computed variables within band. (The working paper's November-2025-vintage scorecard, over its own longer window, reports GDP 2.2% and consumption 3.6%.)
Free-runningDe-seeded, add-factors off. The raw structural dynamics with no OBR judgement.Weak, and reported as such. GDP 5.75%, consumption 9.56%, household income 14.15%, business investment 16.12%, company profits 79.80%. The model contracts 1–2%/yr while the EFO grows.
+
+
+

in band rates within 1.0pp · net balances within 1.5% of GDP · levels within 10% MAPE.

+

note anchored accuracy is a by-construction invariant, not a forecast claim: add-factors absorb the tracking error; CI hard-fails if it slips.

+

caveat the held-add-factor forecast is initialised at the EFO values it is scored against.

+

caveat its add-factor base window includes OBR-forecast (not outturn) quarters, so held add-factors partly encode "agree with the OBR".

+

more full scorecards in the working paper; the charts below regenerate from its committed data.

+
+
+

+ An independent implementation built from the OBR's published model + code and forecast data — not produced, maintained, or endorsed by the + Office for Budget Responsibility. Its results should not be presented + as official OBR estimates. +

+
+
+ +
+
+ evidence — anchored +

Against the OBR's own forecast, and HMRC's reckoner.

+
+
+

+ The chart below shows the anchored tracking error quarter by quarter + (headline MAPEs are in the strip and table above); because anchoring + is by construction, the independent HMRC costing further down is the + stronger test. +

+
+ +obr-macro: anchored baseline vs March 2026 EFO, quarterly deviation +Line chart. Quarterly percentage deviation of the anchored emulator from the published March 2026 EFO, 2025Q1 to 2027Q4. Real GDP ranges from -0.16% to +0.28% (mean absolute deviation 0.15%); consumption from -0.27% to +0.46% (mean absolute deviation 0.25%). Both series stay well inside the plus or minus 1% band at which continuous integration hard-fails the build, which is off the top and bottom of this frame. + +real GDP (peak +0.28%) + +consumption (peak +0.46%) + +-0.6% + +-0.3% + +0 + ++0.3% + ++0.6% +2025Q1 +2026Q1 +2027Q1 +2027Q4 + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Current March 2026 EFO baseline. CI hard-fails at ±1.00% — off the top and bottom of this frame. MAPE: 0.15% for GDP and 0.25% for consumption over 2025Q1–2027Q4. Computed from papers/obr-macro/figures/fig_anchored_data.csv, regenerated from the March 2026 detailed forecast tables on 21 July 2026.
+
+

+ The independent check: a 1pp basic-rate rise from April 2026 scores + £6.46bn in 2026–27 via the PolicyEngine + static-costing bridge, against HMRC's £6.9bn + (−6.4%), inside the £6–8bn range of recent vintages. The + gap widens later (−15.6% by 2028–29), where HMRC's figures + embed administrative-data fiscal drag that survey microdata capture + less fully. +

+
+ +obr-macro: 1p on the basic rate, ours vs HMRC ready reckoner (£bn/yr) +Grouped bar chart in billions of pounds per year. PolicyEngine's static costing of a 1 percentage point rise in the UK basic rate of income tax, against HMRC's Direct effects of illustrative tax changes ready reckoner, June 2025 vintage. For the basic rate +1pp, 2026–27 group, ours is 6.46 against HMRC’s 6.90, a deviation of -6.4%. For the basic rate +1pp, 2028–29 group, ours is 6.92 against HMRC’s 8.20, a deviation of -15.6%. The 2028–29 emulator figure is interpolated between the scored endpoints £6.46bn in 2026 and £7.38bn in 2030. + +0 + +2 + +4 + +6 + +8 + +10 + +6.46 +ours + +6.90 +HMRC +basic rate +1pp, 2026–27 + +6.92 +ours + +8.20 +HMRC +basic rate +1pp, 2028–29 + +
£bn/yr. 2028–29 emulator figure interpolated between scored endpoints (£6.46bn 2026, £7.38bn 2030). PolicyEngine's static costing of a 1pp basic-rate rise against HMRC's Direct effects of illustrative tax changes (June 2025 vintage). Source: obr-macro working paper, comparison table panel B.
+
+
+ + + + + + + + + + + + + + +
obr-macro against the current March 2026 OBR EFO and HMRC's ready reckoner
OursOfficialDeviation
Anchored levels vs EFO March 2026, £bn/qtr
Real GDP, 2025Q1703.8703.4+0.05%
Real GDP, 2027Q4730.6728.6+0.28%
Consumption, 2025Q1429.7429.3+0.09%
Consumption, 2027Q4445.5443.4+0.46%
Basic rate +1pp vs HMRC ready reckoner, £bn/yr
2026–276.466.9−6.4%
2028–29 (interpolated)6.928.2−15.6%
2030 (end of window)7.38≈8.2−10.0%
+
+
+ +How much of the OBR emulator scorecard the model actually computes +Two stacked bars. Of 21 headline variables in the OBR emulator calibration scorecard, 11 are actually computed by the model and 10 are passthrough, held at the OBR published value and therefore scoring zero error trivially. Of the 11 computed, 3 are fair, 1 is an identity, 5 are poor, 2 are off. 4 of the 11, or 36 per cent, land within band, and one of those is a trivial accounting identity, so only 3 non-trivial computed variables are in band. The worst are company profits 79.80 per cent and the current account 4.17 per cent of GDP. +21 headline scorecard variables + + +11 computed +10 passthrough — held at the OBR value + + +of which, the 11 the model computes + +fair 3 + +identity 1 + +poor 5 + +off 2 +Only 3 of 11 non-trivial variables are in band. +A fourth pass is an identity over passthrough inputs. +bands: rates ±1.0pp · net balances ±1.5% of GDP · levels ≤10% MAPE + +
Raw calibration against the March 2026 EFO. Two “off” variables depend on unpublished OBR constants and are regression-gated rather than tuned. Source: docs/calibration_scorecard.md in the obr-macroeconomic-model repository.
+
+

+ The spending multiplier is ~1.0 by construction, against the + OBR's own published 0.6. Under the demand closure a spending + shock lands directly in the GDP identity and the behavioural second + round is largely inactive, so a £5bn injection returns almost + exactly £5bn of GDP — roughly a + two-thirds overstatement of the impact multiplier, + applying to every spending-side figure this page reports. It is the + single most important number for anyone reading a policy score off + this model. +

+

+ Why, and what it biases → obr-macro overview. +

+
+ The honest scorecard, outturns, and the March 2026 re-anchoring +

+ The honest scorecard. The free-running row is the one + that matters for anyone tempted to read the 0.15% as forecasting skill. + The same equations that track the EFO to 0.15% when anchored miss it + by 5.75% free-running — precisely the gap the OBR's + own add-factor judgement closes in the official process, and the + reason reform deltas are always scored against the anchored baseline + rather than the raw one. The free-running score is de-seeded, with + passthrough variables excluded, and is published report-only. + Across the full scorecard only 4 of the 11 computed headline + variables land within band (real GDP, consumption, the trade + balance, and the trivial employment identity); of the full 21-line + scorecard, 10 lines are passthroughs held at the OBR value. The + worst line is company profits at 79.80% MAPE on the + March baseline (54.57% on the paper's November vintage), which + traces to a single unpublished constant in households' operating + surplus OSHH — the paper documents and regression-gates + it rather than re-tuning it, since tuning it would be fitting to the + answer. Other lines that moved on re-anchoring, reported rather than + smoothed: the free-running current account widened from 2.76 to + 4.17% of GDP and is now over band; RPI improved from + 2.03pp to 1.71pp; business investment worsened from + 15.48% to 16.12%; the two household-income + lines stand at 14.15% and 13.86%. +

+
+ +obr-macro: real GDP level, anchored vs free-running vs the March 2026 EFO (£bn/qtr) +Line chart of quarterly real GDP levels in billions of pounds, 2025Q1 to 2027Q4. The published March 2026 EFO path rises from 703.4 to 728.6. The anchored emulator is visually indistinguishable from it, running from 703.8 to 730.6 (mean absolute deviation 0.15 per cent, recomputed here from the plotted series). The free-running emulator, de-seeded and with no add-factors, contracts from 691.0 to 663.1 — a gap that widens to 65 billion pounds, 5.75 per cent mean absolute deviation over the horizon. Free-running and EFO paths from papers/obr-macro/figures/fig_free_running_data.csv; anchored path from papers/obr-macro/figures/fig_anchored_data.csv. Coordinates: value v in billions maps to y = 292 - (v - 660) * 2.95 on a 660 to 740 axis; quarter i of 12 maps to x = 58 + i * 60.545. + +anchored (0.15% MAD) + +free-running (5.75% MAD) +EFO Mar 2026 + +660 + +680 + +700 + +720 + +740 + + + +2025Q1 +2026Q1 +2027Q1 +2027Q4 + +
Current March 2026 EFO baseline. Real GDP, £bn/qtr, 2025Q1–2027Q4. The anchored path (0.15% MAPE) sits on top of the EFO; the same equations free-running — de-seeded, no add-factors — contract away from it (5.75% MAPE). Computed from papers/obr-macro/figures/fig_free_running_data.csv and fig_anchored_data.csv, regenerated on 21 July 2026.
+
+

+ Forecast versus outturn. Comparing one forecast + vintage with another tests agreement, not accuracy. Against ONS + outturns published since anchoring, quarter-on-quarter real GDP + growth ran 0.1% in 2025Q2, 0.2% in Q3, 0.2% in Q4 and 0.6% in + 2026Q1. The emulator's path (0.15, 0.14, 0.25, 0.37) tracks the three + 2025 quarters to within 0.06 percentage points, but — like the + November EFO it inherits (0.28, 0.20, 0.27, 0.39) — misses the strong + 2026Q1 outturn by roughly a quarter of a point. Two caveats govern + the reading: this is primarily a test of the OBR's November vintage, + the emulator's own contribution being the 0.02–0.13 point gap + between the two model rows; and ONS quarterly estimates are + themselves revised, so the outturn is a moving target. +

+
+ +obr-macro: quarterly real GDP growth — emulator vs EFO Nov 2025 vs ONS outturn (% q/q) +Grouped bar chart, percentage quarter-on-quarter real GDP growth for the four quarters with ONS outturns since anchoring. 2025Q2: emulator 0.15, EFO 0.28, ONS 0.1; 2025Q3: emulator 0.14, EFO 0.20, ONS 0.2; 2025Q4: emulator 0.25, EFO 0.27, ONS 0.2; 2026Q1: emulator 0.37, EFO 0.39, ONS 0.6. The emulator tracks the three 2025 outturns to within 0.06 points; both the emulator and the November EFO it inherits miss the strong 0.6 per cent 2026Q1 outturn by roughly a quarter of a point. Data from papers/obr-macro/figures/fig_outturn_data.csv. Coordinates: value v maps to y = 258 - v * 331.4 on a 0 to 0.7 axis. +emulator +EFO Nov 2025 +ONS outturn + +0 + +0.2 + +0.4 + +0.6 + +0.15 + +0.28 + +0.10 +2025Q2 + +0.14 + +0.20 + +0.20 +2025Q3 + +0.25 + +0.27 + +0.20 +2025Q4 + +0.37 + +0.39 + +0.60 +2026Q1 + +
November 2025 EFO vintage — the working paper's study; the live baseline is anchored to the March 2026 EFO. % q/q real GDP growth. The emulator tracks the three 2025 outturns to within 0.06pp; both model rows miss the strong 2026Q1 outturn (0.6%) by roughly a quarter point. Computed from papers/obr-macro/figures/fig_outturn_data.csv; the table below carries the exact values.
+
+
+ + + + + + + + + +
Quarterly real GDP growth: emulator, EFO vintage, and ONS outturn
Real GDP, % q/qEmulatorEFO Nov 2025ONS outturn
2025Q20.150.280.1
2025Q30.140.200.2
2025Q40.250.270.2
2026Q10.370.390.6
+
+

+ Vintage: re-anchored to the March 2026 EFO. The + hosted emulator has been re-anchored from the November 2025 EFO to + the OBR's March 2026 forecast, and the headline numbers on this page + are computed on that baseline: anchored GDP 0.15% MAPE, consumption + 0.25% over 2025Q1–2027Q4, with the anchored horizon extended to + 2031Q1 (GDP reproduced to 0.29% at 2031Q1; anchored unemployment is + unreliable beyond 2027Q4, drifting to 0.9% against the EFO's 4.1% by + 2031Q1). The headline and free-running charts use March 2026. Only + the outturn backtest above retains November 2025, because changing its + forecast vintage would erase the historical forecast being tested. The + working paper opens with a dated current-vintage note. Reform + effects are differences between structurally + identical runs and are insensitive to modest baseline drift, which is + why the re-anchoring leaves the £6.46bn/£7.38bn static costing + untouched and moves the second-round GDP effect only from + −0.057% to −0.058% by 2027Q4. +

+
+ +
+
+ +
+
+ limits +

Known limitations.

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Known limits of the OBR emulator
limitdetail
Two household-income equations never fireThe listing's only bare log() left-hand sides — log(HHTFA) and log(NDIVHH) — never execute: inputs MAJGDP and CORP are absent from the databank. The profits → dividends → household-income channel is inert: a 5pp corporation-tax rise moves FYCPR by −£1,780m; ΔNDIVHH is exactly zero. A parser fix landed, numerically inert; reviving the channel needs a CORP series — a calibration decision not yet made. All published figures have these channels inert.
Impact multiplier ~1 by construction~1.0 vs the OBR's published 0.6 — the warning above explains why and what it biases.
Corporation-tax closure is specialisedThe investment closure runs the published TCPRO → cost-of-capital → investment chain via obr_shock: business-investment equation reconstructed from the OBR's commented, truncated line; level anchored with held add-factors; MSGVA, PIF, PIRHH frozen to a shared tracking path against uncalibrated feedback. CI hard-gates the expected sign and a £50bn-per-quarter bound. A controlled scenario closure, not a calibrated supply block.
Passthrough channelsExports, imports and CPI are exogenous here, held at the OBR value. They score 0.00% error without being behavioural wins — 10 of 21 scorecard lines are passthroughs, labelled as such.
Approximated add-factorsRecent corrections are averaged and held flat. The OBR's judgemental, quarter-by-quarter add-factors are not reproduced.
VintageOctober 2025 equation listing, March 2026 EFO, current-vintage ONS series. Where the ONS has revised history the identities don't close exactly; that slack lands in the add-factors. The working paper remains a November 2025 EFO vintage study.
No behavioural microAggregate equations only. Distributional questions belong to PolicyEngine.
+
+
+
+ + + + + + diff --git a/olg/code/01_install.sh b/olg/code/01_install.sh new file mode 100755 index 0000000..1fb6b30 --- /dev/null +++ b/olg/code/01_install.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# Code-tab Step 1 — install dependencies. +# +# Prerequisites: Python 3.11+ and uv (https://docs.astral.sh/uv/). +# You also need a HuggingFace token with read access to +# `policyengine/policyengine-uk-data`. Create one at +# https://huggingface.co/settings/tokens. + +set -euo pipefail + +# Resolve and install everything in pyproject.toml. +uv sync + +cat < None: + baseline = solve_steady_state(start_year=2026) + reform = solve_steady_state(start_year=2026, policy=REFORM) + + impact = map_to_real_world(baseline, reform) + + print(f"GDP: £{impact.gdp:,.1f}bn ({impact.gdp_pct:+.3f}%)") + print(f"Tax revenue: £{impact.tax_revenue:,.1f}bn ({impact.tax_revenue_pct:+.3f}%)") + print(f"Investment: £{impact.investment:,.1f}bn ({impact.investment_pct:+.3f}%)") + print(f"Interest: {impact.r_baseline:.2%} -> {impact.r_reform:.2%}") + + +if __name__ == "__main__": + main() diff --git a/olg/code/04_transition.py b/olg/code/04_transition.py new file mode 100644 index 0000000..3c87a4f --- /dev/null +++ b/olg/code/04_transition.py @@ -0,0 +1,38 @@ +"""Code-tab Step 4 — solve the year-by-year transition path. + +Run:: + + uv run python scripts/04_transition.py +""" + +from dask.distributed import Client + +from oguk import map_transition_to_real_world, run_transition_path + +from og_dashboard.reform import REFORM + + +def main() -> None: + client = Client(n_workers=2, threads_per_worker=1, memory_limit="2GB") + try: + base_tp, reform_tp = run_transition_path( + start_year=2026, + policy=REFORM, + client=client, + ) + finally: + client.close() + + impact = map_transition_to_real_world(base_tp, reform_tp) + + # First ten years of GDP and tax-revenue impacts. + for i in range(10): + print( + f"{impact.years[i]} " + f"ΔGDP {impact.gdp_change[i]:+6.2f} " + f"ΔRevenue {impact.tax_revenue_change[i]:+6.2f}" + ) + + +if __name__ == "__main__": + main() diff --git a/olg/code/05_map_to_gbp.py b/olg/code/05_map_to_gbp.py new file mode 100644 index 0000000..30bdb1d --- /dev/null +++ b/olg/code/05_map_to_gbp.py @@ -0,0 +1,25 @@ +"""Code-tab Step 5 — the fields map_transition_to_real_world returns. + +Reference file: run after 04_transition.py has produced base_tp / reform_tp. +The mapping is GDP-anchored: one scale factor (real-world GDP / model GDP) +converts model-unit changes, with levels anchored to live ONS series and +HMRC total receipts. +""" + +from oguk import map_transition_to_real_world + + +def describe(base_tp, reform_tp) -> None: + impact = map_transition_to_real_world(base_tp, reform_tp) + + impact.years # fiscal-year strings: ["2026-27", ..., "2085-86"] + impact.gdp # reform GDP path (£bn, per year) + impact.gdp_change # £bn change vs baseline, per year + impact.tax_revenue_change + impact.consumption_change + impact.investment_change + impact.government_change + impact.debt_change + + # Interest-rate paths live on the TPI results themselves: + base_tp.r, reform_tp.r # baseline / reform r(t) diff --git a/olg/code/06_multi_sector.py b/olg/code/06_multi_sector.py new file mode 100644 index 0000000..2edad43 --- /dev/null +++ b/olg/code/06_multi_sector.py @@ -0,0 +1,33 @@ +"""Code-tab Step 6 — transition path with the 8-sector industry calibration. + +Run:: + + uv run python scripts/06_multi_sector.py +""" + +from dask.distributed import Client + +from oguk import map_transition_to_real_world, run_transition_path + +from og_dashboard.reform import REFORM + + +def main() -> None: + client = Client(n_workers=2, threads_per_worker=1, memory_limit="2GB") + try: + base_tp, reform_tp = run_transition_path( + start_year=2026, + policy=REFORM, + client=client, + multi_sector=True, # 8-sector CES production + ) + finally: + client.close() + + impact = map_transition_to_real_world(base_tp, reform_tp) + print(f"GDP at year 10: £{impact.gdp[10]:,.1f}bn Δ £{impact.gdp_change[10]:+,.2f}bn") + print(f"GDP at year 30: £{impact.gdp[30]:,.1f}bn Δ £{impact.gdp_change[30]:+,.2f}bn") + + +if __name__ == "__main__": + main() diff --git a/olg/code/index.html b/olg/code/index.html new file mode 100644 index 0000000..9da9e49 --- /dev/null +++ b/olg/code/index.html @@ -0,0 +1,656 @@ + + + + + +OG-UK code — PolicyEngine Macro + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+

model 05 — overlapping generations · psl-og · UK · local

+

+ Model long-run behavioural change. +

+

+ Estimate how reforms affect work, saving, investment, and public + finances over decades, for the UK. +

+
+
+ + + +
+
+ code +

From a few lines of Python to a full UK transition path.

+
+
+

+ OG-UK is open source, and every step below that carries logic ships as + a runnable file under /olg/code/ + on this site. Pick a step, run it on its own, swap the reform. Full context lives in + PSL's OG-UK repository. +

+
+ Local-only. A solve cannot fit the + hosted MCP server's timeout, so hosted calls + (score_reform with model="og", + dynamic_reform_impact) return install/CLI instructions + instead of results. Locally, until + OG-UK#68 + lands, oguk pins policyengine-uk==2.88.0 and + needs its own environment: + pe-macro og-score --reform '...' --json > og.json + there, then + pe-macro dynamic-score --reform '...' --og-payload og.json + in the main one. Both need a HUGGING_FACE_TOKEN with + access to the gated UK microdata. What the pin means for results is on + the Validation tab. +
+
+ +
+
+
+
1

Install

+

+ A Python package (3.11+); the recommended setup uses + uv. The PolicyEngine + enhanced-FRS microdata downloads on first run and is gated: you need + a HUGGING_FACE_TOKEN with read access to + policyengine/policyengine-uk-data. +

+

+ runnable file: 01_install.sh. + prefer conda? + conda env create -f environment.yml && conda activate oguk-dev && pip install -e . + works too. +

+ +
+ +
+
2

Define a reform

+

+ Reforms use the PolicyEngine API: pick a parameter + from the UK tax-and-benefit rule book, give it a new value and a + start date. Anything PolicyEngine can represent — rates, thresholds, + allowance tapers, new benefits — flows straight through. To simulate + a different reform, swap the parameter path and value; the rest of + the pipeline does not change. +

+

+ runnable file: 02_reform.py — + every later script imports REFORM from it. +

+

+ Stack several ParameterValues in one Policy + to score a package. Common parameter paths: + gov.hmrc.income_tax.rates.uk[0..2].rate (basic 20%, + higher 40%, additional 45%), + gov.hmrc.income_tax.allowances.personal_allowance.amount + (£12,570), and + gov.hmrc.national_insurance.class_1.rates.employee.main + / .higher. +

+

+ Shocks that are not tax-and-benefit statute go through + param_overrides, a dict of OG-Core parameters applied on + top of everything else. Corporation tax lives here, not in + PolicyEngine — it is a structural parameter of the macro model. +

+
+ Use Z (TFP) for productivity shocks — never + g_y_annual, which is the balanced-growth normalisation + the model detrends by, not a productivity lever. +
+ +
+ +
+
3

Solve the long-run steady state

+

+ The fastest way to see what a reform does. + solve_steady_state finds the long-run equilibrium of the + UK economy under a given policy: the prices, quantities and tax + revenues that emerge once the economy has fully adjusted. Run it once + for the baseline, once for the reform; the difference is the answer. +

+

+ The output shown is illustrative — the format matches + 03_steady_state.py, but + the numbers depend on the calibration date and your data release. On + the fast configuration, budget ~17 minutes per steady state. +

+

The main configuration dials and their trade-offs:

+
+
+
mode
+
+ solve_steady_state() + long-run answer, ~17 min per solve +
+
+ run_transition_path() + full 60-year path, multiple hours for baseline + reform +
+
+
+
age_specific
+
+ "pooled" + one tax function for all ages (fastest, most stable) +
+
+ "brackets" + one per age bracket (4 groups, split at state pension age) +
+
+ "each" + one per single year of age (80 functions, slowest) +
+
+
+
multi_sector
+
+ False + one production sector +
+
+ True + eight industries calibrated from ONS Blue Book supply-and-use tables by SIC section; needed for sector-level questions like energy price shocks +
+
+
+

+ start with pooled + single-sector and scale up only once + the fast run answers your question. non-converging solve? raise + max_iter (default 250), fall back to pooled, + and check whether the reform is extreme — OLG models can diverge on + very large shocks. +

+ +
+ +
+
4

Run the year-by-year transition path

+

+ The steady state tells you where the economy ends up. The + transition path tells you how it gets there — year + by year, 60 periods by default (configurable). This is what produced + the reform paths in the Showcase tab. + The transition costs more compute — the model solves every cohort's + lifetime under rational expectations — so OG-UK uses + Dask to parallelise across CPU + cores. +

+

+ runnable file: 04_transition.py. + transition paths are available through the oguk API but + are not wired into the PolicyEngine Macro CLI yet — + the CLI is steady-state only (see step 7). +

+ +
+ +
+
5

From abstract units to pounds

+

+ OG-UK solves in dimensionless model units. To translate them into + figures a policymaker can read, map_to_real_world() + returns a MacroImpact carrying, for each aggregate, the + reform level, the change from baseline, and the percent change — all + current-price £bn — plus baseline and reform interest rates: +

+
    +
  • .gdp / .gdp_change / .gdp_pct
  • +
  • .consumption, .investment, .government — same triple each
  • +
  • .tax_revenue, .debt — same triple each
  • +
  • .r_baseline, .r_reform — steady-state interest rates
  • +
+

+ The £bn mapping is GDP-anchored: one scale factor (real-world GDP ÷ + model GDP) converts model-unit changes, with levels anchored to live + ONS series (GDP, consumption, investment, government, debt ratio) + and HMRC total receipts, falling back to cached values if ONS is + unreachable. The transition-path variant returns NumPy arrays + indexed by year. +

+
+ It cannot give you a quarterly path, a costing on + the OBR's forecast basis, or a distributional table. Those are the + OBR emulator and + PolicyEngine respectively. And it should not be + read as a forecast of anything — see + how far to trust it. +
+ +
+ +
+
6

Bring in the eight industry sectors

+

+ Pass multi_sector=True and the same call returns the + breakdown across the eight UK industry sectors (energy, + manufacturing, construction, trade & transport, info & + finance, real estate, business services, public & other) — + sector-level output, capital and labour alongside the macro + aggregates, and the basis for the industry charts in the + Showcase tab. +

+

+ runnable file: 06_multi_sector.py. +

+ +
+ +
+
7

Where to go next

+

+ Every step above ships as a file you can download and run on its own: +

+ +

+ Through this suite, two CLI commands are wired: + pe-macro og-baseline and + pe-macro og-score --reform '{"gov.hmrc.income_tax.rates.uk[0].rate": 0.21}', + both on the fast configuration (pooled tax functions, single sector, + steady state). For more variations, the upstream + OG-UK examples + directory carries additional pipelines + (run_oguk_fast_tpi.py, run_oguk_fast_sector.py, + plot.py). Full API reference and theory documentation: + pslmodels.github.io/OG-UK. +

+ +
+
+ +
+
+ +
+ + + + + + + + diff --git a/olg/figures/make_showcase.py b/olg/figures/make_showcase.py new file mode 100644 index 0000000..0adc01b --- /dev/null +++ b/olg/figures/make_showcase.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +"""Regenerate the OLG showcase stats table inlined in olg/index.html. + +Run: python3 olg/figures/make_showcase.py # rewrite olg/index.html + python3 olg/figures/make_showcase.py --check # exit 1 if the page is stale + +The showcase charts themselves are rendered in the browser (inline SVG drawn +by the page's script from a runtime fetch of olg/figures/tpi_data.json), so +this script no longer emits chart markup. It still owns the headline stats +tbody between the olg-stats markers: every number comes from the committed +transition-path results in tpi_data.json — the +1pp basic-rate simulation +exported from the OG-UK dashboard (OBR November 2025 EFO baseline; the +committed file carries the first five years, 2026-2030, of the 60-period +solve). Nothing here is computed fresh; this script only formats. +""" + +from __future__ import annotations + +import argparse +import json +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +HERE = Path(__file__).resolve().parent +PAGE = ROOT / "olg" / "index.html" + + +def load(): + return json.loads((HERE / "tpi_data.json").read_text()) + + +def macro_levels_2030(data): + """Baseline and reform 2030 levels per macro panel, for the stats table.""" + out = {} + for p in data["macro6"]: + base = dict(zip(p["traces"][0]["x"], p["traces"][0]["y"])) + reform = dict(zip(p["traces"][1]["x"], p["traces"][1]["y"])) + out[p["title"]] = (base[2030], reform[2030]) + return out + + +def stats_tbody(): + lv = macro_levels_2030(load()) + rows = [] + + def pp_row(title, label): + b, r = lv[title] + return (f'{label} (% of GDP)' + f'{b:.2f}%{r:.2f}%' + f'{r - b:+.2f}pp') + + b, r = lv["GDP (£bn)"] + rows.append(f'GDP (£bn, current prices)' + f'{b:,.1f}{r:,.1f}' + f'{r - b:+,.1f} ({(r / b - 1) * 100:+.2f}%)') + rows.append(pp_row("Tax revenue (% GDP)", "Tax revenue")) + rows.append(pp_row("Consumption (% GDP)", "Consumption")) + rows.append(pp_row("Investment (% GDP)", "Investment")) + rows.append(pp_row("Gov. Consumption (% GDP)", "Government consumption")) + rows.append(pp_row("Debt (% GDP)", "Debt")) + return "\n".join(rows) + + +BLOCKS = [ + ("olg-stats", stats_tbody), +] + + +def render_page(html: str) -> str: + for name, build in BLOCKS: + marker = re.compile( + rf"().*?()", re.DOTALL + ) + if not marker.search(html): + sys.exit(f"marker block {name} not found in {PAGE}") + # A function replacement is used (not a template string) so the + # content is inserted literally, backslashes included. + content = build() + html = marker.sub( + lambda m: m.group(1) + "\n" + content + "\n" + m.group(2), html + ) + return html + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--check", action="store_true", help="exit 1 if the page is out of date") + args = ap.parse_args() + + html = PAGE.read_text() + new = render_page(html) + if args.check: + if new != html: + print(f"{PAGE} is out of date; run: python3 olg/figures/make_showcase.py") + return 1 + print("olg/index.html showcase stats are up to date.") + return 0 + if new == html: + print("no change.") + else: + PAGE.write_text(new) + print(f"wrote {len(BLOCKS)} generated blocks into {PAGE}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/olg/figures/tpi_data.json b/olg/figures/tpi_data.json new file mode 100644 index 0000000..cc69be8 --- /dev/null +++ b/olg/figures/tpi_data.json @@ -0,0 +1 @@ +{"macro6": [{"title": "Consumption (% GDP)", "traces": [{"name": "OBR outturn / forecast", "x": [2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026, 2027, 2028, 2029, 2030], "y": [64.86201231897655, 64.04466805224651, 63.52546048374277, 63.03309482905095, 62.51278244646363, 62.088716569744285, 61.749195004880754, 61.487674533450345, 63.087856586498056, 63.33239740407257, 63.80564704095594, 63.40677848128752, 64.17192381708347, 63.951838155366694, 63.756002021064404, 63.70096875406735, 64.09311150115583, 63.76090800367158, 63.956254318239836, 62.61734229288618, 57.19769137003104, 60.292334854469956, 62.66152077791139, 61.572049739994824, 60.62423423663513, 60.96063213042443, 60.74250261673182, 60.75421822900904, 60.960271527296804, 61.20466254519208, 61.333836908688724], "color": "#e67e22", "width": 2.5, "dash": "solid", "hovertemplate": "%{x}: %{y:.1f}%Baseline", "legendgroup": "base", "showlegend": true}, {"name": "Reform +1pp basic rate", "x": [2026, 2027, 2028, 2029, 2030], "y": [60.74250261673182, 60.58948336396293, 60.76181160913956, 60.97207153660558, 61.100501659579585], "color": "#e67e22", "width": 2.5, "dash": "dash", "hovertemplate": "%{x}: %{y:.1f}%Reform", "legendgroup": "ref", "showlegend": true}], "shapes": [{"x0": 2023, "x1": 2023, "color": "#95a5a6", "width": 1, "dash": "dot"}, {"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}, {"title": "Investment (% GDP)", "traces": [{"name": "", "x": [2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026, 2027, 2028, 2029, 2030], "y": [18.135890623816906, 18.121229786586916, 17.92791548712283, 17.81482250624579, 18.050656596031345, 18.300745406115542, 18.707713197550344, 18.511979466467874, 18.15226198747605, 16.78967755109855, 16.40613230483194, 16.3239907266007, 16.193138553410652, 16.795342508384348, 17.45175344285356, 17.98381242871702, 18.661617133289727, 19.090927576956013, 18.75137852600123, 18.588097237863934, 18.247807104847592, 18.627036016527256, 19.180203284622767, 18.654685238310883, 18.613764162992023, 18.455289448275817, 18.38546572565718, 18.595199616268125, 18.670090193615536, 18.60140144821732, 18.50089741916154], "color": "#27ae60", "width": 2.5, "dash": "solid", "hovertemplate": "%{x}: %{y:.1f}%Baseline", "legendgroup": "base", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [18.38546572565718, 18.283169272512655, 18.47494826888888, 18.42106793926708, 18.27571360548433], "color": "#27ae60", "width": 2.5, "dash": "dash", "hovertemplate": "%{x}: %{y:.1f}%Reform", "legendgroup": "ref", "showlegend": false}], "shapes": [{"x0": 2023, "x1": 2023, "color": "#95a5a6", "width": 1, "dash": "dot"}, {"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}, {"title": "Gov. Consumption (% GDP)", "traces": [{"name": "", "x": [2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026, 2027, 2028, 2029, 2030], "y": [43.02323464930862, 46.008474414033834, 45.50255186089265, 44.350799583803564, 43.26130998804739, 42.18989508394278, 41.6360982440679, 40.82945226374433, 40.02741823613623, 39.6633527489848, 39.090636343585075, 39.095600031280966, 52.268392925863374, 43.36251738253406, 44.05766766274309, 44.13067415613504, 44.00864693329068, 45.015763792485906, 44.73542537292963, 44.813096082542316, 44.494141660839134, 44.26837243663773, 44.28424777224414], "color": "#8e44ad", "width": 2.5, "dash": "solid", "hovertemplate": "%{x}: %{y:.1f}%Baseline", "legendgroup": "base", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [44.73542537292963, 45.26537291432576, 44.855679937153035, 44.628177033235964, 44.66718753062055], "color": "#8e44ad", "width": 2.5, "dash": "dash", "hovertemplate": "%{x}: %{y:.1f}%Reform", "legendgroup": "ref", "showlegend": false}], "shapes": [{"x0": 2023, "x1": 2023, "color": "#95a5a6", "width": 1, "dash": "dot"}, {"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}, {"title": "Tax revenue (% GDP)", "traces": [{"name": "", "x": [2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026, 2027, 2028, 2029, 2030], "y": [32.386285537828876, 29.928465224910184, 30.374922072402548, 31.530033546765885, 31.842871580929348, 31.341285632779453, 31.77774223378461, 32.25579099482106, 32.36121429536909, 32.72460213835413, 33.29011858862684, 32.78316235573973, 31.706221244021314, 29.911706308703806, 29.83674064356255, 29.652385833832618, 30.510950800493912, 31.25300406561654, 31.627721059266722, 31.640080661226204, 31.932493985249888, 32.07765139584891, 32.057388394670696], "color": "#c0392b", "width": 2.5, "dash": "solid", "hovertemplate": "%{x}: %{y:.1f}%Baseline", "legendgroup": "base", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [31.627721059266722, 32.08275405277887, 32.24157818359108, 32.38126085636115, 32.35004713703777], "color": "#c0392b", "width": 2.5, "dash": "dash", "hovertemplate": "%{x}: %{y:.1f}%Reform", "legendgroup": "ref", "showlegend": false}], "shapes": [{"x0": 2023, "x1": 2023, "color": "#95a5a6", "width": 1, "dash": "dot"}, {"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}, {"title": "Debt (% GDP)", "traces": [{"name": "", "x": [2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026, 2027, 2028, 2029, 2030], "y": [57.35, 67.725, 72.87500000000001, 76.42500000000001, 78.72500000000001, 80.925, 81.65, 82.3, 81.9, 81.39999999999999, 83.47500000000001, 94.24999999999999, 96.55000000000001, 93.87499999999999, 94.975, 94.84999999999998, 94.90000000000002, 96.1, 96.5, 96.3, 95.79999999999998, 95.1, 94.5], "color": "#95a5a6", "width": 2.5, "dash": "solid", "hovertemplate": "%{x}: %{y:.1f}%Baseline", "legendgroup": "base", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [96.5, 96.55700270635994, 95.9091654954989, 95.11460829493087, 94.42293503164926], "color": "#95a5a6", "width": 2.5, "dash": "dash", "hovertemplate": "%{x}: %{y:.1f}%Reform", "legendgroup": "ref", "showlegend": false}], "shapes": [{"x0": 2023, "x1": 2023, "color": "#95a5a6", "width": 1, "dash": "dot"}, {"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}, {"title": "GDP (\u00a3bn)", "traces": [{"name": "", "x": [2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026, 2027, 2028, 2029, 2030], "y": [1139.09625, 1183.62, 1245.538, 1310.9827500000001, 1384.2225, 1459.37225, 1533.31675, 1589.595, 1593.267, 1567.07, 1632.926, 1680.937, 1733.512, 1812.306, 1888.114, 1943.837, 2027.118, 2114.617, 2189.839, 2263.357, 2109.996, 2400.398, 2634.3360000000002, 2788.983, 2926.818, 3042.9074849999997, 3164.945075, 3277.488484, 3388.3980389999997, 3503.997179, 3627.868826], "color": "#1a3a6e", "width": 2.5, "dash": "solid", "hovertemplate": "%{x}: \u00a3%{y:,.0f}bnBaseline", "legendgroup": "base", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [3164.945075, 3279.759235019503, 3384.0572479383354, 3499.264999140557, 3622.8430327936553], "color": "#1a3a6e", "width": 2.5, "dash": "dash", "hovertemplate": "%{x}: \u00a3%{y:,.0f}bnReform", "legendgroup": "ref", "showlegend": false}], "shapes": [{"x0": 2023, "x1": 2023, "color": "#95a5a6", "width": 1, "dash": "dot"}, {"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}], "sector3": [{"title": "Sector Output (% change)", "traces": [{"name": "Energy", "x": [2026, 2027, 2028, 2029, 2030], "y": [-0.204, -0.23, -0.262, -0.3, -0.333], "color": "#e6194b", "width": 2, "dash": "solid", "hovertemplate": "%{x}: %{y:.3f}%Energy", "legendgroup": "Energy", "showlegend": true}, {"name": "Manufacturing", "x": [2026, 2027, 2028, 2029, 2030], "y": [-0.238, -0.27, -0.308, -0.352, -0.39], "color": "#3cb44b", "width": 2, "dash": "solid", "hovertemplate": "%{x}: %{y:.3f}%Manufacturing", "legendgroup": "Manufacturing", "showlegend": true}, {"name": "Construction", "x": [2026, 2027, 2028, 2029, 2030], "y": [-0.239, -0.272, -0.31, -0.354, -0.393], "color": "#ffe119", "width": 2, "dash": "solid", "hovertemplate": "%{x}: %{y:.3f}%Construction", "legendgroup": "Construction", "showlegend": true}, {"name": "Trade & Transport", "x": [2026, 2027, 2028, 2029, 2030], "y": [-0.238, -0.27, -0.309, -0.352, -0.39], "color": "#4363d8", "width": 2, "dash": "solid", "hovertemplate": "%{x}: %{y:.3f}%Trade & Transport", "legendgroup": "Trade & Transport", "showlegend": true}, {"name": "Info & Finance", "x": [2026, 2027, 2028, 2029, 2030], "y": [-0.241, -0.272, -0.31, -0.355, -0.394], "color": "#f58231", "width": 2, "dash": "solid", "hovertemplate": "%{x}: %{y:.3f}%Info & Finance", "legendgroup": "Info & Finance", "showlegend": true}, {"name": "Real Estate", "x": [2026, 2027, 2028, 2029, 2030], "y": [-0.239, -0.266, -0.298, -0.346, -0.387], "color": "#911eb4", "width": 2, "dash": "solid", "hovertemplate": "%{x}: %{y:.3f}%Real Estate", "legendgroup": "Real Estate", "showlegend": true}, {"name": "Business Services", "x": [2026, 2027, 2028, 2029, 2030], "y": [-0.239, -0.272, -0.31, -0.354, -0.392], "color": "#42d4f4", "width": 2, "dash": "solid", "hovertemplate": "%{x}: %{y:.3f}%Business Services", "legendgroup": "Business Services", "showlegend": true}, {"name": "Public & Other", "x": [2026, 2027, 2028, 2029, 2030], "y": [0.418, 0.352, 0.03, 0.049, 0.066], "color": "#f032e6", "width": 2, "dash": "solid", "hovertemplate": "%{x}: %{y:.3f}%Public & Other", "legendgroup": "Public & Other", "showlegend": true}], "shapes": [{"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}, {"title": "Sector Capital (% change)", "traces": [{"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [-0.238, -0.222, -0.203, -0.281, -0.349], "color": "#e6194b", "width": 2, "dash": "solid", "hovertemplate": "%{x}: %{y:.3f}%Energy", "legendgroup": "Energy", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [-0.278, -0.26, -0.239, -0.33, -0.409], "color": "#3cb44b", "width": 2, "dash": "solid", "hovertemplate": "%{x}: %{y:.3f}%Manufacturing", "legendgroup": "Manufacturing", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [-0.281, -0.262, -0.239, -0.331, -0.411], "color": "#ffe119", "width": 2, "dash": "solid", "hovertemplate": "%{x}: %{y:.3f}%Construction", "legendgroup": "Construction", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [-0.279, -0.26, -0.239, -0.33, -0.409], "color": "#4363d8", "width": 2, "dash": "solid", "hovertemplate": "%{x}: %{y:.3f}%Trade & Transport", "legendgroup": "Trade & Transport", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [-0.279, -0.263, -0.245, -0.334, -0.411], "color": "#f58231", "width": 2, "dash": "solid", "hovertemplate": "%{x}: %{y:.3f}%Info & Finance", "legendgroup": "Info & Finance", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [-0.272, -0.258, -0.242, -0.328, -0.402], "color": "#911eb4", "width": 2, "dash": "solid", "hovertemplate": "%{x}: %{y:.3f}%Real Estate", "legendgroup": "Real Estate", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [-0.281, -0.261, -0.239, -0.331, -0.411], "color": "#42d4f4", "width": 2, "dash": "solid", "hovertemplate": "%{x}: %{y:.3f}%Business Services", "legendgroup": "Business Services", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [0.373, 0.363, 0.107, 0.073, 0.046], "color": "#f032e6", "width": 2, "dash": "solid", "hovertemplate": "%{x}: %{y:.3f}%Public & Other", "legendgroup": "Public & Other", "showlegend": false}], "shapes": [{"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}, {"title": "Sector Labour (% change)", "traces": [{"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [-0.173, -0.238, -0.314, -0.317, -0.319], "color": "#e6194b", "width": 2, "dash": "solid", "hovertemplate": "%{x}: %{y:.3f}%Energy", "legendgroup": "Energy", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [-0.213, -0.276, -0.351, -0.365, -0.379], "color": "#3cb44b", "width": 2, "dash": "solid", "hovertemplate": "%{x}: %{y:.3f}%Manufacturing", "legendgroup": "Manufacturing", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [-0.216, -0.278, -0.35, -0.367, -0.382], "color": "#ffe119", "width": 2, "dash": "solid", "hovertemplate": "%{x}: %{y:.3f}%Construction", "legendgroup": "Construction", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [-0.213, -0.276, -0.351, -0.366, -0.379], "color": "#4363d8", "width": 2, "dash": "solid", "hovertemplate": "%{x}: %{y:.3f}%Trade & Transport", "legendgroup": "Trade & Transport", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [-0.213, -0.279, -0.356, -0.369, -0.382], "color": "#f58231", "width": 2, "dash": "solid", "hovertemplate": "%{x}: %{y:.3f}%Info & Finance", "legendgroup": "Info & Finance", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [-0.206, -0.274, -0.353, -0.363, -0.373], "color": "#911eb4", "width": 2, "dash": "solid", "hovertemplate": "%{x}: %{y:.3f}%Real Estate", "legendgroup": "Real Estate", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [-0.215, -0.277, -0.35, -0.366, -0.381], "color": "#42d4f4", "width": 2, "dash": "solid", "hovertemplate": "%{x}: %{y:.3f}%Business Services", "legendgroup": "Business Services", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [0.439, 0.347, -0.005, 0.038, 0.076], "color": "#f032e6", "width": 2, "dash": "solid", "hovertemplate": "%{x}: %{y:.3f}%Public & Other", "legendgroup": "Public & Other", "showlegend": false}], "shapes": [{"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}], "sector24": [{"title": "Energy \u2014 Output", "traces": [{"name": "ONS outturn / forecast", "x": [2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2026, 2027, 2028, 2029, 2030], "y": [21.5, 21.2, 20.6, 20.9, 22.3, 25.6, 27.0, 29.8, 34.5, 28.0, 31.2, 35.0, 33.5, 33.0, 28.2, 22.1, 41.8, 44.0, 48.3, 40.7, 30.1, 46.9, 54.8, 48.2, 54.69748385522608, 56.64249116211895, 58.559261737988365, 60.557079059929734, 62.6978642082795], "color": "#e6194b", "width": 2.5, "dash": "solid", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnBaseline", "legendgroup": "baseline", "showlegend": true}, {"name": "Reform +1pp basic rate", "x": [2026, 2027, 2028, 2029, 2030], "y": [54.69748385522608, 56.51221343244608, 58.40583647223484, 60.37540782274994, 62.48908032046592], "color": "#e6194b", "width": 2.5, "dash": "dash", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnReform", "legendgroup": "reform", "showlegend": true}], "shapes": [{"x0": 2023, "x1": 2023, "color": "#95a5a6", "width": 1, "dash": "dot"}, {"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}, {"title": "Energy \u2014 Capital", "traces": [{"name": "", "x": [2000, 2002, 2004, 2006, 2008, 2010, 2012, 2014, 2016, 2018, 2019, 2020, 2021, 2022, 2026, 2027, 2028, 2029, 2030], "y": [105, 100, 100, 108, 130, 135, 145, 150, 155, 170, 175, 172, 181, 197, 236.679823596914, 245.09600572895786, 253.3900055585164, 262.0347003051243, 271.2980267976446], "color": "#e6194b", "width": 2.5, "dash": "solid", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnBaseline", "legendgroup": "baseline", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [236.679823596914, 244.55189259623958, 252.87562384723262, 261.2983827972669, 270.3511966841208], "color": "#e6194b", "width": 2.5, "dash": "dash", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnReform", "legendgroup": "reform", "showlegend": false}], "shapes": [{"x0": 2023, "x1": 2023, "color": "#95a5a6", "width": 1, "dash": "dot"}, {"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}, {"title": "Energy \u2014 Labour", "traces": [{"name": "", "x": [2000, 2002, 2004, 2006, 2008, 2010, 2012, 2014, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2026, 2027, 2028, 2029, 2030], "y": [125, 115, 110, 115, 120, 115, 125, 135, 155, 157, 160, 161, 152, 162, 178, 175, 198.5904496818374, 205.652198202714, 212.61142747194944, 219.86491359932992, 227.63747378524712], "color": "#e6194b", "width": 2.5, "dash": "solid", "hovertemplate": "%{x}: %{y:,.1f} thousandsBaseline", "legendgroup": "baseline", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [198.5904496818374, 205.16274597099152, 211.94382758968752, 219.16794182322005, 226.91131024387218], "color": "#e6194b", "width": 2.5, "dash": "dash", "hovertemplate": "%{x}: %{y:,.1f} thousandsReform", "legendgroup": "reform", "showlegend": false}], "shapes": [{"x0": 2023, "x1": 2023, "color": "#95a5a6", "width": 1, "dash": "dot"}, {"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}, {"title": "Manufacturing \u2014 Output", "traces": [{"name": "", "x": [2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2026, 2027, 2028, 2029, 2030], "y": [128.7, 126.5, 124.0, 124.1, 126.0, 127.6, 130.4, 133.0, 127.1, 118.5, 127.1, 130.1, 131.4, 136.2, 140.7, 144.3, 174.8, 178.6, 181.5, 181.3, 168.1, 183.6, 195.4, 195.0, 221.2865010740474, 229.15530656873847, 236.9098763258865, 244.9923322963962, 253.65318507498966], "color": "#3cb44b", "width": 2.5, "dash": "solid", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnBaseline", "legendgroup": "baseline", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [221.2865010740474, 228.53658724100288, 236.18019390680277, 244.1299592867129, 252.6639376531972], "color": "#3cb44b", "width": 2.5, "dash": "dash", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnReform", "legendgroup": "reform", "showlegend": false}], "shapes": [{"x0": 2023, "x1": 2023, "color": "#95a5a6", "width": 1, "dash": "dot"}, {"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}, {"title": "Manufacturing \u2014 Capital", "traces": [{"name": "", "x": [2000, 2002, 2004, 2006, 2008, 2010, 2012, 2014, 2016, 2018, 2019, 2020, 2021, 2022, 2026, 2027, 2028, 2029, 2030], "y": [105, 100, 97, 100, 110, 112, 118, 122, 128, 140, 143, 142, 152, 168, 201.83863129076929, 209.01588305819755, 216.0889387504099, 223.4610642195984, 231.36075381728068], "color": "#3cb44b", "width": 2.5, "dash": "solid", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnBaseline", "legendgroup": "baseline", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [201.83863129076929, 208.47244176224623, 215.57248618679643, 222.72364270767372, 230.414488334168], "color": "#3cb44b", "width": 2.5, "dash": "dash", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnReform", "legendgroup": "reform", "showlegend": false}], "shapes": [{"x0": 2023, "x1": 2023, "color": "#95a5a6", "width": 1, "dash": "dot"}, {"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}, {"title": "Manufacturing \u2014 Labour", "traces": [{"name": "", "x": [2000, 2002, 2004, 2006, 2008, 2010, 2012, 2014, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2026, 2027, 2028, 2029, 2030], "y": [3800, 3510, 3250, 3050, 2910, 2650, 2600, 2600, 2620, 2630, 2620, 2610, 2490, 2530, 2590, 2570, 2916.4426038989836, 3020.149425034143, 3122.3506777309144, 3228.8733025730166, 3343.018900731915], "color": "#3cb44b", "width": 2.5, "dash": "solid", "hovertemplate": "%{x}: %{y:,.1f} thousandsBaseline", "legendgroup": "baseline", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [2916.4426038989836, 3011.813812621049, 3111.391226852079, 3217.087915018625, 3330.348859098141], "color": "#3cb44b", "width": 2.5, "dash": "dash", "hovertemplate": "%{x}: %{y:,.1f} thousandsReform", "legendgroup": "reform", "showlegend": false}], "shapes": [{"x0": 2023, "x1": 2023, "color": "#95a5a6", "width": 1, "dash": "dot"}, {"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}, {"title": "Construction \u2014 Output", "traces": [{"name": "", "x": [2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2026, 2027, 2028, 2029, 2030], "y": [55.6, 58.4, 63.0, 67.3, 70.8, 75.4, 81.0, 87.0, 81.2, 72.7, 77.0, 80.5, 82.1, 87.8, 95.5, 99.5, 111.4, 118.3, 125.5, 126.4, 113.1, 127.8, 134.5, 140.1, 158.985840002431, 164.63927410400132, 170.21063422182922, 176.01756797294925, 182.24005758464642], "color": "#ffe119", "width": 2.5, "dash": "solid", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnBaseline", "legendgroup": "baseline", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [158.985840002431, 164.1914552784384, 169.68298125574157, 175.394465782325, 181.52385415833876], "color": "#ffe119", "width": 2.5, "dash": "dash", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnReform", "legendgroup": "reform", "showlegend": false}], "shapes": [{"x0": 2023, "x1": 2023, "color": "#95a5a6", "width": 1, "dash": "dot"}, {"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}, {"title": "Construction \u2014 Capital", "traces": [{"name": "", "x": [2000, 2002, 2004, 2006, 2008, 2010, 2012, 2014, 2016, 2018, 2019, 2020, 2021, 2022, 2026, 2027, 2028, 2029, 2030], "y": [14, 15, 16, 18, 20, 20, 21, 22, 24, 26, 27, 27, 29, 32, 38.44545357919415, 39.81254915394239, 41.15979785722094, 42.56401223230446, 44.06871501281537], "color": "#ffe119", "width": 2.5, "dash": "solid", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnBaseline", "legendgroup": "baseline", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [38.44545357919415, 39.70824027515906, 41.061425940342176, 42.42312535181553, 43.8875925941127], "color": "#ffe119", "width": 2.5, "dash": "dash", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnReform", "legendgroup": "reform", "showlegend": false}], "shapes": [{"x0": 2023, "x1": 2023, "color": "#95a5a6", "width": 1, "dash": "dot"}, {"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}, {"title": "Construction \u2014 Labour", "traces": [{"name": "", "x": [2000, 2002, 2004, 2006, 2008, 2010, 2012, 2014, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2026, 2027, 2028, 2029, 2030], "y": [1820, 1870, 1950, 2100, 2150, 2050, 2050, 2100, 2190, 2250, 2280, 2300, 2170, 2250, 2310, 2330, 2644.089987192464, 2738.1121246418493, 2830.76929148367, 2927.344278208221, 3030.830365255005], "color": "#ffe119", "width": 2.5, "dash": "solid", "hovertemplate": "%{x}: %{y:,.1f} thousandsBaseline", "legendgroup": "baseline", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [2644.089987192464, 2730.500172935345, 2820.8615989634773, 2916.600924707197, 3019.2525932597305], "color": "#ffe119", "width": 2.5, "dash": "dash", "hovertemplate": "%{x}: %{y:,.1f} thousandsReform", "legendgroup": "reform", "showlegend": false}], "shapes": [{"x0": 2023, "x1": 2023, "color": "#95a5a6", "width": 1, "dash": "dot"}, {"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}, {"title": "Trade & Transport \u2014 Output", "traces": [{"name": "", "x": [2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2026, 2027, 2028, 2029, 2030], "y": [194.1, 201.9, 210.2, 219.8, 228.5, 237.9, 249.6, 258.7, 251.3, 240.2, 254.1, 262.5, 270.5, 281.5, 296.2, 310.0, 349.5, 363.4, 377.3, 370.1, 303.5, 376.2, 413.0, 421.3, 478.0923225769035, 495.0929777303052, 511.8468251081846, 529.3090748537012, 548.0209583184264], "color": "#4363d8", "width": 2.5, "dash": "solid", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnBaseline", "legendgroup": "baseline", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [478.0923225769035, 493.7562266904334, 510.2652184186003, 527.4459069102162, 545.8836765809846], "color": "#4363d8", "width": 2.5, "dash": "dash", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnReform", "legendgroup": "reform", "showlegend": false}], "shapes": [{"x0": 2023, "x1": 2023, "color": "#95a5a6", "width": 1, "dash": "dot"}, {"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}, {"title": "Trade & Transport \u2014 Capital", "traces": [{"name": "", "x": [2000, 2002, 2004, 2006, 2008, 2010, 2012, 2014, 2016, 2018, 2019, 2020, 2021, 2022, 2026, 2027, 2028, 2029, 2030], "y": [128, 133, 140, 153, 170, 168, 178, 185, 195, 208, 210, 207, 223, 248, 297.95226523875465, 308.54725594305353, 318.9884333934623, 329.8710948003595, 341.5325413493191], "color": "#4363d8", "width": 2.5, "dash": "solid", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnBaseline", "legendgroup": "baseline", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [297.95226523875465, 307.7450330776016, 318.2260510376519, 328.78252018751834, 340.1356732552004], "color": "#4363d8", "width": 2.5, "dash": "dash", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnReform", "legendgroup": "reform", "showlegend": false}], "shapes": [{"x0": 2023, "x1": 2023, "color": "#95a5a6", "width": 1, "dash": "dot"}, {"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}, {"title": "Trade & Transport \u2014 Labour", "traces": [{"name": "", "x": [2000, 2002, 2004, 2006, 2008, 2010, 2012, 2014, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2026, 2027, 2028, 2029, 2030], "y": [7400, 7500, 7600, 7750, 7700, 7500, 7700, 7900, 8150, 8260, 8340, 8320, 7750, 8180, 8470, 8500, 9645.821841689245, 9988.821055560395, 10326.840762923259, 10679.152946253167, 11056.677298140576], "color": "#4363d8", "width": 2.5, "dash": "solid", "hovertemplate": "%{x}: %{y:,.1f} thousandsBaseline", "legendgroup": "baseline", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [9645.821841689245, 9961.251909447048, 10290.593551845399, 10640.06724646988, 11014.772491180624], "color": "#4363d8", "width": 2.5, "dash": "dash", "hovertemplate": "%{x}: %{y:,.1f} thousandsReform", "legendgroup": "reform", "showlegend": false}], "shapes": [{"x0": 2023, "x1": 2023, "color": "#95a5a6", "width": 1, "dash": "dot"}, {"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}, {"title": "Info & Finance \u2014 Output", "traces": [{"name": "", "x": [2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2026, 2027, 2028, 2029, 2030], "y": [119.2, 127.5, 131.5, 137.1, 143.4, 150.6, 162.0, 176.9, 179.8, 170.3, 178.3, 185.6, 192.1, 200.0, 214.0, 228.2, 259.4, 270.5, 282.0, 290.3, 289.3, 308.8, 332.0, 344.4, 390.826004973856, 404.7235260629412, 418.4192892647965, 432.69414996348127, 447.99054840936634], "color": "#f58231", "width": 2.5, "dash": "solid", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnBaseline", "legendgroup": "baseline", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [390.826004973856, 403.62267807204995, 417.1221894680756, 431.1580857311109, 446.2254656486334], "color": "#f58231", "width": 2.5, "dash": "dash", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnReform", "legendgroup": "reform", "showlegend": false}], "shapes": [{"x0": 2023, "x1": 2023, "color": "#95a5a6", "width": 1, "dash": "dot"}, {"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}, {"title": "Info & Finance \u2014 Capital", "traces": [{"name": "", "x": [2000, 2002, 2004, 2006, 2008, 2010, 2012, 2014, 2016, 2018, 2019, 2020, 2021, 2022, 2026, 2027, 2028, 2029, 2030], "y": [72, 80, 85, 95, 108, 112, 120, 128, 135, 146, 148, 150, 161, 178, 213.85283553426746, 221.45730466880457, 228.95137558079145, 236.76231804219353, 245.1322272587855], "color": "#f58231", "width": 2.5, "dash": "solid", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnBaseline", "legendgroup": "baseline", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [213.85283553426746, 220.8748719575256, 228.3904447106185, 235.9715318999326, 244.12473380475188], "color": "#f58231", "width": 2.5, "dash": "dash", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnReform", "legendgroup": "reform", "showlegend": false}], "shapes": [{"x0": 2023, "x1": 2023, "color": "#95a5a6", "width": 1, "dash": "dot"}, {"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}, {"title": "Info & Finance \u2014 Labour", "traces": [{"name": "", "x": [2000, 2002, 2004, 2006, 2008, 2010, 2012, 2014, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2026, 2027, 2028, 2029, 2030], "y": [1650, 1700, 1750, 1850, 1900, 1950, 2050, 2150, 2300, 2350, 2410, 2450, 2420, 2510, 2580, 2620, 2973.1827323795087, 3078.907195949204, 3183.096799865757, 3291.6918493156822, 3408.058178956271], "color": "#f58231", "width": 2.5, "dash": "solid", "hovertemplate": "%{x}: %{y:,.1f} thousandsBaseline", "legendgroup": "baseline", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [2973.1827323795087, 3070.317044872506, 3171.7649752582347, 3279.5455063917075, 3395.039396712658], "color": "#f58231", "width": 2.5, "dash": "dash", "hovertemplate": "%{x}: %{y:,.1f} thousandsReform", "legendgroup": "reform", "showlegend": false}], "shapes": [{"x0": 2023, "x1": 2023, "color": "#95a5a6", "width": 1, "dash": "dot"}, {"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}, {"title": "Real Estate \u2014 Output", "traces": [{"name": "", "x": [2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2026, 2027, 2028, 2029, 2030], "y": [126.7, 138.3, 148.9, 156.0, 161.8, 169.0, 177.6, 182.7, 179.5, 179.0, 187.4, 192.5, 200.8, 210.3, 223.0, 236.5, 257.4, 262.7, 268.6, 271.2, 273.7, 284.3, 299.0, 310.2, 352.01575709317694, 364.53321075703934, 376.8689417245641, 389.72626399149794, 403.5036821039066], "color": "#911eb4", "width": 2.5, "dash": "solid", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnBaseline", "legendgroup": "baseline", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [352.01575709317694, 363.56355241642564, 375.7458722782249, 388.3778111180874, 401.9421228541645], "color": "#911eb4", "width": 2.5, "dash": "dash", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnReform", "legendgroup": "reform", "showlegend": false}], "shapes": [{"x0": 2023, "x1": 2023, "color": "#95a5a6", "width": 1, "dash": "dot"}, {"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}, {"title": "Real Estate \u2014 Capital", "traces": [{"name": "", "x": [2000, 2002, 2004, 2006, 2008, 2010, 2012, 2014, 2016, 2018, 2019, 2020, 2021, 2022, 2026, 2027, 2028, 2029, 2030], "y": [610, 660, 730, 840, 940, 970, 1040, 1100, 1170, 1250, 1280, 1310, 1400, 1510, 1814.144840768224, 1878.6546632016566, 1942.227961387613, 2008.4893272118666, 2079.492489667225], "color": "#911eb4", "width": 2.5, "dash": "solid", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnBaseline", "legendgroup": "baseline", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [1814.144840768224, 1873.8077341705962, 1937.5277697210552, 2001.9014822186118, 2071.132929858763], "color": "#911eb4", "width": 2.5, "dash": "dash", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnReform", "legendgroup": "reform", "showlegend": false}], "shapes": [{"x0": 2023, "x1": 2023, "color": "#95a5a6", "width": 1, "dash": "dot"}, {"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}, {"title": "Real Estate \u2014 Labour", "traces": [{"name": "", "x": [2000, 2002, 2004, 2006, 2008, 2010, 2012, 2014, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2026, 2027, 2028, 2029, 2030], "y": [380, 400, 420, 450, 470, 470, 490, 510, 540, 550, 560, 570, 530, 570, 590, 595, 675.2075289182472, 699.2174738892277, 722.8788534046281, 747.5407062377218, 773.9674108698403], "color": "#911eb4", "width": 2.5, "dash": "solid", "hovertemplate": "%{x}: %{y:,.1f} thousandsBaseline", "legendgroup": "baseline", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [675.2075289182472, 697.3016180107712, 720.3270910521097, 744.8271334740789, 771.0805124272957], "color": "#911eb4", "width": 2.5, "dash": "dash", "hovertemplate": "%{x}: %{y:,.1f} thousandsReform", "legendgroup": "reform", "showlegend": false}], "shapes": [{"x0": 2023, "x1": 2023, "color": "#95a5a6", "width": 1, "dash": "dot"}, {"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}, {"title": "Business Services \u2014 Output", "traces": [{"name": "", "x": [2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2026, 2027, 2028, 2029, 2030], "y": [108.0, 114.0, 120.3, 128.7, 136.7, 145.3, 157.0, 167.5, 163.3, 153.6, 161.9, 167.7, 173.4, 182.8, 193.6, 197.8, 214.2, 224.1, 233.0, 237.3, 216.5, 243.4, 265.9, 274.6, 311.61678561504317, 322.69767786551586, 333.6177027645561, 344.99945871072003, 357.19571600816494], "color": "#42d4f4", "width": 2.5, "dash": "solid", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnBaseline", "legendgroup": "baseline", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [311.61678561504317, 321.8199401817216, 332.58348788598596, 343.7781606268841, 355.7955088014129], "color": "#42d4f4", "width": 2.5, "dash": "dash", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnReform", "legendgroup": "reform", "showlegend": false}], "shapes": [{"x0": 2023, "x1": 2023, "color": "#95a5a6", "width": 1, "dash": "dot"}, {"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}, {"title": "Business Services \u2014 Capital", "traces": [{"name": "", "x": [2000, 2002, 2004, 2006, 2008, 2010, 2012, 2014, 2016, 2018, 2019, 2020, 2021, 2022, 2026, 2027, 2028, 2029, 2030], "y": [38, 40, 43, 50, 58, 59, 64, 69, 74, 82, 83, 82, 89, 98, 117.73920158628209, 121.92593178394857, 126.05188093773911, 130.3522874614324, 134.96043972674707], "color": "#42d4f4", "width": 2.5, "dash": "solid", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnBaseline", "legendgroup": "baseline", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [117.73920158628209, 121.60770510199247, 125.75061694229791, 129.92082138993507, 134.40575231947014], "color": "#42d4f4", "width": 2.5, "dash": "dash", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnReform", "legendgroup": "reform", "showlegend": false}], "shapes": [{"x0": 2023, "x1": 2023, "color": "#95a5a6", "width": 1, "dash": "dot"}, {"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}, {"title": "Business Services \u2014 Labour", "traces": [{"name": "", "x": [2000, 2002, 2004, 2006, 2008, 2010, 2012, 2014, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2026, 2027, 2028, 2029, 2030], "y": [3900, 4100, 4350, 4650, 4800, 4700, 4950, 5150, 5380, 5480, 5560, 5600, 5240, 5580, 5760, 5820, 6604.550955133107, 6839.404534513117, 7070.848616495689, 7312.078840846287, 7570.571985315076], "color": "#42d4f4", "width": 2.5, "dash": "solid", "hovertemplate": "%{x}: %{y:,.1f} thousandsBaseline", "legendgroup": "baseline", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [6604.550955133107, 6820.459383952515, 7046.1006463379545, 7285.316632288789, 7541.728106051026], "color": "#42d4f4", "width": 2.5, "dash": "dash", "hovertemplate": "%{x}: %{y:,.1f} thousandsReform", "legendgroup": "reform", "showlegend": false}], "shapes": [{"x0": 2023, "x1": 2023, "color": "#95a5a6", "width": 1, "dash": "dot"}, {"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}, {"title": "Public & Other \u2014 Output", "traces": [{"name": "", "x": [2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2026, 2027, 2028, 2029, 2030], "y": [272.7, 286.0, 303.2, 321.8, 337.5, 353.1, 364.6, 376.4, 391.3, 397.5, 404.9, 411.8, 419.1, 428.3, 441.6, 451.4, 474.2, 489.2, 501.6, 510.5, 514.5, 524.1, 537.6, 555.1, 629.9289063907883, 652.3287726990089, 674.4034479410236, 697.4115059370746, 722.0660668468039], "color": "#f032e6", "width": 2.5, "dash": "solid", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnBaseline", "legendgroup": "baseline", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [629.9289063907883, 654.6249699789095, 674.6057689754059, 697.7532375749838, 722.5426304509228], "color": "#f032e6", "width": 2.5, "dash": "dash", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnReform", "legendgroup": "reform", "showlegend": false}], "shapes": [{"x0": 2023, "x1": 2023, "color": "#95a5a6", "width": 1, "dash": "dot"}, {"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}, {"title": "Public & Other \u2014 Capital", "traces": [{"name": "", "x": [2000, 2002, 2004, 2006, 2008, 2010, 2012, 2014, 2016, 2018, 2019, 2020, 2021, 2022, 2026, 2027, 2028, 2029, 2030], "y": [270, 280, 295, 315, 340, 350, 365, 380, 400, 425, 430, 440, 465, 495, 594.7031100531595, 615.8503697250463, 636.6906231038864, 658.4120642184596, 681.6879353544878], "color": "#f032e6", "width": 2.5, "dash": "solid", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnBaseline", "legendgroup": "baseline", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [594.7031100531595, 618.0859065671483, 637.3718820706075, 658.892705025339, 682.0015118047507], "color": "#f032e6", "width": 2.5, "dash": "dash", "hovertemplate": "%{x}: %{y:,.1f} \u00a3bnReform", "legendgroup": "reform", "showlegend": false}], "shapes": [{"x0": 2023, "x1": 2023, "color": "#95a5a6", "width": 1, "dash": "dot"}, {"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}, {"title": "Public & Other \u2014 Labour", "traces": [{"name": "", "x": [2000, 2002, 2004, 2006, 2008, 2010, 2012, 2014, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2026, 2027, 2028, 2029, 2030], "y": [7800, 8000, 8150, 8300, 8450, 8500, 8550, 8600, 8640, 8700, 8760, 8800, 8700, 8850, 8960, 9020, 10235.919177886706, 10599.901873077031, 10958.600433125623, 11332.46583237689, 11733.08579167388], "color": "#f032e6", "width": 2.5, "dash": "solid", "hovertemplate": "%{x}: %{y:,.1f} thousandsBaseline", "legendgroup": "baseline", "showlegend": false}, {"name": "", "x": [2026, 2027, 2028, 2029, 2030], "y": [10235.919177886706, 10636.68353257661, 10958.052503103967, 11336.772169393194, 11742.002936875553], "color": "#f032e6", "width": 2.5, "dash": "dash", "hovertemplate": "%{x}: %{y:,.1f} thousandsReform", "legendgroup": "reform", "showlegend": false}], "shapes": [{"x0": 2023, "x1": 2023, "color": "#95a5a6", "width": 1, "dash": "dot"}, {"x0": 2027, "x1": 2027, "color": "#b8860b", "width": 1.2, "dash": "dash"}]}]} \ No newline at end of file diff --git a/olg/index.html b/olg/index.html index a731b7e..3c8872c 100644 --- a/olg/index.html +++ b/olg/index.html @@ -4,12 +4,12 @@ The overlapping-generations model (OG-UK) — PolicyEngine Macro - + - + @@ -19,7 +19,7 @@ - + + + + + + + + + + + + +
+
+
+

model 05 — overlapping generations · psl-og · UK · local

+

+ Model long-run behavioural change. +

+

+ Estimate how reforms affect work, saving, investment, and public + finances over decades, for the UK. +

+
+
+ + + +
+
+ methodology +

Core elements of OG-UK.

+
+
+

+ Eight building blocks, from overlapping cohorts to UK calibration; each + step's formal structure follows its narrative. The theory is + OG-Core's, documented + in full at pslmodels.github.io/OG-Core. +

+
+ +
+
+
+
1

The overlapping-generations idea

+
+ + + Overlapping generations at a point in time + youngenteringworkforce + + + + mid-careerpeakearnings + + + + pre-retirementpeak savings + + elderlydrawingpensions + + age → + +
click to expand
+
+

Many generations coexist

+

The economy is not a single "representative agent." Many generations coexist — new labour-force entrants, workers at peak earnings, retirees drawing savings and pensions — each with a different remaining lifespan, accumulated wealth, and response to a policy change.

+

Age cohorts

+

OG-Core models S economically active age cohorts (typically 80, ages 21 to 100). Each period a new cohort is born and the oldest dies with some probability; the age distribution evolves with fertility, mortality, and immigration, reaching a stationary distribution in the long run.

+

Ability types

+

Each cohort splits into J ability types with distinct permanent labour productivity. High-ability types earn more per hour and typically save more, so reforms hit the income distribution, not just the average.

+ +
+ +
+
2

Household decisions

+
+ + Three components of lifetime utility + + consumption + CRRA utility with + risk aversion σ + composite of I goods + + + labour disutility + elliptical function + Inada at both bounds + always interior solutions + + + + bequests + “Warm glow” + weighted by ρ + drives wealth distribution + maximise discounted sum over remaining lifetime + +
click to expand
+
+

Three decisions each period

+

Each period, every household chooses how much to consume, work, and save to maximise expected lifetime utility, subject to a budget constraint.

+

Consumption utility

+

Consumption utility is CRRA (constant relative risk aversion); the risk aversion parameter governs the preference for smooth consumption. A Stone-Geary Cobb-Douglas form first aggregates the individual goods, allowing for minimum subsistence levels.

+

Labour disutility

+

Labour disutility is elliptical rather than a standard power function: the marginal cost of work is zero at zero hours and infinite at the upper bound, so solutions stay interior without costly occasionally-binding constraint methods.

+

Bequest utility

+

Bequest utility is the "warm glow" of leaving wealth at death, weighted by age-specific mortality. Its strength varies by ability type, calibrated to match the observed wealth distribution.

+

Budget constraint and Euler equations

+

Each period, savings returns, labour earnings, bequests received, government transfers, and pensions must cover consumption (including consumption taxes), income and wealth taxes, and savings carried forward.

+

Two Euler equations characterise the optimum: work until the after-tax wage equals the marginal disutility of labour; save until the discounted, after-tax return justifies forgoing consumption today.

+ +
+ +
+
3

Firms and production

+
+ + CES production + + capital K + private + public + + labour L + efficiency-weighted + + TFP Z + by industry + + + + + CES aggregator per industry m + nests Cobb-Douglas when ε = 1 + + + output Y + at price p + +
click to expand
+
+

CES technology

+

Production comprises M industries of perfectly competitive firms combining private capital, public capital (government infrastructure), and labour through a constant elasticity of substitution (CES) technology.

+

Substitution and productivity

+

The elasticity parameter sets how easily firms substitute capital for labour; at one, the function reduces to Cobb-Douglas. Total factor productivity varies by industry and over time, capturing sector-specific technological change.

+

First-order conditions

+

Profit maximisation yields the standard conditions: the wage equals the marginal product of labour; the rental rate equals the after-tax marginal product of capital, net of depreciation, tax deductions, and investment tax credits.

+

Public capital rents

+

A distinctive OG-Core feature: public capital generates economic rents. Firms cannot deduct the cost of government infrastructure, so its returns flow to private capital owners through an augmented rate of return.

+ +
+ +
+
4

Government

+
+ + Government budget constraint + + revenue + Income + consumption + + wealth + corporate + bequest + + new debt D(t+1) + = + + spending + Debt service + public goods G + + infrastructure I + pensions + + transfers TR + UBI + + Fiscal closure rule + After period T(G1): adjust G, TR, or both to stabilise debt/GDP + +
click to expand
+
+

Revenue and spending

+

The government taxes through five instruments — individual income, consumption, wealth, corporate income, and bequests — and spends on public goods, infrastructure, pensions, lump-sum transfers, and universal basic income. Debt finances the difference.

+

Income tax: Gouveia-Strauss functions

+

OG-UK uses the Gouveia-Strauss specification: the effective tax rate is a smooth, monotonically increasing function of income, the marginal rate its analytical derivative. Estimation runs on PolicyEngine-UK output from the Enhanced FRS — the Family Resources Survey enhanced with HMRC's Survey of Personal Incomes, the Living Costs and Food Survey, and the Wealth and Assets Survey.

+

Other tax instruments

+

The wealth tax is a progressive three-parameter function, spanning zero taxation to smoothly increasing marginal rates. Consumption taxes are linear rates by good, covering VAT and excise. Corporate income taxes are flat rates by industry.

+

Fiscal closure rule

+

Debt cannot grow without bound, so a fiscal closure rule activates after a specified period, bringing debt-to-GDP gradually to a target via government spending, transfers, or both — the modeller's choice.

+ +
+ +
+
5

Market clearing and equilibrium

+
+ + Four markets must clear simultaneously + + Labour market + household supply = firm demand + + Capital market + domestic + foreign savings = demand + + Goods markets + output = consumption per industry + + Debt market + domestic + foreign holdings = issuance + +
click to expand
+
+

Supply equals demand

+

Market clearing conditions tie households, firms, and government together: in equilibrium, supply equals demand in every market simultaneously.

+

Labour and capital markets

+

Labour: efficiency-weighted supply across ages and ability types must equal firm demand across industries. Capital: total domestic and foreign savings must equal firm capital demand plus government debt. The model is an open economy: parameters for foreign capital and foreign debt holdings set the degree of capital mobility.

+

Goods and debt markets

+

Each industry's output must equal consumption demand; the final industry clears residually, absorbing investment, government purchases, and remaining demand. Government debt splits between domestic and foreign holders.

+

What defines an equilibrium

+

An equilibrium is a set of prices — interest rate, wage, goods prices — at which households and firms optimise, the government budget holds, and all four markets clear: a large-scale nonlinear fixed-point problem.

+ +
+ +
+
6

How the model is solved

+
+ + Two-stage solution + + Stage 1: steady state + outer: guess prices + inner: solve Euler equations + aggregate and check + iterate to convergence + + + fixed-point + + converged + + Stage 2: transition + guess time paths + solve all cohorts + update paths + iterate for all t = 1..T + + + TPI iteration + + + OUT + PUT + +
click to expand
+
+

Two-stage approach

+

The model solves in two stages: first the long-run steady state, then the transition path from today's economy to it.

+

Stage 1: Steady state

+

The steady state — all variables constant after removing trend growth — comes from a nested fixed-point algorithm. The outer loop guesses aggregate prices and quantities; the inner loop solves the Euler equations for every ability type and age, in parallel since types are independent. Aggregation, firm demands, and the government budget check yield new guesses; iterate to convergence.

+

Stage 2: Transition path (TPI)

+

Time Path Iteration (TPI) then solves the economy's path to the steady state over T periods: guess entire time paths for prices and quantities, solve every cohort's lifetime decisions given those paths (with rational expectations about future prices), check the implied paths against the guesses, iterate. This produces the year-by-year projections.

+ +
+ +
+
7

Calibrating for the United Kingdom

+
+ + Data sources for OG-UK + + ONS + national accounts + + OBR + fiscal forecasts + + HMRC / GOV.UK + tax parameters + + PolicyEngine + tax functions + + + + + + OG-UK calibration + UK demographics, fiscal parameters, tax functions, institutional structure + +
click to expand
+
+

From theory to UK data

+

OG-Core supplies the theory; the OG-UK calibration layer anchors every parameter to official UK data: depreciation from ONS capital-stocks data (6.5%/yr), potential-output growth from the OBR EFO (1.1%/yr), the discount factor matched to the ONS household saving ratio, the world interest rate to Bank of England gilt yields, and a Frisch labour-supply elasticity of 0.35–0.4 (Blundell, MaCurdy & Meghir). Demographics — population by age, mortality, immigration — come from UN data; the state pension age matches current UK rules.

+

Tax function estimation

+

Taxes are not stylized wedges. OG-UK runs PolicyEngine-UK on household microdata under baseline and reform, computes marginal rates by perturbation — add £1 of employment (or dividend) income per adult, rerun, read the change in net income — and fits three-parameter Gouveia–Strauss schedules to the effective-rate microdata for each year of a three-year budget window. Marginal-rate schedules are analytical derivatives of the same fitted parameters, so ETRs and MTRs are consistent by construction; the model sees the reform's true shape across the income and age distribution.

+

Real-world mapping

+

The model works in abstract units. OG-UK anchors steady-state GDP to the ONS figure — live series with cached fallbacks — and scales all other variables proportionally, so results read as changes in billions of pounds.

+ +
+ +
+
8

What this enables

+
+ + Static vs dynamic analysis + + static (before) + household-level impacts + fixed wages, prices, GDP + no behavioural response + immediate effects only + PolicyEngine static analysis + + +OG-UK + + dynamic (now) + economy-wide effects + endogenous wages, r, GDP + labour + savings responses + multi-decade transition paths + PolicyEngine + OG-UK + +
click to expand
+
+

Two complementary views

+

Paired with a static microsimulation engine, OG-UK gives two views of a UK tax or benefit reform. Static analysis shows which households gain or lose, and by how much. OG-UK shows GDP, investment, government revenue, interest rates, and wages as the economy adjusts over time.

+

Why both views matter

+

The two can tell different stories. A reform that raises revenue statically may raise less in practice as higher rates reduce labour supply and investment; one that costs revenue upfront might pay for itself through extra activity. OG-UK captures both dynamics.

+

Year-by-year transition paths

+

Rather than comparing two steady states, the transition path shows the year-by-year adjustment — how GDP dips in the first years, how interest rates respond, when revenue stabilises — on paths that map onto OBR forecast horizons.

+

What the model does not capture

+

OG-UK inherits the simplifications of structural macro models. Caveats for reading its outputs:

+
    +
  • Smoothed tax functions, not statutory bands. A single Gouveia–Strauss function fitted to PolicyEngine-UK output folds income tax and National Insurance together. Reforms changing average liability flow through cleanly; reforms whose mechanism is the kink itself (a new threshold, an allowance taper change) register only as far as the smoothed function shifts.
  • +
  • Permanent ability types, no earnings risk. With J = 7 deterministic ability types and no idiosyncratic earnings shock within a type, precautionary savings and earnings-risk-driven inequality are not channels here — the same deliberate trade-off the OBR's own UK OLG model (compared on the Validation tab) makes.
  • +
  • UK as a single entity. Calibration is to UK-wide aggregates, with no England / Scotland / Wales / Northern Ireland breakdown; devolved tax differentials and explicitly regional reforms sit below the model's granularity.
  • +
  • Reforms are step changes. A PolicyEngine reform sets parameter values from a start date, holding thereafter; phased introductions, sunset clauses or year-on-year indexation changes need explicit scripting.
  • +
  • Truncated horizon. The default 60-period transition assumes the steady state arrives by year 60; longer-run effects collapse into the steady-state anchor.
  • +
+

These are tractability choices, not bugs — worth knowing when reading the Showcase charts.

+ +
+
+ +
+
+ + + + +
+ + + + + + + + diff --git a/olg/validation/index.html b/olg/validation/index.html new file mode 100644 index 0000000..8f01b82 --- /dev/null +++ b/olg/validation/index.html @@ -0,0 +1,409 @@ + + + + + +OG-UK validation — PolicyEngine Macro + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+

model 05 — overlapping generations · psl-og · UK · local

+

+ Model long-run behavioural change. +

+

+ Estimate how reforms affect work, saving, investment, and public + finances over decades, for the UK. +

+
+
+ + + +
+
+ obr comparison +

Two UK overlapping-generations models, side by side.

+
+
+

+ This page compares OG-UK — the UK calibration of + OG-Core, maintained by the + Policy Simulation Library — with the + OBR UK OLG model described in + OBR Working Paper No. 22 + (Brzezinski, Hantzsche & Watson, April 2025) — the closest available + external check for a model of this kind. +

+
+ + + + + + + + + + +
DimensionOG-UK strengthOBR UK OLG strength
Transition dynamicsFull year-by-year pathsSteady-state only
UK institutional detailSmooth approximationsExplicit bands and rates
Within-cohort inequalityPermanent typesStochastic earnings
Production structureMulti-sector CESSingle-sector Cobb-Douglas
Tax instruments5 instruments3 instruments (more detailed)
Fiscal policy leversG, TR, UBI, IgRBI, age-dependent transfers
OpennessOpen-source, PythonMATLAB, closed toolkit
+
+
+ +
+
+ production +

Production technology.

+
+
+

+ How each model represents firms. +

+
+ + + + + + + + + + +
OG-UKOBR UK OLG
Production functionCES (nests Cobb-Douglas as special case)Cobb-Douglas only
IndustriesM industries (multi-sector)Single sector
Public capitalYes — generates rents to private capitalNo
Capital shareConfigurable per industryα = 0.33
Labour inputEfficiency-weighted across permanent typesEfficiency-weighted by age + stochastic shocks
TFPVaries by industry and over timeNormalised to 1, with labour-augmenting growth g = 1.2%
DepreciationConfigurable per industryδ = 5%
+
+
+ +
+
+ households +

Household heterogeneity.

+
+
+

+ How households differ within each cohort — the deepest structural + difference between the two models. +

+
+ + + + + + + + + + + +
OG-UKOBR UK OLG
Within-cohort variationJ permanent ability types (deterministic)Stochastic earnings shocks (AR(1) persistent + i.i.d. transitory)
Income inequality driverFixed type assignment at birthRandom shocks each period (ρz = 0.9, σz = 0.05, σe = 0.7)
Precautionary savingsNot a primary channelYes — driven by earnings uncertainty
Utility functionCRRA on consumption; elliptical labour disutilityCobb-Douglas over consumption and leisure (non-separable)
Labour disutilityElliptical function — always interior solutionsImplicit in Cobb-Douglas composite — corner solutions possible
Risk aversion parameterσ on consumption onlyσ₂ = 2 on consumption-leisure composite
Consumption aggregationStone-Geary Cobb-Douglas (subsistence levels, I goods)Single consumption good
Discount factorβj varies by ability typeβ = 0.99 (uniform)
+
+
+ +
+
+ bequests +

Bequest motive.

+
+
+

+ How each model handles wealth transfer across generations at death. +

+
+ + + + + + +
OG-UKOBR UK OLG
Motive"Warm glow" weighted by mortality probability ρs"Warm glow" with target level, kicks in at age j* = 91
Varies by typeYes — χbj calibrated per ability typeUniform (φ₁ = 10, φ₂ = 4.55)
DistributionTo surviving householdsEqual distribution to ages 55–75
+
+
+ +
+
+ taxes +

Tax system.

+
+
+

How each model represents the UK tax system.

+
+
+
OG-UK — estimated tax functions
+
Smooth Gouveia–Strauss, DEP or linear forms fitted to PolicyEngine UK microdata, folding in income tax and NICs
+
Consumption, wealth, corporate and bequest taxes modelled separately on top
+
Any PolicyEngine-representable reform — rate, threshold, allowance — flows through after re-estimating on post-reform microdata
+
+
+
OBR UK OLG — statutory schedule
+
Income tax as explicit bands: PA £12,570, HRT £50,270, ART £125,140 at 20 / 40 / 45%
+
NICs separate (£12,570 / £50,270 at 8 / 2%); VAT effective 9.3%; explicit pension relief and drawdown taxation
+
"+1 pp basic rate from 2027–28" is one parameter — less flexible for reforms that reshape the schedule
+
+
+
+
+ +
+
+ government +

Government & fiscal policy.

+
+
+

+ The fiscal closure rule — which item adjusts to keep debt sustainable — determines + who bears the long-run burden of any reform. +

+
+ + + + + + + + + + +
OG-UKOBR UK OLG
Budget constraintRevenue + new debt = debt service + G + Ig + pensions + TR + UBIRevenue = welfare transfers + RBI + debt service (growth-adjusted)
Fiscal closure ruleConfigurable: adjust G, TR, or combination after period TG1Residual Budgetary Item (RBI) adjusts to stabilise debt/GDP at 100%
Debt targetConfigurableFixed at 100% of GDP
Public investmentExplicit (Ig contributes to public capital stock)Not modelled as productive
Welfare transfersLump-sum TRAge-dependent transfers (γj scaling) calibrated to OBR welfare spending projections
State pensionPart of transfersExplicitly modelled as age-dependent welfare transfer from state pension age onwards
Spending categoriesG (public goods) + Ig (infrastructure) + pensions + TR + UBIWelfare transfers + residual non-interest spending (G⁰)
+
+
+ +
+
+ open economy +

Open economy.

+
+
+

+ How foreign capital enters each model, and where the interest rate comes from. +

+
+ + + + + + + +
OG-UKOBR UK OLG
ApproachParameterised capital mobilityK for capital, ζD for debt)Binary switch: fully open (r = 5% exogenous) or fully closed (r endogenous)
Foreign capitalContinuous degree of opennessEither all or nothing
Net foreign assetsDerived from capital mobility parametersNFA residually determined in open economy
Interest rateEndogenous (influenced by openness parameters)Either fixed at 5% (open) or MPK − δ (closed)
+
+
+ +
+
+ solution +

Solution method & calibration.

+
+
+

Both are rational-expectations dynamic general equilibrium; they solve different problems.

+
+
+
OG-UK — Time Path Iteration
+
Households' Euler equations solved directly
+
Full year-by-year transition path; continuous state space, expectations over the whole path
+
~17-minute solve
+
+
+
OBR UK OLG — Value Function Iteration
+
Kirkby's VFI toolkit; assets and stochastic earnings on a grid
+
Compares long-run steady states only — no adjustment path
+
Under two minutes — a difference in method, not ambition
+
+
+

Calibration differs mainly in data sources and anchors.

+
+
+
OG-UK
+
UN World Population Prospects demographics (country 826)
+
Age-earnings profiles via permanent ability types
+
Tax functions from PolicyEngine-UK's Enhanced FRS (HMRC SPI, LCFS, WAS)
+
ONS GDP anchors the aggregates; modeller-set fiscal targets and growth
+
+
+
OBR UK OLG
+
ONS mortality; ASHE + HMRC earnings distribution
+
HMRC / GOV.UK tax parameters directly
+
Pinned to the OBR March 2024 EFO — GDP per person £35,100 (S = 19.81)
+
n = 0.75%, g = 1.2%, debt / GDP at 100%
+
+
+

+ Source: OBR Working Paper No. 22, "A new UK + overlapping generations model" (April 2025); OG-Core documentation and OG-UK + calibration code. +

+
+
+ +
+
+ calibration +

No ground truth. Calibration targets instead.

+
+
+
+ No published replication target exists. The table + compares calibration targets with official UK aggregates. Imposed + matches are anchors; structural counterfactuals remain conditional on + the model's auditable assumptions and code. +
+
+ + + + + + + + + + + +
psl-og calibration targets against official UK aggregates
QuantityOfficial UK valueSourceModel treatmentDeviation
Government debt-to-GDP95.1% (May 2026); OBR March 2026 forecast 94.4%ONS public sector financesimposed via closure target−0.1pp vs latest ONS (target 95.0%)
Household saving ratio8.9% (2026Q1, down from 9.6%)ONS quarterly sector accountstargeted through β0 by construction
Potential growth≈1.1% / yrOBR EFOimposed, gy = 0.011≈0
Depreciation / capital≈6–7% (CFC / net stock)ONS capital stocks bulletinimposed, δ = 0.065within range
Labour share of income≈0.59–0.60, rising in 2024ONS labour-share series / Blue Book 2025imposed, γ = 0.35 (OG-Core default)≈5pp — the capital share is not yet re-anchored to ONS factor shares
Net capital stock£5.6tn (2024) whole-economy net stock; the model's K maps to the narrower business-capital conceptONS capital stocks bulletinemergent — checked, not imposedconcept-adjusted; no published reconciliation
+
+

+ A zero deviation on an imposed quantity is not independent evidence. +

+

+ The nearest external benchmark is + OBR + Working Paper 22, Table 5.1 — the long-run effect of a 1pp + basic-rate rise: GDP/person −0.1%, labour supply −0.2%, + productivity +0.1%. The OG-UK side of that comparison is open + validation work: WP 22's figures are the target a future run will + be judged against, not a match claimed today. +

+
+ What would count as over-identifying evidence, and the dating and structural caveats +

+ The genuinely over-identifying checks are the ones the + steady state must deliver: the capital–output ratio and + the interest rate consistent with the calibrated (γ, δ, β) triple. + These emerge in an economically sensible range in the deployed + configuration, but no systematic published reconciliation of + achieved-versus-target moments exists for OG-UK 0.3.2, and + the working paper declines to manufacture one. Producing that + reconciliation from logged solves is the single most valuable next + step for this member. +

+

+ Two dating caveats: the official column reports figures current at + mid-2026 while the deployed calibration was frozen against slightly + earlier vintages, and the saving ratio is volatile quarter to + quarter, so β is matched to a smoothed level rather than the latest + print. Two structural caveats carried over from OG-Core: the ability + types and lifetime earnings profiles are still the US tax-microdata + estimates, and UK re-estimation is an open task. +

+
+ +
+
+ +
+
+ limits +

Experimental limitations.

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
Known limits of the overlapping-generations model
limitdetail
Version pinThe integration installs oguk from GitHub (0.3.2), which pins policyengine-uk==2.88.0. Versions ≥ 2.89 renamed the microdata dataset keys (enhanced_frs_2023_24_<year>populace_uk_*), so mixing them fails with a KeyError at calibration time; the adapter traps this and raises an actionable error. Lifting the pin is roadmap work — until it lands the OLG member scores against a slightly older statute vintage than the microsimulation member. A declared inconsistency, not a hidden one.
Gated dataCalibration needs the private enhanced-FRS microdata, so a HUGGING_FACE_TOKEN with access is a hard prerequisite.
Thin CICI cannot exercise a seventeen-minute solve on every commit, so regressions in solve behaviour are caught by scheduled runs and by hand, not continuously.
Steady-state-only in the CLIBecause the CLI exposes steady-state solves only (Code, step 7), the model cannot yet speak to fiscal-year profiles through the suite — the long-run destination is available through it, the path there is not.
Ability profiles not UK-estimatedLifetime-ability profiles are inherited rather than re-estimated on UK microdata. On the roadmap.
+
+
+ +
+ + + + + + diff --git a/papers/boe-svar/index.html b/papers/boe-svar/index.html index 9c1efe4..f3d0ae5 100644 --- a/papers/boe-svar/index.html +++ b/papers/boe-svar/index.html @@ -19,7 +19,10 @@ - + + @@ -33,7 +36,7 @@ Home Economy Models - Track record + Forecasts Use @@ -42,20 +45,42 @@
+
+
+

model 02 — Bank of England structural VAR · boe-svar · UK · hosted

+

+ Explain UK growth and inflation. +

+

+ Decompose UK GDP and inflation into six structural shocks, forecast + with credible bands, and explain revisions between quarters. +

+
+
+ + +
boe-svar · UK · hosted · 30 pages -

An open replication of the Bank of England structural VAR

+

An open replication of the Bank of England structural VAR

@@ -88,16 +113,8 @@

An open replication of the Bank of England structural VAR

- Source, figures, and reproduction scripts: - papers/boe-svar/ -

- - - -

Cite this work

@@ -118,13 +135,20 @@

Cite this work

- + + diff --git a/papers/frb-us/index.html b/papers/frb-us/index.html index 639b8c2..900d9aa 100644 --- a/papers/frb-us/index.html +++ b/papers/frb-us/index.html @@ -19,7 +19,10 @@ - + + @@ -33,7 +36,7 @@ Home Economy Models - Track record + Forecasts Use @@ -42,20 +45,42 @@
-
+
+
+

model 03 — Federal Reserve macroeconomic model · frb-us · US · hosted

+

+ Test US monetary and fiscal shocks. +

+

+ Trace how a funds-rate or fiscal shock affects US output, inflation, + and unemployment quarter by quarter using the April 2026 baseline. +

+
+
+ + + +
frb-us · US · hosted · 36 pages -

An open Python implementation of frb-us

+

An open Python implementation of frb-us

@@ -88,16 +113,8 @@

An open Python implementation of frb-us

- Source, figures, and reproduction scripts: - papers/frb-us/ -

- - - -

Cite this work

@@ -118,13 +135,20 @@

Cite this work

- + + diff --git a/papers/index.html b/papers/index.html deleted file mode 100644 index 1206aae..0000000 --- a/papers/index.html +++ /dev/null @@ -1,104 +0,0 @@ - - - - - -Papers — PolicyEngine Macro - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-

evidence for all six models

-

- Choose a model. See the evidence. -

-

- Each card brings a model and its evidence together. Four have full - working papers; us-hank and PolicyEngine link to their evidence - pages. OLG is calibrated, and PolicyEngine is checked against - implemented legislation. -

-
-
obr-macro · UK · hosted · 36 pages

OBR macroeconometric model

Replication of the OBR's published equations against the March 2026 EFO and HMRC ready-reckoner anchors.

Published-anchor replication

-
boe-svar · UK · hosted · 30 pages

Bank of England structural VAR

UK GDP and inflation forecasts, identified shocks, and replication of published forecast-error variance shares.

Validation: published-anchor replication

-
frb-us · US · hosted · 36 pages

Federal Reserve macroeconomic model

A from-scratch Python implementation checked against pyfrbus impulse responses under the same policy rule.

Validation: published-anchor replication

-
us-hank · US · research

Two-asset heterogeneous-agent model

A sequence-space replication checked against the steady-state and shock-response targets in Auclert et al. (2021).

Validation: published-anchor replication; no predictive claim

-
psl-og · UK · local · 34 pages

PSL overlapping generations

The long-run work, saving, capital, and public-finance mechanisms behind the calibrated transition model.

Evidence: calibrated counterfactual; no independent reform replication

-
pe-microsim · UK + US · research

PolicyEngine microsimulation

Household tax-and-benefit calculations checked directly against implemented legislation rather than a working paper.

Evidence: deterministic rule calculations; population estimates also depend on survey data and calibration

-
-
-
- -
-

Cite this work

-

To cite an individual paper, use the “Cite this work” block on that paper’s page. To cite the model suite as a whole: PolicyEngine (2026). “PolicyEngine Macro: open economic models for the UK and US.” PolicyEngine Macro working papers. https://policyengine-macro.vercel.app/papers/

-
@techreport{policyengine2026macro,
-  title       = {PolicyEngine Macro: open economic models for the UK and US},
-  author      = {{PolicyEngine}},
-  year        = {2026},
-  institution = {PolicyEngine},
-  url         = {https://policyengine-macro.vercel.app/papers/},
-  note        = {PolicyEngine Macro working papers}
-}
-

No DOI is assigned. For an exact version stamp, cite the git commit of the artifact you used from github.com/PolicyEngine/macro.

-
- -
- - - - - diff --git a/papers/obr-macro/index.html b/papers/obr-macro/index.html index 69479c0..9282406 100644 --- a/papers/obr-macro/index.html +++ b/papers/obr-macro/index.html @@ -19,7 +19,10 @@ - + + @@ -33,7 +36,7 @@ Home Economy Models - Track record + Forecasts Use @@ -42,25 +45,46 @@
+
+
+

model 01 — OBR macroeconometric model · obr-macro · UK · hosted

+

+ UK fiscal reform, quarter by quarter. +

+

+ Run selected UK tax and spending scenarios and trace GDP, consumption, + and investment over 3–5 years against the March 2026 EFO baseline. + Borrowing is not yet returned by the PolicyEngine Macro adapter. +

+
+
+ + +
obr-macro · UK · hosted · 36 pages -

An open OBR macro model for UK fiscal scoring

+

An open OBR macro model for UK fiscal scoring

-

Current baseline: March 2026 EFO, with GDP MAPE 0.15% and consumption 0.25% over 2025Q1–2027Q4. The PDF preserves its November 2025 historical study below a dated current-vintage note.

- @@ -88,16 +112,8 @@

An open OBR macro model for UK fiscal scoring

- Source, figures, and reproduction scripts: - papers/obr-macro/ -

- - - -

Cite this work

@@ -118,13 +134,20 @@

Cite this work

- + + diff --git a/papers/obr-macro/main.pdf b/papers/obr-macro/main.pdf index d7311e4..e43c3dc 100644 Binary files a/papers/obr-macro/main.pdf and b/papers/obr-macro/main.pdf differ diff --git a/papers/obr-macro/main.tex b/papers/obr-macro/main.tex index 8eb82f6..a0a659b 100644 --- a/papers/obr-macro/main.tex +++ b/papers/obr-macro/main.tex @@ -26,9 +26,6 @@ \noindent I describe an open-source Python implementation of the Office for Budget Responsibility's macroeconometric model of the UK economy, built for near-term fiscal-reform scoring in the PolicyEngine Macro suite. The emulator transpiles the OBR's 372 published EViews equations into Python, solves them by Gauss--Seidel iteration with a demand-side closure swap, and anchors the baseline to the OBR's November 2025 \emph{Economic and Fiscal Outlook} using the OBR's own add-factor device. In anchored mode the model reproduces the published EFO path with a GDP MAPE of 0.18 per cent and consumption of 0.29 per cent over 2025Q1--2027Q4, hard-gated in continuous integration. The free-running fit is deliberately reported without flattery: of 21 headline variables, 10 are passthroughs and only 4 of the 11 actually computed land within band, with company profits 54.6 per cent off---a gap traced to unpublished OBR calibration constants rather than live bugs. A static-costing bridge injects PolicyEngine microsimulation costings into the model as a household-disposable-income shock, delivering the OBR's own ``static costing in, second-round effects out'' workflow. A worked example---raising the basic rate of income tax by 1p---yields a static costing of \pounds 6.46 billion in 2026 rising to \pounds 7.38 billion by 2030, within the range of HMRC's published ready reckoner, with second-round GDP effects building from $-$0.02 per cent on impact to $-$0.06 per cent by end-2027. Three further experiments---a \pounds 5bn government-consumption expansion, a VAT-style one per cent consumer-price shock, and a 2pp corporation-tax rise through the user-cost-of-capital channel---exercise the direct macro levers and are benchmarked against the official ready-reckoner numbers where they exist. \end{abstract} -\medskip -\noindent\textbf{Current-vintage note (21 July 2026).} The deployed emulator and current validation tables are anchored to the March 2026 EFO. Over 2025Q1--2027Q4 the anchored GDP MAPE is 0.15 per cent and consumption MAPE is 0.25 per cent; the free-running values are 5.75 and 9.56 per cent. References below to November 2025 describe the preserved historical study and must not be read as the live baseline. On the extended anchored horizon, GDP stays within 0.29 per cent of the EFO through 2031Q1, but the anchored unemployment rate is unreliable beyond 2027Q4, drifting to 0.9 per cent against the EFO's 4.1 per cent by 2031Q1. Figures~1--2 in the paper now display the March-vintage refresh (as their captions state), while the tables preserve the November 2025 study. - \medskip \noindent\textbf{Keywords:} macroeconometric models, fiscal policy, policy costings, OBR, microsimulation, open source diff --git a/papers/obr-macro/sections/calibration.tex b/papers/obr-macro/sections/calibration.tex index c1954bf..129a52f 100644 --- a/papers/obr-macro/sections/calibration.tex +++ b/papers/obr-macro/sections/calibration.tex @@ -1,4 +1,7 @@ \section{Calibration and validation} + +\noindent\textbf{Current-vintage note (21 July 2026).} The deployed emulator and current validation tables are anchored to the March 2026 EFO. Over 2025Q1--2027Q4 the anchored GDP MAPE is 0.15 per cent and consumption MAPE is 0.25 per cent; the free-running values are 5.75 and 9.56 per cent. References below to November 2025 describe the preserved historical study and must not be read as the live baseline. On the extended anchored horizon, GDP stays within 0.29 per cent of the EFO through 2031Q1, but the anchored unemployment rate is unreliable beyond 2027Q4, drifting to 0.9 per cent against the EFO's 4.1 per cent by 2031Q1. Figures~1--2 in the paper now display the March-vintage refresh (as their captions state), while the tables preserve the November 2025 study. + \label{sec:calibration} All results in this paper are conditioned on the November 2025 EFO vintage \citep{obr2025efo} and the 15 October 2025 model code. Validation is organised around a principle borrowed from the reliability literature: separate what the model reproduces \emph{by construction} from what it genuinely generates, and gate the former hard while reporting the latter honestly. diff --git a/papers/obr-working-paper-22.pdf b/papers/obr-working-paper-22.pdf new file mode 100644 index 0000000..19c7b77 Binary files /dev/null and b/papers/obr-working-paper-22.pdf differ diff --git a/papers/psl-og/index.html b/papers/psl-og/index.html index a3f0302..42cff0b 100644 --- a/papers/psl-og/index.html +++ b/papers/psl-og/index.html @@ -19,7 +19,11 @@ - + + @@ -33,7 +37,7 @@ Home Economy Models - Track record + Forecasts Use @@ -42,25 +46,45 @@
-
+
+
+

model 05 — overlapping generations · psl-og · UK · local

+

+ Model long-run behavioural change. +

+

+ Estimate how reforms affect work, saving, investment, and public + finances over decades, for the UK. +

+
+
+ + + +
psl-og · UK · local · 34 pages -

An overlapping-generations model for PolicyEngine reform scoring

+

An overlapping-generations model for PolicyEngine reform scoring

-

No published ground truth — unlike the OBR and SVAR members this is a structural counterfactual, calibrated to ONS and OBR national-accounts targets rather than validated against a replication anchor.

- @@ -88,19 +112,11 @@

An overlapping-generations model for PolicyEngine reform scoring

- Source, figures, and reproduction scripts: - papers/psl-og/ -

- - - - -

Cite this work

+

Cite this work

Ahmadi, Vahid (2026). “An overlapping-generations model for PolicyEngine reform scoring.” PolicyEngine Macro working paper. https://policyengine-macro.vercel.app/papers/psl-og

@techreport{policyengine2026pslog,
   title       = {An overlapping-generations model for PolicyEngine reform scoring},
@@ -110,7 +126,6 @@ 

Cite this work

url = {https://policyengine-macro.vercel.app/papers/psl-og}, note = {PolicyEngine Macro working paper} }
-

This model builds on the open-source OG-UK model from the Policy Simulation Library (PSLmodels/OG-UK); please also cite the upstream OG-UK project. No DOI is assigned. For an exact version stamp, cite the git commit of the artifact you used from github.com/PolicyEngine/macro.

@@ -118,13 +133,20 @@

Cite this work

- + + diff --git a/pe/code/index.html b/pe/code/index.html new file mode 100644 index 0000000..0927355 --- /dev/null +++ b/pe/code/index.html @@ -0,0 +1,352 @@ + + + + + +PolicyEngine microsim code — PolicyEngine Macro + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+

model 06 — tax-benefit microsimulation · pe-microsim · UK · US · hosted

+

+ Calculate taxes and benefits. +

+

+ Compare current law with a reform for a UK or US household, or estimate + population-wide revenue and distributional effects. +

+
+
+ + + +
+
+ code +

A household in one call.

+
+
+

+ Four steps from nothing to a population-wide costing; each step's + code sits alongside its narrative. +

+
+ +
+
+
+
1

Pick a route: hosted MCP, CLI, or Python

+

Hosted MCP server

+

The quickest route is the hosted MCP server, with five tools — calculate_household, household_reform_impact, population_reform_impact, list_reform_parameters, and score_reform with model="microsim". The UK microdata credential is provisioned server-side.

+

CLI and Python

+

The pe-macro CLI mirrors the same tools; the Python package underneath is what the rest of this page walks through.

+
+ Every number from this model is a static score. + Prices, wages and output do not move. For second-round effects on + GDP, route the same reform through the OBR + emulator: score_reform with model="obr" + takes this package's static costing as the direct effect and returns + the economy-wide feedback. +
+ +
+ +
+
2

Install

+

From PyPI

+

The base install is shared tooling only; add [models] for the pinned UK + US country models, or a single country extra.

+ +
+ +
+
3

Calculate a household

+

Describe, pick a year, read

+

Describe the people, pick a year, read the results — no data access or credentials needed. Pass a reform dict to apply parameter changes.

+

Household-level reform impact

+

+ differencing runs with and without reform gives the + household-level impact: for the UK earner opposite, a 25% basic rate + ({"gov.hmrc.income_tax.rates.uk[0].rate": 0.25}) adds + £1,872 of income tax and cuts net income by the + same amount. +

+ +
+ +
+
4

Score a whole population

+

Representative datasets

+

The same package runs whole representative datasets — the enhanced UK Family Resources Survey, the US CPS — aggregating any variable by decile, entity or demographic.

+
+ UK population microdata is gated — set + HUGGING_FACE_TOKEN from an account with access to + policyengine/policyengine-uk-data before running UK + population examples locally. The hosted MCP server provisions this + credential server-side. +
+ +
+
+ +
+
+ +
+
+ what it can answer +

From one family to the whole distribution.

+
+
+
+ + + + + + + + + + + + + + + + +
Common microsimulation questions and how to answer them
questionhow
What does this reform do to this family?calculate_household / household_reform_impact — every tax, benefit and income variable at person, benefit-unit/tax-unit and household level. On the hosted MCP server and the pe-macro CLI.
What does it cost, and who wins?population_reform_impact and pe.uk.economic_impact_analysis — budgetary impact, decile changes, winners and losers, poverty, inequality, regional impacts. Hosted, enhanced-FRS credential provisioned server-side.
What levers exist?list_reform_parameters — the parameter tree you can write reforms against.
+
+ It cannot tell you what a reform does to GDP, + interest rates, or the fiscal forecast. It is static and + partial-equilibrium by design: behavioural responses are optional + and post-hoc (e.g. labour-supply elasticities), with no general + equilibrium. That second act is what the macro members add — + short-run and long-run. +
+
+
+ +
+ + + + + + + + diff --git a/pe/index.html b/pe/index.html index bfe413f..fada266 100644 --- a/pe/index.html +++ b/pe/index.html @@ -19,7 +19,7 @@ - + + + + + + + + + + +
+
+
+

model 06 — tax-benefit microsimulation · pe-microsim · UK · US · hosted

+

+ Calculate taxes and benefits. +

+

+ Compare current law with a reform for a UK or US household, or estimate + population-wide revenue and distributional effects. +

+
+
+ + + +
+
+ methodology +

Rules, entities, and a dependency graph.

+
+
+

+ Four steps, from statute encoded as a variable graph to the survey + microdata population runs rest on. +

+
+ +
+
+
+
1

Statute as a dependency graph

+
+ + + Request a variable, resolve the graph beneath it + + dated parameters + allowances, tapers, rates + + household inputs + the ones you type + + + + variables + formula + dated parameters, + attached to entities + + + net income + statutory chain + entities: UK 3 (person, benefit unit, household) · US 6 + +
click to expand
+
+

Variables and parameters

+

Every quantity — an allowance, a taper, a means test — is a variable with a formula and dated parameters. Asking for one variable resolves the graph beneath it, so a net-income figure comes from the statutory chain, not an approximating schedule.

+

Entities

+

Variables attach to entities, which differ by country — UK: three (person, benefit unit, household); US: six (person, tax unit, SPM unit, family, marital unit, household) — because US programmes are assessed on differently drawn units.

+ +
+ +
+
2

Reforms and reproducibility

+
+ + + A reform re-dates parameters and re-resolves the same graph + + reform dict + {parameter path + → new value} + + + re-dated parameter + same graph, new values + + + re-resolved graph + household + population APIs + + Version-pinned release + country-model + dataset versions pinned; estimates trace to the bundle + +
click to expand
+
+

One reform format

+

A reform re-dates a parameter and re-resolves the same graph, so the household and population APIs take reforms in the identical format — a flat dict of parameter path → new value.

+

Version-pinned releases

+

Each release pins the country-model and dataset versions it certifies, so a published estimate traces to the exact bundle that produced it.

+ +
+ +
+
3

Survey microdata, enhanced

+
+ + + Building the enhanced UK dataset + + FRS survey + household microdata + + administrative totals + aggregate targets + + + + impute + reweight + under-reported incomes, + weights to match totals + + + enhanced FRS + enhanced_frs_2023_24 + US population runs use the Current Population Survey (CPS) + +
click to expand
+
+

Two modes, two data needs

+

Household calculations need no data — the inputs are the ones you type. Population results run the same rule graph over a representative survey: for the UK the enhanced Family Resources Survey (currently the enhanced_frs_2023_24 vintage), for the US the Current Population Survey.

+

Imputation and reweighting

+

The UK enhancement imputes under-reported incomes and reweights the survey so aggregates better match administrative totals; the resulting microdata is distributed via a gated Hugging Face repository (policyengine/policyengine-uk-data).

+ +
+ +
+
4

What rests on the data, and who consumes it

+
+ + + Where the microsim feeds the rest of the suite + + PE microsim + rule graph + enhanced microdata + + microdata + + static costing + + OLG model + estimates its tax functions from this microdata + + OBR emulator + takes static costings as the direct effect + +
click to expand
+
+

Inherited error

+

Population estimates rest on survey data, so they inherit sampling error and imputation and ageing assumptions in a way a household calculation does not — consequences set out on the validation page.

+

Feeding the suite

+

This same microdata is what the OLG model consumes to estimate its tax functions, and the static costings it produces are what the OBR emulator takes as the direct effect.

+ +
+
+ +
+
+ + + + +
+ + + + + + + + diff --git a/pe/validation/index.html b/pe/validation/index.html new file mode 100644 index 0000000..4e311b9 --- /dev/null +++ b/pe/validation/index.html @@ -0,0 +1,186 @@ + + + + + +PolicyEngine microsim validation — PolicyEngine Macro + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+

model 06 — tax-benefit microsimulation · pe-microsim · UK · US · hosted

+

+ Calculate taxes and benefits. +

+

+ Compare current law with a reform for a UK or US household, or estimate + population-wide revenue and distributional effects. +

+
+
+ + + +
+
+ how far to trust it +

Checked rule by rule, not against a forecast.

+
+
+

+ There is no forecast error to report, because the model is not + forecasting — it applies published rules, checkable by hand. For + the £50,000 UK earner on the code page: + £50,000 − £12,570 personal allowance = £37,430 of basic-rate income; + at 20% that is £7,486 of income tax, at 8% + £2,994 of National Insurance, leaving + £39,520 net. Raising the basic rate 5pp adds 5% of + £37,430 = £1,872. Every figure in the code sample + is deterministic arithmetic from the implemented rules — which + verifies those rules, not that every area of legislation has + complete test coverage. +

+

+ What is not exact is anything resting on survey microdata — + population aggregates inherit sampling error, imputation and ageing + assumptions from the enhanced FRS and the CPS, so a headline + budgetary cost is an estimate in a way a household calculation is + not. For the UK 1p basic-rate case, the + OBR emulator working paper compares + static costings against official sources: £6.46bn in 2026 rising to + £7.38bn by 2030, inside the range of HMRC's June 2025 ready reckoner + (£6.9bn rising to ~£8.2bn), toward its lower end. Suite-wide + evidence: validation. +

+
+
+
+ household calculation + deterministic + implemented rules applied, not estimated +
+
+ countries + 2 + UK · US tax-benefit systems +
+
+ UK / US entities + 3/ 6 + benefit unit vs tax unit, SPM unit, family, marital unit +
+
+
+ +
+
+ limits +

Scope and vintage.

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Known limits of the tax-benefit microsimulation
limitdetail
Static, no general equilibriumReforms are modelled statically, with optional post-hoc behavioural responses; prices, wages and output do not move.
UK data vintageUK population runs use enhanced_frs_2023_24. Upstream now publishes an FRS 2024-25 vintage, which this integration has not adopted.
Survey-based aggregatesPopulation estimates inherit sampling, imputation and ageing error. HMRC's costings use administrative Survey of Personal Incomes data and embed taxable-income elasticities, so differences of a few hundred million on a broad-based rate change are expected.
Gated UK microdataA HUGGING_FACE_TOKEN with access is required locally. Household calculations are unaffected.
Statute-vintage divergence with OLGThis package tracks the latest release; the OLG member is pinned to policyengine-uk==2.88.0, so the two can disagree on baseline statute — a declared inconsistency.
Two countriesUK and US only.
+

+ no standalone working paper for this member yet — its behaviour is + documented in the country model repositories and, where it feeds the + macro members, in the OBR and + OLG papers. +

+
+
+ +
+ + + + + + + diff --git a/reports/us-hank-open-source.html b/reports/us-hank-open-source.html index 735538e..f397c1a 100644 --- a/reports/us-hank-open-source.html +++ b/reports/us-hank-open-source.html @@ -44,16 +44,23 @@ a { color:#155f67; text-decoration:none; border-bottom:1px solid #155f6750; } footer { display:flex; justify-content:space-between; gap:16px; margin-top:8px; padding-top:6px; border-top:1px solid var(--line); color:var(--muted); font-size:7.8px; } .back-link { display:block; width:210mm; max-width:100%; margin:14px auto -8px; font:700 10px/1.2 ui-monospace, monospace; letter-spacing:.08em; text-transform:uppercase; color:var(--teal); border-bottom:0; } + .model-tabs { width:210mm; max-width:100%; margin:16px auto -8px; } + .model-tabs-row { display:flex; flex-wrap:wrap; gap:2px; } + .model-tabs-link { font:700 10px/1.2 ui-monospace, monospace; letter-spacing:.08em; text-transform:uppercase; color:var(--muted); border-bottom:0; padding:5px 10px; border:1px solid var(--line); background:#fff; } + .model-tabs-link:hover { color:var(--teal); } + .model-tabs-link[aria-current="page"] { color:#fff; background:var(--teal); border-color:var(--teal); } @page { size:A4 portrait; margin:0; } @media print { html, body { background:#fff; } .back-link { display:none; } + .model-tabs { display:none; } .sheet { width:210mm; height:297mm; min-height:0; margin:0; padding:12mm 12mm 9mm; box-shadow:none; overflow:hidden; } a { color:inherit; border:0; } } @media (max-width:820px) { .sheet { width:100%; min-height:100vh; margin:0; padding:24px 18px; box-shadow:none; } .back-link { width:auto; margin:14px 18px 0; } + .model-tabs { width:auto; margin:12px 18px -8px; } header { grid-template-columns:1fr; } .stamp { text-align:left; } .table-wrap { overflow-x:auto; } @@ -64,6 +71,15 @@ ← PolicyEngine Macro +
diff --git a/reveal.js b/reveal.js index 6329987..0134738 100644 --- a/reveal.js +++ b/reveal.js @@ -25,8 +25,29 @@ targets.forEach(function (el) { if (el.getBoundingClientRect().top < window.innerHeight * 0.92) { el.classList.add("in-view", "reveal-initial"); + } else if (el.offsetHeight > window.innerHeight * 0.7) { + // Tall blocks (long walkthroughs, scrollytelling wrappers) may never + // reach the 8% intersection threshold — show them without animation. + el.classList.add("in-view", "reveal-initial"); } else { io.observe(el); } }); })(); + +// Keep sticky subnavs flush under the fixed header: expose the header's +// real height as --nav-h so sticky offsets never drift from it. +(function () { + "use strict"; + var nav = document.querySelector("header.nav"); + if (!nav) return; + function set() { + document.documentElement.style.setProperty( + "--nav-h", nav.getBoundingClientRect().height + "px" + ); + } + window.addEventListener("resize", set, { passive: true }); + window.addEventListener("load", set); + if (document.fonts && document.fonts.ready) document.fonts.ready.then(set); + set(); +})(); diff --git a/score/index.html b/score/index.html index 6d5d4ae..e3580c3 100644 --- a/score/index.html +++ b/score/index.html @@ -18,7 +18,7 @@ - + @@ -31,7 +31,7 @@ Home Economy Models - Track record + Forecasts Use @@ -141,7 +141,7 @@

Read the caveats before the headline.

horizon, provenance, per-quantity units and time basis, assumptions, caveats, and a comparability label. Cross-class results are often complementary rather than like-for-like and must not be averaged or - ranked. Suite-wide evidence: validation. + ranked. Suite-wide evidence: validation.

@@ -180,13 +180,10 @@

One reform vocabulary, three surfaces.

- + diff --git a/site_contract.py b/site_contract.py index 1888d06..aff69db 100644 --- a/site_contract.py +++ b/site_contract.py @@ -18,7 +18,6 @@ MODEL_INVENTORY_PAGES = ( "index.html", "models/index.html", - "validation/index.html", ) @@ -34,18 +33,12 @@ def check_public_model_inventory() -> None: if missing: failures.append(f"{path}: missing {', '.join(missing)}") - papers = _read("papers/index.html") - if "evidence for all six models" not in papers: - failures.append("papers/index.html: evidence-guide count is not six") - missing_papers = [model for model in PUBLIC_MODELS if model not in papers] - if missing_papers: - failures.append( - f"papers/index.html: missing {', '.join(missing_papers)}" - ) - validation = _read("validation/index.html") + # /validation was absorbed into /models#validation; the gradient claims + # now live on the models hub page. + validation = _read("models/index.html") if "The six models support" not in validation: - failures.append("validation/index.html: model count is not six") + failures.append("models/index.html: model count is not six") if failures: raise SystemExit("\n".join(failures)) @@ -109,8 +102,8 @@ def check_economy_navigation() -> None: failures.append(f"{path}: Evidence remains a global navigation tab") if 'href="/contact"' in header: failures.append(f"{path}: Contact remains a global navigation tab") - if ">Track record" not in header: - failures.append(f"{path}: Forecasts is not labelled Track record") + if ">Forecasts" not in header: + failures.append(f"{path}: the forecasts tab is not labelled Forecasts") if failures: raise SystemExit("\n".join(failures)) @@ -120,8 +113,7 @@ def check_editorial_consistency() -> None: failures: list[str] = [] stale_claims = { "notes/2026-07-25-cpi-2026q2/index.html": "1992Q1–2023Q2", - "papers/index.html": "against its November 2025 forecast", - "validation/index.html": "This audit covers the three macro models", + "models/index.html": "This audit covers the three macro models", "forecasts/index.html": "a forecast reported without one is marketing", } for path, stale in stale_claims.items(): @@ -130,10 +122,6 @@ def check_editorial_consistency() -> None: home = _read("index.html") for proof in ( - "No account needed", - "Point-in-time data", - "Scored in public", - "Failures published", "Run a hosted model", ): if proof not in home: diff --git a/site_nav.py b/site_nav.py index 29ec911..220f2fc 100644 --- a/site_nav.py +++ b/site_nav.py @@ -21,12 +21,12 @@ ("home", "/", "Home", "nav-mobile"), ("economy", "/economy", "Economy", "nav-mobile"), ("models", "/models", "Models", "nav-mobile"), - ("forecasts", "/forecasts", "Track record", "nav-mobile"), + ("forecasts", "/forecasts", "Forecasts", "nav-mobile"), ("use", "/connect", "Use", "nav-mobile nav-start"), ) MODEL_ROOTS = {"models", "obr", "svar", "frb-us", "us-hank", "olg", "pe"} -EVIDENCE_ROOTS = {"validation", "papers"} +EVIDENCE_ROOTS = {"papers"} # Display names used in the pathway line and the footer directory. PAGE_NAMES = { @@ -40,15 +40,30 @@ "/frb-us": "FRB-US", "/us-hank": "US HANK", "/olg": "OG-UK", + "/obr/code": "Code", + "/obr/methodology": "Methodology", + "/obr/validation": "Validation", + "/svar/code": "Code", + "/svar/methodology": "Methodology", + "/svar/validation": "Validation", + "/frb-us/code": "Code", + "/frb-us/methodology": "Methodology", + "/frb-us/validation": "Validation", + "/us-hank/code": "Code", + "/us-hank/methodology": "Methodology", + "/us-hank/validation": "Validation", + "/pe/code": "Code", + "/pe/methodology": "Methodology", + "/pe/validation": "Validation", + "/olg/code": "Code", + "/olg/methodology": "Methodology", + "/olg/validation": "Validation", "/pe": "PolicyEngine microsim", - "/validation": "Validation", - "/papers": "Papers", "/papers/obr-macro": "obr-macro", "/papers/boe-svar": "boe-svar", "/papers/frb-us": "frb-us", "/papers/psl-og": "psl-og", - "/forecasts": "Track record", - "/notes": "Notes", + "/forecasts": "Forecasts", "/notes/releases": "Releases", "/connect": "Use", "/score": "Score a reform", @@ -64,9 +79,15 @@ "/us-hank": "/models", "/olg": "/models", "/pe": "/models", - "/validation": "/models", - "/papers": "/models", + "/papers/obr-macro": "/models", + "/papers/boe-svar": "/models", + "/papers/frb-us": "/models", + "/papers/psl-og": "/models", "/score": "/connect", + # /notes was folded into /forecasts#notes; the surviving note pages hang + # off Forecasts so no crumb links to the retired /notes page. + "/notes/releases": "/forecasts", + "/notes/2026-07-25-cpi-2026q2": "/forecasts", } @@ -89,6 +110,10 @@ def crumb_chain(url: str) -> list[str]: current = CRUMB_PARENTS[current] else: current = current.rsplit("/", 1)[0] or "/" + if current == "/notes": + # /notes retired into /forecasts#notes: never emit a crumb + # link to the deleted index page. + current = "/forecasts" if current == "/": break return chain @@ -128,9 +153,6 @@ def crumbs(path: Path) -> str: GITHUB = """ """ FOOTER_LINKS = """ PolicyEngine - Validation - Papers - Notes GitHub Contact""" diff --git a/sitemap.xml b/sitemap.xml index a4121c2..54f5825 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -8,10 +8,7 @@ https://policyengine-macro.vercel.app/1.0 https://policyengine-macro.vercel.app/models0.9 https://policyengine-macro.vercel.app/score0.9 - https://policyengine-macro.vercel.app/papers0.9 - https://policyengine-macro.vercel.app/validation0.9 https://policyengine-macro.vercel.app/forecasts0.9 - https://policyengine-macro.vercel.app/notes0.6 https://policyengine-macro.vercel.app/economy0.9 https://policyengine-macro.vercel.app/economy/trends0.8 https://policyengine-macro.vercel.app/economy/us0.9 @@ -28,11 +25,29 @@ https://policyengine-macro.vercel.app/connect0.8 https://policyengine-macro.vercel.app/contact0.8 https://policyengine-macro.vercel.app/obr0.8 + https://policyengine-macro.vercel.app/obr/code0.8 + https://policyengine-macro.vercel.app/obr/methodology0.8 + https://policyengine-macro.vercel.app/obr/validation0.8 https://policyengine-macro.vercel.app/svar0.8 + https://policyengine-macro.vercel.app/svar/code0.8 + https://policyengine-macro.vercel.app/svar/methodology0.8 + https://policyengine-macro.vercel.app/svar/validation0.8 https://policyengine-macro.vercel.app/olg0.8 + https://policyengine-macro.vercel.app/olg/code0.8 + https://policyengine-macro.vercel.app/olg/methodology0.8 + https://policyengine-macro.vercel.app/olg/validation0.8 https://policyengine-macro.vercel.app/pe0.8 + https://policyengine-macro.vercel.app/pe/code0.8 + https://policyengine-macro.vercel.app/pe/methodology0.8 + https://policyengine-macro.vercel.app/pe/validation0.8 https://policyengine-macro.vercel.app/frb-us0.8 + https://policyengine-macro.vercel.app/frb-us/code0.8 + https://policyengine-macro.vercel.app/frb-us/methodology0.8 + https://policyengine-macro.vercel.app/frb-us/validation0.8 https://policyengine-macro.vercel.app/us-hank0.8 + https://policyengine-macro.vercel.app/us-hank/code0.8 + https://policyengine-macro.vercel.app/us-hank/methodology0.8 + https://policyengine-macro.vercel.app/us-hank/validation0.8 https://policyengine-macro.vercel.app/papers/obr-macro0.6 https://policyengine-macro.vercel.app/papers/boe-svar0.6 https://policyengine-macro.vercel.app/papers/psl-og0.6 diff --git a/style.css b/style.css index 2de9796..45c5ea8 100644 --- a/style.css +++ b/style.css @@ -49,7 +49,7 @@ --display: var(--font-sans, "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif); --body: var(--font-sans, "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif); --mono: var(--font-mono, "JetBrains Mono", "Fira Code", Consolas, monospace); - --max: 1140px; + --max: 1320px; /* ---------- type scale (single source of truth for font sizes) ---------- */ /* Steps are ~1.3x apart so h1 > h2 > h3 > body reads as four distinct @@ -365,7 +365,7 @@ h1, h2, h3, p, figcaption, a { overflow-wrap: anywhere; } underline tabs on the right and scroll horizontally when cramped. */ .economy-subnav { position: sticky; - top: 61px; + top: calc(var(--nav-h, 62px) - 8px); z-index: 8; border-top: 1px solid var(--line-soft); border-bottom: 1px solid var(--line-soft); @@ -376,17 +376,24 @@ h1, h2, h3, p, figcaption, a { overflow-wrap: anywhere; } .economy-subnav-row { display: flex; align-items: stretch; + justify-content: center; gap: var(--s-5); max-width: var(--max); margin: 0 auto; - padding: 0 var(--gutter); + padding: 7px var(--gutter) 0; } .economy-subnav-scope { display: flex; align-items: center; - gap: var(--s-3); + gap: var(--s-5); flex: 0 0 auto; - padding: var(--s-3) 0; +} +.economy-subnav-scope::after { + content: ""; + width: 1px; + align-self: stretch; + margin: 10px 0; + background: var(--line-soft); } .economy-subnav-label { color: var(--paper-faint); @@ -394,41 +401,9 @@ h1, h2, h3, p, figcaption, a { overflow-wrap: anywhere; } text-transform: uppercase; letter-spacing: var(--ls-label); } -.economy-subnav-countries { - display: inline-flex; - border: 1px solid var(--line-soft); - border-radius: 999px; - overflow: hidden; -} -.economy-subnav-countries a { - display: inline-flex; - align-items: baseline; - gap: 6px; - padding: 5px 14px; - color: var(--paper-dim); - font-family: var(--mono); - font-size: var(--fs-small); - text-decoration: none; - white-space: nowrap; -} -.economy-subnav-countries a + a { border-left: 1px solid var(--line-soft); } -.economy-subnav-countries a small { - font-size: var(--fs-label); - color: var(--paper-faint); -} -.economy-subnav-countries a:hover { - color: var(--brand-strong); - background: var(--brand-wash); -} -.economy-subnav-countries a[aria-current="page"] { - color: var(--ink); - background: var(--brand-strong); -} -.economy-subnav-countries a[aria-current="page"] small { color: var(--ink); } .economy-topics { display: flex; gap: var(--s-5); - margin-left: auto; overflow-x: auto; scrollbar-width: none; } @@ -511,6 +486,161 @@ h1, h2, h3, p, figcaption, a { overflow-wrap: anywhere; } .economy-page section[id] { scroll-margin-top: 132px; } +/* Per-model page tabs (olg pilot): the .glance-switch capsule inside a + sticky bar in the .economy-subnav idiom. Panels are plain sections with + role=tabpanel toggled by a tiny inline script. */ +.model-tabs { + position: sticky; + top: calc(var(--nav-h, 62px) - 8px); + z-index: 8; + border-top: 1px solid var(--line-soft); + border-bottom: 1px solid var(--line-soft); + background: color-mix(in srgb, var(--ink) 94%, transparent); + backdrop-filter: blur(12px); +} +.model-tabs-row { + display: flex; + align-items: center; + justify-content: center; + gap: var(--s-5); + max-width: var(--max); + margin: 0 auto; + padding: 7px var(--gutter) 0; +} +.model-tabs-link { + flex: 0 0 auto; + padding: 10px 2px; + border-bottom: 2px solid transparent; + color: var(--paper-dim); + font-family: var(--mono); + font-size: var(--fs-small); + text-decoration: none; + white-space: nowrap; +} +.model-tabs-link:hover { color: var(--brand-strong); } +.model-tabs-link[aria-current="location"], +.model-tabs-link[aria-current="page"] { + color: var(--paper); + border-bottom-color: var(--brand-strong); +} +.model-tabs-set { + display: inline-flex; + gap: var(--s-5); +} +.model-tabs-set button { + flex: 0 0 auto; + padding: 9px 2px; + border: 0; + border-bottom: 2px solid transparent; + background: none; + color: var(--paper-dim); + font-family: var(--mono); + font-size: var(--fs-label); + white-space: nowrap; + cursor: pointer; +} +.model-tabs-set button:hover { color: var(--brand-strong); } +.model-tabs-set button.is-current { + color: var(--paper); + border-bottom-color: var(--brand-strong); +} +.model-tabs-spacer { flex: 1 1 auto; } + +/* Methodology scrollytelling: narrative steps left, a sticky formal- + structure panel right that swaps content with the step in view. */ +.scrolly-wrap { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(340px, 460px); + gap: var(--s-7); + align-items: start; + max-width: min(1400px, 100%); + margin: var(--s-7) auto 0; + padding: 0 var(--gutter); +} +.scrolly-steps { min-width: 0; } +.meth-step { + min-height: 90vh; + padding: var(--s-6) 0 var(--s-6) var(--s-5); + border-left: 2px solid var(--line-soft); + opacity: .28; + transition: opacity .5s ease, border-color .5s ease; +} +.meth-step.active { opacity: 1; border-left-color: var(--brand-strong); } +.meth-step:first-child { padding-top: 0; } +.meth-step:last-child { min-height: auto; padding-bottom: 100px; } +.meth-step-head { + display: flex; + align-items: baseline; + gap: var(--s-3); + margin-bottom: var(--s-3); +} +.meth-step-num { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + width: 32px; + height: 32px; + border-radius: 50%; + background: var(--brand-strong); + color: var(--ink); + font-size: var(--fs-small); + font-weight: 700; +} +.meth-step-head h3 { margin: 0; } +.scrolly-sticky { + position: sticky; + top: calc(var(--nav-h, 62px) + 56px); + min-width: 0; +} +.meth-panel-head { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: var(--s-3); + margin-bottom: var(--s-3); + padding-bottom: var(--s-3); + border-bottom: 1px solid var(--line-soft); +} +.meth-panel-title { font-weight: 600; } +.meth-panel-badge { color: var(--paper-faint); font-size: var(--fs-label); } +.meth-inline { display: none; } +@media (max-width: 899px) { + .scrolly-wrap { grid-template-columns: 1fr; } + .scrolly-sticky { display: none; } + .meth-inline { display: block; } + .meth-step { opacity: 1; } +} +@media (max-width: 720px) { + .model-tabs-row { gap: var(--s-3); overflow-x: auto; scrollbar-width: none; } + .model-tabs-row::-webkit-scrollbar { display: none; } + .model-tabs-spacer { display: none; } +} +[role="tabpanel"][id] { scroll-margin-top: 132px; } + +/* Methodology-tab formal panels: labelled equations inside a callout. */ +.prose h4 { + margin: var(--s-5) 0 var(--s-3); + font-size: var(--fs-small); + text-transform: uppercase; + letter-spacing: var(--ls-label); + color: var(--paper-dim); +} +.formal-label { + font-size: var(--fs-label); + text-transform: uppercase; + letter-spacing: var(--ls-label); + color: var(--paper-faint); +} +.formal-eq-label { + display: inline-block; + min-width: 12ch; + margin-right: var(--s-3); + font-size: var(--fs-label); + color: var(--paper-faint); +} +.callout.formal { overflow-x: auto; } + .home-proof { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); @@ -635,7 +765,7 @@ h1 em { font-size: var(--fs-lede); line-height: 1.55; color: var(--paper); - max-width: 56ch; + max-width: 78ch; margin-bottom: var(--s-6); } .lede strong { color: var(--brand-strong); font-weight: 600; } @@ -683,8 +813,9 @@ h1 em { position: relative; } .band + .band { border-top: 1px solid var(--line-soft); } +.band-wide { max-width: min(1400px, 100%); } -.band-head { margin-bottom: var(--s-6); max-width: 60ch; } +.band-head { margin-bottom: var(--s-6); } .kicker { display: block; font-size: var(--fs-small); @@ -702,7 +833,7 @@ h2 { max-width: 26ch; } -.prose { max-width: 68ch; } +.prose { max-width: none; } /* Long unbreakable tokens — file paths, package names, URLs printed inline in the prose — are wider than a 375px column at this body size and pushed the page 2–3px sideways on /validation/. Break them instead of shrinking the @@ -914,12 +1045,12 @@ h2 { .doc .prose > .research-link, .doc .prose > .soon-hint, .doc .prose > .chart-note, -.doc .prose > .opt-note { max-width: 74ch; } +.doc .prose > .opt-note { max-width: none; } /* consistent body copy across every content page */ .doc .prose p { font-size: var(--fs-body); line-height: 1.7; } .doc .prose ul { padding-left: 22px; } -.doc .prose li { margin: var(--s-2) 0; line-height: 1.7; font-size: var(--fs-body); max-width: 74ch; } +.doc .prose li { margin: var(--s-2) 0; line-height: 1.7; font-size: var(--fs-body); max-width: none; } .doc .prose p code, .doc .prose li code, .doc .prose td code { font-family: var(--mono); font-size: var(--fs-code-inline); color: var(--brand-strong); } @@ -961,7 +1092,7 @@ h2 { /* section headings on content pages read a touch tighter than the band default */ .doc .band-head { margin-bottom: var(--s-6); } -.doc h2 { max-width: 30ch; } +.doc h2 { max-width: none; } /* --- code blocks: one look everywhere (was duplicated inline) --- */ .codeblock { @@ -1016,7 +1147,7 @@ h2 { .callout { border-left: 2px solid var(--brand); padding: var(--s-1) 0 var(--s-1) var(--s-5); margin: var(--s-6) 0; - font-size: var(--fs-body-sm); line-height: 1.6; color: var(--paper-dim); max-width: 80ch; + font-size: var(--fs-body-sm); line-height: 1.6; color: var(--paper-dim); max-width: none; } /* ---------- section divider after hero + grid footer ---------- */ @@ -1506,11 +1637,8 @@ h2 { .prose table, .prose .table-scroll, .prose figure, .prose .codeblock { margin-block: var(--s-6); } .prose caption { text-align: left; - font-family: var(--mono); - font-size: var(--fs-label); - letter-spacing: var(--ls-label); - text-transform: uppercase; - color: var(--paper-faint); + font-size: var(--fs-small); + color: var(--paper-dim); padding-bottom: var(--s-3); } @@ -1863,7 +1991,7 @@ h2 { /* one callout treatment (was: left-rule callout + tinted note + bordered box) */ .callout, .opt-note, .chart-note, .soon-hint, .caveat { - max-width: 74ch; + max-width: none; } .vlinks { display: flex; flex-wrap: wrap; gap: 8px 26px; margin-top: 26px; font-size: var(--fs-small); } @@ -1955,7 +2083,8 @@ h2 { gap: 20px; flex-wrap: wrap; margin-bottom: 18px; } .paper-viewer-head > * { min-width: 0; max-width: 100%; } -.paper-viewer-head h1 { +.paper-viewer-head h1, +.paper-viewer-head .paper-title { font-family: var(--display); font-weight: 500; font-size: clamp(1.5rem, 3.4vw, 2.1rem); line-height: 1.25; margin: 6px 0 0; max-width: 30ch; color: var(--paper); @@ -1967,7 +2096,8 @@ h2 { display: grid; grid-template-columns: minmax(0, 1fr); } - .paper-viewer-head h1 { width: 100%; max-width: 100%; } + .paper-viewer-head h1, +.paper-viewer-head .paper-title { width: 100%; max-width: 100%; } } .paper-viewer-actions { display: inline-flex; gap: 10px; flex-wrap: wrap; } .paper-viewer .paper-result, @@ -2170,3 +2300,812 @@ h2 { /* Was --brand-strong: the same teal as links, series 1 and .metric-val, so "Weak" read in the site's affirmative accent. --brand-warning is AA-tested. */ .vchart .vc-warn, td.vc-warn, th.vc-warn { color: var(--brand-warning); font-weight: 600; } + +/* Methodology step diagrams: dashboard schematics remapped to site tokens, + click to expand in a modal. */ +.step-diagram { + --accent: var(--brand-strong); + --accent-light: var(--brand-wash); + --bg-diagram: var(--surface); + --pe-color-gray-500: var(--paper-faint); + --text-light: var(--paper-faint); + --text-mid: var(--paper-dim); + position: relative; + margin: 0 0 var(--s-4); + padding: var(--s-4); + border: 1px solid var(--line-soft); + border-radius: 8px; + background: var(--surface); + cursor: zoom-in; +} +.step-diagram rect[fill="#fff"] { fill: var(--surface); } +.step-diagram { + display: flex; + justify-content: center; + align-items: center; +} +.step-diagram svg { display: block; margin: 0 auto; } +.step-diagram .expand-hint { + position: absolute; + right: 10px; + bottom: 8px; + margin: 0; + color: var(--paper-faint); + font-size: var(--fs-label); + opacity: 0; + transition: opacity .15s ease; +} +.step-diagram:hover .expand-hint, +.step-diagram:focus-visible .expand-hint { opacity: 1; } +.step-diagram:hover { border-color: var(--brand); } +.diagram-modal[hidden] { display: none; } +.diagram-modal { + position: fixed; + inset: 0; + z-index: 40; + display: flex; + align-items: center; + justify-content: center; + padding: var(--s-5); + background: color-mix(in srgb, var(--ink) 72%, transparent); + backdrop-filter: blur(6px); +} +.diagram-modal-box { + --accent: var(--brand-strong); + --accent-light: var(--brand-wash); + --bg-diagram: var(--surface); + --pe-color-gray-500: var(--paper-faint); + --text-light: var(--paper-faint); + --text-mid: var(--paper-dim); + max-width: min(940px, 100%); + max-height: 90vh; + overflow: auto; + padding: var(--s-5); + border: 1px solid var(--line-soft); + border-radius: 10px; + background: var(--ink); +} +.diagram-modal-box rect[fill="#fff"] { fill: var(--surface); } +.diagram-modal-head { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: var(--s-4); + margin-bottom: var(--s-4); +} +.diagram-modal-title { font-weight: 600; } +.diagram-modal-close { + border: 1px solid var(--line-soft); + border-radius: 999px; + width: 30px; height: 30px; + background: none; + color: var(--paper-dim); + font-size: 16px; + cursor: pointer; +} +.diagram-modal-close:hover { color: var(--brand-strong); border-color: var(--brand); } +.diagram-modal-body svg { width: 100%; height: auto; } + +/* At a glance: UK and US side by side as cards; the source note sits + under each table rather than crowding the top. */ +.glance-both { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--s-6); + align-items: stretch; +} +.glance-col { + display: flex; + flex-direction: column; + padding: var(--s-5); + border: 1px solid var(--line-soft); + border-radius: 10px; + background: var(--surface); + min-width: 0; +} +.glance-title { + margin: 0 0 var(--s-4); + padding-bottom: var(--s-3); + border-bottom: 1px solid var(--line-soft); + color: var(--paper); + font-size: var(--fs-small); + text-transform: uppercase; + letter-spacing: var(--ls-label); +} +.glance-col table { width: 100%; } +.glance-col caption { + caption-side: bottom; + padding-top: var(--s-4); + text-align: left; + font-size: var(--fs-body-sm); + line-height: 1.55; + color: var(--paper-faint); +} +@media (max-width: 960px) { .glance-both { grid-template-columns: 1fr; } } + +/* ---------- OLG showcase runtime charts (/olg #showcase) ---------- + Appended rules only: toolbar selects, per-chart legends and a panel-title + text class for the SVG charts drawn at runtime from tpi_data.json. */ +.olg-toolbar { + display: flex; flex-wrap: wrap; gap: 10px 14px; align-items: center; + margin: 18px 0 4px; +} +/* label + select share one pill, dashboard style */ +.olg-toolbar label { + display: inline-flex; align-items: center; gap: 10px; + padding: 7px 8px 7px 16px; + border: 1px solid var(--line-soft); + border-radius: 999px; + background: var(--surface); + font-family: var(--mono); font-size: var(--fs-label); + color: var(--paper-faint); text-transform: uppercase; letter-spacing: 0.06em; + transition: border-color .15s ease, box-shadow .15s ease; +} +.olg-toolbar label:hover, .olg-toolbar label:focus-within { + border-color: var(--brand); + box-shadow: 0 2px 10px color-mix(in srgb, var(--paper) 8%, transparent); +} +.olg-toolbar select { + font: inherit; font-family: var(--body); font-size: var(--fs-small); + font-weight: 600; text-transform: none; letter-spacing: normal; + color: var(--paper); + background: transparent; border: 0; border-radius: 999px; + padding: 2px 6px; min-width: 11ch; cursor: pointer; +} +.olg-toolbar select:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; } +.olg-chart svg { width: 100%; height: auto; display: block; } +.olg-legend { + display: flex; flex-wrap: wrap; gap: 6px 20px; margin: 8px 0 2px; + font-family: var(--mono); font-size: 0.76rem; color: var(--paper-dim); +} +.olg-legend .li { display: inline-flex; align-items: center; gap: 7px; } +.olg-legend .ln { + display: inline-block; width: 24px; height: 0; + border-top: 2.4px solid var(--paper-dim); +} +.olg-legend .ln.dashed { border-top-style: dashed; } +.olg-legend .ln.dotted { border-top-style: dotted; border-top-width: 2.8px; } +.olg-legend .ln.grey { border-top-color: #95a5a6; } +.olg-legend .ln.gold { border-top-color: #b8860b; } +.vchart .vc-title { fill: var(--text); font-size: 12.5px; font-weight: 600; } + +/* ============================================================ + papers/psl-og — paper page with the OG-UK subtab bar. + Header offset lives on .model-tabs-first below; these rules + cover the page's callouts only. Scoped to that page. + ============================================================ */ +.paper-viewer-tabbed .callout strong { color: var(--brand); font-weight: 600; } +.cite-note { margin-top: var(--s-4); } + +/* Subtab bar as the first element of
(paper pages): clear the fixed + header instead of sliding under it. */ +.model-tabs-first { margin-top: calc(var(--nav-h, 62px) + 34px); } +.model-tabs-first + .paper-viewer { padding-top: clamp(28px, 5vh, 48px); } + +/* Methodology freeze-box, structured like the dashboard: bordered card, + header strip with a filled step badge, scrollable body; inside it, + uppercase section titles with a trailing rule, one white sub-card per + labelled equation, and arrow output-lines. */ +.meth-panel, .meth-inline { + border: 2px solid var(--brand-strong); + border-radius: 14px; + overflow: hidden; + background: var(--surface); +} +.meth-inline { margin-top: var(--s-4); } +.meth-panel .meth-panel-head, .meth-inline .meth-panel-head { + margin: 0; + padding: 12px 18px; + border-bottom: 1px solid var(--line-soft); + background: color-mix(in srgb, var(--brand-wash) 60%, transparent); +} +.meth-panel-body-pad { padding: 16px 18px 18px; } +.meth-panel > :not(.meth-panel-head), .meth-inline > :not(.meth-panel-head) { + margin-left: 18px; + margin-right: 18px; +} +.meth-panel > :last-child, .meth-inline > :last-child { margin-bottom: 18px; } +.meth-panel-badge { + padding: 4px 11px; + border-radius: 6px; + background: var(--brand-strong); + color: var(--ink); + font-size: var(--fs-label); + font-weight: 600; +} +.meth-sec-title { + display: flex; + align-items: center; + gap: 8px; + margin-top: var(--s-4); + color: var(--brand-strong); + font-family: var(--mono); + font-size: var(--fs-label); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.08em; +} +.meth-sec-title::after { content: ""; flex: 1; height: 1px; background: var(--line-soft); } +.meth-eq { + margin: 8px 0; + padding: 12px 16px; + border: 1px solid var(--line-soft); + border-radius: 8px; + background: var(--ink); + overflow-x: auto; +} +.meth-eq-label { + margin-bottom: 6px; + color: var(--brand-strong); + font-family: var(--mono); + font-size: var(--fs-label); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.08em; +} +.meth-eq-body { text-align: center; } +.meth-out { + margin: 8px 0; + padding: 10px 14px; + border: 1px solid var(--line-soft); + border-radius: 8px; + background: var(--ink); + font-size: var(--fs-small); +} +.meth-out-line { + display: flex; + align-items: flex-start; + gap: 10px; + padding: 3px 6px; + border-radius: 4px; + color: var(--paper-dim); +} +.meth-out-line:hover { background: var(--brand-wash); } +.meth-out-line .icon { flex: 0 0 auto; color: var(--brand-strong); font-weight: 600; } +/* Narrative sub-headings: bold sentence case, not uppercase micro-labels. */ +.meth-step h4 { + margin: var(--s-5) 0 var(--s-2); + font-size: var(--fs-body); + font-weight: 700; + letter-spacing: -0.01em; + text-transform: none; + color: var(--paper); +} + +/* The sticky freeze-box never grows past the viewport: overflowing panel + content scrolls inside the card. */ +.scrolly-sticky .meth-panel { + max-height: calc(100vh - var(--nav-h, 62px) - 110px); + overflow-y: auto; + overscroll-behavior: contain; +} +.scrolly-sticky .meth-panel .meth-panel-head { + position: sticky; + top: 0; + z-index: 1; + background: color-mix(in srgb, var(--brand-wash) 60%, var(--ink)); +} + +/* ============================================================ + .mo- : model OVERVIEW pages (olg/obr/svar/frb-us/us-hank/pe) + Appended polish only — framed figure/table/chart cards follow + the homepage .glance-col idiom (hairline border, 10px radius, + surface background, caption below). + ============================================================ */ + +/* framed card around a standalone figure (wraps OUTSIDE any + generated marker block; never touches the generated markup) */ +.mo-figcard { + margin: var(--s-6) auto; + max-width: 820px; + padding: var(--s-4) var(--s-5) var(--s-5); + border: 1px solid var(--line-soft); + border-radius: 10px; + background: var(--surface); + min-width: 0; +} +.mo-figcard .vfig, +.mo-figcard figure { margin: 0; } +.mo-figcard .vfig figcaption, +.mo-figcard figure figcaption { + margin-top: var(--s-3); + padding-top: var(--s-3); + border-top: 1px solid var(--line-soft); + text-align: left; +} + +/* clean-table card: same frame; the caption reads as a source + note under the table, matching the homepage glance cards */ +.table-scroll.mo-tablecard { + padding: var(--s-4) var(--s-5); + border: 1px solid var(--line-soft); + border-radius: 10px; + background: var(--surface); +} +.mo-tablecard table caption { + caption-side: bottom; + padding-top: var(--s-4); + text-align: left; + font-size: var(--fs-body-sm); + line-height: 1.55; + color: var(--paper-faint); +} +.mo-tablecard table tr:last-child th, +.mo-tablecard table tr:last-child td { border-bottom: 0; } + +/* interactive showcase (/olg): toolbar + chart + legend read as + one card consistent with the figure treatment above */ +.mo-chartcard { + margin: var(--s-6) auto; + max-width: 880px; + padding: var(--s-4) var(--s-5) var(--s-5); + border: 1px solid var(--line-soft); + border-radius: 10px; + background: var(--surface); + min-width: 0; +} +.mo-chartcard .olg-toolbar { + margin: 0 0 var(--s-3); + padding-bottom: var(--s-3); + border-bottom: 1px solid var(--line-soft); +} +.mo-chartcard .olg-legend { + margin: var(--s-3) 0 0; + padding-top: var(--s-3); + border-top: 1px solid var(--line-soft); +} + +/* fact lines: enumerable results pulled out of a paragraph wall, + in the .meth-out-line idiom (icon + line, hover wash) */ +.mo-facts { + margin: var(--s-5) 0; + padding: var(--s-3) var(--s-4); + border: 1px solid var(--line-soft); + border-radius: 10px; + background: var(--surface); + font-size: var(--fs-body-sm); + line-height: 1.6; +} +.mo-fact { + display: flex; + align-items: flex-start; + gap: 10px; + margin: 0; + padding: 7px 8px; + border-radius: 4px; + color: var(--paper-dim); + max-width: none; +} +.mo-fact + .mo-fact { border-top: 1px solid var(--line-soft); } +.mo-fact:hover { background: var(--brand-wash); } +.mo-fact .icon { flex: 0 0 auto; color: var(--brand-strong); font-weight: 600; } + +/* section rhythm: the opening paragraph of a band reads a touch + larger, so each section starts with a visible beat */ +.prose .mo-lead { + font-size: 1.06em; + line-height: 1.7; +} + +/* Overview figures stay chart-sized: cap the SVG height inside cards + and center it, rather than stretching to the reading column. */ +.mo-figcard svg, .mo-chartcard svg { + display: block; + margin: 0 auto; + width: 100%; + max-height: 380px; + height: auto; +} + +/* Code-page scrollytelling: steps are code-length, not screen-length — + no 90vh minimum, tight rhythm, run-out only after the last step so + its panel can still activate. */ +.scrolly-code .meth-step { + min-height: auto; + padding-top: 36px; + padding-bottom: 36px; +} +.scrolly-code .meth-step:first-child { padding-top: 0; } +.scrolly-code .meth-step:last-child { min-height: auto; padding-bottom: 45vh; } +.scrolly-code .meth-step > :last-child { margin-bottom: 0; } +.scrolly-code .meth-step h4:first-of-type { margin-top: var(--s-3); } + +/* Inline code in prose reads as a chip. */ +.doc .prose p code, +.doc .prose li code, +.doc .prose td code, +.meth-step code { + padding: 1px 6px; + border: 1px solid var(--line-soft); + border-radius: 4px; + background: var(--surface); + font-size: var(--fs-code-inline); +} +.codeblock code, .code-window code, pre code { border: 0; background: none; padding: 0; } + +/* OLG configuration dial cards: one card per dial, one row per choice + (replaces the old three-column dials table on /olg/code/). */ +.dial-cards { + display: grid; + gap: 10px; + margin: var(--s-3) 0; +} +.dial-card { + padding: 10px 14px 12px; + border: 1px solid var(--line-soft); + border-radius: 10px; + background: var(--surface); + font-size: var(--fs-small); +} +.dial-name { + display: inline-block; + margin-bottom: 6px; + padding: 2px 8px; + border: 1px solid var(--line-soft); + border-radius: 4px; + color: var(--brand-strong); + font-size: var(--fs-label); + font-weight: 600; + letter-spacing: 0.04em; +} +.dial-choice { + display: flex; + align-items: baseline; + gap: 10px; + padding: 5px 6px; + border-radius: 4px; +} +.dial-choice + .dial-choice { border-top: 1px solid var(--line-soft); } +.dial-choice:hover { background: var(--brand-wash); } +.dial-choice > code { flex: 0 0 auto; white-space: nowrap; } +.dial-tradeoff { color: var(--paper-dim); } +@media (max-width: 560px) { + .dial-choice { flex-direction: column; gap: 2px; } +} + +/* ============================================================ + .cmp- : /olg/validation comparison tables (OG-UK vs OBR) + Appended scannability polish on top of .mo-tablecard. + ============================================================ */ +.cmp-compare table th, +.cmp-compare table td { + padding: var(--s-3) var(--s-4); + vertical-align: top; +} +/* row headers: quieter, small mono — the data columns lead */ +.cmp-compare table tbody th[scope="row"] { + font-family: var(--font-mono, ui-monospace, monospace); + font-size: var(--fs-body-sm); + font-weight: 500; + color: var(--paper-faint); +} +/* the label column carries a subtle brand wash */ +.cmp-compare table thead th:first-child, +.cmp-compare table tbody th[scope="row"] { + background: var(--brand-wash); +} + +/* One frame, not three: charts inside cards drop the inner .vchart box, + and the toolbar/legend hairlines go — the card border does the work. */ +.mo-chartcard .vchart, .mo-figcard .vchart, .olg-chart .vchart { border: 0; border-radius: 0; padding: 0; } +.mo-chartcard .olg-toolbar { border-bottom: 0; padding-bottom: 0; } +.mo-chartcard .olg-legend { border-top: 0; padding-top: 4px; } +.olg-legend { font-family: inherit; } + +/* Side-by-side model comparison cards. */ +.vs-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--s-5); + margin: var(--s-4) 0 var(--s-6); +} +.vs-card { + padding: var(--s-4) var(--s-5) var(--s-5); + border: 1px solid var(--line-soft); + border-radius: 10px; + background: var(--surface); + min-width: 0; +} +.vs-card-head { + margin-bottom: var(--s-3); + padding-bottom: var(--s-3); + border-bottom: 1px solid var(--line-soft); + font-weight: 600; +} +.vs-line { + display: flex; + align-items: flex-start; + gap: 10px; + padding: 5px 0; + font-size: var(--fs-small); + color: var(--paper-dim); +} +.vs-line .icon { flex: 0 0 auto; color: var(--brand-strong); font-weight: 600; } +@media (max-width: 820px) { .vs-grid { grid-template-columns: 1fr; } } + +/* Model chooser: questions expand in place. */ +.qa-list { display: grid; gap: var(--s-3); } +.qa { + border: 1px solid var(--line-soft); + border-radius: 10px; + background: var(--surface); + overflow: hidden; +} +.qa summary { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: var(--s-4); + padding: var(--s-4) var(--s-5); + cursor: pointer; + list-style: none; +} +.qa summary::-webkit-details-marker { display: none; } +.qa summary::after { + content: "+"; + flex: 0 0 auto; + color: var(--brand-strong); + font-family: var(--mono); + font-size: 18px; + line-height: 1; +} +.qa[open] summary::after { content: "\2212"; } +.qa summary:hover { background: var(--brand-wash); } +.qa summary span { flex: 1 1 auto; font-weight: 600; font-size: var(--fs-body); } +.qa summary strong { + flex: 0 0 auto; + padding: 4px 12px; + border: 1px solid var(--line-soft); + border-radius: 999px; + color: var(--brand-strong); + background: var(--brand-wash); + font-family: var(--mono); + font-size: var(--fs-label); + font-weight: 500; + white-space: nowrap; +} +.qa-body { padding: 0 var(--s-5) var(--s-4); border-top: 1px solid var(--line-soft); } +.qa-body > p:first-child { margin-top: var(--s-4); color: var(--paper-dim); } +.qa-facts { + margin: var(--s-4) 0; + border: 1px solid var(--line-soft); + border-radius: 8px; + overflow: hidden; +} +.qa-fact { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 10px 14px; + font-size: var(--fs-small); + color: var(--paper-dim); +} +.qa-fact + .qa-fact { border-top: 1px solid var(--line-soft); } +.qa-fact:hover { background: var(--brand-wash); } +.qa-fact .icon { flex: 0 0 auto; color: var(--brand-strong); font-weight: 600; } +.qa-link { margin: var(--s-3) 0 0; } +.qa-link a { + display: inline-block; + padding: 7px 16px; + border: 1px solid var(--brand); + border-radius: 999px; + color: var(--brand-strong); + font-size: var(--fs-small); + font-weight: 600; + text-decoration: none; +} +.qa-link a:hover { background: var(--brand-wash); text-decoration: none; } +.qa-link a:hover { text-decoration: underline; } + +/* ---------- .vmet-: OBR validation metric strip + notes ---------- + Unified stat strip: no per-tile background boxes; four identical + stats separated by hairlines only. The numbers carry the contrast. */ +.vmet-strip { + display: grid; + grid-template-columns: 1fr; + gap: 0; + margin: var(--s-6) 0 var(--s-6); + border-top: 1px solid var(--line-soft); + border-bottom: 1px solid var(--line-soft); +} +@media (min-width: 720px) { .vmet-strip { grid-template-columns: repeat(4, 1fr); } } + +.vmet-stat { + padding: 22px 4px 20px; + border-bottom: 1px solid var(--line-soft); + background: none; +} +.vmet-stat:last-child { border-bottom: none; } +@media (min-width: 720px) { + .vmet-stat { padding: 24px 24px 22px; border-bottom: none; border-left: 1px solid var(--line-soft); } + .vmet-stat:first-child { border-left: none; padding-left: 4px; } +} +.vmet-label { + display: block; + font-size: var(--fs-label); + letter-spacing: var(--ls-label); + text-transform: uppercase; + color: var(--paper-faint); + margin-bottom: 12px; +} +.vmet-val { + font-family: var(--display); + font-size: var(--fs-stat); + font-weight: 500; + color: var(--brand-strong); + line-height: 1; + display: flex; + align-items: baseline; + gap: 10px; +} +.vmet-unit { font-family: var(--mono); font-size: var(--fs-small); color: var(--paper-faint); font-weight: 400; } +.vmet-note { display: block; font-family: var(--mono); font-size: var(--fs-label); color: var(--paper-faint); margin-top: 10px; line-height: 1.5; } + +/* compact definition/caveat block under the configurations table, + in the .meth-out-line idiom: keyed lines, no run-on paragraph */ +.vmet-notes { + max-width: 80ch; + margin: var(--s-4) auto var(--s-5); + font-size: var(--fs-label); + color: var(--paper-faint); +} +.vmet-notes-line { + display: flex; + align-items: flex-start; + gap: 10px; + margin: 0; + padding: 4px 0; + line-height: 1.6; +} +.vmet-notes-key { + flex: 0 0 auto; + min-width: 5.5ch; + color: var(--brand-strong); + text-transform: uppercase; + letter-spacing: var(--ls-label); + font-weight: 600; +} +.vmet-notes-line a { color: var(--paper-dim); } + +/* ---- validation evidence blocks (shared by /validation and the per-model + validation subtabs, which now host the generated charts) ---- */ +.more { border-top: 1px solid var(--line-soft); margin: 26px 0 0; padding-top: 4px; } +.more > summary { + cursor: pointer; list-style: none; padding: 14px 0; + font-family: var(--mono); font-size: var(--fs-small); + color: var(--paper-faint); +} +.more > summary::-webkit-details-marker { display: none; } +.more > summary::before { content: "+ "; } +.more[open] > summary::before { content: "\2212 "; } +.more > summary:hover, .more > summary:focus-visible { color: var(--text, #111); } +.more > :last-child { margin-bottom: 8px; } +/* additional vchart marks used by the evidence charts */ +.vchart .vc-band { fill: color-mix(in srgb, var(--brand) 16%, transparent); stroke: none; } +.vchart .vc-edge { stroke: var(--line); stroke-width: 1; stroke-dasharray: 2 3; } +.vchart .vc-s3 { fill: none; stroke: color-mix(in srgb, var(--paper) 45%, transparent); stroke-width: 2; stroke-linejoin: round; } + +/* /models hub: offset anchor targets below the sticky subtab bar. */ +.models-page section[id] { scroll-margin-top: 132px; } + +.olg-legend .ln-s1 { border-top-color: var(--brand); border-top-style: solid; } +.olg-legend .ln-s2 { border-top-color: var(--chart-s2); border-top-style: dashed; } + +@media (min-width: 900px) { + .evidence-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } +} + +/* Comparison table: collapsed to its first row until expanded. */ +.cmp-clamped tbody tr:nth-child(n+2) { display: none; } +.cmp-toggle { + display: inline-block; + padding: 7px 16px; + border: 1px solid var(--brand); + border-radius: 999px; + background: none; + color: var(--brand-strong); + font-size: var(--fs-small); + font-weight: 600; + cursor: pointer; +} +.cmp-toggle:hover { background: var(--brand-wash); } + +/* ============================================================ + .fc- : /forecasts track-record page polish (appended only). + Wrappers sit OUTSIDE the generated scorecard marker blocks and + restyle the generated markup without touching it. + ============================================================ */ + +/* Stat tiles -> unified .vmet-strip treatment: no per-tile boxes, + hairline separators only; the numbers carry the contrast. */ +.fc-vmet .forecast-summary { + gap: 0; + border: 0; + border-top: 1px solid var(--line-soft); + border-bottom: 1px solid var(--line-soft); + background: none; +} +.fc-vmet .forecast-summary article { + padding: 22px 4px 20px; + background: none; + border-bottom: 1px solid var(--line-soft); +} +.fc-vmet .forecast-summary article:last-child { border-bottom: 0; } +@media (min-width: 720px) { + .fc-vmet .forecast-summary article { + padding: 24px 24px 22px; + border-bottom: 0; + border-left: 1px solid var(--line-soft); + } + .fc-vmet .forecast-summary article:first-child { border-left: 0; padding-left: 4px; } +} +.fc-vmet .forecast-summary strong { color: var(--brand-strong); font-weight: 500; } +.fc-vmet .forecast-summary span { + font-family: var(--mono); + font-size: var(--fs-label); + letter-spacing: var(--ls-label); + text-transform: uppercase; +} +@media (max-width: 719px) { + .fc-vmet .forecast-summary { grid-template-columns: 1fr; } +} + +/* Model-vs-official figures and tables -> the .mo-figcard / + .mo-tablecard card idiom used on the model overview pages. */ +.fc-vs-cards .vchart-figure { + margin: var(--s-6) auto; + max-width: 820px; + padding: var(--s-4) var(--s-5) var(--s-5); + border: 1px solid var(--line-soft); + border-radius: 10px; + background: var(--surface); + min-width: 0; +} +.fc-vs-cards .vchart-figure .vchart { border: 0; border-radius: 0; padding: 0; background: none; } +.fc-vs-cards .vchart-figure figcaption { + margin-top: var(--s-3); + padding-top: var(--s-3); + border-top: 1px solid var(--line-soft); + text-align: left; + font-size: var(--fs-body-sm); + line-height: 1.55; + color: var(--paper-faint); +} +.fc-vs-cards .table-scroll { + padding: var(--s-4) var(--s-5); + border: 1px solid var(--line-soft); + border-radius: 10px; + background: var(--surface); +} +.fc-vs-cards .table-scroll table caption { + caption-side: bottom; + padding-top: var(--s-4); + text-align: left; + font-size: var(--fs-body-sm); + line-height: 1.55; + color: var(--paper-faint); +} +.fc-vs-cards .table-scroll table tr:last-child th, +.fc-vs-cards .table-scroll table tr:last-child td { border-bottom: 0; } +/* tinted label column, matching .cmp-compare */ +.fc-vs-cards .table-scroll table tbody th[scope="row"] { + font-family: var(--font-mono, ui-monospace, monospace); + font-size: var(--fs-body-sm); + font-weight: 500; + color: var(--paper-faint); +} +.fc-vs-cards .table-scroll table thead th:first-child, +.fc-vs-cards .table-scroll table tbody th[scope="row"] { + background: var(--brand-wash); +} + +/* The generated BoE MPR/SEF provenance paragraph (inside the + scorecard-vs-official marker block) is restated in the keyed + bullet block immediately after the block; suppress the + duplicate paragraph visually without touching generated markup. */ + +/* Footnote variant: arrow lines without the box. */ +.meth-out.plain { border: 0; border-radius: 0; background: none; padding: 0; } +.meth-out.plain .meth-out-line:hover { background: none; } + +.glance-range { display: block; margin-top: 2px; color: var(--paper-faint); font-size: var(--fs-label); } diff --git a/svar/code/index.html b/svar/code/index.html new file mode 100644 index 0000000..857add8 --- /dev/null +++ b/svar/code/index.html @@ -0,0 +1,242 @@ + + + + + +BoE SVAR code — PolicyEngine Macro + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+

model 02 — Bank of England structural VAR · boe-svar · UK · hosted

+

+ Explain UK growth and inflation. +

+

+ Decompose UK GDP and inflation into six structural shocks, forecast + with credible bands, and explain revisions between quarters. +

+
+
+ + + +
+
+ how to run it +

Estimate, identify, decompose.

+
+
+

+ Four routes into the model, hosted to local: MCP tools, CLI mirrors, + a pip install, and the development runner scripts. Each step's code + follows its narrative. +

+
+ +
+
+
+
1

Hosted MCP tools

+

No install, no credentials

+

The quickest route is the hosted MCP server. Three tools reach this model: forecast_uk (YoY GDP-growth and CPI-inflation fan charts with 68%/90% bands), latest_shocks (posterior sign probabilities of each identified shock in the latest quarter), and model_summary (headline replication results — instant, no estimation).

+

Where to read the results

+

What each tool tells you is on the overview; the estimation and identification choices behind them are on the methodology page.

+ +
+ +
+
2

CLI mirrors

+

Same tools from the terminal

+

The CLI mirrors the three MCP tools one for one: pe-macro forecast, pe-macro shocks, and pe-macro summary. Same outputs, no MCP client required.

+ +
+ +
+
3

Install locally

+

One pip install

+

The package installs straight from GitHub with the prepared quarterly dataset inside, so boe_var.data.load_data() works immediately.

+

All inputs are public

+

Every input series is public: ONS (CPI, CPI energy, real GDP), Bank of England (Bank Rate, sterling ERI), and FRED (oil, exchange rates, world components).

+ +
+ +
+
4

Development runner scripts

+

Clone to rebuild everything

+

The runner scripts are for development — clone the repository to rebuild the dataset or regenerate the paper outputs.

+

What lands where

+

outputs land in results/ — the replication figures (fig2–fig6), the forecast figures (fig1, fig7–fig9), and two summary reports. runtime scales with draw count; the importance weights are the expensive part — one numerical Jacobian per accepted draw.

+ +
+
+ +
+
+ +
+ + + + + + + + diff --git a/svar/index.html b/svar/index.html index e898391..68ee38f 100644 --- a/svar/index.html +++ b/svar/index.html @@ -19,7 +19,7 @@ - + + + + + + + + + + + + +
+
+
+

model 02 — Bank of England structural VAR · boe-svar · UK · hosted

+

+ Explain UK growth and inflation. +

+

+ Decompose UK GDP and inflation into six structural shocks, forecast + with credible bands, and explain revisions between quarters. +

+
+
+ + + +
+
+ methodology +

Eight variables, six named shocks.

+
+
+

+ Four steps from raw quarterly data to named structural shocks, + following Bank of England Macro Technical Paper No. 3 + (Brignone & Piffer, 2025). Each step's formal elements sit + alongside its narrative. +

+
+ +
+
+
+
1

The data: eight quarterly variables

+
+ + + Eight quarterly variables in two blocks + + global block (3) + world GDP + world CPI (UK-trade-weighted) + real oil price in sterling + + UK block (5) + Bank Rate · sterling exchange-rate index + CPI · CPI energy + real GDP + + + + + 1992Q1 + 2025Q1 + 2026Q1 + estimation sample, levels, 4 lags · Covid dummies 2020Q1–2021Q2 + conditioning only + time → + +
click to expand
+
+

Two blocks

+

Eight quarterly variables in two blocks: global — world GDP and world CPI (both UK-trade-weighted) plus the real oil price in sterling — and UK — Bank Rate, the sterling exchange-rate index, CPI, CPI energy, and real GDP.

+

Sample and specification

+

Estimation covers the inflation-targeting era, 1992Q1–2025Q1, in both the paper replication and the hosted adapter — in levels, with \(p = 4\) lags and exogenous Covid dummies for 2020Q1–2021Q2.

+

Conditioning versus estimation

+

The hosted forecast conditions on observations through 2026Q1 without re-estimating coefficients on them.

+ +
+ +
+
2

Bayesian estimation

+
+ + + Priors + data → a posterior, not point estimates + + priors + Minnesota (normal-inverse-Wishart) + sum-of-coefficients · dummy-initial-obs + + data + eight variables, 1992Q1–2025Q1 + levels, 4 lags + + + + posterior over + (Π, Σ) + + + IRFs + decompositions + fan charts + all with bands + +
click to expand
+
+

The prior

+

Estimation is Bayesian with a Minnesota (normal-inverse-Wishart) prior plus sum-of-coefficients and dummy-initial-observation priors — the framework of Giannone, Lenza & Primiceri (2015).

+

The posterior

+

The output is a posterior over the reduced-form coefficients \(\Pi\) and error covariance \(\Sigma\); every downstream object — impulse responses, decompositions, fan charts — is a posterior distribution, not a point estimate.

+ +
+ +
+
3

Identification: naming the shocks

+
+ + + Restrictions turn residuals into named shocks + + posterior + draws + unnamed residuals + + + zero restrictions + small open economy: UK shocks + cannot move world variables on impact + + sign restrictions + impact signs name the shocks + (Arias et al. 2018) + + + six named shocks + world demand · world energy + world supply · UK demand + UK supply · UK monetary policy + + 2 unidentified (residual volatility) + importance weights correct sampler non-uniformity · Chan–Matthes–Yu (2025) permutation search cuts rejections + +
click to expand
+
+

Zero and sign restrictions

+

Identification follows Arias–Rubio-Ramírez–Waggoner (2018): zero restrictions make the UK a small open economy (UK shocks cannot move world variables on impact), and impact sign restrictions name six structural shocks — world demand, world energy, world supply, UK demand, UK supply, and UK monetary policy. Two unidentified shocks absorb residual volatility.

+

Corrections

+

Importance weights correct the sampler's non-uniformity; the Chan–Matthes–Yu (2025) permutation search cuts rejections.

+ +
+ +
+
4

Sampling: draws and acceptance

+
+ + + Production run: draws narrow at each stage + + 10,000 posterior (Π, Σ) draws + + pass the sign restrictions + + 751 accepted (7.5%) + + importance-weighted + + ESS 355.9 + +
click to expand
+
+

Accept/reject sampling

+

This is accept/reject sampling. The production run drew 10,000 posterior \((\Pi, \Sigma)\) draws, of which 751 passed the sign restrictions (7.5%), for an importance-weighted effective sample size of 355.9 — so medians and bands are correct but noisier than the paper's.

+

The hosted run

+

The hosted forecast conditions on the current vintage — data through 2026Q1 — with a 2,000-draw default (135 accepted, ESS 65.3), while retaining the 2025Q1 coefficient-estimation endpoint. The published current-outlook chart on the overview comes from a larger 5,600-draw run of the same pipeline (385 accepted, ESS 176.3).

+ +
+
+ +
+
+ + + +
+
+ sources +

Where the references live.

+
+
+

+ The source and companion papers, the method papers behind each step + above, and the reference toolboxes are collected in one table on the + overview's sources section. How well the + pipeline reproduces the paper — and where it departs from the Bank's + model — is on the validation page. +

+
+
+ +
+ + + + + + + + diff --git a/svar/validation/index.html b/svar/validation/index.html new file mode 100644 index 0000000..3d1d304 --- /dev/null +++ b/svar/validation/index.html @@ -0,0 +1,741 @@ + + + + + +BoE SVAR validation — PolicyEngine Macro + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+

model 02 — Bank of England structural VAR · boe-svar · UK · hosted

+

+ Explain UK growth and inflation. +

+

+ Decompose UK GDP and inflation into six structural shocks, forecast + with credible bands, and explain revisions between quarters. +

+
+
+ + + +
+
+ how far to trust it +

Replicates the paper; forecast evidence remains limited.

+
+
+

+ Two independent checks: replication against the published paper, and + — rarer — a 2024Q2-frozen forecast study scored against ONS outturns + that arrived since, a genuine out-of-sample test that exists + because the data edge is frozen. +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
This replication against Brignone & Piffer (2025), check by check
checkthis replicationthe paper
Global shocks' share of UK GDP variance (1 year)42.1%~40%
Global shocks' share of UK CPI variance (1 year)49.5%~50%
IRFs, FEVDs, shocks, historical decompositionsFigures 2–6 replicatequalitative match
Out-of-sample forecast error, 2024Q3–2026Q10.32pp RMSE for both GDP growth and CPI inflationno published counterpart
68% band coverage over the same seven quarters14 of 14 outturns inside
Forecast-revision adding-up identityexact per draw (max abs error 5.23e-12)holds by construction
+
+
+
+ out-of-sample RMSE + 0.32pp + GDP growth and CPI, 7 quarters from a frozen edge +
+
+ 68% band coverage + 14/14 + no outturn escaped even the 68% band — though the 49-origin rolling evaluation finds the bands under-cover on average +
+
+ tests + comprehensive + estimation, identification, and analysis coverage +
+
+

+ The honest reading: the model was right that the 2025 inflation hump + would mean-revert and wrong about its peak, by up to 0.6 points — + outturns of 3.5 and 3.8% against medians of 3.2%, inside the 68% band + but on its upper half. It never saw the Ofgem cap increases, food + inflation, or the April 2025 administered price rises. Charts and + source data below; full quarterly + scorecard in the working paper. +

+
+ This is an independent replication built from Bank of England Macro + Technical Paper No. 3 and public data. It is not produced, + maintained, or endorsed by the Bank of England, and its results + should not be presented as Bank of England estimates. +
+
+ +
+
+ evidence — replication +

Against a Bank of England paper, and against outturns.

+
+
+

+ Against Brignone & Piffer (2025), the one-year global-shock + shares of UK forecast-error variance land within about a point of the + paper's benchmarks. +

+
+ +boe-svar: global-shock FEVD shares, ours vs Brignone & Piffer (2025) +Grouped bar chart. Share of UK forecast-error variance attributed to identified global shocks (world demand, energy and supply) at the one-year horizon. For GDP, our 10,000-draw production run gives 42.1% against the paper's 40.0%; for CPI, 49.5% against 50.0%. Both deviations are a percentage point or less. The paper's values are approximate. + +0% + +20% + +40% + +60% + +42.1 +ours + +40.0 +paper +UK GDP, 1-yr horizon + +49.5 +ours + +50.0 +paper +UK CPI, 1-yr horizon + +
Global shocks = world demand + energy + supply. Ours = 10,000-draw production run; paper values approximate. Global-shock FEVD shares at the one-year horizon: the production run against the paper's published values. Source: papers/boe-svar/figures/comparison_numbers.json and the paper's validation table.
+
+
+ Why the fast CI configuration lands 6–10 points off +

+ The deliberately cheap unweighted CI configuration lands at 49.6% and + 43.8% — 6–10 points off, in opposite directions for the two + variables. That gap is itself informative rather than embarrassing: + the importance weights correct the Arias et al. (2018) + zero-restriction sampler towards the uniform-over-rotations + posterior, and applying them moves both shares substantially towards + the published values, though on the 2026Q1 vintage GDP still lands + 2.1 points high. Both configurations sit inside the wide [30, 60]% acceptance + band that CI enforces, which is chosen to catch gross regressions + without being flaky to Monte-Carlo variation. +

+
+ + + + + + + +
boe-svar global-shock FEVD shares against Brignone & Piffer (2025)
Global share, 1-yr FEVDPaperCI config (fast)Production (10k draws)Deviation, CI / production
UK GDP~40%49.6%42.1%+9.6 / +2.1 pp
UK CPI~50%43.8%49.5%−6.2 / −0.5 pp
+
+
+

+ Out-of-sample. Forecasting seven quarters ahead from + a frozen 2024Q2 edge, RMSE (root-mean-square error) is + 0.32pp for both GDP growth and CPI inflation + (what the median got right and wrong is in the + honest reading above). +

+

+ Fourteen of fourteen outturns inside the 68% band is a + calibration failure, not a pass — a correct 68% interval + should contain roughly 9.5 of 14, and the bands are about three times + wider than the errors warrant, so this is evidence of over-dispersion, + not the validation win it was once presented as. But one frozen origin + yields perhaps two or three effectively independent observations, so + neither reading carries much weight; the test to weight is the + empirical coverage across all 49 rolling origins and eight horizons + below. +

+
+ +boe-svar: out-of-sample forecast fan from the frozen 2024Q2 edge vs ONS outturns +Two-panel fan chart. Left panel: year-on-year UK GDP growth; right panel: year-on-year UK CPI inflation. Each shows the posterior median forecast from the frozen 2024Q2 data edge as a line, the 68 per cent credible band as a shaded region over thirteen quarters 2024Q3 to 2027Q3, and ONS outturns for the seven evaluated quarters 2024Q3 to 2026Q1 as dots. All fourteen outturn dots fall inside the 68 per cent band; RMSE 0.32 percentage points for both variables. GDP medians run 1.3, 1.8, 1.3, 0.9, 0.8, 0.9, 0.9 per cent over the evaluated quarters against outturns of 1.0, 1.5, 1.3, 1.4, 1.3, 1.0, 0.9; CPI medians 2.3, 2.7, 2.8, 3.2, 3.2, 3.2, 3.2 against outturns of 2.0, 2.5, 2.8, 3.5, 3.8, 3.4, 3.1. Medians and 68 per cent bands from papers/boe-svar/figures/figure_numbers.json (forecast_table, entries [median, lo68, hi68]); ONS outturns from papers/boe-svar/figures/make_figures.py. Coordinates: GDP panel maps value v to y = 292 - (v + 1) * 59 for the -1 to 3 per cent axis; CPI panel y = 292 - (v - 1) * 59 for the 1 to 5 per cent axis; quarter i of 13 maps to x = 58 + i * 25.667 (GDP) or 416 + i * 25.667 (CPI). +median forecast + 68% band +ONS outturn +frozen 2024Q2 edge at left of each panel + +-1% + +0 + ++1% + ++2% + ++3% + +1% + +2% + +3% + +4% + +5% + + + + + + + + + + + + + + + + + + + + +GDP growth (YoY, %) +CPI inflation (YoY, %) +24Q3 +26Q1 +27Q3 +24Q3 +26Q1 +27Q3 + +
14/14 inside the 68% band — see the over-dispersion caveat above. Frozen 2024Q2 data edge; source: papers/boe-svar/figures/figure_numbers.json.
+
+

+ Rolling-origin audit. A stricter expanding-window test + evaluates 49 historical origins without future-data leakage; the full + benchmark treatment is in the + forecast-accuracy section below. The short + version: Bank Rate is the only defensible forecasting claim, and the + single frozen-edge result must not be read as broad forecast + superiority. +

+

+ Unemployment satellite. Unemployment is not a VAR + variable; its published outlook is an Okun's-law regression of the + quarterly change in the ONS unemployment rate on year-on-year GDP + growth, fit 1992Q1–2025Q1 with furlough dummies for + 2020Q1–2021Q2 (which otherwise attenuate the coefficient about + threefold). In a 73-origin rolling test fed by the VAR's own GDP + forecasts it beats no-change at horizons 1–4 (relative RMSE + 0.82–0.99, excluding furlough targets, not statistically + significant) and is clearly worse beyond, so the published path is + capped at four quarters; the bands carry GDP-forecast uncertainty + only. Full specification and skill table: + okun_validation.json + in the model repository. +

+
+ +boe-svar: empirical band coverage across 49 origins +Line chart of empirical interval coverage by forecast horizon, averaged across the eight model variables, against the nominal 68 and 90 percent levels. 68% band, horizons one to eight: 71%, 68%, 64%, 61%, 60%, 60%, 58%, 57%; 90% band, horizons one to eight: 87%, 81%, 81%, 76%, 76%, 73%, 72%, 72%. Both bands under-cover, and coverage worsens with horizon. The window includes the Covid quarters and the evaluation model carries no Covid dummies, which depresses coverage. + +40% + +50% + +60% + +70% + +80% + +90% + +100% + + + + + + +68% band, mean coverage across variables +90% band +nominal 68% +nominal 90% +h1 +h2 +h3 +h4 +h5 +h6 +h7 +h8 + +
Empirical interval coverage across 49 expanding-window origins, mean across the eight model variables, by horizon. Predictive bands combine parameter and shock uncertainty (100 posterior draws × 5 paths per origin), percentile intervals on series levels. Both bands under-cover — the 68% band contains outturns 57–71% of the time and the 90% band 72–87%, worsening with horizon. The window includes the Covid quarters and the evaluation model carries no Covid dummies, which depresses coverage; pseudo- rather than real-time out-of-sample, as above. So the frozen-origin over-dispersion reading and this under-coverage reading disagree — a single origin is simply too little data, and the rolling result is the one to weight. Source: papers/boe-svar/figures/coverage_evaluation.json, generated by make_coverage.py.
+
+
+ Why there is no official yardstick for those forecast errors +

+ No official counterpart exists for those RMSEs. The authors' + companion paper (Staff Working Paper No. 1,165, January 2026) is a + methods paper on a different, four-variable SVAR; it publishes no + root-mean-squared errors, coverage rates or benchmark comparisons, so + the forecast numbers here stand without an official yardstick and are + reported as such. +

+
+ +
+
+ +
+
+ evidence — forecast accuracy +

How does it compare to other forecasters?

+
+
+

+ Three different statistics answer three different questions, and they + are not averaged, ranked, or plotted on one axis: this + SVAR's pseudo-out-of-sample accuracy against a naive benchmark + (computed here, quarterly), the OBR's and external forecasters' own + published real-time errors (cited, annual), and the + OBR emulator's tracking of the official + forecast (replication accuracy, not forecasting accuracy). +

+ +

+ Bank Rate is the only robust forecasting win. CPI + loses its advantage against a drift benchmark; GDP does not beat that + benchmark over the full sample, and Covid dominates squared error. +

+
+ Benchmark and sample diagnostics +

boe-svar against a random walk, computed from our own runs

+

+ An expanding-window pseudo-out-of-sample exercise re-fits the BVAR at + 49 quarterly origins (2012Q1–2024Q1, data sample + 1992Q1–2026Q1) and scores forecasts one to eight quarters ahead + against three naive benchmarks. A ratio below 1.0 means the model beats + the benchmark. +

+

+ The benchmark decides the answer. A no-change random + walk on a trending log level forfeits the whole trend as forecast + error, so beating it on a price index is close to uninformative. + Against a random walk with drift — the textbook naive + for a trending series — the CPI result largely evaporates: + 0.63 becomes 0.83 at one quarter and + 0.67 becomes 1.03 at eight, where the model is no + longer ahead at all. The pattern in the original numbers, wins only on + the two trending price series and ties or losses on the four series + where a random walk is a genuinely hard benchmark, was an artefact of + the benchmark rather than a finding about the model. +

+

+ What survives is Bank Rate. It is the one series here + that does not trend, so no-change is the right naive for it, + and it is the one series that improves under the harder benchmark: + 0.79 against drift at one quarter (Diebold–Mariano + p = 0.018) and 0.85 at eight. On the evidence assembled here + that, not inflation, is the model's defensible forecasting claim. +

+

+ UK GDP is not beaten by the benchmark either. Its + ratio of 1.06–1.09 is not statistically distinguishable from a + random walk at any horizon (p = 0.38 to 0.67), and excluding + the six origins whose target quarter falls in 2020Q1–2021Q2 it + becomes 0.77 at one quarter. Under squared loss the + 2020Q2 collapse dominates a 49-origin average. Both the full-sample + and the excluding-Covid figures are published; neither is the + preferred number. +

+

+ The AR(1) comparison is reported in the source file but is + not usable at long horizons: 94.5% of its eight-step + squared error for UK GDP comes from a single origin, because an AR(1) + extrapolates the Covid collapse. The apparent 3:1 win against it is + arithmetic, not evidence. Every benchmark now carries a + worst_origin_mse_share diagnostic for exactly this reason. +

+
+
+ +boe-svar forecast skill against a random walk with drift, all eight variables +Dot matrix of forecast error relative to a random walk with drift, eight variables by eight quarterly horizons, from 49 expanding-window origins. A ratio below 1.0 means the model beats the benchmark; filled dots mark differences significant at 5 per cent by a Diebold-Mariano test, hollow dots differences that are not statistically distinguishable. Bank Rate runs 0.79 at one quarter to 0.85 at 8; UK CPI runs 0.83 at one quarter to 1.03 at 8; World CPI runs 0.94 at one quarter to 1.07 at 8; Oil price runs 1.01 at one quarter to 0.98 at 8; CPI energy runs 0.98 at one quarter to 1.11 at 8; UK real GDP runs 1.06 at one quarter to 1.12 at 8; World GDP runs 1.07 at one quarter to 1.10 at 8; Exchange rate runs 1.03 at one quarter to 1.33 at 8. Bank Rate is the best at the longest horizon and Exchange rate the worst. Against this harder benchmark only four of eight variables beat naive at one quarter and two at 8. +RMSE ÷ drifting-random-walk RMSE · horizons 1–8 quarters +filled = difference significant at 5% (Diebold–Mariano); hollow = not distinguishable + +0.8 + +0.9 + +1.1 + +1.2 + +1.3 + +1.0 +← model better +benchmark better → +Bank Rate + + + + + + + + + +0.85 +UK CPI + + + + + + + + + +1.03 +World CPI + + + + + + + + + +1.07 +Oil price + + + + + + + + + +0.98 +CPI energy + + + + + + + + + +1.11 +UK real GDP + + + + + + + + + +1.12 +World GDP + + + + + + + + + +1.10 +Exchange rate + + + + + + + + + +1.33 + +
Expanding-window pseudo-out-of-sample error ratios against a random walk with drift, on the level of each series. A driftless walk is too weak a benchmark for a trending series, so this is the fair comparison; the no-change ratios are in the table below. 49 forecast origins, 2012Q1–2024Q1 (the window includes the Covid quarters); estimation sample from 1992Q1, data through 2026Q1; 4 lags. Estimation uses final revised data, so this is pseudo- rather than real-time out-of-sample. No single origin contributes more than 35% of any drift-benchmark squared error. Source: papers/boe-svar/figures/rolling_evaluation.json.
+
+
+ +How many of eight variables beat a drifting random walk, by horizon +Stacked column chart. Of the model's eight forecast variables, how many have lower root mean squared error than a random walk with drift, at horizons one to eight quarters. The counts are 4, 4, 3, 2, 3, 3, 2, 2 respectively. Each column also separates wins whose difference is statistically significant at 5 per cent by a Diebold-Mariano test from wins that are not: significant wins number 2, 1, 1, 1, 1, 0, 0, 0. From horizon 6 onward no variable beats the benchmark by a statistically significant margin. +Of 8 forecast variables, how many beat a random walk with drift + +2 + +4 + +6 + +8 + +0 + + + +4 +h=1 + + + +4 +h=2 + + + +3 +h=3 + + + +2 +h=4 + + + +3 +h=5 + + +3 +h=6 + + +2 +h=7 + + +2 +h=8 +beats it, and the difference is significant +beats it, not significantly +does not beat it + +
Wins are counted at a ratio strictly below 1.0, which is a hard cut: a variable at 0.99 counts as a win and one at 1.01 does not, even though neither is distinguishable from the benchmark. That is why the significance split matters more than the count. Same 49 origins and benchmark as the chart above. Source: papers/boe-svar/figures/rolling_evaluation.json.
+
+
+ + + + + + + + + + + + + + +
boe-svar forecast error relative to two naive benchmarks (ratio; below 1.0 beats the benchmark). Bold marks a win that is statistically significant; * marks any significant difference. All eight variables are shown — the four previously omitted were among the weakest.
variableh=1h=2h=4h=8h=1 ex-Covid
(vs no-change)
vs driftvs no-changevs driftvs no-changevs driftvs no-changevs driftvs no-change
Bank Rate0.79*0.880.85*0.960.89*1.020.851.030.86
UK CPI (level)0.83*0.630.850.620.940.661.030.670.62
World CPI (level)0.940.730.950.681.000.691.070.670.73
Oil price1.011.031.021.051.011.060.981.081.05
CPI energy0.980.980.970.981.031.041.11*1.130.97
UK real GDP (level)1.061.061.091.081.101.091.121.060.77
World GDP (level)1.071.041.101.031.110.931.100.710.51
Exchange rate1.031.041.091.111.191.221.331.411.05
+
+

+ Bank Rate crosses parity beyond h=3 + (source file has all eight variables and horizons). These are + quarterly, level-basis, pseudo-out-of-sample ratios computed with + hindsight-final data — they are not comparable + with the real-time annual-growth errors the OBR publishes, which is + why they get separate tables. +

+ +

What the OBR and external forecasters report about themselves

+

+ The OBR's Forecast + Evaluation Report (July 2025) publishes its own real-time errors + on annual growth rates since 2010, beside the median of external + forecasters compiled by HM Treasury. Cited, not recomputed; the + statistic is the median absolute error on annual rates, so it cannot + be compared with the quarterly RMSE ratios above. +

+
+ + + + + + + + + +
Median absolute forecast errors reported in the OBR Forecast Evaluation Report, July 2025 (annual rates, percentage points, forecasts since 2010)
variable · horizonOBRexternal median
Real GDP growth · 1 year ahead0.6pp0.6pp
Real GDP growth · 2 years ahead0.4pp0.4pp
CPI inflation · 1 year ahead0.3pp0.3pp
CPI inflation · 2 years ahead0.9pp0.9pp
+
+

+ The nearest in-house analogue is the frozen-edge experiment above: + 0.32pp RMSE on quarterly year-on-year GDP growth and + CPI inflation over seven quarters. Different statistic, frequency, + and origin count, so it sits beside the OBR's numbers, not against + them. For the Bank of England, the only precisely sourceable + comparison is the matched-vintage episode on the + validation index (August 2024 MPR + modal CPI of ~2.4% for late 2025 vs an outturn peak of 3.8%); the MPR + publishes no RMSE-by-horizon table. +

+
+ Omitted for lack of a defensible source: NIESR or consensus RMSE + tables by horizon, a Bank of England RMSE-by-horizon table, and an + AR(1) benchmark for the SVAR exercise. Each needs a new computation + or source before it can appear here. +
+
+
+ +
+
+ limits +

Where this departs from the Bank's model.

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
Known limits of the structural VAR replication
limitdetail
Frozen data edgeCoefficient estimation to 2025Q1; conditioning data to 2026Q1 (refreshed July 2026). Results shift with data revisions. The replication claims are scored on the extended 1992Q1–2025Q1 sample, not the paper's original 1992Q1–2023Q2 window.
Proxied world aggregatesThe Bank's internal UK-trade-weighted world GDP and CPI are unpublished, so they are rebuilt as chain-weighted US + euro-area + Japan + China aggregates with time-varying UK trade weights.
One ranking does not replicateThe paper ranks UK monetary policy as the largest domestic contributor to CPI variance; here UK supply (13.5%) exceeds monetary policy (10.1%). Attributed to the proxy world aggregates and the smaller accepted sample, and documented rather than tuned away.
Assumed lag length and simplified pandemic priorp = 4 is assumed, not selected; the pandemic treatment is simplified to exogenous Covid dummies.
Sign-restriction critiques applyPointwise medians mix structural models (Fry–Pagan); the Haar prior over rotations is informative about impulse responses (Baumeister–Hamilton). These apply to the Bank's own outputs identically.
+

+ every deviation is enumerated in the repository's + docs/methodology.md. +

+
+
+ +
+ + + + + + diff --git a/us-hank/code/index.html b/us-hank/code/index.html new file mode 100644 index 0000000..362c898 --- /dev/null +++ b/us-hank/code/index.html @@ -0,0 +1,257 @@ + + + + + +US HANK code — PolicyEngine Macro + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+

model 04 — US heterogeneous-agent NK model · us-hank · US · hosted

+

+ See who responds to a US shock, not just how much. +

+

+ Run stylized monetary, fiscal-spending, and productivity shocks through + a two-asset heterogeneous-agent New Keynesian model — aggregate + impulse responses plus first-order distributional output: MPCs by + wealth quartile and the hand-to-mouth share. +

+
+
+ + + +
+
+ code +

Ask the server, or install it.

+
+
+

+ Four steps from hosted call to local Python API; each step's code + follows its narrative. +

+
+ +
+
+
+
1

Ask the hosted server

+

Two MCP tools

+

Quickest is the hosted MCP server: hank_shock runs a stylized shock, optionally with the distributional block; hank_summary lists shock kinds, units, variants, and scope limits — instant, no solve.

+

Cold vs warm

+

The first call per variant pays the steady-state and Jacobian solves, roughly 18 seconds cold; both are cached, so warm calls are effectively instant.

+ +
+ +
+
2

Or use the CLI

+

Same two calls

+

The same two operations on the command line: hank-summary to see what the model accepts, hank-shock to run one — here a 25bp monetary easing with the distributional block on.

+ +
+ +
+
3

Know the units and variants

+

Units differ per kind

+

monetary: level change in the quarterly policy rate (−0.0025 = a 25bp easing); fiscal_spending: level change in G (steady-state output is 1, so 0.01 = 1% of GDP); productivity: level change in TFP.

+

Two variants

+

variant selects the paper's two_asset model (the default) or a fast one_asset textbook variant — no capital, so no investment response, and monetary/productivity shocks only.

+ +
+ +
+
4

Install and script it

+

The Python API

+

Beyond the hosted surface, install the package and use the Python API directly: solve the steady state once, solve the Jacobian once, then draw impulse responses and distributional summaries instantly.

+

What sits behind it

+

The solve behind these calls — steady state, Jacobian, then instant IRFs — is on the methodology page; what to trust the results for, on the validation page.

+ +
+
+ +
+
+ +
+ + + + + + + + diff --git a/us-hank/index.html b/us-hank/index.html index 2b5f5f9..1b39451 100644 --- a/us-hank/index.html +++ b/us-hank/index.html @@ -19,7 +19,7 @@ - + + + + + + + + + + + + +
+
+
+

model 04 — US heterogeneous-agent NK model · us-hank · US · hosted

+

+ See who responds to a US shock, not just how much. +

+

+ Run stylized monetary, fiscal-spending, and productivity shocks through + a two-asset heterogeneous-agent New Keynesian model — aggregate + impulse responses plus first-order distributional output: MPCs by + wealth quartile and the hand-to-mouth share. +

+
+
+ + + +
+
+ methodology +

Steady state, Jacobian, then instant IRFs.

+
+
+

+ The five stages of the us-hank pipeline, from the heterogeneous-agent + structure to the first-order distributional block. The theory is the + Auclert–Bardóczy–Rognlie–Straub Econometrica 2021 paper's; + the implementation builds on the authors' + sequence-jacobian + toolkit. +

+
+ +
+
+
+
1

Heterogeneous agents, two assets

+
+ + + Each household holds two assets + + liquid wealth + varies by household + + illiquid wealth + varies by household + + + + consumption + decision + + + MPC + varies with + liquid wealth + the full distribution enters the general-equilibrium response + +
click to expand
+
+

Households differ

+

This is a validated replication of the two-asset heterogeneous-agent New Keynesian (HANK) model of Auclert, Bardóczy, Rognlie and Straub (Econometrica 2021, "Using the Sequence-Space Jacobian to Solve and Estimate Heterogeneous-Agent Models"). Households differ in liquid and illiquid wealth, so their marginal propensities to consume differ, and the model carries that whole distribution through the general-equilibrium response — the thing a representative-agent model cannot say.

+

Production grids, not demo grids

+

The model runs at the paper's production grid sizes — 50 liquid × 70 illiquid × 50 capital points — rather than the packaged demo grids. It is a VAR-free, sequence-space HANK: no estimated reduced form, no expectations shortcut. A variant option selects the paper's two_asset model (the default) or a fast one_asset textbook variant — no capital, so no investment response, and monetary/productivity shocks only.

+ +
+ +
+
2

Calibrate and solve the steady state

+
+ + + Calibrate to the paper's targets, solve once, cache + + calibrate + discounting + + portfolio-adjustment + costs + + + match targets + the paper's + asset-market targets, + checked value by value + + + steady state + ~13 s, once, + cached + at the paper's production grids — no data vintage to refresh + +
click to expand
+
+

Hit the paper's targets

+

The first stage solves the general-equilibrium steady state at the production grids, calibrating discounting and portfolio-adjustment costs to hit the paper's asset-market targets. The solve takes about 13 seconds, is computed once, and is cached.

+

Checked value by value

+

The hosted steady state is set against the paper's published calibration targets, value by value, on the validation page. The parameterisation is the published paper's throughout — there is no data vintage to refresh.

+ +
+ +
+
3

Differentiate: the sequence-space Jacobian

+
+ + One T×T matrix per shock–outcome pair (T = 300 quarters) + + + + + outcome path + T × 1 + = + + + + + + + + + + + + + + GE Jacobian + T × T · ~5 s, cached + × + + + + + shock path + T × 1 + + warm IRF + matrix–vector product, + effectively instant + +
click to expand
+
+

One linear map

+

The second stage builds the general-equilibrium sequence-space Jacobian — the \(T \times T\) linear map from each exogenous path to each outcome path, with \(T = 300\) quarters. It takes about 5 seconds and is cached alongside the steady state.

+

Instant IRFs

+

With both caches warm, an impulse response is a matrix–vector product — effectively instant. The first call per variant pays the steady-state and Jacobian solves, roughly 18 seconds cold; every later call reuses them.

+ +
+ +
+
4

Shock: three instruments, endogenous labor tax

+
+ + + An AR(1) shock decays each quarter: size × persistenceᵗ + + + + + + + + + + size + + quarters t → + + + three instruments + monetary · fiscal_spending + · productivity + + + endogenous labor tax + balances the budget each period + +
click to expand
+
+

AR(1) paths

+

A shock is an AR(1) path, \(\varepsilon_t = \text{size} \times \rho^t\), on one of three instruments: the Taylor-rule intercept (monetary), real government spending (fiscal_spending), or TFP (productivity).

+

The design point that matters

+

The labor tax is endogenous. The fiscal block balances the government budget each period, so a spending shock is implicitly tax-financed — and there is no exogenous transfer or tax-rate instrument to shock. Requests for one are refused with that explanation rather than answered with an invented mapping; score_reform refuses model="hank" outright.

+ +
+ +
+
5

Distribute — within first-order limits

+
+ + MPCs by liquid-wealth quartile (schematic, not data) + + + + + + Q1 + Q2 + Q3 + Q4 + liquid-wealth quartile → + + + hand-to-mouth share + from the steady-state distribution + + impact consumption + by total-wealth quartile, + allocated by steady-state MPCs + first-order: linear in the shock, deviations — not forecasts + +
click to expand
+
+

The distributional block

+

From the steady-state joint distribution and consumption policy, the model reports MPCs by liquid-wealth quartile, the hand-to-mouth share, and a first-order allocation of the impact consumption response across total-wealth quartiles — allocated in proportion to steady-state MPCs.

+

What "first-order" means here

+

Responses are linear in the shock, with no state dependence: the distributional cuts capture the MPC-heterogeneity channel, not full household-level dynamics along the transition. And the model is not a forecaster — every result is a deviation around the calibrated steady state, not a projection of the US economy. The code page has the commands and Python API; the validation page has the replication gate and full limits.

+ +
+
+ +
+
+ + + + +
+ + + + + + + + diff --git a/us-hank/validation/index.html b/us-hank/validation/index.html new file mode 100644 index 0000000..b6ce915 --- /dev/null +++ b/us-hank/validation/index.html @@ -0,0 +1,218 @@ + + + + + +US HANK validation — PolicyEngine Macro + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+

model 04 — US heterogeneous-agent NK model · us-hank · US · hosted

+

+ See who responds to a US shock, not just how much. +

+

+ Run stylized monetary, fiscal-spending, and productivity shocks through + a two-asset heterogeneous-agent New Keynesian model — aggregate + impulse responses plus first-order distributional output: MPCs by + wealth quartile and the hand-to-mouth share. +

+
+
+ + + +
+
+ how far to trust it +

A replication gate, not a forecast-accuracy claim.

+
+
+

+ The ground truth is the published paper. The model repository's + 18-test suite gates the steady-state calibration + targets, market clearing, and the signs and magnitudes of the shock + responses against the published Econometrica 2021 results; the + implementation builds on the authors' own + sequence-jacobian toolkit rather than re-deriving the + model. Fidelity is strong, the claim narrow: evidence the replication + is right, not that the model predicts the US economy — no predictive + validation exists or is claimed. The hosted steady state is set + against the paper's published calibration targets, value by value, + below. +

+
+ +us-hank: steady state vs published calibration targets +Table-style chart of the hosted two-asset steady state against the published calibration targets of Auclert, Bardóczy, Rognlie and Straub (2021). Output Y: target 1, achieved 1, deviation 0; Capital K: target 10, achieved 10, deviation 0; Real rate r (quarterly): target 0.0125, achieved 0.0125, deviation 0; Government spending G: target 0.2, achieved 0.2, deviation 0; Government debt Bg: target 2.8, achieved 2.8, deviation 0; Total wealth A+B: target 14, achieved 14, deviation 2.7e-13; Liquid assets B: target 1.04, achieved 1.04, deviation 9.5e-14; Discount factor beta: target 0.976, achieved 0.976274, deviation 2.7e-04. Goods and asset market clearing residuals are 3.3e-08 and 3.1e-13. Beta is the calibrated free parameter that hits the wealth targets. +target +achieved +|deviation| +Output Y +1 +1 +0 + +Capital K +10 +10 +0 + +Real rate r (quarterly) +0.0125 +0.0125 +0 + +Government spending G +0.2 +0.2 +0 + +Government debt Bg +2.8 +2.8 +0 + +Total wealth A+B +14 +14 +2.7e-13 + +Liquid assets B +1.04 +1.04 +9.5e-14 + +Discount factor beta +0.976 +0.976274 +2.7e-04 +Market clearing: goods 3.3e-08, assets 3.1e-13. Achieved values solved from the hosted adapter; targets from the upstream test suite citing the paper. + +
Hosted two-asset steady state against the paper's published calibration targets, solved 2026-07-29 at the production grids (nB=50, nA=70, nK=50). β is the calibrated free parameter. Source: papers/us-hank/figures/replication.json.
+
+
+ This validates replication fidelity, not prediction. + The model generates deterministic, first-order responses around a + calibrated steady state. It does not forecast the US economy, quantify + parameter uncertainty, or map detailed tax reforms into its stylized + instruments. +
+

+ An independent replication of a published academic model — not + produced or endorsed by the paper's authors. Results are model + counterfactuals, not official estimates or forecasts. +

+

+ For how this model sits among other open-source HANK implementations, + see the open US HANK + model landscape report. The toolkit it builds on is at + shade-econ/sequence-jacobian. +

+
+
+ +
+
+ limits +

Known limitations.

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
Known limits of the us-hank implementation
limitdetail
Not a forecasterNo data, no estimation, no forecast origin: every result is an impulse response around the paper's calibrated steady state. Use boe-svar (UK) for forecasts; nothing in this suite forecasts the US.
Stylized instruments onlyThree shock kinds — monetary, fiscal spending, productivity. The labor tax is endogenous (the fiscal block balances the budget): no transfer or tax-rate instrument, and score_reform refuses model="hank" rather than inventing a reform mapping.
First-order responsesIRFs are linear in shock size: no state dependence, no zero lower bound, no asymmetry between large/small or positive/negative shocks.
Approximate distributional outputThe by-quartile consumption response allocates the aggregate response in proportion to steady-state MPCs — the MPC-heterogeneity channel, not full household-level dynamics along the transition.
Calibration, not vintageThe parameterisation is the published paper's; there is no data vintage to refresh. Revisiting the calibration would be a deliberate model change, not a data update.
+
+
+ +
+ + + + + + + diff --git a/validation/figures/make_charts.py b/validation/figures/make_charts.py index 6c3de68..a0fd17e 100644 --- a/validation/figures/make_charts.py +++ b/validation/figures/make_charts.py @@ -1,8 +1,13 @@ #!/usr/bin/env python3 -"""Regenerate the inline SVG charts in validation/index.html from the papers' sources. +"""Regenerate the inline SVG evidence charts on the model validation pages. -Run: python3 validation/figures/make_charts.py # rewrite validation/index.html - python3 validation/figures/make_charts.py --check # exit 1 if the page is stale +Each chart lives inline on the validation subtab of the model it belongs to +(obr/validation, svar/validation, frb-us/validation, us-hank/validation) as an +```` block; this script rewrites each block +in place on its owning page, keyed by the ``data-chart`` attribute. + +Run: python3 validation/figures/make_charts.py # rewrite the pages + python3 validation/figures/make_charts.py --check # exit 1 if any page is stale Why hand-emitted SVG rather than matplotlib: the charts must stay *inline* in the HTML (the site ships no third-party JS and no external assets) and must inherit the @@ -26,7 +31,6 @@ ROOT = Path(__file__).resolve().parents[2] HERE = Path(__file__).resolve().parent -PAGE = ROOT / "validation" / "index.html" # ---------------------------------------------------------------- helpers @@ -881,51 +885,81 @@ def dev(r): return "\n".join(out) +# chart id -> (builder, page that owns it). Every chart lives on the +# validation subtab of the model it provides evidence for. BUILDERS = [ - ("obr-anchored", chart_obr_anchored), - ("obr-reform", chart_obr_reform), - ("obr-computed-share", chart_obr_computed_share), - ("obr-freerun", chart_obr_freerun), - ("obr-outturn", chart_obr_outturn), - ("svar-fevd", chart_svar_fevd), - ("svar-fan", chart_svar_fan), - ("svar-coverage", chart_svar_coverage), - ("frbus-residuals", chart_frbus_residuals), - ("hank-targets", chart_hank_targets), - ("svar-skill-all", chart_svar_skill_all), - ("svar-winrate", chart_svar_winrate), + ("obr-anchored", chart_obr_anchored, "obr/validation/index.html"), + ("obr-reform", chart_obr_reform, "obr/validation/index.html"), + ("obr-computed-share", chart_obr_computed_share, "obr/validation/index.html"), + ("obr-freerun", chart_obr_freerun, "obr/validation/index.html"), + ("obr-outturn", chart_obr_outturn, "obr/validation/index.html"), + ("svar-fevd", chart_svar_fevd, "svar/validation/index.html"), + ("svar-fan", chart_svar_fan, "svar/validation/index.html"), + ("svar-coverage", chart_svar_coverage, "svar/validation/index.html"), + ("svar-skill-all", chart_svar_skill_all, "svar/validation/index.html"), + ("svar-winrate", chart_svar_winrate, "svar/validation/index.html"), + ("frbus-residuals", chart_frbus_residuals, "frb-us/validation/index.html"), + ("hank-targets", chart_hank_targets, "us-hank/validation/index.html"), ] -SVG_RE = re.compile(r'', re.DOTALL) + +def chart_re(chart_id: str) -> re.Pattern: + return re.compile( + rf'', re.DOTALL + ) -def render_page(html: str) -> str: - charts = [build() for _, build in BUILDERS] - found = SVG_RE.findall(html) - if len(found) != len(charts): - sys.exit(f"expected {len(charts)} .vchart SVGs in {PAGE}, found {len(found)}") - it = iter(charts) - return SVG_RE.sub(lambda _m: next(it).replace("\\", "\\\\"), html) +def render_page(page: str, html: str) -> str: + for chart_id, build, target in BUILDERS: + if target != page: + continue + pattern = chart_re(chart_id) + matches = pattern.findall(html) + if len(matches) != 1: + sys.exit( + f'expected exactly one data-chart="{chart_id}" SVG in {page}, ' + f"found {len(matches)}" + ) + svg = build().replace("\\", "\\\\") + html = pattern.sub(lambda _m: svg, html) + return html def main(): ap = argparse.ArgumentParser() - ap.add_argument("--check", action="store_true", help="exit 1 if the page is out of date") + ap.add_argument("--check", action="store_true", help="exit 1 if any page is out of date") args = ap.parse_args() - html = PAGE.read_text() - new = render_page(html) + pages = [] + for _, _, target in BUILDERS: + if target not in pages: + pages.append(target) + + stale = [] + written = 0 + for page in pages: + path = ROOT / page + html = path.read_text() + new = render_page(page, html) + if new == html: + continue + if args.check: + stale.append(page) + else: + path.write_text(new) + written += 1 + print(f"rewrote charts in {page}") + if args.check: - if new != html: - print(f"{PAGE} is out of date; run: python3 validation/figures/make_charts.py") + if stale: + print("stale chart pages; run: python3 validation/figures/make_charts.py") + for page in stale: + print(f" {page}") return 1 - print("validation/index.html charts are up to date.") + print("validation charts are up to date on all model pages.") return 0 - if new == html: + if not written: print("no change.") - else: - PAGE.write_text(new) - print(f"wrote {len(BUILDERS)} charts into {PAGE}") return 0 diff --git a/validation/index.html b/validation/index.html deleted file mode 100644 index 59c31d5..0000000 --- a/validation/index.html +++ /dev/null @@ -1,1470 +0,0 @@ - - - - - -Validation — PolicyEngine Macro - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-

evidence by model

-

- See the evidence for each model. -

-

- Compare results with official benchmarks—and see clearly where no - replication ground truth exists. -

-
-
- - - -
-
- 01 — start here -

Validation is a gradient, not a badge.

-
-
-

- The six models support different kinds of checks. The - verification gradient groups them into - three classes; choose a row, then open its evidence below. -

-
- In plain English: Anchored to the OBR's published - forecast path, the emulator tracks GDP within 0.15% and consumption - within 0.25%, and stays within 0.37% on GDP run as a genuine - forecast with held add-factors. In the UK structural VAR's (SVAR's) - frozen-edge test, all 14 of 14 outturns fell inside its 68% - uncertainty bands — a single-origin result, suggestive rather than - established coverage (see the caveat below). frb-us reproduces the - Fed's own database to machine precision. -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
The verification gradient: what each model is checked against, and its headline result
ModelVerification classWhat it is checked againstHeadline result
obr-macroReplication with a published anchorOBR Economic and Fiscal Outlook, March 2026; HMRC ready reckoner; ONS outturnsAnchored GDP MAPE 0.15%, consumption 0.25%
boe-svarReplication with a published anchorBrignone & Piffer (2025), BoE Macro Technical Paper No. 3; ONS outturnsFEVD (forecast-error variance decomposition — how much of each variable's movement each shock explains) shares 42.1% / 49.5% vs the paper's ~40% / ~50%
frb-usReplication with a published anchorThe Fed's LONGBASE database and pyfrbus solver; Coenen et al. and CBO multiplier rangesTracking invariant 5.6×10−17 — machine precision
us-hankReplication with a published anchorAuclert, Bardóczy, Rognlie & Straub (2021), using the authors' sequence-jacobian toolkit18 tests gate steady-state targets, market clearing, and published shock-response magnitudes
pe-microsimChecked against implemented legislationImplemented UK and US legislation, rule by rule, for a specified householdDeterministic household calculation; population estimates add survey and calibration uncertainty
psl-ogCalibrated counterfactual — no ground truthONS and OBR aggregates as calibration targets, not as a validation setTargets met by construction; no published replication exists
-
-
- Imposed or targeted matches are anchors, not validation. Stronger - evidence comes from out-of-sample forecasts, independent reform - costings, and solver residuals. -
-

Software fidelity and economic validity are separate.

-

- The dimensional audit below covers obr-macro, boe-svar, and frb-us. Levels are categorical - evidence judgements, not a synthetic score: a model can reproduce its - reference code perfectly while still lacking independent forecast or - counterfactual validation. -

-
- - - - - - - - - - - -
Independent model-quality audit by evidence dimension
Evidence dimensionobr-macroboe-svarfrb-us
Implementation fidelityModerate — incomplete channelsStrong — restrictions and identities gatedStrong — four gated scenarios at the reference-solver noise floor
Predictive validationWeak — raw GDP 5.75%, consumption 9.56% MAPEWeak — the CPI win does not survive a drift benchmark; Bank Rate is the only defensible claimNot assessed — LONGBASE is not a Fed forecast
Identification robustnessNot applicableModerate — proxy data and specification matterNot applicable
Policy-counterfactual validityWeak — one independent tax benchmark; spending multiplier ~1.0 vs the OBR's 0.6Not applicable — reform scoring refusedModerate — published ranges, VAR expectations only
Uncertainty calibrationWeak — sensitivity envelopes pendingWeak-to-moderate — measured 49-origin coverage runs below nominal (68% band covers 57–71%)Weak — bootstrap engine exists, public runs remain deterministic
Vintage reproducibilityModerate — multi-EFO archive pendingModerate — public proxies replace internal seriesModerate — artifacts hashed; multi-vintage tests pending
-
-
-
- -
-
- 02 — obr-macro -

Against the OBR's own forecast, and HMRC's reckoner.

-
-
-

- Anchored to the March 2026 EFO with OBR add-factors, the emulator - reproduces GDP to 0.15% MAPE (mean absolute - percentage error — the average percentage gap from the reference - path) and consumption to - 0.25% over 2025Q1–2027Q4 (CI gate: 1%). GDP - remains within 0.29% through 2031Q1, but unemployment becomes - unreliable after 2027Q4. Because anchoring is by construction, the - independent HMRC costing below is the stronger test. -

-
- -obr-macro: anchored baseline vs March 2026 EFO, quarterly deviation -Line chart. Quarterly percentage deviation of the anchored emulator from the published March 2026 EFO, 2025Q1 to 2027Q4. Real GDP ranges from -0.16% to +0.28% (mean absolute deviation 0.15%); consumption from -0.27% to +0.46% (mean absolute deviation 0.25%). Both series stay well inside the plus or minus 1% band at which continuous integration hard-fails the build, which is off the top and bottom of this frame. - -real GDP (peak +0.28%) - -consumption (peak +0.46%) - --0.6% - --0.3% - -0 - -+0.3% - -+0.6% -2025Q1 -2026Q1 -2027Q1 -2027Q4 - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Current March 2026 EFO baseline. CI hard-fails at ±1.00% — off the top and bottom of this frame. MAPE: 0.15% for GDP and 0.25% for consumption over 2025Q1–2027Q4. Computed from papers/obr-macro/figures/fig_anchored_data.csv, regenerated from the March 2026 detailed forecast tables on 21 July 2026.
-
-

- The independent check is the reform costing. Raising the basic rate - of income tax by 1pp from April 2026 scores at £6.46bn - in 2026–27 through the PolicyEngine static-costing bridge, - against HMRC's ready-reckoner figure of £6.9bn — a - deviation of −6.4%; the costing sits inside the £6–8bn - range spanned by recent published vintages. The gap widens in later years - (−15.6% by 2028–29) where HMRC's figures embed - administrative-data fiscal drag that survey microdata capture less - fully. -

-
- -obr-macro: 1p on the basic rate, ours vs HMRC ready reckoner (£bn/yr) -Grouped bar chart in billions of pounds per year. PolicyEngine's static costing of a 1 percentage point rise in the UK basic rate of income tax, against HMRC's Direct effects of illustrative tax changes ready reckoner, June 2025 vintage. For the basic rate +1pp, 2026–27 group, ours is 6.46 against HMRC’s 6.90, a deviation of -6.4%. For the basic rate +1pp, 2028–29 group, ours is 6.92 against HMRC’s 8.20, a deviation of -15.6%. The 2028–29 emulator figure is interpolated between the scored endpoints £6.46bn in 2026 and £7.38bn in 2030. - -0 - -2 - -4 - -6 - -8 - -10 - -6.46 -ours - -6.90 -HMRC -basic rate +1pp, 2026–27 - -6.92 -ours - -8.20 -HMRC -basic rate +1pp, 2028–29 - -
£bn/yr. 2028–29 emulator figure interpolated between scored endpoints (£6.46bn 2026, £7.38bn 2030). PolicyEngine's static costing of a 1pp basic-rate rise against HMRC's Direct effects of illustrative tax changes (June 2025 vintage). Source: obr-macro working paper, comparison table panel B.
-
-
- - - - - - - - - - - - - - - - - - -
obr-macro against the current March 2026 OBR EFO and HMRC's ready reckoner
OursOfficialDeviation
Anchored levels vs EFO March 2026, £bn/qtr
Real GDP, 2025Q1703.8703.4+0.05%
Real GDP, 2027Q4730.6728.6+0.28%
Consumption, 2025Q1429.7429.3+0.09%
Consumption, 2027Q4445.5443.4+0.46%
Basic rate +1pp vs HMRC ready reckoner, £bn/yr
2026–276.466.9−6.4%
2028–29 (interpolated)6.928.2−15.6%
2030 (end of window)7.38≈8.2−10.0%
Tracking error vs the published March 2026 EFO path, MAPE 2025Q1–2027Q4
Anchored (GDP / consumption)0.15% / 0.25%CI gate <1% — passes
Held add-factors (GDP / consumption), 2026Q1–2027Q4 window0.37% / 0.33%6 of 8 computed within band (paper's Nov-2025-vintage scorecard, over its own longer window: 2.2% / 3.6%)
Free-running, raw (GDP / consumption)5.75% / 9.56%over band — report-only, and weak
-
-
- -How much of the OBR emulator scorecard the model actually computes -Two stacked bars. Of 21 headline variables in the OBR emulator calibration scorecard, 11 are actually computed by the model and 10 are passthrough, held at the OBR published value and therefore scoring zero error trivially. Of the 11 computed, 3 are fair, 1 is an identity, 5 are poor, 2 are off. 4 of the 11, or 36 per cent, land within band, and one of those is a trivial accounting identity, so only 3 non-trivial computed variables are in band. The worst are company profits 79.80 per cent and the current account 4.17 per cent of GDP. -21 headline scorecard variables - - -11 computed -10 passthrough — held at the OBR value - - -of which, the 11 the model computes - -fair 3 - -identity 1 - -poor 5 - -off 2 -Only 3 of 11 non-trivial variables are in band. -A fourth pass is an identity over passthrough inputs. -bands: rates ±1.0pp · net balances ±1.5% of GDP · levels ≤10% MAPE - -
Raw calibration against the March 2026 EFO. Two “off” variables depend on unpublished OBR constants and are regression-gated rather than tuned. Source: docs/calibration_scorecard.md in the obr-macroeconomic-model repository.
-
-

- The spending multiplier is ~1.0 by construction, against the - OBR's own published 0.6. Under the demand closure a spending - shock lands directly in the GDP identity and the behavioural second - round is largely inactive, so a £5bn injection returns almost - exactly £5bn of GDP. That is roughly a - two-thirds overstatement of the impact multiplier - relative to the institution being replicated, and it applies to every - spending-side figure this page reports. It is the single most - important number for anyone reading a policy score off this model. -

-

- Why, and what it biases → obr-macro. -

-
- The honest scorecard, the two dead equations, outturns, and the March 2026 re-anchoring -

- The honest scorecard. The last row is the one that - matters for anyone tempted to read the 0.15% as forecasting skill. - The same equations that track the EFO to 0.15% when anchored miss it - by 5.75% free-running — precisely the gap the OBR's - own add-factor judgement closes in the official process, and the - reason reform deltas are always scored against the anchored baseline - rather than the raw one. The free-running score is de-seeded, with - passthrough variables excluded, and is published report-only. - Across the full scorecard only 4 of the 11 computed headline - variables land within band (real GDP, consumption, the trade - balance, and the trivial employment identity); of the full 21-line - scorecard, 10 lines are passthroughs held at the OBR value. The - worst line is company profits at 79.80% MAPE on the - March baseline (54.57% on the paper's November vintage), which - traces to a single unpublished constant in households' operating - surplus OSHH — the paper documents and regression-gates - it rather than re-tuning it, since tuning it would be fitting to the - answer. Other lines that moved on re-anchoring, reported rather than - smoothed: the free-running current account widened from 2.76 to - 4.17% of GDP and is now over band; RPI improved from - 2.03pp to 1.71pp; business investment worsened from - 15.48% to 16.12%; the two household-income - lines stand at 14.15% and 13.86%. -

-
- -obr-macro: real GDP level, anchored vs free-running vs the March 2026 EFO (£bn/qtr) -Line chart of quarterly real GDP levels in billions of pounds, 2025Q1 to 2027Q4. The published March 2026 EFO path rises from 703.4 to 728.6. The anchored emulator is visually indistinguishable from it, running from 703.8 to 730.6 (mean absolute deviation 0.15 per cent, recomputed here from the plotted series). The free-running emulator, de-seeded and with no add-factors, contracts from 691.0 to 663.1 — a gap that widens to 65 billion pounds, 5.75 per cent mean absolute deviation over the horizon. Free-running and EFO paths from papers/obr-macro/figures/fig_free_running_data.csv; anchored path from papers/obr-macro/figures/fig_anchored_data.csv. Coordinates: value v in billions maps to y = 292 - (v - 660) * 2.95 on a 660 to 740 axis; quarter i of 12 maps to x = 58 + i * 60.545. - -anchored (0.15% MAD) - -free-running (5.75% MAD) -EFO Mar 2026 - -660 - -680 - -700 - -720 - -740 - - - -2025Q1 -2026Q1 -2027Q1 -2027Q4 - -
Current March 2026 EFO baseline. Real GDP, £bn/qtr, 2025Q1–2027Q4. The anchored path (0.15% MAPE) sits on top of the EFO; the same equations free-running — de-seeded, no add-factors — contract away from it (5.75% MAPE). Computed from papers/obr-macro/figures/fig_free_running_data.csv and fig_anchored_data.csv, regenerated on 21 July 2026.
-
-
- Two equations do not fire. The October 2025 model - file contains two household-income equations with a bare - log(X) left-hand side — log(HHTFA) and - log(NDIVHH). The transpiler now parses that form, but - both still fail to execute for an independent reason: their exogenous - right-hand-side inputs MAJGDP and CORP are - absent from the databank entirely, so the right-hand side evaluates - non-finite and the solver skips the update. Every OBR figure on this - page is computed with those two channels inert. The practical effect - is bounded and demonstrable: household dividend income does not - respond to a corporate-profits change, so the household-income channel - of a corporation-tax reform is muted — a 5pp corporation-tax rise - moves FYCPR by −£1,780m while - ΔNDIVHH is exactly zero. Reviving the channel needs - a sourced CORP series, a calibration decision rather than - a code fix. -
-

- Forecast versus outturn. Comparing one forecast - vintage with another tests agreement, not accuracy. Against ONS - outturns published since anchoring, quarter-on-quarter real GDP - growth ran 0.1% in 2025Q2, 0.2% in Q3, 0.2% in Q4 and 0.6% in - 2026Q1. The emulator's path (0.15, 0.14, 0.25, 0.37) tracks the three - 2025 quarters to within 0.06 percentage points, but — like the - November EFO it inherits (0.28, 0.20, 0.27, 0.39) — misses the strong - 2026Q1 outturn by roughly a quarter of a point. Two caveats govern - the reading: this is primarily a test of the OBR's November vintage, - the emulator's own contribution being the 0.02–0.13 point gap - between the two model rows; and ONS quarterly estimates are - themselves revised, so the outturn is a moving target. -

-
- -obr-macro: quarterly real GDP growth — emulator vs EFO Nov 2025 vs ONS outturn (% q/q) -Grouped bar chart, percentage quarter-on-quarter real GDP growth for the four quarters with ONS outturns since anchoring. 2025Q2: emulator 0.15, EFO 0.28, ONS 0.1; 2025Q3: emulator 0.14, EFO 0.20, ONS 0.2; 2025Q4: emulator 0.25, EFO 0.27, ONS 0.2; 2026Q1: emulator 0.37, EFO 0.39, ONS 0.6. The emulator tracks the three 2025 outturns to within 0.06 points; both the emulator and the November EFO it inherits miss the strong 0.6 per cent 2026Q1 outturn by roughly a quarter of a point. Data from papers/obr-macro/figures/fig_outturn_data.csv. Coordinates: value v maps to y = 258 - v * 331.4 on a 0 to 0.7 axis. -emulator -EFO Nov 2025 -ONS outturn - -0 - -0.2 - -0.4 - -0.6 - -0.15 - -0.28 - -0.10 -2025Q2 - -0.14 - -0.20 - -0.20 -2025Q3 - -0.25 - -0.27 - -0.20 -2025Q4 - -0.37 - -0.39 - -0.60 -2026Q1 - -
November 2025 EFO vintage — the working paper's study; the live baseline is anchored to the March 2026 EFO. % q/q real GDP growth. The emulator tracks the three 2025 outturns to within 0.06pp; both model rows miss the strong 2026Q1 outturn (0.6%) by roughly a quarter point. Computed from papers/obr-macro/figures/fig_outturn_data.csv; the table below carries the exact values.
-
-
- - - - - - - - - -
Quarterly real GDP growth: emulator, EFO vintage, and ONS outturn
Real GDP, % q/qEmulatorEFO Nov 2025ONS outturn
2025Q20.150.280.1
2025Q30.140.200.2
2025Q40.250.270.2
2026Q10.370.390.6
-
-

- Vintage: re-anchored to the March 2026 EFO. The - hosted emulator has been re-anchored from the November 2025 EFO to - the OBR's March 2026 forecast, and the headline numbers on this page - are computed on that baseline: anchored GDP 0.15% MAPE, consumption - 0.25% over 2025Q1–2027Q4, with the anchored horizon extended to - 2031Q1 (GDP reproduced to 0.29% at 2031Q1; anchored unemployment is - unreliable beyond 2027Q4, drifting to 0.9% against the EFO's 4.1% by - 2031Q1). The headline and free-running charts now use March 2026. Only - the outturn backtest above retains November 2025, because changing its - forecast vintage would erase the historical forecast being tested. The - working paper now opens with a dated current-vintage note. Reform - effects are differences between structurally - identical runs and are insensitive to modest baseline drift, which is - why the re-anchoring leaves the £6.46bn/£7.38bn static costing - untouched and moves the second-round GDP effect only from - −0.057% to −0.058% by 2027Q4. -

-
- -
-
- -
-
- 03 — boe-svar -

Against a Bank of England paper, and against outturns.

-
-
-

- Against Brignone & Piffer (2025), the one-year share of UK - forecast-error variance from global shocks is - 42.1% for GDP and 49.5% for CPI. - The paper reports roughly 40% and 50%. -

-
- -boe-svar: global-shock FEVD shares, ours vs Brignone & Piffer (2025) -Grouped bar chart. Share of UK forecast-error variance attributed to identified global shocks (world demand, energy and supply) at the one-year horizon. For GDP, our 10,000-draw production run gives 42.1% against the paper's 40.0%; for CPI, 49.5% against 50.0%. Both deviations are a percentage point or less. The paper's values are approximate. - -0% - -20% - -40% - -60% - -42.1 -ours - -40.0 -paper -UK GDP, 1-yr horizon - -49.5 -ours - -50.0 -paper -UK CPI, 1-yr horizon - -
Global shocks = world demand + energy + supply. Ours = 10,000-draw production run; paper values approximate. Global-shock FEVD shares at the one-year horizon: the production run against the paper's published values. Source: papers/boe-svar/figures/comparison_numbers.json and the paper's validation table.
-
-
- Why the fast CI configuration lands 6–10 points off -

- The deliberately cheap unweighted CI configuration lands at 49.6% and - 43.8% — 6–10 points off, in opposite directions for the two - variables. That gap is itself informative rather than embarrassing: - the importance weights correct the Arias et al. (2018) - zero-restriction sampler towards the uniform-over-rotations - posterior, and applying them moves both shares substantially towards - the published values, though on the 2026Q1 vintage GDP still lands - 2.1 points high. Both configurations sit inside the wide [30, 60]% acceptance - band that CI enforces, which is chosen to catch gross regressions - without being flaky to Monte-Carlo variation. -

-
- - - - - - - -
boe-svar global-shock FEVD shares against Brignone & Piffer (2025)
Global share, 1-yr FEVDPaperCI config (fast)Production (10k draws)Deviation, CI / production
UK GDP~40%49.6%42.1%+9.6 / +2.1 pp
UK CPI~50%43.8%49.5%−6.2 / −0.5 pp
-
-
-

- Out-of-sample. Forecasting seven quarters ahead from - a frozen 2024Q2 edge, RMSE (root-mean-square error) is - 0.3pp for both GDP - growth and CPI inflation. Activity tracks well; the median misses the - 2025 inflation peak by up to 0.6pp but captures its reversal. -

-

- Fourteen of fourteen outturns inside the 68% band is a - calibration failure, not a pass. A correctly calibrated 68% - interval should contain roughly 9.5 of 14. Containing - all of them means the bands are too wide — the intervals are - roughly ±0.95pp around a forecast whose RMSE is 0.3pp, about - three times wider than the errors warrant. The model is under-confident - here, and this figure was previously presented as a validation win. It - is reported now as what it is: evidence of over-dispersion. -

-

- Neither reading is strong evidence either way. The fourteen points are - seven quarters × two variables from a single forecast - origin, so they are heavily correlated across horizon and across - variables; the effective number of independent observations is perhaps - two or three. Coverage cannot be established from one origin in either - direction. The genuine test is below: empirical coverage across all - 49 rolling origins and eight horizons. -

-
- -boe-svar: out-of-sample forecast fan from the frozen 2024Q2 edge vs ONS outturns -Two-panel fan chart. Left panel: year-on-year UK GDP growth; right panel: year-on-year UK CPI inflation. Each shows the posterior median forecast from the frozen 2024Q2 data edge as a line, the 68 per cent credible band as a shaded region over thirteen quarters 2024Q3 to 2027Q3, and ONS outturns for the seven evaluated quarters 2024Q3 to 2026Q1 as dots. All fourteen outturn dots fall inside the 68 per cent band; RMSE 0.32 percentage points for both variables. GDP medians run 1.3, 1.8, 1.3, 0.9, 0.8, 0.9, 0.9 per cent over the evaluated quarters against outturns of 1.0, 1.5, 1.3, 1.4, 1.3, 1.0, 0.9; CPI medians 2.3, 2.7, 2.8, 3.2, 3.2, 3.2, 3.2 against outturns of 2.0, 2.5, 2.8, 3.5, 3.8, 3.4, 3.1. Medians and 68 per cent bands from papers/boe-svar/figures/figure_numbers.json (forecast_table, entries [median, lo68, hi68]); ONS outturns from papers/boe-svar/figures/make_figures.py. Coordinates: GDP panel maps value v to y = 292 - (v + 1) * 59 for the -1 to 3 per cent axis; CPI panel y = 292 - (v - 1) * 59 for the 1 to 5 per cent axis; quarter i of 13 maps to x = 58 + i * 25.667 (GDP) or 416 + i * 25.667 (CPI). -median forecast + 68% band -ONS outturn -frozen 2024Q2 edge at left of each panel - --1% - -0 - -+1% - -+2% - -+3% - -1% - -2% - -3% - -4% - -5% - - - - - - - - - - - - - - - - - - - - -GDP growth (YoY, %) -CPI inflation (YoY, %) -24Q3 -26Q1 -27Q3 -24Q3 -26Q1 -27Q3 - -
14/14 inside the 68% band — see the over-dispersion caveat above. Frozen 2024Q2 data edge; source: papers/boe-svar/figures/figure_numbers.json.
-
-

- Rolling-origin audit. A stricter expanding-window test - now evaluates 49 historical origins without future-data leakage. Against - a no-change benchmark, CPI-level relative RMSE is 0.63 at one quarter - and 0.67 at eight quarters (lower is better), while UK GDP sits at - 1.06. But a no-change benchmark is too weak for a trending price - level: against a random walk with drift the CPI figures become 0.83 - and 1.03, and the GDP gap is not statistically significant at any - horizon. See section 09 for the full benchmark - comparison. The defensible forecasting claim is Bank Rate, not - inflation, and the single frozen-edge result must not be read as broad - GDP forecast superiority. -

-

- Unemployment satellite. The unemployment rate is not - one of the eight VAR variables; the published unemployment outlook is - an Okun's-law satellite — a small regression of the quarterly change - in the ONS unemployment rate on year-on-year GDP growth (fit - 1992Q1–2025Q1 with the furlough quarters 2020Q1–2021Q2 - dummied out, since they decouple unemployment from GDP and attenuate - the coefficient about threefold). In a 73-origin rolling test fed by - the VAR's own GDP forecasts, it beats a no-change benchmark at - horizons 1–4 excluding furlough targets (relative RMSE - 0.82–0.99) and is clearly worse beyond four quarters, so the - published path is capped at four quarters. The improvement is not - statistically significant at 73 overlapping origins, and the bands - carry GDP-forecast uncertainty only. Specification, code, and the - full skill table: - okun_validation.json - in the model repository. -

-
- -boe-svar: empirical band coverage across 49 origins -Line chart of empirical interval coverage by forecast horizon, averaged across the eight model variables, against the nominal 68 and 90 percent levels. 68% band, horizons one to eight: 71%, 68%, 64%, 61%, 60%, 60%, 58%, 57%; 90% band, horizons one to eight: 87%, 81%, 81%, 76%, 76%, 73%, 72%, 72%. Both bands under-cover, and coverage worsens with horizon. The window includes the Covid quarters and the evaluation model carries no Covid dummies, which depresses coverage. - -40% - -50% - -60% - -70% - -80% - -90% - -100% - - - - - - -68% band, mean coverage across variables -90% band -nominal 68% -nominal 90% -h1 -h2 -h3 -h4 -h5 -h6 -h7 -h8 - -
Empirical interval coverage across 49 expanding-window origins, mean across the eight model variables, by horizon. Predictive bands combine parameter and shock uncertainty (100 posterior draws × 5 paths per origin), percentile intervals on series levels. Both bands under-cover — the 68% band contains outturns 57–71% of the time and the 90% band 72–87%, worsening with horizon. The window includes the Covid quarters and the evaluation model carries no Covid dummies, which depresses coverage; pseudo- rather than real-time out-of-sample, as above. So the frozen-origin over-dispersion reading and this under-coverage reading disagree — a single origin is simply too little data, and the rolling result is the one to weight. Source: papers/boe-svar/figures/coverage_evaluation.json, generated by make_coverage.py.
-
-
- Why there is no official yardstick for those forecast errors -

- No official counterpart exists for those RMSEs. The authors' - companion paper (Staff Working Paper No. 1,165, January 2026) is a - methods paper on a different, four-variable SVAR; it publishes no - root-mean-squared errors, coverage rates or benchmark comparisons, so - the forecast numbers here stand without an official yardstick and are - reported as such. -

-
- -
-
- -
-
- 04 — frb-us -

Against the Fed's own solver — and against the Fed's own noise floor.

-
-
-

- The baseline reproduces the Fed's LONGBASE database across 284 - variables and 20 quarters with a maximum error of - 5.6×10−17 — machine precision, - against a 10−8 CI gate. -

-

- Shock responses match pyfrbus 1.0.0 to - 6.0×10−9 and 1.1.1 to - 1.4×10−8. The Fed's two - releases differ by 1.3×10−8, so - our differences sit at the reference solver's own numerical noise. -

-
- -frb-us: residuals against the Fed’s pyfrbus, log scale -Horizontal bar chart on a base-10 logarithmic axis of maximum absolute residuals; shorter is closer. ours vs LONGBASE (tracking invariant): 5.6e-17; pyfrbus 1.1.1 vs LONGBASE: 1.1e-08; ours vs pyfrbus 1.0.0 (shock): 6.0e-09; ours vs pyfrbus 1.1.1 (shock): 1.4e-08; pyfrbus 1.1.1 vs 1.0.0 — the Fed’s own two releases: 1.3e-08. The framing comparison is the last row: the Federal Reserve's own two pyfrbus releases disagree with each other by as much as this implementation disagrees with either, so our agreement sits at the scale of the reference implementation's own numerical noise rather than at a chosen tolerance. - -1e-18 - -1e-16 - -1e-14 - -1e-12 - -1e-10 - -1e-8 -ours vs LONGBASE (tracking invariant) - -5.6e-17 -pyfrbus 1.1.1 vs LONGBASE - -1.1e-08 -ours vs pyfrbus 1.0.0 (shock) - -6.0e-09 -ours vs pyfrbus 1.1.1 (shock) - -1.4e-08 -pyfrbus 1.1.1 vs 1.0.0 — the Fed’s own two releases - -1.3e-08 - -
Maximum absolute residuals, log scale — lower is closer. The bottom bar is the framing one: the Fed's own two releases disagree by as much as we disagree with either. On the tracking invariant, pyfrbus 1.1.1 reproduces LONGBASE to 1.1×10−8 against this implementation's 5.6×10−17. Source: papers/frb-us validation tables.
-
-

- Multipliers. Government-purchases and personal-tax - shocks of 1% of GDP land inside every published range they are - compared against — the last three rows below. -

-
- - - - - - - - - - - -
frb-us against the Fed's pyfrbus and published multiplier ranges
QuantityOursPublishedReading
Tracking invariant, max abs. error5.6×10−17CI gate <10−8; pyfrbus 1.1.1 gives 1.1×10−8machine precision
Shock responses vs pyfrbus 1.0.06.0×10−9at the reference's noise floor
Shock responses vs pyfrbus 1.1.11.4×10−81.1.1 vs 1.0.0: 1.3×10−8the Fed's releases differ as much as we do
Gov. purchases multiplier, yr 1, inertial Taylor rule0.720.7–1.0 (Coenen et al. 2012)inside range
Gov. purchases multiplier, yr 2, fixed funds rate0.99"roughly one" pegged; 1.1–1.2 with accommodationmatches the Board's characterisation
Personal tax cut multiplier, yrs 1–20.22 → 0.320.2–0.4; CBO central ≈0.3inside both ranges
-
-

- Uncertainty engine. The implementation now supports - seeded stochastic simulations that jointly resample the official - stochastic-equation residual vector, preserving contemporaneous - dependence and reporting failed replications. Model and LONGBASE - packages are also verified against separate SHA-256 provenance gates. - Published probability results still require a reviewed simulation - design; model-consistent expectations remain unavailable. -

-
- Two scope caveats: expectations path, and the 2014 FEDS Note comparison -

- Only the VAR-expectations path was exercised in - the version comparison; model-consistent expectations are not - implemented here, so forward-guidance-style exercises are out of - scope. And published 100bp funds-rate responses are read off the 2014 - FEDS Note charts, so those comparisons are approximate by nature — - our output-gap trough of −0.50pp against the Note's ≈−0.4pp - is within vintage and shock-design differences, not an exact match. -

-
- -
-
- -
-
- 05 — us-hank -

Against the published two-asset HANK (heterogeneous-agent New Keynesian) replication targets.

-
-
-

- The implementation reproduces the calibrated steady state and - sequence-space impulse responses of Auclert, Bardóczy, Rognlie and - Straub (Econometrica, 2021). Its 18-test upstream suite gates - calibration targets, market clearing, and the signs and magnitudes of - published shock responses at the paper's production grids, using the - authors' sequence-jacobian toolkit. -

-
- -us-hank: steady state vs published calibration targets -Table-style chart of the hosted two-asset steady state against the published calibration targets of Auclert, Bardóczy, Rognlie and Straub (2021). Output Y: target 1, achieved 1, deviation 0; Capital K: target 10, achieved 10, deviation 0; Real rate r (quarterly): target 0.0125, achieved 0.0125, deviation 0; Government spending G: target 0.2, achieved 0.2, deviation 0; Government debt Bg: target 2.8, achieved 2.8, deviation 0; Total wealth A+B: target 14, achieved 14, deviation 2.7e-13; Liquid assets B: target 1.04, achieved 1.04, deviation 9.5e-14; Discount factor beta: target 0.976, achieved 0.976274, deviation 2.7e-04. Goods and asset market clearing residuals are 3.3e-08 and 3.1e-13. Beta is the calibrated free parameter that hits the wealth targets. -target -achieved -|deviation| -Output Y -1 -1 -0 - -Capital K -10 -10 -0 - -Real rate r (quarterly) -0.0125 -0.0125 -0 - -Government spending G -0.2 -0.2 -0 - -Government debt Bg -2.8 -2.8 -0 - -Total wealth A+B -14 -14 -2.7e-13 - -Liquid assets B -1.04 -1.04 -9.5e-14 - -Discount factor beta -0.976 -0.976274 -2.7e-04 -Market clearing: goods 3.3e-08, assets 3.1e-13. Achieved values solved from the hosted adapter; targets from the upstream test suite citing the paper. - -
Hosted two-asset steady state against the paper's published calibration targets, solved 2026-07-29 at the production grids (nB=50, nA=70, nK=50). β is the calibrated free parameter. Source: papers/us-hank/figures/replication.json.
-
-
- This validates replication fidelity, not prediction. - The model generates deterministic, first-order responses around a - calibrated steady state. It does not forecast the US economy, quantify - parameter uncertainty, or map detailed tax reforms into its stylized - instruments. -
-

- Read the replication evidence and limits → -

-
-
- -
-
- 06 — pe-microsim -

Against implemented legislation.

-
-
-

- This model does not forecast. It deterministically applies encoded UK - and US tax-benefit law to a specified household, so its reference is - legislation and official guidance. Coverage depends - on the upstream rules and tests. -

-

- Population estimates also depend on survey data and calibration. The - UK run uses enhanced_frs_2023_24; in the - 1pp basic-rate check, it is 6.4% below HMRC in - year one. Survey versus administrative data, policy assumptions, and - HMRC behavioural elasticities explain that separate aggregate gap. -

- -
-
- -
-
- 07 — psl-og -

No ground truth. Calibration targets instead.

-
-
-
- No published replication target exists. The table - compares calibration targets with official UK aggregates. Imposed - matches are anchors; structural counterfactuals remain conditional on - the model's auditable assumptions and code. -
-
- - - - - - - - - - - -
psl-og calibration targets against official UK aggregates
QuantityOfficial UK valueSourceModel treatmentDeviation
Government debt-to-GDP95.1% (May 2026); OBR March 2026 forecast 94.4%ONS public sector financesimposed via closure target−0.1pp vs latest ONS (target 95.0%)
Household saving ratio8.9% (2026Q1, down from 9.6%)ONS quarterly sector accountstargeted through β0 by construction
Potential growth≈1.1% / yrOBR EFOimposed, gy = 0.011≈0
Depreciation / capital≈6–7% (CFC / net stock)ONS capital stocks bulletinimposed, δ = 0.065within range
Labour share of income≈0.59–0.60, rising in 2024ONS labour-share series / Blue Book 2025imposed, γ = 0.35 (OG-Core default)≈5pp — the capital share is not yet re-anchored to ONS factor shares
Net capital stock£5.6tn (2024) whole-economy net stock; the model's K maps to the narrower business-capital conceptONS capital stocks bulletinemergent — checked, not imposedconcept-adjusted; no published reconciliation
-
-

- A zero deviation on an imposed quantity is not independent evidence. -

-
- What would count as over-identifying evidence, and the dating and structural caveats -

- The genuinely over-identifying checks are the ones the - steady state must deliver: the capital–output ratio and - the interest rate consistent with the calibrated (γ, δ, β) triple. - These emerge in an economically sensible range in the deployed - configuration, but no systematic published reconciliation of - achieved-versus-target moments exists for OG-UK 0.3.2, and - the working paper declines to manufacture one. Producing that - reconciliation from logged solves is the single most valuable next - step for this member. -

-

- Two dating caveats: the official column reports figures current at - mid-2026 while the deployed calibration was frozen against slightly - earlier vintages, and the saving ratio is volatile quarter to - quarter, so β is matched to a smoothed level rather than the latest - print. Two structural caveats carried over from OG-Core: the ability - types and lifetime earnings profiles are still the US tax-microdata - estimates, and UK re-estimation is an open task. -

-
- -
-
- -
-
- 08 — side by side -

The same questions, answered by other shops.

-
-
-

- Published institutional answers sit beside ours; each row states how - closely the definitions align. -

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Published results from other institutions beside ours, with comparability stated per row
QuestionOursTheirsHow comparable
Long-run effect of a 1pp basic-rate rise (psl-og)Not yet produced — the OG-UK side of this comparison is open validation workOBR Working Paper 22, Table 5.1: GDP/person −0.1%, labour supply −0.2%, productivity +0.1%Future benchmark — same scenario and model family; WP 22's figures are the target our run will be judged against, not a match we claim today
Cost of cutting the basic rate 1p, 2026–27 (obr-macro + pe)£6.46bn staticHMRC ready reckoner (June 2025): £6.9bn, rising to £8.2bn by 2028–29Direct, −6.4% in year one — HMRC's figure embeds behavioural response and the OBR's March 2025 forecast
Government-purchases multiplier, year one (frb-us)0.72 (inertial Taylor rule); 0.99 in year two with the funds rate fixedRamey (JEP 2019): the literature sits "in a surprisingly narrow range of 0.6 to 1"Direct — both estimates fall inside the survey range
Income-tax multiplier (frb-us, US)0.22 → 0.32 over years 1–2OBR's published UK assumption: 0.3 for income tax and NICsLoose — different country and model class; the coincidence is noted, not claimed
Forecast error, 2024Q3 onward (boe-svar)RMSE 0.32pp on quarterly GDP growth and CPI inflation, data frozen at 2024Q2; the 2025 CPI hump missed by up to 0.6ppOBR forecast evaluation (July 2025): one-year GDP growth underestimated by 0.4pp on average since 2010 (external forecasters 0.5pp). Bank of England MPR (Aug 2024): modal CPI for late 2025 around 2.4%, against an outturn peak of 3.8% — a miss of ~1.4pp on the same episodeAdjusted — RMSE and mean error are different statistics, and the Bank's modal path is conditioned on market rates; the vintages, at least, are matched
-
-
- Comparators that could not be verified against a fetchable source — - CBO's FRB/US multiplier tables, IFS decile charts, per-band Scottish - ready-reckoner figures — are omitted rather than quoted from memory. -
-
-
- -
-
- 09 — forecast accuracy -

How do we compare to other forecasters?

-
-
-

- Three different statistics answer three different questions, and they - are not averaged, ranked, or plotted on one axis: our - SVAR's pseudo-out-of-sample accuracy against a naive benchmark - (computed here, quarterly), the OBR's and external forecasters' own - published real-time errors (cited, annual), and our OBR emulator's - tracking of the official forecast (replication accuracy, not - forecasting accuracy). -

- -

- Bank Rate is the only robust forecasting win. CPI - loses its advantage against a drift benchmark; GDP does not beat that - benchmark over the full sample, and Covid dominates squared error. -

-
- Benchmark and sample diagnostics -

boe-svar against a random walk, computed from our own runs

-

- An expanding-window pseudo-out-of-sample exercise re-fits the BVAR at - 49 quarterly origins (2012Q1–2024Q1, data sample - 1992Q1–2026Q1) and scores forecasts one to eight quarters ahead - against three naive benchmarks. A ratio below 1.0 means the model beats - the benchmark. -

-

- The benchmark decides the answer. A no-change random - walk on a trending log level forfeits the whole trend as forecast - error, so beating it on a price index is close to uninformative. - Against a random walk with drift — the textbook naive - for a trending series — the CPI result largely evaporates: - 0.63 becomes 0.83 at one quarter and - 0.67 becomes 1.03 at eight, where the model is no - longer ahead at all. The pattern in the original numbers, wins only on - the two trending price series and ties or losses on the four series - where a random walk is a genuinely hard benchmark, was an artefact of - the benchmark rather than a finding about the model. -

-

- What survives is Bank Rate. It is the one series here - that does not trend, so no-change is the right naive for it, - and it is the one series that improves under the harder benchmark: - 0.79 against drift at one quarter (Diebold–Mariano - p = 0.018) and 0.85 at eight. On the evidence assembled here - that, not inflation, is the model's defensible forecasting claim. -

-

- UK GDP is not beaten by the benchmark either. Its - ratio of 1.06–1.09 is not statistically distinguishable from a - random walk at any horizon (p = 0.38 to 0.67), and excluding - the six origins whose target quarter falls in 2020Q1–2021Q2 it - becomes 0.77 at one quarter. Under squared loss the - 2020Q2 collapse dominates a 49-origin average. Both the full-sample - and the excluding-Covid figures are published; neither is the - preferred number. -

-

- The AR(1) comparison is reported in the source file but is - not usable at long horizons: 94.5% of its eight-step - squared error for UK GDP comes from a single origin, because an AR(1) - extrapolates the Covid collapse. The apparent 3:1 win against it is - arithmetic, not evidence. Every benchmark now carries a - worst_origin_mse_share diagnostic for exactly this reason. -

-
-
- -boe-svar forecast skill against a random walk with drift, all eight variables -Dot matrix of forecast error relative to a random walk with drift, eight variables by eight quarterly horizons, from 49 expanding-window origins. A ratio below 1.0 means the model beats the benchmark; filled dots mark differences significant at 5 per cent by a Diebold-Mariano test, hollow dots differences that are not statistically distinguishable. Bank Rate runs 0.79 at one quarter to 0.85 at 8; UK CPI runs 0.83 at one quarter to 1.03 at 8; World CPI runs 0.94 at one quarter to 1.07 at 8; Oil price runs 1.01 at one quarter to 0.98 at 8; CPI energy runs 0.98 at one quarter to 1.11 at 8; UK real GDP runs 1.06 at one quarter to 1.12 at 8; World GDP runs 1.07 at one quarter to 1.10 at 8; Exchange rate runs 1.03 at one quarter to 1.33 at 8. Bank Rate is the best at the longest horizon and Exchange rate the worst. Against this harder benchmark only four of eight variables beat naive at one quarter and two at 8. -RMSE ÷ drifting-random-walk RMSE · horizons 1–8 quarters -filled = difference significant at 5% (Diebold–Mariano); hollow = not distinguishable - -0.8 - -0.9 - -1.1 - -1.2 - -1.3 - -1.0 -← model better -benchmark better → -Bank Rate - - - - - - - - - -0.85 -UK CPI - - - - - - - - - -1.03 -World CPI - - - - - - - - - -1.07 -Oil price - - - - - - - - - -0.98 -CPI energy - - - - - - - - - -1.11 -UK real GDP - - - - - - - - - -1.12 -World GDP - - - - - - - - - -1.10 -Exchange rate - - - - - - - - - -1.33 - -
Expanding-window pseudo-out-of-sample error ratios against a random walk with drift, on the level of each series. A driftless walk is too weak a benchmark for a trending series, so this is the fair comparison; the no-change ratios are in the table below. 49 forecast origins, 2012Q1–2024Q1 (the window includes the Covid quarters); estimation sample from 1992Q1, data through 2026Q1; 4 lags. Estimation uses final revised data, so this is pseudo- rather than real-time out-of-sample. No single origin contributes more than 35% of any drift-benchmark squared error. Source: papers/boe-svar/figures/rolling_evaluation.json.
-
-
- -How many of eight variables beat a drifting random walk, by horizon -Stacked column chart. Of the model's eight forecast variables, how many have lower root mean squared error than a random walk with drift, at horizons one to eight quarters. The counts are 4, 4, 3, 2, 3, 3, 2, 2 respectively. Each column also separates wins whose difference is statistically significant at 5 per cent by a Diebold-Mariano test from wins that are not: significant wins number 2, 1, 1, 1, 1, 0, 0, 0. From horizon 6 onward no variable beats the benchmark by a statistically significant margin. -Of 8 forecast variables, how many beat a random walk with drift - -2 - -4 - -6 - -8 - -0 - - - -4 -h=1 - - - -4 -h=2 - - - -3 -h=3 - - - -2 -h=4 - - - -3 -h=5 - - -3 -h=6 - - -2 -h=7 - - -2 -h=8 -beats it, and the difference is significant -beats it, not significantly -does not beat it - -
Wins are counted at a ratio strictly below 1.0, which is a hard cut: a variable at 0.99 counts as a win and one at 1.01 does not, even though neither is distinguishable from the benchmark. That is why the significance split matters more than the count. Same 49 origins and benchmark as the chart above. Source: papers/boe-svar/figures/rolling_evaluation.json.
-
-
- - - - - - - - - - - - - - -
boe-svar forecast error relative to two naive benchmarks (ratio; below 1.0 beats the benchmark). Bold marks a win that is statistically significant; * marks any significant difference. All eight variables are shown — the four previously omitted were among the weakest.
variableh=1h=2h=4h=8h=1 ex-Covid
(vs no-change)
vs driftvs no-changevs driftvs no-changevs driftvs no-changevs driftvs no-change
Bank Rate0.79*0.880.85*0.960.89*1.020.851.030.86
UK CPI (level)0.83*0.630.850.620.940.661.030.670.62
World CPI (level)0.940.730.950.681.000.691.070.670.73
Oil price1.011.031.021.051.011.060.981.081.05
CPI energy0.980.980.970.981.031.041.11*1.130.97
UK real GDP (level)1.061.061.091.081.101.091.121.060.77
World GDP (level)1.071.041.101.031.110.931.100.710.51
Exchange rate1.031.041.091.111.191.221.331.411.05
-
-

- Bank Rate crosses parity beyond h=3 - (source file has all eight variables and horizons). These are - quarterly, level-basis, pseudo-out-of-sample ratios computed with - hindsight-final data — they are not comparable - with the real-time annual-growth errors the OBR publishes, which is - why they get separate tables. -

- -

What the OBR and external forecasters report about themselves

-

- The OBR's Forecast - Evaluation Report (July 2025) publishes its own real-time errors - on annual growth rates since 2010, beside the median of external - forecasters compiled by HM Treasury. Cited, not recomputed; the - statistic is the median absolute error on annual rates, so it cannot - be compared with the quarterly RMSE ratios above. -

-
- - - - - - - - - -
Median absolute forecast errors reported in the OBR Forecast Evaluation Report, July 2025 (annual rates, percentage points, forecasts since 2010)
variable · horizonOBRexternal median
Real GDP growth · 1 year ahead0.6pp0.6pp
Real GDP growth · 2 years ahead0.4pp0.4pp
CPI inflation · 1 year ahead0.3pp0.3pp
CPI inflation · 2 years ahead0.9pp0.9pp
-
-

- Our own frozen-edge experiment is the nearest in-house analogue: the - boe-svar RMSE of 0.32pp on quarterly year-on-year GDP - growth and CPI inflation over seven quarters from a 2024Q2 data edge - (section 03 above). Different statistic (RMSE vs median absolute - error), different frequency (quarterly vs annual), one origin against - many — so it sits beside the OBR's numbers, not against them. - For the Bank of England, the single matched-vintage episode already - cited in section 08 — the August 2024 MPR modal CPI path of - about 2.4% for late 2025 against an outturn peak of 3.8% — is - the only comparison we can source precisely; the Bank does not - publish an RMSE-by-horizon table in the MPR. -

- -

Our OBR emulator: replication accuracy, not forecast accuracy

-

- The obr-macro numbers in section 02 measure how closely the anchored - emulator reproduces the OBR's published March 2026 path - (MAPE 0.15% GDP, 0.25% consumption) — that is replication - accuracy. It is not an independent forecast: de-seeded and stripped - of add-factors, the same model drifts 5.75% below the EFO path over - twelve quarters. Any forecast-accuracy credit for obr-macro therefore - belongs to the OBR's own forecast, whose errors are the FER numbers - above. -

-
- Omitted for lack of a defensible source: a NIESR or consensus RMSE - table by horizon (NIESR does not publish one in a fetchable form we - could verify), a Bank of England RMSE-by-horizon table (not published - in the MPR), and an AR(1) benchmark for the SVAR exercise (our stored - evaluation includes only the random walk). Each would need a new - computation or source before it can appear here. -
-
-
- -
- - - - - - - diff --git a/vercel.json b/vercel.json index b72d3e2..7695182 100644 --- a/vercel.json +++ b/vercel.json @@ -3,8 +3,14 @@ "cleanUrls": true, "trailingSlash": false, "redirects": [ + { "source": "/papers", "destination": "/models#evidence", "permanent": true }, + { "source": "/papers/", "destination": "/models#evidence", "permanent": true }, { "source": "/docs", "destination": "/models#compare", "permanent": true }, - { "source": "/docs/", "destination": "/models#compare", "permanent": true } + { "source": "/docs/", "destination": "/models#compare", "permanent": true }, + { "source": "/validation", "destination": "/models#validation", "permanent": true }, + { "source": "/validation/", "destination": "/models#validation", "permanent": true }, + { "source": "/notes", "destination": "/forecasts#notes", "permanent": true }, + { "source": "/notes/", "destination": "/forecasts#notes", "permanent": true } ], "headers": [ {