From d45f7888c916ded5e9f7cf15a1bd0cca829054d1 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 28 Oct 2026 10:53:27 -0400 Subject: [PATCH] Keep the 5p fuel duty cut to 1 July 2027 (Budget 2026) Budget 2026 (paragraph 6.211; policy costings page 13) extends the 5p cut from 1 January 2027 to 1 July 2027. On that date rates rise by 5p and are uprated by RPI, then by RPI each 1 April from 2028. Other road fuels keep a proportionate cut to the same date. - petrol_and_diesel.yaml: 52.95p to 30 June 2027; the January and March 2027 steps are gone. The generator now uprates the 57.95p pre-cut rate by 2026 RPI on 1 July 2027, then each 1 April from 2028. - lpg.yaml, natural_gas.yaml: full rates from 1 July 2027. - Tests and the excise duty engineering note follow. Co-Authored-By: Claude Opus 5.5 --- changelog.d/ab26-fuel-duty.changed.md | 1 + docs/engineering/excise-duties.md | 50 ++++++----- .../fuel_duty/calculate_fuel_duty_rates.py | 51 +++++++----- .../parameters/gov/hmrc/fuel_duty/lpg.yaml | 7 +- .../gov/hmrc/fuel_duty/natural_gas.yaml | 7 +- .../gov/hmrc/fuel_duty/petrol_and_diesel.yaml | 83 +++++++++---------- .../fuel_duty/petrol_and_diesel_schedule.yaml | 23 +++-- .../gov/hmrc/fuel_duty/road_fuel_gases.yaml | 25 ++++-- .../tests/test_fuel_duty_schedule.py | 63 ++++++++------ 9 files changed, 173 insertions(+), 137 deletions(-) create mode 100644 changelog.d/ab26-fuel-duty.changed.md diff --git a/changelog.d/ab26-fuel-duty.changed.md b/changelog.d/ab26-fuel-duty.changed.md new file mode 100644 index 0000000000..c39e67a335 --- /dev/null +++ b/changelog.d/ab26-fuel-duty.changed.md @@ -0,0 +1 @@ +Keep the 5p fuel duty cut to 1 July 2027, as announced at Budget 2026. On that date petrol and diesel return to 57.95p uprated by 2026 RPI growth, then rise by RPI each 1 April from 2028; LPG and natural gas return to their full rates. diff --git a/docs/engineering/excise-duties.md b/docs/engineering/excise-duties.md index 2e353c04a9..b65fe28607 100644 --- a/docs/engineering/excise-duties.md +++ b/docs/engineering/excise-duties.md @@ -18,11 +18,11 @@ parameter history starts on 1 August 2023; the preceding alcohol regime and a complete 2023/24 calculation are outside this implementation. Tobacco rates start on 22 November 2023. The new alcohol, tobacco and car formulas return their default zero before 2024; that is an unmodelled period, not a statutory exemption. -Gas duty covers 2022/23 onwards and includes the 1 January 2027 and 1 March 2027 -increases. SI 2026/555 removed the September 2026 step announced at Budget 2025 -and moved the December 2026 step to 1 January 2027. Rates after the last dated -entry stay constant; the gas parameters do not forecast future inflation -increases. +Gas duty covers 2022/23 onwards. SI 2026/555 removed the September 2026 step +announced at Budget 2025 and moved the December 2026 step to 1 January 2027. +Budget 2026 then kept the proportionate cut to 1 July 2027, when the gas rates +return to their full levels. Rates after the last dated entry stay constant; +the gas parameters do not forecast future inflation increases. For alcohol and tobacco, a model year such as `2026` represents 6 April 2026 to 5 April 2027. Quantities and cigarette retail prices are constant across that @@ -154,32 +154,36 @@ matching the legislation. No universal litre-to-kilogram density is assumed. The existing `fuel_duty` output includes these charges. Rural fuel duty relief continues to apply only to petrol and diesel. -From 1 March 2027 onwards, the gas parameters hold LPG at £0.3161/kg and natural -gas at £0.2470/kg. This implementation stops at the last numerical rates in the -HMRC 2026-to-2027 schedule and does not extrapolate gas rates using an inflation -forecast. This is a modelling limitation: HMRC states that RPI uprating resumes -from 2027/28. The petrol and diesel rate includes forecast RPI uprating each -1 April from 2027, so its rates rise while the gas rates stay flat. Later-year gas duty is therefore lower than it would be -under the same uprating assumption. Analyses beyond March 2027 that require -consistent fuel-duty forecasts must supply an uprated gas-rate path explicitly. +From 1 July 2027 onwards, the gas parameters hold LPG at £0.3161/kg and natural +gas at £0.2470/kg, the full rates before the 2022 cut. This implementation does +not extrapolate gas rates using an inflation forecast. This is a modelling +limitation: Budget 2026's costing says fuel duty rates are uprated by RPI from +1 July 2027. The petrol and diesel rate includes that forecast uprating, so its +rates rise while the gas rates stay flat. Later-year gas duty is therefore lower +than it would be under the same uprating assumption. Analyses beyond June 2027 +that require consistent fuel-duty forecasts must supply an uprated gas-rate path +explicitly. Sources: [HMRC fuel duty rates 2026 to 2027](https://www.gov.uk/government/publications/fuel-duty-rates-for-2026-to-2027/fuel-duty-rates-2026-to-2027) (RPI uprating from 2027/28), [HMRC amended fuel duty rates 2026 to 2027](https://www.gov.uk/government/publications/amended-fuel-duty-rates-for-2026-to-2027/amended-fuel-duty-rates-2026-to-2027) -and [SI 2026/164 as amended by SI 2026/555](https://www.legislation.gov.uk/uksi/2026/164). +[SI 2026/164 as amended by SI 2026/555](https://www.legislation.gov.uk/uksi/2026/164) +and [Budget 2026 policy costings, page 13](https://assets.publishing.service.gov.uk/media/6ae1883fe4367e5c8b0467df/AB26_Policy_Costing.pdf#page=13). ## Petrol and diesel rate The main rate on unleaded petrol and diesel is keyed to each statutory change: -52.95p a litre from 23 March 2022, continued by annual orders to 31 December 2026; -55.95p from 1 January 2027 (SI 2026/164 as amended by SI 2026/555); and the Hydrocarbon Oil -Duties Act 1979 rate of 57.95p from 1 March 2027. Budget 2025 states that rates -will be uprated by RPI from April 2027, and HM Treasury's Budget 2025 costing -dates this from 1 April 2027. That uprating is policy, not law: HMRC's amended -note of 22 May 2026 says final rates will be confirmed at Budget 2026. No -official source names the RPI measure. The model applies the previous calendar -year's RPI growth from the OBR forecast in -`gov.economic_assumptions.yoy_growth.obr.rpi` on each 1 April, rounded to the +52.95p a litre from 23 March 2022, continued by annual orders to 31 December 2026 +(SI 2026/164 as amended by SI 2026/555). Those orders then stepped to 55.95p on +1 January 2027 and to the Hydrocarbon Oil Duties Act 1979 rate of 57.95p on +1 March 2027. Budget 2026 (paragraph 6.211) cancelled both steps and kept the +cut to 1 July 2027. Its costing (page 13) says: "On 1 July 2027, fuel duty rates +will increase by 5ppl and will be uprated in line with the Retail Prices Index +(RPI). From 1 April 2028, fuel duty rates will be uprated annually in line with +RPI." That is policy, not law. No official source names the RPI measure. The +model takes the 57.95p pre-cut rate and applies 2026 RPI growth on 1 July 2027, +then the previous calendar year's RPI growth on each 1 April from 2028, from the +OBR forecast in `gov.economic_assumptions.yoy_growth.obr.rpi`, rounded to the nearest hundredth of a penny. The steps to 1 April 2031 are written out by `calculate_fuel_duty_rates.py`, and a test fails when they fall out of date with the RPI forecast. Later steps come from the parameter's uprating metadata. diff --git a/policyengine_uk/parameters/gov/hmrc/fuel_duty/calculate_fuel_duty_rates.py b/policyengine_uk/parameters/gov/hmrc/fuel_duty/calculate_fuel_duty_rates.py index f0ec7ffa27..8e34c85736 100644 --- a/policyengine_uk/parameters/gov/hmrc/fuel_duty/calculate_fuel_duty_rates.py +++ b/policyengine_uk/parameters/gov/hmrc/fuel_duty/calculate_fuel_duty_rates.py @@ -1,11 +1,14 @@ """ Generates the forecast RPI uprating steps in petrol_and_diesel.yaml. -The statutory schedule ends with the Hydrocarbon Oil Duties Act 1979 rate of -57.95p from 1 March 2027. Budget 2025 states that rates will then be uprated -by RPI from April 2027. This script applies each previous calendar year's RPI -growth from economic_assumptions/yoy_growth.yaml on 1 April (the date in HM -Treasury's Budget 2025 costing), rounding to the nearest hundredth of a penny, and writes the results between the BEGIN and END +Budget 2026 keeps the 5p cut to 1 July 2027. Its policy costing (page 13) +says that "On 1 July 2027, fuel duty rates will increase by 5ppl and will be +uprated in line with the Retail Prices Index (RPI). From 1 April 2028, fuel +duty rates will be uprated annually in line with RPI." This script takes the +57.95p pre-cut rate (Finance Act 2011 s. 19), applies 2026 RPI growth on +1 July 2027, then each previous calendar year's RPI growth on 1 April from +2028, using economic_assumptions/yoy_growth.yaml. It rounds to the nearest +hundredth of a penny and writes the results between the BEGIN and END markers in petrol_and_diesel.yaml. Run it with --update whenever the RPI forecast changes; test_fuel_duty_schedule.py fails until you do. @@ -24,15 +27,19 @@ PARAMETER_FILE = FUEL_DUTY_DIR / "petrol_and_diesel.yaml" YOY_GROWTH_FILE = FUEL_DUTY_DIR.parents[1] / "economic_assumptions" / "yoy_growth.yaml" -# The last statutory rate, and the first and last 1 April steps written out. -# Later steps come from the parameter's uprating metadata. -STATUTORY_DATE = "2027-03-01" -FIRST_UPRATING_YEAR = 2027 +# The pre-cut rate the first step uprates, the date the cut ends, and the +# first and last 1 April steps written out. Later steps come from the +# parameter's uprating metadata. +BASE_DATE = "2011-03-23" +CUT_END_DATE = "2027-07-01" +FIRST_APRIL_YEAR = 2028 LAST_UPRATING_YEAR = 2031 BEGIN = " # BEGIN RPI forecast generated by calculate_fuel_duty_rates.py\n" END = " # END RPI forecast generated by calculate_fuel_duty_rates.py\n" PENNY_HUNDREDTH = Decimal("0.0001") +BUDGET_2026 = "https://assets.publishing.service.gov.uk/media/6ae1ec57bdb574cf1c9b3f12/E03668986_HMT_Budget_2026_ELAY__2_.pdf#page=138" +COSTING_2026 = "https://assets.publishing.service.gov.uk/media/6ae1883fe4367e5c8b0467df/AB26_Policy_Costing.pdf#page=13" def rpi_series() -> dict: @@ -46,24 +53,28 @@ def rpi_growth_by_year() -> dict: return {int(str(date)[:4]): Decimal(str(value)) for date, value in values.items()} -def statutory_rate() -> Decimal: +def base_rate() -> Decimal: with open(PARAMETER_FILE) as f: values = yaml.safe_load(f)["values"] - entry = {str(date): value for date, value in values.items()}[STATUTORY_DATE] + entry = {str(date): value for date, value in values.items()}[BASE_DATE] return Decimal(str(entry["value"] if isinstance(entry, dict) else entry)) def forecast_steps() -> list: - """(date, rate, previous rate, RPI growth) for each forecast 1 April step.""" + """(date, rate, previous rate, RPI growth) for each forecast step.""" growth = rpi_growth_by_year() - rate = statutory_rate() + dates = [CUT_END_DATE] + [ + f"{year}-04-01" for year in range(FIRST_APRIL_YEAR, LAST_UPRATING_YEAR + 1) + ] + rate = base_rate() steps = [] - for year in range(FIRST_UPRATING_YEAR, LAST_UPRATING_YEAR + 1): + for date in dates: + year = int(date[:4]) previous = rate rate = (previous * (1 + growth[year - 1])).quantize( PENNY_HUNDREDTH, rounding=ROUND_HALF_UP ) - steps.append((f"{year}-04-01", rate, previous, growth[year - 1])) + steps.append((date, rate, previous, growth[year - 1])) return steps @@ -79,10 +90,10 @@ def render(steps: list) -> str: f" note: Forecast. {previous} uprated by {int(date[:4]) - 1} RPI " f"growth of {growth * 100:.1f}%.", " reference:", - " - title: Budget 2025 (fuel duty uprated by RPI from April 2027)", - " href: https://www.gov.uk/government/publications/budget-2025-document/budget-2025-html", - " - title: HM Treasury, Budget 2025 policy costings, page 14 (from 1 April 2027)", - " href: https://assets.publishing.service.gov.uk/media/692872fd2a37784b16ecf676/Budget_2025-Policy_Costings.pdf#page=14", + " - title: Budget 2026, paragraph 6.211 (5p cut ends and RPI uprating from 1 July 2027)", + f" href: {BUDGET_2026}", + " - title: HM Treasury, Budget 2026 policy costings, page 13 (RPI on 1 July 2027, then each 1 April from 2028)", + f" href: {COSTING_2026}", f" - title: {forecast['title']} (RPI forecast)", f" href: {forecast['href']}", ] @@ -106,7 +117,7 @@ def main(): ) args = parser.parse_args() steps = forecast_steps() - print(f"{STATUTORY_DATE}: {statutory_rate()} (statutory)") + print(f"{BASE_DATE}: {base_rate()} (pre-cut rate)") for date, rate, previous, growth in steps: print(f"{date}: {rate} ({previous} x (1 + {growth}))") if args.update: diff --git a/policyengine_uk/parameters/gov/hmrc/fuel_duty/lpg.yaml b/policyengine_uk/parameters/gov/hmrc/fuel_duty/lpg.yaml index ff8911779c..d135be7f05 100644 --- a/policyengine_uk/parameters/gov/hmrc/fuel_duty/lpg.yaml +++ b/policyengine_uk/parameters/gov/hmrc/fuel_duty/lpg.yaml @@ -2,8 +2,7 @@ description: The UK levies road fuel duty on LPG at this rate per kilogram. values: '2021-01-01': 0.3161 '2022-03-23': 0.2888 - '2027-01-01': 0.3052 - '2027-03-01': 0.3161 + '2027-07-01': 0.3161 metadata: unit: currency-GBP period: year @@ -11,8 +10,10 @@ metadata: reference: - title: Hydrocarbon Oil Duties Act 1979, section 8 href: https://www.legislation.gov.uk/ukpga/1979/5/section/8 - - title: SI 2026/164, article 8, as amended by SI 2026/555 (1 January to 28 February 2027) + - title: SI 2026/164, as amended by SI 2026/555 (cut continued to 31 December 2026) href: https://www.legislation.gov.uk/uksi/2026/164 + - title: Budget 2026, paragraph 6.211, and policy costings page 13 (proportionate cut to other fuels to 1 July 2027; not yet law) + href: https://assets.publishing.service.gov.uk/media/6ae1883fe4367e5c8b0467df/AB26_Policy_Costing.pdf#page=13 - title: HMRC, Amended Fuel Duty rates 2026 to 2027 (22 May 2026) href: https://www.gov.uk/government/publications/amended-fuel-duty-rates-for-2026-to-2027/amended-fuel-duty-rates-2026-to-2027 fiscal_year_blend: true diff --git a/policyengine_uk/parameters/gov/hmrc/fuel_duty/natural_gas.yaml b/policyengine_uk/parameters/gov/hmrc/fuel_duty/natural_gas.yaml index e36135665e..54954c6f14 100644 --- a/policyengine_uk/parameters/gov/hmrc/fuel_duty/natural_gas.yaml +++ b/policyengine_uk/parameters/gov/hmrc/fuel_duty/natural_gas.yaml @@ -2,8 +2,7 @@ description: The UK levies road fuel duty on natural gas at this rate per kilogr values: '2021-01-01': 0.247 '2022-03-23': 0.2257 - '2027-01-01': 0.2385 - '2027-03-01': 0.247 + '2027-07-01': 0.247 metadata: unit: currency-GBP period: year @@ -11,8 +10,10 @@ metadata: reference: - title: Hydrocarbon Oil Duties Act 1979, section 8 href: https://www.legislation.gov.uk/ukpga/1979/5/section/8 - - title: SI 2026/164, article 8, as amended by SI 2026/555 (1 January to 28 February 2027) + - title: SI 2026/164, as amended by SI 2026/555 (cut continued to 31 December 2026) href: https://www.legislation.gov.uk/uksi/2026/164 + - title: Budget 2026, paragraph 6.211, and policy costings page 13 (proportionate cut to other fuels to 1 July 2027; not yet law) + href: https://assets.publishing.service.gov.uk/media/6ae1883fe4367e5c8b0467df/AB26_Policy_Costing.pdf#page=13 - title: HMRC, Amended Fuel Duty rates 2026 to 2027 (22 May 2026) href: https://www.gov.uk/government/publications/amended-fuel-duty-rates-for-2026-to-2027/amended-fuel-duty-rates-2026-to-2027 fiscal_year_blend: true diff --git a/policyengine_uk/parameters/gov/hmrc/fuel_duty/petrol_and_diesel.yaml b/policyengine_uk/parameters/gov/hmrc/fuel_duty/petrol_and_diesel.yaml index cf7d2a640f..0092c4d11f 100644 --- a/policyengine_uk/parameters/gov/hmrc/fuel_duty/petrol_and_diesel.yaml +++ b/policyengine_uk/parameters/gov/hmrc/fuel_duty/petrol_and_diesel.yaml @@ -68,7 +68,8 @@ values: href: https://www.legislation.gov.uk/uksi/2025/228/made # Budget 2025 scheduled steps on 1 September 2026, 1 December 2026 and # 1 March 2027. SI 2026/555 removed the September step and moved the - # December step to 1 January 2027. + # December step to 1 January 2027. Budget 2026 then cancelled the January + # and March 2027 steps, keeping the 5p cut to 1 July 2027. 2026-03-23: value: 0.5295 metadata: @@ -80,39 +81,37 @@ values: - title: HMRC, Amended Fuel Duty rates 2026 to 2027 (22 May 2026) href: https://www.gov.uk/government/publications/amended-fuel-duty-rates-for-2026-to-2027/amended-fuel-duty-rates-2026-to-2027 2027-01-01: - value: 0.5595 - metadata: - reference: - - title: SI 2026/164, article 8, as amended by SI 2026/555 (1 January to 28 February 2027) - href: https://www.legislation.gov.uk/uksi/2026/164 - - title: HMRC, Amended Fuel Duty rates 2026 to 2027 (22 May 2026) - href: https://www.gov.uk/government/publications/amended-fuel-duty-rates-for-2026-to-2027/amended-fuel-duty-rates-2026-to-2027 - 2027-03-01: - value: 0.5795 + value: 0.5295 metadata: - reference: - - title: Hydrocarbon Oil Duties Act 1979, section 6(1A) (no reduction order applies after 28 February 2027) - href: https://www.legislation.gov.uk/ukpga/1979/5/section/6 - - title: HMRC, Amended Fuel Duty rates 2026 to 2027 (22 May 2026) - href: https://www.gov.uk/government/publications/amended-fuel-duty-rates-for-2026-to-2027/amended-fuel-duty-rates-2026-to-2027 - # Forecast from April 2027. Budget 2025 states that rates will be uprated by - # RPI from April 2027, and HM Treasury's costing dates this from 1 April - # 2027. That is policy, not law: HMRC's amended note of 22 May 2026 says - # final rates will be confirmed at Budget 2026. No official source names the - # RPI measure. Each 1 April applies the previous calendar year's RPI growth - # from gov.economic_assumptions.yoy_growth.obr.rpi, rounding to the nearest + note: Budget 2026 policy, not yet law. The 5p cut runs to 1 July 2027 in place of SI 2026/164's steps to 55.95p on 1 January 2027 and 57.95p on 1 March 2027. + reference: + - title: Budget 2026, paragraph 6.211 (cut extended to 1 July 2027) + href: https://assets.publishing.service.gov.uk/media/6ae1ec57bdb574cf1c9b3f12/E03668986_HMT_Budget_2026_ELAY__2_.pdf#page=138 + - title: Budget 2026 policy costings, page 13 + href: https://assets.publishing.service.gov.uk/media/6ae1883fe4367e5c8b0467df/AB26_Policy_Costing.pdf#page=13 + - title: Budget 2026, Table 6.1, item 3 + href: https://assets.publishing.service.gov.uk/media/6ae187a2779378370fd80a9a/Table_6.1_and_6.2_Budget_2026_Policy_Decisions.xlsx + # Forecast from 1 July 2027. Budget 2026's costing: "On 1 July 2027, fuel + # duty rates will increase by 5ppl and will be uprated in line with the + # Retail Prices Index (RPI). From 1 April 2028, fuel duty rates will be + # uprated annually in line with RPI." That is policy, not law. No official + # source names the RPI measure. The 1 July 2027 step takes the 57.95p + # pre-cut rate (Finance Act 2011 s. 19) and applies 2026 RPI growth, the + # increase deferred from April 2027; each 1 April from 2028 applies the + # previous calendar year's RPI growth from + # gov.economic_assumptions.yoy_growth.obr.rpi, rounding to the nearest # hundredth of a penny. After the last entry, the uprating metadata below # continues the same April cadence. # BEGIN RPI forecast generated by calculate_fuel_duty_rates.py - 2027-04-01: + 2027-07-01: value: 0.5975 metadata: note: Forecast. 0.5795 uprated by 2026 RPI growth of 3.1%. reference: - - title: Budget 2025 (fuel duty uprated by RPI from April 2027) - href: https://www.gov.uk/government/publications/budget-2025-document/budget-2025-html - - title: HM Treasury, Budget 2025 policy costings, page 14 (from 1 April 2027) - href: https://assets.publishing.service.gov.uk/media/692872fd2a37784b16ecf676/Budget_2025-Policy_Costings.pdf#page=14 + - title: Budget 2026, paragraph 6.211 (5p cut ends and RPI uprating from 1 July 2027) + href: https://assets.publishing.service.gov.uk/media/6ae1ec57bdb574cf1c9b3f12/E03668986_HMT_Budget_2026_ELAY__2_.pdf#page=138 + - title: HM Treasury, Budget 2026 policy costings, page 13 (RPI on 1 July 2027, then each 1 April from 2028) + href: https://assets.publishing.service.gov.uk/media/6ae1883fe4367e5c8b0467df/AB26_Policy_Costing.pdf#page=13 - title: OBR EFO March 2026 (Table A.1) (RPI forecast) href: https://obr.uk/efo/economic-and-fiscal-outlook-march-2026/ 2028-04-01: @@ -120,10 +119,10 @@ values: metadata: note: Forecast. 0.5975 uprated by 2027 RPI growth of 3.0%. reference: - - title: Budget 2025 (fuel duty uprated by RPI from April 2027) - href: https://www.gov.uk/government/publications/budget-2025-document/budget-2025-html - - title: HM Treasury, Budget 2025 policy costings, page 14 (from 1 April 2027) - href: https://assets.publishing.service.gov.uk/media/692872fd2a37784b16ecf676/Budget_2025-Policy_Costings.pdf#page=14 + - title: Budget 2026, paragraph 6.211 (5p cut ends and RPI uprating from 1 July 2027) + href: https://assets.publishing.service.gov.uk/media/6ae1ec57bdb574cf1c9b3f12/E03668986_HMT_Budget_2026_ELAY__2_.pdf#page=138 + - title: HM Treasury, Budget 2026 policy costings, page 13 (RPI on 1 July 2027, then each 1 April from 2028) + href: https://assets.publishing.service.gov.uk/media/6ae1883fe4367e5c8b0467df/AB26_Policy_Costing.pdf#page=13 - title: OBR EFO March 2026 (Table A.1) (RPI forecast) href: https://obr.uk/efo/economic-and-fiscal-outlook-march-2026/ 2029-04-01: @@ -131,10 +130,10 @@ values: metadata: note: Forecast. 0.6154 uprated by 2028 RPI growth of 2.8%. reference: - - title: Budget 2025 (fuel duty uprated by RPI from April 2027) - href: https://www.gov.uk/government/publications/budget-2025-document/budget-2025-html - - title: HM Treasury, Budget 2025 policy costings, page 14 (from 1 April 2027) - href: https://assets.publishing.service.gov.uk/media/692872fd2a37784b16ecf676/Budget_2025-Policy_Costings.pdf#page=14 + - title: Budget 2026, paragraph 6.211 (5p cut ends and RPI uprating from 1 July 2027) + href: https://assets.publishing.service.gov.uk/media/6ae1ec57bdb574cf1c9b3f12/E03668986_HMT_Budget_2026_ELAY__2_.pdf#page=138 + - title: HM Treasury, Budget 2026 policy costings, page 13 (RPI on 1 July 2027, then each 1 April from 2028) + href: https://assets.publishing.service.gov.uk/media/6ae1883fe4367e5c8b0467df/AB26_Policy_Costing.pdf#page=13 - title: OBR EFO March 2026 (Table A.1) (RPI forecast) href: https://obr.uk/efo/economic-and-fiscal-outlook-march-2026/ 2030-04-01: @@ -142,10 +141,10 @@ values: metadata: note: Forecast. 0.6326 uprated by 2029 RPI growth of 2.9%. reference: - - title: Budget 2025 (fuel duty uprated by RPI from April 2027) - href: https://www.gov.uk/government/publications/budget-2025-document/budget-2025-html - - title: HM Treasury, Budget 2025 policy costings, page 14 (from 1 April 2027) - href: https://assets.publishing.service.gov.uk/media/692872fd2a37784b16ecf676/Budget_2025-Policy_Costings.pdf#page=14 + - title: Budget 2026, paragraph 6.211 (5p cut ends and RPI uprating from 1 July 2027) + href: https://assets.publishing.service.gov.uk/media/6ae1ec57bdb574cf1c9b3f12/E03668986_HMT_Budget_2026_ELAY__2_.pdf#page=138 + - title: HM Treasury, Budget 2026 policy costings, page 13 (RPI on 1 July 2027, then each 1 April from 2028) + href: https://assets.publishing.service.gov.uk/media/6ae1883fe4367e5c8b0467df/AB26_Policy_Costing.pdf#page=13 - title: OBR EFO March 2026 (Table A.1) (RPI forecast) href: https://obr.uk/efo/economic-and-fiscal-outlook-march-2026/ 2031-04-01: @@ -153,10 +152,10 @@ values: metadata: note: Forecast. 0.6509 uprated by 2030 RPI growth of 2.3%. reference: - - title: Budget 2025 (fuel duty uprated by RPI from April 2027) - href: https://www.gov.uk/government/publications/budget-2025-document/budget-2025-html - - title: HM Treasury, Budget 2025 policy costings, page 14 (from 1 April 2027) - href: https://assets.publishing.service.gov.uk/media/692872fd2a37784b16ecf676/Budget_2025-Policy_Costings.pdf#page=14 + - title: Budget 2026, paragraph 6.211 (5p cut ends and RPI uprating from 1 July 2027) + href: https://assets.publishing.service.gov.uk/media/6ae1ec57bdb574cf1c9b3f12/E03668986_HMT_Budget_2026_ELAY__2_.pdf#page=138 + - title: HM Treasury, Budget 2026 policy costings, page 13 (RPI on 1 July 2027, then each 1 April from 2028) + href: https://assets.publishing.service.gov.uk/media/6ae1883fe4367e5c8b0467df/AB26_Policy_Costing.pdf#page=13 - title: OBR EFO March 2026 (Table A.1) (RPI forecast) href: https://obr.uk/efo/economic-and-fiscal-outlook-march-2026/ # END RPI forecast generated by calculate_fuel_duty_rates.py diff --git a/policyengine_uk/tests/policy/baseline/gov/hmrc/fuel_duty/petrol_and_diesel_schedule.yaml b/policyengine_uk/tests/policy/baseline/gov/hmrc/fuel_duty/petrol_and_diesel_schedule.yaml index 4dfe920b38..3bdf69819a 100644 --- a/policyengine_uk/tests/policy/baseline/gov/hmrc/fuel_duty/petrol_and_diesel_schedule.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/hmrc/fuel_duty/petrol_and_diesel_schedule.yaml @@ -1,13 +1,13 @@ # Fuel duty on 1,000 litres in each model year. A model year runs from # 6 April to 5 April, and the dated rates are day-weighted across it: # 2025-26: 365 days at 52.95p. -# 2026-27: 270 days at 52.95p (6 Apr to 31 Dec 2026), 59 at 55.95p -# (Jan to Feb 2027), 31 at 57.95p (Mar 2027) and 5 at 59.75p. -# 2027-28 (366 days): 361 at 59.75p and 5 at 61.54p. +# 2026-27: 365 days at 52.95p (Budget 2026 keeps the cut to 1 July 2027). +# 2027-28 (366 days): 86 at 52.95p (6 Apr to 30 Jun 2027), 275 at 59.75p +# (1 Jul 2027 to 31 Mar 2028) and 5 at 61.54p. # 2028-29: 360 at 61.54p and 5 at 63.26p. # 2029-30: 360 at 63.26p and 5 at 65.09p. # 2030-31: 360 at 65.09p and 5 at 66.59p. -# Rates from 1 April 2027 are the RPI forecast in petrol_and_diesel.yaml. +# Rates from 1 July 2027 are the RPI forecast in petrol_and_diesel.yaml. - name: Fuel duty for 2025-26 is 52.95p a litre all year period: 2025 absolute_error_margin: 0.01 @@ -16,24 +16,23 @@ diesel_litres: 0 output: fuel_duty: 529.5 -- name: Fuel duty for 2026-27 carries the January and March 2027 increases +- name: Fuel duty for 2026-27 stays at 52.95p a litre all year period: 2026 absolute_error_margin: 0.01 input: petrol_litres: 1_000 diesel_litres: 0 output: - # 1000 * (0.5295 * 270 + 0.5595 * 59 + 0.5795 * 31 + 0.5975 * 5) / 365 - fuel_duty: 539.5273972602739 -- name: Fuel duty for 2027-28 is the April 2027 RPI-uprated rate + fuel_duty: 529.5 +- name: Fuel duty for 2027-28 carries the 1 July 2027 rise period: 2027 absolute_error_margin: 0.01 input: petrol_litres: 600 diesel_litres: 400 output: - # 1000 * (0.5975 * 361 + 0.6154 * 5) / 366 - fuel_duty: 597.7445355191257 + # 1000 * (0.5295 * 86 + 0.5975 * 275 + 0.6154 * 5) / 366 + fuel_duty: 581.766393442623 - name: Fuel duty for 2028-29 period: 2028 absolute_error_margin: 0.01 @@ -69,5 +68,5 @@ diesel_litres: 0 in_rural_fuel_duty_relief_area: true output: - # 597.7445355191257 - 1000 * 0.05 - fuel_duty: 547.7445355191257 + # 581.766393442623 - 1000 * 0.05 + fuel_duty: 531.766393442623 diff --git a/policyengine_uk/tests/policy/baseline/gov/hmrc/fuel_duty/road_fuel_gases.yaml b/policyengine_uk/tests/policy/baseline/gov/hmrc/fuel_duty/road_fuel_gases.yaml index 6684b8b9b1..10ffa1b2fe 100644 --- a/policyengine_uk/tests/policy/baseline/gov/hmrc/fuel_duty/road_fuel_gases.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/hmrc/fuel_duty/road_fuel_gases.yaml @@ -1,6 +1,6 @@ -# Gas quantities are kilograms. FY2026 weights are 270/59/36 days: the -# 5p-equivalent cut runs to 31 Dec 2026, then rates rise on 1 Jan and -# 1 Mar 2027 (SI 2026/164 as amended by SI 2026/555). +# Gas quantities are kilograms. Budget 2026 keeps the 5p-equivalent cut to +# 1 July 2027 (paragraph 6.211; policy costings page 13), so FY2026 is all at +# the cut rates and FY2027 weights are 86/280 days. # Rural relief only reduces the petrol/diesel component. - name: No road fuel gas consumption period: 2026 @@ -33,7 +33,7 @@ in_rural_fuel_duty_relief_area: true output: fuel_duty: 994 -- name: Fiscal 2026 includes the January and March 2027 gas rate increases +- name: Fiscal 2026 gas rates stay at the cut rates all year period: 2026 absolute_error_margin: 0.01 input: @@ -42,6 +42,17 @@ petrol_litres: 0 diesel_litres: 0 output: - # 1000 * (0.2888 * 270 + 0.3052 * 59 + 0.3161 * 36) / 365 - # + 1000 * (0.2257 * 270 + 0.2385 * 59 + 0.2470 * 36) / 365 - fuel_duty: 524.0134246575342 + # 1000 * (0.2888 + 0.2257) + fuel_duty: 514.5 +- name: Fiscal 2027 gas rates return to the full rates on 1 July 2027 + period: 2027 + absolute_error_margin: 0.01 + input: + lpg_kg: 1000 + natural_gas_kg: 1000 + petrol_litres: 0 + diesel_litres: 0 + output: + # 1000 * (0.2888 * 86 + 0.3161 * 280) / 366 + # + 1000 * (0.2257 * 86 + 0.2470 * 280) / 366 + fuel_duty: 551.6803278688524 diff --git a/policyengine_uk/tests/test_fuel_duty_schedule.py b/policyengine_uk/tests/test_fuel_duty_schedule.py index 743381e783..0062f88e96 100644 --- a/policyengine_uk/tests/test_fuel_duty_schedule.py +++ b/policyengine_uk/tests/test_fuel_duty_schedule.py @@ -2,8 +2,9 @@ The petrol and diesel fuel duty rate follows its dated schedule. Each rate is keyed to the date it takes effect, and `fiscal_year_blend` -day-weights the schedule across each model year (6 April to 5 April). Rates -from 1 April 2027 are an RPI forecast: Budget 2025 policy, not yet law. +day-weights the schedule across each model year (6 April to 5 April). +Budget 2026 keeps the 5p cut to 1 July 2027; rates from then are an RPI +forecast. Both are Budget 2026 policy, not yet law. """ import datetime @@ -21,7 +22,10 @@ from policyengine_uk.system import system PATH = "gov.hmrc.fuel_duty.petrol_and_diesel" -FORECAST_YEARS = range(2027, 2032) +# The 1 July 2027 step, then each 1 April. Each applies the previous calendar +# year's RPI growth. +FORECAST_DATES = ["2027-07-01"] + [f"{year}-04-01" for year in range(2028, 2032)] +LAST_FORECAST_YEAR = 2031 CONVERTED_YEARS = range(2015, 2041) @@ -57,29 +61,29 @@ def model_year_days(year: int) -> list: ("2022-03-22", 0.5795), ("2022-03-23", 0.5295), # SI 2026/555 removed the 1 September 2026 step and moved the - # 1 December 2026 step to 1 January 2027. + # 1 December 2026 step to 1 January 2027; Budget 2026 cancelled the + # January and March 2027 steps and kept the cut to 1 July 2027. ("2026-09-01", 0.5295), ("2026-12-01", 0.5295), ("2026-12-31", 0.5295), - ("2027-01-01", 0.5595), - ("2027-02-28", 0.5595), - ("2027-03-01", 0.5795), - ("2027-03-31", 0.5795), + ("2027-01-01", 0.5295), + ("2027-03-01", 0.5295), + ("2027-04-01", 0.5295), + ("2027-06-30", 0.5295), ], ) -def test_statutory_rate_on_each_date(date, rate): +def test_scheduled_rate_on_each_date(date, rate): schedule = raw_parameters().gov.hmrc.fuel_duty.petrol_and_diesel assert schedule(date) == pytest.approx(rate, abs=1e-12) -# Model year Y runs from 6 April Y to 5 April Y+1. Rates from 1 April 2027: -# 59.75p, then 61.54p (2028), 63.26p (2029), 65.09p (2030), 66.59p (2031). +# Model year Y runs from 6 April Y to 5 April Y+1. Rates: 59.75p from 1 July +# 2027, then 61.54p (2028), 63.26p (2029), 65.09p (2030), 66.59p (2031). HAND_COMPUTED = { 2025: 0.5295, - # 6 Apr to 31 Dec 2026, Jan to Feb 2027, Mar 2027, 1 to 5 Apr 2027. - 2026: (0.5295 * 270 + 0.5595 * 59 + 0.5795 * 31 + 0.5975 * 5) / 365, - # 2028 is a leap year: 361 days to 31 March, then 5 days. - 2027: (0.5975 * 361 + 0.6154 * 5) / 366, + 2026: 0.5295, + # 6 Apr to 30 Jun 2027, then to 31 Mar 2028 (a leap year), then 5 days. + 2027: (0.5295 * 86 + 0.5975 * 275 + 0.6154 * 5) / 366, 2028: (0.6154 * 360 + 0.6326 * 5) / 365, 2029: (0.6326 * 360 + 0.6509 * 5) / 365, 2030: (0.6509 * 360 + 0.6659 * 5) / 365, @@ -92,7 +96,7 @@ def test_model_year_rate_is_hand_computed_day_weighted_average(year, rate): def test_forecast_steps_use_current_rpi_forecast(): - """The written 1 April steps match the RPI series they claim to use. + """The written steps match the RPI series they claim to use. Fails when the RPI forecast in yoy_growth.yaml changes. Regenerate with `uv run python policyengine_uk/parameters/gov/hmrc/fuel_duty/calculate_fuel_duty_rates.py --update`. @@ -100,21 +104,25 @@ def test_forecast_steps_use_current_rpi_forecast(): parameters = raw_parameters() rpi = parameters.gov.economic_assumptions.yoy_growth.obr.rpi schedule = parameters.gov.hmrc.fuel_duty.petrol_and_diesel + # The 57.95p pre-cut rate (Finance Act 2011 s. 19). rate = Decimal("0.5795") - for year in FORECAST_YEARS: - growth = Decimal(str(rpi(f"{year - 1}-01-01"))) + for date in FORECAST_DATES: + day_before = ( + datetime.date.fromisoformat(date) - datetime.timedelta(days=1) + ).isoformat() + growth = Decimal(str(rpi(f"{int(date[:4]) - 1}-01-01"))) rate = (rate * (1 + growth)).quantize(Decimal("0.0001"), ROUND_HALF_UP) - assert schedule(f"{year}-04-01") == pytest.approx(float(rate), abs=1e-12), ( - f"{year}-04-01 is stale against the RPI forecast; run " + assert schedule(date) == pytest.approx(float(rate), abs=1e-12), ( + f"{date} is stale against the RPI forecast; run " "calculate_fuel_duty_rates.py --update" ) - assert schedule(f"{year}-03-31") != schedule(f"{year}-04-01") + assert schedule(day_before) != schedule(date) def test_uprating_continues_each_april_after_the_written_steps(): schedule = dated_schedule() rpi = system.parameters.baseline.gov.economic_assumptions.yoy_growth.obr.rpi - for year in range(max(FORECAST_YEARS) + 1, 2041): + for year in range(LAST_FORECAST_YEAR + 1, 2041): previous = schedule(f"{year - 1}-04-01") assert schedule(f"{year}-03-31") == previous expected = previous * (1 + rpi(f"{year - 1}-01-01")) @@ -162,18 +170,19 @@ def fuel_duty_simulation(parameter_changes=None): return Microsimulation(situation=situation, scenario=scenario) -def test_holding_55_95p_for_2027_removes_the_full_2027_28_rate_rise(): - """Regression: calendar-year averaging cut this effect to 3.30p a litre. +def test_holding_52_95p_for_2027_removes_the_full_2027_28_rate_rise(): + """Regression: calendar-year averaging diluted annual overrides. - The 2027-28 baseline is 59.77p, so holding 55.95p saves 3.82p a litre. + The 2027-28 baseline averages 58.18p, so holding 52.95p for the whole + model year saves 5.23p a litre. """ baseline = fuel_duty_simulation() - reformed = fuel_duty_simulation({PATH: {"2027": 0.5595}}) + reformed = fuel_duty_simulation({PATH: {"2027": 0.5295}}) saving = ( baseline.calculate("fuel_duty", 2027).values[0] - reformed.calculate("fuel_duty", 2027).values[0] ) - assert saving == pytest.approx(1_000 * (HAND_COMPUTED[2027] - 0.5595)) + assert saving == pytest.approx(1_000 * (HAND_COMPUTED[2027] - 0.5295)) @settings(max_examples=8, deadline=None)