Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions changelog.d/2395.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Keep annual legacy-benefit closure scoped to the claimant and partner or a separate member's own award, so unrelated reports cannot close or shield each other's awards. Preserve the existing migration-date proxy and bounded work and capital tests.

Keep each person's legacy closure false alongside the family closure when the report-only benefit reform holds migration off.
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@
# Benefit or tax credits. An adult outside the couple who reports
# income-based JSA is on it, as their own claim, only in the years the model
# has the scheme open (gov.dwp.JSA.income.active: to 2025).
NOT_MOVED = ["legacy_benefits_closed"]
# Hold the family closure and each separate member's own closure.
NOT_MOVED = ["legacy_benefits_closed", "person_legacy_benefits_closed"]

# The first year in which the model pays a carer in Scotland Carer Support
# Payment in place of Carer's Allowance: carers_allowance_pre_overlap and
Expand Down
62 changes: 29 additions & 33 deletions policyengine_uk/tests/legacy_award_work_reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@
(2)(g)), so the limit is 24 either way.
- The claimant is a member who reports the award; when the claimant or
partner reports one, only they are candidates.
- Both screens also end the award once the family's legacy benefits have
closed (legacy_benefits_closed, from PolicyEngine/policyengine-uk#2119),
read here as the model applies it; see legacy_benefits_closed below.
- Reports whose own award is closed are excluded before selecting claimants.
The existing annual proxy is read separately for the couple and for each
other adult; it is not a notice or deadline simulation.
"""

import math
Expand Down Expand Up @@ -143,19 +143,13 @@ def esa_claimant_in_remunerative_work(adult, parameters):


def legacy_benefits_closed(couple, others, parameters):
"""legacy_benefits_closed for a benefit unit whose members (couple +
others, every member who may report) report no tax credits.

Once Income Support or income-based JSA has closed (their active
parameters), a report of it closes the family's legacy benefits, unless
someone reports Pension Credit or the claimant and partner have all
reached the qualifying age for it (SI 2014/1230 reg 8(2B)). This is the
model's reading, not the law's: the model reads every member's reports
and ends every member's award, where the migration notice goes to the
claimant and partner (reg 44(1), (4)) and ends their awards (reg 46(1)).
When the result depends on the claimant's and partner's ages, their ages
must keep clear of 65 to 69, where the qualifying age moves; an assertion
checks this."""
"""Annual closure proxy for the claimant's and partner's own award.

Reports by other members neither close nor shield it (SI 2014/1230
regs 44(1), (4) and 46(1)). This is the existing annual proxy, not a
notice/deadline model. No tax-credit reports are generated here. Ages
avoid the changing Pension Credit qualifying-age boundary.
"""
dwp = parameters.gov.dwp
members = couple + others
assert not any(
Expand All @@ -165,7 +159,7 @@ def legacy_benefits_closed(couple, others, parameters):
), "tax credit closures are not read here"

def reports(variable):
return any(member.get(variable, 0) > 0 for member in members)
return any(member.get(variable, 0) > 0 for member in couple)

closed_report = (
reports("income_support_reported") and not dwp.income_support.active
Expand All @@ -176,14 +170,24 @@ def reports(variable):
return not all(adult["age"] >= 70 for adult in couple)


def candidates(couple, others, reported):
def candidates(couple, others, reported, parameters):
"""Indexes (into couple + others) of members who could be the claimant."""
members = couple + others
couple_reports = any(adult.get(reported, 0) > 0 for adult in couple)
couple_closed = legacy_benefits_closed(couple, [], parameters)
open_reports = [
member.get(reported, 0) > 0
and not (
couple_closed
if i < len(couple)
else legacy_benefits_closed([member], [], parameters)
)
for i, member in enumerate(members)
]
couple_reports = any(open_reports[: len(couple)])
return [
i
for i, member in enumerate(members)
if member.get(reported, 0) > 0 and (i < len(couple) or not couple_reports)
for i, reports in enumerate(open_reports)
if reports and (i < len(couple) or not couple_reports)
]


Expand All @@ -206,13 +210,9 @@ def other_member_works(i):
passes = any(
not esa_claimant_in_remunerative_work(members[i], parameters)
and not other_member_works(i)
for i in candidates(couple, others, "esa_income_reported")
)
return (
passes
and capital <= ESA.capital.limit
and not legacy_benefits_closed(couple, others, parameters)
for i in candidates(couple, others, "esa_income_reported", parameters)
)
return passes and capital <= ESA.capital.limit


def jsa_screen(couple, others, capital, parameters):
Expand All @@ -234,10 +234,6 @@ def other_member_works(i):

passes = any(
weekly_hours(members[i]) < WORK.claimant_hours and not other_member_works(i)
for i in candidates(couple, others, "jsa_income_reported")
)
return (
passes
and capital <= JSA.income.capital.limit
and not legacy_benefits_closed(couple, others, parameters)
for i in candidates(couple, others, "jsa_income_reported", parameters)
)
return passes and capital <= JSA.income.capital.limit
Original file line number Diff line number Diff line change
Expand Up @@ -1179,7 +1179,7 @@
# The lodger's own claim passes, so they are on it; the claimant is not.
is_on_income_related_esa: [false, true]

- name: A non-dependent adult's own ESA ends once the family's legacy benefits have closed
- name: A non-dependent adult's own ESA remains open when only the couple's legacy benefits close
period: 2025
absolute_error_margin: 0
input:
Expand All @@ -1205,15 +1205,13 @@
members: [claimant, lodger, child]
output:
legacy_benefits_closed: true
# The lodger does not work, so the screen fails only on the closure.
# The separate non-working claim remains open under the annual proxy.
esa_income_claimant_remunerative_work: [false, false, false]
esa_income_eligible: false
esa_income: 0
# The model ends every member's award once the family's legacy benefits
# close, the lodger's own claim included, so nobody is on it. That is a
# model simplification: in law the migration notice reaches only the
# claimant and partner (SI 2014/1230 regs 44(1), (4) and 46(1)).
is_on_income_related_esa: [false, false, false]
esa_income_eligible: true
esa_income: 5000
claimant_or_partner_esa_income: 0
# Only the claimant's own award closes; the lodger's own claim passes.
is_on_income_related_esa: [false, true, false]

# The Income Support gate reads the screened award (s.124(1)(h)).

Expand Down Expand Up @@ -1248,3 +1246,32 @@
# Without the work test the partner's award would count as income-related
# ESA of the other member of the couple and bar the claim.
income_support_eligible: true

- name: Issue 2395 outside closed IS report leaves the couple ESA award open
period: 2027
absolute_error_margin: 0
input:
people:
claimant:
age: 30
is_claimant_or_partner: true
esa_income_reported: 5_000
partner:
age: 30
is_claimant_or_partner: true
outside:
age: 40
is_claimant_or_partner: false
current_education: NOT_IN_EDUCATION
income_support_reported: 1_000
benunits:
unit:
members: [claimant, partner, outside]
esa_income_assessable_capital: 0
households:
household:
members: [claimant, partner, outside]
output:
legacy_benefits_closed: false
esa_income: 5_000
claimant_or_partner_esa_income: 5_000
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@
# The lodger's own claim passes, so they are on it; the claimant is not.
is_on_income_based_jsa: [false, true]

- name: A non-dependent adult's own JSA ends once the family's legacy benefits have closed
- name: A non-dependent adult's own JSA remains open when only the couple's legacy benefits close
period: 2025
absolute_error_margin: 0
input:
Expand All @@ -471,15 +471,13 @@
members: [claimant, lodger, child]
output:
legacy_benefits_closed: true
# The lodger does not work, so the screen fails only on the closure.
# The separate non-working claim remains open under the annual proxy.
jsa_remunerative_work_hours: [0, 0, 0]
jsa_income_eligible: false
jsa_income: 0
# The model ends every member's award once the family's legacy benefits
# close, the lodger's own claim included, so nobody is on it. That is a
# model simplification: in law the migration notice reaches only the
# claimant and partner (SI 2014/1230 regs 44(1), (4) and 46(1)).
is_on_income_based_jsa: [false, false, false]
jsa_income_eligible: true
jsa_income: 3000
claimant_or_partner_jsa_income: 0
# Only the claimant's own award closes; the lodger's own claim passes.
is_on_income_based_jsa: [false, true, false]

# The Income Support gate reads the screened award (s.124(1)(f)).

Expand Down Expand Up @@ -508,3 +506,38 @@
jsa_income_eligible: false
jsa_income: 0
income_support_eligible: true

- name: Issue 2395 couple closure leaves a separate outside JSA award open
period: 2025
absolute_error_margin: 0
input:
people:
claimant:
age: 30
is_claimant_or_partner: true
is_parent: true
child_tax_credit_reported: 2_000
partner:
age: 30
is_claimant_or_partner: true
is_parent: true
outside:
age: 40
is_claimant_or_partner: false
current_education: NOT_IN_EDUCATION
jsa_income_reported: 5_000
child:
age: 5
is_claimant_or_partner: false
benunits:
unit:
members: [claimant, partner, outside, child]
jsa_income_assessable_capital: 0
households:
household:
members: [claimant, partner, outside, child]
output:
legacy_benefits_closed: true
jsa_income: 5_000
claimant_or_partner_jsa_income: 0
is_on_income_based_jsa: [false, false, true, false]
Loading
Loading