policyengine 3.1.14__py3-none-any.whl → 3.1.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- policyengine/__pycache__/__init__.cpython-313.pyc +0 -0
- policyengine/core/tax_benefit_model_version.py +9 -1
- policyengine/outputs/decile_impact.py +22 -2
- policyengine/tax_benefit_models/uk/__init__.py +10 -2
- policyengine/tax_benefit_models/uk/analysis.py +175 -4
- policyengine/tax_benefit_models/uk/model.py +5 -2
- policyengine/tax_benefit_models/us/__init__.py +10 -2
- policyengine/tax_benefit_models/us/analysis.py +193 -4
- policyengine/tax_benefit_models/us/model.py +0 -2
- {policyengine-3.1.14.dist-info → policyengine-3.1.15.dist-info}/METADATA +1 -1
- {policyengine-3.1.14.dist-info → policyengine-3.1.15.dist-info}/RECORD +14 -14
- {policyengine-3.1.14.dist-info → policyengine-3.1.15.dist-info}/WHEEL +0 -0
- {policyengine-3.1.14.dist-info → policyengine-3.1.15.dist-info}/licenses/LICENSE +0 -0
- {policyengine-3.1.14.dist-info → policyengine-3.1.15.dist-info}/top_level.txt +0 -0
|
Binary file
|
|
@@ -24,7 +24,15 @@ class TaxBenefitModelVersion(BaseModel):
|
|
|
24
24
|
|
|
25
25
|
variables: list["Variable"] = Field(default_factory=list)
|
|
26
26
|
parameters: list["Parameter"] = Field(default_factory=list)
|
|
27
|
-
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def parameter_values(self) -> list["ParameterValue"]:
|
|
30
|
+
"""Aggregate all parameter values from all parameters."""
|
|
31
|
+
return [
|
|
32
|
+
pv
|
|
33
|
+
for parameter in self.parameters
|
|
34
|
+
for pv in parameter.parameter_values
|
|
35
|
+
]
|
|
28
36
|
|
|
29
37
|
# Lookup dicts for O(1) access (excluded from serialization)
|
|
30
38
|
variables_by_name: dict[str, "Variable"] = Field(
|
|
@@ -2,6 +2,10 @@ import pandas as pd
|
|
|
2
2
|
from pydantic import ConfigDict
|
|
3
3
|
|
|
4
4
|
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
|
|
5
9
|
|
|
6
10
|
|
|
7
11
|
class DecileImpact(Output):
|
|
@@ -93,8 +97,11 @@ class DecileImpact(Output):
|
|
|
93
97
|
|
|
94
98
|
|
|
95
99
|
def calculate_decile_impacts(
|
|
96
|
-
|
|
97
|
-
|
|
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,
|
|
98
105
|
income_variable: str = "equiv_hbai_household_net_income",
|
|
99
106
|
entity: str | None = None,
|
|
100
107
|
quantiles: int = 10,
|
|
@@ -104,6 +111,19 @@ def calculate_decile_impacts(
|
|
|
104
111
|
Returns:
|
|
105
112
|
OutputCollection containing list of DecileImpact objects and DataFrame
|
|
106
113
|
"""
|
|
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
|
+
|
|
107
127
|
results = []
|
|
108
128
|
for decile in range(1, quantiles + 1):
|
|
109
129
|
impact = DecileImpact(
|
|
@@ -5,7 +5,12 @@ from importlib.util import find_spec
|
|
|
5
5
|
if find_spec("policyengine_uk") is not None:
|
|
6
6
|
from policyengine.core import Dataset
|
|
7
7
|
|
|
8
|
-
from .analysis import
|
|
8
|
+
from .analysis import (
|
|
9
|
+
UKHouseholdInput,
|
|
10
|
+
UKHouseholdOutput,
|
|
11
|
+
calculate_household_impact,
|
|
12
|
+
economic_impact_analysis,
|
|
13
|
+
)
|
|
9
14
|
from .datasets import (
|
|
10
15
|
PolicyEngineUKDataset,
|
|
11
16
|
UKYearData,
|
|
@@ -37,7 +42,10 @@ if find_spec("policyengine_uk") is not None:
|
|
|
37
42
|
"PolicyEngineUKLatest",
|
|
38
43
|
"uk_model",
|
|
39
44
|
"uk_latest",
|
|
40
|
-
"
|
|
45
|
+
"economic_impact_analysis",
|
|
46
|
+
"calculate_household_impact",
|
|
47
|
+
"UKHouseholdInput",
|
|
48
|
+
"UKHouseholdOutput",
|
|
41
49
|
"ProgrammeStatistics",
|
|
42
50
|
]
|
|
43
51
|
else:
|
|
@@ -1,17 +1,175 @@
|
|
|
1
1
|
"""General utility functions for UK policy reform analysis."""
|
|
2
2
|
|
|
3
|
+
import tempfile
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
3
7
|
import pandas as pd
|
|
4
|
-
from
|
|
8
|
+
from microdf import MicroDataFrame
|
|
9
|
+
from pydantic import BaseModel, Field, create_model
|
|
5
10
|
|
|
6
11
|
from policyengine.core import OutputCollection, Simulation
|
|
12
|
+
from policyengine.core.policy import Policy
|
|
7
13
|
from policyengine.outputs.decile_impact import (
|
|
8
14
|
DecileImpact,
|
|
9
15
|
calculate_decile_impacts,
|
|
10
16
|
)
|
|
11
17
|
|
|
18
|
+
from .datasets import PolicyEngineUKDataset, UKYearData
|
|
19
|
+
from .model import uk_latest
|
|
12
20
|
from .outputs import ProgrammeStatistics
|
|
13
21
|
|
|
14
22
|
|
|
23
|
+
def _create_entity_output_model(
|
|
24
|
+
entity: str, variables: list[str]
|
|
25
|
+
) -> type[BaseModel]:
|
|
26
|
+
"""Create a dynamic Pydantic model for entity output variables."""
|
|
27
|
+
fields = {var: (float, ...) for var in variables}
|
|
28
|
+
return create_model(f"{entity.title()}Output", **fields)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Create output models dynamically from uk_latest.entity_variables
|
|
32
|
+
PersonOutput = _create_entity_output_model(
|
|
33
|
+
"person", uk_latest.entity_variables["person"]
|
|
34
|
+
)
|
|
35
|
+
BenunitOutput = _create_entity_output_model(
|
|
36
|
+
"benunit", uk_latest.entity_variables["benunit"]
|
|
37
|
+
)
|
|
38
|
+
HouseholdEntityOutput = _create_entity_output_model(
|
|
39
|
+
"household", uk_latest.entity_variables["household"]
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class UKHouseholdOutput(BaseModel):
|
|
44
|
+
"""Output from a UK household calculation with all entity data."""
|
|
45
|
+
|
|
46
|
+
person: list[dict[str, Any]]
|
|
47
|
+
benunit: list[dict[str, Any]]
|
|
48
|
+
household: dict[str, Any]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class UKHouseholdInput(BaseModel):
|
|
52
|
+
"""Input for a UK household calculation."""
|
|
53
|
+
|
|
54
|
+
people: list[dict[str, Any]]
|
|
55
|
+
benunit: dict[str, Any] = Field(default_factory=dict)
|
|
56
|
+
household: dict[str, Any] = Field(default_factory=dict)
|
|
57
|
+
year: int = 2026
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def calculate_household_impact(
|
|
61
|
+
household_input: UKHouseholdInput,
|
|
62
|
+
policy: Policy | None = None,
|
|
63
|
+
) -> UKHouseholdOutput:
|
|
64
|
+
"""Calculate tax and benefit impacts for a single UK household."""
|
|
65
|
+
n_people = len(household_input.people)
|
|
66
|
+
|
|
67
|
+
# Build person data with defaults
|
|
68
|
+
person_data = {
|
|
69
|
+
"person_id": list(range(n_people)),
|
|
70
|
+
"person_benunit_id": [0] * n_people,
|
|
71
|
+
"person_household_id": [0] * n_people,
|
|
72
|
+
"person_weight": [1.0] * n_people,
|
|
73
|
+
}
|
|
74
|
+
# Add user-provided person fields
|
|
75
|
+
for i, person in enumerate(household_input.people):
|
|
76
|
+
for key, value in person.items():
|
|
77
|
+
if key not in person_data:
|
|
78
|
+
person_data[key] = [
|
|
79
|
+
0.0
|
|
80
|
+
] * n_people # Default to 0 for numeric fields
|
|
81
|
+
person_data[key][i] = value
|
|
82
|
+
|
|
83
|
+
# Build benunit data with defaults
|
|
84
|
+
benunit_data = {
|
|
85
|
+
"benunit_id": [0],
|
|
86
|
+
"benunit_weight": [1.0],
|
|
87
|
+
}
|
|
88
|
+
for key, value in household_input.benunit.items():
|
|
89
|
+
benunit_data[key] = [value]
|
|
90
|
+
|
|
91
|
+
# Build household data with defaults (required for uprating)
|
|
92
|
+
household_data = {
|
|
93
|
+
"household_id": [0],
|
|
94
|
+
"household_weight": [1.0],
|
|
95
|
+
"region": ["LONDON"],
|
|
96
|
+
"tenure_type": ["RENT_PRIVATELY"],
|
|
97
|
+
"council_tax": [0.0],
|
|
98
|
+
"rent": [0.0],
|
|
99
|
+
}
|
|
100
|
+
for key, value in household_input.household.items():
|
|
101
|
+
household_data[key] = [value]
|
|
102
|
+
|
|
103
|
+
# Create MicroDataFrames
|
|
104
|
+
person_df = MicroDataFrame(
|
|
105
|
+
pd.DataFrame(person_data), weights="person_weight"
|
|
106
|
+
)
|
|
107
|
+
benunit_df = MicroDataFrame(
|
|
108
|
+
pd.DataFrame(benunit_data), weights="benunit_weight"
|
|
109
|
+
)
|
|
110
|
+
household_df = MicroDataFrame(
|
|
111
|
+
pd.DataFrame(household_data), weights="household_weight"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Create temporary dataset
|
|
115
|
+
tmpdir = tempfile.mkdtemp()
|
|
116
|
+
filepath = str(Path(tmpdir) / "household_impact.h5")
|
|
117
|
+
|
|
118
|
+
dataset = PolicyEngineUKDataset(
|
|
119
|
+
name="Household impact calculation",
|
|
120
|
+
description="Single household for impact calculation",
|
|
121
|
+
filepath=filepath,
|
|
122
|
+
year=household_input.year,
|
|
123
|
+
data=UKYearData(
|
|
124
|
+
person=person_df,
|
|
125
|
+
benunit=benunit_df,
|
|
126
|
+
household=household_df,
|
|
127
|
+
),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Run simulation
|
|
131
|
+
simulation = Simulation(
|
|
132
|
+
dataset=dataset,
|
|
133
|
+
tax_benefit_model_version=uk_latest,
|
|
134
|
+
policy=policy,
|
|
135
|
+
)
|
|
136
|
+
simulation.run()
|
|
137
|
+
|
|
138
|
+
# Extract all output variables defined in entity_variables
|
|
139
|
+
output_data = simulation.output_dataset.data
|
|
140
|
+
|
|
141
|
+
def safe_convert(value):
|
|
142
|
+
"""Convert value to float if numeric, otherwise return as string."""
|
|
143
|
+
try:
|
|
144
|
+
return float(value)
|
|
145
|
+
except (ValueError, TypeError):
|
|
146
|
+
return str(value)
|
|
147
|
+
|
|
148
|
+
person_outputs = []
|
|
149
|
+
for i in range(n_people):
|
|
150
|
+
person_dict = {}
|
|
151
|
+
for var in uk_latest.entity_variables["person"]:
|
|
152
|
+
person_dict[var] = safe_convert(output_data.person[var].iloc[i])
|
|
153
|
+
person_outputs.append(person_dict)
|
|
154
|
+
|
|
155
|
+
benunit_outputs = []
|
|
156
|
+
for i in range(len(output_data.benunit)):
|
|
157
|
+
benunit_dict = {}
|
|
158
|
+
for var in uk_latest.entity_variables["benunit"]:
|
|
159
|
+
benunit_dict[var] = safe_convert(output_data.benunit[var].iloc[i])
|
|
160
|
+
benunit_outputs.append(benunit_dict)
|
|
161
|
+
|
|
162
|
+
household_dict = {}
|
|
163
|
+
for var in uk_latest.entity_variables["household"]:
|
|
164
|
+
household_dict[var] = safe_convert(output_data.household[var].iloc[0])
|
|
165
|
+
|
|
166
|
+
return UKHouseholdOutput(
|
|
167
|
+
person=person_outputs,
|
|
168
|
+
benunit=benunit_outputs,
|
|
169
|
+
household=household_dict,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
15
173
|
class PolicyReformAnalysis(BaseModel):
|
|
16
174
|
"""Complete policy reform analysis result."""
|
|
17
175
|
|
|
@@ -19,7 +177,7 @@ class PolicyReformAnalysis(BaseModel):
|
|
|
19
177
|
programme_statistics: OutputCollection[ProgrammeStatistics]
|
|
20
178
|
|
|
21
179
|
|
|
22
|
-
def
|
|
180
|
+
def economic_impact_analysis(
|
|
23
181
|
baseline_simulation: Simulation,
|
|
24
182
|
reform_simulation: Simulation,
|
|
25
183
|
) -> PolicyReformAnalysis:
|
|
@@ -28,10 +186,23 @@ def general_policy_reform_analysis(
|
|
|
28
186
|
Returns:
|
|
29
187
|
PolicyReformAnalysis containing decile impacts and programme statistics
|
|
30
188
|
"""
|
|
189
|
+
baseline_simulation.ensure()
|
|
190
|
+
reform_simulation.ensure()
|
|
191
|
+
|
|
192
|
+
assert len(baseline_simulation.dataset.data.household) > 100, (
|
|
193
|
+
"Baseline simulation must have more than 100 households"
|
|
194
|
+
)
|
|
195
|
+
assert len(reform_simulation.dataset.data.household) > 100, (
|
|
196
|
+
"Reform simulation must have more than 100 households"
|
|
197
|
+
)
|
|
198
|
+
|
|
31
199
|
# Decile impact
|
|
32
200
|
decile_impacts = calculate_decile_impacts(
|
|
33
|
-
|
|
34
|
-
|
|
201
|
+
dataset=baseline_simulation.dataset,
|
|
202
|
+
tax_benefit_model_version=baseline_simulation.tax_benefit_model_version,
|
|
203
|
+
baseline_policy=baseline_simulation.policy,
|
|
204
|
+
reform_policy=reform_simulation.policy,
|
|
205
|
+
dynamic=baseline_simulation.dynamic,
|
|
35
206
|
)
|
|
36
207
|
|
|
37
208
|
# Major programmes to analyse
|
|
@@ -9,12 +9,10 @@ from microdf import MicroDataFrame
|
|
|
9
9
|
|
|
10
10
|
from policyengine.core import (
|
|
11
11
|
Parameter,
|
|
12
|
-
ParameterValue,
|
|
13
12
|
TaxBenefitModel,
|
|
14
13
|
TaxBenefitModelVersion,
|
|
15
14
|
Variable,
|
|
16
15
|
)
|
|
17
|
-
from policyengine.utils import parse_safe_date
|
|
18
16
|
|
|
19
17
|
from .datasets import PolicyEngineUKDataset, UKYearData
|
|
20
18
|
|
|
@@ -108,6 +106,11 @@ class PolicyEngineUKLatest(TaxBenefitModelVersion):
|
|
|
108
106
|
"rent",
|
|
109
107
|
"council_tax",
|
|
110
108
|
"tenure_type",
|
|
109
|
+
# Poverty measures
|
|
110
|
+
"in_poverty_bhc",
|
|
111
|
+
"in_poverty_ahc",
|
|
112
|
+
"in_relative_poverty_bhc",
|
|
113
|
+
"in_relative_poverty_ahc",
|
|
111
114
|
],
|
|
112
115
|
}
|
|
113
116
|
|
|
@@ -5,7 +5,12 @@ from importlib.util import find_spec
|
|
|
5
5
|
if find_spec("policyengine_us") is not None:
|
|
6
6
|
from policyengine.core import Dataset
|
|
7
7
|
|
|
8
|
-
from .analysis import
|
|
8
|
+
from .analysis import (
|
|
9
|
+
USHouseholdInput,
|
|
10
|
+
USHouseholdOutput,
|
|
11
|
+
calculate_household_impact,
|
|
12
|
+
economic_impact_analysis,
|
|
13
|
+
)
|
|
9
14
|
from .datasets import (
|
|
10
15
|
PolicyEngineUSDataset,
|
|
11
16
|
USYearData,
|
|
@@ -37,7 +42,10 @@ if find_spec("policyengine_us") is not None:
|
|
|
37
42
|
"PolicyEngineUSLatest",
|
|
38
43
|
"us_model",
|
|
39
44
|
"us_latest",
|
|
40
|
-
"
|
|
45
|
+
"economic_impact_analysis",
|
|
46
|
+
"calculate_household_impact",
|
|
47
|
+
"USHouseholdInput",
|
|
48
|
+
"USHouseholdOutput",
|
|
41
49
|
"ProgramStatistics",
|
|
42
50
|
]
|
|
43
51
|
else:
|
|
@@ -1,17 +1,193 @@
|
|
|
1
1
|
"""General utility functions for US policy reform analysis."""
|
|
2
2
|
|
|
3
|
+
import tempfile
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
3
7
|
import pandas as pd
|
|
4
|
-
from
|
|
8
|
+
from microdf import MicroDataFrame
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
5
10
|
|
|
6
11
|
from policyengine.core import OutputCollection, Simulation
|
|
12
|
+
from policyengine.core.policy import Policy
|
|
7
13
|
from policyengine.outputs.decile_impact import (
|
|
8
14
|
DecileImpact,
|
|
9
15
|
calculate_decile_impacts,
|
|
10
16
|
)
|
|
11
17
|
|
|
18
|
+
from .datasets import PolicyEngineUSDataset, USYearData
|
|
19
|
+
from .model import us_latest
|
|
12
20
|
from .outputs import ProgramStatistics
|
|
13
21
|
|
|
14
22
|
|
|
23
|
+
class USHouseholdOutput(BaseModel):
|
|
24
|
+
"""Output from a US household calculation with all entity data."""
|
|
25
|
+
|
|
26
|
+
person: list[dict[str, Any]]
|
|
27
|
+
marital_unit: list[dict[str, Any]]
|
|
28
|
+
family: list[dict[str, Any]]
|
|
29
|
+
spm_unit: list[dict[str, Any]]
|
|
30
|
+
tax_unit: list[dict[str, Any]]
|
|
31
|
+
household: dict[str, Any]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class USHouseholdInput(BaseModel):
|
|
35
|
+
"""Input for a US household calculation."""
|
|
36
|
+
|
|
37
|
+
people: list[dict[str, Any]]
|
|
38
|
+
marital_unit: dict[str, Any] = Field(default_factory=dict)
|
|
39
|
+
family: dict[str, Any] = Field(default_factory=dict)
|
|
40
|
+
spm_unit: dict[str, Any] = Field(default_factory=dict)
|
|
41
|
+
tax_unit: dict[str, Any] = Field(default_factory=dict)
|
|
42
|
+
household: dict[str, Any] = Field(default_factory=dict)
|
|
43
|
+
year: int = 2024
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def calculate_household_impact(
|
|
47
|
+
household_input: USHouseholdInput,
|
|
48
|
+
policy: Policy | None = None,
|
|
49
|
+
) -> USHouseholdOutput:
|
|
50
|
+
"""Calculate tax and benefit impacts for a single US household."""
|
|
51
|
+
n_people = len(household_input.people)
|
|
52
|
+
|
|
53
|
+
# Build person data with defaults
|
|
54
|
+
person_data = {
|
|
55
|
+
"person_id": list(range(n_people)),
|
|
56
|
+
"person_household_id": [0] * n_people,
|
|
57
|
+
"person_marital_unit_id": [0] * n_people,
|
|
58
|
+
"person_family_id": [0] * n_people,
|
|
59
|
+
"person_spm_unit_id": [0] * n_people,
|
|
60
|
+
"person_tax_unit_id": [0] * n_people,
|
|
61
|
+
"person_weight": [1.0] * n_people,
|
|
62
|
+
}
|
|
63
|
+
# Add user-provided person fields
|
|
64
|
+
for i, person in enumerate(household_input.people):
|
|
65
|
+
for key, value in person.items():
|
|
66
|
+
if key not in person_data:
|
|
67
|
+
person_data[key] = [
|
|
68
|
+
0.0
|
|
69
|
+
] * n_people # Default to 0 for numeric fields
|
|
70
|
+
person_data[key][i] = value
|
|
71
|
+
|
|
72
|
+
# Build entity data with defaults
|
|
73
|
+
household_data = {
|
|
74
|
+
"household_id": [0],
|
|
75
|
+
"household_weight": [1.0],
|
|
76
|
+
}
|
|
77
|
+
for key, value in household_input.household.items():
|
|
78
|
+
household_data[key] = [value]
|
|
79
|
+
|
|
80
|
+
marital_unit_data = {
|
|
81
|
+
"marital_unit_id": [0],
|
|
82
|
+
"marital_unit_weight": [1.0],
|
|
83
|
+
}
|
|
84
|
+
for key, value in household_input.marital_unit.items():
|
|
85
|
+
marital_unit_data[key] = [value]
|
|
86
|
+
|
|
87
|
+
family_data = {
|
|
88
|
+
"family_id": [0],
|
|
89
|
+
"family_weight": [1.0],
|
|
90
|
+
}
|
|
91
|
+
for key, value in household_input.family.items():
|
|
92
|
+
family_data[key] = [value]
|
|
93
|
+
|
|
94
|
+
spm_unit_data = {
|
|
95
|
+
"spm_unit_id": [0],
|
|
96
|
+
"spm_unit_weight": [1.0],
|
|
97
|
+
}
|
|
98
|
+
for key, value in household_input.spm_unit.items():
|
|
99
|
+
spm_unit_data[key] = [value]
|
|
100
|
+
|
|
101
|
+
tax_unit_data = {
|
|
102
|
+
"tax_unit_id": [0],
|
|
103
|
+
"tax_unit_weight": [1.0],
|
|
104
|
+
}
|
|
105
|
+
for key, value in household_input.tax_unit.items():
|
|
106
|
+
tax_unit_data[key] = [value]
|
|
107
|
+
|
|
108
|
+
# Create MicroDataFrames
|
|
109
|
+
person_df = MicroDataFrame(
|
|
110
|
+
pd.DataFrame(person_data), weights="person_weight"
|
|
111
|
+
)
|
|
112
|
+
household_df = MicroDataFrame(
|
|
113
|
+
pd.DataFrame(household_data), weights="household_weight"
|
|
114
|
+
)
|
|
115
|
+
marital_unit_df = MicroDataFrame(
|
|
116
|
+
pd.DataFrame(marital_unit_data), weights="marital_unit_weight"
|
|
117
|
+
)
|
|
118
|
+
family_df = MicroDataFrame(
|
|
119
|
+
pd.DataFrame(family_data), weights="family_weight"
|
|
120
|
+
)
|
|
121
|
+
spm_unit_df = MicroDataFrame(
|
|
122
|
+
pd.DataFrame(spm_unit_data), weights="spm_unit_weight"
|
|
123
|
+
)
|
|
124
|
+
tax_unit_df = MicroDataFrame(
|
|
125
|
+
pd.DataFrame(tax_unit_data), weights="tax_unit_weight"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Create temporary dataset
|
|
129
|
+
tmpdir = tempfile.mkdtemp()
|
|
130
|
+
filepath = str(Path(tmpdir) / "household_impact.h5")
|
|
131
|
+
|
|
132
|
+
dataset = PolicyEngineUSDataset(
|
|
133
|
+
name="Household impact calculation",
|
|
134
|
+
description="Single household for impact calculation",
|
|
135
|
+
filepath=filepath,
|
|
136
|
+
year=household_input.year,
|
|
137
|
+
data=USYearData(
|
|
138
|
+
person=person_df,
|
|
139
|
+
household=household_df,
|
|
140
|
+
marital_unit=marital_unit_df,
|
|
141
|
+
family=family_df,
|
|
142
|
+
spm_unit=spm_unit_df,
|
|
143
|
+
tax_unit=tax_unit_df,
|
|
144
|
+
),
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Run simulation
|
|
148
|
+
simulation = Simulation(
|
|
149
|
+
dataset=dataset,
|
|
150
|
+
tax_benefit_model_version=us_latest,
|
|
151
|
+
policy=policy,
|
|
152
|
+
)
|
|
153
|
+
simulation.run()
|
|
154
|
+
|
|
155
|
+
# Extract all output variables defined in entity_variables
|
|
156
|
+
output_data = simulation.output_dataset.data
|
|
157
|
+
|
|
158
|
+
def safe_convert(value):
|
|
159
|
+
"""Convert value to float if numeric, otherwise return as string."""
|
|
160
|
+
try:
|
|
161
|
+
return float(value)
|
|
162
|
+
except (ValueError, TypeError):
|
|
163
|
+
return str(value)
|
|
164
|
+
|
|
165
|
+
def extract_entity_outputs(
|
|
166
|
+
entity_name: str, entity_data, n_rows: int
|
|
167
|
+
) -> list[dict[str, Any]]:
|
|
168
|
+
outputs = []
|
|
169
|
+
for i in range(n_rows):
|
|
170
|
+
row_dict = {}
|
|
171
|
+
for var in us_latest.entity_variables[entity_name]:
|
|
172
|
+
row_dict[var] = safe_convert(entity_data[var].iloc[i])
|
|
173
|
+
outputs.append(row_dict)
|
|
174
|
+
return outputs
|
|
175
|
+
|
|
176
|
+
return USHouseholdOutput(
|
|
177
|
+
person=extract_entity_outputs("person", output_data.person, n_people),
|
|
178
|
+
marital_unit=extract_entity_outputs(
|
|
179
|
+
"marital_unit", output_data.marital_unit, 1
|
|
180
|
+
),
|
|
181
|
+
family=extract_entity_outputs("family", output_data.family, 1),
|
|
182
|
+
spm_unit=extract_entity_outputs("spm_unit", output_data.spm_unit, 1),
|
|
183
|
+
tax_unit=extract_entity_outputs("tax_unit", output_data.tax_unit, 1),
|
|
184
|
+
household={
|
|
185
|
+
var: safe_convert(output_data.household[var].iloc[0])
|
|
186
|
+
for var in us_latest.entity_variables["household"]
|
|
187
|
+
},
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
15
191
|
class PolicyReformAnalysis(BaseModel):
|
|
16
192
|
"""Complete policy reform analysis result."""
|
|
17
193
|
|
|
@@ -19,7 +195,7 @@ class PolicyReformAnalysis(BaseModel):
|
|
|
19
195
|
program_statistics: OutputCollection[ProgramStatistics]
|
|
20
196
|
|
|
21
197
|
|
|
22
|
-
def
|
|
198
|
+
def economic_impact_analysis(
|
|
23
199
|
baseline_simulation: Simulation,
|
|
24
200
|
reform_simulation: Simulation,
|
|
25
201
|
) -> PolicyReformAnalysis:
|
|
@@ -28,10 +204,23 @@ def general_policy_reform_analysis(
|
|
|
28
204
|
Returns:
|
|
29
205
|
PolicyReformAnalysis containing decile impacts and program statistics
|
|
30
206
|
"""
|
|
207
|
+
baseline_simulation.ensure()
|
|
208
|
+
reform_simulation.ensure()
|
|
209
|
+
|
|
210
|
+
assert len(baseline_simulation.dataset.data.household) > 100, (
|
|
211
|
+
"Baseline simulation must have more than 100 households"
|
|
212
|
+
)
|
|
213
|
+
assert len(reform_simulation.dataset.data.household) > 100, (
|
|
214
|
+
"Reform simulation must have more than 100 households"
|
|
215
|
+
)
|
|
216
|
+
|
|
31
217
|
# Decile impact (using household_net_income for US)
|
|
32
218
|
decile_impacts = calculate_decile_impacts(
|
|
33
|
-
|
|
34
|
-
|
|
219
|
+
dataset=baseline_simulation.dataset,
|
|
220
|
+
tax_benefit_model_version=baseline_simulation.tax_benefit_model_version,
|
|
221
|
+
baseline_policy=baseline_simulation.policy,
|
|
222
|
+
reform_policy=reform_simulation.policy,
|
|
223
|
+
dynamic=baseline_simulation.dynamic,
|
|
35
224
|
income_variable="household_net_income",
|
|
36
225
|
)
|
|
37
226
|
|
|
@@ -9,12 +9,10 @@ from microdf import MicroDataFrame
|
|
|
9
9
|
|
|
10
10
|
from policyengine.core import (
|
|
11
11
|
Parameter,
|
|
12
|
-
ParameterValue,
|
|
13
12
|
TaxBenefitModel,
|
|
14
13
|
TaxBenefitModelVersion,
|
|
15
14
|
Variable,
|
|
16
15
|
)
|
|
17
|
-
from policyengine.utils import parse_safe_date
|
|
18
16
|
|
|
19
17
|
from .datasets import PolicyEngineUSDataset, USYearData
|
|
20
18
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
policyengine/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
policyengine/__pycache__/__init__.cpython-313.pyc,sha256=
|
|
2
|
+
policyengine/__pycache__/__init__.cpython-313.pyc,sha256=MPybDS-iHQgTbwpR0cZQ_5__Xexio_9dqsdZDdfQBtQ,175
|
|
3
3
|
policyengine/core/__init__.py,sha256=KBVhkqzkvjWLDDwk96vquQKL63ZFuLen5AzBOBnO9pg,912
|
|
4
4
|
policyengine/core/cache.py,sha256=DcVVFaCt7k9PmqwlhXoNDMtJ8sF4neYP1uRqWik5QYg,1812
|
|
5
5
|
policyengine/core/dataset.py,sha256=iJr9-J6w11uMRYy3EEJO9Gveku1m71AA1yzeo-0SiCs,16094
|
|
@@ -11,30 +11,30 @@ policyengine/core/parameter_value.py,sha256=ZRBZWFYtaY9TqdgjrCymzOZNmuKOBZsrWBET
|
|
|
11
11
|
policyengine/core/policy.py,sha256=ExMrUDMvNk_uuOL0cSm0UCzDyGka0t_yk6x4U0Kp6Ww,1635
|
|
12
12
|
policyengine/core/simulation.py,sha256=h6QbFt3uEvyfRXRVbSFBlrOd6Ze03OeZkwX9oElmO2M,1406
|
|
13
13
|
policyengine/core/tax_benefit_model.py,sha256=2Yc1RlQrUG7djDMZbJOQH4Ns86_lOnLeISCGR4-9zMo,176
|
|
14
|
-
policyengine/core/tax_benefit_model_version.py,sha256=
|
|
14
|
+
policyengine/core/tax_benefit_model_version.py,sha256=iVzEKWzQxoPVicwxcqo9Fy8PfVX07faBvyL9NhVIjuU,3212
|
|
15
15
|
policyengine/core/variable.py,sha256=AjSImORlRkh05xhYxyeT6GFMOfViRzYg0qRQAIj-mxo,350
|
|
16
16
|
policyengine/outputs/__init__.py,sha256=IJUmLP0Og41VrwiqhJF-a9-3fIb4nlXpS7uFuVCINIs,515
|
|
17
17
|
policyengine/outputs/aggregate.py,sha256=exI-U04OF5kVf2BBYV6sf8VldIWnT_IzxgkBs5wtnCw,4846
|
|
18
18
|
policyengine/outputs/change_aggregate.py,sha256=tK4K87YlByKikqFaB7OHyh1SqAuGtUnLL7cSF_EhrOs,7373
|
|
19
|
-
policyengine/outputs/decile_impact.py,sha256=
|
|
19
|
+
policyengine/outputs/decile_impact.py,sha256=f8nR3pea8_qDuQ-M6kaKnVKxbGnfL0IzpRfFTdi7TqA,5522
|
|
20
20
|
policyengine/tax_benefit_models/uk.py,sha256=HzAG_dORmsj1NJ9pd9WrqwgZPe9DUDrZ1wV5LuVCKAg,950
|
|
21
21
|
policyengine/tax_benefit_models/us.py,sha256=G51dAmHo8NJLb2mnbne6iO5eNaatCGUd_2unvawwF84,946
|
|
22
|
-
policyengine/tax_benefit_models/uk/__init__.py,sha256=
|
|
23
|
-
policyengine/tax_benefit_models/uk/analysis.py,sha256=
|
|
22
|
+
policyengine/tax_benefit_models/uk/__init__.py,sha256=StjVt4mV0n2QxlM_2oCp_OqHJu7eyWNbdPndezC7ve0,1294
|
|
23
|
+
policyengine/tax_benefit_models/uk/analysis.py,sha256=iw34SERGdjCO4GSnimOZwXRnzSV7nutoilkVxHaI5WM,8627
|
|
24
24
|
policyengine/tax_benefit_models/uk/datasets.py,sha256=N8pMrlhQFec_cbgvVf5HE2owU14VF1i8-ZUwZYBSeio,9043
|
|
25
|
-
policyengine/tax_benefit_models/uk/model.py,sha256=
|
|
25
|
+
policyengine/tax_benefit_models/uk/model.py,sha256=woVnq5-HRt3EzRqvHr9TFMhWD06CHxc1H0zlo_LqEJ4,8796
|
|
26
26
|
policyengine/tax_benefit_models/uk/outputs.py,sha256=2mYLwQW4QNvrOHtHfm_ACqE9gbmuLxvcCyldRU46s0o,3543
|
|
27
|
-
policyengine/tax_benefit_models/us/__init__.py,sha256=
|
|
28
|
-
policyengine/tax_benefit_models/us/analysis.py,sha256=
|
|
27
|
+
policyengine/tax_benefit_models/us/__init__.py,sha256=0RtqCl01j-Z_T4i9LITBSePegO97gZ4IIYqt-nsv2O0,1290
|
|
28
|
+
policyengine/tax_benefit_models/us/analysis.py,sha256=y-M4QAUyp44-Y9fbkKQ6KMbS9qS9eHju3D5QG3iJHf8,9435
|
|
29
29
|
policyengine/tax_benefit_models/us/datasets.py,sha256=OWqiYK8TWwdYP2qgUNIv6nIpqN5FVtyd8aYkVMUkAno,14757
|
|
30
|
-
policyengine/tax_benefit_models/us/model.py,sha256=
|
|
30
|
+
policyengine/tax_benefit_models/us/model.py,sha256=p5s8LHyGqkkVoJU1_7CGnwY8-KgXo_-3RSPuG9NO7Ww,15572
|
|
31
31
|
policyengine/tax_benefit_models/us/outputs.py,sha256=GT8Eur8DfB9cPQRbSljEl9RpKSNHW80Fq_CBXCybvIU,3519
|
|
32
32
|
policyengine/utils/__init__.py,sha256=1X-VYAWLyB9A0YRHwsGWrqQHns1WfeZ7ISC6DMU5myM,140
|
|
33
33
|
policyengine/utils/dates.py,sha256=HnAqyl8S8EOYp8ibsnMTmECYoDWCSqwL-7A2_qKgxSc,1510
|
|
34
34
|
policyengine/utils/parametric_reforms.py,sha256=4P3U39-4pYTU4BN6JjgmVLUkCkBhRfZJ6UIWTlsjyQE,1155
|
|
35
35
|
policyengine/utils/plotting.py,sha256=ZAzTWz38vIaW0c3Nt4Un1kfrNoXLyHCDd1pEJIlsRg4,5335
|
|
36
|
-
policyengine-3.1.
|
|
37
|
-
policyengine-3.1.
|
|
38
|
-
policyengine-3.1.
|
|
39
|
-
policyengine-3.1.
|
|
40
|
-
policyengine-3.1.
|
|
36
|
+
policyengine-3.1.15.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
|
|
37
|
+
policyengine-3.1.15.dist-info/METADATA,sha256=FkFNAnnQvw5prYSAd-CZeNxAroQ7p1C7vF8Re1q2fwo,45919
|
|
38
|
+
policyengine-3.1.15.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
39
|
+
policyengine-3.1.15.dist-info/top_level.txt,sha256=_23UPobfkneHQkpJ0e0OmDJfhCUfoXj_F2sTckCGOH4,13
|
|
40
|
+
policyengine-3.1.15.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|