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.
@@ -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.14
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.10; extra == "uk"
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.10; extra == "us"
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.10; extra == "dev"
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=mUwBXdcdGEIhSmoQGkgDXX5E9QkcB86jl4HABKXQyAk,175
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=csHeZasdvCyyRTlBIr1gtuJstye3pftQAsoI8mpRHa4,3012
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=IJUmLP0Og41VrwiqhJF-a9-3fIb4nlXpS7uFuVCINIs,515
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=jclhbj5U-xX8D-myy0SuWeJFVfQTqJDCh7qBXugak5U,4811
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=AiA74iED5FEryvUCMfVZi6pYDYuTfQcj9B01h8J5xFA,1105
23
- policyengine/tax_benefit_models/uk/analysis.py,sha256=O4eYJYF7tsgiuLuiWMU0OXq7ss6U8-vzlg6nC2U8sgU,3175
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=7eqCNW9bjCM-6BN2Dp8soUAEjZBP_0QmMC2OtDPCiXw,8694
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=zP-UUQqOc9g0ymyHkweJdi4RVXQDKSR6SUxavUKvV0s,1101
28
- policyengine/tax_benefit_models/us/analysis.py,sha256=Xf-DT0QjVySs0QG_koCwgvOeWI_scLtv3S3SP8u8ZWc,3253
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=nYgad3x2yDpNDSyc44ku1ExuMyLxqfVKyDNVtVOxeZs,15639
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=1X-VYAWLyB9A0YRHwsGWrqQHns1WfeZ7ISC6DMU5myM,140
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.14.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
37
- policyengine-3.1.14.dist-info/METADATA,sha256=SUCzPeZHriMkpLZQ1Ai-QS1xnwNrCIEtzUEUbrCAXUk,45919
38
- policyengine-3.1.14.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
39
- policyengine-3.1.14.dist-info/top_level.txt,sha256=_23UPobfkneHQkpJ0e0OmDJfhCUfoXj_F2sTckCGOH4,13
40
- policyengine-3.1.14.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5