diff --git a/policyengine/economic_impact/budgetary_impact/overall/overall.py b/policyengine/economic_impact/budgetary_impact/overall/overall.py new file mode 100644 index 0000000..c081257 --- /dev/null +++ b/policyengine/economic_impact/budgetary_impact/overall/overall.py @@ -0,0 +1,71 @@ +from policyengine.economic_impact.base_metric_calculator import BaseMetricCalculator +from policyengine_uk import Microsimulation + +class BudgetaryImpact(BaseMetricCalculator): + def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: + super().__init__(baseline, reformed, default_period) + self.baseline = baseline + self.reformed = reformed + + def calculate(self): + + baseline_total_tax = self.baseline.calculate("household_tax").sum() + reformed_total_tax = self.reformed.calculate("household_tax").sum() + + tax_revenue_impact = reformed_total_tax - baseline_total_tax + + baseline_total_benefits = self.baseline.calculate("household_benefits").sum() + reformed_total_benefits = self.reformed.calculate("household_benefits").sum() + + + benefit_spending_impact = reformed_total_benefits - baseline_total_benefits + + budgetary_impact = tax_revenue_impact - benefit_spending_impact + + + + return { + "budgetary_impact" : round(budgetary_impact,2) + } + +class BenefitSpendingImpact(BaseMetricCalculator): + def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: + super().__init__(baseline, reformed, default_period) + self.baseline = baseline + self.reformed = reformed + + def calculate(self): + + baseline_total_benefits = self.baseline.calculate("household_benefits").sum() + reformed_total_benefits = self.reformed.calculate("household_benefits").sum() + + + benefit_spending_impact = reformed_total_benefits - baseline_total_benefits + + + + return { + "baseline_total_benefits": round(baseline_total_benefits,2), + "reformed_total_benefits": round(reformed_total_benefits,2), + "benefit_spending_impact": round(benefit_spending_impact,2) + } + +class TaxRevenueImpact(BaseMetricCalculator): + def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None: + super().__init__(baseline, reformed, default_period) + self.baseline = baseline + self.reformed = reformed + + def calculate(self): + + baseline_total_tax = self.baseline.calculate("household_tax").sum() + reformed_total_tax = self.reformed.calculate("household_tax").sum() + + tax_revenue_impact = reformed_total_tax - baseline_total_tax + + + return { + "baseline_total_tax": round(baseline_total_tax,2), + "reformed_total_tax": round(reformed_total_tax,2), + "tax_revenue_impact": round(tax_revenue_impact,2) + } \ No newline at end of file diff --git a/policyengine/economic_impact/economic_impact.py b/policyengine/economic_impact/economic_impact.py index 417483a..a539eff 100644 --- a/policyengine/economic_impact/economic_impact.py +++ b/policyengine/economic_impact/economic_impact.py @@ -36,6 +36,12 @@ PensionCredit ) +from .budgetary_impact.overall.overall import ( + BudgetaryImpact, + BenefitSpendingImpact, + TaxRevenueImpact +) + from typing import Dict class EconomicImpact: @@ -69,6 +75,9 @@ def __init__(self, reform: dict, country: str) -> None: # Set up metric calculators self.metric_calculators: Dict[str, object] = { + "budgetary/overall/budgetary_impact" : BudgetaryImpact(self.baseline, self.reformed), + "budgetary/overall/benefit_spending_impact" : BenefitSpendingImpact(self.baseline, self.reformed), + "budgetary/overall/tax_revenue_impact" : TaxRevenueImpact(self.baseline, self.reformed), "budgetary/by_program/income_tax" : IncomeTax(self.baseline, self.reformed), "budgetary/by_program/national_insurance" : NationalInsurance(self.baseline, self.reformed), "budgetary/by_program/vat" : Vat(self.baseline, self.reformed), diff --git a/policyengine/tests/economic_impact/budgetary_impact/by_program/test_by_program.py b/policyengine/tests/economic_impact/budgetary_impact/by_program/test_by_program.py index 68f5792..6bb48a0 100644 --- a/policyengine/tests/economic_impact/budgetary_impact/by_program/test_by_program.py +++ b/policyengine/tests/economic_impact/budgetary_impact/by_program/test_by_program.py @@ -3,7 +3,7 @@ import os from policyengine import EconomicImpact -def assert_dict_approx_equal(actual, expected, tolerance=1e-4): +def assert_dict_approx_equal(actual, expected, tolerance=1e3): for key in expected: assert abs(actual[key] - expected[key]) < tolerance, f"Key {key}: expected {expected[key]}, got {actual[key]}" diff --git a/policyengine/tests/economic_impact/budgetary_impact/overall/overall.yaml b/policyengine/tests/economic_impact/budgetary_impact/overall/overall.yaml new file mode 100644 index 0000000..7e9dd91 --- /dev/null +++ b/policyengine/tests/economic_impact/budgetary_impact/overall/overall.yaml @@ -0,0 +1,28 @@ +# overall +- test_budgetary_impact: + reform: + gov.hmrc.income_tax.rates.uk[0].rate: + "2024-01-01.2100-12-31": 0.55 + country: uk + expected: + budgetary_impact: 203274712297.14 + +- test_benefit_spending_impact: + reform: + gov.hmrc.income_tax.rates.uk[0].rate: + "2024-01-01.2100-12-31": 0.55 + country: uk + expected: + baseline_total_benefits: 247160184562.67 + reformed_total_benefits: 249032006583.15 + benefit_spending_impact: 1871822020.49 + +- test_tax_revenue_impact: + reform: + gov.hmrc.income_tax.rates.uk[0].rate: + "2024-01-01.2100-12-31": 0.55 + country: uk + expected: + baseline_total_tax: 447861864968.89 + reformed_total_tax: 653008399286.52 + tax_revenue_impact: 205146534317.63 \ No newline at end of file diff --git a/policyengine/tests/economic_impact/budgetary_impact/overall/test_overall.py b/policyengine/tests/economic_impact/budgetary_impact/overall/test_overall.py new file mode 100644 index 0000000..6c13d4c --- /dev/null +++ b/policyengine/tests/economic_impact/budgetary_impact/overall/test_overall.py @@ -0,0 +1,39 @@ +import pytest +import yaml +import os +from policyengine import EconomicImpact + +def assert_dict_approx_equal(actual, expected, tolerance=1e3): + for key in expected: + assert abs(actual[key] - expected[key]) < tolerance, f"Key {key}: expected {expected[key]}, got {actual[key]}" + + +yaml_file_path = "policyengine/tests/economic_impact/budgetary_impact/overall/overall.yaml" + +# Check if the file exists +if not os.path.exists(yaml_file_path): + raise FileNotFoundError(f"The YAML file does not exist at: {yaml_file_path}") + +with open(yaml_file_path, 'r') as file: + test_cases = yaml.safe_load(file) + +@pytest.mark.parametrize("test_case", test_cases) +def test_economic_impact(test_case): + test_name = list(test_case.keys())[0] + test_data = test_case[test_name] + + economic_impact = EconomicImpact(test_data['reform'], test_data['country']) + + if 'budgetary' in test_name: + result = economic_impact.calculate("budgetary/overall/budgetary_impact") + elif 'benefit' in test_name: + result = economic_impact.calculate("budgetary/overall/benefit_spending_impact") + elif 'tax' in test_name: + result = economic_impact.calculate("budgetary/overall/tax_revenue_impact") + else: + pytest.fail(f"Unknown test case: {test_name}") + + assert_dict_approx_equal(result, test_data['expected']) + +if __name__ == "__main__": + pytest.main([__file__]) \ No newline at end of file