Skip to content
Draft
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
1 change: 1 addition & 0 deletions changelog.d/means-tests-dependants-income.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Housing Benefit, Income Support, tax credit and Council Tax Reduction means tests no longer count a child's or young person's own income, tax or personal benefits as the claimant's (HB Regs 2006 reg 25(3), IS Regs 1987 reg 23(2), Tax Credits (Definition and Calculation of Income) Regs 2002 reg 3(1), the Council Tax Reduction applicant-and-partner rules).
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# A child's or young person's own income is never the claimant's: HB Regs
# 2006 reg 25(3), HB (SPC) Regs 2006 reg 23(1), IS Regs 1987 reg 23(2), Tax
# Credits (Definition and Calculation of Income) Regs 2002 reg 3(1), and the
# applicant-and-partner rules of every Council Tax Reduction scheme (SSI
# 2021/249 reg 36(2) says it in terms).
#
# The family in each case: a lone parent and a 17-year-old at school who earns
# £8,000, below the income tax and National Insurance thresholds. Child
# Benefit is not claimed, so it adds nothing to the income totals.

- name: Case 1, a young person's earnings are not the parent's income in any of the four means tests.
period: 2026
absolute_error_margin: 0.01
input:
people:
parent:
age: 40
is_parent: true
is_claimant_or_partner: true
student:
age: 17
current_education: UPPER_SECONDARY
is_claimant_or_partner: false
employment_income: 8_000
benunits:
benunit:
members: [parent, student]
would_claim_child_benefit: false
would_claim_uc: false
households:
household:
members: [parent, student]
country: ENGLAND
rent: 6_000
council_tax: 1_800
output:
# The student is still the family's young person.
is_child_or_young_person_for_legacy_benefits: [false, true]
housing_benefit_applicable_income: 0
income_support_applicable_income: 0
tax_credits_applicable_income: 0
council_tax_reduction_applicable_income: 0

- name: Case 2, the parent's own earnings still count.
period: 2026
absolute_error_margin: 0.01
input:
people:
parent:
age: 40
is_parent: true
is_claimant_or_partner: true
employment_income: 5_000
student:
age: 17
current_education: UPPER_SECONDARY
is_claimant_or_partner: false
employment_income: 8_000
benunits:
benunit:
members: [parent, student]
would_claim_child_benefit: false
would_claim_uc: false
households:
household:
members: [parent, student]
country: ENGLAND
rent: 6_000
council_tax: 1_800
output:
# £5,000 less the lone parent earnings disregard of £25 a week.
housing_benefit_applicable_income: 3_700
# £5,000 less the lone parent disregard of £20 a week.
income_support_applicable_income: 3_960
tax_credits_applicable_income: 5_000
council_tax_reduction_applicable_income: 5_000

- name: Case 3, a pension-age Housing Benefit claimant's grandchild's earnings are not the claimant's.
period: 2026
absolute_error_margin: 0.01
input:
people:
grandparent:
age: 70
is_parent: true
is_claimant_or_partner: true
# Above the Pension Credit guarantee, so no passport zeroes the income.
private_pension_income: 20_000
student:
age: 17
current_education: UPPER_SECONDARY
is_claimant_or_partner: false
employment_income: 8_000
benunits:
benunit:
members: [grandparent, student]
would_claim_child_benefit: false
would_claim_uc: false
households:
household:
members: [grandparent, student]
country: ENGLAND
rent: 6_000
council_tax: 1_800
output:
housing_benefit_pension_age_regulations_apply: true
pension_credit: 0
# The pension of £20,000 less income tax of 20% of (20,000 - 12,570),
# £1,486; not the student's £8,000.
housing_benefit_applicable_income: 18_514
council_tax_reduction_applicable_income: 18_514
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,9 @@
is_council_tax_reduction_lone_parent: true
# Lone parent aged 18 or over: 95.55 * 52.
council_tax_reduction_applicable_amount: 4_968.60
# Child Benefit 1,406.60 plus the young person's earnings of 2,000, which
# are below the income tax and National Insurance thresholds. Counting a
# young person's own earnings is an older approximation of the model's,
# not the law (reg. 36(2) leaves them out); this pins that the member is
# in the family, not a statutory income figure.
council_tax_reduction_applicable_income: 3_406.60
# Child Benefit, 27.05 * 52. The young person's own earnings of 2,000 are
# not the applicant's (SSI 2021/249 reg. 36(2)).
council_tax_reduction_applicable_income: 1_406.60

- name: Case 3, England keeps the Child Benefit young person past the Universal Credit terminal date.
period: 2026
Expand Down Expand Up @@ -121,7 +118,10 @@
is_child_or_young_person_for_council_tax_reduction: [false, true]
is_council_tax_reduction_lone_parent: true
council_tax_reduction_applicable_amount: 4_968.60
council_tax_reduction_applicable_income: 3_406.60
# Child Benefit only: the young person's own earnings are not the
# applicant's (SI 2012/2885 Sch 1 para 11 counts the applicant and
# partner).
council_tax_reduction_applicable_income: 1_406.60

