policyengine 3.1.12__tar.gz → 3.1.14__tar.gz

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.
Files changed (87) hide show
  1. {policyengine-3.1.12 → policyengine-3.1.14}/CHANGELOG.md +14 -0
  2. {policyengine-3.1.12 → policyengine-3.1.14}/PKG-INFO +1 -1
  3. {policyengine-3.1.12 → policyengine-3.1.14}/changelog.yaml +10 -0
  4. {policyengine-3.1.12 → policyengine-3.1.14}/pyproject.toml +1 -1
  5. policyengine-3.1.14/src/policyengine/__pycache__/__init__.cpython-313.pyc +0 -0
  6. policyengine-3.1.14/src/policyengine/core/parameter.py +65 -0
  7. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/core/tax_benefit_model_version.py +25 -27
  8. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/tax_benefit_models/uk/datasets.py +4 -4
  9. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/tax_benefit_models/uk/model.py +4 -28
  10. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/tax_benefit_models/us/datasets.py +4 -4
  11. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/tax_benefit_models/us/model.py +4 -25
  12. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine.egg-info/PKG-INFO +1 -1
  13. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine.egg-info/SOURCES.txt +2 -1
  14. policyengine-3.1.14/tests/test_models.py +68 -0
  15. {policyengine-3.1.12 → policyengine-3.1.14}/uv.lock +3 -1
  16. policyengine-3.1.12/src/policyengine/__pycache__/__init__.cpython-313.pyc +0 -0
  17. policyengine-3.1.12/src/policyengine/core/parameter.py +0 -21
  18. {policyengine-3.1.12 → policyengine-3.1.14}/.claude/policyengine-guide.md +0 -0
  19. {policyengine-3.1.12 → policyengine-3.1.14}/.claude/quick-reference.md +0 -0
  20. {policyengine-3.1.12 → policyengine-3.1.14}/.github/CONTRIBUTING.md +0 -0
  21. {policyengine-3.1.12 → policyengine-3.1.14}/.github/changelog_template.md +0 -0
  22. {policyengine-3.1.12 → policyengine-3.1.14}/.github/fetch_version.py +0 -0
  23. {policyengine-3.1.12 → policyengine-3.1.14}/.github/get-changelog-diff.sh +0 -0
  24. {policyengine-3.1.12 → policyengine-3.1.14}/.github/has-functional-changes.sh +0 -0
  25. {policyengine-3.1.12 → policyengine-3.1.14}/.github/is-version-number-acceptable.sh +0 -0
  26. {policyengine-3.1.12 → policyengine-3.1.14}/.github/publish-git-tag.sh +0 -0
  27. {policyengine-3.1.12 → policyengine-3.1.14}/.github/workflows/code_changes.yaml +0 -0
  28. {policyengine-3.1.12 → policyengine-3.1.14}/.github/workflows/docs.yml +0 -0
  29. {policyengine-3.1.12 → policyengine-3.1.14}/.github/workflows/pr_code_changes.yaml +0 -0
  30. {policyengine-3.1.12 → policyengine-3.1.14}/.github/workflows/pr_docs_changes.yaml +0 -0
  31. {policyengine-3.1.12 → policyengine-3.1.14}/.github/workflows/versioning.yaml +0 -0
  32. {policyengine-3.1.12 → policyengine-3.1.14}/.gitignore +0 -0
  33. {policyengine-3.1.12 → policyengine-3.1.14}/CLAUDE.md +0 -0
  34. {policyengine-3.1.12 → policyengine-3.1.14}/LICENSE +0 -0
  35. {policyengine-3.1.12 → policyengine-3.1.14}/Makefile +0 -0
  36. {policyengine-3.1.12 → policyengine-3.1.14}/README.md +0 -0
  37. {policyengine-3.1.12 → policyengine-3.1.14}/changelog_entry.yaml +0 -0
  38. {policyengine-3.1.12 → policyengine-3.1.14}/docs/.gitignore +0 -0
  39. {policyengine-3.1.12 → policyengine-3.1.14}/docs/core-concepts.md +0 -0
  40. {policyengine-3.1.12 → policyengine-3.1.14}/docs/country-models-uk.md +0 -0
  41. {policyengine-3.1.12 → policyengine-3.1.14}/docs/country-models-us.md +0 -0
  42. {policyengine-3.1.12 → policyengine-3.1.14}/docs/dev.md +0 -0
  43. {policyengine-3.1.12 → policyengine-3.1.14}/docs/index.md +0 -0
  44. {policyengine-3.1.12 → policyengine-3.1.14}/docs/myst.yml +0 -0
  45. {policyengine-3.1.12 → policyengine-3.1.14}/docs/visualisation.md +0 -0
  46. {policyengine-3.1.12 → policyengine-3.1.14}/examples/employment_income_variation_uk.py +0 -0
  47. {policyengine-3.1.12 → policyengine-3.1.14}/examples/employment_income_variation_us.py +0 -0
  48. {policyengine-3.1.12 → policyengine-3.1.14}/examples/income_bands_uk.py +0 -0
  49. {policyengine-3.1.12 → policyengine-3.1.14}/examples/income_distribution_us.py +0 -0
  50. {policyengine-3.1.12 → policyengine-3.1.14}/examples/policy_change_uk.py +0 -0
  51. {policyengine-3.1.12 → policyengine-3.1.14}/examples/speedtest_us_simulation.py +0 -0
  52. {policyengine-3.1.12 → policyengine-3.1.14}/setup.cfg +0 -0
  53. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/__init__.py +0 -0
  54. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/core/__init__.py +0 -0
  55. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/core/cache.py +0 -0
  56. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/core/dataset.py +0 -0
  57. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/core/dataset_version.py +0 -0
  58. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/core/dynamic.py +0 -0
  59. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/core/output.py +0 -0
  60. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/core/parameter_value.py +0 -0
  61. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/core/policy.py +0 -0
  62. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/core/simulation.py +0 -0
  63. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/core/tax_benefit_model.py +0 -0
  64. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/core/variable.py +0 -0
  65. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/outputs/__init__.py +0 -0
  66. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/outputs/aggregate.py +0 -0
  67. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/outputs/change_aggregate.py +0 -0
  68. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/outputs/decile_impact.py +0 -0
  69. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/tax_benefit_models/uk/__init__.py +0 -0
  70. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/tax_benefit_models/uk/analysis.py +0 -0
  71. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/tax_benefit_models/uk/outputs.py +0 -0
  72. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/tax_benefit_models/uk.py +0 -0
  73. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/tax_benefit_models/us/__init__.py +0 -0
  74. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/tax_benefit_models/us/analysis.py +0 -0
  75. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/tax_benefit_models/us/outputs.py +0 -0
  76. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/tax_benefit_models/us.py +0 -0
  77. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/utils/__init__.py +0 -0
  78. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/utils/dates.py +0 -0
  79. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/utils/parametric_reforms.py +0 -0
  80. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine/utils/plotting.py +0 -0
  81. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine.egg-info/dependency_links.txt +0 -0
  82. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine.egg-info/requires.txt +0 -0
  83. {policyengine-3.1.12 → policyengine-3.1.14}/src/policyengine.egg-info/top_level.txt +0 -0
  84. {policyengine-3.1.12 → policyengine-3.1.14}/tests/test_aggregate.py +0 -0
  85. {policyengine-3.1.12 → policyengine-3.1.14}/tests/test_cache.py +0 -0
  86. {policyengine-3.1.12 → policyengine-3.1.14}/tests/test_change_aggregate.py +0 -0
  87. {policyengine-3.1.12 → policyengine-3.1.14}/tests/test_entity_mapping.py +0 -0
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.1.14] - 2025-12-10 21:59:24
9
+
10
+ ### Fixed
11
+
12
+ - Improvements to loading taxbenefitsystems.
13
+
14
+ ## [3.1.13] - 2025-12-10 19:29:28
15
+
16
+ ### Fixed
17
+
18
+ - Naming improvements.
19
+
8
20
  ## [3.1.12] - 2025-12-10 14:43:52
