Skip to content

UK year files make the State Pension follow CPI instead of the triple lock #556

Description

@MaxGhenis

What happens

A UK population run through policyengine.py (ensure_datasets → Simulation → outputs) does not reproduce policyengine_uk.Microsimulation on the same certified dataset and year. The State Pension differs, and so does everything downstream of it.

2026, £bn direct policyengine-uk policyengine.py gap
policyengine 6.2.1 (uk 2.102.3, data 1.56.16): State Pension 133.69 127.50 −6.19
Pension Credit 6.99 7.51 +0.52
income tax 313.45 312.35 −1.10
household net income 1,764.75 1,760.25 −4.50
#555 branch (uk 2.123.4, data 1.58.0): State Pension 128.31 124.42 −3.89
Pension Credit 7.05 7.42 +0.37

Universal Credit and Child Benefit match.

Mechanism (shown record by record, real runs)

  1. create_datasets builds policyengine_uk.Microsimulation(dataset=<base file>), takes the projected frame sim.dataset[year] and saves it as the year file. The year file is an exact copy of that frame: no input is dropped or frozen, and the weights are identical.
  2. PolicyEngineUKLatest.run() passes that frame back to policyengine-uk as UKSingleYearDataset(fiscal_year=year).
  3. policyengine-uk's basic_state_pension, new_state_pension and additional_state_pension take the data year as min(simulation.dataset.years). They split state_pension_reported at the data year against that year's legislated rates, and scale the share to the period's rates (the triple lock). The direct run's data year is the survey year, 2024. The wrapper's is 2026, so the split uses the projected frame's state_pension_reported, which the dataset projection uprates by CPI.
  4. So the wrapper's State Pension is the reported amount uprated by CPI, and the direct run's is the reported amount uprated by the triple lock. For every uncapped flat-rate recipient (5,040 NEW and 2,295 BASIC records), the wrapper/direct ratio is 0.96967 = CPI growth 2024→26 (1.0578) / rate growth (1.0909). Under 2.123.4, 128.31 × 0.96968 = 124.42 exactly. All 27,875 (27,347) recipient records differ.
  5. Set the wrapper's four State Pension variables to the direct run's values, and income tax, Pension Credit, Housing Benefit, UC, Council Tax Reduction, household net income, HBAI net income and gov_balance all match the direct run exactly. State Pension is the entire discrepancy.

Across the wrapper's default output variables, 26 of 68 differ, including in_poverty_* for 313–372 households and income deciles. Region-scoped runs carry the same error.

Fix

Year files keep the data year and its tables. run() then rebuilds the input that policyengine-uk builds itself: it projects the data-year tables forward and puts the stored year frame at the target year. policyengine-uk then anchors the State Pension on the survey year, as it does in a direct run. Year files written before the fix are regenerated by ensure_datasets and refused by load_datasets.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions