Repository navigation
Report a UK program row for every budget-total component a reform changes - #724
PavelMakarchuk wants to merge 1 commit into
Conversation
…nges UK program statistics came only from the fixed UK_PROGRAMS list, but the headline budgetary impact sums every tax and benefit in policyengine-uk's HOUSEHOLD_TAX_VARIABLES and HOUSEHOLD_BENEFIT_VARIABLES. A change in any other program (Scottish Child Payment lost with Universal Credit, a PIP cut) was in the headline with no row. Read those two lists at run time, materialize the components on both simulations, and add a row for each one the reform changes. The fixed rows are unchanged. Fixes #723 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
left a comment
There was a problem hiding this comment.
Thanks, Pavel. Because of the Budget, building the rows from -uk's two lists is a good enough approach for now: every programme a reform changes gets its own row, and the rows now explain the headline. There's one problem to fix before this merges, and a longer-term change I'd like us to make afterwards.
Before merging. The v3 "Budgetary impact by programme" chart decides which rows are benefits from its own BENEFIT_PROGRAMS set, and sim-api's build_detailed_budget drops is_tax. Once sim-api deploys a .py release with this change, new benefit rows such as scottish_child_payment and pip would be drawn as taxes, with the wrong sign. On report 4738, Scottish Child Payment would show −£25m instead of +£25m, and with policyengine-app-v2#1451 in, an "Other programmes" bar of +£50m would make up the difference. #1451 fills the gap but can't correct the sign, so it doesn't cover this. Could you have sim-api pass is_tax into detailed_budget and have the chart sign rows by it, and land both before this merges?
Longer term. I'd like the list of UK budget programmes to live in one place, most likely a programme registry in -uk (like policyengine-us's programs.yaml) that household_tax and household_benefits are built from and that .py reads. This PR builds on two plain lists inside -uk's formula modules, which leaves us with:
- Several lists that can disagree. We now have -uk's two lists, .py's
UK_PROGRAMSand the app'sBENEFIT_PROGRAMS, and the last of these is what causes the problem above. Of the 14UK_PROGRAMSentries, 11 repeat -uk's, andvat,ni_employerandtax_creditsaren't in the totals at all. - A dependency on -uk's internal file layout. .py imports
HOUSEHOLD_TAX_VARIABLESandHOUSEHOLD_BENEFIT_VARIABLESfrom thehousehold_taxandhousehold_benefitsvariable modules, so moving or renaming either one breaks every UK economy analysis at import. - No metadata beside each name. There's no label, no tax/benefit flag (we infer it from which list a name is in) and no grouping, so the labels and grouping raised in #723 still have to be hard-coded downstream.
- Totals the lists don't fully describe.
abolish_council_taxremoves programmes by name inside the formulas, and benefit uprating treatsbasic_incomeandstate_pensionseparately. With those settings on, the rows won't match the headline. - Amounts mixed with reform-only changes.
household_taxcountsvat_changeand the employer NI incidence terms rather than VAT and employer NI. That's part of why consumers have to know to leave outvatandni_employer(and the overlappingtax_credits) for the rows to add up. - Silent changes between -uk releases. Between 2.151.0 and
main,isa_cash_interest_flat_rate_chargeandwales_childcare_offerwere added andprivate_school_vatbecameprivate_school_vat_change. Because the lists are read at run time, the rows a report can show change with the -uk version, and nothing records that.
With one registry entry per programme (variable, label, tax or benefit, group, and whether it counts towards the headline):
- -uk could build both totals from it;
- .py could read it through a stable function rather than module internals;
- sim-api could pass the flags through;
- the app could drop
BENEFIT_PROGRAMS.
I'd suggest tracking that in an issue for after the Budget.
|
Thanks, Anthony. Both parts are in place: Before merging (the sign fix):
Order: sim-api#972 and app#1451 merge and deploy first; this PR reaches sim-api after that. Longer term: PolicyEngine/policyengine-uk#2702, for after the Budget. -uk already has |
Re-review of #724 at
|
Fixes #723
Problem
UK
program_statisticscame only from the fixedUK_PROGRAMSlist (14 rows). The headline budgetary impact is the change inhousehold_tax - household_benefits, which policyengine-uk builds fromHOUSEHOLD_TAX_VARIABLES(27) andHOUSEHOLD_BENEFIT_VARIABLES(about 50). A change in any program outside the fixed list was in the headline with no row, so the apps' "Budgetary impact by programme" chart didn't add up and couldn't say why.Example, live report 4738 (2027 UC freeze, policyengine 6.10.0 / policyengine-uk 2.132.6 / certified
microcosm_uk_2024_25.h5): UC row £933.5m, headline £958.5m; the missing £25.0m is Scottish Child Payment, lost by Scottish families whose UC falls to zero. A PIP cut had no row at all.Change
uk_budget_component_programs(model_version)reads the two lists from policyengine-uk at run time and returns every component the model version defines that isn't already inUK_PROGRAMS, withis_taxfrom the list it came from (72 on policyengine-uk 2.151.0).configure_uk_budget_component_variablesmaterializes them on both simulations, on each variable's own entity, throughadd_extra_variables(the same route as the labour supply variables).economic_impact_analysisreturns allUK_PROGRAMSrows as before, then a row for each component the reform changes. Unchanged components are dropped, so outputs don't carry dozens of empty rows.No hard-coded list to maintain: a program policyengine-uk adds to the totals gets a row with no change here. Existing rows, keys and fields are unchanged.
Testing
UK_PROGRAMS); components are materialized on their own entities; the analysis adds a row for a changed component (Scottish Child Payment, change −1,530) and none for an unchanged one (PIP); no component rows when nothing changes._validate_program_statistics_confignow accept the new optional argument.make format,make lintand the full test suite pass.Notes for consumers
is_taxon each row says whether it's a tax or a benefit. The app chart currently signs rows from its own list of benefit keys, so the simulation API should passis_taxthrough todetailed_budget(Show an "Other programmes" bar when the programmes fall short of the total policyengine-app-v2#1451 shows any remaining gap as "Other programmes" meanwhile).vatandni_employer(not inhousehold_tax) andtax_credits(overlapsworking_tax_credit+child_tax_credit). Summing every row can still double count; consumers that need rows adding to the headline should leave those three out.extended_childcare_entitlementandtargeted_childcare_entitlementwith a personal allowance cut). Grouping is left to a follow-up.🤖 Generated with Claude Code