diff --git a/changelog_entry.yaml b/changelog_entry.yaml index e69de29bb..c436a7573 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -0,0 +1,4 @@ +- bump: patch + changes: + fixed: + - Fix fuel duty rates to use OBR November 2025 RPI forecasts. \ No newline at end of file diff --git a/docs/book/index.ipynb b/docs/book/index.ipynb deleted file mode 100644 index 38beace5e..000000000 --- a/docs/book/index.ipynb +++ /dev/null @@ -1,149 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# PolicyEngine-UK\n", - "\n", - "This book contains an introduction to using PolicyEngine-UK to model UK taxes and benefits. It is currently a work in progress and may be added to. PolicyEngine-UK is a microsimulation model of the UK tax and benefit system: it is a model which calculates variable values over UK entities from given policy parameters and structures. In practice, this gives it two main uses: calculating statistics under current tax and benefit law, and simulating effects of potential new changes to the legislation.\n", - "\n", - "We're grateful to the [UKMOD](https://www.iser.essex.ac.uk/research/projects/ukmod) team for publishing descriptions of their model; our ability to reference these descriptions accelerated OpenFisca UK's development. UKMOD is maintained, developed and managed by the Centre for Microsimulation and Policy Analysis at the Institute for Social and Economic Research (ISER), University of Essex.\n", - "\n", - "Code examples and outputs are re-run automatically on each new version of PolicyEngine-UK.\n", - "\n", - "## Short demo\n", - "\n", - "### Baseline estimates\n", - "\n", - "Calculating, for example, the total Income Tax liability by region can be done with the following code:" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "region\n", - "LONDON 78.710210\n", - "SOUTH_EAST 75.588976\n", - "EAST_OF_ENGLAND 30.134433\n", - "WEST_MIDLANDS 23.276691\n", - "SCOTLAND 23.037309\n", - "NORTH_WEST 20.306150\n", - "SOUTH_WEST 17.589321\n", - "YORKSHIRE 15.903155\n", - "EAST_MIDLANDS 14.536368\n", - "WALES 10.113785\n", - "NORTH_EAST 7.983999\n", - "NORTHERN_IRELAND 5.428561\n", - "dtype: float64" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from policyengine_uk import Microsimulation\n", - "import pandas as pd\n", - "\n", - "ENHANCED_FRS = \"hf://policyengine/policyengine-uk-data/enhanced_frs_2022_23.h5\"\n", - "\n", - "sim = Microsimulation(dataset=ENHANCED_FRS)\n", - "\n", - "df = sim.calculate_dataframe(\n", - " [\n", - " \"household_id\", # If the first variable is household level, the dataframe will project everything to households. Same for people.\n", - " \"income_tax\",\n", - " \"region\",\n", - " ],\n", - " period=2025,\n", - ")\n", - "\n", - "df.groupby(\"region\").income_tax.sum().sort_values(\n", - " ascending=False\n", - ") / 1e9 # Weights automatically applied" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Reform evaluation\n", - "\n", - "Below is an example of simulating the effects of a reform (namely, increasing the basic rate of income tax from 20% to 23%)." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "tags": [ - "hide-input" - ] - }, - "outputs": [ - { - "data": { - "text/plain": [ - "'Revenue: £21.3bn'" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from policyengine_uk.model_api import *\n", - "\n", - "\n", - "def change_tax_parameters(parameters):\n", - " parameters.gov.hmrc.income_tax.rates.uk.brackets[0].rate.update(\n", - " period=periods.period(\"year:2019:10\"), value=0.23\n", - " )\n", - " return parameters\n", - "\n", - "\n", - "class reform(Reform):\n", - " def apply(self):\n", - " self.modify_parameters(change_tax_parameters)\n", - "\n", - "\n", - "baseline = Microsimulation(dataset=ENHANCED_FRS)\n", - "reformed = Microsimulation(dataset=ENHANCED_FRS, reform=reform)\n", - "revenue = (\n", - " reformed.calculate(\"gov_balance\", 2025).sum()\n", - " - baseline.calc(\"gov_balance\", 2025).sum()\n", - ")\n", - "f\"Revenue: £{round(revenue / 1e9, 1)}bn\"" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": ".venv", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.11" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/docs/book/index.md b/docs/book/index.md new file mode 100644 index 000000000..9b4813fd6 --- /dev/null +++ b/docs/book/index.md @@ -0,0 +1,64 @@ +# PolicyEngine-UK + +This book contains an introduction to using PolicyEngine-UK to model UK taxes and benefits. It is currently a work in progress and may be added to. PolicyEngine-UK is a microsimulation model of the UK tax and benefit system: it is a model which calculates variable values over UK entities from given policy parameters and structures. In practice, this gives it two main uses: calculating statistics under current tax and benefit law, and simulating effects of potential new changes to the legislation. + +We're grateful to the [UKMOD](https://www.iser.essex.ac.uk/research/projects/ukmod) team for publishing descriptions of their model; our ability to reference these descriptions accelerated OpenFisca UK's development. UKMOD is maintained, developed and managed by the Centre for Microsimulation and Policy Analysis at the Institute for Social and Economic Research (ISER), University of Essex. + +Code examples and outputs are re-run automatically on each new version of PolicyEngine-UK. + +## Short demo + +### Baseline estimates + +Calculating, for example, the total Income Tax liability by region can be done with the following code: + +```python +from policyengine_uk import Microsimulation +import pandas as pd + +ENHANCED_FRS = "hf://policyengine/policyengine-uk-data/enhanced_frs_2022_23.h5" + +sim = Microsimulation(dataset=ENHANCED_FRS) + +df = sim.calculate_dataframe( + [ + "household_id", # If the first variable is household level, the dataframe will project everything to households. Same for people. + "income_tax", + "region", + ], + period=2025, +) + +df.groupby("region").income_tax.sum().sort_values( + ascending=False +) / 1e9 # Weights automatically applied +``` + +### Reform evaluation + +Below is an example of simulating the effects of a reform (namely, increasing the basic rate of income tax from 20% to 23%). + +```python +from policyengine_uk.model_api import * + + +def change_tax_parameters(parameters): + parameters.gov.hmrc.income_tax.rates.uk.brackets[0].rate.update( + period=periods.period("year:2019:10"), value=0.23 + ) + return parameters + + +class reform(Reform): + def apply(self): + self.modify_parameters(change_tax_parameters) + + +baseline = Microsimulation(dataset=ENHANCED_FRS) +reformed = Microsimulation(dataset=ENHANCED_FRS, reform=reform) +revenue = ( + reformed.calculate("gov_balance", 2025).sum() + - baseline.calc("gov_balance", 2025).sum() +) +f"Revenue: £{round(revenue / 1e9, 1)}bn" +``` diff --git a/myst.yml b/myst.yml index b4a11aab8..6d0344aec 100644 --- a/myst.yml +++ b/myst.yml @@ -10,7 +10,7 @@ project: branch: master path: docs/book toc: - - file: docs/book/index + - file: docs/book/index.md - title: Usage children: - file: docs/book/usage/getting-started 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 new file mode 100644 index 000000000..f24f902fb --- /dev/null +++ b/policyengine_uk/parameters/gov/hmrc/fuel_duty/calculate_fuel_duty_rates.py @@ -0,0 +1,464 @@ +""" +Calculates calendar year average fuel duty rates and updates petrol_and_diesel.yaml. + +Reads base rates from petrol_and_diesel.yaml and RPI forecasts from yoy_growth.yaml, +then computes weighted averages accounting for the staggered 5p cut reversal and +RPI uprating from April 2027. + +Usage: + python calculate_fuel_duty_rates.py # Print calculations only + python calculate_fuel_duty_rates.py --update # Update the YAML file +""" + +import argparse +import re +from datetime import date +from calendar import isleap +from pathlib import Path + +import yaml + + +# Years to calculate rates for +CALCULATION_YEARS = range(2026, 2031) + + +def get_repo_root() -> Path: + """Find the repository root by looking for policyengine_uk directory.""" + current = Path(__file__).resolve() + while current != current.parent: + if (current / "policyengine_uk").is_dir(): + return current + current = current.parent + raise RuntimeError("Could not find repository root") + + +def get_fuel_duty_yaml_path() -> Path: + """Get the path to the fuel duty YAML file.""" + repo_root = get_repo_root() + return ( + repo_root + / "policyengine_uk/parameters/gov/hmrc/fuel_duty/petrol_and_diesel.yaml" + ) + + +def load_fuel_duty_rates() -> dict: + """Load fuel duty rates from the YAML parameter file.""" + yaml_path = get_fuel_duty_yaml_path() + with open(yaml_path) as f: + return yaml.safe_load(f) + + +def load_rpi_forecasts() -> dict: + """Load RPI year-on-year growth forecasts from economic assumptions.""" + repo_root = get_repo_root() + yaml_path = ( + repo_root + / "policyengine_uk/parameters/gov/economic_assumptions/yoy_growth.yaml" + ) + with open(yaml_path) as f: + data = yaml.safe_load(f) + + # Extract RPI values and convert to percentage format + rpi_values = data["obr"]["rpi"]["values"] + rpi_forecasts = {} + for date_key, value in rpi_values.items(): + # Handle both string and datetime.date keys from YAML + if hasattr(date_key, "year"): + year = date_key.year + else: + year = int(str(date_key).split("-")[0]) + # Convert from decimal (0.0371) to percentage (3.71) + rpi_forecasts[year] = value * 100 + return rpi_forecasts + + +def get_value_for_date(values: dict, target_date: str) -> float: + """ + Get the value for a specific date from a YAML values dict. + Handles both string and datetime.date keys. + """ + for date_key, value in values.items(): + # Normalize date key to string for comparison + if hasattr(date_key, "strftime"): + key_str = date_key.strftime("%Y-%m-%d") + else: + key_str = str(date_key) + + if key_str == target_date: + # Value might be a dict with 'value' key or a direct number + if isinstance(value, dict): + return value["value"] + return value + return None + + +def get_base_rate_with_cut(fuel_duty_data: dict) -> float: + """ + Get the base fuel duty rate with the 5p cut still in place. + This is the rate at 2022-03-23 (when the 5p cut was introduced). + Returns rate in pence. + """ + values = fuel_duty_data["values"] + # The 5p cut rate is at 2022-03-23 + rate = get_value_for_date(values, "2022-03-23") + if rate is None: + raise ValueError("Could not find 2022-03-23 fuel duty rate") + # Convert from GBP to pence + return rate * 100 + + +def get_pre_cut_rate(fuel_duty_data: dict) -> float: + """ + Get the fuel duty rate before the 5p cut (the rate to restore to). + This is the rate at 2021-01-01 (57.95p). + Returns rate in pence. + """ + values = fuel_duty_data["values"] + rate = get_value_for_date(values, "2021-01-01") + if rate is None: + raise ValueError("Could not find 2021-01-01 fuel duty rate") + return rate * 100 + + +# Staggered reversal schedule (date, cumulative increase in pence) +REVERSAL_SCHEDULE = [ + (date(2026, 9, 1), 1.0), # +1p + (date(2026, 12, 1), 3.0), # +2p more (total +3p) + (date(2027, 3, 1), 5.0), # +2p more (total +5p, full reversal) +] + + +def get_rate_on_date( + d: date, + base_rate_with_cut: float, + pre_cut_rate: float, + rpi_forecasts: dict, +) -> float: + """Get the fuel duty rate (in pence) on a specific date.""" + # Start with the base rate (with 5p cut still in place) + rate = base_rate_with_cut + + # Apply staggered reversal + for reversal_date, increase in REVERSAL_SCHEDULE: + if d >= reversal_date: + rate = base_rate_with_cut + increase + + # Apply RPI uprating from April 2027 + if d >= date(2027, 4, 1): + # Rate after full reversal (pre-cut rate) + rate = pre_cut_rate + + # Apply cumulative RPI uprating + for year in sorted(rpi_forecasts.keys()): + if year < 2026: + continue # Only apply forecasts from 2026 onwards + uprating_date = date(year + 1, 4, 1) + if d >= uprating_date: + rpi = rpi_forecasts[year] + rate = rate * (1 + rpi / 100) + + return round(rate, 2) + + +def days_in_year(year: int) -> int: + """Return number of days in a year.""" + return 366 if isleap(year) else 365 + + +def calculate_annual_average( + year: int, + base_rate_with_cut: float, + pre_cut_rate: float, + rpi_forecasts: dict, +) -> float: + """Calculate the weighted average fuel duty rate for a calendar year.""" + total_days = days_in_year(year) + + # Calculate daily rates and sum them + total_rate = 0.0 + for month in range(1, 13): + for day in range(1, 32): + try: + d = date(year, month, day) + total_rate += get_rate_on_date( + d, base_rate_with_cut, pre_cut_rate, rpi_forecasts + ) + except ValueError: + # Invalid date (e.g., Feb 30) + continue + + average = total_rate / total_days + return round(average, 2) + + +def format_as_pounds(pence: float) -> str: + """Convert pence to pounds string format for YAML.""" + return f"{pence / 100:.4f}" + + +def generate_year_breakdown( + year: int, + base_rate_with_cut: float, + pre_cut_rate: float, + rpi_forecasts: dict, +) -> list: + """Generate period breakdown for a year, returns list of (start, end, rate, days).""" + current_rate = None + period_start = None + periods = [] + + for month in range(1, 13): + for day in range(1, 32): + try: + d = date(year, month, day) + rate = get_rate_on_date( + d, base_rate_with_cut, pre_cut_rate, rpi_forecasts + ) + + if rate != current_rate: + if current_rate is not None: + days = (prev_date - period_start).days + 1 + periods.append( + (period_start, prev_date, current_rate, days) + ) + current_rate = rate + period_start = d + prev_date = d + except ValueError: + continue + + # Add final period + if current_rate is not None: + days = (prev_date - period_start).days + 1 + periods.append((period_start, prev_date, current_rate, days)) + + return periods + + +def generate_note_for_year( + year: int, + avg_pence: float, + base_rate_with_cut: float, + pre_cut_rate: float, + rpi_forecasts: dict, +) -> str: + """Generate the note text for a year's YAML entry.""" + periods = generate_year_breakdown( + year, base_rate_with_cut, pre_cut_rate, rpi_forecasts + ) + total_days = days_in_year(year) + year_type = "leap year" if isleap(year) else "" + + lines = [] + if year_type: + lines.append(f"Calendar year {year} average ({year_type}):") + else: + lines.append(f"Calendar year {year} average:") + + for start, end, rate, days in periods: + lines.append( + f"- {start.strftime('%b %d')} - {end.strftime('%b %d')} " + f"({days} days): {rate:.2f}p" + ) + + # Add formula + formula_parts = [f"{rate:.2f}p * {days}" for _, _, rate, days in periods] + lines.append( + f"({' + '.join(formula_parts)}) / {total_days} = {avg_pence:.2f}p" + ) + lines.append("Generated by calculate_fuel_duty_rates.py") + + return "\n".join(lines) + + +def update_yaml_file( + calculated_rates: dict, + rpi_forecasts: dict, + base_rate_with_cut: float, + pre_cut_rate: float, +) -> None: + """Update the petrol_and_diesel.yaml file with calculated rates.""" + yaml_path = get_fuel_duty_yaml_path() + + # Read the current file content + with open(yaml_path, "r") as f: + content = f.read() + + # For each year, update or add the entry + for year, avg_pence in calculated_rates.items(): + date_str = f"{year}-01-01" + value_pounds = avg_pence / 100 + + # Generate the note + note = generate_note_for_year( + year, avg_pence, base_rate_with_cut, pre_cut_rate, rpi_forecasts + ) + # Indent note lines for YAML + note_yaml = note.replace("\n", "\n ") + + # Pattern to match existing entry for this year + # Matches from the date line until the next date or metadata section + pattern = ( + rf"( {date_str}:.*?)(?=\n \d{{4}}-\d{{2}}-\d{{2}}:|\nmetadata:)" + ) + + # New entry text + new_entry = f""" {date_str}: + value: {value_pounds:.4f} + metadata: + reference: + - title: OBR Economic and Fiscal Outlook November 2025 + href: https://obr.uk/efo/economic-and-fiscal-outlook-november-2025/ + note: | + {note_yaml}""" + + # Try to replace existing entry + if re.search(pattern, content, re.DOTALL): + content = re.sub(pattern, new_entry, content, flags=re.DOTALL) + else: + # Entry doesn't exist, need to add it before metadata section + # Find the metadata section and insert before it + metadata_match = re.search(r"\nmetadata:", content) + if metadata_match: + insert_pos = metadata_match.start() + content = ( + content[:insert_pos] + + "\n" + + new_entry + + content[insert_pos:] + ) + + # Write the updated content back + with open(yaml_path, "w") as f: + f.write(content) + + print(f"Updated {yaml_path}") + + +def print_calculations( + base_rate_with_cut: float, + pre_cut_rate: float, + rpi_forecasts: dict, + calculated_rates: dict, +) -> None: + """Print calculation details to stdout.""" + print("Loading data from repository files...") + print(f" Base rate with 5p cut (2022-03-23): {base_rate_with_cut:.2f}p") + print(f" Pre-cut rate (2021-01-01): {pre_cut_rate:.2f}p") + print() + + print( + "RPI forecasts from yoy_growth.yaml (applied in April of next year):" + ) + for year in CALCULATION_YEARS: + rpi = rpi_forecasts.get(year, 0) + print(f" {year}: {rpi:.2f}%") + print() + + print("=" * 60) + print("Fuel Duty Rate Calculations") + print("=" * 60) + print() + + # Show the rate schedule + print("Rate Schedule (actual policy dates):") + print("-" * 40) + key_dates = [ + date(2026, 1, 1), + date(2026, 9, 1), + date(2026, 12, 1), + date(2027, 3, 1), + date(2027, 4, 1), + date(2028, 4, 1), + date(2029, 4, 1), + date(2030, 4, 1), + ] + for d in key_dates: + rate = get_rate_on_date( + d, base_rate_with_cut, pre_cut_rate, rpi_forecasts + ) + print(f" {d}: {rate:.2f}p ({format_as_pounds(rate)} GBP)") + + print() + print("Calendar Year Averages (for YAML parameter file):") + print("-" * 40) + + for year in CALCULATION_YEARS: + avg = calculated_rates[year] + print(f" {year}-01-01:") + print(f" value: {format_as_pounds(avg)}") + print(f" # Average rate: {avg:.2f}p") + print() + + # Show detailed breakdown for each year + print() + print("Detailed Breakdown:") + print("=" * 60) + + for year in CALCULATION_YEARS: + year_type = "leap year" if isleap(year) else "regular year" + print(f"\n{year} ({year_type}):") + print("-" * 40) + + periods = generate_year_breakdown( + year, base_rate_with_cut, pre_cut_rate, rpi_forecasts + ) + total_days = days_in_year(year) + weighted_sum = 0.0 + + for start, end, rate, days in periods: + weighted_sum += rate * days + print( + f" {start.strftime('%b %d')} - {end.strftime('%b %d')}: " + f"{days:3d} days @ {rate:.2f}p" + ) + + avg = weighted_sum / total_days + print(f" Average: {avg:.2f}p ({format_as_pounds(avg)} GBP)") + + +def main(): + parser = argparse.ArgumentParser( + description="Calculate fuel duty rates based on Autumn Budget 2025 policy." + ) + parser.add_argument( + "--update", + action="store_true", + help="Update the petrol_and_diesel.yaml file with calculated rates", + ) + args = parser.parse_args() + + # Load data from repository files + fuel_duty_data = load_fuel_duty_rates() + rpi_forecasts = load_rpi_forecasts() + + base_rate_with_cut = get_base_rate_with_cut(fuel_duty_data) + pre_cut_rate = get_pre_cut_rate(fuel_duty_data) + + # Calculate rates for each year + calculated_rates = {} + for year in CALCULATION_YEARS: + avg = calculate_annual_average( + year, base_rate_with_cut, pre_cut_rate, rpi_forecasts + ) + calculated_rates[year] = avg + + # Print calculations + print_calculations( + base_rate_with_cut, pre_cut_rate, rpi_forecasts, calculated_rates + ) + + # Update YAML file if requested + if args.update: + print() + print("=" * 60) + print("Updating YAML file...") + print("=" * 60) + update_yaml_file( + calculated_rates, rpi_forecasts, base_rate_with_cut, pre_cut_rate + ) + + +if __name__ == "__main__": + main() 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 b19cebf3b..d431477c3 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 @@ -21,16 +21,22 @@ values: - title: Costing Document - Spring Budget 2023 (page 25) href: https://assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/1142824/Costing_Document_-_Spring_Budget_2023.pdf#page=25 # November 2025 Autumn Budget: staggered 5p cut reversal (+1p Sep, +2p Dec, +2p Mar 2027) - # then RPI uprating from Apr 2027: 59.80p (4.1%), 61.54p (3.2%), 63.34p (2.9%) + # then RPI uprating from Apr 2027 using OBR November 2025 RPI forecasts. # # Source values (actual policy implementation dates): # - Mar 22, 2026: 52.95p (continuation of 5p cut) # - Sep 1, 2026: 53.95p (+1p increase) # - Dec 1, 2026: 55.95p (+2p increase) - # - Mar 1, 2027: 57.95p (+2p increase) - # - Apr 1, 2027: 59.80p (RPI uprating: 4.1%) - # - Apr 1, 2028: 61.54p (RPI uprating: 3.2%) - # - Apr 1, 2029: 63.34p (RPI uprating: 2.9%) + # - Mar 1, 2027: 57.95p (+2p increase, full rate restored) + # - Apr 1, 2027: 60.10p (RPI uprating: 3.71% from OBR Nov 2025 Table 1.7) + # - Apr 1, 2028: 61.98p (RPI uprating: 3.13% from OBR Nov 2025 Table 1.7) + # - Apr 1, 2029: 63.76p (RPI uprating: 2.87% from OBR Nov 2025 Table 1.7) + # + # RPI forecasts from OBR EFO November 2025 detailed forecast tables (Table 1.7): + # https://obr.uk/efo/economic-and-fiscal-outlook-november-2025/ + # - 2026 RPI: 3.71% (applied Apr 2027) + # - 2027 RPI: 3.13% (applied Apr 2028) + # - 2028 RPI: 2.87% (applied Apr 2029) # # For annual calculations, we use the average rate over each calendar year # to avoid issues with monthly rate selection in reform calculations. @@ -42,38 +48,60 @@ values: href: https://obr.uk/efo/economic-and-fiscal-outlook-november-2025/ note: | Calendar year 2026 average: - (0.5295 * 243 days + 0.5395 * 91 days + 0.5595 * 31 days) / 365 days = 0.5345 - where 243 days = Jan 1 - Aug 31, 91 days = Sep 1 - Nov 30, 31 days = Dec 1 - Dec 31 + - Jan 01 - Aug 31 (243 days): 52.95p + - Sep 01 - Nov 30 (91 days): 53.95p + - Dec 01 - Dec 31 (31 days): 55.95p + (52.95p * 243 + 53.95p * 91 + 55.95p * 31) / 365 = 53.45p + Generated by calculate_fuel_duty_rates.py 2027-01-01: - value: 0.5902 + value: 0.5925 metadata: reference: - title: OBR Economic and Fiscal Outlook November 2025 href: https://obr.uk/efo/economic-and-fiscal-outlook-november-2025/ note: | Calendar year 2027 average: - (0.5595 * 59 days + 0.5795 * 31 days + 0.598 * 275 days) / 365 days = 0.5902 - where 59 days = Jan 1 - Feb 28, 31 days = Mar 1 - Mar 31, 275 days = Apr 1 - Dec 31 + - Jan 01 - Feb 28 (59 days): 55.95p + - Mar 01 - Mar 31 (31 days): 57.95p + - Apr 01 - Dec 31 (275 days): 60.10p + (55.95p * 59 + 57.95p * 31 + 60.10p * 275) / 365 = 59.25p + Generated by calculate_fuel_duty_rates.py 2028-01-01: - value: 0.6111 + value: 0.6151 metadata: reference: - title: OBR Economic and Fiscal Outlook November 2025 href: https://obr.uk/efo/economic-and-fiscal-outlook-november-2025/ note: | Calendar year 2028 average (leap year): - (0.598 * 91 days + 0.6154 * 275 days) / 366 days = 0.6111 - where 91 days = Jan 1 - Mar 31, 275 days = Apr 1 - Dec 31 + - Jan 01 - Mar 31 (91 days): 60.10p + - Apr 01 - Dec 31 (275 days): 61.98p + (60.10p * 91 + 61.98p * 275) / 366 = 61.51p + Generated by calculate_fuel_duty_rates.py 2029-01-01: - value: 0.6290 + value: 0.6332 metadata: reference: - title: OBR Economic and Fiscal Outlook November 2025 href: https://obr.uk/efo/economic-and-fiscal-outlook-november-2025/ note: | Calendar year 2029 average: - (0.6154 * 90 days + 0.6334 * 275 days) / 365 days = 0.6290 - where 90 days = Jan 1 - Mar 31, 275 days = Apr 1 - Dec 31 + - Jan 01 - Mar 31 (90 days): 61.98p + - Apr 01 - Dec 31 (275 days): 63.76p + (61.98p * 90 + 63.76p * 275) / 365 = 63.32p + Generated by calculate_fuel_duty_rates.py + 2030-01-01: + value: 0.6516 + metadata: + reference: + - title: OBR Economic and Fiscal Outlook November 2025 + href: https://obr.uk/efo/economic-and-fiscal-outlook-november-2025/ + note: | + Calendar year 2030 average: + - Jan 01 - Mar 31 (90 days): 63.76p + - Apr 01 - Dec 31 (275 days): 65.62p + (63.76p * 90 + 65.62p * 275) / 365 = 65.16p + Generated by calculate_fuel_duty_rates.py metadata: unit: currency-GBP name: fuel_duty_rate diff --git a/policyengine_uk/tests/microsimulation/reforms_config.yaml b/policyengine_uk/tests/microsimulation/reforms_config.yaml index 03485e7aa..246e0e271 100644 --- a/policyengine_uk/tests/microsimulation/reforms_config.yaml +++ b/policyengine_uk/tests/microsimulation/reforms_config.yaml @@ -16,7 +16,7 @@ reforms: parameters: gov.hmrc.child_benefit.amount.additional: 25 - name: Reduce Universal Credit taper rate to 20% - expected_impact: -28.3 + expected_impact: -29.3 parameters: gov.dwp.universal_credit.means_test.reduction_rate: 0.2 - name: Raise Class 1 main employee NICs rate to 10% @@ -24,7 +24,7 @@ reforms: parameters: gov.hmrc.national_insurance.class_1.rates.employee.main: 0.1 - name: Raise VAT standard rate by 2pp - expected_impact: 22.1 + expected_impact: 20.7 parameters: gov.hmrc.vat.standard_rate: 0.22 - name: Raise additional rate by 3pp