Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1b69b84
Add HBAI and claimant-or-partner variables; deprecate generic child a…
MaxGhenis Sep 28, 2026
0f53b39
Test the benefit-unit structure and HBAI population groups
MaxGhenis Sep 28, 2026
04e85a1
Use HBAI adults and children in equivalisation; make age-18 splits ex…
MaxGhenis Sep 28, 2026
409633a
Limit Marriage Allowance and Tax-Free Childcare to the couple, not th…
MaxGhenis Sep 28, 2026
94413ce
Use each DfE scheme's own definition of a child or the student's family
MaxGhenis Sep 28, 2026
7117902
Apply the tax credit claimant and child definitions in Working Tax Cr…
MaxGhenis Sep 28, 2026
98853d4
Assess Pension Credit on the claimant and partner, not their children
MaxGhenis Sep 28, 2026
44023f0
Apply the legacy benefits' own child, young person and claimant defin…
MaxGhenis Sep 28, 2026
34cc129
Decide the shared accommodation rate and benefit cap on the claimant'…
MaxGhenis Sep 28, 2026
31e7008
Apply Universal Credit's claimant rules instead of age-18 counts
MaxGhenis Sep 28, 2026
029b262
Pay Guarantee Credit only within Pension Credit; add changelog fragments
MaxGhenis Sep 28, 2026
d4b6745
Add property and differential tests for the child, adult and claimant…
MaxGhenis Sep 29, 2026
2e5fc68
Test Housing Benefit's pension-age route on the claimant and partner
MaxGhenis Sep 30, 2026
0985251
Limit a benefit unit to its head and one partner
MaxGhenis Sep 30, 2026
7285827
Apply both schemes' child tests and the disability exception to the c…
MaxGhenis Sep 30, 2026
16940d8
Apply the Pension Credit severe disability couple rule; exclude place…
MaxGhenis Sep 30, 2026
2de365c
Assess means-test income for the claimant and partner only
MaxGhenis Sep 30, 2026
229bbfb
Make the eldest adult the claimant when the head is not one; flagged …
MaxGhenis Sep 30, 2026
9b13503
Never bar the severe disability addition by its recipient's own carer…
MaxGhenis Sep 30, 2026
2b635fa
Keep counting each programme's own dependants' income; drop only non-…
MaxGhenis Sep 30, 2026
f981459
Drop non-members' hours from the HB worker disregard; take the eldest…
MaxGhenis Sep 30, 2026
143cc3c
Expect a claimant's own Carer Support Payment not to bar their severe…
MaxGhenis Sep 30, 2026
4ed1742
Merge origin/main (7b9fc379) into remove-is-child-flags
MaxGhenis Oct 1, 2026
dd8e98f
Expect no guarantee credit for families not eligible for Pension Cred…
MaxGhenis Oct 2, 2026
207e225
Give main's UC-benefit and CTR property fixtures explicit claimants a…
MaxGhenis Oct 2, 2026
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/generic-child-adult-flags.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Added HBAI population variables (`is_hbai_dependent_child`, `is_hbai_adult`, `is_hbai_working_age_adult`, `is_hbai_pensioner`, `hbai_person_type`, and the equivalence-scale child bands) following the HBAI FYE 2025 glossary, `is_claimant_or_partner` for the single adult or couple a benefit unit is formed around, and programme-named child, young person and responsibility variables for Universal Credit, tax credits, the legacy means-tested benefits, the WTC childcare element and student support.
1 change: 1 addition & 0 deletions changelog.d/generic-child-adult-flags.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Deprecated the generic age-18 variables (`is_child`, `is_adult`, `num_children`, `num_adults`, `family_type`, `child_index`, the eldest and youngest child and adult ages, `benunit_count_children`, `benunit_count_adults`, the disabled child and adult counts, `is_WA_adult`, `is_young_child`, `is_older_child`). They keep their formulas for downstream users, but no model rule reads them any more: each programme uses its own legal definition of a child or young person and of the claimant and partner.
4 changes: 4 additions & 0 deletions changelog.d/generic-child-adult-flags.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- Fixed benefit-unit structure: a lone parent living with an 18- or 19-year-old still at school is no longer treated as a (married) couple, a couple with such a dependant is no longer treated as single or a lone parent, and a 16- or 17-year-old heading their own benefit unit is a single claimant rather than a couple with themselves as the child. Lone parent, single claimant and couple are now mutually exclusive.
- A benefit unit now has at most two claimants or partners: the head (the eldest adult head if more than one is given, or the eldest adult if the head is not an adult) and one partner (a member flagged as a parent if there is one, otherwise the eldest other adult). When the head is not flagged as a parent but two other members are, those two are the couple. Without relationship inputs, a member under 20 and at least 16 years younger than the head is presumed to be the head's child rather than their partner, so a lone parent with an 18-year-old at university stays single and a couple with one stays a couple of two. Datasets and users can supply `is_claimant_or_partner` directly.
- Fixed Marriage Allowance and the CPS marriage reforms to transfer allowance and split income only between spouses or civil partners, never from a child in the benefit unit; the Tax-Free Childcare work condition to test the applicant and partner only; the WTC lone parent, couple, disability and childcare elements and eligibility routes to use the tax credit claimant and child definitions; and the DfE schemes (Care to Learn, Childcare Grant, Parents' Learning Allowance, extended childcare income test) to use each scheme's own definition of a child or of the parent and partner.
- The HBAI equivalence scale now weights HBAI adults and dependent children, which changes factors only in households with no one aged 18 or over.
3 changes: 3 additions & 0 deletions changelog.d/legacy-child-definitions.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- Updated legacy benefits to use claimant and partner roles and programme-specific child definitions for legacy benefit allowances, income disregards, capital allocation, disability premiums, Housing Benefit childcare charges, and Council Tax Reduction exemptions.
- A child or young person placed with the family by a local authority is no longer counted as a member of the family for the legacy benefits (Income Support Regulations 1987 reg 16(4); Housing Benefit Regulations 2006 reg 21(3)).
- Housing Benefit's pension-age route now tests the claimant and any partner (SI 2014/1230 reg 6A(4)), so a pensioner living with an 18- or 19-year-old dependant can make a new Housing Benefit claim.
2 changes: 2 additions & 0 deletions changelog.d/lha-benefit-cap-claimant-definitions.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Replaced age-18 child/adult flags in the LHA shared accommodation rate and the benefit-cap rates with claimant/partner status and responsibility for a child or young person. Because one LHA category and one benefit cap serve both Universal Credit and Housing Benefit, the family rates apply if either scheme's test is met (UC regs 4-5, including the reg 5(1)(a) route for 16-year-olds; HB reg 19), via the new `is_responsible_for_child_or_young_person_for_uc_or_housing_benefit`.
- Renters under 35 receiving attendance allowance, the DLA care component at the middle or highest rate, or the PIP daily living component are now excepted from the shared accommodation rate (UC Regulations 2013 Sch 4 para 29(5)). The exception is UC's; the Housing Benefit "young individual" definition (HB Regulations 2006 reg 2(1)) has no disability exception, but the model uses one LHA category for both schemes. The other paragraph 29 exceptions and the conservative LHA non-dependant proxy remain as documented limits.
1 change: 1 addition & 0 deletions changelog.d/means-test-income-members.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Universal Credit, Housing Benefit, Income Support, tax credit and Council Tax Reduction means tests now count only the income of the claimant, any partner and the programme's own children or young persons, the members the model already counted. The income of any other member of the benefit unit no longer counts: for example an 18- or 19-year-old who is presumed to be the claimant's child but is neither a partner nor a qualifying young person. The same applies to the hours behind the Housing Benefit worker disregard. The shared helper is `add_for_members`. The regulations count only the claimant's and partner's income (UC Regulations 2013 reg 22; HB Regulations 2006 reg 25; IS Regulations 1987 reg 23; Tax Credits Act 2002 s.7; Council Tax Reduction Schemes (Prescribed Requirements) (England) Regulations 2012 Sch 1 para 11). Dropping dependants' own income as well is left to a follow-up, because some survey records give children adult-level incomes.
3 changes: 3 additions & 0 deletions changelog.d/pension-credit-claimant-partner.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- Applied Pension Credit age conditions, income assessment and carer and severe disability additions to claimants and partners rather than dependent children and young people, and removed the severe disability addition's child veto.
- The Pension Credit severe disability addition now follows SPC Regulations 2002 Sch I para 1(1) and reg 6(5): a claimant with a partner qualifies if both receive a qualifying disability benefit and a carer benefit is paid for at most one of them (double amount when none is paid, single amount when one is), or if one receives it, the other is blind and no carer benefit is paid for the first (single amount). The person cared for is not observed, so each carer benefit paid in the benefit unit is taken to be for a different claimant or partner, and never for its recipient (a qualifying claimant's own Carer's Allowance does not bar them). The household non-dependant residence test and the mixed-age transitional protection remain unmodelled.
- Guarantee Credit is now paid only to benefit units eligible for Pension Credit, so it can no longer act as a passport (for example to targeted childcare) for families that do not qualify.
1 change: 1 addition & 0 deletions changelog.d/uc-claimant-rules.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Apply Universal Credit age conditions, childcare work conditions, and household capital allocation to claimants and partners, with documented proxies for the observable minimum-age exceptions from age 16. Rename the capital allocation helper to `household_uc_unreported_claimants`.
43 changes: 43 additions & 0 deletions policyengine_uk/dynamics/demographics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Benefit-unit composition for the labour supply elasticity groups.

The OBR labour supply note (Tables A1 and A2) assigns elasticities by
partnership and by the presence and age of the youngest child, without
defining a child. The dynamics modules keep an explicit age band for this:
benefit-unit members under 18 are children and members aged 18 or over are
adults. This is a modelling choice for the elasticity groups, not a legal
definition.

Reference: https://obr.uk/docs/dlm_uploads/NICS-Cut-Impact-on-Labour-Supply-Note.pdf
"""

import numpy as np
import pandas as pd


def benunit_age_18_composition(sim) -> pd.DataFrame:
"""Per-person view of their benefit unit, split at age 18.

Returns a DataFrame aligned with the person entity, with columns:
- count_under_18: benefit-unit members under 18;
- youngest_under_18_age: age of the youngest such member (inf if none);
- count_aged_18_or_over: benefit-unit members aged 18 or over.
"""
age = np.asarray(sim.calculate("age"), dtype=float)
# Position of each person's benefit unit, from the simulation's own entity
# structure (works for datasets and hand-built situations alike).
benunit_id = np.asarray(sim.populations["benunit"].members_entity_id)
under_18 = age < 18
people = pd.DataFrame(
{
"benunit_id": benunit_id,
"under_18": under_18,
"aged_18_or_over": ~under_18,
"age_if_under_18": np.where(under_18, age, np.inf),
}
)
by_benunit = people.groupby("benunit_id").agg(
count_under_18=("under_18", "sum"),
youngest_under_18_age=("age_if_under_18", "min"),
count_aged_18_or_over=("aged_18_or_over", "sum"),
)
return by_benunit.reindex(benunit_id).reset_index(drop=True)
16 changes: 10 additions & 6 deletions policyengine_uk/dynamics/participation.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from policyengine_uk import Simulation
import warnings

from policyengine_uk.dynamics.demographics import benunit_age_18_composition
from policyengine_uk.model_api import WEEKS_IN_YEAR

# Weekly hours treated as full time when putting non-workers on a comparable
Expand All @@ -38,13 +39,14 @@ def calculate_participation_elasticities(
# Get demographic characteristics
gender = sim.calculate("gender")
is_married = sim.calculate("is_married", map_to="person")
has_children = sim.calculate("benunit_count_children", map_to="person") > 0
youngest_child_age = sim.calculate("youngest_child_age", map_to="person")
composition = benunit_age_18_composition(sim)
has_children = composition["count_under_18"].values > 0
youngest_child_age = composition["youngest_under_18_age"].values
is_single = ~is_married

# Get partner employment status for married individuals
is_household_head = sim.calculate("is_household_head", map_to="person")
benunit_count_adults = sim.calculate("benunit_count_adults", map_to="person")
benunit_count_adults = composition["count_aged_18_or_over"].values
employment_income = sim.calculate("employment_income")
benunit_id = sim.calculate("benunit_id", map_to="person")
adult_index = sim.calculate("adult_index")
Expand All @@ -53,14 +55,16 @@ def calculate_participation_elasticities(
df = pd.DataFrame(
{
"benunit_id": benunit_id,
"is_adult": adult_index > 0,
"is_aged_18_or_over": adult_index > 0,
"employed": employment_income > 0,
"benunit_count_adults": benunit_count_adults,
"count_aged_18_or_over": benunit_count_adults,
}
)

# Calculate total employed adults per benunit
benunit_employed = df[df["is_adult"]].groupby("benunit_id")["employed"].sum()
benunit_employed = (
df[df["is_aged_18_or_over"]].groupby("benunit_id")["employed"].sum()
)

# Map back to individuals
employed_adults_in_benunit = df["benunit_id"].map(benunit_employed).fillna(0)
Expand Down
11 changes: 7 additions & 4 deletions policyengine_uk/dynamics/progression.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import numpy as np
import pandas as pd
from policyengine_uk import Simulation
from policyengine_uk.dynamics.demographics import benunit_age_18_composition
from policyengine_uk.utils.marginal_rates import marginal_rate_step


Expand Down Expand Up @@ -212,8 +213,9 @@ def calculate_labour_substitution_elasticities(
# Get demographic characteristics for elasticity assignment
gender = sim.calculate("gender")
is_married = sim.calculate("is_married", map_to="person")
has_children = sim.calculate("benunit_count_children", map_to="person") > 0
youngest_child_age = sim.calculate("youngest_child_age", map_to="person")
composition = benunit_age_18_composition(sim)
has_children = composition["count_under_18"].values > 0
youngest_child_age = composition["youngest_under_18_age"].values

# Initialize elasticity array
elasticities = np.zeros(gender.shape, dtype=float)
Expand Down Expand Up @@ -282,8 +284,9 @@ def calculate_labour_net_income_elasticities(
# Get demographic characteristics for elasticity assignment
gender = sim.calculate("gender")
is_married = sim.calculate("is_married", map_to="person")
has_children = sim.calculate("benunit_count_children", map_to="person") > 0
youngest_child_age = sim.calculate("youngest_child_age", map_to="person")
composition = benunit_age_18_composition(sim)
has_children = composition["count_under_18"].values > 0
youngest_child_age = composition["youngest_under_18_age"].values

# Initialize elasticity array
elasticities = np.zeros(gender.shape, dtype=float)
Expand Down
1 change: 1 addition & 0 deletions policyengine_uk/model_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
from policyengine_core import periods
from microdf import MicroSeries, MicroDataFrame
from policyengine_uk.utils.scenario import Scenario
from policyengine_uk.utils.benefit_unit import add_for_members

GBP = "currency-GBP"
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
description: Disability benefits whose recipients under 35 are excepted from the shared accommodation rate.
values:
2013-04-29:
- attendance_allowance
- dla_sc_middle_plus
- pip_dl
metadata:
unit: list
label: shared accommodation rate disability exceptions
reference:
- title: Universal Credit Regulations 2013 Sch 4 para 29(5)
href: https://www.legislation.gov.uk/uksi/2013/376/schedule/4/paragraph/29
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
description: The exclusive annual age limit approximating the first Monday in September following the sixteenth birthday for Housing Benefit childcare charges for a disabled child.
values:
2006-03-06: 17
metadata:
unit: year
label: housing benefit childcare disabled child age limit
reference:
title: Housing Benefit Regulations 2006, regulation 28(6) and (13)
href: https://www.legislation.gov.uk/uksi/2006/213/regulation/28
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
description: The exclusive annual age limit approximating the first Monday in September following the fifteenth birthday for Housing Benefit childcare charges.
values:
2006-03-06: 16
metadata:
unit: year
label: housing benefit childcare standard child age limit
reference:
title: Housing Benefit Regulations 2006, regulation 28(6) and (13)
href: https://www.legislation.gov.uk/uksi/2006/213/regulation/28
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
description: Income Support personal allowance age threshold for older single claimants.
values:
1988-04-11: 25
metadata:
unit: year
label: income support older allowance age threshold
reference:
- title: Income Support (General) Regulations 1987, Schedule 2 paragraph 1
href: https://www.legislation.gov.uk/uksi/1987/1967/schedule/2
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
description: Income Support personal allowance age threshold for young claimants and partners.
values:
1988-04-11: 18
metadata:
unit: year
label: income support younger allowance age threshold
reference:
- title: Income Support (General) Regulations 1987, Schedule 2 paragraph 1
href: https://www.legislation.gov.uk/uksi/1987/1967/schedule/2
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
description: Income Support lone-parent youngest-child age limit, applied inclusively under the existing annual-age approximation although legislation specifies under this age.
values:
2012-05-21: 5
metadata:
unit: year
label: income support lone parent child age limit
reference:
- title: Income Support (General) Regulations 1987, Schedule 1B paragraph 1
href: https://www.legislation.gov.uk/uksi/1987/1967/schedule/1B
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
description: Annual age below which a disabled child's childcare charges count for the Working Tax Credit childcare element, approximating the week containing the 1 September after the child's 16th birthday.
values:
2003-04-06: 17
metadata:
unit: year
label: WTC childcare disabled child age limit
reference:
- title: Working Tax Credit (Entitlement and Maximum Rate) Regulations 2002 reg 14(3)-(4)
href: https://www.legislation.gov.uk/uksi/2002/2005/regulation/14
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
description: Annual age below which a child's childcare charges count for the Working Tax Credit childcare element, approximating the week containing the 1 September after the child's 15th birthday.
values:
2003-04-06: 16
metadata:
unit: year
label: WTC childcare child age limit
reference:
- title: Working Tax Credit (Entitlement and Maximum Rate) Regulations 2002 reg 14(3)
href: https://www.legislation.gov.uk/uksi/2002/2005/regulation/14
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
description: Default minimum age for Universal Credit claimants.
values:
2013-04-29: 18
metadata:
unit: year
label: universal credit minimum age
reference:
- title: Welfare Reform Act 2012, section 4(1)(a)
href: https://www.legislation.gov.uk/ukpga/2012/5/section/4
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
description: Reduced minimum age for Universal Credit claimants meeting an age exception.
values:
2013-04-29: 16
metadata:
unit: year
label: universal credit reduced minimum age
reference:
- title: The Universal Credit Regulations 2013, regulation 8(1)
href: https://www.legislation.gov.uk/uksi/2013/376/regulation/8
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
description: Age below which a benefit-unit member much younger than the head is presumed to be the head's child rather than their partner, when relationships are not supplied. This is a PolicyEngine modelling presumption, not a statutory rule.
values:
0001-01-01: 20
metadata:
unit: year
label: presumed child age limit
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
description: Minimum age gap below the benefit-unit head at which a younger member is presumed to be the head's child rather than their partner, when relationships are not supplied. This is a PolicyEngine modelling presumption, not a statutory rule.
values:
0001-01-01: 16
metadata:
unit: year
label: presumed child minimum age gap
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
description: Age from which a dependent child takes the older-child weight in the Households Below Average Income modified OECD equivalence scale.
values:
2010-04-01: 14
metadata:
unit: year
label: HBAI equivalence scale older-child age
reference:
- title: HBAI background information and methodology FYE 2025, Table 4 (equivalence scales)
href: https://www.gov.uk/government/statistics/households-below-average-income-for-financial-years-ending-1995-to-2025/households-below-average-income-background-information-and-methodology-report-fye-2025#equivalisation-1
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
description: Age below which every person is a dependent child in Households Below Average Income statistics.
values:
0001-01-01: 16
metadata:
unit: year
label: HBAI dependent child age limit
reference:
- title: HBAI background information and methodology FYE 2025, glossary (Child)
href: https://www.gov.uk/government/statistics/households-below-average-income-for-financial-years-ending-1995-to-2025/households-below-average-income-background-information-and-methodology-report-fye-2025#child
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
description: Age below which a person in full-time non-advanced education or unwaged government training, living with parents and without a partner, is a dependent child in Households Below Average Income statistics.
values:
0001-01-01: 20
metadata:
unit: year
label: HBAI dependent young person age limit
reference:
- title: HBAI background information and methodology FYE 2025, glossary (Child)
href: https://www.gov.uk/government/statistics/households-below-average-income-for-financial-years-ending-1995-to-2025/households-below-average-income-background-information-and-methodology-report-fye-2025#child
Loading
Loading