Skip to content

Commit 6fb01dc

Browse files
anth-volkclaude
andcommitted
Derive default end year from CPI-U YAML and reorganize test fixtures
- Replace hardcoded DEFAULT_END_YEAR = 2035 with dynamic derivation from the CPI-U parameter YAML (gov.bls.cpi.cpi_u), so datasets automatically extend to whatever year the CPI-U is updated to - Add get_parameter_last_year() utility that reads a parameter's source YAML, sorts date keys, and returns the latest year (reads YAML on disk, not runtime values_list, to avoid the 2100 extrapolation from uprating_extensions.py) - Rename fixtures/economic_assumptions_fixtures.py to fixtures/test_extend_single_year_dataset.py to match test file name - Move all test setup code (helpers, mocks, pytest fixtures) from test file into fixtures, leaving only test classes in test file Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 441169a commit 6fb01dc

4 files changed

Lines changed: 189 additions & 74 deletions

File tree

policyengine_us/data/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
from .dataset_schema import USSingleYearDataset, USMultiYearDataset
2-
from .economic_assumptions import extend_single_year_dataset
2+
from .economic_assumptions import extend_single_year_dataset, get_parameter_last_year

policyengine_us/data/economic_assumptions.py

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,44 @@
1+
import yaml
2+
13
from policyengine_us.data.dataset_schema import (
24
USSingleYearDataset,
35
USMultiYearDataset,
46
)
57

6-
DEFAULT_END_YEAR = 2035
8+
# The default end year for dataset extension is derived at runtime from
9+
# the CPI-U parameter YAML (gov.bls.cpi.cpi_u). When the CPI-U YAML
10+
# is updated with new projection years, datasets will automatically
11+
# extend to match — no hardcoded year constant to maintain.
12+
CPI_U_PARAM_PATH = "gov.bls.cpi.cpi_u"
13+
14+
15+
def get_parameter_last_year(parameter) -> int:
16+
"""Return the latest year explicitly defined in a parameter's YAML file.
17+
18+
Reads the YAML source file (via ``parameter.file_path``), parses
19+
the ``values`` mapping, sorts its date keys chronologically, and
20+
returns the year component of the latest entry.
21+
22+
This deliberately reads the YAML on disk rather than the runtime
23+
``values_list``, because ``uprating_extensions.py`` programmatically
24+
extends many parameters to 2100 after loading — and we want the
25+
last *authored* year, not the extrapolated one.
26+
"""
27+
with open(parameter.file_path) as f:
28+
data = yaml.safe_load(f)
29+
date_keys = sorted(str(k) for k in data.get("values", {}).keys())
30+
return int(date_keys[-1][:4])
31+
32+
33+
def _get_default_end_year(system) -> int:
34+
"""Derive the default end year from the CPI-U parameter's YAML."""
35+
cpi_u = _resolve_parameter(system.parameters, CPI_U_PARAM_PATH)
36+
return get_parameter_last_year(cpi_u)
737

838

939
def extend_single_year_dataset(
1040
dataset: USSingleYearDataset,
11-
end_year: int = DEFAULT_END_YEAR,
41+
end_year: int | None = None,
1242
system=None,
1343
) -> USMultiYearDataset:
1444
"""Extend a single-year US dataset to multiple years via uprating.
@@ -17,8 +47,19 @@ def extend_single_year_dataset(
1747
``end_year``, then applies multiplicative uprating using growth factors
1848
derived from the policyengine-us parameter tree.
1949
50+
If ``end_year`` is not provided, it defaults to the latest year
51+
covered by the CPI-U parameter (gov.bls.cpi.cpi_u).
52+
2053
Variables without an uprating parameter are carried forward unchanged.
2154
"""
55+
if system is None:
56+
from policyengine_us.system import system as _system
57+
58+
system = _system
59+
60+
if end_year is None:
61+
end_year = _get_default_end_year(system)
62+
2263
start_year = int(dataset.time_period)
2364
if end_year < start_year:
2465
raise ValueError(

policyengine_us/tests/microsimulation/data/fixtures/economic_assumptions_fixtures.py renamed to policyengine_us/tests/microsimulation/data/fixtures/test_extend_single_year_dataset.py

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
"""
2-
Fixtures for extend_single_year_dataset and uprating tests.
2+
Fixtures for test_extend_single_year_dataset.py.
33
4-
Provides mock system objects, parameter trees, and sample datasets
5-
so tests can run without loading the full policyengine-us tax-benefit
6-
system.
4+
Provides constants, mock system objects, parameter trees, sample datasets,
5+
and helper functions so tests can run without loading the full policyengine-us
6+
tax-benefit system.
77
"""
88

99
import numpy as np
1010
import pandas as pd
11+
import pytest
1112

1213
from policyengine_us.data.dataset_schema import USSingleYearDataset
1314

@@ -17,6 +18,9 @@
1718

1819
BASE_YEAR = 2024
1920
END_YEAR_DEFAULT = 2035
21+
# END_YEAR_SHORT caps how far tests extend datasets. The mock parameter
22+
# fixtures only define values through 2026-2027, so this is the furthest
23+
# the mock system can uprate to without hitting a missing key.
2024
END_YEAR_SHORT = 2026
2125

2226
NUM_PERSONS = 10
@@ -221,3 +225,63 @@ def build_single_year_dataset(
221225
marital_unit=pd.DataFrame({"marital_unit_id": [1, 2, 3, 4]}),
222226
time_period=time_period,
223227
)
228+
229+
230+
# ---------------------------------------------------------------------------
231+
# Helper to call extend_single_year_dataset with a mock system
232+
# ---------------------------------------------------------------------------
233+
234+
235+
def call_extend_with_mock_system(mock_system, dataset, **kwargs):
236+
"""Call extend_single_year_dataset passing mock system directly."""
237+
from policyengine_us.data.economic_assumptions import (
238+
extend_single_year_dataset,
239+
)
240+
241+
return extend_single_year_dataset(dataset, system=mock_system, **kwargs)
242+
243+
244+
# ---------------------------------------------------------------------------
245+
# Mock Microsimulation.__init__ helpers
246+
# ---------------------------------------------------------------------------
247+
248+
249+
class MockHolder:
250+
"""Minimal holder stub — reports no known periods."""
251+
252+
def get_known_periods(self):
253+
return []
254+
255+
256+
def make_mock_super_init(system_module, captured=None):
257+
"""Return a mock CoreMicrosimulation.__init__ that sets up just enough
258+
state for the rest of Microsimulation.__init__ to run."""
259+
260+
def mock_super_init(self, *args, **kwargs):
261+
ds = kwargs.get("dataset")
262+
if captured is not None:
263+
captured[0] = ds
264+
self.dataset = ds
265+
self.tax_benefit_system = system_module.system
266+
self.is_over_dataset = True
267+
self.input_variables = []
268+
self.get_holder = lambda name: MockHolder()
269+
self.set_input = lambda *a, **kw: None
270+
self.apply_reform = lambda r: None
271+
272+
return mock_super_init
273+
274+
275+
# ---------------------------------------------------------------------------
276+
# Pytest fixtures
277+
# ---------------------------------------------------------------------------
278+
279+
280+
@pytest.fixture
281+
def mock_system():
282+
return build_mock_system()
283+
284+
285+
@pytest.fixture
286+
def base_dataset():
287+
return build_single_year_dataset()

0 commit comments

Comments
 (0)