Skip to content

Assess each Council Tax Reduction claim under its own family's scheme and exemption - #2015

Merged
vahid-ahmadi merged 20 commits into
mainfrom
ctr-per-family-scheme
Oct 8, 2026
Merged

vahid-ahmadi merged 20 commits into
mainfrom
ctr-per-family-scheme

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #2157

#2009 merged on 2026-10-04 (eeb8108f6), so this PR now targets main. It merges main at 6b366b281 (merge commit 37a00dcc5); four later commits address CI and the after-merge reviews. That brings in #2006's joint-claim machinery, #1944's reg 3 pensioner test and #1907's qualifying age.

What this does on top of main

Main already assesses a joint claim on the applicant's own family. Since #2006, where families share the rent, council_tax_reduction_claim_pensioner and council_tax_reduction_claim_scheme_supported follow each family, and the councils apply each joint applicant's own exemption. Every other claim still reads the household flags council_tax_reduction_household_has_pensioner and council_tax_reduction_household_has_non_dep_exemption.

This PR makes one rule for every claim, joint or not, and fixes three outcomes that main still gets wrong.

One rule.

  • A claim's scheme follows its own family's council_tax_reduction_pensioner.
  • Its non-dependant exemption follows its own council_tax_reduction_applicant_has_non_dep_exemption.
  • Both read the claim's applicant and partner, not the benefit unit's claimant and partner (below).

Deleted:

council_tax_reduction_scheme_supported becomes a BenUnit variable. No CTR formula now reads a household-level pensioner or exemption flag.

What changes for households, relative to main. Main is 6b366b281; each case is 2026 with no capital.

Case Main This PR
Maidstone, £1,800 bill. A pension-age head with no income and a working-age sharer, jointly liable, each for half. The sharer reports £1,500. £900 simulated plus the sharer's £1,500 report: £2,400, more than the bill The report is capped at the sharer's half, £900: £1,800
Maidstone, £1,800 bill. An 80-year-old grandmother heads the household and is liable. Her benefit unit's claimant and partner are 17-year-old parents; the mother gets PIP daily living. Not a pensioner, because the young parents are the claimant and partner. Maidstone's working-age scheme is not simulated: £0. Main's exemption flag counts the mother's PIP. The grandmother is the applicant and a pensioner: £1,800 under the pensioner scheme. The mother's PIP is not the applicant's or her partner's, so no exemption.
Cardiff, £1,800 bill. The only resident is 17 (the head) and reports £500, so no family can claim. The household's Welsh scheme counts as simulated, so the family gets the simulated nil: £0 Nothing in the household is simulated, so it keeps its report: £500

In the first case, the cap rests on SI 2012/2885 Sch 1 para 7(3)-(4): a jointly liable applicant's maximum reduction is on their share of the bill. It applies only beside a simulated claim that pays something, and not where the share may count a student para 7(5) leaves out (see "Reconciliation" below). In the second case, the young parents' Income Support, income-based JSA, income-related ESA or Universal Credit does not stop the grandmother being a pensioner: those are their awards, not hers.

Run on main, 20 of this PR's 24 YAML cases pass, and they pin the per-family behaviour this PR keeps. The four that fail are:

  • the first two rows above;
  • the grandmother case with the young parents on Income Support;
  • the cap again, with a 20-year-old sharer.

Eight of the passing cases failed on #2009's head (4b84ea069) and were fixed on main by #2006 and #1944: the seven Oxford, Merton, Maidstone and Newham sharer cases, and the working-age couple whose family includes a pension-age grandmother.

The law

Read on legislation.gov.uk on 2026-10-01 (revised text):

Changes

  • Whose circumstances. is_council_tax_reduction_applicant_or_partner (Person) and council_tax_reduction_head_applies_alone (BenUnit) are copied byte for byte from Means-test the Council Tax Reduction applicant and partner, not the benefit unit's claimant #2078, which needs the same people for the means test.
    • Where the family's liable person (council_tax_reduction_liable_person) is its claimant or partner, these are the claimant and partner, whatever their ages.
    • Where the liable person is a household head who is not the family's claimant or partner, it is the head alone. An example is the grandmother above.
    • In a family that cannot claim, they are its claimant and partner.
  • council_tax_reduction_pensioner keeps Apply the benefit cap to mixed-age couples on Universal Credit, and keep them off pensioner Council Tax Reduction #1944's reg 3 formula on those members: Use the State Pension Credit qualifying age, and start-of-year status for Class 4 NI #1907's qualifying age for State Pension Credit, no IS/JSA/ESA, and no UC award with a member under that age. Where the head applies alone, the benefit unit's IS/JSA/ESA and UC are its claimant and partner's, not the applicant's, so they do not count (as Means-test the Council Tax Reduction applicant and partner, not the benefit unit's claimant #2078 does in council_tax_reduction_relevant_income_based_benefit).
  • council_tax_reduction_applicant_has_non_dep_exemption: the same conditions as main, on those members.
  • Scheme selection. The national scheme (simulated_council_tax_reduction_benunit), the four _legacy councils and Oxford take each claim's own council_tax_reduction_pensioner. council_tax_reduction_scheme_supported is that claim's scheme.
  • Councils' non-dependant deductions.
    • The person-level amounts (*_council_tax_reduction_individual_non_dep_deduction) depend only on the non-dependant and the council area (config.is_*_area).
    • local_non_dep_deductions applies each applicant's own exemption.
    • normal_gross_income_non_dep_deduction no longer takes any household exemption.
  • Reconciliation (council_tax_benefit). One bill can carry both simulated and reported reductions.
    • A claiming family gets its simulated reduction where its own scheme is simulated, and its reported reduction otherwise.
    • Beside a simulated claim in the household that pays something, a jointly liable claimant's report is capped at its share of the council tax. That share is the one the simulated claim uses, so the two cannot together exceed the bill.
    • Give sharers, boarders and lodgers their own rent, tenure and LHA category #2006's share leaves out only people in higher education (in_HE). Where a jointly liable person it counts may be a student para 7(5) leaves out, the share may be too small, so the report is kept and the household bound is not guaranteed. task_20e2dfa8 corrects the divisor itself.
      • "May be a student" is the model's student test for non-dependants (current_education other than not in education), or in_FE.
      • In the enhanced FRS, current_education is an observed input. Neither file has in_HE or in_FE, so their shares leave out no one.
      • With no education input, current_education defaults to tertiary at 18 and 19. A jointly liable 18- or 19-year-old therefore keeps the cap off; a YAML case pins a £1,500 report kept beside a £900 simulated claim on an £1,800 bill. The same default already makes such a person a student for non-dependant deductions.
      • Part-time students, and students para 75(2) brings back in, are treated the same way.
      • Where the cap is off, the outcome is main's.
    • Otherwise reports are kept as reported.
    • A family that cannot claim keeps a report only where no claim in its household is simulated (new Household council_tax_reduction_household_has_simulated_claim). On main, outside shared-rent households, it followed the household's scheme, which is the head's family's.
  • council_tax_reduction_claims_are_joint stays; council_tax_reduction_claimant_benunit still uses it for the excluded-student rule.
  • Tests on main that read the deleted flags now read the per-family ones:
    • council_tax_reduction_pensioner.yaml, joint_liability.yaml and state_pension_credit_qualifying_age.yaml;
    • test_state_pension_credit_qualifying_age.py, test_council_tax_reduction_pension_credit_properties.py (the claimant family's flag, indexed per family);
    • test_rent_of_sharers_boarders_and_lodgers_properties.py. Each shared-rent claim's scheme is simulated exactly when its own family is a pensioner (Maidstone). In a household without sharers, the head's family is the one claim, and every family is simulated or reported as that claim's scheme is.
    • test_benefit_cap_and_ctr_pension_age_properties.py. Its "before Apply the benefit cap to mixed-age couples on Universal Credit, and keep them off pensioner Council Tax Reduction #1944" reference was a reform of the household flag; it is now a reform of council_tax_reduction_pensioner to the age test alone, on the same members (P3, P4). That test also sets the CTR applicable amount, so P5 (Welsh and Scottish CTR moves only through UC) now compares against a reform of the cap exception alone.
  • A changelog fragment.

National schemes and the exemption (for #2007)

The national schemes (England pensioners, Wales, Scotland) still apply no applicant exemption on main; only the councils' schemes do. #2007 adds it and reads both deleted flags. When it merges after this PR:

  1. council_tax_reduction_non_dep_deductions: applicant_exempt reads the claim's own council_tax_reduction_applicant_has_non_dep_exemption.
  2. Wales's both-members rule (WSI 2013/3029 Sch 6 para 5(3)) turns on the applicant's scheme. each_member_deducted is evaluated on the non-dependant's family, so the family flag cannot go there.
    • Build two household pools of the non-dependant families' deductions: the higher member's, and the sum for a non-dependant couple with a UC award.
    • Each claimant takes the sum pool when it is in Wales and its own council_tax_reduction_pensioner is false, and the higher-member pool otherwise.
  3. council_tax_reduction_individual_non_dep_deduction and council_tax_reduction_non_dep_deduction_exempt select England's pensioner parameters by country only, as the councils' amounts do here. Apply Housing Benefit and national Council Tax Reduction non-dependant deductions as legislated #2007's two tests pinning "no national deduction in English working-age households" then check that the working-age award is unaffected.
  4. Its tests that read the deleted flags move to the per-family ones, indexed per benefit unit:
    • non_dependant_deduction_review_cases.yaml (three outputs);
    • test_non_dependant_deduction_properties.py (the English scale choice, the Welsh couple rule and the exemption oracles);
    • the pension-credit property test, which this PR has already migrated on main.

Invariants (property-tested)

policyengine_uk/tests/test_council_tax_reduction_claimant_family_scheme_properties.py uses Hypothesis.

  • Populations have up to 6 households. Each has a flagged head's family, up to two sharer families and at most one non-dependant.
  • All eight scheme types appear (Maidstone, the five councils, Edinburgh and Cardiff). Ages straddle State Pension age, and disability inputs cover all five exempting conditions. Families may claim Universal Credit and carry random reported reductions. About a third of adults are enrolled in further education, by current_education or by in_FE.
  • Derandomized, 20 examples each.
  1. Own members. A family is a pensioner exactly when its claimant or partner has reached the qualifying age for State Pension Credit and neither has IS, income-based JSA, income-related ESA or a UC award. A UC award needs one of them under that age. It is exempt exactly when its claimant or partner is blind or gets AA, DLA care, PIP daily living or AFIP. This is a differential check against a direct computation. Every adult in these populations is 20 or over and the head is a claimant, so applicant and partner are claimant and partner here; the YAML covers a head who applies alone.
  2. As if alone. Each family's pensioner flag, exemption and simulated-scheme flag are what they would be if it lived alone with its own benefit awards. A family's UC award depends on the rent it pays, which changes when it lives alone, and reg 3 follows the award. So the alone simulation is given the family's own UC, IS, JSA and ESA.
  3. Scheme. A family's scheme is simulated exactly when it is in Scotland or Wales, is a pensioner, or is in a modelled council. In England only pensioner families are paid by the national scheme, and only working-age families by a council's.
  4. Others do not matter. Replace the ages and disability benefits of every other claiming family, holding each family's applicable amount and income as they were. A family's flags, its councils' awards, its joint-liability share and its simulated award are unchanged. So is its council_tax_benefit, wherever property 5's reconciliation is the same before and after; the intended exception is a family falling back to a report, when whether a simulated claim beside it pays something changes.
    • The means test is held fixed because main's severe disability premium counts the other adults in the household (has_non_dependant_for_severe_disability_premium), joint occupiers included. Their ages and benefits can therefore change a family's applicable amount whatever its scheme. CI found one such case.
  5. Fallback. council_tax_benefit is exactly the reconciliation rule above, for every family.
  6. Bounds. A family that cannot claim gets no simulated reduction. A household whose simulated claims pay something, and whose share counts no one who may be a student, never gets more than its council tax.
  7. The exemption is the applicant's own. A working-age head and a sharer live under each modelled council, with a non-dependant in remunerative work, so both claims bear a deduction.
    • Giving one claimant an exempting benefit removes that claimant's deductions.
    • It leaves the other claim's exemption, deductions, council award and council_tax_benefit unchanged.

Tests

  • YAML. claimant_family_scheme.yaml has 24 cases. They pin the scheme and the exemption per family, the reconciliation rules, and whose circumstances count (applicant and partner).
    • Run on main (6b366b281) with the outputs main cannot evaluate removed (council_tax_reduction_scheme_supported, a Household variable there, and three branch-only variables): 20 pass and 4 fail, as listed above.
    • Cases added after review fail on the head they correct and pass here:
      • a further-education student in the share, shown by current_education (fails on 2b5ffe38d) or by in_FE (fails on 38364ca49);
      • the grandmother head beside the parents' Income Support (fails on 2b5ffe38d).
    • Two more pin the age boundary of the student guard at 19 and 20.
    • The no-claimant case above was run on both models: main pays £0 in Cardiff and keeps the £500 in Maidstone.
  • Locally:
    • on a9466b871, the local-authority YAML passed (161 cases);
    • on 37a00dcc5, the 11 other YAML files that read CTR variables passed (112 cases);
    • ruff format --check and ruff check are clean;
    • properties 1-6 passed on CI's failing property-4 population and on two student variants.
    • The host was too loaded to finish the property suites; CI runs them.
  • CI on a9466b871: run 37370040784 (running).

Enhanced FRS impact

These are real Microsimulation runs of main (6b366b281) against this PR (a9466b871) on the same datasets, six runs in all. Comparisons at the earlier heads 37a00dcc5 and 38364ca49 also showed no change anywhere. Aggregates only; any cell behind fewer than 10 survey households is suppressed, along with any cell from which it could be recovered.

Production enhanced FRS 2024-25 (e433e532…), 2026-27 #2006's companion build (efrs_payer.h5, ed1a29912656…), 2025-26 and 2026-27
CTR, main and this PR £2.3366bn £2.0971bn and £2.1650bn
Change, any household or family none none
People entering or leaving poverty (BHC, AHC) none none

Why nothing moves:

  • Scheme and exemption flags. In both files, no claiming family's scheme or exemption flag differs from its own status, on main or here. Main already applies each joint claim's own flags. No family's pensioner status or exemption differs between its claimant and partner and its applicant and partner.
  • The cap. It bites only where a jointly liable claimant reports more than its share, beside a simulated claim that pays and with no one who may be a student counted in the share. Fewer than 10 survey households in the payer build have a jointly liable claimant reporting more than its share at all; the production file has no sharers. No award changes.
  • Families that cannot claim. Their reported reductions are unchanged in both files.
  • Bounds. No household whose simulated claims pay something gets more than its council tax.

Not caused by this PR: on main in 2026, about 523k households (221 survey households) in the payer build, and 498k (221) in the production file, already receive more CTR than their modelled council tax, because their reported CTR exceeds it. None has a simulated claim that pays. A follow-up task (task_84c4759d) is looking for the cause.

Review

  • r1, of ae3e5e0ea: REQUEST_CHANGES. Five findings: reports added on top of a simulated claim, incomplete complementary suppression, two wrong mechanism claims, a weak exemption property, and a missed Apply Housing Benefit and national Council Tax Reduction non-dependant deductions as legislated #2007 consumer. r2 confirmed 1, 2 and 4 resolved; 3 and 5 carried into r2's findings 2 and 4.
  • r2, of 7ac22effc: REQUEST_CHANGES. It confirmed r1's findings 1, 2 and 4 resolved, and the legal reading.
    1. The share cap could discard a lawful report through para 7(5)'s students. The cap now applies only beside a simulated claim that pays.
    2. Impact claims were too broad.
    3. The body described a later head.
    4. Apply Housing Benefit and national Council Tax Reduction non-dependant deductions as legislated #2007's test migration was incomplete.
  • r3, of cc4d9aeb9: never completed; all three attempts hit session limits.
  • After the merge, of 2b5ffe38d (GPT-6.1 Sol): REQUEST_CHANGES. It found no lost main hunk in the 16 conflict resolutions, no remaining reader of a deleted variable, and the Means-test the Council Tax Reduction applicant and partner, not the benefit unit's claimant #2078 files byte for byte. It confirmed r2's findings 2-4 resolved. Four should-fix findings:
    1. The cap still used a share that can count a further-education student. Reports are now kept there.
    2. The grandmother's pensioner test read the young parents' IS/JSA/ESA. It no longer does.
    3. The migrated P5 comparator also moved the CTR allowance. P5 now has its own cap-only reform.
    4. Property 2's non-empty filter could reject valid populations. Awards are now pinned instead.
  • Re-review of 38364ca49 (GPT-6.1 Sol): REQUEST_CHANGES. It confirmed the head-alone mask, P5's comparator, property 2's pinned awards, and property 4's explanation. Two should-fix findings and a nit:
    1. The student guard missed in_FE. It now reads it.
    2. current_education's age default turns the guard on at 18 and 19 with no education input. This is kept deliberately, as explained under "Reconciliation", and now documented and pinned at 19 and 20.
    3. The body lacked the template's first line and the axiom blocker. Council Tax Reduction: assess every claim's scheme and exemption on its own applicant #2157 now tracks this change, and the blocker is recorded on rulespec-uk#410.
  • Re-review of a9466b871 (GPT-6.1 Sol): APPROVE. It found the two should-fix findings resolved or adequately answered, all 24 YAML expectations right by hand, the generator's student facts independent of the model, and no regression. Its one nit was this metadata: the template's first line, and recording the blocker on Fix child benefit bug #410 itself. Both are done.

Coordination with other open PRs

axiom: TheAxiomFoundation/rulespec-uk#410 queued (pe-parity; the signed encoder run awaits approval)

🤖 Generated with Claude Code

… and exemption

In a shared-rent household every family liable for the rent claims, but the
pensioner scheme and the non-dependant exemption were picked once per
household from the head's family. SI 2012/2885 reg 3 (and SI 2012/2886 Sch
para 3, SI 2013/3029 reg 3, SSI 2021/249 reg 3, SSI 2012/319 reg 12) define
"pensioner" per person and partner, and the exemption applies where "the
applicant or his partner" is blind or gets a listed benefit (SI 2012/2885 Sch 1
para 8(6) and equivalents).

- council_tax_reduction_pensioner (BenUnit) replaces the household flag; the
  name and entity match #1944 and #1966, which extend the same definition.
- council_tax_reduction_applicant_has_non_dep_exemption (BenUnit) replaces
  the household exemption; the councils' schemes apply it per claim.
- council_tax_reduction_scheme_supported is per family, so each family falls
  back to its reported reduction only where its own scheme is unmodelled.
- Councils' person-level non-dependant deductions no longer depend on the
  head's family; each claimant's own scheme decides whether to use them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Oct 2, 2026
SI 2012/2885 Sch 1 para 8(6) exempts "an applicant's dwelling if the
applicant or his partner is" blind or on one of the listed benefits. With
jointly liable sharers each family is its own applicant, but the composed
aggregator read the household-level exemption, which looks only at the
oldest adult's family. A blind head therefore exempted a sighted sharer's
claim, and a blind sharer exempted nothing.

council_tax_reduction_applicant_has_non_dep_exemption is the benefit-unit
exemption over is_claimant_or_partner, with the same name and formula as
#2015's, which applies it to the local schemes. The national aggregator now
uses it; the local schemes keep the household variable here.

YAML: a blind head does not exempt a jointly liable sharer, a blind sharer is
exempt while the head is not, and a blind sharer's own adult son costs no one
a deduction.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 2 commits October 2, 2026 08:08
With the scheme chosen per family, one household can hold a simulated claim
and a family whose scheme is not simulated. Review r1 found that a family
that cannot claim then kept its reported reduction beside a head whose
simulated award already covered the whole bill: £3,000 against £1,800.

