Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9ca36fe
Read the OBR's national living wage levels from economy Table 1.13
MaxGhenis Oct 31, 2026
13b2029
Test the national living wage import from Table 1.13
MaxGhenis Oct 31, 2026
a40bb69
Document the national living wage import
MaxGhenis Oct 31, 2026
882227b
Merge origin/fix-2738-gambling-bases into fix-2721-import-nlw-forecast
MaxGhenis Oct 31, 2026
2ed288c
Check the finished national living wage index before writing it
MaxGhenis Oct 31, 2026
92649f4
Merge origin/fix-2738-gambling-bases into fix-2721-import-nlw-forecast
MaxGhenis Oct 31, 2026
ea84590
Check the level series' index to the end of the growth file
MaxGhenis Oct 31, 2026
b25ac1a
Name who acts in the national living wage notes
MaxGhenis Oct 31, 2026
ab10632
Merge origin/fix-2738-gambling-bases into fix-2721-import-nlw-forecast
MaxGhenis Oct 31, 2026
5bae36a
Keep each national living wage estimate's penny through the model's path
MaxGhenis Oct 31, 2026
fad8832
Draw the penny property's year set from years the index covers
MaxGhenis Oct 31, 2026
1b52679
Test the summary's note on a growth that keeps the penny
MaxGhenis Oct 31, 2026
38165b6
Merge origin/fix-2738-gambling-bases into fix-2721-import-nlw-forecast
MaxGhenis Oct 31, 2026
a88c4e4
Add earnings years only to average_earnings in the tail-years test
MaxGhenis Oct 31, 2026
2823883
Test that an estimate takes the nearest growth that keeps its penny
MaxGhenis Oct 31, 2026
a473bf2
Project the national living wage from its parameter file's rate
MaxGhenis Oct 31, 2026
695fd77
Merge origin/fix-2738-gambling-bases into fix-2721-import-nlw-forecast
MaxGhenis Oct 31, 2026
e43fd2e
Name who acts in the national living wage docs and messages
MaxGhenis Oct 31, 2026
5d2c2da
Test that the builder, not the importer, ends the index
MaxGhenis Oct 31, 2026
48de2a9
Give the direct main test the synthetic national living wage model
MaxGhenis Oct 31, 2026
22fb1e2
Project a table's first year after the anchor; read anchor dates as c…
MaxGhenis Nov 1, 2026
58dce28
Write the comments' levels to the penny the model projects
MaxGhenis Nov 1, 2026
e7fd50d
Bound the national living wage's index guard to the builder's horizon
MaxGhenis Nov 1, 2026
c6c32dd
Test that a table after the rate set keeps every penny
MaxGhenis Nov 1, 2026
0040cf8
Describe the national living wage import as it now runs
MaxGhenis Nov 1, 2026
b86f0e1
Build the falling-tail fixture as a new list
MaxGhenis Nov 1, 2026
17dbd90
Name who acts in the National Living Wage importer's notes
MaxGhenis Nov 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/2721.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The OBR forecasts importer now writes national living wage growth from the EFO economy tables' national living wage table (Table 1.13). It reads the OBR's levels across the table's row of years, projects each estimate from the last national living wage rate in the minimum wage parameters as the model does, and writes growth that keeps each estimate's penny.
79 changes: 60 additions & 19 deletions policyengine_uk/parameters/gov/economic_assumptions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,56 @@ for each duty's rates, the higher-stake share Machine Games Duty's rate
assumes, and the approximations.

`national_living_wage` projects the minimum wage rates
(`gov.hmrc.minimum_wage`) after the last rate set. It is the growth of the
rates set to 2027, then the OBR's estimates in Table 1.13 of the detailed
economy tables, then `average_earnings`' long-run growth. The checked-in
estimates are the October 2026 EFO's, ahead of the other series' March 2026
vintage, because HM Treasury costed Budget 2026's Carer's Allowance taper on
them. `utils/import_obr_forecasts.py` doesn't read Table 1.13, so the series
is updated by hand with each EFO. `test_minimum_wage_projection.py` holds the
table and fails if the series stops reproducing it, or if its long-run years
stop matching `average_earnings`. Each rate is set by an S.I., so this is a projection, not
a formula.
(`gov.hmrc.minimum_wage`) after the last rate set. The government sets each
rate following the Low Pay Commission's recommendations, and the OBR
publishes the rates the government has set, with its estimates of later
ones, in the national living wage table of the detailed economy tables
(Table 1.13 in October 2026). The series holds the growth into each of those
rates, then `average_earnings`' long-run growth, year for year. HM Treasury
costed Budget 2026's Carer's Allowance taper on the OBR's estimates.

