Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add UK program-statistics rows for fuel duty, state pension, employer National Insurance, and tax credits.
113 changes: 95 additions & 18 deletions src/policyengine/tax_benefit_models/uk/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,31 @@
Poverty,
calculate_uk_poverty_rates,
)
from policyengine.utils.errors import format_conditional_error_detail

# Map of UK program-statistics variable name -> program metadata. The
# entity for each program is derived from the variable's own metadata at
# runtime, so this list does not silently drift if policyengine-uk moves
# a variable between entities.
UK_PROGRAMS: dict[str, dict] = {
"income_tax": {"is_tax": True},
"national_insurance": {"is_tax": True},
"vat": {"is_tax": True},
"council_tax": {"is_tax": True},
"fuel_duty": {"is_tax": True},
"ni_employer": {"is_tax": True},
"universal_credit": {"is_tax": False},
"child_benefit": {"is_tax": False},
"pension_credit": {"is_tax": False},
"income_support": {"is_tax": False},
# tax_credits overlaps with the separate working_tax_credit and
# child_tax_credit rows. Downstream budget adapters should select the
# row set they need rather than summing all program statistics.
"tax_credits": {"is_tax": False},
"working_tax_credit": {"is_tax": False},
"child_tax_credit": {"is_tax": False},
"state_pension": {"is_tax": False},
}


class PolicyReformAnalysis(BaseModel):
Expand All @@ -36,11 +61,78 @@ class PolicyReformAnalysis(BaseModel):
reform_inequality: Inequality


def _format_missing_program_variables(missing_variables: set[str]) -> str | None:
"""Format the optional missing-variable detail for program statistics."""
return format_conditional_error_detail(
"Missing model variables",
missing_variables,
)


def _uk_program_statistics_config_error_message(
missing_variables: set[str],
missing_outputs: set[tuple[str, str]],
) -> str:
lines = ["UK program statistics config is invalid:"]

missing_variables_message = _format_missing_program_variables(missing_variables)
if missing_variables_message is not None:
lines.append(missing_variables_message)

if missing_outputs:
formatted = ", ".join(
f"{program_name} on {entity}"
for program_name, entity in sorted(missing_outputs)
)
lines.append("Variables not materialized in simulation outputs: " + formatted)
lines.append(
"Add them to the model version's entity_variables or pass them "
"via Simulation.extra_variables before running the simulation."
)

return "\n".join(lines)


def _validate_program_statistics_config(
baseline_simulation: Simulation,
reform_simulation: Simulation,
) -> None:
"""Validate UK program-stat variables before running simulations."""
missing_variables: set[str] = set()
missing_outputs: set[tuple[str, str]] = set()

simulations = (baseline_simulation, reform_simulation)
for program_name in UK_PROGRAMS:
for simulation in simulations:
model_version = simulation.tax_benefit_model_version
try:
variable = model_version.get_variable(program_name)
except ValueError:
missing_variables.add(program_name)
continue

resolved_variables = model_version.resolve_entity_variables(simulation)
if program_name not in resolved_variables.get(variable.entity, []):
missing_outputs.add((program_name, variable.entity))

if not missing_variables and not missing_outputs:
return

raise ValueError(
_uk_program_statistics_config_error_message(
missing_variables,
missing_outputs,
),
)


def economic_impact_analysis(
baseline_simulation: Simulation,
reform_simulation: Simulation,
) -> PolicyReformAnalysis:
"""Perform comprehensive analysis of a UK policy reform."""
_validate_program_statistics_config(baseline_simulation, reform_simulation)

baseline_simulation.ensure()
reform_simulation.ensure()

Expand All @@ -56,29 +148,14 @@ def economic_impact_analysis(
reform_simulation=reform_simulation,
)

programs = {
"income_tax": {"is_tax": True},
"national_insurance": {"is_tax": True},
"vat": {"is_tax": True},
"council_tax": {"is_tax": True},
"universal_credit": {"is_tax": False},
"child_benefit": {"is_tax": False},
"pension_credit": {"is_tax": False},
"income_support": {"is_tax": False},
"working_tax_credit": {"is_tax": False},
"child_tax_credit": {"is_tax": False},
}

model_version = baseline_simulation.tax_benefit_model_version
program_statistics = []
for program_name, program_info in programs.items():
entity = baseline_simulation.tax_benefit_model_version.get_variable(
program_name
).entity
for program_name, program_info in UK_PROGRAMS.items():
stats = ProgramStatistics(
baseline_simulation=baseline_simulation,
reform_simulation=reform_simulation,
program_name=program_name,
entity=entity,
entity=model_version.get_variable(program_name).entity,
is_tax=program_info["is_tax"],
)
stats.run()
Expand Down
4 changes: 4 additions & 0 deletions src/policyengine/tax_benefit_models/uk/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,11 @@ class PolicyEngineUKLatest(MicrosimulationModelVersion):
"income_support",
"working_tax_credit",
"child_tax_credit",
"state_pension",
# Tax
"income_tax",
"national_insurance",
"ni_employer",
],
"benunit": [
# IDs and weights
Expand All @@ -84,6 +86,7 @@ class PolicyEngineUKLatest(MicrosimulationModelVersion):
"child_benefit",
"pension_credit",
"income_support",
"tax_credits",
"working_tax_credit",
"child_tax_credit",
],
Expand All @@ -104,6 +107,7 @@ class PolicyEngineUKLatest(MicrosimulationModelVersion):
"household_benefits",
"household_tax",
"vat",
"fuel_duty",
# Housing
"rent",
"council_tax",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
"benunit.family_type": "COUPLE_WITH_CHILDREN",
"benunit.income_support": 0.0,
"benunit.pension_credit": 0.0,
"benunit.tax_credits": 0.0,
"benunit.universal_credit": 0.0,
"benunit.working_tax_credit": 0.0,
"household.council_tax": 0.0,
"household.equiv_hbai_household_net_income": 52503.68,
"household.fuel_duty": 0.0,
"household.hbai_household_net_income": 73505.15,
"household.household_benefits": 5880.35,
"household.household_count_people": 4.0,
Expand Down Expand Up @@ -44,6 +46,7 @@
"person[0].is_child": 0.0,
"person[0].is_male": 1.0,
"person[0].national_insurance": 3110.6,
"person[0].ni_employer": 7501.2,
"person[0].pension_credit": 0.0,
"person[0].pension_income": 0.0,
"person[0].person_id": 0.0,
Expand All @@ -52,6 +55,7 @@
"person[0].property_income": 0.0,
"person[0].savings_interest_income": 0.0,
"person[0].self_employment_income": 0.0,
"person[0].state_pension": 0.0,
"person[0].total_income": 55000.0,
"person[0].universal_credit": 0.0,
"person[0].working_tax_credit": 0.0,
Expand All @@ -71,6 +75,7 @@
"person[1].is_child": 0.0,
"person[1].is_male": 1.0,
"person[1].national_insurance": 1794.4,
"person[1].ni_employer": 4501.2,
"person[1].pension_credit": 0.0,
"person[1].pension_income": 0.0,
"person[1].person_id": 0.0,
Expand All @@ -79,6 +84,7 @@
"person[1].property_income": 0.0,
"person[1].savings_interest_income": 0.0,
"person[1].self_employment_income": 0.0,
"person[1].state_pension": 0.0,
"person[1].total_income": 35000.0,
"person[1].universal_credit": 0.0,
"person[1].working_tax_credit": 0.0,
Expand All @@ -98,6 +104,7 @@
"person[2].is_child": 1.0,
"person[2].is_male": 1.0,
"person[2].national_insurance": 0.0,
"person[2].ni_employer": 0.0,
"person[2].pension_credit": 0.0,
"person[2].pension_income": 0.0,
"person[2].person_id": 0.0,
Expand All @@ -106,6 +113,7 @@
"person[2].property_income": 0.0,
"person[2].savings_interest_income": 0.0,
"person[2].self_employment_income": 0.0,
"person[2].state_pension": 0.0,
"person[2].total_income": 0.0,
"person[2].universal_credit": 0.0,
"person[2].working_tax_credit": 0.0,
Expand All @@ -125,6 +133,7 @@
"person[3].is_child": 1.0,
"person[3].is_male": 1.0,
"person[3].national_insurance": 0.0,
"person[3].ni_employer": 0.0,
"person[3].pension_credit": 0.0,
"person[3].pension_income": 0.0,
"person[3].person_id": 0.0,
Expand All @@ -133,6 +142,7 @@
"person[3].property_income": 0.0,
"person[3].savings_interest_income": 0.0,
"person[3].self_employment_income": 0.0,
"person[3].state_pension": 0.0,
"person[3].total_income": 0.0,
"person[3].universal_credit": 0.0,
"person[3].working_tax_credit": 0.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
"benunit.family_type": "SINGLE",
"benunit.income_support": 0.0,
"benunit.pension_credit": 0.0,
"benunit.tax_credits": 0.0,
"benunit.universal_credit": 0.0,
"benunit.working_tax_credit": 0.0,
"household.council_tax": 0.0,
"household.equiv_hbai_household_net_income": 37491.94,
"household.fuel_duty": 0.0,
"household.hbai_household_net_income": 25119.6,
"household.household_benefits": 0.0,
"household.household_count_people": 1.0,
Expand Down Expand Up @@ -44,6 +46,7 @@
"person[0].is_child": 0.0,
"person[0].is_male": 1.0,
"person[0].national_insurance": 1394.4,
"person[0].ni_employer": 3751.2,
"person[0].pension_credit": 0.0,
"person[0].pension_income": 0.0,
"person[0].person_id": 0.0,
Expand All @@ -52,6 +55,7 @@
"person[0].property_income": 0.0,
"person[0].savings_interest_income": 0.0,
"person[0].self_employment_income": 0.0,
"person[0].state_pension": 0.0,
"person[0].total_income": 30000.0,
"person[0].universal_credit": 0.0,
"person[0].working_tax_credit": 0.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
"benunit.family_type": "SINGLE",
"benunit.income_support": 0.0,
"benunit.pension_credit": 0.0,
"benunit.tax_credits": 0.0,
"benunit.universal_credit": 5079.13,
"benunit.working_tax_credit": 0.0,
"household.council_tax": 0.0,
"household.equiv_hbai_household_net_income": 7580.79,
"household.fuel_duty": 0.0,
"household.hbai_household_net_income": 5079.13,
"household.household_benefits": 5079.13,
"household.household_count_people": 1.0,
Expand Down Expand Up @@ -44,6 +46,7 @@
"person[0].is_child": 0.0,
"person[0].is_male": 1.0,
"person[0].national_insurance": 0.0,
"person[0].ni_employer": 0.0,
"person[0].pension_credit": 0.0,
"person[0].pension_income": 0.0,
"person[0].person_id": 0.0,
Expand All @@ -52,6 +55,7 @@
"person[0].property_income": 0.0,
"person[0].savings_interest_income": 0.0,
"person[0].self_employment_income": 0.0,
"person[0].state_pension": 0.0,
"person[0].total_income": 0.0,
"person[0].universal_credit": 5079.13,
"person[0].working_tax_credit": 0.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
"benunit.family_type": "LONE_PARENT",
"benunit.income_support": 0.0,
"benunit.pension_credit": 0.0,
"benunit.tax_credits": 0.0,
"benunit.universal_credit": 1544.43,
"benunit.working_tax_credit": 0.0,
"household.council_tax": 0.0,
"household.equiv_hbai_household_net_income": 28120.33,
"household.fuel_duty": 0.0,
"household.hbai_household_net_income": 24464.69,
"household.household_benefits": 2945.09,
"household.household_count_people": 2.0,
Expand Down Expand Up @@ -44,6 +46,7 @@
"person[0].is_child": 0.0,
"person[0].is_male": 1.0,
"person[0].national_insurance": 994.4,
"person[0].ni_employer": 3001.2,
"person[0].pension_credit": 0.0,
"person[0].pension_income": 0.0,
"person[0].person_id": 0.0,
Expand All @@ -52,6 +55,7 @@
"person[0].property_income": 0.0,
"person[0].savings_interest_income": 0.0,
"person[0].self_employment_income": 0.0,
"person[0].state_pension": 0.0,
"person[0].total_income": 25000.0,
"person[0].universal_credit": 1544.43,
"person[0].working_tax_credit": 0.0,
Expand All @@ -71,6 +75,7 @@
"person[1].is_child": 1.0,
"person[1].is_male": 1.0,
"person[1].national_insurance": 0.0,
"person[1].ni_employer": 0.0,
"person[1].pension_credit": 0.0,
"person[1].pension_income": 0.0,
"person[1].person_id": 0.0,
Expand All @@ -79,6 +84,7 @@
"person[1].property_income": 0.0,
"person[1].savings_interest_income": 0.0,
"person[1].self_employment_income": 0.0,
"person[1].state_pension": 0.0,
"person[1].total_income": 0.0,
"person[1].universal_credit": 1544.43,
"person[1].working_tax_credit": 0.0
Expand Down
Loading
Loading