policyengine 3.1.12__py3-none-any.whl → 3.1.13__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.
@@ -1,7 +1,7 @@
1
- from typing import TYPE_CHECKING
1
+ from typing import TYPE_CHECKING, Any
2
2
  from uuid import uuid4
3
3
 
4
- from pydantic import BaseModel, Field
4
+ from pydantic import BaseModel, Field, PrivateAttr
5
5
 
6
6
  from .parameter_value import ParameterValue
7
7
  from .tax_benefit_model_version import TaxBenefitModelVersion
@@ -11,6 +11,8 @@ if TYPE_CHECKING:
11
11
 
12
12
 
13
13
  class Parameter(BaseModel):
14
+ model_config = {"arbitrary_types_allowed": True}
15
+
14
16
  id: str = Field(default_factory=lambda: str(uuid4()))
15
17
  name: str
16
18
  label: str | None = None
@@ -18,4 +20,46 @@ class Parameter(BaseModel):
18
20
  data_type: type | None = None
19
21
  tax_benefit_model_version: TaxBenefitModelVersion
20
22
  unit: str | None = None
21
- parameter_values: list["ParameterValue"] = Field(default_factory=list)
23
+
24
+ # Lazy loading: store core param ref, build values on demand
25
+ _core_param: Any = PrivateAttr(default=None)
26
+ _parameter_values: list["ParameterValue"] | None = PrivateAttr(
27
+ default=None
28
+ )
29
+
30
+ def __init__(self, _core_param: Any = None, **data):
31
+ super().__init__(**data)
32
+ self._core_param = _core_param
33
+ self._parameter_values = None
34
+
35
+ @property
36
+ def parameter_values(self) -> list["ParameterValue"]:
37
+ """Lazily build parameter values on first access."""
38
+ if self._parameter_values is None:
39
+ self._parameter_values = []
40
+ if self._core_param is not None:
41
+ from policyengine.utils import parse_safe_date
42
+
43
+ for i in range(len(self._core_param.values_list)):
44
+ param_at_instant = self._core_param.values_list[i]
45
+ if i + 1 < len(self._core_param.values_list):
46
+ next_instant = self._core_param.values_list[i + 1]
47
+ else:
48
+ next_instant = None
49
+ pv = ParameterValue(
50
+ parameter=self,
51
+ start_date=parse_safe_date(
52
+ param_at_instant.instant_str
53
+ ),
54
+ end_date=parse_safe_date(next_instant.instant_str)
55
+ if next_instant
56
+ else None,
57
+ value=param_at_instant.value,
58
+ )
59
+ self._parameter_values.append(pv)
60
+ return self._parameter_values
61
+
62
+ @parameter_values.setter
63
+ def parameter_values(self, value: list["ParameterValue"]) -> None:
64
+ """Allow direct setting of parameter values."""
65
+ self._parameter_values = value
@@ -14,6 +14,8 @@ if TYPE_CHECKING:
14
14
 
15
15
 
16
16
  class TaxBenefitModelVersion(BaseModel):
17
+ model_config = {"arbitrary_types_allowed": True}
18
+
17
19
  id: str = Field(default_factory=lambda: str(uuid4()))
18
20
  model: TaxBenefitModel
19
21
  version: str
@@ -24,6 +26,14 @@ class TaxBenefitModelVersion(BaseModel):
24
26
  parameters: list["Parameter"] = Field(default_factory=list)
25
27
  parameter_values: list["ParameterValue"] = Field(default_factory=list)
26
28
 
29
+ # Lookup dicts for O(1) access (excluded from serialization)
30
+ variables_by_name: dict[str, "Variable"] = Field(
31
+ default_factory=dict, exclude=True
32
+ )
33
+ parameters_by_name: dict[str, "Parameter"] = Field(
34
+ default_factory=dict, exclude=True
35
+ )
36
+
27
37
  def run(self, simulation: "Simulation") -> "Simulation":
28
38
  raise NotImplementedError(
29
39
  "The TaxBenefitModel class must define a method to execute simulations."
@@ -39,40 +49,28 @@ class TaxBenefitModelVersion(BaseModel):
39
49
  "The TaxBenefitModel class must define a method to load simulations."
40
50
  )
41
51
 
42
- def get_parameter(self, name: str) -> "Parameter":
43
- """Get a parameter by name.
44
-
45
- Args:
46
- name: The parameter name (e.g., "gov.hmrc.income_tax.allowances.personal_allowance.amount")
52
+ def add_parameter(self, param: "Parameter") -> None:
53
+ """Add a parameter and index it for fast lookup."""
54
+ self.parameters.append(param)
55
+ self.parameters_by_name[param.name] = param
47
56
 
48
- Returns:
49
- Parameter: The matching parameter
57
+ def add_variable(self, var: "Variable") -> None:
58
+ """Add a variable and index it for fast lookup."""
59
+ self.variables.append(var)
60
+ self.variables_by_name[var.name] = var
50
61
 
51
- Raises:
52
- ValueError: If parameter not found
53
- """
54
- for param in self.parameters:
55
- if param.name == name:
56
- return param
62
+ def get_parameter(self, name: str) -> "Parameter":
63
+ """Get a parameter by name (O(1) lookup)."""
64
+ if name in self.parameters_by_name:
65
+ return self.parameters_by_name[name]
57
66
  raise ValueError(
58
67
  f"Parameter '{name}' not found in {self.model.id} version {self.version}"
59
68
  )
60
69
 
61
70
  def get_variable(self, name: str) -> "Variable":
62
- """Get a variable by name.
63
-
64
- Args:
65
- name: The variable name (e.g., "income_tax", "household_net_income")
66
-
67
- Returns:
68
- Variable: The matching variable
69
-
70
- Raises:
71
- ValueError: If variable not found
72
- """
73
- for var in self.variables:
74
- if var.name == name:
75
- return var
71
+ """Get a variable by name (O(1) lookup)."""
72
+ if name in self.variables_by_name:
73
+ return self.variables_by_name[name]
76
74
  raise ValueError(
77
75
  f"Variable '{name}' not found in {self.model.id} version {self.version}"
78
76
  )
