Repository navigation
Conversation
…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>
…p-terminal-date-19
UC Regs 2013 reg. 5(1)(b) and SPC Regs 2002 reg. 4A(1)(b) keep a young person in education or training a qualifying young person up to, but not including, the 1 September following their 19th birthday. - Universal Credit read the date from an input that defaulted to false, so a 19-year-old at school was not a qualifying young person in a household calculation, while the microdata sets the input true for every 19-year-old in education. - Pension Credit did not model the date at all. Add is_before_first_september_following_19th_birthday, which counts a year that starts before the date, as the 16th-birthday fact does. is_before_universal_credit_qualifying_young_person_terminal_date now follows it unless set, and Pension Credit's new terminal date condition reads it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
…p-terminal-date-19 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A mutation that read a 19th birthday on 1 September as its own "1 September following" was caught by the calendar property test only. Case 11 pins the edge in YAML too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…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>
…r 20 Review round 1 of #2300: setting the 19th-birthday fact for one person gave everyone else in the situation false (the default), so another 19-year-old student silently lost the Pension Credit and Universal Credit status. The formula gives true for everyone under 20 on 6 October, which is everyone the age limit lets be a qualifying young person, so true is now the default for the fact and for the two terminal-date variables that read it. A partial setting then leaves everyone else where the formula puts them. Tested from age alone for ages 0 to 25 and all months, and in a mixed household (property test and YAML Case 12). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Case 3's parent, not set, now takes the default (true): harmless, since every reader is behind the qualifying young person age limit. The older child_qualifying_young_person.yaml case left its after-terminal-date person unset and relied on the old false default; it now sets the input explicitly. The docstrings now say the default is the formula's value under 20 and that older people are excluded before it is read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in main e01f2f5. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2291
Stacked on #2279. Do not merge before it. This PR builds on #2279's Pension Credit qualifying young person; #2279 waits on d1345. Until #2279 merges, the diff here also shows its commits. This PR's own change, against
mainwith #2279 merged:git diff $(git merge-tree --write-tree origin/main origin/uc-pc-qyp-sixteen-route) origin/qyp-terminal-date-19Summary
A young person in non-advanced education or approved training is a qualifying young person (QYP) "up to, but not including, the 1st September following their 19th birthday". The model treated a 19-year-old three ways:
This PR places the date from the date of birth for both programmes. Against its base (
main4d80b29 with #2279 merged), 2026-27, a lone parent aged 45 with a 19-year-old at school (scratch/probe_D_base_vs_branch.txt; the same onmain9475c95 before #2285):is_qualifying_young_person_for_universal_credit(the 19-year-old)uc_individual_child_elementis_uc_work_allowance_eligibleis_qualifying_young_person_for_pension_creditThis is a follow-up from #2279 (its items "Pension Credit reg. 4A(1)(b)'s 1 September" and the Universal Credit default).
The law
Change
is_before_first_september_following_19th_birthday(Person, year): true when the fiscal year starts (6 April) before the 1 September following the 19th birthday. It readsdate_of_birth, orageandmonths_since_last_birthday, throughutils.dates.birth_day, and mirrors Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday #2279's 16th-birthday fact.is_before_universal_credit_qualifying_young_person_terminal_datewas an input withdefault_value = False. It now has a formula that returns the date fact. A dataset or situation can still set it.meets_qualifying_young_person_terminal_date_condition_for_pension_credit(under 19, or the date fact), required by the education route inis_qualifying_young_person_for_pension_credit, with a new parameterterminal_date_age_limit(19).The annual approximation
The period ends on a fixed 1 September that an annual period cannot split. As in #2279, a year counts if any of it falls in the period, which is the same as the year starting before that 1 September.
So the default matches what the microdata and Pension Credit did already. Set either variable to false for a 19-year-old whose 1 September has passed.
Setting the date for one person
The review of this PR found that setting the date fact for one person gave everyone else in the situation
false: an input set for some people fills the others with the variable's default and skips its formula for them. A second 19-year-old student then silently lost both statuses.The fact's formula gives true for everyone under 20 on 6 October, which is everyone the age limit lets be a QYP. So each of the three variables now defaults to true. Setting one for some people leaves everyone else under 20 where the formula puts them. For someone older the default is true where the formula says false, but every reader of these variables is behind the QYP age limit, which excludes them first. Property test: the formula is true for every age 0 to 19 and every month since the last birthday, in three years, and each variable's default is true; and in a household of two 19-year-old students with each variable set false for the first only, the second stays a QYP for both programmes. YAML Case 12 pins the same household.
Two YAML cases changed with it. Case 3's parent, not set, now shows the default (true) for the Universal Credit variable; nothing reads it for a 45-year-old. And
child_qualifying_young_person.yaml's "aged 19 requires being before terminal date" case left its after-the-date student unset and relied on the old false default; it now sets that input to false explicitly. #2279 documents the same partial-input behaviour for its 16th-birthday fact, whose formula is not true for everyone it could default for.Invariants
Tests
test_qualifying_young_person_terminal_date_properties.py:qualifying_young_person_terminal_date.yaml, twelve cases hand-computed (2026-27):Mutation checks, each applied alone in a copy of the branch (YAML cases and property tests that fail):
>and not>=The 1 September mutation was first caught by the property test alone; Case 11 was added to pin it in YAML.
Run locally at b5cbd2e:
policyengine-core teston the 5 YAML files the branch changes againstmain(Count 16-year-olds outside education as UC and Pension Credit qualifying young persons until the 1 September after their 16th birthday #2279's included): 49 passed. At 6f6e7de, the five YAML files that set these variables: 53 passed.pyteston the 4 Python test files it changes againstmain: 34 passed.policyengine-core teston the 198 YAML files that mention these programmes, 2,000 passed;pyteston the 76 Python test files that mention them is still running and will be added here.ruff format --checkandruff checkonpolicyengine_uk: clean.Round 2 mutations at b5cbd2e (
mutation/prD_r2.txt): each default put back to false fails the two new property tests; the date fact's also fails YAML Cases 3 and 12.The full suites run in CI.
Microsimulation impact
Real runs on Enhanced FRS 2024-25 (uk-data 1.58.0, sha256
5ba399ea5ab9a6c7), one run each for 2025-26 to 2030-31, exact comparison of every record. Base:maine01f2f5 with #2279 (9d2fadb) merged (local merge 006c0e813). Branch: b5cbd2e. Outputs:impact/prD_r2/.No record changes in any year, in any of the Universal Credit, Pension Credit, Housing Benefit, benefit cap, net income or poverty outputs. The reason is the data: policyengine-uk-data supplies
is_before_universal_credit_qualifying_young_person_terminal_date, true for the 102 records aged 19 in qualifying education in 2026-27, and a supplied input takes precedence over the new formula (impact/prD_r1/context.txt, on the earlier base). The new date fact is true for all 780 records aged 19 that year, so the Pension Credit condition removes no one either. The PR changes household calculations (the table at the top) and any dataset that does not supply the input.Reproducer: policyengine_uk/tests/test_qualifying_young_person_terminal_date_properties.py::test_universal_credit_and_pension_credit_agree_on_every_combination
axiom: UC reg 5(1)(b) is encoded at
uk:regulations/uksi/2013/376/5#continued_education_or_training_qualifying_condition, but no companion case sets the 19th-birthday input false; the cases are queued on TheAxiomFoundation/rulespec-uk#355 (addendum; test-only, waiting on TheAxiomFoundation/rulespec-uk#446, which has the file open). SPC reg 4A(1)(b) TheAxiomFoundation/rulespec-uk#360 queued (addendum; reg 4A's row is in an unreleased ingest and a billed encoder run needs approval)🤖 Generated with Claude Code