Reforms#

Reforms allow you to modify a tax-benefit system’s parameters or variable logic to simulate policy changes. PolicyEngine Core provides two main approaches to creating reforms:

  1. Reform.from_dict() - Create reforms programmatically from a dictionary (recommended for most use cases)

  2. Reform subclass - Create a custom class for complex reforms requiring variable logic changes

Creating reforms with Reform.from_dict()#

The Reform.from_dict() method is the simplest way to create parameter-based reforms. It takes a dictionary mapping parameter paths to their new values.

Method signature#

Reform.from_dict(
    parameter_values: dict,  # Parameter path -> period -> value mappings
    country_id: str = None,  # Optional: country code for API integration
    name: str = None,        # Optional: human-readable name for the reform
) -> Reform

Dictionary format#

The parameter_values dictionary uses parameter paths as keys and period-value mappings as values:

{
    'path.to.parameter': {
        'YYYY-MM-DD.YYYY-MM-DD': value,  # Period format: start.end
    },
    'path.to.another.parameter': {
        '2024-01-01.2100-12-31': 1000,   # Example: effective 2024 onwards
    },
}

Period formats:

  • 'YYYY-MM-DD.YYYY-MM-DD' - Date range (start date, end date separated by .)

  • 'year:YYYY:N' - N years starting from YYYY (e.g., 'year:2024:10' for 2024-2033)

  • Simple value (no dict) - Applies for 100 years from 2000 (convenience shorthand)

Basic example#

Let’s create a simple reform that increases the basic income amount in the country template.

from policyengine_core.country_template import Microsimulation
from policyengine_core.reforms import Reform

# Create a reform that increases basic income from 600 to 1000
basic_income_reform = Reform.from_dict(
    {
        "benefits.basic_income": {
            "2020-01-01.2100-12-31": 1000,
        }
    }
)

# Run simulations
baseline = Microsimulation()
reformed = Microsimulation(reform=basic_income_reform)

# Compare results
print("Baseline basic income:")
print(baseline.calculate("basic_income", "2022-01"))
print("\nReformed basic income:")
print(reformed.calculate("basic_income", "2022-01"))

Multiple parameters#

You can modify multiple parameters in a single reform.

# Reform modifying multiple parameters
multi_param_reform = Reform.from_dict(
    {
        "benefits.basic_income": {
            "2020-01-01.2100-12-31": 800,
        },
        "taxes.income_tax_rate": {
            "2020-01-01.2100-12-31": 0.20,  # Increase from 0.15 to 0.20
        },
    }
)

reformed_multi = Microsimulation(reform=multi_param_reform)

print(
    "Reformed basic income:",
    reformed_multi.calculate("basic_income", "2022-01"),
)
print("Reformed income tax:", reformed_multi.calculate("income_tax", "2022-01"))

Bracket parameters#

For parameters with brackets (like tax scales), use array index notation: parameter.brackets[index].rate or parameter.brackets[index].threshold.

# Reform modifying a bracket parameter (social security contribution scale)
bracket_reform = Reform.from_dict(
    {
        # Modify the first bracket's rate
        "taxes.social_security_contribution.brackets[0].rate": {
            "2020-01-01.2100-12-31": 0.05,
        },
        # Modify the second bracket's threshold
        "taxes.social_security_contribution.brackets[1].threshold": {
            "2020-01-01.2100-12-31": 15000,
        },
    }
)

reformed_bracket = Microsimulation(reform=bracket_reform)
print(
    "Reformed social security contribution:",
    reformed_bracket.calculate("social_security_contribution", "2022-01"),
)

Shorthand for permanent changes#

If you want a parameter change to apply indefinitely, you can omit the period dictionary and just provide the value directly.

# Shorthand: value applies from year 2000 for 100 years
simple_reform = Reform.from_dict(
    {
        "benefits.basic_income": 1200,  # No period dict needed
        "taxes.income_tax_rate": 0.18,
    }
)

reformed_simple = Microsimulation(reform=simple_reform)
print("Basic income:", reformed_simple.calculate("basic_income", "2022-01"))

Named reforms for API integration#

When working with the PolicyEngine API, you can provide a country_id and name for better integration.

# Named reform with country ID (useful for API integration)
named_reform = Reform.from_dict(
    {
        "benefits.basic_income": {
            "2024-01-01.2100-12-31": 1500,
        },
    },
    country_id="country_template",
    name="Increased Basic Income Reform",
)

# The reform class now has these attributes set
print(f"Reform name: {named_reform.name}")
print(f"Country ID: {named_reform.country_id}")

Creating reforms with a Reform subclass#

For more complex reforms that need to modify variable logic or simulation data, define a class inheriting from Reform with an apply(self) method. Inside it, self is the tax-benefit system attached to the simulation with loaded data via self.simulation: Simulation.

from policyengine_core.model_api import *

baseline = Microsimulation()


class custom_reform(Reform):
    def apply(self):
        simulation = self.simulation

        # Modify parameters
        simulation.tax_benefit_system.parameters.taxes.housing_tax.rate.update(20)

        # Modify simulation data
        salary = simulation.calculate("salary", "2022-01")
        new_salary = salary * 1.1
        simulation.set_input("salary", "2022-01", new_salary)


reformed = Microsimulation(reform=custom_reform)
(
    reformed.calculate("salary", "2022-01"),
    baseline.calculate("salary", "2022-01"),
)
(
    reformed.calculate("housing_tax", 2022),
    baseline.calculate("housing_tax", 2022),
)

Comparing baseline and reformed simulations#

A common workflow is to compare results between baseline and reformed scenarios.

# Create reform
reform = Reform.from_dict({"benefits.basic_income": {"2020-01-01.2100-12-31": 1000}})

# Run both simulations
baseline = Microsimulation()
reformed = Microsimulation(reform=reform)

# Calculate and compare
baseline_income = baseline.calculate("basic_income", "2022-01")
reformed_income = reformed.calculate("basic_income", "2022-01")

print("Baseline basic income:")
print(baseline_income)
print("\nReformed basic income:")
print(reformed_income)
print(
    f"\nDifference: {reformed_income['value'].sum() - baseline_income['value'].sum():.2f}"
)