9
21
 
10
22
  ### Fixed
@@ -269,6 +281,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
269
281
 
270
282
 
271
283
 
284
+ [3.1.14]: https://github.com/PolicyEngine/policyengine.py/compare/3.1.13...3.1.14
285
+ [3.1.13]: https://github.com/PolicyEngine/policyengine.py/compare/3.1.12...3.1.13
272
286
  [3.1.12]: https://github.com/PolicyEngine/policyengine.py/compare/3.1.11...3.1.12
273
287
  [3.1.11]: https://github.com/PolicyEngine/policyengine.py/compare/3.1.10...3.1.11
274
288
  [3.1.10]: https://github.com/PolicyEngine/policyengine.py/compare/3.1.9...3.1.10
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: policyengine
3
- Version: 3.1.12
3
+ Version: 3.1.14
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
@@ -220,3 +220,13 @@
220
220
  fixed:
221
221
  - Bug where all datasets would be the last year.
222
222
  date: 2025-12-10 14:43:52
223
+ - bump: patch
224
+ changes:
225
+ fixed:
226
+ - Naming improvements.
227
+ date: 2025-12-10 19:29:28
228
+ - bump: patch
229
+ changes:
230
+ fixed:
231
+ - Improvements to loading taxbenefitsystems.
232
+ date: 2025-12-10 21:59:24
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "policyengine"
7
- version = "3.1.12"
7
+ version = "3.1.14"
8
8
  description = "A package to conduct policy analysis using PolicyEngine tax-benefit models."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -0,0 +1,65 @@