- A family that cannot claim keeps a reported reduction only where no claim
  in its household is simulated (new household flag
  council_tax_reduction_household_has_simulated_claim). Without sharers this
  is the base behaviour.
- A jointly liable claimant's reported reduction is limited to its own share
  of the council tax (SI 2012/2885 Sch 1 para 7(3)-(4)), the most any scheme
  could give it.

Tests: three YAML cases; the property tests now generate reported
reductions, check the fallback and the household bound, and toggle one
claimant's exempting benefit to show it leaves the other claim unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#2009 now names the liable people directly. A household head can be liable
without being the claimant or partner of their family (a grandparent heading
a family formed around a young couple), so "the applicant or his partner"
(SI 2012/2885 reg 3, Sch 1 para 8(6)) is not always the family's claimant and
partner. New person-level council_tax_reduction_applicant_or_partner: the
liable member, and the other member of the couple where the liable member is
the claimant or partner, whatever the partner's age; in a family that cannot
claim, its claimant and partner. The pensioner flag and the applicant
exemption read it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#2078 (also stacked on #2009) adds
is_council_tax_reduction_applicant_or_partner and
council_tax_reduction_head_applies_alone for the CTR means test. They define
the same people as this PR's council_tax_reduction_applicant_or_partner: the
claimant and partner where the liable person is one of them, the liable head
alone otherwise, and the claimant and partner in a family that cannot claim.
Take #2078's two files byte for byte, so whichever PR lands second merges
without conflict, and point the pensioner flag and the applicant exemption
at it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Coordination note from #2062 (issue #2059), also stacked on #2009.

