From f3115ae17722fca3c817f6b6b0ee485f75f4a1e4 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 30 Sep 2026 13:34:23 -0400 Subject: [PATCH 1/3] Store the tax credit severe disability flag, not a mix of premium tests `is_severely_disabled_for_benefits` is what policyengine-uk uses for the Child Tax Credit and Working Tax Credit severe disability elements (CTC Regs 2002 reg 8; WTC Regs 2002 reg 17). Those count the DLA care component at the highest rate, the PIP daily living component at the enhanced rate, higher-rate Attendance Allowance (WTC) and Armed Forces Independence Payment. The stored flag also counted lower-rate Attendance Allowance and FRS code 8, which covers every Armed Forces Compensation Scheme payment (including war disablement pensions and guaranteed income payments), not Armed Forces Independence Payment, which the FRS does not code. The legacy severe disability premium's wider list (any Attendance Allowance, DLA care middle rate, PIP daily living standard rate) is now read from the benefit categories in policyengine-uk rather than from this flag. A grid test pins the stored flag to the tax credit condition on the categories the same amounts map to. Co-Authored-By: Claude Opus 5.5 --- ...bility-flag-tax-credit-definition.fixed.md | 1 + .../datasets/disability_benefits.py | 15 +++- .../tests/test_disability_benefits.py | 72 ++++++++++++++++++- 3 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 changelog.d/severe-disability-flag-tax-credit-definition.fixed.md diff --git a/changelog.d/severe-disability-flag-tax-credit-definition.fixed.md b/changelog.d/severe-disability-flag-tax-credit-definition.fixed.md new file mode 100644 index 00000000..f859ed38 --- /dev/null +++ b/changelog.d/severe-disability-flag-tax-credit-definition.fixed.md @@ -0,0 +1 @@ +Set the stored `is_severely_disabled_for_benefits` flag to the Child Tax Credit and Working Tax Credit severe disability condition policyengine-uk uses it for (DLA care highest rate, PIP daily living enhanced rate, higher-rate Attendance Allowance). Lower-rate Attendance Allowance and FRS code 8 (all Armed Forces Compensation Scheme payments, not Armed Forces Independence Payment) no longer set it. The legacy severe disability premium reads the benefit categories in policyengine-uk instead. diff --git a/policyengine_uk_data/datasets/disability_benefits.py b/policyengine_uk_data/datasets/disability_benefits.py index 5aec6c05..9bbac8d9 100644 --- a/policyengine_uk_data/datasets/disability_benefits.py +++ b/policyengine_uk_data/datasets/disability_benefits.py @@ -193,7 +193,6 @@ def add_disability_benefit_flags_from_reported_amounts( attendance_allowance = _reported_amount(person, "attendance_allowance_reported") dla_sc = _reported_amount(person, "dla_sc_reported") pip_dl = _reported_amount(person, "pip_dl_reported") - afcs = _reported_amount(person, "afcs_reported") person["is_disabled_for_benefits"] = ( _reported_amount_sum(person, BASE_DISABILITY_FLAG_REPORTED_AMOUNT_COLUMNS) > 0 @@ -218,11 +217,21 @@ def add_disability_benefit_flags_from_reported_amounts( | (dla_sc > dla_sc_higher) | (pip_dl >= pip_dl_enhanced) ) + # The Child Tax Credit and Working Tax Credit severe disability conditions + # (CTC Regs 2002 reg 8; WTC Regs 2002 reg 17), which is what + # policyengine-uk uses this flag for: DLA care at the highest rate, PIP + # daily living at the enhanced rate, higher-rate Attendance Allowance, or + # armed forces independence payment. The FRS has no code for armed forces + # independence payment. Code 8 (`afcs_reported`) covers every Armed Forces + # Compensation Scheme payment, including war disablement pensions and + # guaranteed income payments, so it cannot stand in for it. The legacy + # severe disability premium's wider list (any Attendance Allowance, DLA + # care at the middle rate, PIP daily living at the standard rate) is read + # by policyengine-uk from the benefit categories, not from this flag. person["is_severely_disabled_for_benefits"] = ( - (attendance_allowance > 0) + (attendance_allowance >= aa_higher) | (dla_sc >= dla_sc_higher) | (pip_dl >= pip_dl_enhanced) - | (afcs > 0) ) return person diff --git a/policyengine_uk_data/tests/test_disability_benefits.py b/policyengine_uk_data/tests/test_disability_benefits.py index e946a876..859063b9 100644 --- a/policyengine_uk_data/tests/test_disability_benefits.py +++ b/policyengine_uk_data/tests/test_disability_benefits.py @@ -1,5 +1,7 @@ from __future__ import annotations +from itertools import product + import pandas as pd from policyengine_uk import CountryTaxBenefitSystem from policyengine_uk.data import UKSingleYearDataset @@ -163,7 +165,75 @@ def test_attendance_allowance_feeds_stronger_disability_flags(): assert result["is_disabled_for_benefits"].tolist() == [True, True] assert result["is_enhanced_disabled_for_benefits"].tolist() == [False, True] - assert result["is_severely_disabled_for_benefits"].tolist() == [True, True] + # WTC Regs 2002 reg 17(2): only the higher rate is a severe disability. + assert result["is_severely_disabled_for_benefits"].tolist() == [False, True] + + +def test_armed_forces_compensation_scheme_is_not_a_severe_disability(): + # FRS code 8 covers every Armed Forces Compensation Scheme payment + # (including war disablement pensions and guaranteed income payments); + # only armed forces independence payment is a severe disability under CTC + # Regs 2002 reg 8(5) and WTC Regs 2002 reg 17(4), and the FRS has no code + # for it. + person = pd.DataFrame({"afcs_reported": [1_000.0]}) + + result = add_disability_benefit_flags_from_reported_amounts(person, 2025) + + assert result["is_disabled_for_benefits"].tolist() == [True] + assert result["is_severely_disabled_for_benefits"].tolist() == [False] + + +def test_severe_flag_is_the_tax_credit_condition_on_the_categories(): + # The stored flag must equal the tax credit severe disability condition + # (DLA care highest, PIP daily living enhanced, Attendance Allowance + # higher) read off the categories the same amounts map to, for every + # combination of rates and near-threshold amounts. DLA care at the middle + # rate and PIP daily living at the standard rate are not severe for tax + # credits; the legacy severe disability premium reads them from the + # categories in policyengine-uk. + year = 2025 + dwp = CountryTaxBenefitSystem().parameters(year).gov.dwp + weeks = SURVEY_REPORTED_AMOUNT_WEEKS_IN_YEAR + offsets = (0.0, -0.5, -1.5, 0.5) + + def amounts(rates): + return sorted( + {0.0} | {max(0.0, (float(r) + o) * weeks) for r in rates for o in offsets} + ) + + grid = list( + product( + amounts([dwp.attendance_allowance.lower, dwp.attendance_allowance.higher]), + amounts( + [ + dwp.dla.self_care.lower, + dwp.dla.self_care.middle, + dwp.dla.self_care.higher, + ] + ), + amounts([dwp.pip.daily_living.standard, dwp.pip.daily_living.enhanced]), + ) + ) + person = pd.DataFrame( + grid, + columns=[ + "attendance_allowance_reported", + "dla_sc_reported", + "pip_dl_reported", + ], + ) + + categories = add_disability_benefit_categories_from_reported_amounts(person, year) + flags = add_disability_benefit_flags_from_reported_amounts(person, year) + + expected = ( + (categories["aa_category"] == "HIGHER") + | (categories["dla_sc_category"] == "HIGHER") + | (categories["pip_dl_category"] == "ENHANCED") + ) + assert (flags["is_severely_disabled_for_benefits"] == expected).all() + assert (categories["dla_sc_category"] == "MIDDLE").any() + assert (categories["pip_dl_category"] == "STANDARD").any() def test_categories_and_flags_share_the_survey_fiscal_year_rates(): From 06a2d80f4328f812f276aef8c4fac98f47bc64a6 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 30 Sep 2026 16:31:47 -0400 Subject: [PATCH 2/3] Derive the severe flag with the category rule and test it against the model The stored flag and the benefit categories used two different floating-point comparisons, which disagreed at exactly the GBP 1/week tolerance edge (for example survey year 2022 at rate minus GBP 1.00). The flag now uses the category derivation itself, so the two agree by construction. The grid test covers survey years 2019-2026 and offsets either side of the tolerance edge, and fails on the old comparison. A differential test checks the stored flag against policyengine-uk's formula for the same categories; it skips on policyengine-uk releases that predate PolicyEngine/policyengine-uk#1946. Co-Authored-By: Claude Opus 5.5 --- ...bility-flag-tax-credit-definition.fixed.md | 2 +- .../datasets/disability_benefits.py | 43 ++++-- .../tests/test_disability_benefits.py | 139 ++++++++++++++---- 3 files changed, 138 insertions(+), 46 deletions(-) diff --git a/changelog.d/severe-disability-flag-tax-credit-definition.fixed.md b/changelog.d/severe-disability-flag-tax-credit-definition.fixed.md index f859ed38..ff6ead9b 100644 --- a/changelog.d/severe-disability-flag-tax-credit-definition.fixed.md +++ b/changelog.d/severe-disability-flag-tax-credit-definition.fixed.md @@ -1 +1 @@ -Set the stored `is_severely_disabled_for_benefits` flag to the Child Tax Credit and Working Tax Credit severe disability condition policyengine-uk uses it for (DLA care highest rate, PIP daily living enhanced rate, higher-rate Attendance Allowance). Lower-rate Attendance Allowance and FRS code 8 (all Armed Forces Compensation Scheme payments, not Armed Forces Independence Payment) no longer set it. The legacy severe disability premium reads the benefit categories in policyengine-uk instead. +Set the stored `is_severely_disabled_for_benefits` flag to the severe disability condition policyengine-uk uses it for: the Child Tax Credit disability element, the Working Tax Credit severe disability element and the Universal Credit higher disabled child addition (DLA care highest rate, PIP daily living enhanced rate, higher-rate Attendance Allowance). It is derived with the category rule, so it agrees with the stored benefit categories by construction. Lower-rate Attendance Allowance and FRS code 8 no longer set it; code 8 covers every Armed Forces Compensation Scheme and war disablement pension payment, and Armed Forces Independence Payment cannot be separated from it. From policyengine-uk's release with PolicyEngine/policyengine-uk#1946, the legacy severe disability premium reads the benefit categories instead of this flag. diff --git a/policyengine_uk_data/datasets/disability_benefits.py b/policyengine_uk_data/datasets/disability_benefits.py index 9bbac8d9..b3ee8c67 100644 --- a/policyengine_uk_data/datasets/disability_benefits.py +++ b/policyengine_uk_data/datasets/disability_benefits.py @@ -112,6 +112,18 @@ def _category_from_reported_amount( return category +def _reaches_weekly_rate(reported_amount: pd.Series, weekly_rate: float) -> pd.Series: + """Whether reported amounts reach a weekly rate under the category rule. + + Uses the category derivation itself, so a flag built from this agrees with + the categories by construction, including at the tolerance boundary. + """ + category = _category_from_reported_amount( + reported_amount, (("REACHED", weekly_rate),) + ) + return pd.Series(category == "REACHED", index=reported_amount.index) + + def add_disability_benefit_categories_from_reported_amounts( person: pd.DataFrame, year: int, @@ -217,21 +229,26 @@ def add_disability_benefit_flags_from_reported_amounts( | (dla_sc > dla_sc_higher) | (pip_dl >= pip_dl_enhanced) ) - # The Child Tax Credit and Working Tax Credit severe disability conditions - # (CTC Regs 2002 reg 8; WTC Regs 2002 reg 17), which is what + # The severe disability conditions of the Child Tax Credit disability + # element (CTC Regs 2002 reg 8), the Working Tax Credit severe disability + # element (WTC Regs 2002 reg 17) and the Universal Credit higher disabled + # child addition (UC Regs 2013 reg 24(2)(b)), which is what # policyengine-uk uses this flag for: DLA care at the highest rate, PIP - # daily living at the enhanced rate, higher-rate Attendance Allowance, or - # armed forces independence payment. The FRS has no code for armed forces - # independence payment. Code 8 (`afcs_reported`) covers every Armed Forces - # Compensation Scheme payment, including war disablement pensions and - # guaranteed income payments, so it cannot stand in for it. The legacy - # severe disability premium's wider list (any Attendance Allowance, DLA - # care at the middle rate, PIP daily living at the standard rate) is read - # by policyengine-uk from the benefit categories, not from this flag. + # daily living at the enhanced rate, higher-rate Attendance Allowance (a + # WTC condition; children cannot receive it), or armed forces independence + # payment. FRS code 8 (`afcs_reported`) covers every Armed Forces + # Compensation Scheme and war disablement pension payment, including the + # guaranteed income payment every AFIP recipient also receives; AFIP has + # no code of its own and cannot be separated out, so AFIP recipients are + # not flagged (a known under-count). The legacy severe disability + # premium's wider list (any Attendance Allowance, DLA care at the middle + # rate, PIP daily living at the standard rate) is read by policyengine-uk + # from the benefit categories, not from this flag. The flag uses the + # category rule, so it agrees with the stored categories by construction. person["is_severely_disabled_for_benefits"] = ( - (attendance_allowance >= aa_higher) - | (dla_sc >= dla_sc_higher) - | (pip_dl >= pip_dl_enhanced) + _reaches_weekly_rate(attendance_allowance, dwp.attendance_allowance.higher) + | _reaches_weekly_rate(dla_sc, dwp.dla.self_care.higher) + | _reaches_weekly_rate(pip_dl, dwp.pip.daily_living.enhanced) ) return person diff --git a/policyengine_uk_data/tests/test_disability_benefits.py b/policyengine_uk_data/tests/test_disability_benefits.py index 859063b9..4de17d3d 100644 --- a/policyengine_uk_data/tests/test_disability_benefits.py +++ b/policyengine_uk_data/tests/test_disability_benefits.py @@ -3,6 +3,7 @@ from itertools import product import pandas as pd +import pytest from policyengine_uk import CountryTaxBenefitSystem from policyengine_uk.data import UKSingleYearDataset @@ -183,39 +184,41 @@ def test_armed_forces_compensation_scheme_is_not_a_severe_disability(): assert result["is_severely_disabled_for_benefits"].tolist() == [False] -def test_severe_flag_is_the_tax_credit_condition_on_the_categories(): - # The stored flag must equal the tax credit severe disability condition - # (DLA care highest, PIP daily living enhanced, Attendance Allowance - # higher) read off the categories the same amounts map to, for every - # combination of rates and near-threshold amounts. DLA care at the middle - # rate and PIP daily living at the standard rate are not severe for tax - # credits; the legacy severe disability premium reads them from the - # categories in policyengine-uk. - year = 2025 +SEVERE_GRID_YEARS = range(2019, 2027) +# Offsets in GBP a week around each rate, including the GBP 1 tolerance edge. +SEVERE_GRID_OFFSETS = (0.0, 0.5, -0.5, -0.99, -1.0, -1.01, -1.5) + + +def _severe_grid(year): dwp = CountryTaxBenefitSystem().parameters(year).gov.dwp weeks = SURVEY_REPORTED_AMOUNT_WEEKS_IN_YEAR - offsets = (0.0, -0.5, -1.5, 0.5) def amounts(rates): return sorted( - {0.0} | {max(0.0, (float(r) + o) * weeks) for r in rates for o in offsets} + {0.0} + | { + max(0.0, (float(rate) + offset) * weeks) + for rate in rates + for offset in SEVERE_GRID_OFFSETS + } ) - grid = list( - product( - amounts([dwp.attendance_allowance.lower, dwp.attendance_allowance.higher]), - amounts( - [ - dwp.dla.self_care.lower, - dwp.dla.self_care.middle, - dwp.dla.self_care.higher, - ] - ), - amounts([dwp.pip.daily_living.standard, dwp.pip.daily_living.enhanced]), - ) - ) - person = pd.DataFrame( - grid, + return pd.DataFrame( + list( + product( + amounts( + [dwp.attendance_allowance.lower, dwp.attendance_allowance.higher] + ), + amounts( + [ + dwp.dla.self_care.lower, + dwp.dla.self_care.middle, + dwp.dla.self_care.higher, + ] + ), + amounts([dwp.pip.daily_living.standard, dwp.pip.daily_living.enhanced]), + ) + ), columns=[ "attendance_allowance_reported", "dla_sc_reported", @@ -223,17 +226,89 @@ def amounts(rates): ], ) - categories = add_disability_benefit_categories_from_reported_amounts(person, year) - flags = add_disability_benefit_flags_from_reported_amounts(person, year) - expected = ( +def _tax_credit_severe_condition(categories): + return ( (categories["aa_category"] == "HIGHER") | (categories["dla_sc_category"] == "HIGHER") | (categories["pip_dl_category"] == "ENHANCED") ) - assert (flags["is_severely_disabled_for_benefits"] == expected).all() - assert (categories["dla_sc_category"] == "MIDDLE").any() - assert (categories["pip_dl_category"] == "STANDARD").any() + + +def test_severe_flag_is_the_tax_credit_condition_on_the_categories(): + # The stored flag must equal the tax credit severe disability condition + # (DLA care highest, PIP daily living enhanced, Attendance Allowance + # higher) read off the categories the same amounts map to, for every + # combination of rates and amounts around each rate, including the + # GBP 1/week tolerance edge, in every survey year. DLA care at the middle + # rate and PIP daily living at the standard rate are not severe for tax + # credits; the legacy severe disability premium reads them from the + # categories in policyengine-uk. + for year in SEVERE_GRID_YEARS: + person = _severe_grid(year) + categories = add_disability_benefit_categories_from_reported_amounts( + person, year + ) + flags = add_disability_benefit_flags_from_reported_amounts(person, year) + + expected = _tax_credit_severe_condition(categories) + assert (flags["is_severely_disabled_for_benefits"] == expected).all(), year + assert (categories["dla_sc_category"] == "MIDDLE").any() + assert (categories["pip_dl_category"] == "STANDARD").any() + assert expected.any() and not expected.all() + + +def test_severe_flag_matches_the_policyengine_uk_formula(): + # Differential: the stored flag and policyengine-uk's formula for + # is_severely_disabled_for_benefits, given the same categories, must agree. + # policyengine-uk releases before PolicyEngine/policyengine-uk#1946 still + # count any AFCS payment and omit higher-rate Attendance Allowance, so the + # check applies once the pinned policyengine-uk has that definition. + from policyengine_uk import Simulation + + year = 2025 + + def model_flags(combos): + people = { + f"p{i}": { + "aa_category": {year: aa}, + "dla_sc_category": {year: dla}, + "pip_dl_category": {year: pip}, + } + for i, (aa, dla, pip) in enumerate(combos) + } + simulation = Simulation(situation={"people": people}) + return simulation.calculate("is_severely_disabled_for_benefits", year) + + if not model_flags([("HIGHER", "NONE", "NONE")])[0]: + pytest.skip( + "The installed policyengine-uk predates the tax credit severe " + "disability definition (PolicyEngine/policyengine-uk#1946)." + ) + + person = _severe_grid(year) + categories = add_disability_benefit_categories_from_reported_amounts(person, year) + flags = add_disability_benefit_flags_from_reported_amounts(person, year) + combos = sorted( + set( + zip( + categories["aa_category"], + categories["dla_sc_category"], + categories["pip_dl_category"], + ) + ) + ) + model = dict(zip(combos, model_flags(combos))) + stored = flags["is_severely_disabled_for_benefits"] + for combo, value in zip( + zip( + categories["aa_category"], + categories["dla_sc_category"], + categories["pip_dl_category"], + ), + stored, + ): + assert bool(model[combo]) == bool(value), combo def test_categories_and_flags_share_the_survey_fiscal_year_rates(): From 83e338a666880c08898bb76ab59cffc0cc508077 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 30 Sep 2026 18:18:31 -0400 Subject: [PATCH 3/3] Make the model differential fail until the lock includes #1946 The differential test between the stored flag and policyengine-uk's formula no longer skips on older policyengine-uk releases: it fails until the lock includes PolicyEngine/policyengine-uk#1946, which keeps the new flag from shipping with a model that still reads it for the severe disability premium. The code comment and changelog now describe the flag as the tax credit condition and note that UC's higher disabled child addition, which is also keyed on it, differs (UC Regs reg 24(2)(b)). Co-Authored-By: Claude Opus 5.5 --- ...bility-flag-tax-credit-definition.fixed.md | 2 +- .../datasets/disability_benefits.py | 32 +++++++++---------- .../tests/test_disability_benefits.py | 14 +++----- 3 files changed, 21 insertions(+), 27 deletions(-) diff --git a/changelog.d/severe-disability-flag-tax-credit-definition.fixed.md b/changelog.d/severe-disability-flag-tax-credit-definition.fixed.md index ff6ead9b..8222631c 100644 --- a/changelog.d/severe-disability-flag-tax-credit-definition.fixed.md +++ b/changelog.d/severe-disability-flag-tax-credit-definition.fixed.md @@ -1 +1 @@ -Set the stored `is_severely_disabled_for_benefits` flag to the severe disability condition policyengine-uk uses it for: the Child Tax Credit disability element, the Working Tax Credit severe disability element and the Universal Credit higher disabled child addition (DLA care highest rate, PIP daily living enhanced rate, higher-rate Attendance Allowance). It is derived with the category rule, so it agrees with the stored benefit categories by construction. Lower-rate Attendance Allowance and FRS code 8 no longer set it; code 8 covers every Armed Forces Compensation Scheme and war disablement pension payment, and Armed Forces Independence Payment cannot be separated from it. From policyengine-uk's release with PolicyEngine/policyengine-uk#1946, the legacy severe disability premium reads the benefit categories instead of this flag. +Set the stored `is_severely_disabled_for_benefits` flag to the tax credit severe disability condition (CTC Regs 2002 reg 8(3)-(5); WTC Regs 2002 reg 17(2)-(4)): DLA care highest rate, PIP daily living enhanced rate, higher-rate Attendance Allowance. It is derived with the category rule, so it agrees with the stored benefit categories by construction. Lower-rate Attendance Allowance and FRS code 8 no longer set it; code 8 covers every Armed Forces Compensation Scheme and war disablement pension payment, and Armed Forces Independence Payment cannot be separated from it. policyengine-uk also keys the Universal Credit higher disabled child addition on this flag, although UC Regs 2013 reg 24(2)(b) differs (it adds blindness and has no AFIP limb). From policyengine-uk's release with PolicyEngine/policyengine-uk#1946, the legacy severe disability premium reads the benefit categories instead of this flag. diff --git a/policyengine_uk_data/datasets/disability_benefits.py b/policyengine_uk_data/datasets/disability_benefits.py index b3ee8c67..6dd83461 100644 --- a/policyengine_uk_data/datasets/disability_benefits.py +++ b/policyengine_uk_data/datasets/disability_benefits.py @@ -229,22 +229,22 @@ def add_disability_benefit_flags_from_reported_amounts( | (dla_sc > dla_sc_higher) | (pip_dl >= pip_dl_enhanced) ) - # The severe disability conditions of the Child Tax Credit disability - # element (CTC Regs 2002 reg 8), the Working Tax Credit severe disability - # element (WTC Regs 2002 reg 17) and the Universal Credit higher disabled - # child addition (UC Regs 2013 reg 24(2)(b)), which is what - # policyengine-uk uses this flag for: DLA care at the highest rate, PIP - # daily living at the enhanced rate, higher-rate Attendance Allowance (a - # WTC condition; children cannot receive it), or armed forces independence - # payment. FRS code 8 (`afcs_reported`) covers every Armed Forces - # Compensation Scheme and war disablement pension payment, including the - # guaranteed income payment every AFIP recipient also receives; AFIP has - # no code of its own and cannot be separated out, so AFIP recipients are - # not flagged (a known under-count). The legacy severe disability - # premium's wider list (any Attendance Allowance, DLA care at the middle - # rate, PIP daily living at the standard rate) is read by policyengine-uk - # from the benefit categories, not from this flag. The flag uses the - # category rule, so it agrees with the stored categories by construction. + # The tax credit severe disability condition (CTC Regs 2002 reg 8(3)-(5); + # WTC Regs 2002 reg 17(2)-(4)): DLA care at the highest rate, PIP daily + # living at the enhanced rate, higher-rate Attendance Allowance (a WTC + # condition; children cannot receive it), or armed forces independence + # payment. policyengine-uk also keys the Universal Credit higher disabled + # child addition on this flag, although UC Regs 2013 reg 24(2)(b) differs + # (it adds blindness and has no AFIP limb). FRS code 8 (`afcs_reported`) + # covers every Armed Forces Compensation Scheme and war disablement pension + # payment, including the guaranteed income payment every AFIP recipient + # also receives; AFIP has no code of its own and cannot be separated out, + # so AFIP recipients are not flagged (a known under-count). The legacy + # severe disability premium's wider list (any Attendance Allowance, DLA + # care at the middle rate, PIP daily living at the standard rate) is read + # by policyengine-uk from the benefit categories, not from this flag. The + # flag uses the category rule, so it agrees with the stored categories by + # construction. person["is_severely_disabled_for_benefits"] = ( _reaches_weekly_rate(attendance_allowance, dwp.attendance_allowance.higher) | _reaches_weekly_rate(dla_sc, dwp.dla.self_care.higher) diff --git a/policyengine_uk_data/tests/test_disability_benefits.py b/policyengine_uk_data/tests/test_disability_benefits.py index 4de17d3d..ba0e2aa4 100644 --- a/policyengine_uk_data/tests/test_disability_benefits.py +++ b/policyengine_uk_data/tests/test_disability_benefits.py @@ -3,7 +3,6 @@ from itertools import product import pandas as pd -import pytest from policyengine_uk import CountryTaxBenefitSystem from policyengine_uk.data import UKSingleYearDataset @@ -261,9 +260,10 @@ def test_severe_flag_is_the_tax_credit_condition_on_the_categories(): def test_severe_flag_matches_the_policyengine_uk_formula(): # Differential: the stored flag and policyengine-uk's formula for # is_severely_disabled_for_benefits, given the same categories, must agree. - # policyengine-uk releases before PolicyEngine/policyengine-uk#1946 still - # count any AFCS payment and omit higher-rate Attendance Allowance, so the - # check applies once the pinned policyengine-uk has that definition. + # policyengine-uk releases before PolicyEngine/policyengine-uk#1946 count + # any AFCS payment and omit higher-rate Attendance Allowance, so this test + # fails until the lock includes that release; that failure is what keeps + # the new flag from shipping with an older model. from policyengine_uk import Simulation year = 2025 @@ -280,12 +280,6 @@ def model_flags(combos): simulation = Simulation(situation={"people": people}) return simulation.calculate("is_severely_disabled_for_benefits", year) - if not model_flags([("HIGHER", "NONE", "NONE")])[0]: - pytest.skip( - "The installed policyengine-uk predates the tax credit severe " - "disability definition (PolicyEngine/policyengine-uk#1946)." - ) - person = _severe_grid(year) categories = add_disability_benefit_categories_from_reported_amounts(person, year) flags = add_disability_benefit_flags_from_reported_amounts(person, year)