UK · what the platform produces
Score a reform. Then ask what it does to the economy.
The microsimulation is the engine: a reform goes in, and household incomes, revenue and a distribution come out. The macro models are what it cannot do alone — a second round, a general-equilibrium overlay, a forecast. Every forecast here is archived with a timestamp before the outturn is published, kept immutable, and scored against realised data, whatever that shows.
Six outputs, and the models behind each one.
The engine underneath this section is pe-microsim — PolicyEngine's own microsimulation, the only member whose question types include household and the only one that covers both countries. It applies a reform to household microdata and reports taxes, benefits, net_income, revenue, distribution over a single policy year. The only two members that report a distribution are pe-microsim and psl-og+microsim, and the second is an overlay that runs the first.
The macro members are what it cannot do on its own. pe-microsim cannot answer GDP, inflation, interest rates and macro feedback — so every macro number in this section comes from a member that produces one, and the table says which, in the order they run. Four of the six routes below put pe-microsim in the chain; two are a macro model standing alone, which is also a fine thing to publish.
| What you get | Models, in the order they run | What the chain does | Command |
|---|---|---|---|
| One household, exactly | pe-microsim | arithmetic over the statutory rules; no sampling, no weights | pe-macro household-impact --country uk --people '[{"age":35,"employment_income":50000}]' --reform '{"gov.hmrc.income_tax.rates.uk[0].rate":0.21}' |
| The whole population, one policy year | pe-microsim | revenue and distribution; a static costing | pe-macro score --country uk --reform '{"gov.hmrc.income_tax.rates.uk[0].rate":0.21}' --model microsim |
| The same reform, with a macro second round | pe-microsim → obr-macro | the static costing becomes a held add-factor on household income | pe-macro score --country uk --reform '{"gov.hmrc.income_tax.rates.uk[0].rate":0.21}' --model obr |
| The same reform, with long-run general equilibrium | psl-og → pe-microsim | steady-state earnings ratio scales the microsim's income inputs | pe-macro dynamic-score --reform '{"gov.hmrc.income_tax.rates.uk[0].rate":0.21}' |
| A GDP and CPI forecast, standing alone | boe-svar | medians with 68% and 90% bands; archived and scored below | pe-macro forecast --horizons 8 |
| That forecast carried into household incomes | boe-svar → pe-microsim | the model-versus-OBR CPI gap, scored as a real uprating reform | pe-macro svar-inflation-incidence --year 2027 --reference obr |
pe-microsim does not forecast, and this section does not pretend it does. Its question types are household, population, policy_reform — forecast is not among them, and its predictive validation is recorded as not_applicable. The forecast rows are boe-svar, whose uncertainty is posterior 68% and 90% intervals; the record below is what those rounds have been worth so far. See how a score is put together →
Over MCP the same six routes are household_reform_impact, population_reform_impact, score_reform, dynamic_reform_impact, forecast_uk and svar_inflation_incidence; recommend_model routes a question to a member, or refuses when none supports it. Connect a client →
The record so far.
Next due: 2026Q2 (UK real GDP, y/y, unemployment), which lands when the ONS publishes it.
One scored period is not an accuracy headline. Publishing from day one makes the sparse early record — and any failures — part of the evidence.
Scored so far: one round.
UK CPI, y/y
Errors are signed forecast − outturn, so a positive number means the forecast ran high. Naive baseline: the last outturn at the round's data edge, held flat (read from the current vintage, so revisions can shift it slightly). The official number is the OBR March 2026 EFO — fixed months earlier on less data, so part of its larger error is the information gap.
On the record, waiting to be scored.
GDP growth from the 2026-07-29 round — our median and bands beside the OBR's path.
Same round, CPI: the model sees stickier inflation than the EFO — partly because a VAR estimated through the 2021–23 surge mean-reverts toward a higher sample mean.
Unemployment, from the 2026-07-28 svar-unemployment satellite round — no official quarterly path to overlay, so the fan stands alone. Bands are raw model quantiles: the satellite has no coverage evaluation of its own, so no re-calibration is applied.
The EFO was fixed in March and these rounds in July, so the charts show where the views differ, not who forecasts better — that evaluation is on the boe-svar validation page.
Band calibration, and where the quarter-by-quarter numbers are
The GDP and CPI bands are rescaled per horizon to the coverage
measured on the validation page — which
widens some and narrows others: averaged across variables the bands
under-cover, but UK GDP over-covers at short horizons, so its 68%
factor is 0.86 at one quarter ahead and 1.44 at eight (factors
in band_calibration.json).
Those factors are measured on the model that is actually shown, with
the six Covid dummies; the earlier set came from a coverage run
estimated without them, and narrowed the GDP fan by 36% at one
quarter ahead where the published model wants 14%. They are quoted
to four decimals because that is the arithmetic of the run, not the
precision of the estimate — 49 overlapping origins carry a standard
error that moves k far more than its fourth decimal.
The raw quantiles stay untouched in the round artifacts. The
quarter-by-quarter numbers, including the BoE MPR central path and the
SEF consensus, are in those round artifacts below and in the committed
official CSVs.
What was claimed, and when.
One card per round: a frozen snapshot of what the models predicted, never edited afterwards (CI enforces it). The data edge is the last observation the model was allowed to see; once scored, the card shows how each claim fared.
- Data edge
- 2026Q1
- Periods
- 13
- Scored
- 1
boe-svar.json →
GitHub commit history →
- Data edge
- 2026Q1
- Periods
- 4
- Scored
- 0
okun-unemployment.json →
GitHub commit history →
- Data edge
- 2026Q1
- Periods
- 13
- Scored
- 0
boe-svar.json →
GitHub commit history →
Append-only: a CI job fails any pull request that edits or deletes a round, so a correction creates a new round and the superseded one stays scored. One round is weaker than the rest: the first was archived retroactively from a file already in Git — ahead of the CPI outturn by around 16 hours, but with thinner provenance than every round since.
Scoring inputs
Scores are computed from outturns.json,
which is vintage-versioned so revisions never silently rewrite a
score, and written to scorecard.json;
the method is in the README.
Every number, as it was published.
Rounds are scored against official data, and official data gets revised. Every series this site reads is therefore archived as dated snapshots that are never edited and never deleted — 20 series, 76 snapshots since 2026-07-25 — so a score can be reproduced against the data as it stood, not as it was later revised. Reading only the current value would make look-ahead bias undetectable, because only one version of the past would ever exist.
What is tracked
| Series | Source | Frequency | Latest | Snapshots |
|---|---|---|---|---|
| ONS | monthly | 755.0 2026-06 | 2 | |
Every snapshot taken, newest first — each file immutable. |
||||
| Bank of England | daily | 3.75 2026-08-24 | 9 | |
Every snapshot taken, newest first — each file immutable. |
||||
| ONS | quarterly | 77,134.0 2026Q1 | 1 | |
Every snapshot taken, newest first — each file immutable. |
||||
| ONS | monthly | 2.6 2026-07 | 2 | |
Every snapshot taken, newest first — each file immutable. |
||||
| ONS | quarterly | 2.8 2026Q2 | 3 | |
Every snapshot taken, newest first — each file immutable. |
||||
| ONS | quarterly | 709,598.0 2026Q1 | 2 | |
Every snapshot taken, newest first — each file immutable. |
||||
| Bank of England | daily | 5.0506 2026-08-21 | 9 | |
Every snapshot taken, newest first — each file immutable. |
||||
| Bank of England | daily | 5.5631 2026-08-21 | 9 | |
Every snapshot taken, newest first — each file immutable. |
||||
| Bank of England | daily | 4.5562 2026-08-21 | 9 | |
Every snapshot taken, newest first — each file immutable. |
||||
| ONS | monthly | 103.4 2026-06 | 2 | |
Every snapshot taken, newest first — each file immutable. |
||||
| ONS | monthly | -1,800.0 2026-07 | 2 | |
Every snapshot taken, newest first — each file immutable. |
||||
| ONS | monthly | 94.1 2026-07 | 2 | |
Every snapshot taken, newest first — each file immutable. |
||||
| ONS | quarterly | 4.9 2026Q2 | 3 | |
Every snapshot taken, newest first — each file immutable. |
||||
| ONS | monthly | 707.0 2026-06 | 2 | |
Every snapshot taken, newest first — each file immutable. |
||||
| FRED | monthly | 332.813 2026-07 | 2 | |
Every snapshot taken, newest first — each file immutable. |
||||
| FRED | monthly | 3.63 2026-07 | 2 | |
Every snapshot taken, newest first — each file immutable. |
||||
| FRED | monthly | 158,858.0 2026-07 | 2 | |
Every snapshot taken, newest first — each file immutable. |
||||
| FRED | quarterly | 24,270.599 2026Q2 | 2 | |
Every snapshot taken, newest first — each file immutable. |
||||
| FRED | daily | 4.7 2026-08-24 | 9 | |
Every snapshot taken, newest first — each file immutable. |
||||
| FRED | monthly | 4.1 2026-07 | 2 | |
Every snapshot taken, newest first — each file immutable. |
||||
Read a series as it was published on a date
Take the newest snapshot on or before your date. No key, no account, standard library only.
import json, urllib.request BASE = "https://policyengine-macro.vercel.app/data" def get(path): with urllib.request.urlopen(f"{BASE}/{path}") as response: return json.load(response) def as_of(source, series, date): dates = get("MANIFEST.json")["series"][series]["vintages"] # every snapshot ever taken dates = [d for d in dates if d <= date] if not dates: raise LookupError(f"no {series} vintage on or before {date}") return get(f"vintages/{source}/{series}/{max(dates)}.json") # immutable file cpi = as_of("ons", "uk_cpi_yoy", "2026-07-25") print(cpi["vintage"], cpi["observations"][-1]) # 2026-07-25 {'period': '2026Q2', 'value': 2.8}Two dates, two snapshots: the difference between them is the revision. The store starts 2026-07-25 and runs forward from there — it does not reconstruct vintages older than itself.
Announced releases
| Date | Series | Source | |
|---|---|---|---|
| 13 August 2026 | Real gross domestic product | uk_gdp_cvm | ONS · ABMI |
| 11 September 2026 | Monthly gross value added index | uk_monthly_gva | ONS · ECY2 |
| 15 September 2026 | Average weekly earnings | uk_average_weekly_earnings | ONS · KAB9 |
| 15 September 2026 | Unemployment rate | uk_unemployment_rate | ONS · MGSX |
| 15 September 2026 | UK vacancies | uk_vacancies | ONS · AP2Y |
| 16 September 2026 | Core CPI inflation | uk_core_cpi_yoy | ONS · DKO8 |
| 16 September 2026 | CPI inflation | uk_cpi_yoy | ONS · D7G7 |
| 22 September 2026 | Public-sector net borrowing | uk_public_sector_net_borrowing | ONS · J5II |
| 22 September 2026 | Public-sector net debt | uk_public_sector_net_debt_gdp | ONS · HF6X |
| 30 September 2026 | Real business investment | uk_business_investment | ONS · NPEL |
Endpoints
| Endpoint | Contains | Stability |
|---|---|---|
| /data/MANIFEST.json | Index of every series: source, CDID, units, frequency, coverage, and every snapshot date. | Rewritten on each fetch; short cache. |
| /data/latest/<series>.json | The newest snapshot, flattened. Same schema as a vintage file. | Moves. Do not cite as a fixed reference. |
| /data/vintages/<source>/<series>/<YYYY-MM-DD>.json | One dated snapshot, exactly as fetched. | Immutable by construction; one-year cache. |
| /data/calendar.ics | The release calendar above, as iCalendar. | Regenerated with the store. |
Each file carries series, source, cdid, title, frequency, units, url, release_updated, first_period, last_period, observations, vintage, fetched_utc. Snapshot dates are when a change was recorded, not a daily calendar: a fetch that found nothing new wrote no file. ONS and Bank of England series are under the Open Government Licence v3.0; US series come via FRED with their original BEA, BLS and Federal Reserve attribution.
Limits worth stating plainly.
One scored round proves little, and there will be no skill claim here until the scored count is well into double figures — at least a year away. The bands run too narrow: in hindsight testing the 68% band covers about 62% and the 90% band about 77%, worsening with horizon, which is why the fans above are re-calibrated; the full study is on the validation page.
CPI does not beat a fair naive benchmark. Against a random walk with drift the advantage largely evaporates. Bank Rate is the only robust win, and it is not forecast here yet. What is forecast is three variables on no fixed calendar — GDP growth and CPI from boe-svar, unemployment from its satellite — with rounds following data-edge refreshes rather than a schedule, and coefficients not re-estimated every round.
What the releases mean, read through the models.
Dated readings of UK releases, tied to the vintage available at publication — interpretation, not evidence of forecast skill.