Skip to content

Commit 2a89618

Browse files
Merge pull request #20 from masterismail/BudgetaryImpactOverall1
[1] - added the overall budgetary impact metric
2 parents 791a9ab + c5b6919 commit 2a89618

File tree

5 files changed

+148
-1
lines changed

5 files changed

+148
-1
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from policyengine.economic_impact.base_metric_calculator import BaseMetricCalculator
2+
from policyengine_uk import Microsimulation
3+
4+
class BudgetaryImpact(BaseMetricCalculator):
5+
def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None:
6+
super().__init__(baseline, reformed, default_period)
7+
self.baseline = baseline
8+
self.reformed = reformed
9+
10+
def calculate(self):
11+
12+
baseline_total_tax = self.baseline.calculate("household_tax").sum()
13+
reformed_total_tax = self.reformed.calculate("household_tax").sum()
14+
15+
tax_revenue_impact = reformed_total_tax - baseline_total_tax
16+
17+
baseline_total_benefits = self.baseline.calculate("household_benefits").sum()
18+
reformed_total_benefits = self.reformed.calculate("household_benefits").sum()
19+
20+
21+
benefit_spending_impact = reformed_total_benefits - baseline_total_benefits
22+
23+
budgetary_impact = tax_revenue_impact - benefit_spending_impact
24+
25+
26+
27+
return {
28+
"budgetary_impact" : round(budgetary_impact,2)
29+
}
30+
31+
class BenefitSpendingImpact(BaseMetricCalculator):
32+
def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None:
33+
super().__init__(baseline, reformed, default_period)
34+
self.baseline = baseline
35+
self.reformed = reformed
36+
37+
def calculate(self):
38+
39+
baseline_total_benefits = self.baseline.calculate("household_benefits").sum()
40+
reformed_total_benefits = self.reformed.calculate("household_benefits").sum()
41+
42+
43+
benefit_spending_impact = reformed_total_benefits - baseline_total_benefits
44+
45+
46+
47+
return {
48+
"baseline_total_benefits": round(baseline_total_benefits,2),
49+
"reformed_total_benefits": round(reformed_total_benefits,2),
50+
"benefit_spending_impact": round(benefit_spending_impact,2)
51+
}
52+
53+
class TaxRevenueImpact(BaseMetricCalculator):
54+
def __init__(self, baseline: Microsimulation, reformed: Microsimulation, default_period: int = 2024) -> None:
55+
super().__init__(baseline, reformed, default_period)
56+
self.baseline = baseline
57+
self.reformed = reformed
58+
59+
def calculate(self):
60+
61+
baseline_total_tax = self.baseline.calculate("household_tax").sum()
62+
reformed_total_tax = self.reformed.calculate("household_tax").sum()
63+
64+
tax_revenue_impact = reformed_total_tax - baseline_total_tax
65+
66+
67+
return {
68+
"baseline_total_tax": round(baseline_total_tax,2),
69+
"reformed_total_tax": round(reformed_total_tax,2),
70+
"tax_revenue_impact": round(tax_revenue_impact,2)
71+
}

policyengine/economic_impact/economic_impact.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@
3636
PensionCredit
3737
)
3838

39+
from .budgetary_impact.overall.overall import (
40+
BudgetaryImpact,
41+
BenefitSpendingImpact,
42+
TaxRevenueImpact
43+
)
44+
3945
from typing import Dict
4046

4147
class EconomicImpact:
@@ -69,6 +75,9 @@ def __init__(self, reform: dict, country: str) -> None:
6975

7076
# Set up metric calculators
7177
self.metric_calculators: Dict[str, object] = {
78+
"budgetary/overall/budgetary_impact" : BudgetaryImpact(self.baseline, self.reformed),
79+
"budgetary/overall/benefit_spending_impact" : BenefitSpendingImpact(self.baseline, self.reformed),
80+
"budgetary/overall/tax_revenue_impact" : TaxRevenueImpact(self.baseline, self.reformed),
7281
"budgetary/by_program/income_tax" : IncomeTax(self.baseline, self.reformed),
7382
"budgetary/by_program/national_insurance" : NationalInsurance(self.baseline, self.reformed),
7483
"budgetary/by_program/vat" : Vat(self.baseline, self.reformed),

policyengine/tests/economic_impact/budgetary_impact/by_program/test_by_program.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import os
44
from policyengine import EconomicImpact
55

6-
def assert_dict_approx_equal(actual, expected, tolerance=1e-4):
6+
def assert_dict_approx_equal(actual, expected, tolerance=1e3):
77
for key in expected:
88
assert abs(actual[key] - expected[key]) < tolerance, f"Key {key}: expected {expected[key]}, got {actual[key]}"
99

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# overall
2+
- test_budgetary_impact:
3+
reform:
4+
gov.hmrc.income_tax.rates.uk[0].rate:
5+
"2024-01-01.2100-12-31": 0.55
6+
country: uk
7+
expected:
8+
budgetary_impact: 203274712297.14
9+
10+
- test_benefit_spending_impact:
11+
reform:
12+
gov.hmrc.income_tax.rates.uk[0].rate:
13+
"2024-01-01.2100-12-31": 0.55
14+
country: uk
15+
expected:
16+
baseline_total_benefits: 247160184562.67
17+
reformed_total_benefits: 249032006583.15
18+
benefit_spending_impact: 1871822020.49
19+
20+
- test_tax_revenue_impact:
21+
reform:
22+
gov.hmrc.income_tax.rates.uk[0].rate:
23+
"2024-01-01.2100-12-31": 0.55
24+
country: uk
25+
expected:
26+
baseline_total_tax: 447861864968.89
27+
reformed_total_tax: 653008399286.52
28+
tax_revenue_impact: 205146534317.63
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import pytest
2+
import yaml
3+
import os
4+
from policyengine import EconomicImpact
5+
6+
def assert_dict_approx_equal(actual, expected, tolerance=1e3):
7+
for key in expected:
8+
assert abs(actual[key] - expected[key]) < tolerance, f"Key {key}: expected {expected[key]}, got {actual[key]}"
9+
10+
11+
yaml_file_path = "policyengine/tests/economic_impact/budgetary_impact/overall/overall.yaml"
12+
13+
# Check if the file exists
14+
if not os.path.exists(yaml_file_path):
15+
raise FileNotFoundError(f"The YAML file does not exist at: {yaml_file_path}")
16+
17+
with open(yaml_file_path, 'r') as file:
18+
test_cases = yaml.safe_load(file)
19+
20+
@pytest.mark.parametrize("test_case", test_cases)
21+
def test_economic_impact(test_case):
22+
test_name = list(test_case.keys())[0]
23+
test_data = test_case[test_name]
24+
25+
economic_impact = EconomicImpact(test_data['reform'], test_data['country'])
26+
27+
if 'budgetary' in test_name:
28+
result = economic_impact.calculate("budgetary/overall/budgetary_impact")
29+
elif 'benefit' in test_name:
30+
result = economic_impact.calculate("budgetary/overall/benefit_spending_impact")
31+
elif 'tax' in test_name:
32+
result = economic_impact.calculate("budgetary/overall/tax_revenue_impact")
33+
else:
34+
pytest.fail(f"Unknown test case: {test_name}")
35+
36+
assert_dict_approx_equal(result, test_data['expected'])
37+
38+
if __name__ == "__main__":
39+
pytest.main([__file__])

0 commit comments

Comments
 (0)