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/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", 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
diff --git a/obr/index.html b/obr/index.html index 2f246c5d..6061c1f7 100644 --- a/obr/index.html +++ b/obr/index.html @@ -82,7 +82,7 @@

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.

diff --git a/obr/validation/index.html b/obr/validation/index.html index c1f265f3..104f106b 100644 --- a/obr/validation/index.html +++ b/obr/validation/index.html @@ -104,7 +104,7 @@

Excellent anchored. Weak free-running. Both published.

free-running - 5.75% MAPE + 4.48% MAPE real 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.

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. +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, 5 are fair, 1 is an identity, 3 are poor, 2 are off. 6 of the 11, or 55 per cent, land within band, and one of those is a trivial accounting identity, so only 5 non-trivial computed variables are in band. The worst are company profits 63.29 per cent and the current account 3.60 per cent of GDP. 21 headline scorecard variables @@ -291,15 +291,15 @@

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

of which, the 11 the model computes - -fair 3 - -identity 1 - -poor 5 + +fair 5 + +identity 1 + +poor 3 off 2 -Only 3 of 11 non-trivial variables are in band. +Only 5 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 @@ -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.

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. +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 694.4 to 675.8 — a gap that widens to 53 billion pounds, 4.48 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) +free-running (4.48% MAD) EFO Mar 2026 660 @@ -369,13 +373,13 @@

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

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.
+
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 9c79a02c..c90e865b 100644 Binary files a/papers/obr-macro/figures/fig_free_running.pdf and b/papers/obr-macro/figures/fig_free_running.pdf differ 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" + } + ] + } }