diff --git a/changelog.d/1793.fixed.md b/changelog.d/1793.fixed.md new file mode 100644 index 0000000000..167067ae56 --- /dev/null +++ b/changelog.d/1793.fixed.md @@ -0,0 +1 @@ +Applied the minimum ages to computed Carer's Allowance, Carer Support Payment and DLA mobility awards, preserving DLA care and explicit amount overrides. diff --git a/changelog.d/2474-combined-csp-gates.fixed.md b/changelog.d/2474-combined-csp-gates.fixed.md new file mode 100644 index 0000000000..c20958307b --- /dev/null +++ b/changelog.d/2474-combined-csp-gates.fixed.md @@ -0,0 +1 @@ +Retain both the minimum-age and earnings conditions when combining Carer Support Payment fixes. diff --git a/docs/book/assumptions/benefit-minimum-ages.md b/docs/book/assumptions/benefit-minimum-ages.md new file mode 100644 index 0000000000..01b444e4aa --- /dev/null +++ b/docs/book/assumptions/benefit-minimum-ages.md @@ -0,0 +1,81 @@ +# Benefit minimum ages + +Computed Carer's Allowance, Carer Support Payment (CSP) and Disability Living +Allowance (DLA) mobility awards apply these minimum ages to each person's existing +annual `age` input: + +| Computed benefit | Minimum age | +| --- | ---: | +| Carer's Allowance | 16 | +| CSP | 16 | +| Higher DLA mobility component | 3 | +| Lower DLA mobility component | 5 | + +The Carer's Allowance condition appears in section 70(3) of the +[Great Britain Act](https://www.legislation.gov.uk/ukpga/1992/4/section/70) and its +[Northern Ireland counterpart](https://www.legislation.gov.uk/ukpga/1992/7/section/70). +CSP's condition is in +[regulation 4(1)](https://www.legislation.gov.uk/ssi/2023/302/regulation/4). +The DLA mobility ages and corresponding higher/lower routes appear in sections +73(1), (1A) and (11) of the +[Great Britain Act](https://www.legislation.gov.uk/ukpga/1992/4/section/73) and +[Northern Ireland Act](https://www.legislation.gov.uk/ukpga/1992/7/section/73). +Other qualifying conditions remain necessary. These gates do not implement a full +eligibility assessment or change education, caring, disability, duration, upper-age +or programme-replacement rules. + +## Reported inputs and amount overrides + +A positive `carers_allowance_reported` value or sufficient caring hours cannot +bypass the age condition in the computed CA/CSP pre-overlap amount. A DLA mobility +category similarly cannot bypass its category's age condition. The raw reported +amounts and categories remain stored inputs. + +Explicitly supplying a pre-overlap or final amount keeps the model's generic input +override behavior. For example, an explicit `carers_allowance` or `dla_m` amount +can override its formula. Such an override is not evidence of statutory +entitlement. Report-only positive carer cases also preserve an existing proxy; +reported receipt does not legally waive the qualifying-care requirements. + +DLA care has no categorical minimum-age floor. Its separate needs, duration and +child-comparison conditions still apply, as set out in +[section 72](https://www.legislation.gov.uk/ukpga/1992/4/section/72) and its +[Northern Ireland counterpart](https://www.legislation.gov.uk/ukpga/1992/7/section/72). +A care category for an infant assumes those conditions; it is not automatic +entitlement. DLA totals continue to add the care and mobility components. + +## Dates and the annual model + +The CA age parameter begins on 1 January 2015 as the model's existing coverage +anchor. This is not a claim that the legal age condition commenced in 2015. +CSP's raw age parameter starts on 19 November 2023, the commencement of regulation +4. The DLA mobility parameters start on 9 April 2001, as recorded in the Acts' +amendment annotations. These dates do not fill missing historical benefit rates +or expand the existing Scottish CA-to-CSP model switch from 2025. + +`age` is a Person/YEAR floating-point stock, with an existing default of 40 when +omitted. The gates compare it directly, without flooring or rounding fractional +ages. Awards retain the existing annual rate-times-52 convention. They do not +calculate exact birthdays, part-year awards or award weeks. In particular, this +does not newly implement CSP regulation 4(2)'s leap-day birthday rule. + +At the unchanged 2026 rates, an otherwise qualifying carer aged 16 receives +£4,495.40 before overlap; an under-16 computed award is zero. Higher and lower DLA +mobility amounts are respectively £4,160 and £1,575.60 annually once the relevant +age floor is met. These conditional examples do not estimate a population effect +or establish independently encoded parity. + +## Combined CSP conditions + +This change is stacked on the repaired [PR #2473](https://github.com/PolicyEngine/policyengine-uk/pull/2473). +Both the age condition and the earnings condition apply to computed CSP, including +the reported-Carer’s-Allowance input proxy. Natural-pay cases combine age15, +fractional ages below16, age16, the exact annual earnings limit and favorable +fractional-penny treatment. The shared reader retains merged [PR #2444](https://github.com/PolicyEngine/policyengine-uk/pull/2444) +NI-age deductions. Existing annual/award-week, PAYE and care-charge qualifications +remain in the [CSP earnings assumptions](carer-support-payment-earnings.md). + +Only after #2473 actually merges should this branch be retargeted and revalidated +on current main; a queue entry or passing checks do not establish that landing. +The shared-parameter change in open [PR #2518](https://github.com/PolicyEngine/policyengine-uk/pull/2518) +requires future CSP-reader/prose coordination; it is not copied or repaired here. diff --git a/policyengine_uk/parameters/gov/dwp/carers_allowance/min_age.yaml b/policyengine_uk/parameters/gov/dwp/carers_allowance/min_age.yaml new file mode 100644 index 0000000000..860c60873a --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/carers_allowance/min_age.yaml @@ -0,0 +1,14 @@ +description: Minimum age for computed Carer's Allowance entitlement. +# Existing model coverage anchor, not the statutory commencement date. +# The age condition predates the model's supported 2015 fiscal year. +values: + 2015-01-01: 16 +metadata: + unit: year + period: year + label: Carer's Allowance minimum age + reference: + - title: Social Security Contributions and Benefits Act 1992, section 70(3) + href: https://www.legislation.gov.uk/ukpga/1992/4/section/70 + - title: Social Security Contributions and Benefits (Northern Ireland) Act 1992, section 70(3) + href: https://www.legislation.gov.uk/ukpga/1992/7/section/70 diff --git a/policyengine_uk/parameters/gov/dwp/dla/mobility/higher_min_age.yaml b/policyengine_uk/parameters/gov/dwp/dla/mobility/higher_min_age.yaml new file mode 100644 index 0000000000..4fcf8ad82d --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/dla/mobility/higher_min_age.yaml @@ -0,0 +1,12 @@ +description: Minimum age for the higher DLA mobility component. +values: + 2001-04-09: 3 +metadata: + unit: year + period: year + label: Higher DLA mobility minimum age + reference: + - title: Social Security Contributions and Benefits Act 1992, section 73(1A)(a) and (11) + href: https://www.legislation.gov.uk/ukpga/1992/4/section/73 + - title: Social Security Contributions and Benefits (Northern Ireland) Act 1992, section 73(1A)(a) and (11) + href: https://www.legislation.gov.uk/ukpga/1992/7/section/73 diff --git a/policyengine_uk/parameters/gov/dwp/dla/mobility/lower_min_age.yaml b/policyengine_uk/parameters/gov/dwp/dla/mobility/lower_min_age.yaml new file mode 100644 index 0000000000..ceb4c75c13 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/dla/mobility/lower_min_age.yaml @@ -0,0 +1,12 @@ +description: Minimum age for the lower DLA mobility component. +values: + 2001-04-09: 5 +metadata: + unit: year + period: year + label: Lower DLA mobility minimum age + reference: + - title: Social Security Contributions and Benefits Act 1992, section 73(1A)(b) and (11) + href: https://www.legislation.gov.uk/ukpga/1992/4/section/73 + - title: Social Security Contributions and Benefits (Northern Ireland) Act 1992, section 73(1A)(b) and (11) + href: https://www.legislation.gov.uk/ukpga/1992/7/section/73 diff --git a/policyengine_uk/parameters/gov/social_security_scotland/carer_support_payment/min_age.yaml b/policyengine_uk/parameters/gov/social_security_scotland/carer_support_payment/min_age.yaml new file mode 100644 index 0000000000..de802d9190 --- /dev/null +++ b/policyengine_uk/parameters/gov/social_security_scotland/carer_support_payment/min_age.yaml @@ -0,0 +1,10 @@ +description: Minimum age for computed Carer Support Payment entitlement. +values: + 2023-11-19: 16 +metadata: + unit: year + period: year + label: Carer Support Payment minimum age + reference: + - title: The Carer's Assistance (Carer Support Payment) (Scotland) Regulations 2023, regulation 4(1) + href: https://www.legislation.gov.uk/ssi/2023/302/regulation/4 diff --git a/policyengine_uk/tests/policy/integration/carer_and_dla_minimum_ages.yaml b/policyengine_uk/tests/policy/integration/carer_and_dla_minimum_ages.yaml new file mode 100644 index 0000000000..b3ad82b939 --- /dev/null +++ b/policyengine_uk/tests/policy/integration/carer_and_dla_minimum_ages.yaml @@ -0,0 +1,484 @@ +# Conditional age-boundary cash cases, with the existing annual age/rate convention. +# Positive carers otherwise meet the qualifying-care/disability/duration conditions, +# have no earnings/overlapping personal benefit, and are not in full-time education. +# Report-only adult cases retain an existing input proxy, not a waiver of care rules. +# DLA care categories assume the separate needs/duration conditions; infant care is +# not automatic. No final amount, pre-overlap, earnings or eligibility is supplied. +- name: Reported Carers Allowance cannot pay a fifteen year old + period: 2026 + absolute_error_margin: 0.01 + input: + people: + caree: + age: 50 + pip_dl_category: STANDARD + carer: + age: 15 + care_hours: 0 + carers_allowance_reported: 4000 + benunits: + family: + members: + - caree + - carer + households: + home: + members: + - caree + - carer + country: ENGLAND + output: + carers_allowance: + - 0 + - 0 + carer_support_payment: + - 0 + - 0 +- name: Thirty five caring hours cannot pay Carers Allowance below sixteen + period: 2026 + absolute_error_margin: 0.01 + input: + people: + caree: + age: 50 + pip_dl_category: STANDARD + carer: + age: 15 + care_hours: 35 + benunits: + family: + members: + - caree + - carer + households: + home: + members: + - caree + - carer + country: ENGLAND + output: + carers_allowance: + - 0 + - 0 + carer_support_payment: + - 0 + - 0 +- name: Reported Carers Allowance retains the existing proxy at sixteen + period: 2026 + absolute_error_margin: 0.01 + input: + people: + caree: + age: 50 + pip_dl_category: STANDARD + carer: + age: 16 + care_hours: 0 + carers_allowance_reported: 4000 + benunits: + family: + members: + - caree + - carer + households: + home: + members: + - caree + - carer + country: ENGLAND + output: + carers_allowance: + - 0 + - 4495.4 + carer_support_payment: + - 0 + - 0 +- name: A sixteen year old Northern Ireland carer passes the age floor + period: 2026 + absolute_error_margin: 0.01 + input: + people: + caree: + age: 50 + pip_dl_category: STANDARD + carer: + age: 16 + care_hours: 35 + benunits: + family: + members: + - caree + - carer + households: + home: + members: + - caree + - carer + country: NORTHERN_IRELAND + output: + carers_allowance: + - 0 + - 4495.4 + carer_support_payment: + - 0 + - 0 +- name: A fractional age below sixteen is not rounded up for Carers Allowance + period: 2026 + absolute_error_margin: 0.01 + input: + people: + caree: + age: 50 + pip_dl_category: STANDARD + carer: + age: 15.5 + care_hours: 35 + benunits: + family: + members: + - caree + - carer + households: + home: + members: + - caree + - carer + country: WALES + output: + carers_allowance: + - 0 + - 0 + carer_support_payment: + - 0 + - 0 +- name: Scottish Carers Allowance also screens age in the retained twenty twenty four route + period: 2024 + absolute_error_margin: 0.01 + input: + people: + caree: + age: 50 + pip_dl_category: STANDARD + carer: + age: 15 + care_hours: 35 + benunits: + family: + members: + - caree + - carer + households: + home: + members: + - caree + - carer + country: SCOTLAND + output: + carers_allowance: + - 0 + - 0 + carer_support_payment: + - 0 + - 0 + scottish_carer_supplement: + - 0 + - 0 +- name: An eligible Scottish carer retains Carers Allowance in twenty twenty four + period: 2024 + absolute_error_margin: 0.01 + input: + people: + caree: + age: 50 + pip_dl_category: STANDARD + carer: + age: 16 + care_hours: 35 + benunits: + family: + members: + - caree + - carer + households: + home: + members: + - caree + - carer + country: SCOTLAND + output: + carers_allowance: + - 0 + - 4258.8 + carer_support_payment: + - 0 + - 0 + scottish_carer_supplement: + - 0 + - 0 +- name: Reported Carers Allowance cannot pay CSP below sixteen + period: 2026 + absolute_error_margin: 0.01 + input: + people: + caree: + age: 50 + pip_dl_category: STANDARD + carer: + age: 15 + care_hours: 0 + carers_allowance_reported: 4000 + benunits: + family: + members: + - caree + - carer + households: + home: + members: + - caree + - carer + country: SCOTLAND + output: + carers_allowance: + - 0 + - 0 + carer_support_payment: + - 0 + - 0 + scottish_carer_supplement: + - 0 + - 0 +- name: Thirty five caring hours cannot pay CSP below sixteen + period: 2026 + absolute_error_margin: 0.01 + input: + people: + caree: + age: 50 + pip_dl_category: STANDARD + carer: + age: 15 + care_hours: 35 + benunits: + family: + members: + - caree + - carer + households: + home: + members: + - caree + - carer + country: SCOTLAND + output: + carers_allowance: + - 0 + - 0 + carer_support_payment: + - 0 + - 0 + scottish_carer_supplement: + - 0 + - 0 +- name: CSP and its supplement retain the sixteen year old age boundary + period: 2026 + absolute_error_margin: 0.01 + input: + people: + caree: + age: 50 + pip_dl_category: STANDARD + carer: + age: 16 + care_hours: 35 + benunits: + family: + members: + - caree + - carer + households: + home: + members: + - caree + - carer + country: SCOTLAND + output: + carers_allowance: + - 0 + - 0 + carer_support_payment: + - 0 + - 4495.4 + scottish_carer_supplement: + - 0 + - 608.4 +- name: Reported receipt retains the CSP proxy at sixteen in twenty twenty five + period: 2025 + absolute_error_margin: 0.01 + input: + people: + caree: + age: 50 + pip_dl_category: STANDARD + carer: + age: 16 + care_hours: 0 + carers_allowance_reported: 4000 + benunits: + family: + members: + - caree + - carer + households: + home: + members: + - caree + - carer + country: SCOTLAND + output: + carers_allowance: + - 0 + - 0 + carer_support_payment: + - 0 + - 4331.6 + scottish_carer_supplement: + - 0 + - 0 +- name: A fractional age below sixteen is not rounded up for CSP + period: 2026 + absolute_error_margin: 0.01 + input: + people: + caree: + age: 50 + pip_dl_category: STANDARD + carer: + age: 15.5 + care_hours: 35 + benunits: + family: + members: + - caree + - carer + households: + home: + members: + - caree + - carer + country: SCOTLAND + output: + carers_allowance: + - 0 + - 0 + carer_support_payment: + - 0 + - 0 + scottish_carer_supplement: + - 0 + - 0 +- name: DLA mobility uses each child and categorys own minimum age + period: 2026 + absolute_error_margin: 0.01 + input: + people: + caregiver: + age: 40 + higher_under: + age: 2 + dla_m_category: HIGHER + dla_sc_category: HIGHER + higher_at: + age: 3 + dla_m_category: HIGHER + lower_under: + age: 4 + dla_m_category: LOWER + dla_sc_category: HIGHER + lower_at: + age: 5 + dla_m_category: LOWER + benunits: + family: + members: + - caregiver + - higher_under + - higher_at + - lower_under + - lower_at + households: + home: + members: + - caregiver + - higher_under + - higher_at + - lower_under + - lower_at + country: ENGLAND + output: + dla_m: + - 0 + - 0 + - 4160 + - 0 + - 1575.6 + dla_sc: + - 0 + - 5959.2 + - 0 + - 5959.2 + - 0 + dla: + - 0 + - 5959.2 + - 4160 + - 5959.2 + - 1575.6 +- name: DLA care has no categorical age floor and fractional mobility ages are not rounded + period: 2026 + absolute_error_margin: 0.01 + input: + people: + caregiver: + age: 40 + infant: + age: 1 + dla_m_category: NONE + dla_sc_category: HIGHER + higher_under: + age: 2.5 + dla_m_category: HIGHER + lower_under: + age: 4.5 + dla_m_category: LOWER + benunits: + family: + members: + - caregiver + - infant + - higher_under + - lower_under + households: + home: + members: + - caregiver + - infant + - higher_under + - lower_under + country: NORTHERN_IRELAND + output: + dla_m: + - 0 + - 0 + - 0 + - 0 + dla_sc: + - 0 + - 5959.2 + - 0 + - 0 + dla: + - 0 + - 5959.2 + - 0 + - 0 diff --git a/policyengine_uk/tests/policy/integration/carer_support_payment_age_and_earnings.yaml b/policyengine_uk/tests/policy/integration/carer_support_payment_age_and_earnings.yaml new file mode 100644 index 0000000000..a16dc65ad6 --- /dev/null +++ b/policyengine_uk/tests/policy/integration/carer_support_payment_age_and_earnings.yaml @@ -0,0 +1,105 @@ +# Reg4 age16 AND reg14 earnings; Sch2para4 favourable penny. +# Natural pay and annual-constant-earnings approximation; no eligibility/award overrides. +- name: Combined underage low earnings + period: 2026 + absolute_error_margin: 0.01 + input: + age: 15 + employment_income: 8000 + care_hours: 40 + carers_allowance_reported: 0 + country: SCOTLAND + output: + carer_support_payment_pre_overlap: 0 + carer_support_payment: 0 +- name: Combined fractional underage reported CA route + period: 2026 + absolute_error_margin: 0.01 + input: + age: 15.999 + employment_income: 8000 + care_hours: 0 + carers_allowance_reported: 4000 + country: SCOTLAND + output: + carer_support_payment_pre_overlap: 0 + carer_support_payment: 0 +- name: Combined age16 first excluded whole weekly penny + period: 2026 + absolute_error_margin: 0.01 + input: + age: 16 + employment_income: 10608.52 + care_hours: 40 + carers_allowance_reported: 0 + country: SCOTLAND + output: + carer_support_payment_pre_overlap: 0 + carer_support_payment: 0 +- name: Combined age16 exact annual cap + period: 2026 + absolute_error_margin: 0.01 + input: + age: 16 + employment_income: 10608 + care_hours: 40 + carers_allowance_reported: 0 + country: SCOTLAND + output: + carer_support_payment_pre_overlap: 4495.4 + carer_support_payment: 4495.4 +- name: Combined age16 favourable fractional weekly penny + period: 2026 + absolute_error_margin: 0.01 + input: + age: 16 + employment_income: 10608.26 + care_hours: 40 + carers_allowance_reported: 0 + country: SCOTLAND + output: + carer_support_payment_pre_overlap: 4495.4 + carer_support_payment: 4495.4 +- name: Combined age16 ordinary below cap + period: 2026 + absolute_error_margin: 0.01 + input: + age: 16 + employment_income: 8000 + care_hours: 40 + carers_allowance_reported: 0 + country: SCOTLAND + output: + carer_support_payment_pre_overlap: 4495.4 + carer_support_payment: 4495.4 +- name: Combined personal age and earnings gates do not leak between people + period: 2026 + absolute_error_margin: 0.01 + input: + people: + young: + age: 15 + employment_income: 8000 + care_hours: 40 + high: + age: 16 + employment_income: 10608.52 + care_hours: 40 + low: + age: 16 + employment_income: 8000 + care_hours: 40 + benunits: + b1: + members: [young] + b2: + members: [high] + b3: + members: [low] + households: + home: + members: [young, high, low] + country: SCOTLAND + output: + carer_support_payment_pre_overlap: [0, 0, 4495.4] + carer_support_payment: [0, 0, 4495.4] diff --git a/policyengine_uk/tests/test_carer_and_dla_minimum_ages.py b/policyengine_uk/tests/test_carer_and_dla_minimum_ages.py new file mode 100644 index 0000000000..9f4166cb42 --- /dev/null +++ b/policyengine_uk/tests/test_carer_and_dla_minimum_ages.py @@ -0,0 +1,224 @@ +"""Minimum-age cash/override contracts using the existing annual FLOAT age. + +Other qualifying-care/disability/duration conditions are assumed. These tests +preserve the category/reported-receipt proxies; they do not audit eligibility. +""" + +from pathlib import Path + +import pytest + +from policyengine_core.parameters.helpers import load_parameter_file +from policyengine_uk import CountryTaxBenefitSystem, Simulation + +YEAR = 2026 + + +def situation(person, country="ENGLAND"): + return { + "people": { + "caregiver": {"age": {YEAR: 50}, "pip_dl_category": {YEAR: "STANDARD"}}, + "person": {name: {YEAR: value} for name, value in person.items()}, + }, + "benunits": {"family": {"members": ["caregiver", "person"]}}, + "households": { + "home": {"members": ["caregiver", "person"], "country": {YEAR: country}} + }, + } + + +@pytest.mark.parametrize( + "path,first,minimum", + [ + ("gov.dwp.carers_allowance.min_age", "2015-01-01", 16), + ( + "gov.social_security_scotland.carer_support_payment.min_age", + "2023-11-19", + 16, + ), + ("gov.dwp.dla.mobility.higher_min_age", "2001-04-09", 3), + ("gov.dwp.dla.mobility.lower_min_age", "2001-04-09", 5), + ], +) +def test_raw_age_dates_and_supported_annual_parameters(path, first, minimum): + parameter_file = ( + Path(__file__).resolve().parents[1] + / "parameters" + / (path.replace(".", "/") + ".yaml") + ) + raw = load_parameter_file(str(parameter_file), name=path) + assert raw.values_list[-1].instant_str == first + assert raw(first) == minimum + assert raw("2040-01-01") == minimum + # CA's first date is model coverage, not asserted legal commencement. + # Statutory CSP dates do not expand the existing annual replacement rule. + system = CountryTaxBenefitSystem() + for year in [2015, 2024, 2025, 2026, 2040]: + node = system.parameters(str(year)) + for part in path.split("."): + node = getattr(node, part) + assert node == minimum + + +@pytest.mark.parametrize("year", [2015, 2024, 2025]) +def test_age_gate_keeps_supported_annual_csp_and_ca_routing(year): + inputs = situation({"age": 15, "care_hours": 35}, "SCOTLAND") + for person in inputs["people"].values(): + for name, value in person.items(): + person[name] = {year: value[YEAR]} + inputs["households"]["home"]["country"] = {year: "SCOTLAND"} + sim = Simulation(situation=inputs) + assert sim.calculate("carers_allowance", year)[1] == 0 + assert sim.calculate("carer_support_payment", year)[1] == 0 + + +@pytest.mark.parametrize( + "parameter,raised,person,country,output,baseline,other_person,other_country,other_output,other_cash", + [ + ( + "gov.dwp.carers_allowance.min_age", + 18, + {"age": 17, "care_hours": 35}, + "ENGLAND", + "carers_allowance", + 4495.40, + {"age": 17, "care_hours": 35}, + "SCOTLAND", + "carer_support_payment", + 4495.40, + ), + ( + "gov.social_security_scotland.carer_support_payment.min_age", + 18, + {"age": 17, "care_hours": 35}, + "SCOTLAND", + "carer_support_payment", + 4495.40, + {"age": 17, "care_hours": 35}, + "ENGLAND", + "carers_allowance", + 4495.40, + ), + ( + "gov.dwp.dla.mobility.higher_min_age", + 4, + {"age": 3, "dla_m_category": "HIGHER"}, + "ENGLAND", + "dla_m", + 4160, + {"age": 5, "dla_m_category": "LOWER"}, + "ENGLAND", + "dla_m", + 1575.60, + ), + ( + "gov.dwp.dla.mobility.lower_min_age", + 6, + {"age": 5, "dla_m_category": "LOWER"}, + "ENGLAND", + "dla_m", + 1575.60, + {"age": 3, "dla_m_category": "HIGHER"}, + "ENGLAND", + "dla_m", + 4160, + ), + ], +) +def test_each_minimum_age_reform_moves_only_its_cash_boundary( + parameter, + raised, + person, + country, + output, + baseline, + other_person, + other_country, + other_output, + other_cash, +): + original = Simulation(situation=situation(person, country)) + reformed = Simulation( + situation=situation(person, country), reform={parameter: raised} + ) + assert original.calculate(output, YEAR)[1] == pytest.approx(baseline, abs=0.01) + assert reformed.calculate(output, YEAR)[1] == 0 + countercontrol = Simulation( + situation=situation(other_person, other_country), reform={parameter: raised} + ) + assert countercontrol.calculate(other_output, YEAR)[1] == pytest.approx( + other_cash, abs=0.01 + ) + + +@pytest.mark.parametrize( + "input_name,output,amount,country,age", + [ + ("carers_allowance", "carers_allowance", 123, "ENGLAND", 15), + ("carers_allowance_pre_overlap", "carers_allowance", 234, "ENGLAND", 15), + ("carer_support_payment", "carer_support_payment", 345, "SCOTLAND", 15), + ( + "carer_support_payment_pre_overlap", + "carer_support_payment", + 456, + "SCOTLAND", + 15, + ), + ("dla_m", "dla", 567, "ENGLAND", 1), + ], +) +def test_explicit_amount_overrides_keep_the_generic_input_contract( + input_name, output, amount, country, age +): + # A supplied amount is intentionally an override, not entitlement proof. + sim = Simulation(situation=situation({"age": age, input_name: amount}, country)) + assert sim.calculate(output, YEAR)[1] == amount + + +def test_missing_age_keeps_the_existing_default_and_none_category_pays_nothing(): + sim = Simulation( + situation=situation({"care_hours": 35, "dla_m_category": "HIGHER"}) + ) + assert sim.calculate("age", YEAR)[1] == 40 + assert sim.calculate("carers_allowance", YEAR)[1] == pytest.approx( + 4495.40, abs=0.01 + ) + assert sim.calculate("dla_m", YEAR)[1] == 4160 + none = Simulation(situation=situation({"age": 40, "dla_m_category": "NONE"})) + assert none.calculate("dla_m", YEAR)[1] == 0 + + +@pytest.mark.parametrize("reverse", [False, True]) +def test_each_persons_age_controls_cash_across_families_and_request_order(reverse): + people, benunits, households = {}, {}, {} + specs = [ + ("english", "ENGLAND", 15, 3), + ("scottish", "SCOTLAND", 16, 2), + ] + for family, country, carer_age, child_age in specs: + names = [family + suffix for suffix in ["_caree", "_carer", "_child"]] + facts = [ + {"age": 50, "pip_dl_category": "STANDARD"}, + {"age": carer_age, "care_hours": 35}, + {"age": child_age, "dla_m_category": "HIGHER"}, + ] + for name, fact in zip(names, facts): + people[name] = {key: {YEAR: value} for key, value in fact.items()} + members = list(reversed(names)) if reverse else names + benunits[family] = {"members": members} + households[family] = {"members": members, "country": {YEAR: country}} + if reverse: + people = dict(reversed(list(people.items()))) + sim = Simulation( + situation={"people": people, "benunits": benunits, "households": households} + ) + if reverse: + sim.calculate("is_entitled_to_carer_benefit", YEAR) + for variable, expected in [ + ("carers_allowance", {}), + ("carer_support_payment", {"scottish_carer": 4495.40}), + ("dla", {"english_child": 4160}), + ]: + values = dict(zip(sim.populations["person"].ids, sim.calculate(variable, YEAR))) + for name, value in values.items(): + assert value == pytest.approx(expected.get(name, 0), abs=0.01) diff --git a/policyengine_uk/variables/gov/dwp/carers_allowance_pre_overlap.py b/policyengine_uk/variables/gov/dwp/carers_allowance_pre_overlap.py index 6bca6f6b93..0227da876a 100644 --- a/policyengine_uk/variables/gov/dwp/carers_allowance_pre_overlap.py +++ b/policyengine_uk/variables/gov/dwp/carers_allowance_pre_overlap.py @@ -22,6 +22,7 @@ def formula(person, period, parameters): csp_replaces_ca = period.start.year >= 2025 receives_ca = person("carers_allowance_reported", period) > 0 ca = parameters(period).gov.dwp.carers_allowance + meets_age_condition = person("age", period) >= ca.min_age weekly_care_hours = person("care_hours", period) meets_work_condition = weekly_care_hours >= ca.min_hours would_claim = person("would_claim_carers_allowance", period) @@ -36,5 +37,6 @@ def formula(person, period, parameters): & (meets_work_condition | receives_ca) & ~gainfully_employed & would_claim + & meets_age_condition ) return eligible * ca.rate * WEEKS_IN_YEAR diff --git a/policyengine_uk/variables/gov/dwp/dla/dla_m.py b/policyengine_uk/variables/gov/dwp/dla/dla_m.py index 742d4ea8b1..de0f768f4e 100644 --- a/policyengine_uk/variables/gov/dwp/dla/dla_m.py +++ b/policyengine_uk/variables/gov/dwp/dla/dla_m.py @@ -7,16 +7,21 @@ class dla_m(Variable): definition_period = YEAR value_type = float unit = GBP + reference = ( + "https://www.legislation.gov.uk/ukpga/1992/4/section/73", + "https://www.legislation.gov.uk/ukpga/1992/7/section/73", + ) def formula(person, period, parameters): dla_m = parameters(period).gov.dwp.dla.mobility category = person("dla_m_category", period) categories = category.possible_values + age = person("age", period) return ( select( [ - category == categories.HIGHER, - category == categories.LOWER, + (category == categories.HIGHER) & (age >= dla_m.higher_min_age), + (category == categories.LOWER) & (age >= dla_m.lower_min_age), category == categories.NONE, ], [ diff --git a/policyengine_uk/variables/gov/social_security_scotland/carer_support_payment_pre_overlap.py b/policyengine_uk/variables/gov/social_security_scotland/carer_support_payment_pre_overlap.py index a2306a9965..83dd4aed74 100644 --- a/policyengine_uk/variables/gov/social_security_scotland/carer_support_payment_pre_overlap.py +++ b/policyengine_uk/variables/gov/social_security_scotland/carer_support_payment_pre_overlap.py @@ -22,6 +22,7 @@ class carer_support_payment_pre_overlap(Variable): reference = ( "https://www.legislation.gov.uk/asp/2018/9/part/4", "https://www.legislation.gov.uk/ssi/2023/302/regulation/3", + "https://www.legislation.gov.uk/ssi/2023/302/regulation/4", "https://www.legislation.gov.uk/ssi/2023/302/regulation/16", "https://www.legislation.gov.uk/ssi/2023/302/regulation/14", "https://www.legislation.gov.uk/ssi/2023/302/schedule/2/paragraph/4", @@ -33,6 +34,7 @@ def formula(person, period, parameters): if not csp_in_effect: return person.empty_array() csp = parameters(period).gov.social_security_scotland.carer_support_payment + meets_age_condition = person("age", period) >= csp.min_age weekly_care_hours = person("care_hours", period) meets_hours = weekly_care_hours >= csp.min_hours receives_ca = person("carers_allowance_reported", period) > 0 @@ -52,6 +54,7 @@ def formula(person, period, parameters): & csp_in_effect & (meets_hours | receives_ca) & meets_earnings + & meets_age_condition & would_claim ) return eligible * csp.rate * WEEKS_IN_YEAR