policyengine 3.1.14__py3-none-any.whl → 3.1.16__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/__init__.py +28 -0
- policyengine/outputs/decile_impact.py +22 -2
- policyengine/outputs/inequality.py +276 -0
- policyengine/outputs/poverty.py +238 -0
- policyengine/tax_benefit_models/uk/__init__.py +10 -2
- policyengine/tax_benefit_models/uk/analysis.py +199 -4
- policyengine/tax_benefit_models/uk/model.py +15 -4
- policyengine/tax_benefit_models/us/__init__.py +10 -2
- policyengine/tax_benefit_models/us/analysis.py +219 -5
- policyengine/tax_benefit_models/us/model.py +15 -4
- policyengine/utils/__init__.py +4 -0
- policyengine/utils/parameter_labels.py +213 -0
- {policyengine-3.1.14.dist-info → policyengine-3.1.16.dist-info}/METADATA +5 -5
- {policyengine-3.1.14.dist-info → policyengine-3.1.16.dist-info}/RECORD +19 -16
- {policyengine-3.1.14.dist-info → policyengine-3.1.16.dist-info}/WHEEL +1 -1
- {policyengine-3.1.14.dist-info → policyengine-3.1.16.dist-info}/licenses/LICENSE +0 -0
- {policyengine-3.1.14.dist-info → policyengine-3.1.16.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""Utilities for generating human-readable labels for tax-benefit parameters."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def generate_label_for_parameter(param_node, system, scale_lookup):
|
|
7
|
+
"""
|
|
8
|
+
Generate a label for a parameter that doesn't have one.
|
|
9
|
+
|
|
10
|
+
For breakdown parameters: Uses parent label + enum value
|
|
11
|
+
For bracket parameters: Uses scale label + bracket info
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
param_node: The CoreParameter object
|
|
15
|
+
system: The tax-benefit system (has variables and parameters)
|
|
16
|
+
scale_lookup: Dict mapping scale names to ParameterScale objects
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
str or None: Generated label, or None if cannot generate
|
|
20
|
+
"""
|
|
21
|
+
if param_node.metadata.get("label"):
|
|
22
|
+
return param_node.metadata.get("label")
|
|
23
|
+
|
|
24
|
+
param_name = param_node.name
|
|
25
|
+
|
|
26
|
+
if "[" in param_name:
|
|
27
|
+
return _generate_bracket_label(param_name, scale_lookup)
|
|
28
|
+
|
|
29
|
+
# Check for breakdown - either direct child or nested
|
|
30
|
+
breakdown_parent = _find_breakdown_parent(param_node)
|
|
31
|
+
if breakdown_parent:
|
|
32
|
+
return _generate_breakdown_label(param_node, system, breakdown_parent)
|
|
33
|
+
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _find_breakdown_parent(param_node):
|
|
38
|
+
"""
|
|
39
|
+
Walk up the tree to find the nearest ancestor with breakdown metadata.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
param_node: The CoreParameter object
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
The breakdown parent node, or None if not found
|
|
46
|
+
"""
|
|
47
|
+
current = param_node.parent
|
|
48
|
+
while current:
|
|
49
|
+
if current.metadata.get("breakdown"):
|
|
50
|
+
return current
|
|
51
|
+
current = getattr(current, "parent", None)
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _generate_breakdown_label(param_node, system, breakdown_parent=None):
|
|
56
|
+
"""
|
|
57
|
+
Generate label for a breakdown parameter using enum values.
|
|
58
|
+
|
|
59
|
+
Handles both single-level and nested breakdowns by walking up to the
|
|
60
|
+
breakdown parent and collecting all dimension values.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
param_node: The CoreParameter object
|
|
64
|
+
system: The tax-benefit system
|
|
65
|
+
breakdown_parent: The ancestor node with breakdown metadata (optional)
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
str or None: Generated label, or None if cannot generate
|
|
69
|
+
"""
|
|
70
|
+
# Find breakdown parent if not provided
|
|
71
|
+
if breakdown_parent is None:
|
|
72
|
+
breakdown_parent = _find_breakdown_parent(param_node)
|
|
73
|
+
if not breakdown_parent:
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
parent_label = breakdown_parent.metadata.get("label")
|
|
77
|
+
if not parent_label:
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
breakdown_vars = breakdown_parent.metadata.get("breakdown", [])
|
|
81
|
+
breakdown_labels = breakdown_parent.metadata.get("breakdown_labels", [])
|
|
82
|
+
|
|
83
|
+
# Collect dimension values from breakdown parent to param_node
|
|
84
|
+
dimension_values = _collect_dimension_values(
|
|
85
|
+
param_node, breakdown_parent
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
if not dimension_values:
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
# Generate labels for each dimension
|
|
92
|
+
formatted_parts = []
|
|
93
|
+
for i, (dim_key, dim_value) in enumerate(dimension_values):
|
|
94
|
+
var_name = breakdown_vars[i] if i < len(breakdown_vars) else None
|
|
95
|
+
dim_label = breakdown_labels[i] if i < len(breakdown_labels) else None
|
|
96
|
+
|
|
97
|
+
formatted_value = _format_dimension_value(
|
|
98
|
+
dim_value, var_name, dim_label, system
|
|
99
|
+
)
|
|
100
|
+
formatted_parts.append(formatted_value)
|
|
101
|
+
|
|
102
|
+
return f"{parent_label} ({', '.join(formatted_parts)})"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _collect_dimension_values(param_node, breakdown_parent):
|
|
106
|
+
"""
|
|
107
|
+
Collect dimension keys and values from breakdown parent to param_node.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
param_node: The CoreParameter object
|
|
111
|
+
breakdown_parent: The ancestor node with breakdown metadata
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
list of (dimension_key, value) tuples, ordered from parent to child
|
|
115
|
+
"""
|
|
116
|
+
# Build path from param_node up to breakdown_parent
|
|
117
|
+
path = []
|
|
118
|
+
current = param_node
|
|
119
|
+
while current and current != breakdown_parent:
|
|
120
|
+
path.append(current)
|
|
121
|
+
current = getattr(current, "parent", None)
|
|
122
|
+
|
|
123
|
+
# Reverse to get parent-to-child order
|
|
124
|
+
path.reverse()
|
|
125
|
+
|
|
126
|
+
# Extract dimension values
|
|
127
|
+
dimension_values = []
|
|
128
|
+
for i, node in enumerate(path):
|
|
129
|
+
key = node.name.split(".")[-1]
|
|
130
|
+
dimension_values.append((i, key))
|
|
131
|
+
|
|
132
|
+
return dimension_values
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _format_dimension_value(value, var_name, dim_label, system):
|
|
136
|
+
"""
|
|
137
|
+
Format a single dimension value with semantic label if available.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
value: The raw dimension value (e.g., "SINGLE", "1", "CA")
|
|
141
|
+
var_name: The breakdown variable name (e.g., "filing_status", "range(1, 9)")
|
|
142
|
+
dim_label: The human-readable label for this dimension (e.g., "Household size")
|
|
143
|
+
system: The tax-benefit system
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
str: Formatted dimension value
|
|
147
|
+
"""
|
|
148
|
+
# First, try to get enum display value
|
|
149
|
+
if var_name and isinstance(var_name, str) and not var_name.startswith("range(") and not var_name.startswith("list("):
|
|
150
|
+
var = system.variables.get(var_name)
|
|
151
|
+
if var and hasattr(var, "possible_values") and var.possible_values:
|
|
152
|
+
try:
|
|
153
|
+
enum_value = var.possible_values[value].value
|
|
154
|
+
return str(enum_value)
|
|
155
|
+
except (KeyError, AttributeError):
|
|
156
|
+
pass
|
|
157
|
+
|
|
158
|
+
# For range() dimensions or when no enum found, use breakdown_label if available
|
|
159
|
+
if dim_label:
|
|
160
|
+
return f"{dim_label} {value}"
|
|
161
|
+
|
|
162
|
+
return value
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _generate_bracket_label(param_name, scale_lookup):
|
|
166
|
+
"""Generate label for a bracket parameter."""
|
|
167
|
+
match = re.match(r"^(.+)\[(\d+)\]\.(\w+)$", param_name)
|
|
168
|
+
if not match:
|
|
169
|
+
return None
|
|
170
|
+
|
|
171
|
+
scale_name = match.group(1)
|
|
172
|
+
bracket_index = int(match.group(2))
|
|
173
|
+
field_name = match.group(3)
|
|
174
|
+
|
|
175
|
+
scale = scale_lookup.get(scale_name)
|
|
176
|
+
if not scale:
|
|
177
|
+
return None
|
|
178
|
+
|
|
179
|
+
scale_label = scale.metadata.get("label")
|
|
180
|
+
scale_type = scale.metadata.get("type", "")
|
|
181
|
+
|
|
182
|
+
if not scale_label:
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
bracket_num = bracket_index + 1
|
|
186
|
+
|
|
187
|
+
if scale_type in ("marginal_rate", "marginal_amount"):
|
|
188
|
+
bracket_desc = f"bracket {bracket_num}"
|
|
189
|
+
elif scale_type == "single_amount":
|
|
190
|
+
bracket_desc = f"tier {bracket_num}"
|
|
191
|
+
else:
|
|
192
|
+
bracket_desc = f"bracket {bracket_num}"
|
|
193
|
+
|
|
194
|
+
return f"{scale_label} ({bracket_desc} {field_name})"
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def build_scale_lookup(system):
|
|
198
|
+
"""
|
|
199
|
+
Build a lookup dict mapping scale names to ParameterScale objects.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
system: The tax-benefit system
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
dict: Mapping of scale name -> ParameterScale object
|
|
206
|
+
"""
|
|
207
|
+
from policyengine_core.parameters import ParameterScale
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
p.name: p
|
|
211
|
+
for p in system.parameters.get_descendants()
|
|
212
|
+
if isinstance(p, ParameterScale)
|
|
213
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: policyengine
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.16
|
|
4
4
|
Summary: A package to conduct policy analysis using PolicyEngine tax-benefit models.
|
|
5
5
|
Author-email: PolicyEngine <hello@policyengine.org>
|
|
6
6
|
License: GNU AFFERO GENERAL PUBLIC LICENSE
|
|
@@ -670,15 +670,15 @@ Description-Content-Type: text/markdown
|
|
|
670
670
|
License-File: LICENSE
|
|
671
671
|
Requires-Dist: pydantic>=2.0.0
|
|
672
672
|
Requires-Dist: pandas>=2.0.0
|
|
673
|
-
Requires-Dist: microdf_python
|
|
673
|
+
Requires-Dist: microdf_python>=1.2.1
|
|
674
674
|
Requires-Dist: plotly>=5.0.0
|
|
675
675
|
Requires-Dist: requests>=2.31.0
|
|
676
676
|
Requires-Dist: psutil>=5.9.0
|
|
677
677
|
Provides-Extra: uk
|
|
678
|
-
Requires-Dist: policyengine_core>=3.
|
|
678
|
+
Requires-Dist: policyengine_core>=3.23.6; extra == "uk"
|
|
679
679
|
Requires-Dist: policyengine-uk>=2.51.0; extra == "uk"
|
|
680
680
|
Provides-Extra: us
|
|
681
|
-
Requires-Dist: policyengine_core>=3.
|
|
681
|
+
Requires-Dist: policyengine_core>=3.23.6; extra == "us"
|
|
682
682
|
Requires-Dist: policyengine-us>=1.213.1; extra == "us"
|
|
683
683
|
Provides-Extra: dev
|
|
684
684
|
Requires-Dist: black; extra == "dev"
|
|
@@ -691,7 +691,7 @@ Requires-Dist: itables; extra == "dev"
|
|
|
691
691
|
Requires-Dist: build; extra == "dev"
|
|
692
692
|
Requires-Dist: pytest-asyncio>=0.26.0; extra == "dev"
|
|
693
693
|
Requires-Dist: ruff>=0.5.0; extra == "dev"
|
|
694
|
-
Requires-Dist: policyengine_core>=3.
|
|
694
|
+
Requires-Dist: policyengine_core>=3.23.6; extra == "dev"
|
|
695
695
|
Requires-Dist: policyengine-uk>=2.51.0; extra == "dev"
|
|
696
696
|
Requires-Dist: policyengine-us>=1.213.1; extra == "dev"
|
|
697
697
|
Dynamic: license-file
|
|
@@ -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=pXlfNHK9qaW6hIe40yAYhaHxkBtl6c_uvAIzt-n6hzM,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,33 @@ 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
|
-
policyengine/outputs/__init__.py,sha256=
|
|
16
|
+
policyengine/outputs/__init__.py,sha256=fcqkl1iK4lMpkdS0OBj3wWGAd1zZjc6IiJ-nrXy9VU8,1254
|
|
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
|
+
policyengine/outputs/inequality.py,sha256=W_yc9Ibeavx7KA3reJTFArK3fR1kf_YFV0jAaC121w0,9356
|
|
21
|
+
policyengine/outputs/poverty.py,sha256=h8dHj-S8YeEQ6CXqmWje3gEz30H8jgE-gmXQ0NoJTUU,7866
|
|
20
22
|
policyengine/tax_benefit_models/uk.py,sha256=HzAG_dORmsj1NJ9pd9WrqwgZPe9DUDrZ1wV5LuVCKAg,950
|
|
21
23
|
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=
|
|
24
|
+
policyengine/tax_benefit_models/uk/__init__.py,sha256=StjVt4mV0n2QxlM_2oCp_OqHJu7eyWNbdPndezC7ve0,1294
|
|
25
|
+
policyengine/tax_benefit_models/uk/analysis.py,sha256=uPQt2EI2y6obibLfZfV-fHuN-FVzvUVUgeZY9kKSB5E,9527
|
|
24
26
|
policyengine/tax_benefit_models/uk/datasets.py,sha256=N8pMrlhQFec_cbgvVf5HE2owU14VF1i8-ZUwZYBSeio,9043
|
|
25
|
-
policyengine/tax_benefit_models/uk/model.py,sha256=
|
|
27
|
+
policyengine/tax_benefit_models/uk/model.py,sha256=8byY9n8rEWA2DxG3uq7N3SogZWvl_o9JqKcL0xfQ6fk,8984
|
|
26
28
|
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=
|
|
29
|
+
policyengine/tax_benefit_models/us/__init__.py,sha256=0RtqCl01j-Z_T4i9LITBSePegO97gZ4IIYqt-nsv2O0,1290
|
|
30
|
+
policyengine/tax_benefit_models/us/analysis.py,sha256=qJ9pZjyEY1xS7HCpT5-AETdPTtd-k7hWePxJz-NpXDE,10344
|
|
29
31
|
policyengine/tax_benefit_models/us/datasets.py,sha256=OWqiYK8TWwdYP2qgUNIv6nIpqN5FVtyd8aYkVMUkAno,14757
|
|
30
|
-
policyengine/tax_benefit_models/us/model.py,sha256=
|
|
32
|
+
policyengine/tax_benefit_models/us/model.py,sha256=t8YPeiEzOskzSnEIwCSZFPvdxFubtLTAUPkTEcG_JN8,15945
|
|
31
33
|
policyengine/tax_benefit_models/us/outputs.py,sha256=GT8Eur8DfB9cPQRbSljEl9RpKSNHW80Fq_CBXCybvIU,3519
|
|
32
|
-
policyengine/utils/__init__.py,sha256=
|
|
34
|
+
policyengine/utils/__init__.py,sha256=qq9ElvVnZtmM0CAjbkJV_QFBHz3bAjOSCTJGqx29F0c,311
|
|
33
35
|
policyengine/utils/dates.py,sha256=HnAqyl8S8EOYp8ibsnMTmECYoDWCSqwL-7A2_qKgxSc,1510
|
|
36
|
+
policyengine/utils/parameter_labels.py,sha256=_QTCTOjOdaW-pPVOsYMn7VyN-75QTD6IILfH-6oAd7U,6549
|
|
34
37
|
policyengine/utils/parametric_reforms.py,sha256=4P3U39-4pYTU4BN6JjgmVLUkCkBhRfZJ6UIWTlsjyQE,1155
|
|
35
38
|
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.
|
|
39
|
+
policyengine-3.1.16.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
|
|
40
|
+
policyengine-3.1.16.dist-info/METADATA,sha256=rmdahoK--THkMMXjckeYhFpagrIk6w_oANMFgTE9rts,45932
|
|
41
|
+
policyengine-3.1.16.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
|
|
42
|
+
policyengine-3.1.16.dist-info/top_level.txt,sha256=_23UPobfkneHQkpJ0e0OmDJfhCUfoXj_F2sTckCGOH4,13
|
|
43
|
+
policyengine-3.1.16.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|