Repository navigation
Conversation
programs.yaml now flags each term of household_tax and household_benefits with `budget`, an optional `group`, and the council tax abolition and benefit uprating rules. Terms that no programme entry carries sit in a new `budget_terms` section with a `kind`, and `budget_order` fixes the summation order. policyengine_uk.programs reads the file, and budget_programs() returns each term with its label, sign and group. HOUSEHOLD_TAX_VARIABLES and HOUSEHOLD_BENEFIT_VARIABLES keep their modules, contents and order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6Kyd2hZt9Nh1cPJ8mH
policyengine_uk.programs.budget_programs() is reachable after `import policyengine_uk` without relying on the variable modules having imported the submodule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6Kyd2hZt9Nh1cPJ8mH
Aggregate impact on certified data: passBase Checks: 0 broken by this change, 1 failing on both sides, 0 fixed. Moves flagged for review: 0 (thresholds in Notes
Moves flagged for reviewNone. Spending by benefit and service
Caseloads
Standard reform costings (change in gov_balance)
The report suppresses a reform's costing and target change in these years, for the reasons below (
Standard reforms: change in each reform's target
Household net income by decileNo household's net income changes in any year. No change in any year: receipts by tax head; government balance, each component counted once; model totals; household income totals; population; poverty rates; people in poverty; inequality; mean household net income in each side's own deciles. Checks failing on both base and head (1)
Counted-once checkEach of 92 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.
Provenance
Dataset: Receipts by tax head: head levels
Spending by benefit and service: head levels
Caseloads: head levels
Government balance, each component counted once: head levels
Model totals: head levels
Household income totals: head levels
Population: head levels
Poverty rates: head levels
People in poverty: head levels
Inequality: head levels
Mean household net income in each side's own deciles: head levels
Standard reform costings (change in gov_balance): head levels
Standard reforms: change in each reform's target: head levels
|
|
Ready for hub review Head Items
Tests run
I ran the 549-test batch before commit Mutation and revert checks (scratch worktree)
Aggregate impact report
The check failing on both sides is Skipped
Judgment calls
Pre-Budget lists
Generated by Claude Code |
|
Picking this up for the UK hub. Reply here if you're mid-change and I'll stop. Generated by Claude Code |
The loader now refuses a misspelt budget field, a budget_terms field outside the documented ones, budget fields on an unflagged entry and a non-string budget_dropped_when, and parses programs.yaml with PyYAML's C parser when it is available. The drift test checks the order in which household_benefits first reads its terms, a property checks that the in-effect and uprated selections keep budget_order, a test ties fiscal_partition.yaml's dropped_when to the registry's, the existence check covers every term, and one negative case covers each loader check. The pinned grid gains a High Value Council Tax Surcharge payer, with values from main. private_school_vat_change names its programme, the Welsh Land Transaction Tax label matches the other purchase taxes, and CONTRIBUTING.md lists every step of adding a term. The changelog fragment becomes an added entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6KiLH9sS455unmyngi
|
Generated by Claude Code |
|
Generated by Claude Code |
|
Ready for hub review Head Findings
Voice rows
Tests run at this head
Mutation and revert checks (each applied to the working tree, then restored with
|
| Change | Result |
|---|---|
Swap income_tax and national_insurance in budget_order.tax |
1 failed: the differential test (the pinned sums happen not to move) |
Drop the State Pension's budget_uprating: [all] |
9 failed: the rules test and every uprating cell of the pinned test |
Drop budget_dropped_when from the High Value Council Tax Surcharge |
5 failed: the rules test, the partition test for tax, the traced drift test with council tax abolished, and the 2028 abolish_council_tax and abolish_uprating_basic_income cells, where the surcharge payer's household_tax gives 76,493.6015625 against 72,993.6015625 |
Make household_tax ignore budget_dropped_when |
9 failed: the traced drift test and every council tax abolition cell |
Sort the output of uprated_benefit_variables (test_budget_programs.py only) |
3 failed: the subsequence property and both benefit drift cases |
Reverse the benefit list in household_benefits (test_budget_programs.py only) |
2 failed: both benefit drift cases |
| Disable each of the loader's 18 checks in turn (the loader tests only) | each fails at least one test; disabling the check on flagged programs entries stops the package loading |
Remove budget: benefit from Scottish Child Payment, order unchanged |
the package refuses to load: budget_order.benefit and the entries flagged budget: benefit differ: only ordered ['scottish_child_payment'], only flagged [] |
Restore main's two formula modules |
139 passed, as an unchanged result requires |
I ran the first six rows and the last row against both new test files unless the row says otherwise.
Skipped
- The 9 certified-dataset tests and the
policyengine_bundlestest (above). I used no survey data. - CI: this branch has no pull request. The hub moves Build the budget totals from the programme registry #2809's branch.
- The review's seeded 3,150-value differential: this change touches no formula arithmetic. The pinned grid on both trees covers the totals.
Judgment calls
- The loader allow-lists every
budget_termsfield, but forprogramsentries it checks only fields starting withbudget, because those entries carry the coverage schema's own fields. - The benefit drift test asserts first-read order rather than the full trace, because the formula reads each term up to three times.
- The household docstrings name council tax abolition as the example of a removed term, because it is the only
budget_dropped_whenswitch today. - I kept the changelog text as it was. The review flagged nothing in it, and only the type changes.
- I left the earlier hand-off comment unedited (finding 5).
PR_TITLE.txtstays unchanged.
Generated by Claude Code
|
Picking this up for the UK hub. Reply here if you're mid-change and I'll stop. Generated by Claude Code |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N2tyHPjDWxZLvTpKk7Pt12
Projected State Pension weekly rates now round to 5p each April, which moves the last float32 bit of six 2028 cells for the two households with a reported new State Pension. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N2tyHPjDWxZLvTpKk7Pt12
|
Picking this up for the UK hub. Reply here if you're mid-change and I'll stop. Generated by Claude Code |
A budget field on an unflagged budget_terms entry, and a budget_terms entry without a variable, each get their own negative case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6Kz7oVBUKi9kgxm7fh
The cited reference is the Scottish Government's 3 December 2025 confirmation that it will not lay the regulations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6Kz7oVBUKi9kgxm7fh
|
Generated by Claude Code |
|
Generated by Claude Code |
|
Ready for hub review Head
No pull request has this branch as its head, so no CI has run on it. #2809's branch is still at Checking the stopped session's two commitsMerge Pins
The input that moves them is #2751's rounding. In 2028, the projected new State Pension weekly rate falls from 259.2357292385871 to £259.20, and the basic rate rises from 198.6435405562153 to £198.65.
None of the other 2028 cells moved:
Against I also checked a merge of this head with Items 3 to 63. Mutation and revert table. I reran every row at this head. Each change went into the working tree, ran, and was restored with
Apart from the row counts noted, every count matches the earlier table. The revert row moves from 139 to 141 because of the two new loader cases. 4. Loader cases.
The 17 earlier cases covered 17 of these 19. I disabled each condition in turn against
5a. 5b. Two Child Limit Payment note. 6. PR text. I posted
Tests run at this head
The environment came from Skipped
Judgment calls
Generated by Claude Code |
|
Ready for hub review Verdict: APPROVE WITH NITS at I reviewed Findings1. P3: two of the 19 "refused conditions" change only the error message, and the body's count rule isn't applied evenly
2. P3: previous finding 5 is only partly closed
The twelve P3s from the previous round, at the head
Checked and sound
What I couldn't check
Generated by Claude Code |
juaristi22
left a comment
There was a problem hiding this comment.
Approved at exact head 3d234c759fbd48c1d7bd69bbe25cb682a7becc4e. Review COMPLETE, with no findings or material evidence gaps; applicable required checks pass at this head.
All 311 selected checks passed. The independent paired comparison substituted the exact merge-base formulas into the same combined-reform simulations: all 78 values across 13 households and six final household/government outputs were exactly equal. The source/contract review also confirmed the same ordered 29 tax and 57 benefit terms, council-tax abolition exclusions, benefit-uprating exceptions and coverage-programme content apart from additive budget metadata.
No new statutory rule or parameter value is introduced. The new budget descriptors keep programme measures, reform measures and public services distinct from the coverage list.
Addresses #2702
Reproducer: n/a: this changes no result
axiom: n/a: this changes how the package lists programmes and changes no rule or parameter value
What the code does now
policyengine_uk/programs.yamldefines the budget totals as well as model coverage.programsentry whosevariablea total adds carriesbudget: taxorbudget: benefit, and an optionalgroupfor terms that move together (for example the five childcare entitlements, or ESA's two parts).budget_termssection. Each has akind:programme_measure(the amount a total counts for a listed programme, such asesa_incomefor ESA, withprogramnaming it),programme(a programme the coverage list does not carry yet),reform_measure(not a programme: a hypothetical tax or benefit, or a change from baseline such asvat_changeand the employer NI incidence terms) orpublic_service(public services in kind). These entries stay out ofprograms, so the coverage page never lists them.budget_dropped_when: gov.contrib.abolish_council_taxsits on council tax, the High Value Council Tax Surcharge and Council Tax Reduction.budget_upratingsits on the State Pension ([all]) and basic income ([]); every other benefit term takes bothgov.contrib.benefit_upratingrates.budget_ordergives each total's summation order. The loader refuses a registry whose flags and order disagree, a misspelt field whose name starts withbudget, and anybudget_termsfield outside the documented ones.policyengine_uk/programs.pyparses the YAML (with PyYAML's C parser where the environment has it) with no import from the package's top level.budget_programs()returns oneBudgetProgramper term (variable, label,taxorbenefit, group, kind, programme id, dropped-when parameter, uprating rates), taxes first, each in summation order.household_taxandhousehold_benefitsbuild their lists from it, andHOUSEHOLD_TAX_VARIABLESandHOUSEHOLD_BENEFIT_VARIABLESstay in their modules with the same contents in the same order.build_metadata.DATA_BUILD_SURFACEnow includesprograms.py.policyengine-core loads the whole of
programs.yamlas the system'smodelled_policies, so that attribute, and any metadata a consumer builds from it, now carriesbudget_terms,budget_orderand the new fields on eachprogramsentry.The branch merges
mainat 46a6067, which brings #2757's corrections to programme agencies and national coverage inprograms.yamlandtest_program_registry.py, and #2751's rounding of projected State Pension weekly rates to the nearest 5p.programs.yamlkeepsmain's corrections alongside the budget fields; the merge needed no conflict resolution.Invariants
HOUSEHOLD_TAX_VARIABLESandHOUSEHOLD_BENEFIT_VARIABLESequalmain's lists, element by element.budget_dropped_whenparameter is true.household_taxreads them inbudget_order;household_benefitsfirst reads them in the order its threeaddcalls imply (the terms theallrate scales, then those thenon_sprate scales, then the rest), each inbudget_order.budget_variables_in_effectanduprated_benefit_variablesreturn subsequences ofbudget_order, in order.fiscal_partition.yaml'sdropped_whennames the same terms as the registry'sbudget_dropped_when.budget_programs()variable exists in the system and appears once, and its sign agrees with the list it came from.budget_orderand refuses one whose order drops or repeats a flagged term.household_tax,household_benefitsandhousehold_net_incomeequalmain's values exactly: the totals add the same float32 terms in the same order.Tests
New tests:
tests/test_budget_programs.py(124 tests): drift between the registry and what each total's formula reads (traced, at current law and with council tax abolished, including the benefit total's first-read order); the partition'sdropped_whenagainst the registry's; the differential test againstmain's two lists, which the file holds as ordered literals, and its council tax and uprating rules;budget_programs()from a fresh interpreter, importingpolicyengine_uk.programs, thehousehold_taxmodule or the package first; the existence check parametrized over all 86 terms; a Hypothesis property that the in-effect and uprated selections keepbudget_order; Hypothesis properties on generated registries; and 19 negative loader cases, one for each condition the loader refuses. We count its 16 refusals as 19 conditions: the two field-name checks once for each section, and thevariableandbudgettest once for each field. That test already refuses abudget_termsentry with no flag, so the flag check's case forbudget_termstests its message.tests/test_budget_totals_pinned.py(17 tests) pinshousehold_tax,household_benefitsandhousehold_net_incomewith exact equality for 13 hand-built households in 2023, 2025, 2026 and 2028 under four reforms (current law;abolish_council_tax; both uprating rates; both together with a flat basic income): 208 cells. One household, a London owner, pays the High Value Council Tax Surcharge from 2028. Every value comes from a run ofmainat 46a6067 with this file copied in. Six of the 208 differ from the pins the branch took earlier at 2da212a, all in 2028: the pensioner couple under current law andabolish_council_tax, and the East of England household under all four reforms. Round projected State Pension weekly rates to the nearest 5p each April #2751 rounds each projected year's weekly rate to the nearest 5p, starting from the previous year's rounded rate, so the 2028 new State Pension rate comes to £259.20 in place of the unrounded £259.2357. That moves each reported 2028 new State Pension in these households by one or two float32 units in the last place: £11,000.0010 becomes £10,999.9990, £9,000 becomes £8,999.9990 and £10,500.0010 becomes £10,500. The branch,mainat 46a6067 andmainat 6c6e229 all reproduce the 208 pins exactly.test_budget_programs.pytest_budget_totals_pinned.pyon this branch, onmain's code at 46a6067 and onmain's code at 6c6e229test_program_registry.pytest_program_registry.py,test_fiscal_partition.py,test_state_pension_rate_rounding.py,test_household_benefits_properties.py,test_gov_spending_properties.py,test_uc_statutory_pay_properties.py,tests/aggregate_impact,test_build_metadata.pypolicyengine_bundles)test_program_registry.py,test_fiscal_partition.pyandtest_state_pension_rate_rounding.pyon this branch merged withmainat 6c6e229tests/code_healthformat --check,check,check --select F811,F821;git diff --checkuv buildofgit archive HEADpolicyengine_uk/programs.pyandpolicyengine_uk/programs.yamlWe ran each mutation below alone, in a scratch copy, against the two new test files:
income_taxandnational_insuranceinbudget_order.taxbudget_uprating: [all]budget_dropped_whenfrom the High Value Council Tax Surchargeabolish_council_taxandabolish_uprating_basic_incomecells, where the surcharge payer'shousehold_taxgives 76,493.6015625 against 72,993.6015625household_taxignorebudget_dropped_whenuprated_benefit_variables(test_budget_programs.pyonly)household_benefits(test_budget_programs.pyonly)budget: benefitfrom Scottish Child Payment, order unchangedbudget_order.benefit and the entries flagged budget: benefit differ: only ordered ['scottish_child_payment'], only flagged []main's two formula modulesmain's formulas give the same totals🤖 Generated with Claude Code