Skip to content

Benefit cap counts another member's own income-based JSA and income-related ESA #2298

Closed

Description

@MaxGhenis

Defect

benefit_cap_reduction counts a benefit-unit member's own income-based JSA and income-related ESA, not only the claimant's and partner's. Its total is

claimants = benunit.members("is_uc_assessed_claimant", period)
total = add_for_members(benunit, period, CAPPED_BENEFITS, claimants)

with jsa_income and esa_income in CAPPED_BENEFITS. Both are benefit-unit variables, built from every member's report (jsa_income sums jsa_income_reported over the unit). add_for_members adds a benefit-unit variable as it is, whatever members says; its docstring says so and says to pass claimant_or_partner_jsa_income and claimant_or_partner_esa_income instead. So a member who is not the claimant, the partner or a dependant, and who claims JSA or ESA in their own right, raises the family's cap reduction. The variable's own comment says the cap applies to "the welfare benefits to which a single person or couple is entitled".

The law

Welfare Reform Act 2012 s.96(1): "Regulations may provide for a benefit cap to be applied to the welfare benefits to which a single person or couple is entitled." s.96(2): "where a single person's or couple's total entitlement to welfare benefits in respect of the reference period exceeds the relevant amount, their entitlement to welfare benefits [...] is reduced by an amount up to or equalling the excess."

UC Regs 2013 reg. 80(1): "the amount of a welfare benefit to be used when determining total entitlement to welfare benefits is the amount to which the single person or couple is entitled during the reference period [...]".

Another adult's own JSA or ESA is not an amount to which the single person or couple is entitled.

Reproduction

On main (9475c95), 2025-26, England, no savings or earnings: a couple aged 35, each reporting £3,000 of income-based JSA, two children aged 15, and a lodger aged 30 in the same benefit unit. A member aged 20 who is not the claimant, the partner or a dependant reports £0, then £3,000, of income-based JSA in their own right.

Variable Added member reports £0 Reports £3,000
claimant_or_partner_jsa_income £6,000.00 £6,000.00
jsa_income (whole unit) £6,000.00 £9,000.00
benefit_cap £25,323.00 £25,323.00
benefit_cap_reduction £0.00 £1,047.52
universal_credit £15,118.92 £14,071.40
council_tax_reduction_applicable_income £23,370.52 £22,323.00

The couple's own JSA does not change; the cap reduction rises by the part of the member's award that takes the unit over the cap, and Universal Credit falls by the same amount.

Script
"""The benefit cap counts another member's own income-based JSA (review r1 of #2294)."""
import subprocess
from policyengine_uk import Simulation

Y = 2025
print("# checkout", subprocess.run(["git", "rev-parse", "--short", "HEAD"], capture_output=True, text=True).stdout.strip())

def situation(outsider_jsa):
    people = {
        "claimant": {"age": 35, "is_claimant_or_partner": True, "jsa_income_reported": 3_000,
                     "is_parent": True, "receives_carer_benefit": False, "care_hours": 0},
        "partner": {"age": 35, "is_claimant_or_partner": True, "jsa_income_reported": 3_000,
                    "is_parent": True, "receives_carer_benefit": False, "care_hours": 0},
        "child1": {"age": 15, "is_claimant_or_partner": False},
        "child2": {"age": 15, "is_claimant_or_partner": False},
        "lodger": {"age": 30, "is_claimant_or_partner": False},
        "outsider": {"age": 20, "is_claimant_or_partner": False, "jsa_income_reported": outsider_jsa},
    }
    for p in people.values():
        p.setdefault("current_education", "NOT_IN_EDUCATION")
        for k in ("esa_income_reported", "income_support_reported", "jsa_income_reported", "employment_income"):
            p.setdefault(k, 0)
    names = list(people)
    return {
        "people": {n: {k: {Y: v} for k, v in p.items()} for n, p in people.items()},
        "benunits": {"b": {"members": names, "claims_all_entitled_benefits": {Y: False}}},
        "households": {"h": {"members": names, "country": {Y: "ENGLAND"}, "savings": {Y: 0}}},
    }

rows = {}
for jsa in (0, 3_000):
    sim = Simulation(situation=situation(jsa))
    rows[jsa] = {v: float(sim.calculate(v, Y)[0]) for v in (
        "claimant_or_partner_jsa_income", "jsa_income", "benefit_cap", "benefit_cap_reduction",
        "universal_credit", "council_tax_reduction_applicable_income")}
for v in rows[0]:
    print(f"{v}: {rows[0][v]:,.2f} -> {rows[3_000][v]:,.2f}")

The review of #2294 found this; test_legacy_award_readers_properties.py::test_other_members_awards_never_change_the_readers holds benefit_cap_reduction at zero until it is fixed (#2294 adds the family above as an @example).

Fix

Pass claimant_or_partner_jsa_income and claimant_or_partner_esa_income in CAPPED_BENEFITS in place of jsa_income and esa_income. Then drop the hold in the property test, so the @example checks the fix. Check income_support too: the CTR income comment says it already needs the claimant's or partner's own award.

Invariant to test: the cap reduction never depends on what a member who is not the claimant or partner reports.

axiom: WRA 2012 s.96 and UC Regs 2013 reg. 80: to check in rulespec-uk when the fix is built.

Activity

  1. MaxGhenis commented on Oct 11, 2026

    @MaxGhenis
    CollaboratorAuthor

    UK hub: already addressed in an open PR. #2076 ("Lift the benefit cap only for the support component and the people HB reg 75F and UC reg 83 name, and cap only the family's benefits") changes the cap's total to count claimant_or_partner_esa_income and claimant_or_partner_jsa_income, not the unit-wide esa_income and jsa_income. Its tests include a non-dependant with esa_income_reported: 5_000 and claimant_or_partner_esa_income: 0, and the same for JSA.

    When #2076 merges, this issue can close, and #2294's CAP_HELD hold can be dropped so its @example checks the fix.

    #2089 also rewrites CAPPED_BENEFITS, so whichever of #2076 and #2089 lands second must keep the claimant-or-partner variables.

  2. added a commit that references this issue on Oct 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions