Skip to content

Commit ae8b340

Browse files
Merge pull request #202 from PolicyEngine/dev
Add household impacts
2 parents 75a914e + 57fa570 commit ae8b340

File tree

13 files changed

+757
-22
lines changed

13 files changed

+757
-22
lines changed

changelog_entry.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- bump: patch
2+
changes:
3+
added:
4+
- Household impacts
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
"""Example: Calculate household tax and benefit impacts.
2+
3+
This script demonstrates using calculate_household_impact for both UK and US
4+
to compute taxes and benefits for custom households.
5+
6+
Run: python examples/household_impact_example.py
7+
"""
8+
9+
from policyengine.tax_benefit_models.uk import (
10+
UKHouseholdInput,
11+
)
12+
from policyengine.tax_benefit_models.uk import (
13+
calculate_household_impact as calculate_uk_impact,
14+
)
15+
from policyengine.tax_benefit_models.us import (
16+
USHouseholdInput,
17+
)
18+
from policyengine.tax_benefit_models.us import (
19+
calculate_household_impact as calculate_us_impact,
20+
)
21+
22+
23+
def uk_example():
24+
"""UK household impact example."""
25+
print("=" * 60)
26+
print("UK HOUSEHOLD IMPACT")
27+
print("=" * 60)
28+
29+
# Single adult earning £50,000
30+
household = UKHouseholdInput(
31+
people=[{"age": 35, "employment_income": 50_000}],
32+
year=2026,
33+
)
34+
result = calculate_uk_impact(household)
35+
36+
print("\nSingle adult, £50k income:")
37+
print(
38+
f" Net income: £{result.household['hbai_household_net_income']:,.0f}"
39+
)
40+
print(f" Income tax: £{result.person[0]['income_tax']:,.0f}")
41+
print(
42+
f" National Insurance: £{result.person[0]['national_insurance']:,.0f}"
43+
)
44+
print(f" Total tax: £{result.household['household_tax']:,.0f}")
45+
46+
# Family with two children, £30k income, renting
47+
household = UKHouseholdInput(
48+
people=[
49+
{"age": 35, "employment_income": 30_000},
50+
{"age": 33},
51+
{"age": 8},
52+
{"age": 5},
53+
],
54+
benunit={
55+
"would_claim_uc": True,
56+
"would_claim_child_benefit": True,
57+
},
58+
household={
59+
"rent": 12_000, # £1k/month
60+
"region": "NORTH_WEST",
61+
},
62+
year=2026,
63+
)
64+
result = calculate_uk_impact(household)
65+
66+
print("\nFamily (2 adults, 2 children), £30k income, renting:")
67+
print(
68+
f" Net income: £{result.household['hbai_household_net_income']:,.0f}"
69+
)
70+
print(f" Income tax: £{result.person[0]['income_tax']:,.0f}")
71+
print(f" Child benefit: £{result.benunit[0]['child_benefit']:,.0f}")
72+
print(f" Universal credit: £{result.benunit[0]['universal_credit']:,.0f}")
73+
print(f" Total benefits: £{result.household['household_benefits']:,.0f}")
74+
75+
76+
def us_example():
77+
"""US household impact example."""
78+
print("\n" + "=" * 60)
79+
print("US HOUSEHOLD IMPACT")
80+
print("=" * 60)
81+
82+
# Single adult earning $50,000
83+
household = USHouseholdInput(
84+
people=[
85+
{"age": 35, "employment_income": 50_000, "is_tax_unit_head": True}
86+
],
87+
tax_unit={"filing_status": "SINGLE"},
88+
household={"state_code_str": "CA"},
89+
year=2024,
90+
)
91+
result = calculate_us_impact(household)
92+
93+
print("\nSingle adult, $50k income (California):")
94+
print(f" Net income: ${result.household['household_net_income']:,.0f}")
95+
print(f" Income tax: ${result.tax_unit[0]['income_tax']:,.0f}")
96+
print(f" Payroll tax: ${result.tax_unit[0]['employee_payroll_tax']:,.0f}")
97+
98+
# Married couple with children, lower income
99+
household = USHouseholdInput(
100+
people=[
101+
{"age": 35, "employment_income": 40_000, "is_tax_unit_head": True},
102+
{"age": 33, "is_tax_unit_spouse": True},
103+
{"age": 8, "is_tax_unit_dependent": True},
104+
{"age": 5, "is_tax_unit_dependent": True},
105+
],
106+
tax_unit={"filing_status": "JOINT"},
107+
household={"state_code_str": "TX"},
108+
year=2024,
109+
)
110+
result = calculate_us_impact(household)
111+
112+
print("\nMarried couple with 2 children, $40k income (Texas):")
113+
print(f" Net income: ${result.household['household_net_income']:,.0f}")
114+
print(f" Federal income tax: ${result.tax_unit[0]['income_tax']:,.0f}")
115+
print(f" EITC: ${result.tax_unit[0]['eitc']:,.0f}")
116+
print(f" Child tax credit: ${result.tax_unit[0]['ctc']:,.0f}")
117+
print(f" SNAP: ${result.spm_unit[0]['snap']:,.0f}")
118+
119+
120+
def main():
121+
uk_example()
122+
us_example()
123+
print("\n" + "=" * 60)
124+
print("Done!")
125+
126+
127+
if __name__ == "__main__":
128+
main()

src/policyengine/core/tax_benefit_model_version.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ class TaxBenefitModelVersion(BaseModel):
2828
@property
2929
def parameter_values(self) -> list["ParameterValue"]:
3030
"""Aggregate all parameter values from all parameters."""
31-
yield from (
31+
return [
3232
pv
3333
for parameter in self.parameters
3434
for pv in parameter.parameter_values
35-
)
35+
]
3636

3737
# Lookup dicts for O(1) access (excluded from serialization)
3838
variables_by_name: dict[str, "Variable"] = Field(

src/policyengine/outputs/decile_impact.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
from pydantic import ConfigDict
33

44
from policyengine.core import Output, OutputCollection, Simulation
5+
from policyengine.core.dataset import Dataset
6+
from policyengine.core.dynamic import Dynamic
7+
from policyengine.core.policy import Policy
8+
from policyengine.core.tax_benefit_model_version import TaxBenefitModelVersion
59

610

711
class DecileImpact(Output):
@@ -93,8 +97,11 @@ def run(self):
9397

9498

9599
def calculate_decile_impacts(
96-
baseline_simulation: Simulation,
97-
reform_simulation: Simulation,
100+
dataset: Dataset,
101+
tax_benefit_model_version: TaxBenefitModelVersion,
102+
baseline_policy: Policy | None = None,
103+
reform_policy: Policy | None = None,
104+
dynamic: Dynamic | None = None,
98105
income_variable: str = "equiv_hbai_household_net_income",
99106
entity: str | None = None,
100107
quantiles: int = 10,
@@ -104,6 +111,19 @@ def calculate_decile_impacts(
104111
Returns:
105112
OutputCollection containing list of DecileImpact objects and DataFrame
106113
"""
114+
baseline_simulation = Simulation(
115+
dataset=dataset,
116+
tax_benefit_model_version=tax_benefit_model_version,
117+
policy=baseline_policy,
118+
dynamic=dynamic,
119+
)
120+
reform_simulation = Simulation(
121+
dataset=dataset,
122+
tax_benefit_model_version=tax_benefit_model_version,
123+
policy=reform_policy,
124+
dynamic=dynamic,
125+
)
126+
107127
results = []
108128
for decile in range(1, quantiles + 1):
109129
impact = DecileImpact(

src/policyengine/tax_benefit_models/uk/__init__.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55
if find_spec("policyengine_uk") is not None:
66
from policyengine.core import Dataset
77

8-
from .analysis import general_policy_reform_analysis
8+
from .analysis import (
9+
UKHouseholdInput,
10+
UKHouseholdOutput,
11+
calculate_household_impact,
12+
economic_impact_analysis,
13+
)
914
from .datasets import (
1015
PolicyEngineUKDataset,
1116
UKYearData,
@@ -37,7 +42,10 @@
3742
"PolicyEngineUKLatest",
3843
"uk_model",
3944
"uk_latest",
40-
"general_policy_reform_analysis",
45+
"economic_impact_analysis",
46+
"calculate_household_impact",
47+
"UKHouseholdInput",
48+
"UKHouseholdOutput",
4149
"ProgrammeStatistics",
4250
]
4351
else:

0 commit comments

Comments
 (0)