`utils/import_obr_forecasts.py` writes the series with the other economy
series. It finds the table by its title and reads the level under each year
across the table's one row of years. The importer divides each year's level
by the previous year's level, subtracts one, and rounds to six decimals. The
OBR identifies in its note the last year the government has set, and the
importer labels the growth into those years and into the OBR's estimates with
separate comments.

The model projects the national living wage from the last rate in the last
bracket of `gov/hmrc/minimum_wage/non_apprentice.yaml`. It carries that rate
through the index it builds from this series and rounds each projected rate
to the nearest penny. The importer reads that rate through policyengine-core's
loader and Parameter, then projects each later table year with the model's
rounding. Where a year's ratio would leave the model a penny off the
OBR's level, the importer writes the six-decimal growth that keeps the OBR's
penny. Where the OBR publishes no level for the year of that rate or earlier,
the importer keeps the series' earlier growth into the table's first year and
checks the model's penny there against the OBR's. The importer
then runs the model's own generation steps on the file it would write. It
checks that the model's index reaches the table's last year, stays above zero
and finite in every year the model builds it, and matches the index the
importer compounded.

The importer stops before writing any file when:

- the unit differs from £ per hour, or the table lacks one row of years or
one row labelled "National Living Wage (NLW)";
- the years skip or repeat, a year lacks a positive level, or a figure has no
year above it;
- the OBR's note names no single calendar year the government has set, or its
statements name different years;
- the OBR's level in the year of the file's last rate differs from that rate
at the penny;
- the OBR publishes no level for the year of that rate or earlier, and the
model carries the rate to another penny in the table's first year;
- the model's index, built from the series the importer would write, reaches
zero or infinity in a year the model builds it; or
- no six-decimal growth keeps an estimate's penny.

`test_minimum_wage_projection.py` holds the October 2026 table, checks it
against the importer's fixture, and fails if the series stops reproducing the
table to the penny, or if its long-run years stop matching `average_earnings`.

## Time horizons

