Skip to content

Apply each growth index's first listed year - #2693

Draft
vahid-ahmadi wants to merge 1 commit into
savings-interest-per-capita-upratingfrom
index-base-year-for-all-series
Draft

vahid-ahmadi wants to merge 1 commit into
savings-interest-per-capita-upratingfrom
index-base-year-for-all-series

Conversation

@vahid-ahmadi

Copy link
Copy Markdown
Collaborator

Fixes #2692.

Stacked on #2638 (savings-interest-per-capita-uprating), which adds the opt-in index_base_year setting. This merges after #2638, and I will retarget it to main then.

What changes

The index generator puts an index of 1 in each series' first listed year, so that year's growth is never in the index. A direct input dated before that year (Core's class-level uprating, or the legacy-gross savings fallback) is then carried forward by a factor of 1 for that step. This sets index_base_year to the year before the first listed growth on the ten series #2692 found can be hit:

Series index_base_year First step now in the index
obr.per_capita.gdp 2020 2021, 8.92%
obr.per_capita.mixed_income 2020 2021, 6.36%
obr.house_prices 2020 2021, 8.2%
obr.mortgage_interest 2020 2021, -2.26%
obr.petrol_spending_litre_proxy 2020 2021, 42.8%
obr.diesel_spending_litre_proxy 2020 2021, 40.4%
orr.per_capita.rail_passenger_journeys 2020 2021, 154.33%
ons.population 2020 2021, 0.36%
obr.private_pension_index (generated) 2019 2020, 2.2%
obr.lagged_average_earnings (generated) 2021 2022, 5.9%

No growth value is added or changed: only the year the index is 1 in. Every first-year growth used is already listed. The two generated series get the setting through their generators (create_private_pension_uprating.py, and a new optional metadata argument on add_lagged_parameter). capital_gains copies the per-person GDP index exactly (data/capital_gains_projection.py), so it moves with it.

Why explicit, not a default for every series

I considered anchoring every series a year earlier by default and did not, for three reasons:

  • earnings_uprating and september_cpi_uprating start with a None placeholder, so a default needs a carve-out.
  • Index levels are rounded to 5 decimal places as they cumulate (create_economic_assumption_indices.py:63). Re-anchoring moves the rounding points, so later-year ratios shift by up to about 1e-5 relative. A default would do that to all ~40 other series, including CPI, RPI and earnings, which uprate rounded benefit rates.
  • Some code reads levels across years (AHC deflator, house price projection, HVCTS bands), which a default would make me audit series by series.

Effect on later years

I dumped every index at b1ee29e and on this branch (CountryTaxBenefitSystem, 2008 to 2039). The other 41 series are identical. For the 11 changed series, year-on-year ratios after the first growth year move by at most 9.1e-6 relative (ons.population, 2024), the same rounding effect #2638's anchor has on its own series. The largest reader of this is the house price index: the dataset projection of main_residence_value and the HVCTS bands both read it, so they stay on one basis, but projected values move by up to about 6.5e-6 relative (about £13 on £2m). @juaristi22, if you would rather the rounding kept later ratios exact (for example by rounding relative to the first growth year), that is a change to #2638's generator, so I have left it out of here.

Tests

New policyengine_uk/tests/test_growth_index_first_year.py:

  • for each of the ten series, the index ratio across the first growth year equals 1 + that year's growth, and the index is 1 in and before the base year;
  • every later year-on-year ratio still equals 1 + that year's growth (to 2e-5, the rounding);
  • capital_gains still equals the per-person GDP index;
  • direct route: a rail_usage input dated 2020 and queried for 2021 gives 254.33, not 100.

Against b1ee29e the first-year and rail tests fail (11 failed, 21 passed); on this branch all 32 pass.

Existing tests run locally, one file at a time: code_health/test_uprating_declarations.py (8), test_rail_usage_uprating.py (65), test_savings_interest_uprating.py (24), test_lagged_series.py (4), test_high_value_council_tax_surcharge_liability.py (5), test_residential_property_uprating.py (26), test_wealth_components.py (3), test_isa_accounting.py (12), test_isa_income_contract.py (53), test_capital_gains_projection.py (115), test_dated_reform_routes.py (114), test_population_vintage.py (86) and test_pension_credit_uprating.py (34), all passed.

Not run: the full suite and anything needing the enhanced FRS (microsimulation tests are left to CI).

🤖 Generated with Claude Code

Set index_base_year to the year before the first listed growth on the
ten series a direct input dated before that year could hit: per-person
GDP and mixed income, house prices, mortgage interest, petrol and diesel
spending, rail journeys per person, population, and the generated
private pension and lagged earnings indices. No growth value changes.

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

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

Program review

Base repository: PolicyEngine/policyengine-uk
PR number: 2693
Reviewed head SHA: b213aaf
Merge base SHA: b1ee29e
Mode: full
Scope: Ten explicit first-growth index anchors, generated metadata forwarding and affected direct-input/projection mechanics against the declared stacked PR2638 base; no full historical statistical-source audit
Source manifest: /private/tmp/policyengine-command-runs/881387a1b745/pr2693-direct-review-20261027-review-sources.json
Review status: PARTIAL

Source Documents

No source documents registered; see scope and validation.

Critical

None.

Should Address

None.

Suggestions

S1 — code-s1 — Qualify the dataset-projection statement (OPEN)

Location: policyengine_uk/parameters/gov/economic_assumptions/README.md:17–18, also changelog.d/2692-index-first-growth-year.fixed.md:1 and the new test module's lines 6–8. These passages describe projection as reading growth rates directly and being unchanged. That accurately describes the ordinary mapped growth loop (data/economic_assumptions.py:356–376), but residential-property projection explicitly reads the rebased, five-decimal rounded cumulative house-price index (:311–313, :533–546). Its ratios can therefore change slightly even for an observation after 2021, as the PR body already discloses. For example, the unchanged literal 2021/2022 house-price growth rates produce old levels 1 / 1.09260 and new levels 1.08200 / 1.18219, so the 2021→2022 ratio changes from 1.0926 to approximately 1.09259704. Qualify the prose to preserve the body's rounding caveat. This is an optional accuracy correction, not evidence that the tiny numerical drift is an incorrect policy output. Independent scalar arithmetic is saved in pr2693-direct-review-20261027-code-rounding-arithmetic.json; no model or dataset was run.

Coordinator assessment: POLICY independently corroborates this same documentation item; count it once. No material incorrect household tax or benefit result was established from the disclosed rounding drift.

Evidence Gaps

  • Exact-head execution remains unverified: the 32 new assertions and directly affected lagged-series, CGT and residential-property/HVCTS regressions were NOT RUN in this review; hosted CI was absent at capture. Static inspection does not prove test collection, supported-Core integration or passing results. A matching-head run of those tests or equivalent associated CI would close this gap; private population data is not required.
  • Role checks incomplete: pr2693-direct-review-20261027-review-code.md; see recovered report.

Notes

  • No functional defect or critical finding was established. The ten opt-ins expose the already-listed first growth interval; no growth observation, lag, statutory value or source vintage changes. Both generated series are created before index generation; default CGT indices still copy per-person GDP, and lagged CPI retains its prior metadata behavior.
  • The existing five-decimal cumulative rounding can slightly change later ratios; the body already discloses that effect. The optional prose correction does not demand a different index algorithm or establish a material policy error.
  • Primary-source revalidation is not needed for this normalization-metadata delta; the empty manifest means no new primary documents verified. Parent PR2638’s OBR1.9/1.11 corroboration gap remains unresolved and this review does not approve the entire stack against main. No blocked source route was retried.

Validation Summary

Reviewed exact b213aaf against its declared stacked base b1ee29e. Local model tests, collection, Core/country imports and environment setup NOT RUN; hosted CI absent at capture. Author-reported passes are not reviewer execution. Changed Python files parse under isolated AST inspection; scalar rounding arithmetic is independent engineering evidence, not a model run. Source fetches: 0. Both role reports and source-manifest integrity are complete; exact snapshot was removed normally after both DONE notifications. Cleanup: /private/tmp/policyengine-command-runs/881387a1b745/pr2693-direct-review-20261027-Root-normal-cleanup.json.

Timing

setup seconds: 9.49s; parallel review seconds: 347.95s; policy role seconds: 295.06s; code role seconds: 317.98s; adjudication seconds: 0.00s; consolidation cleanup seconds: 72.44s; elapsed seconds: 612.51s

Review Severity

COMMENT. Open findings: 0 critical, 0 should address, 1 suggestions.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants