|
| 1 | +# Fiscal-year uprating: April-date alignment plan |
| 2 | + |
| 3 | +```{note} |
| 4 | +**Planning page.** PolicyEngine UK currently stores OBR growth indices |
| 5 | +at **January 1** dates and post-processes parameters to the fiscal year |
| 6 | +via `convert_to_fiscal_year_parameters`. This page captures the proposed |
| 7 | +move to **April 6** dates throughout, tracked under |
| 8 | +[#1437](https://github.com/PolicyEngine/policyengine-uk/issues/1437), |
| 9 | +and the on-the-fly alternative tracked under |
| 10 | +[#1436](https://github.com/PolicyEngine/policyengine-uk/issues/1436). |
| 11 | +This is a low-priority enhancement — the current approach is correct |
| 12 | +for annual analysis after PR #1435 extended the fiscal-year-conversion |
| 13 | +range out to 2040. |
| 14 | +``` |
| 15 | + |
| 16 | +## Current pipeline |
| 17 | + |
| 18 | +The model currently does three things in sequence: |
| 19 | + |
| 20 | +1. **OBR indices** live at January 1 dates in |
| 21 | + [`parameters/gov/economic_assumptions/yoy_growth.yaml`](../../../policyengine_uk/parameters/gov/economic_assumptions/yoy_growth.yaml) |
| 22 | + (annual calendar-year growth rates) and the cumulative indices are |
| 23 | + generated by |
| 24 | + [`create_economic_assumption_indices.py`](../../../policyengine_uk/parameters/gov/economic_assumptions/create_economic_assumption_indices.py) |
| 25 | + at January 1 dates (`f"{year}-01-01"`). |
| 26 | +2. **`uprate_parameters`** in `policyengine-core` writes uprated |
| 27 | + parameter values at the same dates as the uprating index, so all |
| 28 | + uprated parameters land at January 1. |
| 29 | +3. **`convert_to_fiscal_year_parameters`** in |
| 30 | + [`utils/parameters.py`](../../../policyengine_uk/utils/parameters.py) |
| 31 | + post-processes the parameter tree by sampling each parameter at |
| 32 | + April 30 and overwriting the whole calendar year with that value. |
| 33 | + This is what produces the fiscal-year-correct outputs PolicyEngine |
| 34 | + ships today. |
| 35 | + |
| 36 | +This works for annual simulations but has three downsides: |
| 37 | + |
| 38 | +- **Intra-year queries are wrong.** `param("2026-03-15")` returns the |
| 39 | + fiscal-year-2026 value when April 6 2026 hasn't happened yet; the |
| 40 | + intended pre-April-6 value is the fiscal-year-2025 value. |
| 41 | +- The fiscal-year conversion is a **post-process workaround**, not a |
| 42 | + correct upstream model. |
| 43 | +- Parameter `values_list` entries appear at January 1 dates even |
| 44 | + though the legislative change usually takes effect on April 6. |
| 45 | + |
| 46 | +## Proposed change (#1437): store at April 6 throughout |
| 47 | + |
| 48 | +Update each step of the pipeline so it speaks fiscal years natively: |
| 49 | + |
| 50 | +1. Move the dates in `yoy_growth.yaml` from `YYYY-01-01` to |
| 51 | + `YYYY-04-06` for every entry, ensuring the growth rate that applies |
| 52 | + between fiscal year T and fiscal year T+1 sits at the start of T+1. |
| 53 | +2. Update `create_economic_assumption_indices.py` to emit cumulative |
| 54 | + indices at April 6 dates. |
| 55 | +3. Confirm that `policyengine-core`'s `uprate_parameters` does not bake |
| 56 | + any January-1 assumption — if it does, parametrise the date. |
| 57 | +4. **Remove** `convert_to_fiscal_year_parameters` and its call site in |
| 58 | + [`tax_benefit_system.py`](../../../policyengine_uk/tax_benefit_system.py). |
| 59 | + Once steps 1-3 land, the natural answer at every date is already the |
| 60 | + fiscal-year-correct value. |
| 61 | + |
| 62 | +### What changes for users |
| 63 | + |
| 64 | +| Query | Today | After April-date move | |
| 65 | +|-------|-------|------------------------| |
| 66 | +| `param("2026")` (year-string) | Fiscal year 2026/27 value (post-process) | Fiscal year 2026/27 value (direct) | |
| 67 | +| `param("2026-03-15")` (pre-April 6) | Fiscal year 2025/26 value (correct) | Fiscal year 2025/26 value (correct) | |
| 68 | +| `param("2026-05-15")` (post-April 6) | Fiscal year 2025/26 value (**wrong**) | Fiscal year 2026/27 value (correct) | |
| 69 | +| `values_list` dates | January 1 | April 6 | |
| 70 | + |
| 71 | +The third row is the headline correctness gain. |
| 72 | + |
| 73 | +### Scope |
| 74 | + |
| 75 | +- Pure documentation/wiring change — **no household-impact values |
| 76 | + should move** for any annual query. |
| 77 | +- Regression tests must include both an annual-query and an |
| 78 | + April-30-style mid-year-query case so the next refactor can't |
| 79 | + reintroduce the off-by-three-months bug. |
| 80 | +- Reform-side parameters (legislated rate changes) should already be |
| 81 | + dated April 6 in their YAML files (most are); spot-check the |
| 82 | + exceptions during the migration. |
| 83 | + |
| 84 | +## On-the-fly alternative (#1436) |
| 85 | + |
| 86 | +The complementary issue [#1436](https://github.com/PolicyEngine/policyengine-uk/issues/1436) |
| 87 | +proposes the inverse: instead of moving every date to April 6, keep |
| 88 | +indices at January 1 and resolve the fiscal-year conversion lazily at |
| 89 | +query time. |
| 90 | + |
| 91 | +That would let the conversion stop being a 26-year pre-computation step |
| 92 | +at startup (the `range(2015, 2041)` loop in |
| 93 | +`convert_to_fiscal_year_parameters`) and remove the year-range |
| 94 | +upper-bound altogether. |
| 95 | + |
| 96 | +The two approaches are mutually exclusive: either we change the data to |
| 97 | +match the queries (#1437) or we change the resolver to translate |
| 98 | +queries to the data (#1436). Recommendation: |
| 99 | + |
| 100 | +- Prefer **#1437 (April dates)** for correctness and ergonomics — the |
| 101 | + underlying policy change happens on April 6, so the YAML should say |
| 102 | + April 6. |
| 103 | +- Consider **#1436 (on-the-fly)** if a refactor of |
| 104 | + `convert_to_fiscal_year_parameters` becomes hot-path for any specific |
| 105 | + user — it's primarily a startup-cost optimisation. |
| 106 | + |
| 107 | +## Sequencing |
| 108 | + |
| 109 | +1. Land #1437 first: it removes the post-process workaround and makes |
| 110 | + the YAML files self-explanatory. |
| 111 | +2. Once #1437 is live, evaluate whether the lazy approach in #1436 is |
| 112 | + still worth doing — most of its benefit is already captured by step 4 |
| 113 | + above ("remove `convert_to_fiscal_year_parameters`"). |
| 114 | + |
| 115 | +## References |
| 116 | + |
| 117 | +- Issue [#1437](https://github.com/PolicyEngine/policyengine-uk/issues/1437) — April-date alignment. |
| 118 | +- Issue [#1436](https://github.com/PolicyEngine/policyengine-uk/issues/1436) — on-the-fly conversion exploration. |
| 119 | +- PR [#1435](https://github.com/PolicyEngine/policyengine-uk/pull/1435) — extended the fiscal-year-conversion range to 2040. |
| 120 | +- [`growthfactors.md`](./growthfactors.md) — the underlying OBR growth rates. |
0 commit comments