policyengine 3.1.7__tar.gz → 3.1.9__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 (90) hide show
  1. {policyengine-3.1.7 → policyengine-3.1.9}/CHANGELOG.md +14 -0
  2. {policyengine-3.1.7 → policyengine-3.1.9}/PKG-INFO +2 -1
  3. {policyengine-3.1.7 → policyengine-3.1.9}/changelog.yaml +10 -0
  4. {policyengine-3.1.7 → policyengine-3.1.9}/pyproject.toml +2 -1
  5. policyengine-3.1.9/src/policyengine/__pycache__/__init__.cpython-313.pyc +0 -0
  6. policyengine-3.1.9/src/policyengine/core/cache.py +56 -0
  7. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/core/simulation.py +8 -0
  8. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/tax_benefit_models/uk/datasets.py +27 -4
  9. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine.egg-info/PKG-INFO +2 -1
  10. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine.egg-info/SOURCES.txt +3 -6
  11. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine.egg-info/requires.txt +1 -0
  12. policyengine-3.1.9/tests/test_cache.py +145 -0
  13. {policyengine-3.1.7 → policyengine-3.1.9}/uv.lock +1 -1
  14. policyengine-3.1.7/src/policyengine/__pycache__/__init__.cpython-313.pyc +0 -0
  15. policyengine-3.1.7/tests/test_get_parameter_variable.py +0 -141
  16. policyengine-3.1.7/tests/test_uk_dataset.py +0 -112
  17. policyengine-3.1.7/tests/test_us_datasets.py +0 -109
  18. policyengine-3.1.7/tests/test_us_entity_mapping.py +0 -334
  19. policyengine-3.1.7/tests/test_us_simulation.py +0 -249
  20. {policyengine-3.1.7 → policyengine-3.1.9}/.claude/policyengine-guide.md +0 -0
  21. {policyengine-3.1.7 → policyengine-3.1.9}/.claude/quick-reference.md +0 -0
  22. {policyengine-3.1.7 → policyengine-3.1.9}/.github/CONTRIBUTING.md +0 -0
  23. {policyengine-3.1.7 → policyengine-3.1.9}/.github/changelog_template.md +0 -0
  24. {policyengine-3.1.7 → policyengine-3.1.9}/.github/fetch_version.py +0 -0
  25. {policyengine-3.1.7 → policyengine-3.1.9}/.github/get-changelog-diff.sh +0 -0
  26. {policyengine-3.1.7 → policyengine-3.1.9}/.github/has-functional-changes.sh +0 -0
  27. {policyengine-3.1.7 → policyengine-3.1.9}/.github/is-version-number-acceptable.sh +0 -0
  28. {policyengine-3.1.7 → policyengine-3.1.9}/.github/publish-git-tag.sh +0 -0
  29. {policyengine-3.1.7 → policyengine-3.1.9}/.github/workflows/code_changes.yaml +0 -0
  30. {policyengine-3.1.7 → policyengine-3.1.9}/.github/workflows/docs.yml +0 -0
  31. {policyengine-3.1.7 → policyengine-3.1.9}/.github/workflows/pr_code_changes.yaml +0 -0
  32. {policyengine-3.1.7 → policyengine-3.1.9}/.github/workflows/pr_docs_changes.yaml +0 -0
  33. {policyengine-3.1.7 → policyengine-3.1.9}/.github/workflows/versioning.yaml +0 -0
  34. {policyengine-3.1.7 → policyengine-3.1.9}/.gitignore +0 -0
  35. {policyengine-3.1.7 → policyengine-3.1.9}/CLAUDE.md +0 -0
  36. {policyengine-3.1.7 → policyengine-3.1.9}/LICENSE +0 -0
  37. {policyengine-3.1.7 → policyengine-3.1.9}/Makefile +0 -0
  38. {policyengine-3.1.7 → policyengine-3.1.9}/README.md +0 -0
  39. {policyengine-3.1.7 → policyengine-3.1.9}/changelog_entry.yaml +0 -0
  40. {policyengine-3.1.7 → policyengine-3.1.9}/docs/.gitignore +0 -0
  41. {policyengine-3.1.7 → policyengine-3.1.9}/docs/core-concepts.md +0 -0
  42. {policyengine-3.1.7 → policyengine-3.1.9}/docs/country-models-uk.md +0 -0
  43. {policyengine-3.1.7 → policyengine-3.1.9}/docs/country-models-us.md +0 -0
  44. {policyengine-3.1.7 → policyengine-3.1.9}/docs/dev.md +0 -0
  45. {policyengine-3.1.7 → policyengine-3.1.9}/docs/index.md +0 -0
  46. {policyengine-3.1.7 → policyengine-3.1.9}/docs/myst.yml +0 -0
  47. {policyengine-3.1.7 → policyengine-3.1.9}/docs/visualisation.md +0 -0
  48. {policyengine-3.1.7 → policyengine-3.1.9}/examples/employment_income_variation_uk.py +0 -0
  49. {policyengine-3.1.7 → policyengine-3.1.9}/examples/employment_income_variation_us.py +0 -0
  50. {policyengine-3.1.7 → policyengine-3.1.9}/examples/income_bands_uk.py +0 -0
  51. {policyengine-3.1.7 → policyengine-3.1.9}/examples/income_distribution_us.py +0 -0
  52. {policyengine-3.1.7 → policyengine-3.1.9}/examples/policy_change_uk.py +0 -0
  53. {policyengine-3.1.7 → policyengine-3.1.9}/examples/speedtest_us_simulation.py +0 -0
  54. {policyengine-3.1.7 → policyengine-3.1.9}/setup.cfg +0 -0
  55. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/__init__.py +0 -0
  56. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/core/__init__.py +0 -0
  57. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/core/dataset.py +0 -0
  58. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/core/dataset_version.py +0 -0
  59. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/core/dynamic.py +0 -0
  60. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/core/output.py +0 -0
  61. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/core/parameter.py +0 -0
  62. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/core/parameter_value.py +0 -0
  63. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/core/policy.py +0 -0
  64. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/core/tax_benefit_model.py +0 -0
  65. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/core/tax_benefit_model_version.py +0 -0
  66. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/core/variable.py +0 -0
  67. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/outputs/__init__.py +0 -0
  68. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/outputs/aggregate.py +0 -0
  69. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/outputs/change_aggregate.py +0 -0
  70. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/outputs/decile_impact.py +0 -0
  71. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/tax_benefit_models/uk/__init__.py +0 -0
  72. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/tax_benefit_models/uk/analysis.py +0 -0
  73. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/tax_benefit_models/uk/model.py +0 -0
  74. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/tax_benefit_models/uk/outputs.py +0 -0
  75. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/tax_benefit_models/uk.py +0 -0
  76. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/tax_benefit_models/us/__init__.py +0 -0
  77. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/tax_benefit_models/us/analysis.py +0 -0
  78. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/tax_benefit_models/us/datasets.py +0 -0
  79. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/tax_benefit_models/us/model.py +0 -0
  80. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/tax_benefit_models/us/outputs.py +0 -0
  81. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/tax_benefit_models/us.py +0 -0
  82. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/utils/__init__.py +0 -0
  83. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/utils/dates.py +0 -0
  84. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/utils/parametric_reforms.py +0 -0
  85. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine/utils/plotting.py +0 -0
  86. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine.egg-info/dependency_links.txt +0 -0
  87. {policyengine-3.1.7 → policyengine-3.1.9}/src/policyengine.egg-info/top_level.txt +0 -0
  88. {policyengine-3.1.7 → policyengine-3.1.9}/tests/test_aggregate.py +0 -0
  89. {policyengine-3.1.7 → policyengine-3.1.9}/tests/test_change_aggregate.py +0 -0
  90. {policyengine-3.1.7 → policyengine-3.1.9}/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.9] - 2025-12-02 12:48:37