This PR's council_tax_reduction_head_applies_alone and is_council_tax_reduction_applicant_or_partner read council_tax_reduction_household_head. #2062 removes that variable and adds is_resolved_household_head, with the same formula (the eldest flagged member, else the eldest member; exactly one per household), which every programme now reads.

Whichever of the two PRs lands second renames those reads to is_resolved_household_head and changes nothing else. #2078 adds the same two files and has agreed the same with #2062. A missed read fails as VariableNotFound, so it cannot pass CI. #2062's landing job does the rename if this PR lands first.

…ted claim

Review r2 found that capping every jointly liable claimant's reported
reduction at its share could discard a lawful report: the model's divisor
(#2006's joint-liability share) counts students that SI 2012/2885 Sch 1
para 7(5) leaves out. The cap exists to stop a reported claim and a simulated
claim together exceeding the bill, so it now applies only where another claim
in the household is simulated, on the share that claim uses. Where nothing is
simulated, reports are kept as reported, as on the base.

Tests:
- YAML: a jointly liable claimant keeps its whole report where nothing is
  simulated; a pension-age grandmother in working-age applicants' family does
  not make their claim a pensioner's (an intended change under reg 3(1): the
  base paid £1,800 on the pensioner scheme).
- Properties: the fallback oracle follows the narrowed cap, and property 4
  compares council_tax_benefit wherever the household's reconciliation is
  unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Oct 3, 2026
…cant-means-test

Stack #2078 on #2015 so each claim's non-dependant exemption is its own
applicant's or partner's (review r1, P1). Keeps #2015's deletion of the
household pensioner and exemption flags; #2015 already reads
is_council_tax_reduction_applicant_or_partner. Repoints three
applicant_or_partner.yaml cases and the differential property at the
per-family flags, and adds the review's two-claimant Merton regression:
a blind grandmother head applying alone is exempt, the sharer's claim
still bears half the son's deduction (£554.20).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the payer build, the only remaining change came from heads whose reported
reduction was capped beside a sharer whose simulated claim paid nothing. With
no simulated reduction competing for the bill there is nothing to reconcile,
so the cap now applies only where the simulated claims in the household pay
something. The household bound still holds: each simulated award is within
its share, and a capped report within its own.

Tests: a YAML case where the simulated claim beside a report pays nothing;
the fallback oracle, the bound and property 4's comparison follow the paid
condition.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis

MaxGhenis commented Oct 4, 2026 •

Copy link
Copy Markdown
Collaborator Author

Composition note from #1966 (Scotland/Wales working-age CTR), now based on main at a5e5a73.

#1966 routes the devolved working-age rules on council_tax_reduction_claim_pensioner, the per-claim scheme flag from #2006. Its only read is in working_age/council_tax_reduction_devolved_working_age.py, so any redefinition here carries through.

Whichever lands second, please rerun council_tax_reduction_devolved_working_age.yaml, which has 24 cases.

🤖 Generated with Claude Code

Base automatically changed from ctr-claimant-household-head to main October 4, 2026 18:13

@vahid-ahmadi vahid-ahmadi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Max. Reviewed at cc4d9ae: not ready.

Choosing the scheme and the non-dependant exemption per claiming family is right (SI 2012/2885 Sch 1 paras 2–3, 8(6)), and the five councils are wired consistently.

  1. Blocking: this needs a rebase, and the current head drops the UC limb.
    • #2009's head has merged main (#2006, #1899, #1907, #1940, #1944). A trial merge gives 16 conflicts, including add/add on council_tax_reduction_pensioner.py.
    • At council_tax_reduction_pensioner.py:28-36 the PR uses is_SP_age and leaves out the UC limb. A 70/50 couple on UC in Maidstone gets £1,800 of pensioner CTR here, against £0 on #2009's head.
    • Reg 3(1)(a)(ii): "his partner is not— … (bb) a person with an award of universal credit".
    • Fix: keep main's reg 3 test (has_attained_state_pension_credit_qualifying_age plus the IS/JSA/ESA/UC limb) and change only the members it reads, to is_council_tax_reduction_applicant_or_partner.
  2. Should fix: add a mixed-age UC couple case that pins £0. Every pensioner case today is 70/40 with no UC.
  3. Should fix: rewrite the body against the current base.
    • 17 of the 19 new cases already pass there, because main assesses joint claims per family.
    • What this PR adds is the other two cases:
      • a sharer limited to their share (council_tax_benefit is [900, 1500] against [900, 900]);
      • a pension-age head with young parents.
    • Say plainly that the PR chooses "always per family" over main's "per family only for joint claims".
  4. Nit: council_tax_reduction_applicant_has_non_dep_exemption.py:38-44 leaves out pension age disability payment and ADP, which para 8(6) now lists. Main has the same gap.
  5. Nit: remove the stale "CI (running)" and r3 lines from the body.

Verified:

  • Tests at cc4d9ae: 42/42 new YAML, 3/3 property tests and 82/82 local-authority YAML pass.
  • The dispatched code-changes run passed, but against the old base.
  • The body gives aggregates only.

MaxGhenis and others added 2 commits October 5, 2026 12:55
Brings in #2009, #2006, #1944, #1907, #2033 and the rest of main since
4b84ea0. Each claim's scheme and non-dependant exemption still follow its
own family's council_tax_reduction_pensioner and
council_tax_reduction_applicant_has_non_dep_exemption, read on
is_council_tax_reduction_applicant_or_partner, for every claim.

- council_tax_reduction_pensioner: keeps #1944's reg 3 test (#1907's
  qualifying age, the IS/JSA/ESA and Universal Credit conditions) on this
  PR's applicant-and-partner members.
- #2006's joint-claims-only machinery is replaced by the per-family rule it
  anticipated: council_tax_reduction_claim_pensioner,
  council_tax_reduction_claim_scheme_supported and
  _legacy.single_claim_is_pensioner are removed, and nothing reads
  council_tax_reduction_claims_are_joint for a scheme or an exemption.
  council_tax_benefit keeps this PR's reconciliation.
- council_tax_reduction_household_has_pensioner is deleted again.
- simulated_council_tax_reduction_benunit keeps main's
  council_tax_reduction_assessable_capital.
- Tests on main that read the deleted flags now read the per-family ones:
  council_tax_reduction_pensioner.yaml, joint_liability.yaml,
  state_pension_credit_qualifying_age.yaml and four property tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
council_tax_reduction_pensioner is now #1944's SI 2012/2885 reg 3 test
on this PR's members: the qualifying age for State Pension Credit, and no
Income Support, income-based JSA, income-related ESA or Universal Credit
award. Property 1's oracle computes that directly. A family's own Universal
Credit award depends on the rent it pays, which changes when it lives
alone, so property 2 compares the families whose own awards are the same
either way (and checks there are some).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 3 commits October 5, 2026 15:27
- council_tax_benefit: the share leaves out only people in higher
  education, so where a jointly liable person it counts is in other
  education (who may be a student SI 2012/2885 Sch 1 para 7(5) leaves out),
  the share may be too small; a report is then kept rather than capped.
- council_tax_reduction_pensioner: where the household head applies alone,
  the benefit unit's Income Support, income-based JSA, income-related ESA
  and Universal Credit are its claimant and partner's, not the applicant's
  (as #2078 does for council_tax_reduction_relevant_income_based_benefit).
- Two YAML regressions: a report beside a paying simulated claim where a
  further-education student is counted in the share, and a grandmother
  head who stays a pensioner when the young parents get Income Support.
- Property test: the alone simulation is given each family's own awards
  instead of filtering; some generated adults are further-education
  students, and properties 5 and 6 follow the new rule.
- test_benefit_cap_and_ctr_pension_age_properties P5 compares against a
  reform of the cap exception alone, so the CTR pensioner test (which also
  sets the applicable amount) is held fixed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI found that swapping the other claiming families' ages and disability
benefits can move a family's simulated award through its applicable
amount: the model's severe disability premium counts the other adults in
the household (has_non_dependant_for_severe_disability_premium), joint
occupiers included. That is main's means test, not the scheme or the
exemption, so the perturbed run is given each family's applicable amount
and income from the unperturbed one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The re-review of 38364ca found that the guard missed in_FE, and that
current_education's age default (tertiary at 18 and 19) turns it on with
no education input. The guard now uses the model's student test for
non-dependants (_legacy.is_full_time_student_non_dep) or in_FE, for a
jointly liable person the share counts. The age default is kept
deliberately: in the enhanced FRS current_education is an observed input,
and the same test already treats an 18- or 19-year-old non-dependant as a
student. The documentation says so, including that part-time students and
students para 75(2) brings back in are treated the same way.

Regressions: the student shown by in_FE (fails on 38364ca), and a
jointly liable sharer with no education input at 19 (report kept) and 20
(capped). The property generator makes students by current_education or
by in_FE.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in #2081 (Universal Credit minimum income floor and work-related
groups) and the 2.122.0 release since 6b366b2. A clean merge: nothing
under local_authorities or council_tax_benefit changed on main, and no
conflict needed resolving.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI on d988725 (run 37383946508) failed property 4 on a Newham household:
a working-age head (47) claiming Universal Credit, a sharer couple aged 100
and 63 with earnings, and a non-dependant. Perturbing the head to 85 moves
the non-dependant into the sharer's claim (uc_non_dependants_counted: the
household's non-dependants count in one claim only, UC Regs 2013 Sch 4 para
9(2), the head family's where it claims). The sharer's size criteria rise
from one bedroom to two, its LHA cap from 9,000 to 10,860 and its award
from 0 to 1,288.29, and reg 3(1)(a)(ii) then makes it not a pensioner.

That is the family's own award, which the pensioner test is meant to follow,
so the change is intended. Property 4 now holds each family's own UC, IS,
JSA and ESA awards fixed, as property 2 already does, along with its means
test. The same population fails on a9466b8, so this predates the merge
of main; CI on a9466b8 did not generate it. With the awards held, a
household-level pensioner flag still fails property 4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

#2062 is now on main (merge commit c4659aa), so council_tax_reduction_household_head is gone from main. Its reads become is_resolved_household_head, which has the identical formula (the eldest member the is_household_head input flags, else the eldest member; a tie in age goes to the member listed first). Rename the reads and change nothing else. In this PR they are in:

  • policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_head_applies_alone.py
  • policyengine_uk/variables/gov/local_authorities/council_tax_reduction/is_council_tax_reduction_applicant_or_partner.py

Until they are renamed, those variables fail with VariableNotFound once this branch has main merged in.

MaxGhenis and others added 4 commits October 5, 2026 21:17
Brings in #2062 (one resolved household head) since 60821c7. #2062
deletes council_tax_reduction_household_head, which this PR's applicant
files read; the merge had no textual conflict, so without this change
every Council Tax Reduction calculation on the merged tree fails with
VariableNotFound. As agreed on #2062 ("whichever lands second"), the two
reads in is_council_tax_reduction_applicant_or_partner and
council_tax_reduction_head_applies_alone now use is_resolved_household_head,
which has the same formula. Nothing else changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The review of 94041e9 found that holding every family's own awards still
let property 4 fail through the same channel, and hid cross-family reads of
awards:
- In Newham, when the head's family stops claiming Universal Credit, the
  non-dependant moves into the sharer's claim and raises its
  uc_maximum_amount, which the four _legacy councils use as a UC family's
  applicable amount. The sharer's council award moved from 241.67 to 417.02
  with its awards held.
- Holding every family's awards also gives a perturbed 85-year-old head the
  award of the 47-year-old it replaced, and a mutant that read another
  family's UC survived.

Property 4 now holds, with each family's CTR means test, the two household
allocations through which other families reach a family's own awards:
uc_non_dependants_counted (UC Regs 2013 Sch 4 para 9(2)) and
has_non_dependant_for_severe_disability_premium. Each family's awards are
computed. The body passes on CI's population, its minimal household and
the reviewer's counterexample, and catches all five mutants the reviewer
and author built.

The generator also sets is_claimant_or_partner on every family's adults, so
wide-gap couples stay couples once #2082 presumes a member 20 or more years
younger to be a child. build() keeps each held value's own type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pension Credit's guarantee credit sets a family's CTR capital to nil, and
the model lets other families reach that guarantee credit twice: the
severe disability addition's residence condition counts every other adult
in the household, and pension_credit_assessable_capital divides the
household's savings among its pension-age adults. Property 4 held the CTR
applicable amount and income but not the capital, so a head reaching
pension age, or gaining Attendance Allowance, could move a pension-age
sharer's award from nil to its £900 share of an £1,800 bill (review r5,
finding 1).

Hold council_tax_reduction_assessable_capital with the rest of the means
test, name the two Pension Credit routes in the docstring, and pin as
Hypothesis examples the four populations that broke earlier versions of
the property: CI's Universal Credit non-dependant move, the
uc_maximum_amount move, and the two Pension Credit routes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main at 4ec7ef1 adds #2158: two savings-credit-only CTR cases and a Housing
Benefit regression case, all tests. Clean merge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@vahid-ahmadi vahid-ahmadi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review pass (Claude Code, on Vahid's behalf) at ad1c2be7, round 2

APPROVE. My round-1 blocker is fixed, and the rest is handled or cosmetic.

Round-1 items:

  1. Blocking (rebase and UC limb): closed. The branch has merged main. council_tax_reduction_pensioner.py keeps main's reg 3 test (has_attained_state_pension_credit_qualifying_age plus the IS, JSA(IB), ESA(IR) and UC limbs) and changes only whose circumstances it reads, to is_council_tax_reduction_applicant_or_partner. The UC award counts only where the applicant or partner is under the qualifying age, and not where the head applies alone. That matches SI 2012/2885 reg 3(1)(a)(ii)(bb).
  2. The mixed-age UC case: closed. council_tax_reduction_pensioner.yaml pins English and Newham mixed-age couples on UC to the working-age scheme.
  3. The description: closed. It now says what main already does (per-family assessment of joint claims) and what this PR adds: one rule for every claim, plus the three outcomes main still gets wrong.
  4. PADP/ADP in the non-dependant exemption: still open. council_tax_reduction_applicant_has_non_dep_exemption.py:40-42 lists AA, PIP daily living and DLA care, but not Pension Age Disability Payment or Adult Disability Payment, which para 8(6) now names. Main has the same gap; fine as a follow-up.
  5. Stale review-history lines in the description: mostly cleaned. One line about an incomplete "r3" remains.

Checked locally (merged with main at b7a73267, 22 commits ahead; clean):

  • Local-authority YAML: 163 passed.
  • test_council_tax_reduction_claimant_family_scheme_properties.py and test_council_tax_reduction_pension_credit_properties.py: 11 passed.
  • CI is green at this head.

Stack: #2078 is based on this branch and retargets to main once it merges. #1966 asked that its 24 devolved working-age cases be rerun after whichever lands second; they're in the local-authority suite above and pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vahid-ahmadi
vahid-ahmadi merged commit e28fd18 into main Oct 8, 2026
6 checks passed
@vahid-ahmadi
vahid-ahmadi deleted the ctr-per-family-scheme branch October 8, 2026 12:08
MaxGhenis added a commit that referenced this pull request Oct 8, 2026
…nto uc-reg3-3-single-claim

No conflicts. None of this PR's files changed on main since 9dd771a.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Council Tax Reduction: assess every claim's scheme and exemption on its own applicant

2 participants