- name: Case 4, Wales keeps the Child Benefit young person past the Universal Credit terminal date.
period: 2026
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
36(1)). A household head aged 18 or 19 who applies alone was reached by
the income mask only as a Child Benefit young person; they stay in the
means test where Scotland's working-age scheme does not read them as a
young person. A member who is not the applicant counts only as that
scheme's young person.
young person. A member who is not the applicant never counts: a young
person's income is not the applicant's.

The pensioner test and the claimant and partner are supplied, so these tests
exercise the young person rule alone; the YAML cases use the derived tests.
Expand Down Expand Up @@ -452,11 +452,10 @@ def test_applicants_own_earnings_count_under_every_scheme():
"is_child_or_young_person_for_council_tax_reduction", year
)[0::3]
income = sim.calculate("council_tax_reduction_applicable_income", year)
np.testing.assert_allclose(income, earnings * (heads | young_person), atol=0.01)
# The applicant's earnings count in every case.
np.testing.assert_allclose(income[heads], earnings, atol=0.01)
# Not vacuous: some applicants are not young persons under their scheme,
# and a member who is neither does not count.
assert (heads & ~young_person).any()
assert (income[~heads & ~young_person] == 0).all()
np.testing.assert_allclose(income, earnings * heads, atol=0.01)
# The applicant's earnings count in every case, whether or not their
# scheme reads them as a young person.
assert (heads & ~young_person).any() and (heads & young_person).any()
# Not vacuous: a non-applicant who is the scheme's young person does not
# count, nor one who is not.
assert (~heads & ~young_person).any() and (~heads & young_person).any()
97 changes: 91 additions & 6 deletions policyengine_uk/tests/test_means_test_income_properties.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
"""A means test never counts the income of someone outside its family.
"""A means test counts the claimant's and partner's income, no one else's.

Invariant: for any benefit unit, giving income to a member who is neither the
claimant, the partner, nor the programme's own child or young person (for
example an 18- or 19-year-old presumed the claimant's child but not in
education, or a third adult) never changes a means test's income. The UC, HB,
IS, tax credit, CTR and Pension Credit income totals are covered.
Invariants:

1. For any benefit unit, giving income to a member who is neither the
claimant, the partner, nor the programme's own child or young person (for
example an 18- or 19-year-old presumed the claimant's child but not in
education, or a third adult) never changes a means test's income. The UC,
HB, IS, tax credit, CTR and Pension Credit income totals are covered.
2. Giving income to the family's own child or young person never changes it
either (HB Regs 2006 reg 25(3); IS Regs 1987 reg 23(2); Tax Credits
(Definition and Calculation of Income) Regs 2002 reg 3(1); SSI 2021/249 reg
36(2) and the applicant-and-partner rules of the other Council Tax
Reduction schemes).

Each example builds families twice in one simulation: once with the other
member's income and once without, in separate households and benefit units.
Expand Down Expand Up @@ -132,3 +139,81 @@ def test_income_of_non_claimants_never_counts(units):
would_claim_uc,
units[i],
)


@st.composite
def families_with_a_dependant(draw):
# A head, an optional partner, and the family's own child or young
# person: a child under 16, or a 16- to 19-year-old at school on a course
# begun at 17, at least 16 years younger than the head and with the head
# flagged a parent, so every programme counts them as the family's.
dependant_age = draw(st.integers(10, 19))
head_age = draw(st.integers(dependant_age + 16, 60))
family = [
{
"age": head_age,
"is_parent": True,
"employment_income": draw(money),
}
]
if draw(st.booleans()):
family.append(
{
"age": draw(st.integers(dependant_age + 16, 60)),
"is_parent": True,
"employment_income": draw(money),
}
)
family.append(
{
"age": dependant_age,
"current_education": "UPPER_SECONDARY",
"age_started_or_accepted_current_education_or_training": 17,
"is_before_universal_credit_qualifying_young_person_terminal_date": True,
"employment_income": draw(st.floats(1, 50_000)),
"private_pension_income": draw(money),
"savings_interest_income": draw(money),
}
)
return family


@settings(
max_examples=15,
deadline=None,
derandomize=True,
suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large],
)
@given(st.lists(families_with_a_dependant(), min_size=1, max_size=6))
def test_income_of_children_and_young_persons_never_counts(units):
# Invariant 2: the same families with the dependant's income removed.
without = [
family[:-1]
+ [
{
**family[-1],
"employment_income": 0,
"private_pension_income": 0,
"savings_interest_income": 0,
}
]
for family in units
]
for would_claim_uc in (None, False):
sim = Simulation(situation=situation(units + without, would_claim_uc))
offsets = np.cumsum([0] + [len(f) for f in units])
n = len(units)
# Not vacuous: the dependant is every legacy programme's own child or
# young person.
for variable in FAMILY_FLAGS[1:]:
flags = sim.calculate(variable, YEAR)
for i in range(n):
assert flags[offsets[i + 1] - 1], (variable, units[i])
for variable in INCOME_TESTS:
values = sim.calculate(variable, YEAR)
for i in range(n):
assert abs(values[i] - values[n + i]) < 0.01, (
variable,
would_claim_uc,
units[i],
)
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,13 @@ class housing_benefit_applicable_income(Variable):
unit = GBP

def formula(benunit, period, parameters):
# Members whose income counts: the claimant and partner and, as the model did
# before, the programme's own children or young persons. The regulations count
# only the claimant's and partner's (HB Regs 2006 reg 25); dropping dependants'
# own income is a follow-up. Anyone else in the benefit unit does not count.
# Members whose income counts: the claimant and partner (SSCBA 1992
# s.136(1); HB Regs 2006 reg 25(1), HB (SPC) Regs 2006 reg 23(1)). "The
# income and capital of a child or young person shall not be treated as
# the income and capital of the claimant" (HB Regs 2006 reg 25(3)).
# Anyone else in the benefit unit does not count either.
person = benunit.members
members = person("is_claimant_or_partner", period) | person(
"is_child_or_young_person_for_legacy_benefits", period
)
members = person("is_claimant_or_partner", period)
pension_age_regulations = benunit(
"housing_benefit_pension_age_regulations_apply", period
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,13 @@ class income_support_applicable_income(Variable):
reference = "https://www.legislation.gov.uk/uksi/1987/1967/schedule/8"

def formula(benunit, period, parameters):
# Members whose income counts: the claimant and partner and, as the model did
# before, the programme's own children or young persons. The regulations count
# only the claimant's and partner's (IS Regs 1987 reg 23); dropping dependants'
# own income is a follow-up. Anyone else in the benefit unit does not count.
# Members whose income counts: the claimant and partner (IS Regs 1987
# reg 23(1)). The income of a child or young person in the claimant's
# family "shall not be treated as the income or capital of the
# claimant" (reg 23(2)). Anyone else in the benefit unit does not count
# either.
person = benunit.members
members = person("is_claimant_or_partner", period) | person(
"is_child_or_young_person_for_legacy_benefits", period
)
members = person("is_claimant_or_partner", period)
IS = parameters(period).gov.dwp.income_support
INCOME_COMPONENTS = [
"employment_income",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,12 @@ class tax_credits_applicable_income(Variable):
)

def formula(benunit, period, parameters):
# Members whose income counts: the claimant and partner and, as the model did
# before, the programme's own children or young persons. The regulations count
# only the claimant's and partner's (TCA 2002 s.7); dropping dependants' own
# income is a follow-up. Anyone else in the benefit unit does not count.
# Members whose income counts: the claimant or, on a joint claim, the
# claimants (TCA 2002 s.7; Tax Credits (Definition and Calculation of
# Income) Regs 2002 reg 3(1)). A child's or qualifying young person's
# own income does not count, nor anyone else's in the benefit unit.
person = benunit.members
members = person("is_claimant_or_partner", period) | person(
"is_child_or_qualifying_young_person_for_child_tax_credit", period
)
members = person("is_claimant_or_partner", period)
TC = parameters(period).gov.dwp.tax_credits
STEP_1_COMPONENTS = [
"private_pension_income",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,22 @@ class council_tax_reduction_applicable_income(Variable):
]

def formula(benunit, period, parameters):
# Members whose income counts: the claimant and partner and, as the model did
# before, the programme's own children or young persons. The regulations count
# only the claimant's and partner's (CTR (Prescribed Requirements) (England)
# Regs 2012 Sch 1 para 11); dropping dependants' own income is a follow-up.
# Anyone else in the benefit unit does not count.
# Members whose income counts: the applicant and partner (CTR
# (Prescribed Requirements) (England) Regs 2012 Sch 1 para 11; WSI
# 2013/3029 Sch 1 para 5; SSI 2012/319 reg 21; SSI 2021/249 reg 36(1)).
# "The income and capital of a child or young person is not to be
# treated as the income and capital of the applicant" (SSI 2021/249 reg
# 36(2)); the other schemes count the applicant's and partner's alone.
person = benunit.members
# A household head who applies alone
# (council_tax_reduction_head_applies_alone) is not the benefit unit's
# claimant, so this mask has reached them only as a Child Benefit young
# person. The applicant's own income always counts (SSI 2021/249 reg
# 36(1)), so they stay counted where Scotland's working-age scheme no
# longer reads them as a young person.
applicant_counted_as_young_person = person(
# (council_tax_reduction_head_applies_alone) is the applicant but not
# the benefit unit's claimant. The model has counted such a head only
# when they are a Child Benefit young person; that is kept, as their
# own income. Counting every such applicant is #2078's change.
head_applicant_counted = person(
"is_council_tax_reduction_applicant_or_partner", period
) & person("is_child_or_young_person_for_legacy_benefits", period)
members = (
person("is_claimant_or_partner", period)
| person("is_child_or_young_person_for_council_tax_reduction", period)
| applicant_counted_as_young_person
)
members = person("is_claimant_or_partner", period) | head_applicant_counted
# Only the claimant's and partner's income-related awards: the scheme
# counts the applicant's and partner's income (Sch 1 para 11 above). A
# member outside the family who has an award of their own claims in their
Expand Down
Loading