diff --git a/changelog.d/additional-state-pension-period-type.fixed.md b/changelog.d/additional-state-pension-period-type.fixed.md new file mode 100644 index 0000000000..8d6871c631 --- /dev/null +++ b/changelog.d/additional-state-pension-period-type.fixed.md @@ -0,0 +1 @@ +Split additional State Pension by each person's State Pension type in the year simulated, as basic and new State Pension already are, instead of their type in the data year. Survey ages are held fixed across projected years, so records that were on the basic State Pension in the data year stand for new State Pension cohorts later, and the part of their reported State Pension between the two flat rates was paid twice. Basic, new and additional State Pension now add up to the reported amount uprated by the flat rate. diff --git a/docs/book/programs/gov/dwp/state-pension.md b/docs/book/programs/gov/dwp/state-pension.md index 2f068e4f27..9f2c1d8b09 100644 --- a/docs/book/programs/gov/dwp/state-pension.md +++ b/docs/book/programs/gov/dwp/state-pension.md @@ -25,13 +25,40 @@ Three variables under `gov/dwp/`: accrual that exceeds the new flat rate, folded into NSP under current law but tracked separately in PolicyEngine so the reform surface stays clean. - - Both are computed as `max(reported - flat_max_for_type, 0)` per week, - multiplied by `WEEKS_IN_YEAR`. + - Both are the reported weekly amount above the type's full rate in the + data year, `max(reported - flat_max_for_type, 0)`, uprated as described + below and multiplied by `WEEKS_IN_YEAR`. + +All three split the amount a person reported in the data year (the +dataset's first year) by the person's `state_pension_type` in the year +simulated. The part up to that type's full rate in the data year is basic +or new State Pension, and the part above it is additional State Pension. +Both parts are uprated by the type's full rate (the year's over the data +year's), so together they pay the reported amount uprated by the flat rate, +for anyone over State Pension age. In law, additional pensions and +protected payments rise with prices instead: the Social Security Benefits +Up-rating Order 2026 raised them by 3.8% in April 2026 (articles 4(3) and +6(3)), and the full rates by 4.8%. This is tracked in +[#1941](https://github.com/PolicyEngine/policyengine-uk/issues/1941). + +Survey ages are held fixed in the years a dataset is projected to, so a +record's birth cohort moves one year later for each year projected, and its +type can change: a man aged 75 in the 2024-25 data reached State Pension age +in 2014, on the basic State Pension, but a man aged 75 in 2030-31 reached it +in 2021, on the new State Pension. Until the fix for +[#1921](https://github.com/PolicyEngine/policyengine-uk/issues/1921), +`additional_state_pension` used the data year's type while the other two +used the year's, so for records like his the part of the reported amount +between the basic and new flat rates was paid twice. The records keep their +reported amounts: a record that moves to a new State Pension cohort is not +given a new State Pension award. That is one reason the model's State +Pension per recipient grows more slowly than DWP's (see Known aggregate gap +below). The flag-up split (`state_pension_type`) is settled by [PR #1618](https://github.com/PolicyEngine/policyengine-uk/pull/1618): classification is based on whether the person reaches State Pension age -before or on/after 6 April 2016. No component is paid before State -Pension age. +before or on/after 6 April 2016. A computed `state_pension_type` is `NONE` +below State Pension age, so no component is paid there. ## State Pension age @@ -246,10 +273,68 @@ still included and the rise needed to reach the earnings path, rounded up to ## Known aggregate gap (#1632) -After the BASIC/NEW classification fix in PR #1618 and the pro-rating -+ Protected Payment fixes in [PR #1634](https://github.com/PolicyEngine/policyengine-uk/pull/1634), -the model's State Pension aggregate is **~£127.5 bn** against the OBR -2025 target of **~£140 bn** — a **-£12 bn gap**. +The model's State Pension covers UK private households: the Family +Resources Survey covers private households only, not nursing homes and +other communal establishments. DWP's State Pension spending covers Great +Britain and UK State Pensions paid to people living abroad, but not +Northern Ireland, whose State Pension the Department for Communities pays. +DWP's Spring Forecast 2026 is consistent with the OBR's March 2026 forecast. +From 2026-27 the two differ by about £2m a year. The OBR's figures are +£1.4bn higher in 2024-25, which the OBR records as outturn, and £0.1bn +higher in 2025-26. + +The table compares full microsimulation runs on the enhanced FRS 2024-25 +(policyengine-uk-data-private 1.57.4) with DWP's spending less its payments +abroad. + +| £bn | 2024-25 | 2025-26 | 2026-27 | 2027-28 | 2028-29 | 2029-30 | 2030-31 | +|---|---:|---:|---:|---:|---:|---:|---:| +| Model State Pension | 119.0 | 124.8 | 129.6 | 131.6 | 133.6 | 137.5 | 141.6 | +| OBR March 2026 EFO, State Pension | 138.0 | 146.2 | 154.2 | 158.9 | 164.0 | 172.2 | 180.7 | +| DWP Spring Forecast 2026, State Pension | 136.6 | 146.1 | 154.2 | 158.9 | 164.0 | 172.2 | 180.7 | +| of which paid abroad | 5.3 | 5.6 | 5.9 | 6.1 | 6.2 | 6.4 | 6.6 | +| Model minus DWP less paid abroad | -12.2 | -15.6 | -18.6 | -21.3 | -24.2 | -28.3 | -32.5 | + +The model also includes Northern Ireland, which DWP's figures do not, so the +like-for-like gap is larger by Northern Ireland's State Pension. + +**The data year.** In 2024-25 the model has 11.36m State Pension recipients +averaging £201.6 a week, for the UK. The FRS's own grossed figures for Great +Britain are 11.53m recipients averaging £212 a week, and DWP's administrative +figures are 11.88m and £212 (FRS methodology tables M.6a and M.6b, which +cover Great Britain). The FRS's grossed estimate for the +UK, with benefit amounts linked to DWP records, is £130.6bn, against +£135.1bn from administrative data. So most of the model's £16bn shortfall +against administrative data for the UK arises in building the enhanced FRS, +not in the survey. For FRS respondents, `state_pension_reported` is the +FRS benefits table's weekly State Pension amount times 52 +(policyengine-uk-data's `datasets/frs.py`). On the rows the enhanced FRS +adds from the Survey of Personal Incomes, it is imputed +(`datasets/imputations/frs_only.py`). The shortfall is tracked in +[policyengine-uk-data#493](https://github.com/PolicyEngine/policyengine-uk-data/issues/493). + +**Projected years.** The gap widens after the data year for two reasons, +tracked in [#1929](https://github.com/PolicyEngine/policyengine-uk/issues/1929): + +- **Caseload.** Survey ages are held fixed in projected years and household + weights grow with total population + (`policyengine_uk/data/uprating_indices.yaml`), so the pension-age + population keeps the data year's age structure. From 2025-26 to 2030-31 + the model's State Pension recipients fall 3.0%, as State Pension age rises + to 67, while DWP's caseload less those paid abroad rises 4.8%. +- **Awards.** Each record keeps its reported amount, uprated by the flat + rate, including records whose cohort moves from basic to new State + Pension. The model's State Pension per recipient grows 17.0% over those + years. DWP's spending per recipient, less those paid abroad, grows 18.3%, + as its new State Pension spending grows from £56.1bn to £102.4bn and its + basic State Pension spending falls from £66.7bn to £57.8bn. + +Until the fix for [#1921](https://github.com/PolicyEngine/policyengine-uk/issues/1921), +`additional_state_pension` paid the band between the flat rates twice for +records moving from basic to new State Pension cohorts (see Components +above): £0.8bn in 2025-26, rising to £6.8bn in 2030-31. That made the +model's State Pension per recipient grow 21.8%, faster than DWP's, and hid +part of the widening. ### What's been fixed @@ -258,34 +343,28 @@ the model's State Pension aggregate is **~£127.5 bn** against the OBR | BASIC vs NEW classification used `is_SP_age` heuristic | Fixed | #1618 | | `new_state_pension` returned flat max for every NEW retiree, ignoring partial NI records | Fixed | #1634 | | Protected Payment only computed for BASIC-type recipients | Fixed | #1634 (ASP now extends to NEW) | +| `additional_state_pension` split the reported amount by the data year's State Pension type, paying part of it twice for records moving from basic to new cohorts | Fixed | #1921 | ### What's still open -The remaining ~£12 bn gap appears to come from the **data side** rather -than the formula. The FRS records State Pension as a single weekly -benefit value (`state_pension_reported`), which is derived from the -DWP-administered single weekly figure (SRP). For BASIC-type retirees -who reported exactly the maximum basic rate, the formula assigns ASP = -0 — but in reality many of those retirees also received SERPS / S2P -top-ups that the single weekly figure either caps or rounds. +- The data year's shortfall in the enhanced FRS: + [policyengine-uk-data#493](https://github.com/PolicyEngine/policyengine-uk-data/issues/493). +- The pension-age population and new-cohort awards in projected years: + [#1929](https://github.com/PolicyEngine/policyengine-uk/issues/1929). -The proposed data-side fix lives in `policyengine-uk-data` and would: - -- Impute an ASP component on BASIC-type rows whose reported state - pension matches the max basic rate exactly, using the DWP-published - share of SERPS / S2P recipients in that band. -- Source the ASP-by-band distribution from ONS *National Pensioners - Survey* breakdowns or DWP administrative caseload by pension type. - -This is tracked under [#1632](https://github.com/PolicyEngine/policyengine-uk/issues/1632) +Both are part of [#1632](https://github.com/PolicyEngine/policyengine-uk/issues/1632) and the broader UK pipeline-alignment tracker [#1621](https://github.com/PolicyEngine/policyengine-uk/issues/1621). ## References - DWP, [New State Pension](https://www.gov.uk/new-state-pension) and [Basic State Pension](https://www.gov.uk/state-pension) user-facing pages. -- HMRC, [State Pension forecast](https://www.gov.uk/check-state-pension) (the underlying SP1 figure that lands in the FRS). +- GOV.UK, [Check your State Pension forecast](https://www.gov.uk/check-state-pension). - [Pensions Act 2014](https://www.legislation.gov.uk/ukpga/2014/19/contents) — introduces the New State Pension and the BASIC / NEW boundary. - [Social Security Contributions and Benefits Act 1992, Part 2](https://www.legislation.gov.uk/ukpga/1992/4/part/II) — primary statute for the basic scheme. -- OBR March 2026 EFO — State Pension expenditure target. +- OBR, [Economic and fiscal outlook, March 2026: detailed forecast tables, expenditure](https://obr.uk/download/march-2026-economic-and-fiscal-outlook-detailed-forecast-tables-expenditure/), table 4.9, "State pension". +- DWP, [Benefit expenditure and caseload tables 2026: outturn and forecast, Spring Forecast 2026](https://assets.publishing.service.gov.uk/media/69dcdc8c6b695d635c34dcc4/outturn-and-forecast-tables-spring-forecast-2026.xlsx), "State Pension" sheet: total, paid outside the UK, components and caseload. +- DWP, [Family Resources Survey, integrating administrative data for benefits: tables](https://assets.publishing.service.gov.uk/media/69c416acb66ff902f45441f0/family-resources-survey-transformation-benefits.xlsx), sheet 16, State Pension: admin-linked survey and administrative estimates, 2024-25. +- DWP, [Family Resources Survey 2024-25: methodology and standard error tables](https://assets.publishing.service.gov.uk/media/69c3c0d2471d520038d0f571/ch1_methodology_and_standard_errors.xlsx), tables M.6a and M.6b: State Pension recipients and weekly amounts, FRS against administrative data. +- DWP, [Family Resources Survey 2024-25: background information and methodology](https://www.gov.uk/government/statistics/family-resources-survey-financial-year-2024-to-2025/family-resources-survey-background-information-and-methodology), section 4.1, for the survey's coverage of private households. - House of Commons Library, [State Pension triple lock](https://commonslibrary.parliament.uk/research-briefings/cbp-7812/) — context for the triple-lock parameters. diff --git a/policyengine_uk/tests/test_state_pension_components.py b/policyengine_uk/tests/test_state_pension_components.py new file mode 100644 index 0000000000..69f7c5d49b --- /dev/null +++ b/policyengine_uk/tests/test_state_pension_components.py @@ -0,0 +1,314 @@ +"""State Pension components when the model year is not the data year. + +basic_state_pension, new_state_pension and additional_state_pension split the +State Pension a person reported in the data year. Survey ages are held fixed +across the years a dataset is projected to, so a record's birth cohort moves +one year later for each year projected, and its State Pension type in the +period can differ from its type in the data year. All three components split +by the period's type, so together they split the reported amount with no +overlap and no gap: the part up to the type's full rate is uprated by the full +rate, and the part above it by the add-on's uprating (see add_on_uprating). + +The YAML runner builds simulations without a dataset, where the data year is +the period by construction, so these cases build simulations from data. +""" + +import numpy as np +import pandas as pd +import pytest +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_uk import Microsimulation +from policyengine_uk.data import UKMultiYearDataset, UKSingleYearDataset + +DATA_YEAR = 2024 +YEARS = range(DATA_YEAR, 2031) +WEEKS_IN_YEAR = 52 +COMPONENTS = ["basic_state_pension", "new_state_pension", "additional_state_pension"] + + +def person_table(people: list, ages=None) -> pd.DataFrame: + ids = np.arange(len(people)) + return pd.DataFrame( + { + "person_id": ids, + "person_benunit_id": ids, + "person_household_id": ids, + "age": [p["age"] for p in people] if ages is None else ages, + "gender": ["MALE" if p["male"] else "FEMALE" for p in people], + "months_since_last_birthday": [p["months"] for p in people], + "state_pension_reported": [p["weekly"] * WEEKS_IN_YEAR for p in people], + } + ) + + +def year_dataset(people: list, year: int, ages=None) -> UKSingleYearDataset: + """One person per household.""" + ids = np.arange(len(people)) + household = pd.DataFrame( + { + "household_id": ids, + "household_weight": np.ones(len(people)), + "region": "LONDON", + "tenure_type": "OWNED_OUTRIGHT", + "rent": 0.0, + "council_tax": 0.0, + } + ) + return UKSingleYearDataset( + person=person_table(people, ages), + benunit=pd.DataFrame({"benunit_id": ids}), + household=household, + fiscal_year=year, + ) + + +def projected_simulation(people: list) -> Microsimulation: + """Data for 2024-25, projected forward as survey data is: ages held fixed.""" + return Microsimulation(dataset=year_dataset(people, DATA_YEAR)) + + +def flat_rates(sim, year: int) -> dict: + p = sim.tax_benefit_system.parameters.gov.dwp.state_pension + return { + "BASIC": p.basic_state_pension.amount(year), + "NEW": p.new_state_pension.amount(year), + } + + +def flat_uprating(sim, year: int, pension_type: str) -> float: + """The type's full rate in the year over its full rate in the data year.""" + return ( + flat_rates(sim, year)[pension_type] / flat_rates(sim, DATA_YEAR)[pension_type] + ) + + +def add_on_uprating(sim, year: int, pension_type: str) -> float: + """Uprating of the part above the full rate (additional pension, protected + payments). The model uprates it by the full rate; in law these rise with + prices (Social Security Benefits Up-rating Order 2026 arts 4(3) and 6(3)), + tracked in PolicyEngine/policyengine-uk#1941. Change it here with the model.""" + return flat_uprating(sim, year, pension_type) + + +def uprated_reported(sim, year: int, pension_type: str, weekly: float) -> float: + """The data-year amount, its part up to the full rate uprated by the full + rate and the part above it by the add-on's uprating.""" + full = flat_rates(sim, DATA_YEAR)[pension_type] + return WEEKS_IN_YEAR * ( + min(weekly, full) * flat_uprating(sim, year, pension_type) + + max(weekly - full, 0) * add_on_uprating(sim, year, pension_type) + ) + + +def calculate(sim, variable: str, year: int) -> np.ndarray: + return np.asarray(sim.calculate(variable, year)) + + +def test_basic_in_data_year_and_new_in_period_is_not_paid_twice(): + """A man aged 75 on 6 October 2024, born 6 July 1949, reached State Pension + age at 65 in 2014: basic State Pension. Held at 75, the record in 2030-31 + stands for a man born 6 July 1955, who reached it at 66 in 2021: new State + Pension. His £200 a week, below the new State Pension's full rate, is all + new State Pension; before the fix, the £30.50 above the basic State + Pension's full rate was paid again as additional State Pension.""" + person = {"age": 75, "male": True, "months": 3, "weekly": 200} + sim = projected_simulation([person]) + assert calculate(sim, "state_pension_type", DATA_YEAR)[0] == "BASIC" + for year in (2027, 2030): + assert calculate(sim, "state_pension_type", year)[0] == "NEW" + expected = uprated_reported(sim, year, "NEW", 200) + assert calculate(sim, "new_state_pension", year)[0] == pytest.approx( + expected, abs=0.01 + ) + assert calculate(sim, "basic_state_pension", year)[0] == 0 + assert calculate(sim, "additional_state_pension", year)[0] == 0 + assert calculate(sim, "state_pension", year)[0] == pytest.approx( + expected, abs=0.01 + ) + + +def test_protected_payment_above_new_state_pension_follows_period_type(): + """A woman aged 72 in 2024-25 (born 6 July 1952) reached State Pension age in + 2014 on the basic State Pension; held at 72 she stands, by 2030-31, for a + woman born in 1958, on the new State Pension. Her £240 a week exceeds the + new State Pension's full rate, so the excess is additional State Pension + (a protected payment), uprated as the model uprates add-ons.""" + person = {"age": 72, "male": False, "months": 3, "weekly": 240} + sim = projected_simulation([person]) + assert calculate(sim, "state_pension_type", DATA_YEAR)[0] == "BASIC" + assert calculate(sim, "state_pension_type", 2030)[0] == "NEW" + rates, data_rates = flat_rates(sim, 2030), flat_rates(sim, DATA_YEAR) + assert calculate(sim, "new_state_pension", 2030)[0] == pytest.approx( + rates["NEW"] * WEEKS_IN_YEAR, abs=0.01 + ) + assert calculate(sim, "additional_state_pension", 2030)[0] == pytest.approx( + (240 - data_rates["NEW"]) * WEEKS_IN_YEAR * add_on_uprating(sim, 2030, "NEW"), + abs=0.01, + ) + + +@pytest.mark.parametrize( + "person, pension_type", + [ + # Aged 90: born 1934 in the data year, 1940 by 2030-31. + ({"age": 90, "male": True, "months": 3, "weekly": 250}, "BASIC"), + # Aged 70: born 1954 in the data year, 1960 by 2030-31; both reached + # State Pension age after 5 April 2016. + ({"age": 70, "male": True, "months": 3, "weekly": 240}, "NEW"), + ], +) +def test_unchanged_type_splits_at_its_own_flat_rate(person, pension_type): + sim = projected_simulation([person]) + for year in YEARS: + assert calculate(sim, "state_pension_type", year)[0] == pension_type + total = sum(calculate(sim, v, year)[0] for v in COMPONENTS) + assert total == pytest.approx( + uprated_reported(sim, year, pension_type, person["weekly"]), abs=0.01 + ) + + +def test_nothing_is_paid_once_the_record_falls_below_state_pension_age(): + """Aged 66 in 2024-25 (born 6 July 1958) is over State Pension age on the new + State Pension; aged 66 in 2027-28 (born 6 July 1961) attains it at 67.""" + person = {"age": 66, "male": True, "months": 3, "weekly": 230} + sim = projected_simulation([person]) + assert calculate(sim, "state_pension_type", DATA_YEAR)[0] == "NEW" + assert calculate(sim, "state_pension_type", 2027)[0] == "NONE" + for variable in COMPONENTS + ["state_pension"]: + assert calculate(sim, variable, 2027)[0] == 0 + + +def test_new_in_data_year_and_basic_in_period_is_paid_in_full(): + """Held-fixed ages only move cohorts later, so a record never goes from new + to basic State Pension when projected. A dataset may still carry other ages + in later years; the band between the two flat rates must still be paid.""" + people = [ + # New State Pension at 70 in 2024-25; 85 in 2030-31 is basic. + {"age": 70, "male": True, "months": 3, "weekly": 240}, + # Below State Pension age at 60 in 2024-25; 70 in 2030-31 is new. + {"age": 60, "male": True, "months": 3, "weekly": 230}, + ] + dataset = UKMultiYearDataset( + datasets=[ + year_dataset(people, DATA_YEAR), + year_dataset(people, 2030, ages=[85, 70]), + ] + ) + sim = Microsimulation(dataset=dataset) + assert list(calculate(sim, "state_pension_type", DATA_YEAR)) == ["NEW", "NONE"] + assert list(calculate(sim, "state_pension_type", 2030)) == ["BASIC", "NEW"] + total = sum(calculate(sim, v, 2030) for v in COMPONENTS) + assert total[0] == pytest.approx( + uprated_reported(sim, 2030, "BASIC", 240), abs=0.01 + ) + assert total[1] == pytest.approx(uprated_reported(sim, 2030, "NEW", 230), abs=0.01) + + +# Each example builds a microsimulation, so examples are slow to generate. +PROPERTY_SETTINGS = settings( + max_examples=10, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow], +) + + +PEOPLE = st.lists( + st.fixed_dictionaries( + { + "age": st.integers(min_value=55, max_value=100), + "male": st.booleans(), + "months": st.floats(min_value=0, max_value=12, exclude_max=True), + # Include each data-year flat rate, where the split changes. + "weekly": st.one_of( + st.floats(min_value=0, max_value=600), + st.sampled_from([0, 169.5, 221.2]), + ), + } + ), + min_size=1, + max_size=30, +) + + +def by_type(pension_type: np.ndarray, basic, new) -> np.ndarray: + return np.select([pension_type == "BASIC", pension_type == "NEW"], [basic, new], 0) + + +def assert_components_add_up(sim, people: list, years) -> None: + """For each person and year: below State Pension age, no component and type + NONE. Over it, only the period type's flat-rate component, which is the + reported amount up to the type's full rate, uprated by the full rate; and, + deflating each component by its own uprating, the three components add up + to exactly the reported amount, with no overlap and no gap.""" + weekly = np.array([p["weekly"] for p in people]) + for year in years: + pension_type = calculate(sim, "state_pension_type", year).astype(str) + over_spa = calculate(sim, "is_SP_age", year).astype(bool) + basic, new, additional = (calculate(sim, v, year) for v in COMPONENTS) + rates, data_rates = flat_rates(sim, year), flat_rates(sim, DATA_YEAR) + full = by_type(pension_type, data_rates["BASIC"], data_rates["NEW"]) + flat_up = by_type( + pension_type, + flat_uprating(sim, year, "BASIC"), + flat_uprating(sim, year, "NEW"), + ) + add_up = by_type( + pension_type, + add_on_uprating(sim, year, "BASIC"), + add_on_uprating(sim, year, "NEW"), + ) + reported = weekly * WEEKS_IN_YEAR + assert np.array_equal(pension_type == "NONE", ~over_spa), year + flat = basic + new + expected_flat = over_spa * np.minimum(reported, full * WEEKS_IN_YEAR) * flat_up + assert np.allclose(flat, expected_flat, rtol=1e-5, atol=0.01) + deflated = np.where( + over_spa, + flat / np.where(over_spa, flat_up, 1) + + additional / np.where(over_spa, add_up, 1), + 0, + ) + assert np.allclose(deflated, over_spa * reported, rtol=1e-5, atol=0.01) + assert np.all(additional[~over_spa] == 0) + assert np.all(basic[pension_type != "BASIC"] == 0) + assert np.all(new[pension_type != "NEW"] == 0) + assert np.all(additional >= 0) + assert np.all(basic <= rates["BASIC"] * WEEKS_IN_YEAR * (1 + 1e-6)) + assert np.all(new <= rates["NEW"] * WEEKS_IN_YEAR * (1 + 1e-6)) + + +@PROPERTY_SETTINGS +@given(people=PEOPLE) +def test_components_add_up_to_uprated_reported_amount(people): + """Data projected with ages held fixed, every year from 2024-25 to 2030-31.""" + sim = projected_simulation(people) + assert_components_add_up(sim, people, YEARS) + + +@PROPERTY_SETTINGS +@given( + people=PEOPLE, + period=st.integers(min_value=DATA_YEAR + 1, max_value=2030), + data=st.data(), +) +def test_components_add_up_whatever_ages_the_period_carries(people, period, data): + """Any age in the period, so every pair of data-year and period types occurs, + including new to basic and none to either.""" + ages = data.draw( + st.lists( + st.integers(min_value=55, max_value=100), + min_size=len(people), + max_size=len(people), + ) + ) + dataset = UKMultiYearDataset( + datasets=[ + year_dataset(people, DATA_YEAR), + year_dataset(people, period, ages=ages), + ] + ) + sim = Microsimulation(dataset=dataset) + assert_components_add_up(sim, people, (DATA_YEAR, period)) diff --git a/policyengine_uk/variables/gov/dwp/additional_state_pension.py b/policyengine_uk/variables/gov/dwp/additional_state_pension.py index 0467ec7d3e..9dd31f7c5d 100644 --- a/policyengine_uk/variables/gov/dwp/additional_state_pension.py +++ b/policyengine_uk/variables/gov/dwp/additional_state_pension.py @@ -19,7 +19,14 @@ def formula(person, period, parameters): else: data_year = period.start.year reported = person("state_pension_reported", data_year) / WEEKS_IN_YEAR - pension_type = person("state_pension_type", data_year) + # Split by the period's type, as basic_state_pension and + # new_state_pension do, so the three components add up to the + # reported amount. Survey ages are held fixed across years, so a + # record's birth cohort, and with it its type, can differ from the + # data year's. The data year's type would pay the band between the + # two flat-rate ceilings twice for a record that is BASIC in the data + # year and NEW in the period, and leave it unpaid the other way round. + pension_type = person("state_pension_type", period) types = pension_type.possible_values bsp_amount = parameters.gov.dwp.state_pension.basic_state_pension.amount @@ -51,7 +58,7 @@ def formula(person, period, parameters): max_for_type_period / max_for_type_data, 1, ) - # No State Pension is paid before State Pension age; the data-year - # type above only splits the reported amount into its components. + # No State Pension is paid before State Pension age. A computed type + # is already NONE there; this also holds if the type is an input. is_sp_age = person("is_SP_age", period) return is_sp_age * amount_in_data_year * uprating * WEEKS_IN_YEAR