@@ -161,8 +161,8 @@ def create_datasets(
161
161
 
162
162
  uk_dataset = PolicyEngineUKDataset(
163
163
  id=f"{Path(dataset).stem}_year_{year}",
164
- name=f"{dataset}-year-{year}",
165
- description=f"UK Dataset for year {year} based on {dataset}",
164
+ name=f"{Path(dataset).stem}-year-{year}",
165
+ description=f"UK Dataset for year {year} based on {Path(dataset).stem}",
166
166
  filepath=f"{data_folder}/{Path(dataset).stem}_year_{year}.h5",
167
167
  year=int(year),
168
168
  data=UKYearData(
@@ -118,7 +118,6 @@ class PolicyEngineUKLatest(TaxBenefitModelVersion):
118
118
 
119
119
  self.id = f"{self.model.id}@{self.version}"
120
120
 
121
- self.variables = []
122
121
  for var_obj in system.variables.values():
123
122
  variable = Variable(
124
123
  id=self.id + "-" + var_obj.name,
@@ -140,9 +139,8 @@ class PolicyEngineUKLatest(TaxBenefitModelVersion):
140
139
  var_obj.possible_values._value2member_map_.values(),
141
140
  )
142
141
  )
143
- self.variables.append(variable)
142
+ self.add_variable(variable)
144
143
 
145
- self.parameters = []
146
144
  from policyengine_core.parameters import Parameter as CoreParameter
147
145
 
148
146
  for param_node in system.parameters.get_descendants():
@@ -153,33 +151,11 @@ class PolicyEngineUKLatest(TaxBenefitModelVersion):
153
151
  label=param_node.metadata.get("label", param_node.name),
154
152
  tax_benefit_model_version=self,
155
153
  description=param_node.description,
156
- data_type=type(
157
- param_node(2025)
158
- ), # Example year to infer type
154
+ data_type=type(param_node(2025)),
159
155
  unit=param_node.metadata.get("unit"),
156
+ _core_param=param_node, # Store for lazy value loading
160
157
  )
161
- self.parameters.append(parameter)
162
-
163
- for i in range(len(param_node.values_list)):
164
- param_at_instant = param_node.values_list[i]
165
- if i + 1 < len(param_node.values_list):
166
- next_instant = param_node.values_list[i + 1]
167
- else:
168
- next_instant = None
169
- parameter_value = ParameterValue(
170
- parameter=parameter,
171
- start_date=parse_safe_date(
172
- param_at_instant.instant_str
173
- ),
174
- end_date=parse_safe_date(next_instant.instant_str)
175
- if next_instant
176
- else None,
177
- value=param_at_instant.value,
178
- )
179
- self.parameter_values.append(parameter_value)
180
- self.get_parameter(parameter.name).parameter_values.append(
181
- parameter_value
182
- )
158
+ self.add_parameter(parameter)
183
159
 
184
160
  def run(self, simulation: "Simulation") -> "Simulation":
185
161
  from policyengine_uk import Microsimulation
@@ -266,8 +266,8 @@ def create_datasets(
266
266
 
267
267
  us_dataset = PolicyEngineUSDataset(
268
268
  id=f"{Path(dataset).stem}_year_{year}",
269
- name=f"{dataset}-year-{year}",
270
- description=f"US Dataset for year {year} based on {dataset}",
269
+ name=f"{Path(dataset).stem}-year-{year}",
270
+ description=f"US Dataset for year {year} based on {Path(dataset).stem}",
271
271
  filepath=f"{data_folder}/{Path(dataset).stem}_year_{year}.h5",
272
272
  year=int(year),
273
273
  data=USYearData(
@@ -111,7 +111,6 @@ class PolicyEngineUSLatest(TaxBenefitModelVersion):
111
111
 
112
112
  self.id = f"{self.model.id}@{self.version}"
113
113
 
114
- self.variables = []
115
114
  for var_obj in system.variables.values():
116
115
  variable = Variable(
117
116
  id=self.id + "-" + var_obj.name,
@@ -133,9 +132,8 @@ class PolicyEngineUSLatest(TaxBenefitModelVersion):
133
132
  var_obj.possible_values._value2member_map_.values(),
134
133
  )
135
134
  )
136
- self.variables.append(variable)
135
+ self.add_variable(variable)
137
136
 
138
- self.parameters = []
139
137
  from policyengine_core.parameters import Parameter as CoreParameter
140
138
 
141
139
  for param_node in system.parameters.get_descendants():
@@ -147,29 +145,9 @@ class PolicyEngineUSLatest(TaxBenefitModelVersion):
147
145
  description=param_node.description,
148
146
  data_type=type(param_node(2025)),
149
147
  unit=param_node.metadata.get("unit"),
148
+ _core_param=param_node, # Store for lazy value loading
150
149
  )
151
- self.parameters.append(parameter)
152
-
153
- for i in range(len(param_node.values_list)):
154
- param_at_instant = param_node.values_list[i]
155
- if i + 1 < len(param_node.values_list):
156
- next_instant = param_node.values_list[i + 1]
157
- else:
158
- next_instant = None
159
- parameter_value = ParameterValue(
160
- parameter=parameter,
161
- start_date=parse_safe_date(
162
- param_at_instant.instant_str
163
- ),
164
- end_date=parse_safe_date(next_instant.instant_str)
165
- if next_instant
166
- else None,
167
- value=param_at_instant.value,
168
- )
169
- self.parameter_values.append(parameter_value)
170
- self.get_parameter(parameter.name).parameter_values.append(
171
- parameter_value
172
- )
150
+ self.add_parameter(parameter)
173
151
 
174
152
  def run(self, simulation: "Simulation") -> "Simulation":
175
153
  from policyengine_us import Microsimulation
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: policyengine
3
- Version: 3.1.12
3
+ Version: 3.1.13
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
@@ -1,17 +1,17 @@
1
1
  policyengine/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- policyengine/__pycache__/__init__.cpython-313.pyc,sha256=2hzM29K_GrxvAZxRI8IB8gIAj0kAhBHsikLWKkeDug0,175
2
+ policyengine/__pycache__/__init__.cpython-313.pyc,sha256=uVHsGqNxZlQfIUi3c2mJIGYwZoh7v_hBZty19_E2l2g,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
6
6
  policyengine/core/dataset_version.py,sha256=6KeFCRGQto_Yyl4QY4Vo2JFythjaXrNAOHQiwRGESyM,378
7
7
  policyengine/core/dynamic.py,sha256=ng9BjDzxdwjJ0e7zoqXFmq33E1SRbaaPYfW7pjRSSzI,1641
8
8
  policyengine/core/output.py,sha256=cCW4vbzkLdQaT_nJTyDJBl7Hubm7nZeRuR7aVG1dKvg,643
9
- policyengine/core/parameter.py,sha256=8RKKuGCDW1OoHPoXI9vF3JY6COc1qhUtMolXTUxPoEs,626
9
+ policyengine/core/parameter.py,sha256=MMhx5bN-9jn6n2sRxJf_ow-j-ze8IehCF4r_VrpfR94,2407
10
10
  policyengine/core/parameter_value.py,sha256=ZRBZWFYtaY9TqdgjrCymzOZNmuKOBZsrWBET24DIJ_Q,434
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=V1CGft5Y6YflMASx0wR3V73jr-WqQu2R8N5QVMRm9yw,2752
14
+ policyengine/core/tax_benefit_model_version.py,sha256=csHeZasdvCyyRTlBIr1gtuJstye3pftQAsoI8mpRHa4,3012
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
@@ -21,20 +21,20 @@ policyengine/tax_benefit_models/uk.py,sha256=HzAG_dORmsj1NJ9pd9WrqwgZPe9DUDrZ1wV
21
21
  policyengine/tax_benefit_models/us.py,sha256=G51dAmHo8NJLb2mnbne6iO5eNaatCGUd_2unvawwF84,946
22
22
  policyengine/tax_benefit_models/uk/__init__.py,sha256=AiA74iED5FEryvUCMfVZi6pYDYuTfQcj9B01h8J5xFA,1105
23
23
  policyengine/tax_benefit_models/uk/analysis.py,sha256=O4eYJYF7tsgiuLuiWMU0OXq7ss6U8-vzlg6nC2U8sgU,3175
24
- policyengine/tax_benefit_models/uk/datasets.py,sha256=fGKfeCJ6bOj0LHTcvHFDScYem5vTb3XVPp9nk8G5lGE,8999
25
- policyengine/tax_benefit_models/uk/model.py,sha256=djCLLBeik9ZGFqe5WXUNVcu1JXjh0U8MXakvncasUj0,9754
24
+ policyengine/tax_benefit_models/uk/datasets.py,sha256=OtBGQPI-P2jnLfurw1MiFDo3LRi5xye0uI3XM9J-9rY,9021
25
+ policyengine/tax_benefit_models/uk/model.py,sha256=7eqCNW9bjCM-6BN2Dp8soUAEjZBP_0QmMC2OtDPCiXw,8694
26
26
  policyengine/tax_benefit_models/uk/outputs.py,sha256=2mYLwQW4QNvrOHtHfm_ACqE9gbmuLxvcCyldRU46s0o,3543
27
27
  policyengine/tax_benefit_models/us/__init__.py,sha256=zP-UUQqOc9g0ymyHkweJdi4RVXQDKSR6SUxavUKvV0s,1101
28
28
  policyengine/tax_benefit_models/us/analysis.py,sha256=Xf-DT0QjVySs0QG_koCwgvOeWI_scLtv3S3SP8u8ZWc,3253
29
- policyengine/tax_benefit_models/us/datasets.py,sha256=Si0oeTVLpoXLpLbQCxtp3ZZB8JFrcHgxRa_16k7x8BQ,14713
30
- policyengine/tax_benefit_models/us/model.py,sha256=x3xRoIpXV2idVr0IwPJzujLHqu3qeO-iWN2Sv7Tuj9Q,16563
29
+ policyengine/tax_benefit_models/us/datasets.py,sha256=Jfw8cYHBTjokiJJ1WBQtNcHEDd7BJuQ4A4wD6dN7Znk,14735
30
+ policyengine/tax_benefit_models/us/model.py,sha256=eNlot8yMZkNwPiYfrWpDJkxRAwTBYLGWL-VC4Pss4JI,15579
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.12.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
37
- policyengine-3.1.12.dist-info/METADATA,sha256=lmH-TG4Q8JYw_NebpYc3IwwZKAyNeGbeyNnx3kCX5is,45919
38
- policyengine-3.1.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
39
- policyengine-3.1.12.dist-info/top_level.txt,sha256=_23UPobfkneHQkpJ0e0OmDJfhCUfoXj_F2sTckCGOH4,13
40
- policyengine-3.1.12.dist-info/RECORD,,
36
+ policyengine-3.1.13.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
37
+ policyengine-3.1.13.dist-info/METADATA,sha256=35l_TCb-BLgwO2Gbdpnl5up_-LMcqSowLieYrjTrjpw,45919
38
+ policyengine-3.1.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
39
+ policyengine-3.1.13.dist-info/top_level.txt,sha256=_23UPobfkneHQkpJ0e0OmDJfhCUfoXj_F2sTckCGOH4,13
40
+ policyengine-3.1.13.dist-info/RECORD,,