Skip to content

Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday - #2279

Open
MaxGhenis wants to merge 5 commits into
mainfrom
uc-pc-qyp-sixteen-route
Open

MaxGhenis wants to merge 5 commits into
mainfrom
uc-pc-qyp-sixteen-route

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

UC Regs 2013 reg 5(1) makes a person who has reached 16 but not 20 a qualifying young person:

(a) up to, but not including, the 1st September following their 16th birthday; and
(b) up to, but not including, the 1st September following their 19th birthday, if they are enrolled on, or accepted for, approved training or a course of education [...]

SPC Regs 2002 reg 4A(1) is the same. Route (a) has no education condition. The model encoded only route (b), so a 16-year-old outside education was not a UC or Pension Credit qualifying young person (QYP), and nothing that reads QYP status counted them: the UC child element, the two-child limit order, the work allowance, UC responsibility, and the Pension Credit child addition. The benefit cap and LHA family rules already counted every 16-year-old non-claimant through a separate stand-in on main, so there the change reaches only 17-year-olds born from 1 September to 6 October.

  • New date fact. is_before_first_september_following_16th_birthday (Person, year) is true when the fiscal year starts (6 April) before the 1 September following the person's 16th birthday. It places the date from date_of_birth, or from age and months_since_last_birthday, as the model's other date-of-birth rules do, and a user can set it directly. It mirrors the Day-level input current_day_is_before_first_september_following_16th_birthday in rulespec-uk's uk/regulations/uksi/2013/376/5.yaml.
  • Both QYP variables now read: age condition AND (that fact OR the education route) AND NOT own-right benefits. Reg 5(5) and reg 4A(5) apply to both routes. Reg 5(3)'s contract-of-employment exclusion applies to route (b) only, so a 16-year-old apprentice qualifies through route (a).
  • Docstring only: is_responsible_for_child_or_young_person_for_uc_or_housing_benefit said "the UC child element does not yet model reg 5(1)(a)". It now says the UC test includes it. Its separate count of any 16-year-old non-claimant stays for Housing Benefit, whose young person follows Child Benefit's unmodelled school-leaver rules (Child Benefit (General) Regulations 2006 regs 4, 5 and 7).

The annual approximation

The route ends on a fixed 1 September; the model's year runs from 6 April, and its age is the age on 6 October. A year counts in the route if any of it falls in the route, which is the same as the year starting before that 1 September. This follows the rule that main already applies to this route for the benefit cap and LHA family rates (is_responsible_for_child_or_young_person_for_uc_or_housing_benefit, and has_sixteen_year_old_dependant in #2089), so UC's child element, work allowance, cap and LHA tests all agree on who is a QYP. It is also the approximation the task suggested ("treat age 16 as within the route for the part-year").

Who it covers:

  • Every annual age of 16. The 16th birthday falls between 7 October of the year before and 6 October, so the following 1 September is in the year or the next, always after 6 April.
  • A 17-year-old born from 1 September to 6 October. Their route runs to 1 September of the year.
  • Nobody older. Below 16 the person is a child anyway.

Bias, computed by calendar arithmetic over every birthday (date arithmetic, ages on 6 October, fiscal year 2026-27, birthdays equally likely). It compares the share of the year a person outside education is a child or in the route with what each rule counts:

Annual age True share of the year This PR (year starts before the date) Status on 6 October Status quo (route omitted)
16 0.464 1.000 0.099 0
17 0.040 0.099 0 0
Sum 0.504 1.099 (+0.595) 0.099 (−0.405) 0 (−0.504)

No annual boolean is unbiased. A status-on-6-October rule is less biased in aggregate, but it would count about one 16-year-old outside education in ten. It would also disagree with the cap and LHA family rules, which count all of them. Switching to it is one character in the new variable: year_of_first_september >= period.start.year becomes >, since 6 October falls before the 1 September of a later year only. With no date of birth, a household calculation places the birthday on 6 April, so a 16-year-old outside education is a QYP for the year and a 17-year-old is not.

Tests

YAML, hand-computed from the regulations: new qualifying_young_person_no_education_route.yaml, 14 cases (2026-27 unless stated):

Case Facts Expected
1 16, outside education UC and PC QYP; child or QYP for both
2 16, apprentice QYP: reg 5(3) excludes employment contracts from route (b) only
3 16, outside education, route set false not a QYP
4 16, upper secondary, route set false QYP through route (b)
5 17, born 15 September 2009 in the route until 31 August 2026: QYP
6 17, born 1 September 2009 16th birthday on 1 September runs the route to the 17th birthday: QYP
7 17, born 31 August 2009 the route ended on 31 August 2025: not a QYP
8 16, receiving UC in own right not a QYP (reg 5(5), reg 4A(5))
9 16, looked after QYP, but not the family's child or QYP
10 15 and 18, outside education neither is a QYP; the 15-year-old is a child
11 lone parent 40 + 16-year-old outside education first-child higher amount £351.88 × 12 = £4,222.56; work allowance (reg 22(1)(b)) £710 × 12 = £8,520; maximum amount £424.90 × 12 + £4,222.56 = £9,321.36; not the cap's single rate
12 pensioner 70 + 16-year-old outside education first child addition (Sch IIA para 10, born before 6 April 2017) £81.07 × 52 = £4,215.64; minimum guarantee £238.00 × 52 + £4,215.64 = £16,591.64
13 2025-26: lone parent, 16-year-old outside education, children 7 and 5 the 16-year-old ranks first: £339 × 12 = £4,068; the 7-year-old £292.81 × 12 = £3,513.72; the 5-year-old (born after 6 April 2017) is the third child under reg 24A: £0, limit-affected; total £7,581.72
14 a 17-year-old with date_of_birth 15 September 2009 beside another household's 16-year-old with none both in the route and QYPs: a date of birth given for one person leaves the other's age-placed date

is_uc_work_allowance_eligible.yaml Case 6 expected no work allowance for "a nonqualifying 16-year-old" outside education, which reg 5(1)(a) contradicts. It now sets the route false, and a new Case 6a asserts the default.

Fail on base (for the first 12 cases). With main's two QYP formulas restored, the new variable removed and its output lines stripped, the 10 cases that do not set the new input run: 7 fail and 3 pass. Cases 1, 2, 5, 6 and 9 fail on is_qualifying_young_person_for_universal_credit ([0, 0] for [0, 1]), case 11 on uc_individual_child_element (£0 for £4,222.56) and case 12 on child_minimum_guarantee_addition (£0 for £4,215.64). Cases 7, 8 and 10, where the route does not apply, pass on both.

Properties (test_child_and_adult_definitions_properties.py, new invariant 5):

  1. Calendar differential, exhaustive. For every date of birth from 2008 to 2011 (1,461 people, ages 14 to 18 on 6 October 2026), the new variable equals date(2026, 4, 6) < first 1 September after the 16th birthday, computed with Python date arithmetic, which shares no code with the model's month grid.
  2. Calendar differential, generated. Hypothesis draws dates of birth from 1990 to 2031 and years from 2013 to 2031.
  3. Route properties over a grid. Ages 14 to 21, every education value, own-right benefits, approved training, apprenticeship and looked-after status, with the route off and on (2,048 families). For UC and PC: switching the route on never removes QYP status; with it on, exactly ages 16 to 19 without own-right benefits qualify, whatever the education inputs; own-right benefits exclude both routes; UC and PC agree below 19.
  4. The existing oracle (qualifying_young_person) gains the route for UC and PC, and the module docstring drops the route from its list of omissions.

Mutation checks. Each mutation below was applied alone, and in each case a new test failed:

  • reading the raw date_of_birth input instead of birth_day (killed by case 14);
  • >= 901 to > 901 (1 September birthdays);
  • >= to > on the year;
  • dropping the route from UC;
  • dropping it from PC;
  • applying the own-right exclusion to route (b) only.

Two property tests elsewhere built "a member outside every programme's family" as a 16 to 19 year old not in education (test_means_test_income_properties.py, test_legacy_award_readers_properties.py). At 16 that member is now a UC QYP, so the family gains a child element (£4,068 in 2025-26) and the premise fails. CI on 6af701944 failed on exactly these two tests, with 3,618 passed and the YAML suite at 2,972 passed. Their generators now set the route false for that member (2ef1d8dbf). The test functions are unchanged, so Hypothesis draws the same examples as on main.

Run locally at 6af701944, plus the two modules above at 2ef1d8dbf (test_legacy_award_readers_properties.py 11 passed; test_means_test_income_properties.py passed):

  • pytest policyengine_uk/tests/test_child_and_adult_definitions_properties.py: 15 passed (12 existing, 3 new).
  • policyengine-core test on the new YAML, the work allowance, QYP and benefit cap files: 44 passed.
  • ruff format and ruff check: clean.

The full suites run in CI, because the host was over its memory threshold for a full local run.

Review

Independent review r1c (Opus 5.5, Subfleet job 20261010-204535-qyp16-review-r1c; the Codex lanes had no capacity) APPROVED 2ef1d8dbf and rulespec-uk#446 at 15f391e, with seven P2 findings, all addressed in b171ef789:

  • P2-1: the route variable's docs now warn that setting it for some people gives everyone else false (verified: it zeroes another household's 16-year-old), and point to date_of_birth.
  • P2-2: merge rulespec-uk#446 first so the axiom: line's companion case is on main.
  • P2-3: case 13 (two-child limit order).
  • P2-4: case 14 (mixed date-of-birth inputs).
  • P2-5: the docs state the departure from the 6 October reading that is_SP_age uses.
  • P2-6: the fail-on-base note says the outputs were stripped.
  • P2-7: the summary is qualified for the cap and LHA.

The review is in ~/reviews/pe-uk-qyp-sixteen-route-2026-10-10/review_r1c.md. A delta review (GPT-6.1 Sol, 20261011-085419-qyp16-review-delta, review_delta.md) APPROVED b171ef789 with no new defects. rulespec-uk#446 merged first, as P2-2 asked (c65fd7dfb).

addd1270d merges main (43 commits), including the claimant-or-partner filter for PC and the move of Scottish CTR to the UC definition. Run on the merged tree: 66 YAML cases (this PR's file, the work allowance, benefit cap and means-test files, and main's new claimant and CTR young-person files) and 34 property tests (the child and adult definitions, CTR young person, legacy award readers and means-test income modules) all pass.

Microsimulation impact

These are real Microsimulation runs on the Enhanced FRS 2024-25 (uk-data release 1.57.4, SHA-256 03fe15e4…8d4), run one at a time, each in its own venv, for 2025-26 to 2030-31:

Each run saved 38 arrays a year (microsim_dump.py):

  • per person: the QYP and child-or-QYP flags for UC and PC, the council tax reduction young-person flag, uc_individual_child_element, is_uc_claimant, and BHC/AHC poverty;
  • per benefit unit: UC (before and after the cap), the child element, maximum amount, work allowance, both responsibility tests, the cap rate and reduction, the LHA shared-rate test, HB, PC, the PC child addition, the Scottish two-child limit payment, council tax benefit, the CTR lone-parent test and applicable amount;
  • per household: household_net_income, council_tax_reduction and council_tax_less_benefit.

Ids and weights were checked identical, and the arrays were compared exactly, with no tolerance.

What people receive: no change in any year. These differ in 0 records in every year from 2025 to 2030:

  • UC: universal_credit and universal_credit_pre_benefit_cap;
  • benefit_cap_reduction, housing_benefit and pension_credit;
  • council tax: council_tax_reduction, council_tax_benefit and council_tax_less_benefit;
  • two_child_limit_payment, household_net_income, in_poverty_bhc and in_poverty_ahc.

So do the cap's single rate, the LHA shared-rate test, and the CTR lone-parent test and applicable amount.

What changes:

Year New UC and PC QYPs (people) UC child element (£m) UC work allowance (£m) UC maximum amount (£m) PC child addition, intermediate (£m) Scottish CTR young persons (people)
2025-26 +240,663 +27.1 +19.7 +3.0 +27.1 +137
2026-27 +241,578 +28.3 +20.5 +3.1 +28.2 +138
2027-28 +242,471 +29.0 +21.1 +3.2 +29.2 +138
2028-29 +243,441 +29.9 +21.6 +3.3 +30.1 +139
2029-30 +244,512 +30.9 +22.1 +3.3 +31.2 +139
2030-31 +245,613 +33.3 +22.7 +4.0 +33.9 +140

Why the money doesn't move (2026-27):

  • Who the new QYPs are. 101 records hold all of them: 196,728 people aged 16 and 44,850 aged 17. The 17-year-olds are those whose imputed birthdays fall from 1 September to 6 October.
  • Most head their own benefit unit. 234,205 of them are the claimant of their own unit, apart from their parents, as the FRS records them. The UC consumers exclude claimants from the child index, the child element and the responsibility test. Since 9d3a9be5a, the PC child-or-QYP flag and Scottish CTR also exclude the benefit unit's claimant and partner, so only 7,372 of the new QYPs (the dependants) change the PC flag.
  • The rest are dependants in units that don't receive UC. The 7,372 dependants are in 22 benefit units, where the UC child element rises by £28.3m, but none of those units receives UC in either run.
  • The higher maximum amounts don't reach UC. Four of those units are UC-eligible, so their maximum amount rises (+£3.1m). Their UC is £0 before and after, because the take-up draw (would_claim_uc, verified false for all four) has them not claiming.
  • The PC child addition is an ungated intermediate. It is computed for every benefit unit with a child or QYP (£62.0bn on main in 2026-27), and only units eligible for Pension Credit receive it. The +£28.2m sits in the same 22 units, none of which receives pension_credit.
  • Scottish working-age CTR now reads the UC definition (SSI 2021/249 reg 6, on main since that scheme moved to it). Two records (138 people) become young persons for it, and no CTR amount changes.

An earlier run against 3173baa89 (main before 9d3a9be5a) gave the same paid outputs (all £0). Its PC child-addition intermediate rose by £1.0bn, because own-unit heads then counted as their own QYPs; 9d3a9be5a removed that.

The route would reach the parents' claims only if the data put these 16- and 17-year-olds in their parents' benefit units. The uk-data investigation the UK hub started measured that on this branch against 3173baa89, with uk-data 1.58.0:

Whether to re-form those units is the open question in PolicyEngine/policyengine-uk-data#562.

Follow-ups (not in this PR; queued with the UK hub)

  • Child Benefit regs 4, 5 and 7 (school leavers), and through them Housing Benefit and legacy young persons, are not modelled. Nor is CTC Regs 2002 reg 4 (up to 2024-25).
  • Pension Credit reg 4A(1)(b)'s 1 September after the 19th birthday is not modelled. Reg 4A(5)'s income support exclusion is also missing: receives_benefits_in_own_right covers UC, ESA and JSA.
  • After Give Universal Credit and Housing Benefit their own benefit cap and LHA shared-accommodation rules #2089 lands, has_sixteen_year_old_dependant in the UC cap's single rate is redundant with UC responsibility, except for 16-year-olds receiving benefits in their own right or looked after.
  • Already done on main since this PR opened: Scottish working-age CTR reads the UC definition, and the PC child-or-QYP flag excludes the benefit unit's claimant and partner (9d3a9be5a).

axiom: uk:regulations/uksi/2013/376/5#qualifying_young_person encoded-correct, companion case aged_16_before_first_september_following_16th_birthday_without_education_or_training_qualifies in uk/regulations/uksi/2013/376/5.test.yaml (TheAxiomFoundation/rulespec-uk#446, merged c65fd7dfb); uk/regulation/uksi/2002/1792/4A TheAxiomFoundation/rulespec-uk#360 queued (corpus scope in TheAxiomFoundation/axiom-corpus#765 not yet released)

🤖 Generated with Claude Code

Reproducer: policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/work_allowance/is_uc_work_allowance_eligible.yaml::Case 6, a 16-year-old outside education past the reg. 5(1)(a) route does not create a work allowance., policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/work_allowance/is_uc_work_allowance_eligible.yaml::Case 6a, a 16-year-old outside education in the reg. 5(1)(a) route creates a work allowance., policyengine_uk/tests/policy/baseline/finance/benefit/qualifying_young_person_no_education_route.yaml, policyengine_uk/tests/policy/baseline/gov/dwp/means_test_income_members.yaml::A 17-year-old outside education and past the 1 September after their 16th birthday is not a UC young person, so their pay does not count, policyengine_uk/tests/test_child_and_adult_definitions_properties.py::test_route_year_starts_before_first_september_for_every_birthday, policyengine_uk/tests/test_child_and_adult_definitions_properties.py::test_route_matches_calendar_arithmetic_in_any_year

…ducation condition

UC Regs 2013 reg. 5(1)(a) and SPC Regs 2002 reg. 4A(1)(a) make a person who
has reached 16 a qualifying young person up to the 1 September following
their 16th birthday, whatever their education. The model encoded only the
education route, so a 16-year-old outside education was not a UC or PC
qualifying young person.

is_before_first_september_following_16th_birthday places that date from the
date of birth and counts the route for a year that starts (6 April) before
it: every annual age of 16, and a 17-year-old born from 1 September to 6
October. Both QYP variables OR it with the education route, under the same
age band and own-right benefits exclusion. Tests: YAML cases hand-computed
from the regulations (flags, date edges, UC child element and work
allowance, PC child addition), the property-test oracle, a calendar
differential over every birth date in 2008-2011 and across years, and a
grid test of sufficiency, monotonicity, exclusion and UC/PC agreement.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 10, 2026
Two property tests build a member who is outside every programme's family
as a 16 to 19 year old not in education. At 16 that member is now a UC
qualifying young person under reg 5(1)(a), so the family's UC gains a child
element and the tests' premise no longer holds. Their generators now set
is_before_first_september_following_16th_birthday false for that member.
The test functions are unchanged, so Hypothesis draws the same examples.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Oct 11, 2026
…nefit reg. 4 period

test_excluded_member_never_changes_is_eligibility adds a member aged 16 or over who is not in education, on the premise that they are not a qualifying young person. With reg. 4 a 16-year-old who has left education is one until the 31 August after their 16th birthday, so the generator now puts the member past that date, as #2279 does for the same reason in two other property tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 2 commits October 11, 2026 08:53
…date-of-birth tests

- The route variable's docs now warn that setting it for some people gives
  everyone else false, and point to date_of_birth for placing one person.
  They also state that the rule departs from the 6 October reading that
  is_SP_age uses.
- YAML: a 2025-26 case where the 16-year-old ranks first under the
  two-child limit and the youngest child loses the element (GBP 7,581.72),
  and a case mixing a person with date_of_birth and one without (both in
  the route). The second kills a mutant that reads the raw date_of_birth
  input instead of birth_day.
- means_test_income_members.yaml: the 17-year-old case's name says why they
  are outside the route.

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

Copy link
Copy Markdown
Collaborator Author

PE-UK main fix landed (UK hub, 2026-10-11 21:42Z): #2349 merged as ed7b42f9. It fixes test_dataframe_dataset.py::test_an_exported_frame_loads_back_and_exports_itself, which failed on every PR after #2251. Merge main into this branch and push once; don't re-run an old run, because re-runs use the original commit. If this PR's only Test failure was that test, the next run should be green.

MaxGhenis added a commit that referenced this pull request Oct 12, 2026
Brings in #2279's head 9d2fadb.

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

Brings in #2299's round 1 fixes, #2387 and #2279's head 9d2fadb.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Oct 12, 2026
Brings in #2279's head 9d2fadb and main e01f2f5 with it.

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

Brings in #2301 and #2299's round 1 fixes, #2387 and #2279's head. The
one conflict, the CTC QYP docstring, keeps both changes: the ceased-
education wording and the Income Support own-right input.

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

This branch has not been deployed

No deployments
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.

1 participant