From 3ea6b614d6c687eb902efacab83db797c3452fdb Mon Sep 17 00:00:00 2001
From: vahid-ahmadi
Date: Wed, 12 Aug 2026 14:09:24 +0100
Subject: [PATCH] Fix what five page-level audits found, including two
self-contradictions
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The five section audits killed when the machine slept were rerun. Their
findings, worst first.
/forecasts headlined an accuracy number, which is the one thing its own
generator says it must never do. The record band's third tile read "0.12pp —
latest absolute error", level with the round and scored counts, while
score.py's module docstring argues that "a track record that reports an
impressive figure derived from one observation is worse than an empty one",
and the disclaimer saying so sat below the tile. The tile now shows the next
scored period. The error still appears on the scored card, beside its
benchmarks.
The same page called its one favourable result "beats naive" using the
driftless walk that its own section 06 calls too weak for a trending series.
The footer now names the benchmark: "beats driftless-naive error".
frb-us/index promised a "shock explorer on this page". There is none, on
that page or anywhere on the site — the page contains no form, input or
button. A reader was told to do something impossible.
/economy carried the only hand-typed live number on the page, and it had
already rotted: "the model expects it back above 3% by Q4", from the
2026-07-21 round, against a current round putting 2026Q4 at 2.94% and an
inflation topic one click away saying not until 2027Q2. It now describes the
note as the dated artifact it is and points at the live view.
svar/index cited the 49-origin rolling evaluation as a validation
credential. That study is precisely what found the model no better than
naive: against a random walk with drift, UK GDP is not statistically
distinguishable (p=0.38-0.67). The page published a GDP fan with no
qualifier. Both now sit in the fact.
obr/index presented 0.15% anchored MAPE as validation. Anchored accuracy is
a by-construction invariant — the page's own validation page says so — and
free-running the same equations miss by 5.75%. Both now stated.
Every equation and code block on fourteen pages was invisible to screen
readers. .meth-inline and .scrolly-sticky are a responsive pair, exactly one
visible per viewport, and both carried aria-hidden="true" — so in every
viewport the only visible copy was hidden from assistive technology. On
pe/code and define/code, whose entire subject is the commands, not one was
exposed. 58 blocks across 14 pages now are; the copy buttons in the hidden
aside are no longer focusable.
The sticky column blanked through step 1 on five pages, and that one is
mine: my simulation of the data-step pairing skipped the active === 0 case,
which is exactly the case that fails. Panels now pin from the first
available step.
models/index called all 284 FRB/US equations behavioural; the paper says 120
behavioural and 164 identities. It counted seven model classes where there
are six — obr-macro and frb-us share one. Its HMRC link said June 2025 and
pointed at the January bulletin. And it attached "machine precision" to the
~1e-8 cross-validation figure, which is solver tolerance, not the 5.6e-17
tracking invariant.
/connect said "everything else here runs hosted"; the three define_* tools
return run instructions, as the unlicensed upstream is never hosted.
/models#score landed on a closed accordion because the id sits on the
element, which browsers do not auto-expand — breaking the primary
"score a reform" journey from three high-traffic links. reveal.js now opens
a details element a fragment targets.
olg/validation published debt-to-GDP as 95.1% for May 2026. The committed
store says May is 94.8 and June 94.9, and the site's own economy page says
94.9. Corrected, with the deviation sign flipped to match.
And the DEFINE clean-room status was stale everywhere, which is my doing: I
merged §3.3.1 and §3.3.2 today and updated neither the site nor the
registry. Four surfaces said "§3.2, 23 equations, three defects" against a
reality of 118 equations and some thirty-five. The §3.3.2 blocker — six
untabulated parameters and two variables defined nowhere, so the section as
published cannot be simulated forward — was published nowhere at all, while
the same pages call the clean room the path to hosting. Now stated.
One audit finding was wrong and is not acted on: define/validation cites
upstream issue DEFINE_UK_1.1#1 for the emissions divergence, flagged as
unsupported. The issue exists, is open, and its title matches the finding
exactly. Verified before deleting anything.
Twelve gates, 1038 site tests, 274 integration tests.
Co-Authored-By: Claude Opus 5 (1M context)
---
connect/index.html | 2 +-
define/code/index.html | 24 ++++++++-----
define/methodology/index.html | 12 +++----
economy/index.html | 5 ++-
forecasts/index.html | 4 +--
forecasts/score.py | 26 ++++++++++----
frb-us/code/index.html | 20 +++++++----
frb-us/index.html | 4 +--
frb-us/methodology/index.html | 12 +++----
.../src/policyengine_macro/capabilities.py | 3 +-
models/index.html | 6 ++--
obr/code/index.html | 20 +++++++----
obr/index.html | 2 +-
obr/methodology/index.html | 10 +++---
olg/code/index.html | 34 +++++++++++--------
olg/methodology/index.html | 22 +++++++-----
olg/validation/index.html | 2 +-
pe/code/index.html | 20 +++++++----
pe/methodology/index.html | 14 +++++---
reports/define-uk-replication/index.html | 14 ++------
reveal.js | 25 ++++++++++++++
svar/code/index.html | 20 +++++++----
svar/index.html | 2 +-
svar/methodology/index.html | 10 +++---
us-hank/code/index.html | 18 ++++++----
us-hank/methodology/index.html | 16 ++++++---
26 files changed, 220 insertions(+), 127 deletions(-)
diff --git a/connect/index.html b/connect/index.html
index 512153aa..2c3a88ef 100644
--- a/connect/index.html
+++ b/connect/index.html
@@ -372,7 +372,7 @@
Connect in three steps.
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.
+ The three define_* tools likewise return run instructions rather than results when called on the hosted server — the unlicensed upstream is never hosted. Everything else here runs hosted.
diff --git a/define/code/index.html b/define/code/index.html
index c8bc13fd..362c74eb 100644
--- a/define/code/index.html
+++ b/define/code/index.html
@@ -103,7 +103,7 @@
Where to read the results
2
CLI scenario runs
Deltas from the terminal
The CLI lists the curated scenarios and runs any of them locally: pe-macro define-scenarios to browse, pe-macro define-scenario to produce a scenario's deltas vs baseline — real GDP, emissions, unemployment, real consumption. Deltas only, never levels.
-
+
CLI commands
pe-macro define-scenarios # list the curated set
pe-macro define-scenario <name> # scenario deltas vs baseline
@@ -116,7 +116,7 @@
One pip install, upstream fetched at runtime
The adapter installs from GitHub. Nothing upstream is vendored: at runtime it fetches DEFINE-model/DEFINE_UK_1.1 at pinned commit 846081a and executes the published notebook unmodified.
R does the heavy lifting once
A fresh full run needs local R with the notebook's packages; after that, scenario deltas are instant from the cached pinned run.
The test suite carries the validation gate: hermetic artifact checks always run, and the oracle comparisons against the pinned upstream run execute wherever a local cache exists — in CI they must skip, because a passing oracle test there would mean the unlicensed upstream was fetched.
The path to hosting
a clean-room Python reimplementation of the published Model Manual v1.1 is the route to a hostable model — protocol and milestones in REIMPLEMENTATION.md, upstream code used only as a numerical oracle.
-
Two slices have landed. The §2.2 accounting core — the transactions and balance-sheet matrices — is satisfied identically at the manual's own initial values. §3.2 adds the 23 high-level macroeconomic equations, Eqs. (21)–(43): the demand-determined expenditure core, the Kaldor–Verdoorn productivity relation, and the Leontief labour and capital supply constraints. §3.3 production and the comparison against the upstream oracle are outstanding, so nothing here is hostable yet and the hosted tools continue to return run instructions.
-
Reimplementing from the manual is also what surfaces its gaps. Three are recorded so far: Table 6 tabulates a net-lending residual the manual itself says “should equal 0”; Eq. (27) does not hold at the manual's own initial values, and disagrees with Eq. (25) about the same variable; and Eq. (31) uses an intercept that Table 5 never tabulates. Each is pinned by a test rather than absorbed into a tolerance, so a later manual revision fails loudly instead of passing quietly.
-
+
Four slices have landed. The §2.2 accounting core — the transactions and balance-sheet matrices — is satisfied identically at the manual's own initial values. §3.2 adds the high-level macroeconomic block, §3.3.1 domestic production, and §3.3.2 the power generation sector: 118 equations, Eqs. (21)–(138), contiguous. §3.3.3 input–output and the comparison against the upstream oracle are outstanding, so nothing here is hostable yet and the hosted tools continue to return run instructions.
+
Reimplementing from the manual is also what surfaces its gaps, and roughly thirty-five are now pinned by tests rather than absorbed into a tolerance — a net-lending residual the manual itself says “should equal 0”, identities that fail at the manual's own initial values, and symbols the equations use that the parameter tables never list. The largest is a blocker: §3.3.2 as published cannot be simulated forward. Six parameters it uses are absent from Table 5, and two variables are never defined anywhere in the manual, so credit rationing collapses to a constant and desired power-sector investment turns negative. That is the section the climate-policy scenarios run through, and it is recorded in full for the authors.
+
development
git clone https://github.com/PolicyEngine/define-uk-model
cd define-uk-model
@@ -142,18 +142,18 @@
The path to hosting
CLI scenario runsCode 2
CLI commands
-
pe-macro define-scenarios # list the curated set
+
pe-macro define-scenarios # list the curated set
pe-macro define-scenario <name> # scenario deltas vs baseline
git clone https://github.com/PolicyEngine/define-uk-model
cd define-uk-model
pytest # hermetic gate everywhere; oracle tests need the local cache
@@ -205,7 +205,13 @@
The path to hosting
});
// Panels pair to steps by data-step. A step with no panel of its own
// keeps the nearest earlier panel pinned rather than blanking the column.
- var want = -1;
+ // Start at the FIRST panel, not -1: several pages have no panel for
+ // step 0, and seeding -1 left the sticky column empty for the whole of
+ // the opening step before popping in. Pinning the earliest panel means
+ // the column is populated from the top and only ever moves forward.
+ var want = Math.min.apply(null, panels.map(function (panel) {
+ return Number(panel.getAttribute("data-step"));
+ }));
panels.forEach(function (panel) {
var ds = Number(panel.getAttribute("data-step"));
if (ds <= active && ds > want) want = ds;
diff --git a/define/methodology/index.html b/define/methodology/index.html
index 416c3dd6..5774294c 100644
--- a/define/methodology/index.html
+++ b/define/methodology/index.html
@@ -132,7 +132,7 @@
Accounting before theory
DEFINE-UK is an ecological stock-flow consistent (E-SFC) model: DEFINE-UK 1.1, April 2026, by George, Dafermos, Nikolaidi and co-authors (define-model.org). Stock-flow consistency is an accounting discipline before it is an economic theory. Every monetary flow in the model — wages, taxes, interest, dividends, lending — leaves one sector's account and enters another's, so a transactions matrix sums to zero in every row and column; and every flow accumulates into a stock, so sector balance sheets close each period. Nothing appears from nowhere and nothing leaks out.
The ecological coupling
The ecological part couples that closed monetary accounting to an emissions and energy block, so climate policy and the financial system move together in one system. An emissions block splits electricity from non-electricity emissions; the components sum exactly to the total in the model's output.
-
+
matrix identities (manual §2.2)
flow rows
\(\sum_{s} T_{f,s} = 0\) — every flow leaves one sector and enters another
sector columns
\(\sum_{f} T_{f,s} = \mathrm{LEND}_s\) — column sums give net lending
@@ -173,7 +173,7 @@
The sectors
Households, firms, monetary financial institutions (MFIs), non-monetary financial institutions, government, the central bank, and the rest of the world — each with a full balance sheet. The balance-sheet matrix (manual §2.2 Table 2) mirrors the transactions matrix: every financial instrument row sums to zero — one sector's asset is another's liability — and each sector's column sums to its net worth.
Calibration
The model is calibrated to UK national accounting data (ONS Blue Book / UKEA), with the manual's §5 tables giving parameters and initial values.
-
+
balance-sheet identities (manual §2.2 Table 2)
instrument rows
\(\sum_{s} B_{i,s} = 0\) — one sector's asset is another's liability
sector columns
financial rows sum to \(\mathrm{FNW}_s\); adding real assets gives \(\mathrm{NW}_s\)
@@ -212,7 +212,7 @@
Power
The power-sector block carries separate fossil and renewable capital stocks: government renewable investment is triggered when non-fossil capacity falls short of a utilisation threshold relative to electricity demand, and regulation policies (new in version 1.1) can directly depreciate fossil capital — stranded assets as an explicit balance-sheet event, felt by the financial institutions that hold the claims.
What version 1.1 added
Version 1.1 (April 2026) added regulatory policies, limited forward-looking behaviour to policy announcements, direct government ownership of non-fossil power capital, and a green sovereign bond instrument.
-
+
the two UK blocks
housing
stock tracked by energy efficiency; retrofit policy works through the stock
power
separate fossil and renewable capital stocks; utilisation threshold triggers renewable investment
@@ -244,7 +244,7 @@
The closure
DEFINE-UK is closed demand-led: output adjusts to aggregate demand rather than being pinned to a supply-side potential path. That is the standard post-Keynesian SFC closure, and it has a direct, observable consequence — fiscal expansions are not crowded out by an assumed return to potential, so fiscal multipliers come out larger than in supply-constrained models like the OBR emulator.
Measured, not asserted
We measure this rather than assert it: our own computation of the model's cumulative green-public-investment multiplier is 1.78, against IMF green-spending estimates of 1.1–1.5 and an OBR capital-spending impact multiplier of ≈1.0. Above the official range, but explicably so given the closure — and every surface that quotes a DEFINE-UK delta carries the demand-led caveat. The full comparison is on the validation page.
-
+
closure and multipliers
closure
demand-led: \(Y\) adjusts to aggregate demand, not a potential path
What that gate does and does not establish is spelled out on the
validation page.
-
+
the switch sets
fossil-fuel ban
ban flag + its capacity and investment timings
power subsidy
POWER_SUB alone
@@ -318,7 +318,7 @@
Deltas only, local only
pe-macro define-scenarios and pe-macro define-scenario return annualised scenario-minus-baseline delta paths only, framed with mandatory caveats — never levels, because the baseline's calibration gap to ONS/DESNZ/OBR actuals is computed and material. The hosted MCP tools return run instructions instead of results, because the unlicensed upstream is never hosted. score_reform will not accept this model.
The clean-room path to hosting
The path to hosting is a clean-room Python reimplementation of the published Model Manual — the same adapted-model approach as the OBR emulator. The protocol: the manual is the specification, and the upstream run is a numerical oracle only — its outputs are compared against ours, but its source code is never read to write equations, and every equation must carry a reference to the manual section and equation number it implements. Five milestones run from the accounting core to the full policy scenarios; milestone 1 — the §2.2 transactions and balance-sheet matrices on the §5 initial values — passed with 27 identity tests, including one documented inconsistency in the manual itself (Table 6 omits a dividend term of Eq. 383 from the interbank-lending row, which the tests pin exactly rather than paper over).
-
+
adapter architecture
pinned commit
runtime fetch at 846081a; executed unmodified, in R; nothing vendored or hosted
cached run
R 4.3.0 · 151 output files across all four scenario blocks
Six topics, each with its data, its model, and its limits.
Research note · 25 July 2026
- UK CPI fell to 2.8% in 2026Q2—and the model expects it back above 3% by Q4.
+ UK CPI fell to 2.8% in 2026Q2. The note reads that release through the
+ 2026-07-21 round; the current round is more subdued, and
+ the inflation topic carries the
+ live model view.
Read the CPI note →All notes
3Archived roundscommitted to Git before the outturn existed1Scored periodquarters whose outturn is now published and scored
- 0.12ppLatest absolute errorlatest forecast vs its outturn, percentage points
+ 2026Q2Next scored periodwhen the record next grows, not how it is doing
Next due: 2026Q2 (UK real GDP, y/y, unemployment), which lands when the ONS publishes it.
@@ -124,7 +124,7 @@
Scored so far: one round.
Naive3.10%
Official2.14%
-
+
diff --git a/forecasts/score.py b/forecasts/score.py
index 60d4d95f..21148cd9 100644
--- a/forecasts/score.py
+++ b/forecasts/score.py
@@ -549,9 +549,8 @@ def render_status(card: dict) -> str:
for entry in detail["entries"]
]
latest_entry = sorted(entries, key=lambda entry: entry["period"])[-1] if entries else None
- latest_error = (
- f"{latest_entry['abs_error']:.2f}pp" if latest_entry else "Awaiting outturn"
- )
+ pending_card = card.get("pending_detail") or {}
+ scored_target = esc(pending_card.get("period") or "—")
lines = [
'
',
f" {rounds}Archived "
@@ -560,9 +559,16 @@ def render_status(card: dict) -> str:
f" {scored}Scored "
f"period{'' if scored == 1 else 's'}"
"quarters whose outturn is now published and scored",
- f" {latest_error}Latest absolute "
- "error"
- "latest forecast vs its outturn, percentage points",
+ # NOT the latest error. This module's whole argument is that a
+ # headline accuracy figure derived from one observation is worse than
+ # none, and putting it in the record band — level with the round and
+ # scored counts — was the page doing exactly that, with the "one
+ # scored period is not an accuracy headline" disclaimer underneath it.
+ # The error still appears on the scored card in §02, where it sits
+ # beside its benchmarks and its caveats.
+ f" {scored_target}Next scored "
+ "period"
+ "when the record next grows, not how it is doing",
"
",
'
',
]
@@ -626,8 +632,14 @@ def render_results(card: dict) -> str:
if naive is not None:
marks.append(("Naive", naive, "naive", "up"))
verdict = "beats" if e["beats_naive"] else "does not beat"
+ # Name the benchmark. This is the DRIFTLESS walk, and §06 of the
+ # page says plainly that against a random walk WITH drift the
+ # advantage largely evaporates. Reporting a win against the weaker
+ # benchmark without saying which one it is was the page's single
+ # favourable data point resting on the comparison its own
+ # validation calls too weak for a trending series.
naive_footer = (
- f"{verdict} naive error "
+ f"{verdict} driftless-naive error "
f"{e['naive_abs_error']:.2f}pp"
)
official_footer = ""
diff --git a/frb-us/code/index.html b/frb-us/code/index.html
index 90584fe4..f5db6dd0 100644
--- a/frb-us/code/index.html
+++ b/frb-us/code/index.html
@@ -96,7 +96,7 @@
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.
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.
-
+
clone and install
git clone https://github.com/PolicyEngine/us-frb-model
cd us-frb-model
@@ -159,7 +159,7 @@
Series you will reach for
rff
Federal funds rate
endogenous under the rule, pp
-
+
monetary_policy_shock.py
import pandas as pd
from frbus import Frbus, load_data
@@ -189,21 +189,21 @@
import pandas as pd
from frbus import Frbus, load_data
data = load_data("vendor/data_only_package/LONGBASE.TXT")
@@ -272,7 +272,13 @@
Series you will reach for
});
// Panels pair to steps by data-step. A step with no panel of its own
// keeps the nearest earlier panel pinned rather than blanking the column.
- var want = -1;
+ // Start at the FIRST panel, not -1: several pages have no panel for
+ // step 0, and seeding -1 left the sticky column empty for the whole of
+ // the opening step before popping in. Pinning the earliest panel means
+ // the column is populated from the top and only ever moves forward.
+ var want = Math.min.apply(null, panels.map(function (panel) {
+ return Number(panel.getAttribute("data-step"));
+ }));
panels.forEach(function (panel) {
var ds = Number(panel.getAttribute("data-step"));
if (ds <= active && ds > want) want = ds;
diff --git a/frb-us/index.html b/frb-us/index.html
index f1adc7af..df15b8f5 100644
--- a/frb-us/index.html
+++ b/frb-us/index.html
@@ -80,9 +80,9 @@
Its labour-market path tells the microsimulation who bears a shock.
→Connection. Shock-to-incidence bridge: frbus_shock_incidence scales the US microsimulation's employment income with the wage and hours deviations a shock produces. Uniform scaling — an experimental illustration, and labelled as such. score_reform refuses model="frbus": no mapping from statute exists.
-
→Worked example. The 100bp tightening above moves the 2027 wage bill −0.65%; the microsimulation then shows how much the federal and state tax system absorbs and what SNAP and the EITC give back, decile by decile.
+
→Worked example. The 100bp tightening below moves the 2027 wage bill −0.65%; the microsimulation then shows how much the federal and state tax system absorbs and what SNAP and the EITC give back, decile by decile.
The parse stage reads model.xml directly: variables, equations, per-equation coefficients, and the endogenous / exogenous classification for the VAR-expectations ("standard") equation set — the only equation set this pipeline reads. 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\).
-
+
parse
system
\(284\) endogenous eqs (behavioural + identities), from model.xml
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 to machine precision — an invariant checked on the validation page. 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.
-
+
LONGBASE conditioning
tracking residual
\(\text{trac}_i = -F_i(\text{baseline})\) per equation, per quarter
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.
-
+
Newton iteration
step
\(J(x^{(k)})\,\Delta x = -F(x^{(k)})\), sparse LU (SuperLU)
damping
halve \(\Delta x\) until \(F, J\) evaluate cleanly
@@ -251,10 +251,10 @@
One block, not many
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.
+
The choice is usually the economic point of the exercise. Run the same government-purchases shock (1% of GDP) under both settings and the year-two multiplier moves from 0.64 under the inertial Taylor rule to 0.99 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. The shock term \(\varepsilon_t\) in the inertial-Taylor rule above is rffintay_aerr — the instrument a monetary shock moves — and it works only under inertial_taylor; asking for it under taylor, which applies the same reaction bracket without the inertia term and reads rfftay_aerr instead, 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.
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 (lur) peaking +0.26pp, core inflation (picxfe) 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.
-
+
impulse responses
shock
\(\text{aerr} \mathrel{+}= \text{shock}\) on the add-factored baseline
response
\(\text{IRF}_t = \text{sim}_t - \text{baseline}_t\) over the same window
diff --git a/integration/src/policyengine_macro/capabilities.py b/integration/src/policyengine_macro/capabilities.py
index a5003aeb..bd0d8b30 100644
--- a/integration/src/policyengine_macro/capabilities.py
+++ b/integration/src/policyengine_macro/capabilities.py
@@ -541,7 +541,8 @@ def _quality(level: str, evidence: str, next_gate: str) -> dict:
"The upstream R code runs unmodified at pinned commit 846081a and "
"the manual's Table 4 macro block replicates within stated "
"tolerances. The clean-room Python reimplementation has landed "
- "§2.2 accounting and §3.2's 23 equations, and surfaced three "
+ "§2.2 accounting and §3.2, §3.3.1 and §3.3.2 — 118 equations, "
+ "Eqs. (21)-(138) contiguous — and surfaced some thirty-five "
"defects in the manual itself, each pinned by a test rather than "
"absorbed into a tolerance.",
"Complete §3.3 and the oracle comparison, closing milestone 2.",
diff --git a/models/index.html b/models/index.html
index 9d92d5d5..23f7ec13 100644
--- a/models/index.html
+++ b/models/index.html
@@ -118,7 +118,7 @@
Choose the model that fits the quest
A monetary or fiscal shock in the US over coming quartersUS · frb-us
-
The Federal Reserve’s FRB/US model — 284 behavioural equations parsed from the Board’s own model file — is solved by damped Newton around the April 2026 LONGBASE baseline; you pick the lever and the policy rule, and read the deviation path quarter by quarter.
+
The Federal Reserve’s FRB/US model — 284 equations — 120 estimated behavioural equations and 164 identities — parsed from the Board’s own model file — is solved by damped Newton around the April 2026 LONGBASE baseline; you pick the lever and the policy rule, and read the deviation path quarter by quarter.
→Levers: federal funds rate, government spending, transfers, taxes — each in its own units
→Replicates the Board’s dynamics to machine precision (residuals ~1e-8 or better)
@@ -193,7 +193,7 @@
Choose the model that fits the quest
compare the models
-
Seven model classes, side by side.
+
Seven models, side by side, side by side.
@@ -313,7 +313,7 @@
How each model is validated.
Cost of cutting the basic rate 1p, 2026–27 (obr-macro + pe)
from obr_macro import run_reform
# £5bn/yr government spending shock (£1.25bn per quarter)
results = run_reform(
@@ -269,7 +269,13 @@
Shock a variable, read the deviation.
});
// Panels pair to steps by data-step. A step with no panel of its own
// keeps the nearest earlier panel pinned rather than blanking the column.
- var want = -1;
+ // Start at the FIRST panel, not -1: several pages have no panel for
+ // step 0, and seeding -1 left the sticky column empty for the whole of
+ // the opening step before popping in. Pinning the earliest panel means
+ // the column is populated from the top and only ever moves forward.
+ var want = Math.min.apply(null, panels.map(function (panel) {
+ return Number(panel.getAttribute("data-step"));
+ }));
panels.forEach(function (panel) {
var ds = Number(panel.getAttribute("data-step"));
if (ds <= active && ds > want) want = ds;
diff --git a/obr/index.html b/obr/index.html
index 0e45db24..24cfcc3a 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, CI-gated) and 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 5.75%. 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.
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.
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 ships inside the package — there is no download step — and it gives the circular fiscal and financial sub-blocks real balancing data rather than guessed starting values.
-
+
data sources
forecast tables
OBR detailed forecast tables, March 2026 EFO
ONS snapshot
348 vendored series; 199 of 261 unprinted variables matched
@@ -201,7 +201,7 @@
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.
-
+
solver
within quarter
cycle → update → repeat to convergence
across quarters
sequential forward solve
@@ -237,7 +237,7 @@
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.
-
+
anchoring
add-factor window
fit 2024Q1–2025Q4, hold constant, project to 2027Q4
scenario solves
12 quarters, 2025Q1–2027Q4
@@ -274,7 +274,7 @@
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.