1
+ from typing import TYPE_CHECKING, Any
2
+ from uuid import uuid4
3
+
4
+ from pydantic import BaseModel, Field, PrivateAttr
5
+
6
+ from .parameter_value import ParameterValue
7
+ from .tax_benefit_model_version import TaxBenefitModelVersion
8
+
9
+ if TYPE_CHECKING:
10
+ from .parameter_value import ParameterValue
11
+
12
+
13
+ class Parameter(BaseModel):
14
+ model_config = {"arbitrary_types_allowed": True}
15
+
16
+ id: str = Field(default_factory=lambda: str(uuid4()))
17
+ name: str
18
+ label: str | None = None
19
+ description: str | None = None
20
+ data_type: type | None = None
21
+ tax_benefit_model_version: TaxBenefitModelVersion
22
+ unit: str | None = None
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(
@@ -196,8 +196,8 @@ def load_datasets(
196
196
  for year in years:
197
197
  filepath = f"{data_folder}/{Path(dataset).stem}_year_{year}.h5"
198
198
  uk_dataset = PolicyEngineUKDataset(
199
- name=f"{dataset}-year-{year}",
200
- description=f"UK Dataset for year {year} based on {dataset}",
199
+ name=f"{Path(dataset).stem}-year-{year}",
200
+ description=f"UK Dataset for year {year} based on {Path(dataset).stem}",
201
201
  filepath=filepath,
202
202
  year=int(year),
203
203
  )
@@ -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(
@@ -317,8 +317,8 @@ def load_datasets(
317
317
  for year in years:
318
318
  filepath = f"{data_folder}/{Path(dataset).stem}_year_{year}.h5"
319
319
  us_dataset = PolicyEngineUSDataset(
320
- name=f"{dataset}-year-{year}",
321
- description=f"US Dataset for year {year} based on {dataset}",
320
+ name=f"{Path(dataset).stem}-year-{year}",
321
+ description=f"US Dataset for year {year} based on {Path(dataset).stem}",
322
322
  filepath=filepath,
323
323
  year=year,
324
324
  )
@@ -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():
@@ -143,33 +141,14 @@ class PolicyEngineUSLatest(TaxBenefitModelVersion):
143
141
  parameter = Parameter(
144
142
  id=self.id + "-" + param_node.name,
145
143
  name=param_node.name,
144
+ label=param_node.metadata.get("label"),
146
145
  tax_benefit_model_version=self,
147
146
  description=param_node.description,
148
147
  data_type=type(param_node(2025)),
149
148
  unit=param_node.metadata.get("unit"),
149
+ _core_param=param_node, # Store for lazy value loading
150
150
  )
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
- )
151
+ self.add_parameter(parameter)
173
152
 
174
153
  def run(self, simulation: "Simulation") -> "Simulation":
175
154
  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.14
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
@@ -79,4 +79,5 @@ src/policyengine/utils/plotting.py
79
79
  tests/test_aggregate.py
80
80
  tests/test_cache.py
81
81
  tests/test_change_aggregate.py
82
- tests/test_entity_mapping.py
82
+ tests/test_entity_mapping.py
83
+ tests/test_models.py
@@ -0,0 +1,68 @@
1
+ """Tests for UK and US tax-benefit model versions."""
2
+
3
+ import pytest
4
+
5
+ from policyengine.tax_benefit_models.uk import uk_latest
6
+ from policyengine.tax_benefit_models.us import us_latest
7
+
8
+
9
+ class TestUKModel:
10
+ """Tests for PolicyEngine UK model."""
11
+
12
+ def test_has_hundreds_of_parameters(self):
13
+ """UK model should have hundreds of parameters."""
14
+ assert len(uk_latest.parameters) >= 100
15
+
16
+ def test_has_hundreds_of_variables(self):
17
+ """UK model should have hundreds of variables."""
18
+ assert len(uk_latest.variables) >= 100
19
+
20
+ def test_parameters_have_values(self):
21
+ """Each parameter should have at least one parameter value."""
22
+ total_values = 0
23
+ for param in uk_latest.parameters[:100]: # Check first 100 for speed
24
+ values = param.parameter_values
25
+ assert len(values) >= 1, f"Parameter {param.name} has no values"
26
+ total_values += len(values)
27
+
28
+ # Should have many parameter values in total
29
+ assert total_values >= 100
30
+
31
+ def test_parameter_values_have_required_fields(self):
32
+ """Parameter values should have start_date and value."""
33
+ for param in uk_latest.parameters[:50]:
34
+ for pv in param.parameter_values:
35
+ assert pv.start_date is not None
36
+ assert pv.value is not None
37
+ assert pv.parameter is param
38
+
39
+
40
+ class TestUSModel:
41
+ """Tests for PolicyEngine US model."""
42
+
43
+ def test_has_hundreds_of_parameters(self):
44
+ """US model should have hundreds of parameters."""
45
+ assert len(us_latest.parameters) >= 100
46
+
47
+ def test_has_hundreds_of_variables(self):
48
+ """US model should have hundreds of variables."""
49
+ assert len(us_latest.variables) >= 100
50
+
51
+ def test_parameters_have_values(self):
52
+ """Each parameter should have at least one parameter value."""
53
+ total_values = 0
54
+ for param in us_latest.parameters[:100]: # Check first 100 for speed
55
+ values = param.parameter_values
56
+ assert len(values) >= 1, f"Parameter {param.name} has no values"
57
+ total_values += len(values)
58
+
59
+ # Should have many parameter values in total
60
+ assert total_values >= 100
61
+
62
+ def test_parameter_values_have_required_fields(self):
63
+ """Parameter values should have start_date and value."""
64
+ for param in us_latest.parameters[:50]:
65
+ for pv in param.parameter_values:
66
+ assert pv.start_date is not None
67
+ assert pv.value is not None
68
+ assert pv.parameter is param
@@ -1080,12 +1080,13 @@ wheels = [
1080
1080
 
1081
1081
  [[package]]
1082
1082
  name = "policyengine"
1083
- version = "3.1.7"
1083
+ version = "3.1.12"
1084
1084
  source = { editable = "." }
1085
1085
  dependencies = [
1086
1086
  { name = "microdf-python" },
1087
1087
  { name = "pandas" },
1088
1088
  { name = "plotly" },
1089
+ { name = "psutil" },
1089
1090
  { name = "pydantic" },
1090
1091
  { name = "requests" },
1091
1092
  ]
@@ -1133,6 +1134,7 @@ requires-dist = [
1133
1134
  { name = "policyengine-uk", marker = "extra == 'uk'", specifier = ">=2.51.0" },
1134
1135
  { name = "policyengine-us", marker = "extra == 'dev'", specifier = ">=1.213.1" },
1135
1136
  { name = "policyengine-us", marker = "extra == 'us'", specifier = ">=1.213.1" },
1137
+ { name = "psutil", specifier = ">=5.9.0" },
1136
1138
  { name = "pydantic", specifier = ">=2.0.0" },
1137
1139
  { name = "pytest", marker = "extra == 'dev'" },
1138
1140
  { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.26.0" },
@@ -1,21 +0,0 @@
1
- from typing import TYPE_CHECKING
2
- from uuid import uuid4
3
-
4
- from pydantic import BaseModel, Field
5
-
6
- from .parameter_value import ParameterValue
7
- from .tax_benefit_model_version import TaxBenefitModelVersion
8
-
9
- if TYPE_CHECKING:
10
- from .parameter_value import ParameterValue
11
-
12
-
13
- class Parameter(BaseModel):
14
- id: str = Field(default_factory=lambda: str(uuid4()))
15
- name: str
16
- label: str | None = None
17
- description: str | None = None
18
- data_type: type | None = None
19
- tax_benefit_model_version: TaxBenefitModelVersion
20
- unit: str | None = None
21
- parameter_values: list["ParameterValue"] = Field(default_factory=list)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes