Skip to content

Apply Housing Benefit accommodation disregards from their start date - #2234

Merged
juaristi22 merged 14 commits into
mainfrom
fix-hb-accommodation-dates
Oct 31, 2026
Merged

juaristi22 merged 14 commits into
mainfrom
fix-hb-accommodation-dates

Conversation

@anth-volk

@anth-volk anth-volk commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #2233
Fixes #2747

This PR changes 18 files and adds no model inputs. The fiscal-year loader samples parameters on 30 April, before the accommodation earnings disregard starts on 5 October 2026. The PR keeps the seven monetary parameters' actual dates and uses fiscal_year_segments to calculate a complete Housing Benefit award for each date segment, then combines those awards by elapsed fiscal-year days.

Calculation and scope

Each segment floors net earnings at zero, reads the shared housing_benefit_applied_standard_earnings_disregard, and caps its full accommodation amount against remaining earnings. The childcare helper uses that full amount and adds nonnegative WTC/CTC payments to residual earnings. The calculation tests additional-disregard eligibility before applying the income floor, applicable-amount threshold, 65% taper and zero-award floor. It weights completed awards after these steps.

Calendar arithmetic gives 182 days before commencement and 183 days afterwards in 2026–27. For annual earnings of £1,000, a £260 standard disregard, a £500 applicable amount and £10,400 eligible rent, the complete annual-equivalent awards equal £10,244 before commencement and £10,400 afterwards. Their day-weighted mean equals (10,244 × 182 + 10,400 × 183) / 365 = £10,322.21.

The calculation reads reformed schedules and clones the seven monetary leaves for its temporary schedule. The parameter-integrity test confirms that their values and parent links remain intact. The override checks treat a value stored as an input for the year, supplied or a scenario's own (set_model_value), as the annual amount, and recalculate the segments over a cached formula result. Example tests cover dated and fiscal-year reforms, supplied-period attribution, later set_input, replacement formulas, neutralization, and populations taken from another simulation.

Annual earnings, rent, ages and household circumstances stay constant across segments. Other parameters keep the existing annual convention. Entitlement recalculates segment income and deductions when needed; pension-age and passported cases keep their annual income treatment. The PR changes no accommodation eligibility, working-age closure, rent valuation, take-up or dataset contents.

Parameter metadata and formula references cite GB commencement, SI 2026/753 regulation 1, GB amounts and earnings-test amendment, SI 2026/978 regulation 2, NI commencement and amounts, SR 2026/157, the GB award taper, childcare earnings limits, and their NI counterparts.

Properties

test_housing_benefit_accommodation_dates_properties.py checks:

  • D1: in a year with no change, one segment gives the unsegmented award;
  • D2: the annual award equals an independent oracle's day-weighted mean of the segment awards, and lies between them. The oracle calculates the standard and permitted-work disregards, childcare, additional-disregard eligibility and awards directly, without calling production helpers, and derives the weights from calendar dates;
  • D3: the award lies between zero and eligible rent;
  • D4: with the additional disregard disabled, raising accommodation never lowers the award;
  • D5: the production segments for 2026–27 have 182 and 183 days.

D4 tests monotonicity without the additional disregard. The statutory earnings threshold can break monotonicity when that disregard applies. GB Schedule 4 paragraph 17 gives the whole £17.10 weekly additional disregard only where net earnings cover the standard disregard, accommodation disregard, deductible childcare and additional disregard together. SI 2026/978 regulation 2 adds the accommodation disregard to that threshold; NI Schedule 5 paragraph 17 and SR 2026/157 regulation 2 make the same change. So a penny of accommodation can remove the whole disregard:

  • with annual net earnings of £1,149.20, a £260 standard disregard, no childcare or other income, a £0 applicable amount and £10,400 rent, raising the later segment's annual accommodation amount from £0 to £0.01 removes the £889.20 additional disregard, and the 2026–27 award falls from £10,400 to £10,110.22;
  • test_d4_a_penny_of_accommodation_can_switch_off_the_additional_disregard pins that case, and checks the later segment's award (£9,822.03) against the oracle and the 182/183-day combination;
  • main shows the same cliff in 2027–28, with a flat accommodation parameter and no segments: the award falls from £10,400 to £9,822.03. This PR keeps that rule.

An earlier commit recorded the cliff as a strict xfail. CI's Test / Result check fails any xfail that has no allowlist entry, and the allowlist is for known bugs, so bda6163b8 replaces it with the passing regression.

Validation

The branch merges main after #2229 and keeps #2211's ordinary-income helper, #2225's disabled-worker correction, #2219's permitted-work and savings-credit paths, and #2229's shared applied standard and residual childcare limit. Each segment uses those shared helpers before the calculation weights the completed award.

An independent GPT-6.1 Sol review at 218c03bb7 checked the merge resolution against both parents, every call into #2229's helpers, the law behind D4, and main's behaviour. It found one P2, the xfail, which bda6163b8 fixes. Its runs:

  • Housing Benefit YAML: 44 files, 584 cases passed, one file at a time.
  • Accommodation date examples: 17 passed. Earnings-disregard properties: 10 passed, including working-age and pension-age agreement.
  • Mutations: the review ran each in a scratch copy under a 2 GB RSS cap and checked with SHA-256 that it restored every file byte for byte. The completed-award example caught pre-weighted accommodation and swapped day counts; D5 caught 30 April sampling; D2 caught a segment that dropped permitted-work replacement.
  • Ruff format and check passed on all five PR Python files.

At bda6163b8, the UK hub ran:

  • Accommodation properties: 6 passed, with xfail strict and no expected failures.
  • Accommodation date examples: 17 passed. Earnings-disregard properties: 10 passed.
  • A mutation that leaves the accommodation disregard out of the additional-disregard threshold, the rule before SI 2026/978: the new regression fails, with £10,400 against £10,110.22.
  • Ruff format and check, and git diff --check, passed.

A second Sol review, of the change from 218c03bb7 to bda6163b8, approved it with nits in this body, which the body now applies (REVIEW-delta.md in the UK hub's records).

At b488ed1cc the branch merges main again (2d437eb). Main's new test_every_walk_up_to_an_ancestor_simulation_is_reviewed lists each function that walks parent_branch, and CI failed it on this PR's hb_uses_annual_override, which walked the chain itself to collect branch names. b8e40c3a1 made it call main's reviewed _visible_branch_names instead, which returns the same set: this branch, the branches it came from, and default.

A third Sol review, at b488ed1cc, confirmed that set equality on five branch shapes, the clean merge, and that a household subset reads only its own households. It found one P2: Simulation.build_from_populations keeps the replaced holders' input records (policyengine-core#605). Once the applicable-income formula refilled the period, the check read the stale record as a supplied value and skipped the segments, so the award came to £10,400 instead of the fresh simulation's £10,322.21. 5dc13b793 fixed that case:

  • the five variables the check asks about join SUPPLIED_INPUT_VARIABLES, so Simulation.calculate forgets a record that a rebuild left without its value before the formula refills the period;
  • the check calls a new shared helper, stored_as_input in utils/supplied_inputs.py, which counts a value stored as an input for the period, supplied or the model's own;
  • test_rebuilt_populations_leave_no_supplied_income_behind pins the rebuild case at £10,322.21, and test_scenario_adjusted_income_keeps_its_value pins a set_model_value adjustment of £2,000 at the annual taper's £9,425.

The review proposed supplied_input for the check. That helper skips the model's own values, so a scenario that set the income to £2,000 would read £2,000 while the 2026–27 award ignored it and stayed at £10,322.21. stored_as_input keeps them together.

A fourth Sol review, at 5dc13b793, agreed that the check should count the model's own values and passed 42 rebuild-order, branch and later-input checks. It found one P2: stored_as_input still read this simulation's record, so populations whose donor had already calculated the applicable income, or either disregard, still awarded £10,400, and a donor's supplied income of £3,000 counted as calculated (£10,322.21 instead of the annual taper's £8,775). d5c96d95a fixes it:

  • stored_as_input asks the holder (Holder._is_input), which answers from the input record of the simulation bound to it. build_from_populations points the populations at the new simulation but leaves each holder bound to the simulation that made it, so moved populations kept their own provenance;
  • test_rebuilt_populations_keep_a_calculated_value_calculated pins the three precomputed-donor cases at £10,322.21, and test_rebuilt_populations_keep_a_supplied_income pins the supplied donor income at £8,775.

At 5dc13b793 the UK hub ran:

  • accommodation date examples: 19 passed; accommodation properties: 6 passed; earnings-disregard properties: 10 passed; the whole household-subset file, ancestor-walk guard included: 9 passed; the aggregate-impact guard: 39 passed; input export: 25 passed; all with xfail strict;
  • three mutations in a scratch copy under a 2 GB RSS cap: supplied_input in place of stored_as_input fails the scenario test; skipping the record cleanup in Simulation.calculate fails the rebuild test; dropping the record condition fails four date tests;
  • Ruff format and check on the three changed files, and git diff --check: passed.

None of the UK hub's local runs used a survey dataset. The household-subset file's one Microsimulation test builds a synthetic 60-person dataset in memory.

At d5c96d95a the UK hub ran:

  • the four new tests: all four fail at 5dc13b793 and pass here;
  • accommodation date examples: 23 passed; accommodation properties: 6 passed; earnings-disregard properties: 10 passed; household subset: 9 passed; the registry test in test_ni_class_4_profits_properties.py: 1 passed; input export: 25 passed; the aggregate-impact files: childcare provenance 42, compare 133, data mode 4, dataset 13, guard 39, measure 44, partition 11 and reforms 20 passed; all with xfail strict;
  • a probe of the rebuild: the population points at the new simulation and the holder at the donor, whose record has no income input, so the check returns false and the award is £10,322.21;
  • Ruff format and check on the three changed files, and git diff --check: passed.

It did not run test_aggregate_impact_certified.py: with a Hugging Face token in the hub's environment, its fixture builds a Microsimulation on the survey dataset, and the 4 GB RSS cap stopped it after 36 s, before any test ran. CI ran all 9 of its tests on the certified dataset at this head, and they passed (run 40857494571, Python shards 1 and 3–6).

A fifth Sol review, at d5c96d95a, passed 49 lifecycle checks and 964 tests, and found one P2: a clone or branch of the rebuilt simulation lost the other simulation's provenance. Core's Simulation.clone copies the simulation's own input record, and Holder.clone binds each copied holder to the clone, so the clone read the record of the populations the rebuild had replaced. A supplied income of £3,000 in the other simulation gave £8,775 in the rebuilt simulation but £10,322.21 in its clone or branch, and the aggregate guard's snapshot_inputs discarded that income before capture (also £10,322.21). 6e090ed00 moved the records with the holders. Simulation.build_from_populations called a new helper, adopt_population_records, which:

  • bound each holder from another simulation to this one. Core's Holder._set records an input in its own simulation's record, so before this an input set on the rebuilt simulation went into the other simulation's record, and supplied_input missed it;
  • kept that simulation's records of the inputs and model values (set_model_value) the holder still stores;
  • dropped the other values the holder stores, which that simulation calculated or carried over, perhaps before an input changed or under another tax-benefit system, so this simulation calculates them again;
  • forgot this simulation's records of values the rebuild removed (policyengine-core#605).

build_from_populations also empties the fast cache (it still does), as a rebuild from a situation already does: formulas read it, so it served values calculated for the replaced populations.

Clones and branches copy the simulation's records, so they now read the new populations' provenance, and so does a household subset, which starts from a branch. UK builds a reform's baseline when it constructs the simulation, so a later rebuild does not reach it. supplied_input, supplied_input_periods and supplied_by read the same record, so the change also fixes #2747. policyengine-core#636 reports the holder binding upstream, with core-only reproductions; core's own Simulation class keeps both gaps until core fixes it.

Invariant R1: for the states the property generates (each override variable absent, supplied, set by a scenario or calculated for 2026, on the default branch, in either simulation, with every value in memory), after build_from_populations(other.populations) a simulation, its clone and its branch give the same stored_as_input and supplied_input answers for the three override variables, and the same award, as a fresh copy of the other simulation. test_rebuilt_populations_answer_as_the_simulation_that_made_them checks R1 with Hypothesis over those states. It does not cover other branches or values stored on disk; example tests in test_input_records.py cover the disk and cache cases. Its first runs found two more gaps, both present at d5c96d95a, which two @examples now pin:

  • the other simulation calculated the income before a scenario set its disregard, and the rebuilt simulation reused that stale income: £10,244 against the fresh £10,075. Dropping calculated values at the rebuild fixes it;
  • this simulation calculated its own income before taking supplied income of £2,589, and its formulas read the old income from the fast cache: £10,400 against the fresh £9,042.15. Emptying the fast cache fixes it.

New example tests:

  • test_copies_of_rebuilt_populations_keep_a_supplied_income, …_keep_a_calculated_income_calculated and …_keep_a_scenario_adjusted_income check the award and supplied_input in the rebuilt simulation, a clone, a branch and an aggregate-guard snapshot;
  • test_rebuilt_populations_replace_values_calculated_before checks the fast-cache case;
  • in test_input_records.py: the export of the new populations' inputs, an input set after the rebuild (in the simulation and a clone), and Supplied-input helpers misread provenance after build_from_populations with another simulation's populations #2747's two adjusted-net-income cases.

At d5c96d95a, 16 of the 17 new example tests fail; only the scenario-adjusted income in the rebuilt simulation itself passes there. Both of the property's pinned examples fail there too.

At 6e090ed00 the UK hub ran:

  • accommodation date examples, the property included: 37 passed; input records: 5 passed; accommodation properties: 6 passed; earnings-disregard properties: 10 passed; household subset: 9 passed; input export: 25 passed; scenario and direct modifier inputs: 40 and 21 passed; reform inputs through a reset: 17 passed; measurement branches: 5 passed; data-source provenance: 20 passed; the other files that call the supplied-input helpers, benefit-cap properties 46, capital-gains projection 115, childcare birthday terms 53, current education order 11, Gift Aid 12 and Class 4 NI profits properties 25 passed; the program registry: 106 passed; the aggregate-impact files other than test_aggregate_impact_certified.py: childcare provenance 42, compare 133, data mode 4, dataset 13, guard 39, measure 44, partition 11 and reforms 20 passed; one file at a time, with xfail strict and no survey dataset;
  • the property with random examples (derandomize=False) under three seeds, 20 examples each: 60 passed, peak 1.3 GB per run;
  • five mutations, each in its own scratch copy under a 2 GB RSS cap, with SHA-256 manifests showing the worktree unchanged, each failing at least one new test: leaving holders bound to the other simulation fails test_rebuilt_populations_record_an_input_set_later; skipping _drop_computed fails the property's first pinned example; dropping the other simulation's model values fails the four scenario-adjusted cases; keeping the fast cache fails test_rebuilt_populations_replace_values_calculated_before; skipping adopt_population_records fails 15 of the 17 new example tests;
  • a household subset of rebuilt populations whose other simulation supplied £3,000 for one household: supplied_input returns 3,000 and the award is £8,775 (at d5c96d95a, the same award, with supplied_input None);
  • Ruff 0.17.2 (the version uv.lock pins) format and check on the five changed Python files, the F811/F821 check on the package, and git diff --check: passed.

A sixth Sol review, at 6e090ed00, found three P2s, all from the two simulations sharing holders after the rebinding:

  • a holder from a simulation that stores values on disk kept that setting. The rebuilt simulation had no memory configuration, so the next input it stored crashed in core's Holder._set;
  • the record filter asked only the first storage tier. A value cached in memory hid an input stored on disk, so the rebuilt simulation lost the input: £10,322.21 instead of £8,775 with a supplied £3,000;
  • an input set on the other simulation after the rebuild went into the rebuilt simulation's record. Neither simulation noted the change, so both kept a stale weekly_hours of 10 instead of 20.

371c22093 copies instead of sharing. Simulation.build_from_populations now calls copy_other_populations in utils/supplied_inputs.py, which reads what the other simulation recorded of each holder it made (the inputs that survive in either storage tier, and the model values still stored), then copies those populations and holders with core's Population.clone, GroupPopulation.clone and Holder.clone, as Simulation.clone does. keep_population_records then keeps those records and this simulation's own records for the holders it made, drops the other values the copies hold, and gives each copy the storage settings this simulation gives its own holders. The other simulation keeps its own populations, values and records, so an input either simulation stores later with set_input does not reach the other (but see the in-place caveat at 8cac7e91f below). Populations no simulation has taken yet, the only ones the model's own builders pass, go in as before. The rebuild no longer rebinds anything, so the holder-binding gap in policyengine-core#636 no longer reaches UK simulations.

New tests:

  • in test_input_records.py: test_rebuilt_populations_take_inputs_from_a_simulation_storing_on_disk, …_store_values_where_this_simulation_does, …_keep_an_input_a_cache_hides and …_leave_their_simulation_as_it_was;
  • in the Housing Benefit tests: test_simulations_rebuilt_from_the_same_populations_answer_alone, where two simulations rebuild from the same populations, taken from a simulation that supplied an income of £3,000. An income of £4,000 set on the first afterwards gives it £8,125 and leaves the second and the original simulation at £8,775.

At 371c22093 the UK hub ran:

  • the five new tests: all five fail at 6e090ed00 and pass here;
  • the review's reproductions: the disk case stores and returns 700; the tier case exports 100 for both simulations; the shared-holder case leaves the other simulation at 20 and the rebuilt one at 10, and a later input of 5 in the rebuilt one leaves the other at 20;
  • the two-tier supplied income of £3,000: £8,775 in the rebuilt simulation, its clone and its branch, and the rebuilt simulation still records the income as supplied after an aggregate-guard snapshot;
  • the same 26 files as at 6e090ed00, one at a time, with xfail strict and no survey dataset: accommodation date examples, the property included, 38 passed; input records 9; accommodation properties 6; earnings-disregard properties 10; household subset 9; input export 25; scenario and direct modifier inputs 40 and 21; reform inputs through a reset 17; measurement branches 5; data-source provenance 20; benefit-cap properties 46; capital-gains projection 115; childcare birthday terms 53; current education order 11; Gift Aid 12; Class 4 NI profits properties 25; the program registry 106; the aggregate-impact files other than test_aggregate_impact_certified.py: childcare provenance 42, compare 133, data mode 4, dataset 13, guard 39, measure 44, partition 11 and reforms 20 passed (874 tests, all passed);
  • the Housing Benefit YAML directory: 584 cases passed;
  • ten mutations, each in its own scratch copy under a 2 GB RSS cap, with SHA-256 manifests showing the worktree unchanged. Copying nothing fails …_answer_alone and …_leave_their_simulation_as_it_was; asking only the first storage tier fails …_keep_an_input_a_cache_hides; skipping the storage settings fails both disk tests, and skipping only the new disk storage fails …_store_values_where_this_simulation_does; skipping _drop_computed fails the property's first pinned example; dropping the other simulation's model values fails the four scenario-adjusted cases; keeping the fast cache fails test_rebuilt_populations_replace_values_calculated_before; skipping keep_population_records fails 20 tests. Copying the arrays at once instead of sharing them (share_arrays=False) passes every test, as expected. Keeping this simulation's records of the inputs the rebuild replaced passed every test too, which 3cfab0639 fixes (below);
  • Ruff 0.17.2 format and check on the changed Python files, the F811/F821 check on the package, and git diff --check: passed.

3cfab0639 adds test_rebuilt_populations_forget_the_inputs_they_replace. This simulation supplied an adjusted net income that the rebuild replaces; a value of 123 cached in that period afterwards must not read back as the supplied input. supplied_input checks the record and the stored value but not storage's input marks, so it returned 123 on main (daec84d5b, policyengine-core#605). The test passes at 6e090ed00 and 371c22093, and fails the mutation above.

A seventh Sol review, at 3cfab0639, reran its three counterexamples and found each fixed. It passed 532 Python tests, 584 YAML cases and 45 more random R1 examples, and found one P2. _records_of compared each model value (set_model_value) with what storage returns first, which is memory. So when a value cached in memory hid a model value stored on disk, the copy kept the disk value as an input but dropped the record that the model set it, and once _drop_computed removed the cache, the rebuilt simulation read the model's value as supplied. In the review's example, the other simulation's model set an adjusted net income of £50,000 over employment income of £30,000: there, supplied_input returns None and net income is £30,000. In the rebuilt simulation, its clone and its branch, supplied_input returned £50,000 and net income rose to £50,000.

8cac7e91f compares each model value with the stored input instead. It calls core's get_stored_input, which returns the value that storage marks as an input for the period and branch, memory first and then disk. The review found it in core 3.33.0, the oldest version pyproject.toml allows. The commit also states the limit of the copy in copy_other_populations's docstring: the copy shares each in-memory array with the original until it first reads it (core's share_arrays), so writing in place into the original's array before then changes what the copy reads. An input set afterwards with set_input does not reach the other simulation (test_rebuilt_populations_leave_their_simulation_as_it_was).

At 8cac7e91f the UK hub ran:

  • the new test, test_rebuilt_populations_keep_a_model_value_a_cache_hides: it fails at 3cfab0639 (supplied_input returns £50,000) and passes here, in the rebuilt simulation, its clone and its branch;
  • the review's reproduction: the rebuilt simulation, its clone and its branch give supplied_input None and net income £30,000, and an aggregate-guard snapshot records the value as the model's;
  • 12 of the 26 files from the 371c22093 batch, the ones that build, rebuild or copy simulations, one at a time with xfail strict and no survey dataset: input records 11, accommodation date examples, the property included, 38, accommodation properties 6, household subset 9, input export 25, scenario and direct modifier inputs 40 and 21, reform inputs through a reset 17, measurement branches 5, data-source provenance 20, and the aggregate-impact guard 39 and childcare provenance 42 passed (273 tests);
  • eleven mutations, each in its own scratch copy under a 2 GB RSS cap, with the worktree unchanged. Each of the ten expected to fail does: the nine from 371c22093 and 3cfab0639, and model_value_first_tier, which compares model values with what storage returns first again and fails the new test. Copying the arrays at once (share_arrays=False) passes every test, as expected;
  • Ruff 0.17.2 format and check on the changed Python files, the F811/F821 check on the package, and git diff --check: passed.

An eighth Sol review, at 8cac7e91f, approved it with two nits, both now applied. It reran the seventh review's reproduction (it exits 1 at 3cfab0639 and 0 here) and checked core 3.33.0's get_stored_input: memory first, then disk, on the exact branch, and None when neither tier holds an input. It built a model value and a different input in the two tiers, both ways round, a model value on one branch with a supplied input on another, a later set_input over a model value, and a model value already dropped, and found the expected record in each. It passed 533 Python tests in 20 files, 619 Housing Benefit YAML cases in 51 files and 40 more random R1 examples. Its nits:

  • both changelog fragments started with a list marker, which towncrier doubles; dd79d3fb5 removes them;
  • one sentence in this body still said neither simulation's later changes reach the other; it now names set_input and points to the in-place caveat.

Aggregate impact: the run for 3cfab0639 (comment, run 40944196229, merge 0a10904eb2) shows Housing Benefit spending £2.23 lower in 2026–27, out of about £8.9 billion, and equal to main in every other year from 2025–26 to 2031–32. Government spending and household net income move by the same £2. The runs for d5c96d95a and 371c22093 gave the same Housing Benefit figures in every year. The run for dd79d3fb5 (run 40995789501, merge f108389ffc) shows Housing Benefit £2.23 lower in 2026–27 (£8,886,896,403.24 against main's £8,886,896,405.47) and equal to main in every other year. No metric moves outside 2026–27, it broke no check and it flagged no move.

CI at 3cfab0639: all 19 checks passed; the YAML job took 33 minutes 20 seconds. At 371c22093 it passed all 5,249 cases and then sat after its summary until GitHub cancelled it at the job's 45-minute limit, as it did on other branches that day. CI at 8cac7e91f passed the YAML job in 43 minutes 42 seconds. At dd79d3fb5 all 19 checks passed.

Reproducer: policyengine_uk/tests/test_housing_benefit_accommodation_dates.py::test_first_year_combines_completed_awards

axiom: TheAxiomFoundation/rulespec-uk#375 queued
The specified-accommodation disregard dates extension to rulespec-uk#375, with the law, required outputs and companion tests: TheAxiomFoundation/rulespec-uk#375 (comment) (UK hub, 24 Oct).

🤖 Generated with Claude Code

@MaxGhenis

Copy link
Copy Markdown
Collaborator

Parent-gated build hand-off. Head remains de48e391989960e56c8454ec5aea9693e299dec5; no branch changes, push or retarget. Immediate parent #2229 is still OPEN/unmerged at c5e5f88471395f09d1ecd0770cbaee2c938c7807 (checked 22 October). Root #2211 has a local reconciliation awaiting the required disk gate: #2211 (comment)

Tests for this child: 0 executed in this job; no claim is made about the current head's CI. Preserve Anthony's corrected disabled-worker earnings-disregard regression (£1,929.20) when updating the base. Draft status unchanged; no merge/readiness/closure action.

Once #2229 lands, the pending commands are:

git fetch origin main fix-hb-accommodation-dates
git switch fix-hb-accommodation-dates  # create tracking branch if absent
git merge --no-ff origin/main
# Resolve, run focused foreground tests with -n 2 after load1 <=110 and
# os.statvfs available disk >=55 GB decimal; commit fixes and push once.
gh api -X PATCH repos/PolicyEngine/policyengine-uk/pulls/2234 -f base=main

Open items: parent landing; merge/test/push/body refresh as needed; retarget; hub independent review.

Corrected the published body to describe existing childcare_expenses fixtures (no new qualifying-charge input) and main merges instead of rebases. Historical author validation (17 Python tests / 49 YAML tests) is now explicitly labelled as historical, not rerun here. Preserve #2131's shared entitlement changes when resolving the future merge. No extra simulation.py edits have been made by this job.

@juaristi22 juaristi22 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.

Reviewed at de48e391989960e56c8454ec5aea9693e299dec5. No actionable findings from independent policy/source and code reviews of the date-integration change.

GB and NI sources establish commencement on 5 October 2026; the intervening GB amendment’s 2 October start does not move the allowance forward. Computing completed awards within each date segment preserves the taper and floors before day-weighting.

Validation: 137 focused tests passed. An actual-income NI case produces £10214.80, matching 182/365 of the zero-allowance control plus 183/365 of the full-allowance control, and differs from tapering averaged income. Supplied overrides and parameter-parent links were also checked. The annual constant-circumstance approximation and documented whole-stack reconciliation prerequisites remain.

@MaxGhenis

Copy link
Copy Markdown
Collaborator

#2229 merged into main at 00:05Z on 29 October (c66d02b47), so this PR now conflicts with main in two files: housing_benefit_applicable_income_disregard.py and housing_benefit_specified_or_temporary_accommodation_disregard.py.

The UK hub is restacking it the way #2219 and #2229 went:

Anthony, the commits will appear on fix-hb-accommodation-dates, followed by an independent review before the hub lands it. Head before the restack: de48e3919.

MaxGhenis and others added 3 commits October 29, 2026 00:27
…xfail

CI's Test / Result job fails any xfail without an allowlist entry, and the
allowlist is for known bugs. The D4 counterexample is the law, not a bug:
HB Regs 2006 Sch. 4 para. 17(1) and (3), as SI 2026/978 reg. 2 amends
them, apply the additional disregard only where net earnings reach the
standard, accommodation, childcare and additional amounts together, so a
penny of accommodation can remove all £889.20 of it. Main shows the same
cliff in 2027-28, with no date segments.

The new test pins the case: £10,400 with no accommodation, £10,110.22 with
a penny, the later segment's award from the independent oracle, and the
182/183-day combination. The restricted monotonicity property stays.

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

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Aggregate impact on certified data: pass

Base 64cf5828ee against head f108389ffc, both on enhanced_frs_2024_25.h5 (policyengine-uk-data 1.58.1, sha256 056e80fd8e91), fiscal years 2025-26 to 2031-32. Each year is a full microsimulation run on each side.

Checks: 0 broken by this change, 1 failing on both sides, 0 fixed. Moves flagged for review: 0 (thresholds in report_spec.yaml; a flag does not fail the job).

Workflow run and full report

Notes

  • In the years a figure from fewer than 10 records is suppressed (spending.disabled_students_allowance), every total containing it, and the decile incomes, are rebuilt from their other components, so no published total includes it. Those totals are marked †. A change is withheld where the two sides leave out different figures and can't be lined up.

Moves flagged for review

None.

Spending by benefit and service

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Housing Benefit 0 -£2 0 0 0 0 0
Disabled Students' Allowance suppressed suppressed suppressed suppressed suppressed suppressed suppressed

Caseloads

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Disabled Students' Allowance suppressed suppressed suppressed suppressed suppressed suppressed suppressed

Government balance, each component counted once

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Government spending 0 † -£2 † 0 † 0 † 0 † 0 † 0 †
Government balance, each receipt once 0 † +£2 † 0 † 0 † 0 † 0 † 0 †

Model totals

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
gov_spending 0 † -£2 † 0 † 0 † 0 † 0 † 0 †
household_benefits 0 † -£2 † 0 † 0 † 0 † 0 † 0 †
gov_balance 0 † +£2 † 0 † 0 † 0 † 0 † 0 †

Household income totals

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Household net income 0 † -£2 † 0 † 0 † 0 † 0 † 0 †
Household net income (HBAI definition) 0 -£1 0 0 0 0 0

Mean household net income in each side's own deciles

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Decile 2: mean household_net_income per household 0 † -£0.00 † 0 † 0 † 0 † 0 † 0 †
Decile 3: mean household_net_income per household 0 † -£0.00 † 0 † 0 † 0 † 0 † 0 †
Decile 4: mean household_net_income per household 0 † -£0.00 † 0 † 0 † 0 † 0 † 0 †
Decile 5: mean household_net_income per household 0 † -£0.00 † 0 † 0 † 0 † 0 † 0 †

Standard reform costings (change in gov_balance)

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Raise basic rate by 1pp 0 +£1 0 0 0 0 0
Raise personal allowance by ~800GBP/year 0 -£0 0 0 0 0 0
Raise Class 1 main employee NICs rate to 10% 0 +£0 0 0 0 0 0
Raise VAT standard rate by 2pp 0 +£1 0 0 0 0 0
Raise additional rate by 3pp 0 0 0 0 0 suppressed suppressed
Raise the Universal Credit standard allowance by 10% 0 +£0 0 0 0 0 0
Raise the State Pension by 10% 0 +£3 0 0 0 0 0

Standard reforms: change in each reform's target

Measure 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Raise additional rate by 3pp: change in income_tax 0 0 0 0 0 suppressed suppressed

Household net income by decile

No household's net income changes in any year.

No change in any year: receipts by tax head; population; poverty rates; people in poverty; inequality.

Checks failing on both base and head (1)
  • VAT excluding private school fees is never negative (sign:vat): 2025-26: 76 units below zero, a weighted £-53,888,684 in total; 2026-27: 76 units below zero, a weighted £-55,774,885 in total; 2027-28: 76 units below zero, a weighted £-57,687,092 in total
Counted-once check

Each of 91 components was raised by £1 per unit in 2026-27, and every head of its totals was read household by household: its own head must rise by exactly £1, no other tax head may move, no other benefit may rise, and the model's own total must move by what its heads moved. A head that breaks a rule is measured again twice: with every other head held at its baseline value, and with every other head held at its value after the first £1 while the component rises again. It fails where either still shows the response. 0 failed; 0 were skipped.

  • widowed_parents_allowance: pension_credit responds only through other heads in fewer than 10 households (other heads that moved there: carers_allowance; gov_spending; not a failure)
  • widowed_parents_allowance: pension_credit responds only through other heads in fewer than 10 households (other heads that moved there: carers_allowance; household_benefits; not a failure)
Provenance
Side Commit Runtime Environment hash Partition hash Time
base main 64cf5828ee uk 2.165.0 (files 8c1197397e80), core 3.33.12, py 3.13.18 aarch64 26f24baae8a9 22e0ebaf0971 1926.56s
head #2234 merged into main f108389ffc uk 2.165.0 (files f1c9c257733c), core 3.33.12, py 3.13.18 aarch64 26f24baae8a9 22e0ebaf0971 1932.15s

Dataset: hf://policyengine/policyengine-uk-data-private/enhanced_frs_2024_25.h5@2526edbbb687a3b54dbb11272e5389e4b62d2ac2, sha256 056e80fd8e9151d78f432a25cde863340baee81304c6216a37fb575ce8563784 (verified on both sides), policyengine-uk-data 1.58.1.

Receipts by tax head: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Final charge on cash interest in non-cash ISAs £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Lifetime ISA withdrawal charge £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Income tax £294.46bn £315.23bn £334.00bn £349.45bn £365.10bn £383.16bn £397.71bn
National Insurance (employee and self-employed) £51.70bn £54.14bn £56.13bn £57.73bn £59.76bn £61.64bn £63.51bn
National Insurance (employer) £148.71bn £155.37bn £160.88bn £165.09bn £172.06bn £177.36bn £182.57bn
Employer NI change borne by capital £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Employer NI change borne by consumers £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Capital gains tax £12.60bn £13.15bn £13.60bn £14.11bn £14.67bn £15.24bn £15.80bn
Student loan repayments £7.82bn £8.13bn £8.39bn £8.58bn £8.79bn £9.04bn £9.29bn
VAT excluding private school fees £249.37bn £256.88bn £266.66bn £272.70bn £278.97bn £285.24bn £291.59bn
Change in VAT from its baseline £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
VAT on private school fees £1.95bn £2.01bn £2.09bn £2.14bn £2.21bn £2.27bn £2.31bn
Change in private school VAT from its baseline £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Fuel duties £24.71bn £24.59bn £26.54bn £26.58bn £26.26bn £25.53bn £24.40bn
Alcohol duties £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Tobacco duties £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Vehicle excise duties £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Betting and gaming duties £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
TV licence fee £4.26bn £4.40bn £4.41bn £4.42bn £4.43bn £4.44bn £4.45bn
Council tax £49.21bn £52.16bn £54.89bn £57.95bn £61.16bn £64.47bn £67.91bn
High value council tax surcharge £0.00bn £0.00bn £0.00bn £0.48bn £0.49bn £0.50bn £0.51bn
Northern Ireland domestic rates £0.55bn £0.58bn £0.60bn £0.63bn £0.66bn £0.69bn £0.72bn
Business rates £34.68bn £36.20bn £38.00bn £39.46bn £42.27bn £43.25bn £44.29bn
Stamp duty land tax on the household's own purchases £6.88bn £7.32bn £7.66bn £8.06bn £8.47bn £8.91bn £9.35bn
Stamp duty land tax paid by companies £4.72bn £4.57bn £5.29bn £6.03bn £6.37bn £6.92bn £7.45bn
Land transaction tax (Wales) £0.07bn £0.08bn £0.08bn £0.09bn £0.10bn £0.10bn £0.11bn
Land and buildings transaction tax (Scotland) £0.31bn £0.32bn £0.33bn £0.35bn £0.36bn £0.38bn £0.40bn
Unspecified tax change borne by capital £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Unspecified tax change borne by consumers £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Wealth tax £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Wealth tax excluding the main residence £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Land value tax £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Carbon tax £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Spending by benefit and service: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Lifetime ISA bonus £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
State Pension £121.68bn £125.92bn £126.73bn £129.50bn £132.99bn £136.66bn £140.43bn
Pension Credit £6.66bn £7.03bn £7.30bn £7.51bn £7.65bn £7.84bn £8.03bn
Winter Fuel Payment £1.86bn £1.85bn £1.79bn £1.77bn £1.78bn £1.78bn £1.78bn
Pension Age Winter Heating Payment (Scotland) £0.19bn £0.20bn £0.20bn £0.21bn £0.21bn £0.22bn £0.22bn
Universal Credit £75.48bn £80.52bn £83.04bn £85.11bn £86.31bn £87.90bn £88.58bn
Housing Benefit £8.56bn £8.89bn £9.24bn £9.48bn £9.72bn £9.99bn £10.27bn
Council Tax Reduction £2.07bn £2.20bn £2.31bn £2.45bn £2.58bn £2.72bn £2.86bn
Employment and Support Allowance (income-related) £0.79bn £0.82bn £0.84bn £0.86bn £0.88bn £0.90bn £0.92bn
Employment and Support Allowance (contribution-based) £5.20bn £5.37bn £5.56bn £5.69bn £5.82bn £5.95bn £6.09bn
Jobseeker's Allowance (income-based) £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Jobseeker's Allowance (contribution-based) £0.29bn £0.30bn £0.31bn £0.31bn £0.32bn £0.33bn £0.34bn
Income Support £0.11bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Working Tax Credit £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Child Tax Credit £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Incapacity Benefit £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Personal Independence Payment £26.95bn £28.03bn £29.05bn £30.09bn £30.75bn £31.51bn £32.21bn
Disability Living Allowance £7.49bn £7.79bn £8.07bn £8.36bn £8.55bn £8.76bn £8.95bn
Attendance Allowance £8.68bn £9.03bn £9.36bn £9.69bn £9.90bn £10.15bn £10.38bn
Carer's Allowance £4.48bn £4.66bn £4.83bn £5.00bn £5.11bn £5.23bn £5.35bn
Carer Support Payment (Scotland) £0.31bn £0.32bn £0.33bn £0.34bn £0.35bn £0.36bn £0.37bn
Carer's Allowance Supplement (Scotland) £0.00bn £0.04bn £0.04bn £0.05bn £0.05bn £0.05bn £0.05bn
Severe Disablement Allowance £0.03bn £0.03bn £0.03bn £0.03bn £0.03bn £0.03bn £0.03bn
Industrial Injuries Disablement Benefit £0.50bn £0.52bn £0.54bn £0.55bn £0.56bn £0.58bn £0.59bn
Armed Forces Compensation Scheme £0.61bn £0.63bn £0.65bn £0.66bn £0.68bn £0.69bn £0.71bn
War Widow's or Widower's Pension £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Child Benefit £16.19bn £16.82bn £17.39bn £18.00bn £18.39bn £18.84bn £19.25bn
Scottish Child Payment £0.49bn £0.52bn £0.53bn £0.56bn £0.57bn £0.57bn £0.57bn
Two Child Limit Payment (Scotland, counterfactual) £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Healthy Start vouchers £0.05bn £0.05bn £0.05bn £0.05bn £0.05bn £0.05bn £0.05bn
Maternity Allowance £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Statutory Maternity Pay £3.30bn £3.41bn £3.54bn £3.62bn £3.70bn £3.78bn £3.87bn
Statutory Paternity Pay £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Statutory Sick Pay £1.12bn £1.16bn £1.20bn £1.23bn £1.26bn £1.29bn £1.32bn
Sure Start Maternity Grant £0.01bn £0.01bn £0.01bn £0.01bn £0.01bn £0.01bn £0.01bn
Bereavement Support Payment £0.67bn £0.69bn £0.71bn £0.73bn £0.75bn £0.76bn £0.78bn
Widowed Parent's Allowance £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Tax-Free Childcare £0.55bn £0.56bn £0.58bn £0.59bn £0.59bn £0.59bn £0.57bn
Extended childcare entitlement £2.99bn £3.50bn £3.62bn £3.75bn £3.83bn £3.92bn £4.01bn
Universal childcare entitlement £2.06bn £2.22bn £2.29bn £2.38bn £2.44bn £2.51bn £2.56bn
Targeted childcare entitlement £0.50bn £0.51bn £0.52bn £0.54bn £0.55bn £0.57bn £0.56bn
Childcare Offer for Wales £0.26bn £0.28bn £0.28bn £0.29bn £0.30bn £0.31bn £0.32bn
Care to Learn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Childcare Grant £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Parents' Learning Allowance £0.20bn £0.20bn £0.20bn £0.20bn £0.20bn £0.20bn £0.20bn
Adult Dependants' Grant £0.06bn £0.06bn £0.04bn £0.04bn £0.04bn £0.04bn £0.04bn
Travel Grant £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Disabled Students' Allowance suppressed suppressed suppressed suppressed suppressed suppressed suppressed
16 to 19 Bursary Fund £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
NHS spending £203.89bn £204.30bn £204.56bn £204.91bn £205.34bn £205.85bn £206.35bn
State education spending £0.02bn £0.02bn £0.02bn £0.02bn £0.02bn £0.02bn £0.02bn
Transport subsidy spending £25.05bn £25.10bn £25.14bn £25.18bn £25.23bn £25.30bn £25.36bn
Cost-of-living support payment £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Energy Bills Rebate £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Energy Price Guarantee subsidy £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Basic income £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Disability basic income £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Other public spending budget change £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Caseloads: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Lifetime ISA bonus 0k 0k 0k 0k 0k 0k 0k
State Pension 11,264k 11,145k 10,819k 10,700k 10,723k 10,750k 10,776k
Pension Credit 1,486k 1,480k 1,474k 1,455k 1,453k 1,455k 1,464k
Winter Fuel Payment 7,844k 7,783k 7,473k 7,375k 7,391k 7,409k 7,427k
Pension Age Winter Heating Payment (Scotland) 803k 802k 800k 797k 799k 801k 802k
Universal Credit 6,357k 6,349k 6,350k 6,318k 6,307k 6,299k 6,253k
Housing Benefit 1,249k 1,245k 1,246k 1,229k 1,229k 1,229k 1,232k
Council Tax Reduction 3,176k 3,195k 3,200k 3,215k 3,224k 3,235k 3,242k
Employment and Support Allowance (income-related) 71k 71k 71k 71k 71k 71k 71k
Employment and Support Allowance (contribution-based) 719k 720k 721k 722k 724k 726k 728k
Jobseeker's Allowance (income-based) 0k 0k 0k 0k 0k 0k 0k
Jobseeker's Allowance (contribution-based) 67k 67k 67k 67k 67k 68k 68k
Income Support 25k 0k 0k 0k 0k 0k 0k
Working Tax Credit 0k 0k 0k 0k 0k 0k 0k
Child Tax Credit 0k 0k 0k 0k 0k 0k 0k
Incapacity Benefit 0k 0k 0k 0k 0k 0k 0k
Personal Independence Payment 3,567k 3,575k 3,579k 3,585k 3,593k 3,602k 3,611k
Disability Living Allowance 1,291k 1,294k 1,296k 1,298k 1,300k 1,304k 1,307k
Attendance Allowance 1,695k 1,698k 1,700k 1,703k 1,707k 1,711k 1,715k
Carer's Allowance 1,034k 1,037k 1,038k 1,040k 1,042k 1,044k 1,046k
Carer Support Payment (Scotland) 71k 71k 71k 72k 72k 72k 72k
Carer's Allowance Supplement (Scotland) 0k 71k 71k 72k 72k 72k 72k
Severe Disablement Allowance 4k 4k 4k 4k 4k 4k 4k
Industrial Injuries Disablement Benefit 137k 137k 137k 138k 138k 138k 139k
Armed Forces Compensation Scheme 93k 93k 94k 94k 94k 94k 94k
War Widow's or Widower's Pension 0k 0k 0k 0k 0k 0k 0k
Child Benefit 8,123k 8,120k 8,120k 8,130k 8,145k 8,161k 8,178k
Scottish Child Payment 351k 352k 352k 355k 353k 351k 340k
Two Child Limit Payment (Scotland, counterfactual) 0k 0k 0k 0k 0k 0k 0k
Healthy Start vouchers 200k 200k 200k 201k 201k 201k 202k
Maternity Allowance 0k 0k 0k 0k 0k 0k 0k
Statutory Maternity Pay 333k 333k 334k 334k 335k 336k 337k
Statutory Paternity Pay 0k 0k 0k 0k 0k 0k 0k
Statutory Sick Pay 181k 182k 182k 182k 182k 183k 183k
Sure Start Maternity Grant 26k 26k 26k 26k 26k 26k 26k
Bereavement Support Payment 98k 98k 98k 98k 98k 99k 99k
Widowed Parent's Allowance 0k 0k 0k 0k 0k 0k 0k
Tax-Free Childcare 1,014k 1,025k 1,021k 1,022k 1,008k 1,005k 991k
Extended childcare entitlement 795k 800k 804k 806k 809k 814k 814k
Universal childcare entitlement 1,071k 1,072k 1,074k 1,076k 1,079k 1,089k 1,090k
Targeted childcare entitlement 165k 156k 154k 154k 155k 155k 150k
Childcare Offer for Wales 39k 39k 39k 39k 39k 39k 39k
Care to Learn 0k 0k 0k 0k 0k 0k 0k
Childcare Grant 0k 0k 0k 0k 0k 0k 0k
Parents' Learning Allowance 98k 98k 98k 99k 99k 99k 99k
Adult Dependants' Grant 18k 18k 12k 12k 12k 12k 12k
Travel Grant 0k 0k 0k 0k 0k 0k 0k
Disabled Students' Allowance suppressed suppressed suppressed suppressed suppressed suppressed suppressed
16 to 19 Bursary Fund 0k 0k 0k 0k 0k 0k 0k
Cost-of-living support payment 0k 0k 0k 0k 0k 0k 0k
Energy Bills Rebate 0k 0k 0k 0k 0k 0k 0k
Energy Price Guarantee subsidy 0k 0k 0k 0k 0k 0k 0k
Basic income 0k 0k 0k 0k 0k 0k 0k
Disability basic income 0k 0k 0k 0k 0k 0k 0k
Government balance, each component counted once: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Government receipts, each component once £892.00bn £935.14bn £979.55bn £1,013.85bn £1,052.14bn £1,089.13bn £1,122.37bn
Government spending £529.37bn † £543.55bn † £550.93bn † £559.76bn † £567.52bn † £576.28bn † £584.02bn †
Government balance, each receipt once £362.63bn † £391.59bn † £428.62bn † £454.08bn † £484.61bn † £512.85bn † £538.35bn †
Benefits in household_benefits but not in gov_spending £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn £0.00bn
Model totals: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
gov_tax £892.00bn £935.14bn £979.55bn £1,013.85bn £1,052.14bn £1,089.13bn £1,122.37bn
household_tax £491.97bn £520.87bn £549.93bn £573.92bn £598.90bn £624.26bn £645.90bn
pre_budget_change_household_tax £484.15bn £512.74bn £541.54bn £564.86bn £589.61bn £614.72bn £636.09bn
gov_spending £529.37bn † £543.55bn † £550.93bn † £559.76bn † £567.52bn † £576.28bn † £584.02bn †
household_benefits £529.37bn † £543.55bn † £550.93bn † £559.76bn † £567.52bn † £576.28bn † £584.02bn †
gov_balance £362.63bn † £391.59bn † £428.62bn † £454.08bn † £484.61bn † £512.85bn † £538.35bn †
Household income totals: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Household market income £1,637.96bn £1,702.77bn £1,757.35bn £1,806.41bn £1,855.97bn £1,912.43bn £1,970.44bn
Household net income £1,636.68bn † £1,685.25bn † £1,716.89bn † £1,749.83bn † £1,781.12bn † £1,819.78bn † £1,862.63bn †
Household net income (HBAI definition) £1,449.57bn £1,497.73bn £1,532.81bn £1,567.21bn £1,600.01bn £1,638.04bn £1,679.21bn
Population: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
People 69,245k 69,383k 69,473k 69,592k 69,738k 69,912k 70,080k
Benefit units 35,110k 35,181k 35,226k 35,286k 35,360k 35,449k 35,534k
Households 31,046k 31,108k 31,149k 31,201k 31,267k 31,345k 31,420k
Poverty rates: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Absolute poverty, before housing costs: everyone 16.3% 15.6% 16.4% 16.3% 16.5% 16.5% 16.6%
Absolute poverty, before housing costs: children 24.7% 22.3% 23.3% 22.9% 23.1% 23.2% 23.4%
Absolute poverty, before housing costs: working-age adults 14.1% 13.9% 14.7% 14.7% 14.8% 14.8% 14.9%
Absolute poverty, before housing costs: pensioners 13.7% 13.5% 14.0% 13.9% 14.0% 13.9% 13.9%
Absolute poverty, after housing costs: everyone 21.6% 21.1% 22.0% 22.5% 22.7% 23.0% 23.3%
Absolute poverty, after housing costs: children 33.3% 31.3% 32.2% 32.4% 32.5% 33.0% 33.9%
Absolute poverty, after housing costs: working-age adults 19.9% 19.9% 20.8% 21.5% 21.8% 22.0% 22.2%
Absolute poverty, after housing costs: pensioners 12.7% 12.6% 13.1% 13.2% 13.3% 13.4% 13.5%
Relative poverty, before housing costs: everyone 17.0% 16.3% 16.4% 16.3% 16.3% 16.3% 16.7%
Relative poverty, before housing costs: children 25.9% 23.4% 23.3% 22.9% 22.8% 22.9% 23.7%
Relative poverty, before housing costs: working-age adults 14.5% 14.3% 14.6% 14.7% 14.7% 14.7% 15.0%
Relative poverty, before housing costs: pensioners 14.5% 14.7% 13.9% 13.9% 13.8% 13.9% 14.0%
Relative poverty, after housing costs: everyone 21.4% 21.0% 21.2% 21.3% 21.3% 21.5% 21.7%
Relative poverty, after housing costs: children 33.1% 31.1% 31.1% 31.0% 31.1% 31.2% 31.7%
Relative poverty, after housing costs: working-age adults 19.9% 19.8% 20.2% 20.4% 20.5% 20.6% 20.8%
Relative poverty, after housing costs: pensioners 12.5% 12.4% 12.2% 11.7% 11.7% 11.8% 12.0%
People in poverty: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Absolute poverty, before housing costs: everyone 11,278k 10,837k 11,403k 11,358k 11,478k 11,506k 11,643k
Absolute poverty, before housing costs: children 3,688k 3,332k 3,496k 3,437k 3,477k 3,499k 3,545k
Absolute poverty, before housing costs: working-age adults 5,965k 5,921k 6,308k 6,354k 6,417k 6,426k 6,517k
Absolute poverty, before housing costs: pensioners 1,624k 1,584k 1,599k 1,568k 1,583k 1,580k 1,581k
Absolute poverty, after housing costs: everyone 14,944k 14,662k 15,296k 15,679k 15,862k 16,047k 16,331k
Absolute poverty, after housing costs: children 4,975k 4,689k 4,832k 4,863k 4,898k 4,986k 5,125k
Absolute poverty, after housing costs: working-age adults 8,463k 8,491k 8,965k 9,325k 9,464k 9,549k 9,669k
Absolute poverty, after housing costs: pensioners 1,506k 1,482k 1,500k 1,491k 1,500k 1,513k 1,537k
Relative poverty, before housing costs: everyone 11,750k 11,332k 11,389k 11,348k 11,358k 11,410k 11,702k
Relative poverty, before housing costs: children 3,872k 3,504k 3,496k 3,434k 3,431k 3,451k 3,577k
Relative poverty, before housing costs: working-age adults 6,159k 6,103k 6,301k 6,349k 6,363k 6,388k 6,540k
Relative poverty, before housing costs: pensioners 1,720k 1,724k 1,592k 1,565k 1,563k 1,571k 1,585k
Relative poverty, after housing costs: everyone 14,849k 14,583k 14,728k 14,809k 14,885k 15,005k 15,219k
Relative poverty, after housing costs: children 4,942k 4,664k 4,660k 4,663k 4,681k 4,702k 4,798k
Relative poverty, after housing costs: working-age adults 8,426k 8,460k 8,679k 8,821k 8,877k 8,964k 9,056k
Relative poverty, after housing costs: pensioners 1,482k 1,459k 1,389k 1,325k 1,327k 1,340k 1,366k
Inequality: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Gini coefficient, equivalised HBAI income before housing costs 0.3080 0.3057 0.3063 0.3062 0.3068 0.3073 0.3089
Mean household net income in each side's own deciles: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Decile 1: mean household_net_income per household £17,681 † £17,895 † £17,904 † £18,146 † £18,308 † £18,581 † £18,739 †
Decile 2: mean household_net_income per household £27,939 † £28,874 † £28,683 † £29,083 † £29,354 † £29,679 † £30,165 †
Decile 3: mean household_net_income per household £34,320 † £35,455 † £36,108 † £36,626 † £37,218 † £37,722 † £38,220 †
Decile 4: mean household_net_income per household £38,933 † £40,603 † £41,873 † £42,783 † £43,423 † £43,928 † £44,548 †
Decile 5: mean household_net_income per household £43,240 † £45,443 † £45,787 † £46,995 † £47,729 † £48,921 † £50,229 †
Decile 6: mean household_net_income per household £47,900 † £49,126 † £50,887 † £51,727 † £52,600 † £54,202 † £55,195 †
Decile 7: mean household_net_income per household £55,684 † £56,509 † £57,443 † £57,966 † £59,083 † £59,211 † £60,667 †
Decile 8: mean household_net_income per household £68,789 † £70,704 † £71,779 † £73,356 † £73,945 † £75,356 † £77,241 †
Decile 9: mean household_net_income per household £78,890 † £80,992 † £82,907 † £84,246 † £85,584 † £87,169 † £89,597 †
Decile 10: mean household_net_income per household £117,005 † £120,262 † £122,221 † £124,681 † £126,775 † £129,108 † £131,486 †
Standard reform costings (change in gov_balance): head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Raise basic rate by 1pp £6.41bn £6.66bn £6.64bn £6.88bn £7.04bn £7.26bn £7.48bn
Raise higher rate by 1pp £3.61bn £3.87bn £4.00bn £4.23bn £4.43bn £4.70bn £4.83bn
Raise personal allowance by ~800GBP/year £-3.80bn £-3.92bn £-3.98bn £-4.10bn £-4.17bn £-4.21bn £-1.96bn
Raise child benefit by 25GBP/week per additional child £-1.99bn £-1.75bn £-1.55bn £-1.39bn £-1.30bn £-1.17bn £-1.06bn
Reduce Universal Credit taper rate to 20% £-20.37bn £-20.88bn £-21.32bn £-21.52bn £-21.86bn £-22.25bn £-22.42bn
Raise Class 1 main employee NICs rate to 10% £10.89bn £11.30bn £11.73bn £12.03bn £12.30bn £12.61bn £13.00bn
Raise VAT standard rate by 2pp £24.89bn £25.71bn £26.63bn £27.24bn £27.87bn £28.50bn £29.14bn
Raise additional rate by 3pp £2.90bn £3.15bn £3.22bn £3.42bn £3.61bn suppressed suppressed
Raise the Universal Credit standard allowance by 10% £-3.06bn £-3.18bn £-3.25bn £-3.34bn £-3.44bn £-3.48bn £-3.50bn
Raise the State Pension by 10% £-7.78bn £-8.01bn £-7.99bn £-8.14bn £-8.42bn £-8.55bn £-8.89bn
Standard reforms: change in each reform's target: head levels
Measure (head) 2025-26 2026-27 2027-28 2028-29 2029-30 2030-31 2031-32
Raise basic rate by 1pp: change in income_tax £6.55bn £6.86bn £6.80bn £7.01bn £7.21bn £7.44bn £7.66bn
Raise higher rate by 1pp: change in income_tax £3.61bn £3.92bn £4.01bn £4.23bn £4.45bn £4.72bn £4.84bn
Raise personal allowance by ~800GBP/year: change in income_tax £-3.93bn £-4.07bn £-4.16bn £-4.25bn £-4.32bn £-4.38bn £-1.75bn
Raise child benefit by 25GBP/week per additional child: change in child_benefit £2.33bn £2.14bn £1.94bn £1.76bn £1.65bn £1.52bn £1.40bn
Reduce Universal Credit taper rate to 20%: change in universal_credit £20.34bn £20.87bn £21.30bn £21.52bn £21.81bn £22.20bn £22.41bn
Raise Class 1 main employee NICs rate to 10%: change in national_insurance £11.15bn £11.63bn £12.00bn £12.29bn £12.61bn £12.94bn £13.31bn
Raise VAT standard rate by 2pp: change in vat £24.69bn £25.51bn £26.42bn £27.03bn £27.65bn £28.28bn £28.91bn
Raise additional rate by 3pp: change in income_tax £2.90bn £3.15bn £3.22bn £3.42bn £3.62bn suppressed suppressed
Raise the Universal Credit standard allowance by 10%: change in universal_credit £3.06bn £3.18bn £3.28bn £3.38bn £3.44bn £3.50bn £3.53bn
Raise the State Pension by 10%: change in state_pension £10.68bn £11.17bn £11.28bn £11.60bn £12.04bn £12.47bn £12.97bn

@MaxGhenis

Copy link
Copy Markdown
Collaborator

UK hub: restacked on main after #2229, reviewed, and fixed the one finding. Head bda6163b8.

The PR is still a draft. Merging waits for that review, green CI, the aggregate-impact check, and the team's request for no automatic agent merges.

🤖 Generated with Claude Code

MaxGhenis and others added 2 commits October 29, 2026 04:30
main's test_every_walk_up_to_an_ancestor_simulation_is_reviewed lists each function that walks parent_branch. hb_uses_annual_override walked it itself to collect branch names; _visible_branch_names returns the same set (this branch, its ancestors, default), so the check reads the same storage keys.

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

Copy link
Copy Markdown
Collaborator

Pushed b488ed1cc, which merges main and fixes the one failing test.

Test / Python 4/6 failed at bda6163b8 on main's new test_every_walk_up_to_an_ancestor_simulation_is_reviewed, which found hb_uses_annual_override walking parent_branch (job log). The function only collects branch names there, so b8e40c3a1 replaces its loop with main's reviewed _visible_branch_names, which returns the same set. No calculation changes.

On the merged tree: the guard, accommodation date examples and accommodation properties gave 24 passed (xfail strict); earnings-disregard properties gave 10 passed; Ruff and git diff --check passed. An independent review of this delta is running. The PR stays a draft until CI and the aggregate impact report.

A Sol review of b488ed1 found that build_from_populations keeps the
replaced holders' input records (policyengine-core#605). Once the
applicable-income formula refilled the period, hb_uses_annual_override
read the stale record as a supplied value and skipped the date segments:
£10,400 instead of the fresh £10,322.21.

The five variables the check asks about join SUPPLIED_INPUT_VARIABLES, so
Simulation.calculate forgets such a record before the formula refills the
period. The check moves to a shared stored_as_input helper, which counts a
value stored as an input, supplied or the model's own (set_model_value).
supplied_input would drop a scenario's own value: the variable would read
£2,000 while the 2026-27 award ignored it (£10,322.21, not £9,425).

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

Copy link
Copy Markdown
Collaborator

Pushed 5dc13b793 for the P2 from the Sol review of b488ed1cc: after Simulation.build_from_populations, a stale input record made hb_uses_annual_override take a recalculated applicable income for a supplied one (award £10,400; a fresh simulation gives £10,322.21).

  • The five variables the check asks about now sit in SUPPLIED_INPUT_VARIABLES, so Simulation.calculate forgets such a record before the formula refills the period.
  • The check calls a new stored_as_input helper in utils/supplied_inputs.py. Unlike supplied_input, it counts a scenario's own value (set_model_value), so the 2026–27 award follows a scenario that adjusts the income: £9,425 for £2,000, where supplied_input would leave it at £10,322.21.
  • Two new examples pin both cases. Mutations of each fix fail the matching test.

Local runs and mutations are in the body's Validation section. A Sol review of this commit is running; the PR stays a draft until it, CI and the aggregate impact report.

stored_as_input checked this simulation's input record, so after
build_from_populations a stale record could call a donor's calculated
applicable income supplied (award £10,400, not £10,322.21), and a value
the donor had supplied could count as calculated (£10,322.21, not the
annual taper's £8,775). It now asks the holder, which answers from the
record of the simulation it was built in, so moved populations keep
their own provenance. set_model_value values still count.

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

Copy link
Copy Markdown
Collaborator

Pushed d5c96d95a for the P2 in the fourth Sol review at 5dc13b793: after build_from_populations with populations whose donor had already calculated the applicable income (or either disregard), stored_as_input read this simulation's stale record and the award came to £10,400 instead of £10,322.21; a donor's supplied income counted as calculated (£10,322.21 instead of £8,775).

stored_as_input now asks the holder (Holder._is_input), which answers from the record of the simulation it was built in. Four new tests fail at 5dc13b793 and pass here; the body's d5c96d95a section lists every run. A Sol review of d5c96d95a comes next; the PR stays a draft until that review, CI and the aggregate impact are in.

Simulation.build_from_populations points each population at the new
simulation, but core leaves each holder bound to the simulation that made
it and keeps the new simulation's record of the values the rebuild replaced
(policyengine-core#605). A clone or branch copies the simulation's record and
binds its copied holders to itself, so it read the replaced populations'
record: a supplied income of 3,000 from another simulation awarded 8,775 in
the rebuilt simulation but 10,322.21 in its clone or branch. An input set on
the rebuilt simulation went into the other simulation's record, and
supplied_input missed the other simulation's inputs (#2747).

adopt_population_records binds each holder from another simulation to this
one, keeps that simulation's input and model-value records for the values
the holder still stores, drops the values it calculated or carried over, and
forgets this simulation's records of values the rebuild removed.
build_from_populations also empties the fast cache, which formulas read.

A Hypothesis property checks that the rebuilt simulation, its clone and its
branch give the provenance and award of a fresh copy of the other
simulation. It found two cases that also fail at d5c96d9, now pinned as
examples: a donor's income cached before a scenario set its disregard
(10,244 against 10,075), and a receiver's fast-cache income from before the
rebuild (10,400 against 9,042.15).

Fixes #2747

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

Copy link
Copy Markdown
Collaborator

Pushed 6e090ed00 for the P2 in the fifth Sol review at d5c96d95a: a clone or branch of a simulation rebuilt from another simulation's populations lost that simulation's provenance, so a supplied income of £3,000 gave £8,775 in the rebuilt simulation and £10,322.21 in its clone or branch.

Simulation.build_from_populations now moves the input and model-value records with the holders (adopt_population_records), drops the values the other simulation calculated, and empties the fast cache. That also fixes #2747. A Hypothesis property (R1) checks the rebuilt simulation, its clone and its branch against a fresh copy of the other simulation; its two pinned examples and 16 of the 17 new example tests fail at d5c96d95a. The body's 6e090ed00 section lists every run. PolicyEngine/policyengine-core#636 reports the holder binding upstream.

A Sol review of 6e090ed00 comes next; the PR stays a draft until that review, CI and the aggregate impact are in.

… them

6e090ed rebound each holder taken from another simulation to the rebuilt
one. The two simulations still shared those holders, so the rebinding left
the other simulation with holders bound to the rebuilt one. The sixth review
found three failures:

- A holder from a simulation that stores values on disk kept its disk
  setting. The rebuilt simulation has no memory configuration, so the next
  input it stored crashed in core's Holder._set.
- The input-record filter asked only the first storage tier. A cache in
  memory hid an input on disk, so the rebuilt simulation lost the input:
  £10,322.21 instead of £8,775 with a supplied £3,000.
- An input set on the other simulation went into the rebuilt simulation's
  record. Neither simulation noted the change, so both kept a stale
  weekly_hours of 10 instead of 20.

Simulation.build_from_populations now takes a copy of each population and
holder another simulation made, as Simulation.clone copies them
(copy_other_populations in utils/supplied_inputs.py), and leaves the other
simulation's populations, values and records as they were. Neither
simulation's later changes reach the other. Populations no simulation has
taken yet, the only ones the model's own builders pass, go in as before.

Before copying, the helper reads what the other simulation recorded of each
holder: the inputs that survive in either storage tier, and the model values
(set_model_value) still stored. keep_population_records then keeps those
records and this simulation's own records for the holders it made, drops
the other values the copies hold, and gives each copy the storage settings
this simulation gives its own holders.

New tests in test_input_records.py cover:
- a later input on populations from a simulation that stores on disk;
- storage on disk when this simulation stores there;
- an input on disk under a cache in memory;
- the other simulation keeping its own values and inputs.

In the Housing Benefit tests, two simulations rebuilt from the same
populations now answer independently. The R1 property's docstring now names
the states it generates.

Fixes #2747

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 2 commits October 29, 2026 15:13
The copy at 371c220 keeps this simulation's input records only for the
holders it made, but no test failed when it kept them all: the readers
that also check storage hid the stale record. supplied_input does not check
storage marks, so a value cached in the replaced period read back as the
replaced input. The new test sets a cache of 123 for an adjusted net income
the rebuild replaced and checks that supplied_input returns None.

It fails on main (daec84d), where supplied_input returns the cache, and
passes at 6e090ed and 371c220. The keep_replaced_records mutation,
which survived the hub's mutation run, now fails it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The seventh review found that copying another simulation's holders dropped
a model value (set_model_value) stored on disk under a value cached in
memory. _records_of compared the recorded model value with what storage
returned first, the cache, so the copy kept the input key but not the
model marker. _drop_computed then removed the cache, and the disk value
read back as a supplied input. In the review's example, adjusted net
income of £50,000 set by the model on disk under a cache of 999 gave a
supplied_input of 50,000 and net income of £50,000 in the rebuilt
simulation, its clone and its branch, against the original simulation's
None and £30,000.

_records_of now compares each model value with the stored input, read
through core's get_stored_input, which asks each tier whether it holds
the input. The review's script now gives None and £30,000 in all three
copies, and the aggregate-guard snapshot keeps the model origin.

test_rebuilt_populations_keep_a_model_value_a_cache_hides pins the case.
It fails at 3cfab06 and passes here.

The copy_other_populations docstring now says that the copy shares each
in-memory array with the original until it first reads it, so writing in
place into the original's array before then changes what the copy reads.

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

Copy link
Copy Markdown
Collaborator

Pushed 8cac7e91f for the P2 in the seventh Sol review at 3cfab0639: when a value cached in memory hid a model value (set_model_value) stored on disk, the copy lost the record that the model set it. In the review's example, the rebuilt simulation, its clone and its branch read a model-set adjusted net income of £50,000 as supplied, and net income rose from £30,000 to £50,000.

_records_of now compares each model value with the stored input through core's get_stored_input, which checks memory and then disk. test_rebuilt_populations_keep_a_model_value_a_cache_hides fails at 3cfab0639 and passes here, and so does the review's reproduction. The copy_other_populations docstring now states the review's P3: a copy shares each in-memory array until it first reads it, so an in-place write into the original's array before then reaches the copy. The body's 8cac7e91f section lists every run: 273 tests across 12 files and eleven mutations, all as expected.

An eighth Sol review of 8cac7e91f is running; the PR stays a draft until that review, CI and the aggregate impact are in.

Towncrier adds each entry's bullet itself, so a fragment that starts with
'- ' renders as '- - ...' in CHANGELOG.md. The Changelog fragments check
warned on both at 8cac7e9, and the eighth review asked for the fix.

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

Copy link
Copy Markdown
Collaborator

The eighth Sol review approved 8cac7e91f with two nits. Pushed dd79d3fb5 for the first: both changelog fragments started with - , which towncrier doubles, as the Changelog fragments check warned. The body now covers the second, naming set_input where it said neither simulation's later changes reach the other, and adds the review's section. The commit changes no code, and the review's runs (533 Python tests, 619 Housing Benefit YAML cases, 40 more random R1 examples) all passed at 8cac7e91f.

The PR stays a draft until CI and the aggregate impact pass at dd79d3fb5. The merge stays held with the other policyengine-uk merges.

@MaxGhenis
MaxGhenis marked this pull request as ready for review October 30, 2026 14:14
@juaristi22
juaristi22 added this pull request to the merge queue Oct 30, 2026
@MaxGhenis MaxGhenis mentioned this pull request Oct 30, 2026
40 tasks done
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 30, 2026
@juaristi22
juaristi22 added this pull request to the merge queue Oct 31, 2026
Merged via the queue into main with commit 12693ac Oct 31, 2026
20 of 22 checks passed
@juaristi22
juaristi22 deleted the fix-hb-accommodation-dates branch October 31, 2026 01:52
MaxGhenis added a commit that referenced this pull request Oct 31, 2026
Brings in #2234's accommodation segments. The only conflict sat in the
supplied-input registry, where both sides add the two childcare names;
main's side lists them with #2234's three new names, so the merge keeps it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Oct 31, 2026
#2234 settles Schedule 4 paragraph 17 separately before and after the
accommodation disregard starts on 5 October 2026, but its segment income
left out the WTC disregard. Paragraph 56 follows paragraph 17's outcome, so
each segment now has its own WTC disregard: the childcare limit takes it,
hb_segment_deductions returns it, and entitlement deducts it. The annual
variable combines the segments by days. The WTC and earnings decisions now
share one paragraph 17 test and one check for a supplied or replaced
childcare deduction, #2234's hb_uses_annual_override.

Under a reform that keeps tax credits in 2026-27, a claimant in specified
accommodation with £500 of earnings and £2,000 of WTC now gets £9,600.19
rather than £9,022.21. Current-law awards do not change: tax credits end
before the accommodation disregard starts.

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

3 participants