9
+
10
+ ### Fixed
11
+
12
+ - Added caching of saved simulations
13
+
14
+ ## [3.1.8] - 2025-12-02 00:20:11
15
+
16
+ ### Fixed
17
+
18
+ - Dataset speedup with better handling of string cols.
19
+
8
20
  ## [3.1.7] - 2025-11-24 16:34:53
9
21
 
10
22
  ### Fixed
@@ -239,6 +251,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
239
251
 
240
252
 
241
253
 
254
+ [3.1.9]: https://github.com/PolicyEngine/policyengine.py/compare/3.1.8...3.1.9
255
+ [3.1.8]: https://github.com/PolicyEngine/policyengine.py/compare/3.1.7...3.1.8
242
256
  [3.1.7]: https://github.com/PolicyEngine/policyengine.py/compare/3.1.6...3.1.7
243
257
  [3.1.6]: https://github.com/PolicyEngine/policyengine.py/compare/3.1.5...3.1.6
244
258
  [3.1.5]: https://github.com/PolicyEngine/policyengine.py/compare/3.1.4...3.1.5
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: policyengine
3
- Version: 3.1.7
3
+ Version: 3.1.9
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
@@ -673,6 +673,7 @@ Requires-Dist: pandas>=2.0.0
673
673
  Requires-Dist: microdf_python
674
674
  Requires-Dist: plotly>=5.0.0
675
675
  Requires-Dist: requests>=2.31.0
676
+ Requires-Dist: psutil>=5.9.0
676
677
  Provides-Extra: uk
677
678
  Requires-Dist: policyengine_core>=3.10; extra == "uk"
678
679
  Requires-Dist: policyengine-uk>=2.51.0; extra == "uk"
@@ -195,3 +195,13 @@
195
195
  fixed:
196
196
  - Build error
197
197
  date: 2025-11-24 16:34:53
198
+ - bump: patch
199
+ changes:
200
+ fixed:
201
+ - Dataset speedup with better handling of string cols.
202
+ date: 2025-12-02 00:20:11
203
+ - bump: patch
204
+ changes:
205
+ fixed:
206
+ - Added caching of saved simulations
207
+ date: 2025-12-02 12:48:37
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "policyengine"
7
- version = "3.1.7"
7
+ version = "3.1.9"
8
8
  description = "A package to conduct policy analysis using PolicyEngine tax-benefit models."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -18,6 +18,7 @@ dependencies = [
18
18
  "microdf_python",
19
19
  "plotly>=5.0.0",
20
20
  "requests>=2.31.0",
21
+ "psutil>=5.9.0",
21
22
  ]
22
23
 
23
24
  [project.optional-dependencies]
@@ -0,0 +1,56 @@
1
+ import logging
2
+ from collections import OrderedDict
3
+
4
+ import psutil
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ _MEMORY_THRESHOLDS_GB = [8, 16, 32]
9
+ _warned_thresholds: set[int] = set()
10
+
11
+
12
+ class LRUCache[T]:
13
+ """Least-recently-used cache with configurable size limit and memory monitoring."""
14
+
15
+ def __init__(self, max_size: int = 100):
16
+ self._max_size = max_size
17
+ self._cache: OrderedDict[str, T] = OrderedDict()
18
+
19
+ def get(self, key: str) -> T | None:
20
+ """Get item from cache, marking it as recently used."""
21
+ if key not in self._cache:
22
+ return None
23
+ self._cache.move_to_end(key)
24
+ return self._cache[key]
25
+
26
+ def add(self, key: str, value: T) -> None:
27
+ """Add item to cache with LRU eviction when full."""
28
+ if key in self._cache:
29
+ self._cache.move_to_end(key)
30
+ else:
31
+ self._cache[key] = value
32
+ if len(self._cache) > self._max_size:
33
+ self._cache.popitem(last=False)
34
+
35
+ self._check_memory_usage()
36
+
37
+ def clear(self) -> None:
38
+ """Clear all items from cache."""
39
+ self._cache.clear()
40
+ _warned_thresholds.clear()
41
+
42
+ def __len__(self) -> int:
43
+ return len(self._cache)
44
+
45
+ def _check_memory_usage(self) -> None:
46
+ """Check memory usage and warn at threshold crossings."""
47
+ process = psutil.Process()
48
+ memory_gb = process.memory_info().rss / (1024**3)
49
+
50
+ for threshold in _MEMORY_THRESHOLDS_GB:
51
+ if memory_gb >= threshold and threshold not in _warned_thresholds:
52
+ logger.warning(
53
+ f"Memory usage has reached {memory_gb:.2f}GB (threshold: {threshold}GB). "
54
+ f"Cache contains {len(self._cache)} items."
55
+ )
56
+ _warned_thresholds.add(threshold)
@@ -3,11 +3,14 @@ from uuid import uuid4
3
3
 
4
4
  from pydantic import BaseModel, Field
5
5
 
6
+ from .cache import LRUCache
6
7
  from .dataset import Dataset
7
8
  from .dynamic import Dynamic
8
9
  from .policy import Policy
9
10
  from .tax_benefit_model_version import TaxBenefitModelVersion
10
11
 
12
+ _cache: LRUCache["Simulation"] = LRUCache(max_size=100)
13
+
11
14
 
12
15
  class Simulation(BaseModel):
13
16
  id: str = Field(default_factory=lambda: str(uuid4()))
@@ -25,12 +28,17 @@ class Simulation(BaseModel):
25
28
  self.tax_benefit_model_version.run(self)
26
29
 
27
30
  def ensure(self):
31
+ cached_result = _cache.get(self.id)
32
+ if cached_result:
33
+ return cached_result
28
34
  try:
29
35
  self.tax_benefit_model_version.load(self)
30
36
  except Exception:
31
37
  self.run()
32
38
  self.save()
33
39
 
40
+ _cache.add(self.id, self)
41
+
34
42
  def save(self):
35
43
  """Save the simulation's output dataset."""
36
44
  self.tax_benefit_model_version.save(self)
@@ -40,14 +40,37 @@ class PolicyEngineUKDataset(Dataset):
40
40
  self.load()
41
41
 
42
42
  def save(self) -> None:
43
- """Save dataset to HDF5 file."""
43
+ """Save dataset to HDF5 file.
44
+
45
+ Converts object columns to categorical dtype to avoid slow pickle serialization.
46
+ """
44
47
  filepath = Path(self.filepath)
45
48
  if not filepath.parent.exists():
46
49
  filepath.parent.mkdir(parents=True, exist_ok=True)
50
+
51
+ # Convert DataFrames and optimize object columns to categorical
52
+ person_df = pd.DataFrame(self.data.person)
53
+ benunit_df = pd.DataFrame(self.data.benunit)
54
+ household_df = pd.DataFrame(self.data.household)
55
+
56
+ # Convert object columns to categorical to avoid pickle serialization
57
+ for col in person_df.columns:
58
+ if person_df[col].dtype == "object":
59
+ person_df[col] = person_df[col].astype("category")
60
+
61
+ for col in benunit_df.columns:
62
+ if benunit_df[col].dtype == "object":
63
+ benunit_df[col] = benunit_df[col].astype("category")
64
+
65
+ for col in household_df.columns:
66
+ if household_df[col].dtype == "object":
67
+ household_df[col] = household_df[col].astype("category")
68
+
47
69
  with pd.HDFStore(filepath, mode="w") as store:
48
- store["person"] = pd.DataFrame(self.data.person)
49
- store["benunit"] = pd.DataFrame(self.data.benunit)
50
- store["household"] = pd.DataFrame(self.data.household)
70
+ # Use format='table' to support categorical dtypes
71
+ store.put("person", person_df, format="table")
72
+ store.put("benunit", benunit_df, format="table")
73
+ store.put("household", household_df, format="table")
51
74
 
52
75
  def load(self) -> None:
53
76
  """Load dataset from HDF5 file into this instance."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: policyengine
3
- Version: 3.1.7
3
+ Version: 3.1.9
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
@@ -673,6 +673,7 @@ Requires-Dist: pandas>=2.0.0
673
673
  Requires-Dist: microdf_python
674
674
  Requires-Dist: plotly>=5.0.0
675
675
  Requires-Dist: requests>=2.31.0
676
+ Requires-Dist: psutil>=5.9.0
676
677
  Provides-Extra: uk
677
678
  Requires-Dist: policyengine_core>=3.10; extra == "uk"
678
679
  Requires-Dist: policyengine-uk>=2.51.0; extra == "uk"
@@ -44,6 +44,7 @@ src/policyengine.egg-info/requires.txt
44
44
  src/policyengine.egg-info/top_level.txt
45
45
  src/policyengine/__pycache__/__init__.cpython-313.pyc
46
46
  src/policyengine/core/__init__.py
47
+ src/policyengine/core/cache.py
47
48
  src/policyengine/core/dataset.py
48
49
  src/policyengine/core/dataset_version.py
49
50
  src/policyengine/core/dynamic.py
@@ -76,10 +77,6 @@ src/policyengine/utils/dates.py
76
77
  src/policyengine/utils/parametric_reforms.py
77
78
  src/policyengine/utils/plotting.py
78
79
  tests/test_aggregate.py
80
+ tests/test_cache.py
79
81
  tests/test_change_aggregate.py
80
- tests/test_entity_mapping.py
81
- tests/test_get_parameter_variable.py
82
- tests/test_uk_dataset.py
83
- tests/test_us_datasets.py
84
- tests/test_us_entity_mapping.py
85
- tests/test_us_simulation.py
82
+ tests/test_entity_mapping.py
@@ -3,6 +3,7 @@ pandas>=2.0.0
3
3
  microdf_python
4
4
  plotly>=5.0.0
5
5
  requests>=2.31.0
6
+ psutil>=5.9.0
6
7
 
7
8
  [dev]
8
9
  black
@@ -0,0 +1,145 @@
1
+ import os
2
+ import tempfile
3
+
4
+ import pandas as pd
5
+ from microdf import MicroDataFrame
6
+
7
+ from policyengine.core import Simulation
8
+ from policyengine.core.cache import LRUCache
9
+ from policyengine.tax_benefit_models.uk import (
10
+ PolicyEngineUKDataset,
11
+ UKYearData,
12
+ uk_latest,
13
+ )
14
+
15
+
16
+ def test_simulation_cache_hit():
17
+ """Test that simulation caching works with UK simulations."""
18
+ person_df = MicroDataFrame(
19
+ pd.DataFrame(
20
+ {
21
+ "person_id": [1, 2, 3],
22
+ "benunit_id": [1, 1, 2],
23
+ "household_id": [1, 1, 2],
24
+ "age": [30, 25, 40],
25
+ "employment_income": [50000, 30000, 60000],
26
+ "person_weight": [1.0, 1.0, 1.0],
27
+ }
28
+ ),
29
+ weights="person_weight",
30
+ )
31
+
32
+ benunit_df = MicroDataFrame(
33
+ pd.DataFrame(
34
+ {
35
+ "benunit_id": [1, 2],
36
+ "benunit_weight": [1.0, 1.0],
37
+ }
38
+ ),
39
+ weights="benunit_weight",
40
+ )
41
+
42
+ household_df = MicroDataFrame(
43
+ pd.DataFrame(
44
+ {
45
+ "household_id": [1, 2],
46
+ "household_weight": [1.0, 1.0],
47
+ }
48
+ ),
49
+ weights="household_weight",
50
+ )
51
+
52
+ with tempfile.TemporaryDirectory() as tmpdir:
53
+ filepath = os.path.join(tmpdir, "test.h5")
54
+
55
+ dataset = PolicyEngineUKDataset(
56
+ name="Test",
57
+ description="Test dataset",
58
+ filepath=filepath,
59
+ year=2024,
60
+ data=UKYearData(
61
+ person=person_df, benunit=benunit_df, household=household_df
62
+ ),
63
+ )
64
+
65
+ simulation = Simulation(
66
+ dataset=dataset,
67
+ tax_benefit_model_version=uk_latest,
68
+ output_dataset=dataset,
69
+ )
70
+
71
+ # Import the cache
72
+ from policyengine.core.simulation import _cache
73
+
74
+ # Manually add to cache (simulating what ensure() does)
75
+ _cache.add(simulation.id, simulation)
76
+
77
+ # Verify simulation is in cache
78
+ assert simulation.id in _cache._cache
79
+ assert len(_cache) >= 1
80
+
81
+ # Verify cache returns same object
82
+ cached_sim = _cache.get(simulation.id)
83
+ assert cached_sim is simulation
84
+
85
+ # Clear cache for other tests
86
+ _cache.clear()
87
+
88
+
89
+ def test_lru_cache_eviction():
90
+ """Test that LRU cache properly evicts old items."""
91
+ cache = LRUCache[str](max_size=3)
92
+
93
+ cache.add("a", "value_a")
94
+ cache.add("b", "value_b")
95
+ cache.add("c", "value_c")
96
+
97
+ assert len(cache) == 3
98
+ assert cache.get("a") == "value_a"
99
+
100
+ # Add fourth item, should evict 'b' (least recently used)
101
+ cache.add("d", "value_d")
102
+
103
+ assert len(cache) == 3
104
+ assert cache.get("b") is None
105
+ assert cache.get("a") == "value_a"
106
+ assert cache.get("c") == "value_c"
107
+ assert cache.get("d") == "value_d"
108
+
109
+
110
+ def test_lru_cache_access_updates_order():
111
+ """Test that accessing items updates their position in LRU order."""
112
+ cache = LRUCache[str](max_size=3)
113
+
114
+ cache.add("a", "value_a")
115
+ cache.add("b", "value_b")
116
+ cache.add("c", "value_c")
117
+
118
+ # Access 'a' to move it to most recently used
119
+ cache.get("a")
120
+
121
+ # Add fourth item, should evict 'b' (now least recently used)
122
+ cache.add("d", "value_d")
123
+
124
+ assert cache.get("a") == "value_a"
125
+ assert cache.get("b") is None
126
+ assert cache.get("c") == "value_c"
127
+ assert cache.get("d") == "value_d"
128
+
129
+
130
+ def test_lru_cache_clear():
131
+ """Test that clearing cache works properly."""
132
+ cache = LRUCache[str](max_size=10)
133
+
134
+ cache.add("a", "value_a")
135
+ cache.add("b", "value_b")
136
+ cache.add("c", "value_c")
137
+
138
+ assert len(cache) == 3
139
+
140
+ cache.clear()
141
+
142
+ assert len(cache) == 0
143
+ assert cache.get("a") is None
144
+ assert cache.get("b") is None
145
+ assert cache.get("c") is None
@@ -1080,7 +1080,7 @@ wheels = [
1080
1080
 
1081
1081
  [[package]]
1082
1082
  name = "policyengine"
1083
- version = "3.0.0"
1083
+ version = "3.1.7"
1084
1084
  source = { editable = "." }
1085
1085
  dependencies = [
1086
1086
  { name = "microdf-python" },
@@ -1,141 +0,0 @@
1
- """Tests for get_parameter and get_variable methods on TaxBenefitModelVersion."""
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
- def test_uk_get_variable():
10
- """Test getting a variable by name from UK model."""
11
- # Get a known variable
12
- var = uk_latest.get_variable("income_tax")
13
-
14
- assert var is not None
15
- assert var.name == "income_tax"
16
- assert var.entity == "person"
17
- assert var.tax_benefit_model_version == uk_latest
18
-
19
-
20
- def test_uk_get_variable_not_found():
21
- """Test error handling when variable doesn't exist."""
22
- with pytest.raises(
23
- ValueError, match="Variable 'nonexistent_variable' not found"
24
- ):
25
- uk_latest.get_variable("nonexistent_variable")
26
-
27
-
28
- def test_uk_get_parameter():
29
- """Test getting a parameter by name from UK model."""
30
- # Get a known parameter
31
- param = uk_latest.get_parameter(
32
- "gov.hmrc.income_tax.allowances.personal_allowance.amount"
33
- )
34
-
35
- assert param is not None
36
- assert (
37
- param.name
38
- == "gov.hmrc.income_tax.allowances.personal_allowance.amount"
39
- )
40
- assert param.tax_benefit_model_version == uk_latest
41
-
42
-
43
- def test_uk_get_parameter_not_found():
44
- """Test error handling when parameter doesn't exist."""
45
- with pytest.raises(
46
- ValueError, match="Parameter 'nonexistent.parameter' not found"
47
- ):
48
- uk_latest.get_parameter("nonexistent.parameter")
49
-
50
-
51
- def test_us_get_variable():
52
- """Test getting a variable by name from US model."""
53
- # Get a known variable
54
- var = us_latest.get_variable("income_tax")
55
-
56
- assert var is not None
57
- assert var.name == "income_tax"
58
- assert var.entity == "tax_unit"
59
- assert var.tax_benefit_model_version == us_latest
60
-
61
-
62
- def test_us_get_variable_not_found():
63
- """Test error handling when variable doesn't exist."""
64
- with pytest.raises(
65
- ValueError, match="Variable 'nonexistent_variable' not found"
66
- ):
67
- us_latest.get_variable("nonexistent_variable")
68
-
69
-
70
- def test_us_get_parameter():
71
- """Test getting a parameter by name from US model."""
72
- # Get a known parameter
73
- param = us_latest.get_parameter(
74
- "gov.irs.investment.net_investment_income_tax.rate"
75
- )
76
-
77
- assert param is not None
78
- assert param.name == "gov.irs.investment.net_investment_income_tax.rate"
79
- assert param.tax_benefit_model_version == us_latest
80
-
81
-
82
- def test_us_get_parameter_not_found():
83
- """Test error handling when parameter doesn't exist."""
84
- with pytest.raises(
85
- ValueError, match="Parameter 'nonexistent.parameter' not found"
86
- ):
87
- us_latest.get_parameter("nonexistent.parameter")
88
-
89
-
90
- def test_uk_multiple_variables():
91
- """Test getting multiple different variables."""
92
- vars_to_test = [
93
- "income_tax",
94
- "national_insurance",
95
- "universal_credit",
96
- "household_net_income",
97
- ]
98
-
99
- for var_name in vars_to_test:
100
- var = uk_latest.get_variable(var_name)
101
- assert var.name == var_name
102
-
103
-
104
- def test_us_multiple_variables():
105
- """Test getting multiple different variables."""
106
- vars_to_test = [
107
- "income_tax",
108
- "employee_payroll_tax",
109
- "eitc",
110
- "household_net_income",
111
- ]
112
-
113
- for var_name in vars_to_test:
114
- var = us_latest.get_variable(var_name)
115
- assert var.name == var_name
116
-
117
-
118
- def test_uk_multiple_parameters():
119
- """Test getting multiple different parameters."""
120
- params_to_test = [
121
- "gov.hmrc.income_tax.allowances.personal_allowance.amount",
122
- "gov.hmrc.income_tax.rates.uk[0].rate",
123
- "gov.dwp.universal_credit.means_test.reduction_rate",
124
- ]
125
-
126
- for param_name in params_to_test:
127
- param = uk_latest.get_parameter(param_name)
128
- assert param.name == param_name
129
-
130
-
131
- def test_us_multiple_parameters():
132
- """Test getting multiple different parameters."""
133
- params_to_test = [
134
- "gov.irs.investment.net_investment_income_tax.rate",
135
- "gov.irs.self_employment.rate.social_security",
136
- "gov.irs.vita.eligibility.income_limit",
137
- ]
138
-
139
- for param_name in params_to_test:
140
- param = us_latest.get_parameter(param_name)
141
- assert param.name == param_name
@@ -1,112 +0,0 @@
1
- import os
2
- import tempfile
3
-
4
- import pandas as pd
5
- from microdf import MicroDataFrame
6
-
7
- from policyengine.core import Dataset, TaxBenefitModel
8
- from policyengine.tax_benefit_models.uk import (
9
- PolicyEngineUKDataset,
10
- UKYearData,
11
- )
12
-
13
-
14
- def test_imports():
15
- """Test that basic imports work."""
16
- # Verify classes are importable
17
- assert PolicyEngineUKDataset is not None
18
- assert UKYearData is not None
19
- assert Dataset is not None
20
- assert TaxBenefitModel is not None
21
-
22
-
23
- def test_uk_latest_instantiation():
24
- """Test that uk_latest can be instantiated without errors."""
25
- from policyengine.tax_benefit_models.uk import uk_latest
26
-
27
- assert uk_latest is not None
28
- assert uk_latest.version is not None
29
- assert uk_latest.model is not None
30
- assert uk_latest.created_at is not None
31
- assert (
32
- len(uk_latest.variables) > 0
33
- ) # Should have variables from policyengine-uk
34
-
35
-
36
- def test_save_and_load_single_year():
37
- """Test saving and loading a dataset with a single year."""
38
- # Create sample data
39
- person_df = MicroDataFrame(
40
- pd.DataFrame(
41
- {
42
- "person_id": [1, 2, 3],
43
- "age": [25, 30, 35],
44
- "income": [30000, 45000, 60000],
45
- "person_weight": [1.0, 1.0, 1.0],
46
- }
47
- ),
48
- weights="person_weight",
49
- )
50
-
51
- benunit_df = MicroDataFrame(
52
- pd.DataFrame(
53
- {
54
- "benunit_id": [1, 2],
55
- "size": [2, 1],
56
- "total_income": [75000, 60000],
57
- "benunit_weight": [1.0, 1.0],
58
- }
59
- ),
60
- weights="benunit_weight",
61
- )
62
-
63
- household_df = MicroDataFrame(
64
- pd.DataFrame(
65
- {
66
- "household_id": [1],
67
- "num_people": [3],
68
- "rent": [1200],
69
- "household_weight": [1.0],
70
- }
71
- ),
72
- weights="household_weight",
73
- )
74
-
75
- # Create dataset
76
- with tempfile.TemporaryDirectory() as tmpdir:
77
- filepath = os.path.join(tmpdir, "test_dataset.h5")
78
-
79
- dataset = PolicyEngineUKDataset(
80
- name="Test Dataset",
81
- description="A test dataset",
82
- filepath=filepath,
83
- year=2025,
84
- data=UKYearData(
85
- person=person_df, benunit=benunit_df, household=household_df
86
- ),
87
- )
88
-
89
- # Save to file
90
- dataset.save()
91
-
92
- # Load it back
93
- loaded = PolicyEngineUKDataset(
94
- name="Loaded Dataset",
95
- description="Loaded from file",
96
- filepath=filepath,
97
- year=2025,
98
- )
99
- loaded.load()
100
-
101
- # Verify data
102
- assert loaded.year == 2025
103
- # Convert to DataFrame for comparison (MicroDataFrame inherits from DataFrame)
104
- pd.testing.assert_frame_equal(
105
- pd.DataFrame(loaded.data.person), pd.DataFrame(person_df)
106
- )
107
- pd.testing.assert_frame_equal(
108
- pd.DataFrame(loaded.data.benunit), pd.DataFrame(benunit_df)
109
- )
110
- pd.testing.assert_frame_equal(
111
- pd.DataFrame(loaded.data.household), pd.DataFrame(household_df)
112
- )