From 06aeafec0bf55c72a059de1361edf5c54a665ec4 Mon Sep 17 00:00:00 2001 From: Vahid Ahmadi Date: Wed, 27 May 2026 11:57:43 +0200 Subject: [PATCH 1/4] Document future-date policy announcements (#636) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #636 (2022) asked for an explicit way to encode announced-but-not-yet- implemented policy changes, citing the Truss "growth plan" tax cuts as the motivating example. The feature has been live the whole time — the date-keyed parameter values mechanism handles it natively — but it wasn't documented. Persist the pattern in-repo at docs/book/policy/future-date-announcements.md covering: - the mechanism (parameter values keyed by effective date, latest on or before the query date wins), - the three common patterns: * announced-then-reversed (Truss mini-budget): two rows, one at announcement date with the announced value, one at reversal date with the actual landing value; * phased-in changes: one row per phase with separate references (NI thresholds through the Health & Social Care Levy + April 2024 cuts as a worked example); * announced abolitions: limit value up to the effective date, then sentinel (.inf or 0) from that date onwards (Budget 2025 two-child limit removal as the cross-link), - best practices: cite every future-dated row, don't extrapolate beyond what was announced, use policy_date queries for back-cast scenarios, - distinction from gov.contrib.* reform parameters (uncertain / hypothetical) vs baseline future-dated rows (announced policy). --- changelog.d/636.md | 1 + docs/book/policy/future-date-announcements.md | 122 ++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 changelog.d/636.md create mode 100644 docs/book/policy/future-date-announcements.md diff --git a/changelog.d/636.md b/changelog.d/636.md new file mode 100644 index 0000000000..887a5e1ff6 --- /dev/null +++ b/changelog.d/636.md @@ -0,0 +1 @@ +- Add a documentation page at `docs/book/policy/future-date-announcements.md` explaining how PolicyEngine UK encodes announced-but-not-yet-implemented policy changes via date-keyed parameter values, with patterns for announced-then-reversed cases (Truss mini-budget), phased-in changes, announced abolitions (Budget 2025 two-child limit removal), and best practices for citing announcements. diff --git a/docs/book/policy/future-date-announcements.md b/docs/book/policy/future-date-announcements.md new file mode 100644 index 0000000000..4d13c43a64 --- /dev/null +++ b/docs/book/policy/future-date-announcements.md @@ -0,0 +1,122 @@ +# Future-dating policy announcements + +PolicyEngine UK supports modelling policy changes that have been +**announced but not yet implemented**, or that take effect at a known +future date. The mechanism is the same one that handles legislated rate +schedules: parameters in `policyengine_uk/parameters/` carry a list of +values keyed by effective date. + +This was originally requested in [#636](https://github.com/PolicyEngine/policyengine-uk/issues/636) +in the context of the Truss "growth plan" announcements in autumn 2022. +The feature has been live and well-exercised since the model moved to +date-keyed parameter values; this page documents the current usage +pattern so that future analyses don't need to reinvent it. + +## How it works + +Every parameter is a `values:` map from effective-date to value. When +the simulation queries a parameter for a given period, the model picks +the value whose date is the **latest one on or before** the queried +date. + +Example from +[`gov/hmrc/income_tax/allowances/personal_allowance/amount.yaml`](../../../policyengine_uk/parameters/gov/hmrc/income_tax/allowances/personal_allowance/amount.yaml): + +```yaml +values: + ... + 2027-04-06: 12_570 + 2030-04-06: + value: 12_570 + metadata: + reference: + - title: Autumn Budget 2026 — PA freeze extended to 2030-31 + href: https://www.gov.uk/government/publications/autumn-budget-2026 +``` + +The 2030-04-06 entry encodes a **future-dated** policy decision (the +PA freeze extension). Until that date is reached in real time, querying +`personal_allowance.amount(period="2029")` returns £12,570 from the +2027-04-06 row; from `period="2030"` onwards the 2030-04-06 row's value +applies. + +## Patterns for common cases + +### Announced but reversed before taking effect + +Some announcements are walked back before the effective date — the +Truss "growth plan" basic-rate cut to 19% (announced September 2022, +reversed October 2022) is the canonical example. To represent this +honestly: + +1. Add the announced value at the announced effective date with a + reference to the announcement. +2. Add a *second* row at the reversal date with the original value (or + the actual landing value) and a reference to the reversal + announcement. + +This means a date-stamped baseline (e.g. `policy_date="2022-09-28"`) +will show the reform, and a later date-stamped baseline will show the +reversal. PolicyEngine UK supports this kind of "what did the model +think the world looked like on date X" query. + +### Phased-in or staggered changes + +Phased rate changes are the most common future-dating pattern. Each +phase is a row at its effective date with a separate `reference`. The +`national_insurance/class_1/thresholds/primary_threshold.yaml` +trajectory through the 2022 Health and Social Care Levy and the +April 2024 cuts is a good worked example. + +### Announced abolitions + +For an abolition that hasn't yet taken effect (e.g. the [2025 Budget +two-child limit removal](../validation/child-poverty-tcl.md)): + +- The relevant parameter (`gov.dwp.universal_credit.elements.child.limit.child_count`) + has its limiting value (2) up to the announced effective date + (2026-04-06) and `.inf` from that date onwards. +- Until April 2026, queries return 2 and the model behaves as if the + limit is in force. +- From April 2026, queries return `.inf` and the limit silently + vanishes from the UC child element calculation. + +The same pattern applies in reverse for sunset clauses (a Cost-of-Living +Payment whose value goes back to zero at a stated date, see +[`changelog/609.md`](https://github.com/PolicyEngine/policyengine-uk/blob/main/changelog.d/609.md)). + +## Best practices + +1. **Cite the announcement in `metadata.reference`** at every + future-dated row. The convention through the repo is to include both + the press-release URL and the formal regulation / statutory + instrument once it's published — see the multi-row references on + `national_insurance/class_1/rates/employer.yaml` for an example. +2. **Don't extrapolate**. Each future-dated row should reflect a + specific announcement. If a parameter would otherwise drift via + uprating, let `policyengine-core`'s uprating handle that automatically + — adding a "frozen forever" row is a policy assertion that needs its + own reference. +3. **Use the `policy_date` query** for back-cast scenarios. If you need + to recover "what the model thought the world looked like as of + 2022-09-30" (post-mini-budget, pre-reversal), pass that date when + constructing the simulation. + +## What this isn't + +This is not the same as **scenario analysis** or **policy reform** +parameters under `gov.contrib.*`. Those are reform-side toggles for +hypothetical changes the user wants to evaluate; future-dated parameter +rows under `gov.hmrc.*`, `gov.dwp.*` etc. are **baseline** policy +representing what is actually announced. + +If a future change is uncertain or contested, model it under +`gov.contrib.*` until it's announced; once announced, move it into the +baseline tree with an effective-date row. + +## References + +- Issue [#636](https://github.com/PolicyEngine/policyengine-uk/issues/636) — original Truss-era future-dating ask. +- PolicyEngine blog post: [Tax cuts in Prime Minister Truss's growth plan 2022](https://blog.policyengine.org/tax-cuts-in-prime-minister-trusss-growth-plan-2022-a4a862892dc1) — worked example of modelling an announcement before reversal. +- [PolicyEngine Core date-keyed parameter API](https://github.com/PolicyEngine/policyengine-core) — underlying mechanism. +- Validation example using a real future-dated change: [child-poverty-tcl.md](../validation/child-poverty-tcl.md) (#1398). From 66a9115cde15fcb99188f9ad4122e90ffcb01ee0 Mon Sep 17 00:00:00 2001 From: vahid-ahmadi Date: Thu, 8 Oct 2026 15:49:31 +0100 Subject: [PATCH 2/4] Refresh the page against current main and Microcosm UK; type the changelog fragment Co-Authored-By: Claude Opus 5.5 --- changelog.d/636.added.md | 1 + changelog.d/636.md | 1 - docs/book/policy/future-date-announcements.md | 61 +++++++++++-------- 3 files changed, 36 insertions(+), 27 deletions(-) create mode 100644 changelog.d/636.added.md delete mode 100644 changelog.d/636.md diff --git a/changelog.d/636.added.md b/changelog.d/636.added.md new file mode 100644 index 0000000000..aefcb63f8e --- /dev/null +++ b/changelog.d/636.added.md @@ -0,0 +1 @@ +Added a page (docs/book/policy/future-date-announcements.md) on encoding announced policy changes as date-keyed parameter values, and how those dates become model-year values. diff --git a/changelog.d/636.md b/changelog.d/636.md deleted file mode 100644 index 887a5e1ff6..0000000000 --- a/changelog.d/636.md +++ /dev/null @@ -1 +0,0 @@ -- Add a documentation page at `docs/book/policy/future-date-announcements.md` explaining how PolicyEngine UK encodes announced-but-not-yet-implemented policy changes via date-keyed parameter values, with patterns for announced-then-reversed cases (Truss mini-budget), phased-in changes, announced abolitions (Budget 2025 two-child limit removal), and best practices for citing announcements. diff --git a/docs/book/policy/future-date-announcements.md b/docs/book/policy/future-date-announcements.md index 4d13c43a64..8784fb5a92 100644 --- a/docs/book/policy/future-date-announcements.md +++ b/docs/book/policy/future-date-announcements.md @@ -14,31 +14,40 @@ pattern so that future analyses don't need to reinvent it. ## How it works -Every parameter is a `values:` map from effective-date to value. When -the simulation queries a parameter for a given period, the model picks -the value whose date is the **latest one on or before** the queried -date. +Every parameter is a `values:` map from effective date to value. When +the tax-benefit system is built, `convert_to_fiscal_year_parameters` +(`policyengine_uk/utils/parameters.py`) turns each parameter into one +value per model year, where model year 2026 is the fiscal year 6 April +2026 to 5 April 2027: + +- by default, the value in force on 30 April of that year; +- for parameters with `fiscal_year_blend: true` in their metadata (fuel + duty, for example), the day-weighted average over the fiscal year, so a + change part-way through the year counts for the share of the year it + applies; +- parameters with `preserve_calendar_dates: true` keep their statutory + dates, and their formulas annualise explicitly. Example from -[`gov/hmrc/income_tax/allowances/personal_allowance/amount.yaml`](../../../policyengine_uk/parameters/gov/hmrc/income_tax/allowances/personal_allowance/amount.yaml): +[`gov/hmrc/income_tax/allowances/personal_allowance/amount.yaml`](https://github.com/PolicyEngine/policyengine-uk/blob/main/policyengine_uk/parameters/gov/hmrc/income_tax/allowances/personal_allowance/amount.yaml): ```yaml values: ... + 2021-04-06: 12_570 2027-04-06: 12_570 2030-04-06: value: 12_570 metadata: reference: - - title: Autumn Budget 2026 — PA freeze extended to 2030-31 - href: https://www.gov.uk/government/publications/autumn-budget-2026 + - title: OBR Economic and Fiscal Outlook November 2025 + href: https://obr.uk/efo/economic-and-fiscal-outlook-november-2025/ ``` -The 2030-04-06 entry encodes a **future-dated** policy decision (the -PA freeze extension). Until that date is reached in real time, querying -`personal_allowance.amount(period="2029")` returns £12,570 from the -2027-04-06 row; from `period="2030"` onwards the 2030-04-06 row's value -applies. +The 2030-04-06 row records a future-dated decision: the freeze extended +to 2030-31 at the 2025 Budget. Without it, uprating would move the +allowance from 2028 onwards; with it, model years up to 2030 return +£12,570. ## Patterns for common cases @@ -55,10 +64,11 @@ honestly: the actual landing value) and a reference to the reversal announcement. -This means a date-stamped baseline (e.g. `policy_date="2022-09-28"`) -will show the reform, and a later date-stamped baseline will show the -reversal. PolicyEngine UK supports this kind of "what did the model -think the world looked like on date X" query. +The baseline then follows what was finally enacted. PolicyEngine UK has +no "policy as announced on date X" switch, so to score an announcement as +it stood before a reversal, apply it as a reform (see +the [excise duties page](../../engineering/excise-duties.md) for how +dated reforms are annualised). ### Phased-in or staggered changes @@ -76,14 +86,13 @@ two-child limit removal](../validation/child-poverty-tcl.md)): - The relevant parameter (`gov.dwp.universal_credit.elements.child.limit.child_count`) has its limiting value (2) up to the announced effective date (2026-04-06) and `.inf` from that date onwards. -- Until April 2026, queries return 2 and the model behaves as if the - limit is in force. -- From April 2026, queries return `.inf` and the limit silently - vanishes from the UC child element calculation. +- Model year 2025 (fiscal year 2025-26) returns 2, so the limit applies. +- From model year 2026 the value is `.inf`, so the limit no longer + restricts the UC child element. The same pattern applies in reverse for sunset clauses (a Cost-of-Living Payment whose value goes back to zero at a stated date, see -[`changelog/609.md`](https://github.com/PolicyEngine/policyengine-uk/blob/main/changelog.d/609.md)). +[`changelog.d/609.md`](https://github.com/PolicyEngine/policyengine-uk/blob/main/changelog.d/609.md)). ## Best practices @@ -97,10 +106,10 @@ Payment whose value goes back to zero at a stated date, see uprating, let `policyengine-core`'s uprating handle that automatically — adding a "frozen forever" row is a policy assertion that needs its own reference. -3. **Use the `policy_date` query** for back-cast scenarios. If you need - to recover "what the model thought the world looked like as of - 2022-09-30" (post-mini-budget, pre-reversal), pass that date when - constructing the simulation. +3. **Check how the parameter is annualised.** A change dated part-way + through a fiscal year only counts for part of the year if the + parameter has `fiscal_year_blend: true`; otherwise the model year takes + the value in force on 30 April. ## What this isn't @@ -117,6 +126,6 @@ baseline tree with an effective-date row. ## References - Issue [#636](https://github.com/PolicyEngine/policyengine-uk/issues/636) — original Truss-era future-dating ask. -- PolicyEngine blog post: [Tax cuts in Prime Minister Truss's growth plan 2022](https://blog.policyengine.org/tax-cuts-in-prime-minister-trusss-growth-plan-2022-a4a862892dc1) — worked example of modelling an announcement before reversal. +- PolicyEngine research: [Tax cuts in Prime Minister Truss's growth plan](https://www.policyengine.org/uk/research/tax-cuts-in-prime-minister-trusss-growth-plan) — worked example of modelling an announcement before reversal. - [PolicyEngine Core date-keyed parameter API](https://github.com/PolicyEngine/policyengine-core) — underlying mechanism. - Validation example using a real future-dated change: [child-poverty-tcl.md](../validation/child-poverty-tcl.md) (#1398). From dfe3e086713a9656941d44a24f054c2462402540 Mon Sep 17 00:00:00 2001 From: vahid-ahmadi Date: Mon, 12 Oct 2026 11:08:51 +0200 Subject: [PATCH 3/4] Bring the future-date announcements page up to date with main The personal allowance freeze to 2030-31 is now enacted by Finance Act 2026 s. 10, with s. 57 indexation from 2031-32. Use the employee NI main rate as the phased example, link parameter files instead of a changelog fragment and a validation page that is not on main, and drop em dashes. Co-Authored-By: Claude Opus 5.5 --- docs/book/policy/future-date-announcements.md | 68 +++++++++++-------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/docs/book/policy/future-date-announcements.md b/docs/book/policy/future-date-announcements.md index 8784fb5a92..b0649e838b 100644 --- a/docs/book/policy/future-date-announcements.md +++ b/docs/book/policy/future-date-announcements.md @@ -34,26 +34,32 @@ Example from ```yaml values: ... - 2021-04-06: 12_570 - 2027-04-06: 12_570 + 2021-04-06: + value: 12_570 + ... + 2027-04-06: + value: 12_570 + ... 2030-04-06: value: 12_570 metadata: reference: - - title: OBR Economic and Fiscal Outlook November 2025 - href: https://obr.uk/efo/economic-and-fiscal-outlook-november-2025/ + - title: Finance Act 2021 s. 5 (2), as amended by Finance Act 2026 s. 10 (3) + href: https://www.legislation.gov.uk/ukpga/2026/11/section/10 ``` -The 2030-04-06 row records a future-dated decision: the freeze extended -to 2030-31 at the 2025 Budget. Without it, uprating would move the -allowance from 2028 onwards; with it, model years up to 2030 return -£12,570. +The 2030-04-06 row records a future-dated decision: the freeze to +2030-31, announced at the 2025 Budget and since enacted by Finance Act +2026 s. 10. The file has no uprating. From the tax year after its last +row, `policyengine_uk/parameters/gov/hmrc/create_threshold_indexation.py` +applies the Income Tax Act 2007 s. 57 CPI indexation year by year. So +model years up to 2030 return £12,570, and indexation starts in 2031. ## Patterns for common cases ### Announced but reversed before taking effect -Some announcements are walked back before the effective date — the +Some announcements are walked back before the effective date. The Truss "growth plan" basic-rate cut to 19% (announced September 2022, reversed October 2022) is the canonical example. To represent this honestly: @@ -74,14 +80,16 @@ dated reforms are annualised). Phased rate changes are the most common future-dating pattern. Each phase is a row at its effective date with a separate `reference`. The -`national_insurance/class_1/thresholds/primary_threshold.yaml` -trajectory through the 2022 Health and Social Care Levy and the -April 2024 cuts is a good worked example. +`national_insurance/class_1/rates/employee/main.yaml` trajectory is a +good worked example: 13.25% from April 2022 under the Health and Social +Care Levy, back to 12% from November 2022 when the levy was repealed, +then the cuts to 10% in January 2024 and 8% in April 2024. ### Announced abolitions -For an abolition that hasn't yet taken effect (e.g. the [2025 Budget -two-child limit removal](../validation/child-poverty-tcl.md)): +For an abolition announced ahead of its effective date (for example the +2025 Budget removal of the two-child limit, in +[`gov/dwp/universal_credit/elements/child/limit/child_count.yaml`](https://github.com/PolicyEngine/policyengine-uk/blob/main/policyengine_uk/parameters/gov/dwp/universal_credit/elements/child/limit/child_count.yaml)): - The relevant parameter (`gov.dwp.universal_credit.elements.child.limit.child_count`) has its limiting value (2) up to the announced effective date @@ -90,22 +98,24 @@ two-child limit removal](../validation/child-poverty-tcl.md)): - From model year 2026 the value is `.inf`, so the limit no longer restricts the UC child element. -The same pattern applies in reverse for sunset clauses (a Cost-of-Living -Payment whose value goes back to zero at a stated date, see -[`changelog.d/609.md`](https://github.com/PolicyEngine/policyengine-uk/blob/main/changelog.d/609.md)). +The same pattern applies in reverse to one-off or time-limited +payments. The means-tested Cost-of-Living Payment in +[`gov/treasury/cost_of_living_support/means_tested_households/amount.yaml`](https://github.com/PolicyEngine/policyengine-uk/blob/main/policyengine_uk/parameters/gov/treasury/cost_of_living_support/means_tested_households/amount.yaml) +is £650 from 2022, £900 from 2023 and goes back to zero from +2024-01-01. ## Best practices 1. **Cite the announcement in `metadata.reference`** at every - future-dated row. The convention through the repo is to include both - the press-release URL and the formal regulation / statutory - instrument once it's published — see the multi-row references on - `national_insurance/class_1/rates/employer.yaml` for an example. + future-dated row. Where possible, cite both the announcement and + the legislation that enacts it once it is published. The 2022-04-06 + row of `national_insurance/class_1/rates/employee/main.yaml`, for + example, cites both the Health and Social Care Levy Act 2021 and the + Budget document. 2. **Don't extrapolate**. Each future-dated row should reflect a - specific announcement. If a parameter would otherwise drift via - uprating, let `policyengine-core`'s uprating handle that automatically - — adding a "frozen forever" row is a policy assertion that needs its - own reference. + specific announcement. If a parameter would otherwise move with + uprating, let the uprating handle it. Adding a "frozen forever" row is + a policy assertion that needs its own reference. 3. **Check how the parameter is annualised.** A change dated part-way through a fiscal year only counts for part of the year if the parameter has `fiscal_year_blend: true`; otherwise the model year takes @@ -125,7 +135,7 @@ baseline tree with an effective-date row. ## References -- Issue [#636](https://github.com/PolicyEngine/policyengine-uk/issues/636) — original Truss-era future-dating ask. -- PolicyEngine research: [Tax cuts in Prime Minister Truss's growth plan](https://www.policyengine.org/uk/research/tax-cuts-in-prime-minister-trusss-growth-plan) — worked example of modelling an announcement before reversal. -- [PolicyEngine Core date-keyed parameter API](https://github.com/PolicyEngine/policyengine-core) — underlying mechanism. -- Validation example using a real future-dated change: [child-poverty-tcl.md](../validation/child-poverty-tcl.md) (#1398). +- Issue [#636](https://github.com/PolicyEngine/policyengine-uk/issues/636): the original request, from the Truss growth plan period. +- PolicyEngine research: [Tax cuts in Prime Minister Truss's growth plan](https://www.policyengine.org/uk/research/tax-cuts-in-prime-minister-trusss-growth-plan), a worked example of modelling an announcement before its reversal. +- [PolicyEngine Core](https://github.com/PolicyEngine/policyengine-core): the date-keyed parameter mechanism underneath. +- `convert_to_fiscal_year_parameters` in `policyengine_uk/utils/parameters.py`: how dated values become model-year values. From a60894ff7954eb28ce602ef10a32522f47031d3b Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 23 Oct 2026 01:58:48 -0400 Subject: [PATCH 4/4] Correct the future-dated announcements page against main The page reached main through #2386 (with d7ed2889a's reversal fix) and its changelog line shipped in 2.143.0, so this drops the released .added fragment and corrects main's copy: - Parameters have been date-keyed since the 2020 initial import; the model did not "move to" them. - Fiscal-year conversion covers the gov tree for 2015 to 2040. - preserve_calendar_dates readers split the year themselves (excise.py). - The Class 1 employee rate file was refactored into employee_scale.yaml (0d8ed3631); the 13.25% came from HSCL Act 2021 s. 5, not the levy, and 12% returned for earnings paid from 6 November 2022 (Repeal Act s. 2). Without fiscal_year_blend the mid-year changes don't reach annual results (#1807). - The two-child limit removal is now enacted (2026 c. 13). - Link the scenarios guide's dated changes for reform annualisation. Co-Authored-By: Claude Opus 5.5 --- ...-date-announcements-corrections.changed.md | 1 + changelog.d/636.added.md | 1 - docs/book/policy/future-date-announcements.md | 67 ++++++++++++------- 3 files changed, 42 insertions(+), 27 deletions(-) create mode 100644 changelog.d/636-future-date-announcements-corrections.changed.md delete mode 100644 changelog.d/636.added.md diff --git a/changelog.d/636-future-date-announcements-corrections.changed.md b/changelog.d/636-future-date-announcements-corrections.changed.md new file mode 100644 index 0000000000..6edcc41afc --- /dev/null +++ b/changelog.d/636-future-date-announcements-corrections.changed.md @@ -0,0 +1 @@ +Corrected the future-dated announcements page (docs/book/policy/future-date-announcements.md): the Class 1 employee rate now lives in employee_scale.yaml, the levy and repeal wording, the 30 April caveat for mid-year NI changes, the preserve_calendar_dates wording, and a link to the dated-changes guide. diff --git a/changelog.d/636.added.md b/changelog.d/636.added.md deleted file mode 100644 index aefcb63f8e..0000000000 --- a/changelog.d/636.added.md +++ /dev/null @@ -1 +0,0 @@ -Added a page (docs/book/policy/future-date-announcements.md) on encoding announced policy changes as date-keyed parameter values, and how those dates become model-year values. diff --git a/docs/book/policy/future-date-announcements.md b/docs/book/policy/future-date-announcements.md index 8e91ec8ce7..e12b48e4d0 100644 --- a/docs/book/policy/future-date-announcements.md +++ b/docs/book/policy/future-date-announcements.md @@ -8,17 +8,18 @@ values keyed by effective date. This was originally requested in [#636](https://github.com/PolicyEngine/policyengine-uk/issues/636) in the context of the Truss "growth plan" announcements in autumn 2022. -The feature has been live and well-exercised since the model moved to -date-keyed parameter values; this page documents the current usage -pattern so that future analyses don't need to reinvent it. +Nothing new was needed: parameter values have been keyed by effective +date since the model began in 2020, and the parameter files already carry +many future-dated rows. This page documents the current usage pattern so +that future analyses don't need to reinvent it. ## How it works Every parameter is a `values:` map from effective date to value. When the tax-benefit system is built, `convert_to_fiscal_year_parameters` -(`policyengine_uk/utils/parameters.py`) turns each parameter into one -value per model year, where model year 2026 is the fiscal year 6 April -2026 to 5 April 2027: +(`policyengine_uk/utils/parameters.py`) turns each parameter under `gov` +into one value per model year from 2015 to 2040, where model year 2026 is +the fiscal year 6 April 2026 to 5 April 2027: - by default, the value in force on 30 April of that year; - for parameters with `fiscal_year_blend: true` in their metadata (fuel @@ -26,7 +27,9 @@ value per model year, where model year 2026 is the fiscal year 6 April change part-way through the year counts for the share of the year it applies; - parameters with `preserve_calendar_dates: true` keep their statutory - dates, and their formulas annualise explicitly. + dates, and the code that reads them handles the dates itself (the + alcohol and tobacco duty formulas, for example, split each fiscal year + into dated segments with `policyengine_uk/utils/excise.py`). Example from [`gov/hmrc/income_tax/allowances/personal_allowance/amount.yaml`](https://github.com/PolicyEngine/policyengine-uk/blob/main/policyengine_uk/parameters/gov/hmrc/income_tax/allowances/personal_allowance/amount.yaml): @@ -53,15 +56,16 @@ The 2030-04-06 row records a future-dated decision: the freeze to 2026 s. 10. The file has no uprating. From the tax year after its last row, `policyengine_uk/parameters/gov/hmrc/create_threshold_indexation.py` applies the Income Tax Act 2007 s. 57 CPI indexation year by year. So -model years up to 2030 return £12,570, and indexation starts in 2031. +model years 2021 to 2030 return £12,570, and indexation starts in 2031. ## Patterns for common cases ### Announced but reversed before taking effect Some announcements are walked back before the effective date. The -Truss "growth plan" basic-rate cut to 19% (announced September 2022, -reversed October 2022) is the canonical example. To represent this +basic-rate cut to 19%, which the Truss "growth plan" of 23 September 2022 +brought forward to April 2023 and which was reversed on 17 October 2022, +is the canonical example. To represent this honestly: 1. When the announcement is made, add the announced value at the @@ -78,23 +82,32 @@ honestly: The baseline then follows what was finally enacted. PolicyEngine UK has no "policy as announced on date X" switch, so to score an announcement as -it stood before a reversal, apply it as a reform (see -the [excise duties page](../../engineering/excise-duties.md) for how -dated reforms are annualised). +it stood before a reversal, apply it as a reform (see "Dated changes" in +the [scenarios guide](../usage/scenarios.md) for how dated reforms are +annualised, and the [excise duties page](../../engineering/excise-duties.md) +for duties that keep their calendar dates). ### Phased-in or staggered changes -Phased rate changes are the most common future-dating pattern. Each -phase is a row at its effective date with a separate `reference`. The -`national_insurance/class_1/rates/employee/main.yaml` trajectory is a -good worked example: 13.25% from April 2022 under the Health and Social -Care Levy, back to 12% from November 2022 when the levy was repealed, -then the cuts to 10% in January 2024 and 8% in April 2024. +Phased rate changes are a common future-dating pattern. Each phase is a +row at its effective date with a separate `reference`. The Class 1 +employee main rate (`gov.hmrc.national_insurance.class_1.employee_scale[1].rate`, +in `national_insurance/class_1/employee_scale.yaml`) is a good worked +example: 13.25% from April 2022 under the Health and Social Care Levy Act +2021 (a temporary 1.25 percentage point rise for 2022-23, ahead of a +separate levy due from April 2023), back to 12% for earnings paid from +6 November 2022 under the Health and Social Care Levy (Repeal) Act 2022, +then the cuts to 10% in January 2024 and 8% in April 2024. The rate has +no `fiscal_year_blend`, so each model year takes the 30 April rate: +13.25% for 2022, 12% for 2023 and 8% from 2024. The November 2022 and +January 2024 changes do not show in annual results +([#1807](https://github.com/PolicyEngine/policyengine-uk/issues/1807)). ### Announced abolitions For an abolition announced ahead of its effective date (for example the -2025 Budget removal of the two-child limit, in +removal of the two-child limit, announced at the 2025 Budget and since +enacted by the Universal Credit (Removal of Two Child Limit) Act 2026, in [`gov/dwp/universal_credit/elements/child/limit/child_count.yaml`](https://github.com/PolicyEngine/policyengine-uk/blob/main/policyengine_uk/parameters/gov/dwp/universal_credit/elements/child/limit/child_count.yaml)): - The relevant parameter (`gov.dwp.universal_credit.elements.child.limit.child_count`) @@ -115,17 +128,19 @@ is £650 from 2022, £900 from 2023 and goes back to zero from 1. **Cite the announcement in `metadata.reference`** at every future-dated row. Where possible, cite both the announcement and the legislation that enacts it once it is published. The 2022-04-06 - row of `national_insurance/class_1/rates/employee/main.yaml`, for - example, cites both the Health and Social Care Levy Act 2021 and the - Budget document. + row of the Class 1 employee main rate + (`national_insurance/class_1/employee_scale.yaml`), for example, cites + both the Health and Social Care Levy Act 2021 and the Budget document. 2. **Don't extrapolate**. Each future-dated row should reflect a specific announcement. If a parameter would otherwise move with uprating, let the uprating handle it. Adding a "frozen forever" row is a policy assertion that needs its own reference. 3. **Check how the parameter is annualised.** A change dated part-way through a fiscal year only counts for part of the year if the - parameter has `fiscal_year_blend: true`; otherwise the model year takes - the value in force on 30 April. + parameter has `fiscal_year_blend: true`, or has + `preserve_calendar_dates: true` and a formula that splits the year (as + the alcohol and tobacco duties do); otherwise the model year takes the + value in force on 30 April. ## What this isn't @@ -142,6 +157,6 @@ baseline tree with an effective-date row. ## References - Issue [#636](https://github.com/PolicyEngine/policyengine-uk/issues/636): the original request, from the Truss growth plan period. -- PolicyEngine research: [Tax cuts in Prime Minister Truss's growth plan](https://www.policyengine.org/uk/research/tax-cuts-in-prime-minister-trusss-growth-plan), a worked example of modelling an announcement before its reversal. +- PolicyEngine research: [Tax cuts in Prime Minister Truss's Growth Plan 2022](https://www.policyengine.org/uk/research/tax-cuts-in-prime-minister-trusss-growth-plan), a worked example of modelling an announcement before its reversal. - [PolicyEngine Core](https://github.com/PolicyEngine/policyengine-core): the date-keyed parameter mechanism underneath. - `convert_to_fiscal_year_parameters` in `policyengine_uk/utils/parameters.py`: how dated values become model-year values.