From c576e9cea17116b93c5e25cba1019274ddb66b9c Mon Sep 17 00:00:00 2001
From: vahid-ahmadi
Date: Wed, 12 Aug 2026 16:11:56 +0100
Subject: [PATCH 1/2] Refresh OBR figures that had been stale since the ONS
anchor, and name the two levers that do not work
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
A fresh review of the emulator recomputed every published number and
probed each fiscal instrument. Two things needed changing here.
**The free-running scorecard had been stale since PR #22 upstream.** The
`OSHH` ONS anchor landed and these pages went on publishing the pre-anchor
figures, which *understated* the model's accuracy on every line: real GDP
4.48% not 5.75%, consumption 7.49% not 9.56%, household income 6.27% not
14.15%, real household income 6.03% not 13.86%, business investment 15.73%
not 16.12%, company profits 63.29% not 79.80%, current account 3.60% of
GDP not 4.17%. Six of the eleven computed variables land within band, not
four. Regenerated `fig_free_running_data.csv` from the model and
recomputed the MAPEs from the CSV rather than transcribing them, so the
chart and the prose now come from the same place.
**Two of the four fiscal levers do not work, and nothing said so.**
`CGIPS` moves business investment by exactly 0.0 in all twelve quarters
and leaves a wrong-signed residual GDP effect, against the OBR's published
1.0 — the channel is absent, not weak. `TCPRO` never converges: for a
sustained +5pp rise the investment response runs £97m at q3, £2,876m at
q12 and £43,387m at q25, compounding 1.21–1.27x every quarter for 25
quarters, because the stabiliser anchors the level rather than the
deviation. Truncating the shock from twelve quarters to eight barely moves
the q12 answer, so the published number is carried by accumulated drift
rather than by the tax rate.
Both now carry a caveat in the `obr_shock` payload — a docstring does not
reach an MCP caller reading JSON — and on the lever list at /obr/code. The
site already said the government-consumption multiplier is ~1 by
construction against the OBR's published 0.6, and the review confirmed it
is exactly 1.0000 on impact at both £0.1bn and £10bn and still 0.9995 at
quarter 12, with consumption moving £0.64m on a £1,250m shock. That leaves
the household-income lever as the one instrument here with behaviour in it.
Upstream: PolicyEngine/obr-macroeconomic-model#27, which also fixes three
ways to get a confidently wrong answer out of this model — a silent
all-NaN shock reading as "no effect", a corporation-tax rate above 1
returning a wrong-signed finite answer, and a transmission audit that had
published +53.99% GDP for a 1pp corporation-tax rise.
Co-Authored-By: Claude Opus 5 (1M context)
---
economy/topics/public-finances/index.html | 2 +-
index.html | 2 +-
.../src/policyengine_macro/capabilities.py | 2 +-
integration/src/policyengine_macro/core.py | 35 ++-
integration/tests/test_wiring.py | 19 ++
obr/code/index.html | 8 +-
obr/index.html | 2 +-
obr/validation/index.html | 54 ++--
papers/obr-macro/figures/fig_free_running.pdf | Bin 17353 -> 17946 bytes
.../figures/fig_free_running_data.csv | 24 +-
validation/figures/chart_data.json | 266 +++++++++---------
11 files changed, 238 insertions(+), 176 deletions(-)
diff --git a/economy/topics/public-finances/index.html b/economy/topics/public-finances/index.html
index 25beceaf..a01f423a 100644
--- a/economy/topics/public-finances/index.html
+++ b/economy/topics/public-finances/index.html
@@ -124,7 +124,7 @@
The model view, and the same breath its limits.
Predictive validation: weak.
- Free-running GDP and consumption MAPE are 5.75% and 9.56%; the anchored fit is by construction.
+ Free-running GDP and consumption MAPE are 4.48% and 7.49%; the anchored fit is by construction.
Policy counterfactuals: weak.
One income-tax costing is independently compared with HMRC; trade, labour, prices and parts of household income remain constrained.
Read the validation page →
diff --git a/index.html b/index.html
index 956bc79c..7fe326a9 100644
--- a/index.html
+++ b/index.html
@@ -210,7 +210,7 @@
What the evidence shows—and what it doesn't.
pe-microsimStatutory checks
PolicyEngine’s own tax–benefit microsimulation for the UK and US — the engine behind policyengine.org.
Rules are tested against statute; population totals inherit survey uncertainty. It cannot answer GDP, inflation, interest rates or macro feedback — that is what the models below add.
-
obr-macroAnchored replication
Emulator of the OBR’s UK macroeconometric forecast model.
Tracks the March 2026 EFO to 0.15% GDP MAPE with published anchors; free-running error is 5.75%.
+
obr-macroAnchored replication
Emulator of the OBR’s UK macroeconometric forecast model.
Tracks the March 2026 EFO to 0.15% GDP MAPE with published anchors; free-running error is 4.48%.
boe-svarForecast evaluation
Bank of England structural VAR separating global and domestic shocks.
Reproduces the published decomposition for GDP (37.4% vs ~40%) and falls about 8pp short on CPI (42.3% vs ~50%) once measured the way the paper defines it. Against a random walk with drift, and after adjusting for the 64 tests run, no variable at any horizon shows skill. See the evaluation →
frb-usSoftware replication
The Federal Reserve’s large-scale model of the US economy.
Matches the Fed’s pyfrbus to within the Fed’s own two releases’ disagreement (~1×10⁻⁸); no predictive claim.
us-hankPublished replication
Heterogeneous-agent New Keynesian model of US households.
Hits every Auclert et al. (2021) calibration target; responses are first-order and stylized.
diff --git a/integration/src/policyengine_macro/capabilities.py b/integration/src/policyengine_macro/capabilities.py
index b306b4e2..01357f4d 100644
--- a/integration/src/policyengine_macro/capabilities.py
+++ b/integration/src/policyengine_macro/capabilities.py
@@ -204,7 +204,7 @@ def _quality(level: str, evidence: str, next_gate: str) -> dict:
),
"predictive_validation": _quality(
"weak",
- "Free-running GDP and consumption MAPE are 5.75% and 9.56%; the "
+ "Free-running GDP and consumption MAPE are 4.48% and 7.49%; the "
"anchored fit is by construction.",
"Pass rolling-origin historical-vintage tests against simple "
"benchmarks and first-release outturns.",
diff --git a/integration/src/policyengine_macro/core.py b/integration/src/policyengine_macro/core.py
index d8c01edb..587fec1b 100644
--- a/integration/src/policyengine_macro/core.py
+++ b/integration/src/policyengine_macro/core.py
@@ -136,12 +136,33 @@ def _import_boe_var():
"description": "Corporation tax (main) rate",
"units": "rate change in decimal (e.g. -0.05 = 5pp cut from 25% to 20%)",
"investment_closure": True,
+ # The investment closure's stabiliser anchors the LEVEL, not the
+ # deviation, and the deviation does not converge at any horizon: for a
+ # sustained +5pp rise |delta_IF| runs £97m at q3, £2,876m at q12 and
+ # £43,387m at q25, growing 1.21-1.27x every quarter for 25 quarters.
+ # Truncating the shock from 12 quarters to 8 barely moves the q12
+ # response, so the number is carried by accumulated drift rather than
+ # by the tax rate. The 12-quarter default is not where this settles;
+ # it is where the magnitude still looks plausible.
+ "caveat": (
+ "the response does not converge: it compounds at roughly "
+ "25%/quarter with no steady state, so read the sign and the "
+ "first few quarters, not the level or the cumulative total"
+ ),
},
{
"var": "CGIPS",
"description": "Nominal central government investment (feeds real GGI via the GGIPS/GGIDEF chain)",
"units": "£m nominal per quarter (e.g. 3000 ≈ £2.5bn real per quarter)",
"investment_closure": False,
+ # Measured: delta_IF is exactly 0.0 in all twelve quarters and the
+ # residual GDP effect goes negative (-0.047 by q12) against the OBR's
+ # published 1.0. The channel is not weak, it is absent.
+ "caveat": (
+ "this channel is dead under the demand closure: business "
+ "investment does not respond at all and the residual GDP effect "
+ "is wrong-signed. Do not use it to score capital spending"
+ ),
},
]
@@ -203,7 +224,16 @@ def obr_shock(
rows = _obr_result_rows(df)
shocked = rows[: int(periods)]
peak = max(rows, key=lambda r: abs(r["pct_gdp"]))
- return {
+ # A lever whose channel is dead or non-convergent must say so in the
+ # payload, not only in a docstring nobody reads over MCP. Of the four
+ # fiscal instruments here exactly one contains behaviour: CGG is an
+ # accounting identity (multiplier exactly 1.0000, flat, against the OBR's
+ # published 0.6), CGIPS is dead, TCPRO never converges, and the
+ # household-income lever is the only one with a response worth reading.
+ caveat = next(
+ (v.get("caveat") for v in OBR_VARIABLES if v["var"] == var), None
+ )
+ result = {
"name": name,
"provenance": _provenance(
model_id="obr-emulator",
@@ -222,6 +252,9 @@ def obr_shock(
"peak_pct_gdp": peak["pct_gdp"],
"peak_period": peak["period"],
}
+ if caveat:
+ result["caveat"] = caveat
+ return result
# ---------------------------------------------------------------------------
diff --git a/integration/tests/test_wiring.py b/integration/tests/test_wiring.py
index 6d249521..6b8cfb90 100644
--- a/integration/tests/test_wiring.py
+++ b/integration/tests/test_wiring.py
@@ -404,3 +404,22 @@ def test_cli_score_still_rejects_a_genuinely_unknown_model(runner):
assert res.exit_code != 0
assert "Traceback" not in res.output
assert "model must be one of" in res.output
+
+
+def test_obr_shock_carries_the_lever_caveat_in_the_payload():
+ """Two of the four OBR levers do not work, and the payload must say so.
+
+ Measured in the model repo: CGIPS moves business investment by exactly
+ zero in all twelve quarters and leaves a wrong-signed residual, and TCPRO
+ compounds at roughly 25%/quarter with no steady state. A docstring does
+ not reach an MCP caller reading a JSON result, so the caveat travels with
+ the numbers.
+ """
+ from policyengine_macro import core
+
+ levers = {v["var"]: v for v in core.obr_list_variables()}
+ assert levers["TCPRO"]["caveat"], "the non-convergent lever declares no caveat"
+ assert levers["CGIPS"]["caveat"], "the dead lever declares no caveat"
+ # CGG has its own documented "~1 by construction" reading on the site and
+ # is not caveated here; asserting that keeps this test honest about scope.
+ assert "caveat" not in levers["CGG"]
diff --git a/obr/code/index.html b/obr/code/index.html
index 3716e050..54f8766b 100644
--- a/obr/code/index.html
+++ b/obr/code/index.html
@@ -170,8 +170,8 @@
Shock a variable, read the deviation.
levers — set these (exogenous)
CGGGovernment consumption — spending shocks, £m per quarter
-
CGIPSCentral government investment — public investment, £m per quarter (nominal)
-
TCPROCorporation tax rate — rate, e.g. −0.05 for a 5pp cut
+
CGIPSCentral government investment — public investment, £m per quarter (nominal). Dead channel: business investment does not respond at all and the residual GDP effect is wrong-signed, against the OBR’s published 1.0. Do not score capital spending with it
+
TCPROCorporation tax rate — rate, e.g. −0.05 for a 5pp cut. Does not converge: the response compounds at roughly 25% a quarter with no steady state, so read the sign and the first few quarters, never the level or the cumulative total
HHDI_ADDFACTORExternally costed household reform — virtual instrument, quarterly £m; positive revenue lowers HHDI via a held add-factor
R / GILT / RXBank Rate · gilt yields · exchange rate — monetary & market assumptions
@@ -218,8 +218,8 @@
Shock a variable, read the deviation.
levers — set these (exogenous)
CGGGovernment consumption — spending shocks, £m per quarter
-
CGIPSCentral government investment — public investment, £m per quarter (nominal)
-
TCPROCorporation tax rate — rate, e.g. −0.05 for a 5pp cut
+
CGIPSCentral government investment — public investment, £m per quarter (nominal). Dead channel: business investment does not respond at all and the residual GDP effect is wrong-signed, against the OBR’s published 1.0. Do not score capital spending with it
+
TCPROCorporation tax rate — rate, e.g. −0.05 for a 5pp cut. Does not converge: the response compounds at roughly 25% a quarter with no steady state, so read the sign and the first few quarters, never the level or the cumulative total
HHDI_ADDFACTORExternally costed household reform — virtual instrument, quarterly £m; positive revenue lowers HHDI via a held add-factor
R / GILT / RXBank Rate · gilt yields · exchange rate — monetary & market assumptions
Microsimulation supplies the direct cost; this model returns the feedback.
→Connection. Static-costing bridge, both hosted: PolicyEngine computes the direct Exchequer effect of a statutory reform; score_reform injects it through the declared HHDI_ADDFACTOR point and this model returns the economy-wide second round.
→Worked example. 1p on the basic rate from April 2026: direct yield £6.46bn in 2026; GDP −0.020% on impact, deepening to −0.058% by 2027Q4 (full walkthrough → score a reform).
-
→Validation.Anchored to the March 2026 EFO (0.15% GDP MAPE) — but anchored accuracy is a by-construction invariant, not a forecast claim: free-running, the same equations miss GDP by 5.75%. Checked against HMRC’s ready reckoner (£6.46bn vs £6.9bn).
+
→Validation.Anchored to the March 2026 EFO (0.15% GDP MAPE) — but anchored accuracy is a by-construction invariant, not a forecast claim: free-running, the same equations miss GDP by 4.48%. Checked against HMRC’s ready reckoner (£6.46bn vs £6.9bn).
→Results. Run it from any connected client; every score carries its assumptions and data vintage.
Excellent anchored. Weak free-running. Both published.
free-running
- 5.75% MAPE
+ 4.48% MAPEreal GDP; 4 of 11 computed in band — report-only
@@ -126,7 +126,7 @@
Excellent anchored. Weak free-running. Both published.
Free-running
De-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.
+
Weak, and reported as such. GDP 4.48%, consumption 7.49%, household income 6.27%, business investment 15.73%, company profits 63.29%. The model contracts while the EFO grows.
@@ -282,7 +282,7 @@
Against the OBR's own forecast, and HMRC's reckoner.
@@ -327,35 +327,39 @@
Against the OBR's own forecast, and HMRC's reckoner.
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
+ by 4.48% 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
+ Across the full scorecard 6 of the 11 computed headline
+ variables land within band (real GDP, consumption, both
+ household-income lines, 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 63.29% MAPE on the
+ March baseline, 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%.
+ 3.60% of GDP and is now over band; RPI improved from
+ 2.03pp to 1.71pp; business investment stands at
+ 15.73%; the two household-income lines stand at
+ 6.27% and 6.03%. Every figure in this paragraph moved when the
+ OSHH ONS anchor landed upstream, and this page had gone
+ on publishing the pre-anchor ones — which understated the model's
+ accuracy on every line.
- 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.
+ 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 (4.48% MAPE). Computed from papers/obr-macro/figures/fig_free_running_data.csv and fig_anchored_data.csv, regenerated on 12 August 2026.
Forecast versus outturn. Comparing one forecast
diff --git a/papers/obr-macro/figures/fig_free_running.pdf b/papers/obr-macro/figures/fig_free_running.pdf
index 9c79a02cae310459ff5901bd380b93233419b8f7..c90e865b83c745635b281ca2c0ebd839fc24cf18 100644
GIT binary patch
delta 4069
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
a|I0K23Ps>BY8s3Pn4$s$1fqe~WcU{@!(`n6
delta 3448
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
diff --git a/papers/obr-macro/figures/fig_free_running_data.csv b/papers/obr-macro/figures/fig_free_running_data.csv
index b9dca0f6..ab49d71a 100644
--- a/papers/obr-macro/figures/fig_free_running_data.csv
+++ b/papers/obr-macro/figures/fig_free_running_data.csv
@@ -1,13 +1,13 @@
,GDPM_model,CONS_model,HHDI_model,GDPM_efo,CONS_efo,HHDI_efo
-2025Q1,690992.9612408569,415663.1861982189,395470.8554215652,703435.0,429345.0,483896.0
-2025Q2,690064.4574558692,414284.7278124491,456827.6520898548,704973.0,429692.0,489329.0
-2025Q3,682504.9053481992,407321.4208021562,442651.91540539387,705603.0,431309.0,489031.0
-2025Q4,679217.7429655327,403300.7917593888,450856.9087182827,706873.0854,431735.996,493761.36
-2026Q1,669335.0796969897,392016.10911167425,380694.5880951302,709064.39196474,432363.521,497806.675
-2026Q2,670743.7904103745,392085.3213201746,431117.9577943034,711475.2108974202,433402.28,501643.436
-2026Q3,668452.3099806694,388486.23361647705,431763.6280174015,714178.8166988303,434670.508,504904.318
-2026Q4,666785.4735566229,385390.4601104848,433531.1783981844,717142.6587881305,436147.044,508289.706
-2027Q1,665595.0499860753,383057.54290709173,435672.94582449965,720047.0865562225,437846.999,511577.507
-2027Q2,664683.2112467056,381182.5306251082,438067.19693830475,722869.6711355229,439649.831,514949.525
-2027Q3,663653.83081479,379252.61883668625,440333.4402611372,725688.8628529514,441508.475,518417.48900000006
-2027Q4,663080.9377536196,377752.83704250376,443020.67862184666,728562.5907498491,443414.728,521864.0829999999
+2025Q1,694404.6301766383,419074.85513400025,428871.49559585686,703435.0,429345.0,483896.0
+2025Q2,694493.7051226624,418713.97547924233,492096.16673654737,704973.0,429692.0,489329.0
+2025Q3,688118.2699442019,412934.785398159,479946.9522647471,705603.0,431309.0,489031.0
+2025Q4,685583.2721162075,409666.3209100636,494601.9543338685,706873.0854,431735.996,493761.36
+2026Q1,678334.1920101173,401015.2214248019,427518.02423624403,709064.39196474,432363.521,497806.675
+2026Q2,680495.7841757275,401837.3150855276,472253.41553964955,711475.2108974202,433402.28,501643.436
+2026Q3,678933.6826921239,398967.60632793163,472489.3885262448,714178.8166988303,434670.508,504904.318
+2026Q4,677792.5151012673,396397.50165512913,474696.4595384664,717142.6587881305,436147.044,508289.706
+2027Q1,677128.2532110797,394590.7461320963,477084.4862161981,720047.0865562225,437846.999,511577.507
+2027Q2,676679.6257150497,393178.9450934523,478879.5400344412,722869.6711355229,439649.831,514949.525
+2027Q3,676024.8513143926,391623.6393362889,481110.3763989253,725688.8628529514,441508.475,518417.48900000006
+2027Q4,675802.5728209037,390474.4721097879,483681.23482099245,728562.5907498491,443414.728,521864.0829999999
diff --git a/validation/figures/chart_data.json b/validation/figures/chart_data.json
index a6843e28..29c1792c 100644
--- a/validation/figures/chart_data.json
+++ b/validation/figures/chart_data.json
@@ -1,134 +1,140 @@
{
- "_comment": "Values that are NOT machine-readable in papers/*/figures/ are transcribed here from the papers' LaTeX sources, with an explicit source pointer per value. Everything else is read directly from the papers' committed CSV/JSON data by make_charts.py. Do not edit a number here without changing the cited source.",
- "obr_reform": {
- "source": "papers/obr-macro/sections/comparison.tex, Table tab:comparison panel B (cross-checked against papers/obr-macro/sections/results.tex)",
- "groups": [
- {
- "label": "basic rate +1pp, 2026–27",
- "bars": [
- {
- "name": "ours",
- "value": 6.46,
- "series": 1,
- "source": "comparison.tex panel B / results.tex L18 (PolicyEngine static costing)"
- },
- {
- "name": "HMRC",
- "value": 6.9,
- "series": 2,
- "source": "comparison.tex panel B; HMRC Direct effects of illustrative tax changes, June 2025"
- }
- ]
- },
- {
- "label": "basic rate +1pp, 2028–29",
- "bars": [
- {
- "name": "ours",
- "value": 6.92,
- "series": 1,
- "source": "comparison.tex panel B; interpolated between scored endpoints 6.46 (2026) and 7.38 (2030)"
- },
- {
- "name": "HMRC",
- "value": 8.2,
- "series": 2,
- "source": "comparison.tex panel B; HMRC June 2025 reckoner"
- }
- ]
- }
+ "_comment": "Values that are NOT machine-readable in papers/*/figures/ are transcribed here from the papers' LaTeX sources, with an explicit source pointer per value. Everything else is read directly from the papers' committed CSV/JSON data by make_charts.py. Do not edit a number here without changing the cited source.",
+ "obr_reform": {
+ "source": "papers/obr-macro/sections/comparison.tex, Table tab:comparison panel B (cross-checked against papers/obr-macro/sections/results.tex)",
+ "groups": [
+ {
+ "label": "basic rate +1pp, 2026\u201327",
+ "bars": [
+ {
+ "name": "ours",
+ "value": 6.46,
+ "series": 1,
+ "source": "comparison.tex panel B / results.tex L18 (PolicyEngine static costing)"
+ },
+ {
+ "name": "HMRC",
+ "value": 6.9,
+ "series": 2,
+ "source": "comparison.tex panel B; HMRC Direct effects of illustrative tax changes, June 2025"
+ }
]
- },
- "obr_computed_share": {
- "source": "docs/calibration_scorecard.md in the PolicyEngine/obr-macroeconomic-model repository (March 2026 EFO vintage calibration scorecard); counts cross-checked against papers/obr-macro/sections/calibration.tex Table tab:rawscore (preserved November 2025 study)",
- "total": 21,
- "computed": 11,
- "passthrough": 10,
- "bands_note": "bands: rates ±1.0pp · net balances ±1.5% of GDP · levels ≤10% MAPE",
- "_grades_comment": "The 11 computed variables graded against the bands above. 'identity' is employment, where both inputs are themselves passthrough, so the accounting identity closes trivially and the pass carries no information.",
- "grades": [
- {
- "key": "fair",
- "label": "fair",
- "count": 3,
- "series": 1,
- "in_band": true,
- "examples": [
- "real GDP 5.75 per cent",
- "consumption 9.56 per cent",
- "trade balance 0.70 per cent of GDP"
- ],
- "source": "calibration_scorecard.md, free-running MAPE column"
- },
- {
- "key": "identity",
- "label": "identity",
- "count": 1,
- "series": 2,
- "in_band": true,
- "trivial": true,
- "examples": [
- "employment"
- ],
- "source": "calibration_scorecard.md; employment closes from two passthrough inputs"
- },
- {
- "key": "poor",
- "label": "poor",
- "count": 5,
- "series": 3,
- "in_band": false,
- "examples": [],
- "source": "calibration_scorecard.md, variables outside band but within an order of magnitude"
- },
- {
- "key": "off",
- "label": "off",
- "count": 2,
- "series": 3,
- "in_band": false,
- "examples": [
- "company profits 79.80 per cent",
- "the current account 4.17 per cent of GDP"
- ],
- "source": "calibration_scorecard.md, worst two computed variables"
- }
+ },
+ {
+ "label": "basic rate +1pp, 2028\u201329",
+ "bars": [
+ {
+ "name": "ours",
+ "value": 6.92,
+ "series": 1,
+ "source": "comparison.tex panel B; interpolated between scored endpoints 6.46 (2026) and 7.38 (2030)"
+ },
+ {
+ "name": "HMRC",
+ "value": 8.2,
+ "series": 2,
+ "source": "comparison.tex panel B; HMRC June 2025 reckoner"
+ }
]
- },
- "frbus_residuals": {
- "source": "papers/frb-us/sections/validation.tex, Tables tab:tracking and tab:refnoise",
- "rows": [
- {
- "label": "ours vs LONGBASE (identity — not evidence)",
- "value": 5.6e-17,
- "series": 1,
- "source": "validation.tex tab:tracking, max abs error",
- "note": "init_trac defines add-factors as minus the residuals at the input data, so re-solving reproduces it up to float64 for ANY input — verified: the same test passes at 6.7e-9 on randomly scrambled data. Shown for completeness; it measures arithmetic, not economics."
- },
- {
- "label": "pyfrbus 1.1.1 vs LONGBASE",
- "value": 1.1e-08,
- "series": 3,
- "source": "validation.tex subsec:refnoise prose"
- },
- {
- "label": "ours vs pyfrbus 1.0.0 (shock)",
- "value": 6e-09,
- "series": 1,
- "source": "validation.tex tab:refnoise row 1"
- },
- {
- "label": "ours vs pyfrbus 1.1.1 (shock)",
- "value": 1.4e-08,
- "series": 1,
- "source": "validation.tex tab:refnoise row 2"
- },
- {
- "label": "pyfrbus 1.1.1 vs 1.0.0 — the Fed’s own two releases",
- "value": 1.3e-08,
- "series": 2,
- "source": "validation.tex tab:refnoise row 3"
- }
- ]
- }
+ }
+ ]
+ },
+ "obr_computed_share": {
+ "source": "docs/calibration_scorecard.md in the PolicyEngine/obr-macroeconomic-model repository, regenerated 2026-08-12 after the OSHH ONS anchor landed (PR #22); the previous figures here predated that anchor and understated the model's accuracy across the board",
+ "total": 21,
+ "computed": 11,
+ "passthrough": 10,
+ "bands_note": "bands: rates \u00b11.0pp \u00b7 net balances \u00b11.5% of GDP \u00b7 levels \u226410% MAPE",
+ "_grades_comment": "The 11 computed variables graded against the bands above. 'identity' is employment, where both inputs are themselves passthrough, so the accounting identity closes trivially and the pass carries no information.",
+ "grades": [
+ {
+ "key": "fair",
+ "label": "fair",
+ "count": 5,
+ "series": 1,
+ "in_band": true,
+ "examples": [
+ "real GDP 4.48 per cent",
+ "consumption 7.49 per cent",
+ "household income 6.27 per cent",
+ "real household income 6.03 per cent",
+ "trade balance 0.69 per cent of GDP"
+ ],
+ "source": "calibration_scorecard.md, free-running MAPE column"
+ },
+ {
+ "key": "identity",
+ "label": "identity",
+ "count": 1,
+ "series": 2,
+ "in_band": true,
+ "trivial": true,
+ "examples": [
+ "employment"
+ ],
+ "source": "calibration_scorecard.md; employment closes from two passthrough inputs"
+ },
+ {
+ "key": "poor",
+ "label": "poor",
+ "count": 3,
+ "series": 3,
+ "in_band": false,
+ "examples": [
+ "business investment 15.73 per cent",
+ "the unemployment rate 1.01 percentage points",
+ "RPI inflation 1.71 percentage points"
+ ],
+ "source": "calibration_scorecard.md, variables outside band but within an order of magnitude"
+ },
+ {
+ "key": "off",
+ "label": "off",
+ "count": 2,
+ "series": 3,
+ "in_band": false,
+ "examples": [
+ "company profits 63.29 per cent",
+ "the current account 3.60 per cent of GDP"
+ ],
+ "source": "calibration_scorecard.md, worst two computed variables"
+ }
+ ]
+ },
+ "frbus_residuals": {
+ "source": "papers/frb-us/sections/validation.tex, Tables tab:tracking and tab:refnoise",
+ "rows": [
+ {
+ "label": "ours vs LONGBASE (identity \u2014 not evidence)",
+ "value": 5.6e-17,
+ "series": 1,
+ "source": "validation.tex tab:tracking, max abs error",
+ "note": "init_trac defines add-factors as minus the residuals at the input data, so re-solving reproduces it up to float64 for ANY input \u2014 verified: the same test passes at 6.7e-9 on randomly scrambled data. Shown for completeness; it measures arithmetic, not economics."
+ },
+ {
+ "label": "pyfrbus 1.1.1 vs LONGBASE",
+ "value": 1.1e-08,
+ "series": 3,
+ "source": "validation.tex subsec:refnoise prose"
+ },
+ {
+ "label": "ours vs pyfrbus 1.0.0 (shock)",
+ "value": 6e-09,
+ "series": 1,
+ "source": "validation.tex tab:refnoise row 1"
+ },
+ {
+ "label": "ours vs pyfrbus 1.1.1 (shock)",
+ "value": 1.4e-08,
+ "series": 1,
+ "source": "validation.tex tab:refnoise row 2"
+ },
+ {
+ "label": "pyfrbus 1.1.1 vs 1.0.0 \u2014 the Fed\u2019s own two releases",
+ "value": 1.3e-08,
+ "series": 2,
+ "source": "validation.tex tab:refnoise row 3"
+ }
+ ]
+ }
}
From 47ee1dbaa246d92af5497286c15ba0fcbb3b038c Mon Sep 17 00:00:00 2001
From: vahid-ahmadi
Date: Wed, 12 Aug 2026 16:12:42 +0100
Subject: [PATCH 2/2] Re-pin the OBR emulator to the merged review
PolicyEngine/obr-macroeconomic-model#27: the multiplier documentation,
the TCPRO domain guard, the transmission-audit fix and the corrected
WB/WP/WV/RDELTA seeds.
Co-Authored-By: Claude Opus 5 (1M context)
---
integration/modal_app.py | 2 +-
integration/pyproject.toml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/integration/modal_app.py b/integration/modal_app.py
index cd267f7d..5cafb30b 100644
--- a/integration/modal_app.py
+++ b/integration/modal_app.py
@@ -129,7 +129,7 @@
BOE_URL = "https://github.com/PolicyEngine/boe-var-model"
FRB_URL = "https://github.com/PolicyEngine/us-frb-model"
HANK_URL = "https://github.com/PolicyEngine/us-hank-model"
-OBR_REVISION = "85ada9be070a34a4a4a0a65d92f8f148a476ccb1"
+OBR_REVISION = "9c4b595f3b60b305459ba8c91eac5bc69fd0ffc8"
BOE_REVISION = "42cbcbb728b55b3f416764568fa8f3aef68a6730"
FRB_REVISION = "2508a2d3ee7d078fd82f38bc17567e4433489913"
HANK_REVISION = "19a8e5e5cd8e7120fbbd2e3de3c5b63509824f3c"
diff --git a/integration/pyproject.toml b/integration/pyproject.toml
index b8a909e9..1bb8d538 100644
--- a/integration/pyproject.toml
+++ b/integration/pyproject.toml
@@ -49,7 +49,7 @@ Issues = "https://github.com/PolicyEngine/macro/issues"
# particular, frbus includes model.xml and LONGBASE.TXT inside its wheel, so a
# clean non-editable installation is a supported access path.
models = [
- "obr-macro-model @ git+https://github.com/PolicyEngine/obr-macroeconomic-model@85ada9be070a34a4a4a0a65d92f8f148a476ccb1",
+ "obr-macro-model @ git+https://github.com/PolicyEngine/obr-macroeconomic-model@9c4b595f3b60b305459ba8c91eac5bc69fd0ffc8",
"boe_var @ git+https://github.com/PolicyEngine/boe-var-model@42cbcbb728b55b3f416764568fa8f3aef68a6730",
"frbus @ git+https://github.com/PolicyEngine/us-frb-model@2508a2d3ee7d078fd82f38bc17567e4433489913",
"us-hank-model @ git+https://github.com/PolicyEngine/us-hank-model@19a8e5e5cd8e7120fbbd2e3de3c5b63509824f3c",