Expand Down Expand Up @@ -489,9 +529,17 @@ the forecast levels, though not their growth.
EFO's policy tables for any new measure in the betting and gaming lines.
Then update the published levels in
`policyengine_uk/tests/test_gambling_duty_base_projection.py`.
7. If the new EFO runs past `PROJECTION_END_YEAR`, move it on (see
7. The importer refreshes `national_living_wage` in the same run, from the
new economy tables' national living wage table (Table 1.13 in October
2026), with `average_earnings` after it. Then replace the cut-down table in
`policyengine_uk/tests/fixtures/obr_economy/` (see its README) and the
levels in `OBR_NATIONAL_LIVING_WAGE` in
`policyengine_uk/tests/test_minimum_wage_projection.py`. Pass
`--skip-national-living-wage` only to leave the series on the previous
EFO.
8. If the new EFO runs past `PROJECTION_END_YEAR`, move it on (see
[Dataset projection horizon](#dataset-projection-horizon)).
8. Refresh the household energy price inputs (see
9. Refresh the household energy price inputs (see
[Household energy prices](#household-energy-prices)) from the new EFO's
economy tables, the MM23 release of the same month and the latest Energy
Trends tables. The importer records the last quarter ONS publishes in
Expand Down Expand Up @@ -544,13 +592,6 @@ Bank Rate (`gov.boe.base_rate`, economy Table 1.9) and households' financial
assets (`household.wealth.financial_assets`, Table 1.11) also take October
2026 values.

The national living wage forecast (economy Table 1.13) is not read here. Until
PolicyEngine/policyengine-uk#2700 adds it as `obr.national_living_wage`, the
minimum wage rates, and the rules built on them, stay at their April 2026
levels; #2721 tracks teaching the importer to read the table. Whichever of
#2700 and the October 2026 update merges second copies the 2032+ earnings path
into that series.

The EFO refresh does not touch `finance_ni.domestic_rates`; OBR does not
publish a Northern Ireland domestic rates bill series (see below).

Expand Down
38 changes: 20 additions & 18 deletions policyengine_uk/parameters/gov/economic_assumptions/yoy_growth.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -207,34 +207,36 @@ obr:

national_living_wage:
description: National living wage year-on-year growth, for the rate from April of each year.
# The OBR forecasts the national living wage itself (EFO detailed economy
# tables, Table 1.13): rates set to 2027, then "OBR estimates consistent
# with the rest of our economy forecast". Each value here is one year's
# rate over the year before's, to six decimals, so compounding them from
# £13.21 reproduces the table to the penny: £13.51 (2028), £13.85 (2029),
# £14.23 (2030) and £14.62 (2031). HM Treasury costed the Budget 2026
# Carer's Allowance taper on these "OBR projections for increases in the
# National Living Wage" (policy costings, October 2026). After the EFO
# horizon the series takes average_earnings' long-run growth: the Low Pay
# Commission's reference point for the rate is two-thirds of median
# hourly earnings (remit 2026). Refresh 2028 onwards with each EFO.
# The OBR forecasts the national living wage itself, in £ an hour, in the
# EFO detailed economy tables (Table 1.13 in October 2026): the rates the
# Government has set, then "OBR estimates consistent with the rest of our
# economy forecast". utils/import_obr_forecasts.py reads the table and
# writes each year's rate over the year before's, to six decimals, so
# compounding them reproduces the table to the penny. It writes the years
# the Government has set and the OBR's estimates under separate comments
# below; gov.hmrc.minimum_wage cites the regulations or Budget behind
# each rate set. HM Treasury costed the Budget 2026 Carer's Allowance
# taper on these "OBR projections for increases in the National Living
# Wage" (policy costings, October 2026). After the EFO horizon the
# importer copies average_earnings' long-run growth: in its 2026 remit to
# the Low Pay Commission, the government says two-thirds of median hourly
# earnings "should continue to be the key reference point for future
# rates".
values:
# Rates set: S.I. 2021/329 to 2026/357, and £13.21 for 2027 (Budget
# 2026, paragraph 6.128), from £8.72 in 2020.
# The government set the rates for 2021-2027: £8.72 in 2020 to £13.21 in 2027 (OBR EFO October 2026, economy Table 1.13 "National Living Wage (NLW)")
2021-01-01: 0.021789
2022-01-01: 0.066218
2023-01-01: 0.096842
2024-01-01: 0.097889
2025-01-01: 0.067308
2026-01-01: 0.040950
2026-01-01: 0.04095
2027-01-01: 0.039339
# OBR estimates (2028-2031), EFO October 2026 Table 1.13
# The OBR estimates the rates for 2028-2031: £13.21 in 2027 to £14.62 in 2031 (OBR EFO October 2026, economy Table 1.13 "National Living Wage (NLW)")
2028-01-01: 0.022984
2029-01-01: 0.025251
2030-01-01: 0.027289
2031-01-01: 0.026986
# Long run (2032+): average_earnings growth, as refreshed by the
# October 2026 EFO (#2717)
# After the OBR forecast, the importer copies average_earnings year for year
2032-01-01: 0.0285
2033-01-01: 0.0309
2034-01-01: 0.0334
Expand Down Expand Up @@ -281,7 +283,7 @@ obr:
unit: /1
label: National living wage growth
reference:
- title: OBR EFO October 2026, detailed forecast tables (economy), Table 1.13
- title: OBR EFO October 2026 (detailed forecast tables, economy, Table 1.13)
href: https://obr.uk/efo/economic-and-fiscal-outlook-october-2026/
- title: HM Treasury, Budget 2026 policy costings, Carer's Allowance taper (cost base)
href: https://assets.publishing.service.gov.uk/media/6ae1883fe4367e5c8b0467df/AB26_Policy_Costing.pdf
Expand Down
23 changes: 23 additions & 0 deletions policyengine_uk/tests/fixtures/obr_economy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Real OBR economy table fixture

`efo_october_2026_economy_table_1_13.xlsx` holds the Contents and 1.13
(National Living Wage) worksheets of the OBR October 2026 EFO detailed
forecast tables, economy
(`efo-october-2026-detailed-forecast-tables-economy.xlsx` in the detailed
forecast tables zip file, SHA-256
`96795d1747bb5a4978c7fb768689d5fd31f568e5223db8a94b1bd92ba7f6c118`). Source:
<https://obr.uk/efo/economic-and-fiscal-outlook-october-2026/>.

As with the energy price fixtures, a script kept only those worksheets in the
workbook's sheet list and relationships, dropped its defined names, and copied
the worksheet XML and shared strings byte for byte. The archive serves the
importer's XLSX reader and leaves out the styles and other parts a full Excel
file has.

`policyengine_uk/utils/import_obr_forecasts.py` must reproduce
`obr.national_living_wage` in `yoy_growth.yaml` from it byte for byte. Tests
change cells of sheet 1.13 to check that the importer refuses a changed layout
rather than reading another row. No test downloads a workbook. With each new
EFO, replace the file with the new table and update `OBR_NATIONAL_LIVING_WAGE`
in `policyengine_uk/tests/test_minimum_wage_projection.py`, which must equal
it.
Binary file not shown.
Loading
Loading