Track the source series when building the lagged CPI and earnings - #1851
Conversation
Both lagged series were built with a hardcoded range ending in 2029. A parameter carries its final value forward rather than raising, so neither failed past that point — they silently held a stale growth rate. For CPI that is currently harmless, as the OBR series is flat at 2% from 2027, so the carried value is what the loop would have produced anyway. Average earnings are not flat: they ramp from 0.021 in 2028 to 0.0383 by 2036, so lagged earnings stayed at 0.021 and the index drifted from 2030, reaching about 14% below the series it lags by 2039. The end year is now derived from the source rather than written down twice. Start years are unchanged, so historic values are untouched. The only consumer is housing_service_charges, which nothing consumes in turn, so no tax or benefit result moves. Also fixes the lagged earnings parameter being constructed without the .obr in its name, which propagated into the mirrored index node, and a docs table that still credited CPI for uprating service charges. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Program reviewBase repository: PolicyEngine/policyengine-uk Source Documents
CriticalNone. Should AddressA1 — Assert the terminal lag year and value exactly (OPEN)Severity: SHOULD ADDRESS. Location: SuggestionsS1 — State or enforce the annual-January source contract (OPEN)Severity: SUGGESTION. Location: Evidence Gaps
Notes
Validation SummaryHead 93b1a0e: remote CI passed Test, Lint, docs, and smoke imports on Python 3.11–3.14. Local combined bounded pytest run passed 10 tests in 5.94s (wrapper 6.98s); focused diagnostic passed and imported from the detached snapshot. Policy review validated 5 official originals (3 HTML, 2 XLSX), with 0 PDFs/renders and 1 unresolved material reconciliation gap. Code role 9m51s; policy role 11m38s. TimingNot measured. Review SeverityCOMMENT. Open findings: 0 critical, 1 should address, 1 suggestions. |
The previous assertion only required the lag to reach its source's last year. Because a parameter carries its terminal value forward, a lag that stopped a year early still answered correctly at every year tested, so the off-by-one this file exists to prevent would have passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Thanks — A1 is a fair catch and it's now fixed in the latest commit. A1. You're right that the old assertion was too weak: assert _last_year(lagged) == last_source_year + 1
assert lagged(str(last_source_year + 1)) == source(str(last_source_year))Confirmed it catches the case: changing the helper's On the unresolved reconciliation. The 2031–2038 average earnings values aren't introduced by this PR — they're the existing That makes the reconciliation question real but separate: if those values don't match the current OBR workbook, that's a defect in the source parameter and it affects everything uprated off average earnings, not just the lag. Happy to open an issue for it — I'd rather not fold a source-data revision into a fix for the freezing, since they'd want different reviewers and different evidence. Suites: 1,140 YAML policy tests, 215 pytest, ruff clean. |
lag_cpi.pyandlag_average_earnings.pyboth built their series with a hardcodedrange(..., 2030), so values stopped at 2029. A parameter carries its final value forward rather than raising, so nothing failed past that point — the series just held a stale growth rate, andcreate_economic_assumption_indicescompounded that stale rate into the index.Spotted by @edward-mcpherson.
CPI: harmless today
The OBR CPI series is flat at 2% from 2027, so the carried-forward value is exactly what the loop would have produced. Patching the old range to 2045 and diffing
yoy_growth.obr.lagged_cpi,indices.obr.lagged_cpiandgov.benefit_uprating_cpiacross 2024–2050 gives zero differing years. Bumping the number would have been a no-op.Average earnings: already wrong
Earnings growth is not flat — it ramps from 0.021 in 2028 to 0.0383 by 2036 — so the lagged series stayed at 0.021 and the index diverged from 2030:
indices.obr.lagged_average_earningsAbout 14% low by 2039.
No modelled outcome moves. The only consumer is
housing_service_charges, and nothing consumes that in turn. Running three households at 2039 and computing every year-period variable, exactly one changes —housing_service_chargesitself — andhousehold_net_incomeis bit-identical. This is a correctness fix to an input series, not a change in results.The fix
The end year is derived from the source series instead of being written down a second time. Start years are unchanged (2010 for CPI, 2022 for earnings), so historic values are untouched — deriving the start too would have pulled earnings back to 2010 and moved the index base, which is a bigger change than the bug warrants.
Two smaller things in the same area:
Parameterwas constructed asgov.economic_assumptions.yoy_growth.lagged_average_earnings, missing the.obr, while being added as a child ofobr. The mismatch propagated into the mirrored index node. Harmless for tree access, but.nameis what gets exported to metadata and what a name-keyed reform would target.docs/book/validation/hbai.mdstill said service charges were uprated by CPI.Known limitation, not addressed here
create_economic_assumption_indicesloopsrange(start_year + 1, 2040), so every index flatlines at 2039 regardless of how far its source runs. Worth a separate look.Tests
test_lagged_series.pyasserts each lagged series reaches its source's last year, equals the source lagged one year at 2030/2035/2039, that the earnings index keeps growing, and that the parameter names match their position in the tree.Full suite: 1,140 YAML policy tests, 211 pytest, ruff clean.