From 5dfcd134304f9e488f81dbc6033fad80255c2941 Mon Sep 17 00:00:00 2001 From: David Trimmer Date: Mon, 26 Oct 2026 15:19:21 -0400 Subject: [PATCH] Require spm-calculator 1.1 for the 2022-2036 SPM forecast spm-calculator 1.1.0 extends the canonical forecast to 2036 without changing any 2022-2035 value (content 335e6755...). The pin moves to >=1.1.0,<1.2 and the lock to 1.1.0, whose wheel and sdist hashes match PyPI. The threshold test expects 2022-2036 and checks that 2037, the first year after the horizon, has no entry. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/release-lock.md | 6 +++--- README.md | 2 +- changelog.d/spm-calculator-1.1.changed.md | 1 + docs/spm.md | 2 +- .../income/spm_unit/test_spm_unit_spm_threshold.py | 7 ++++--- pyproject.toml | 2 +- uv.lock | 8 ++++---- 7 files changed, 15 insertions(+), 13 deletions(-) create mode 100644 changelog.d/spm-calculator-1.1.changed.md diff --git a/.github/release-lock.md b/.github/release-lock.md index a378ede38f4..4382148c7e9 100644 --- a/.github/release-lock.md +++ b/.github/release-lock.md @@ -101,11 +101,11 @@ The opt-in probe creates an isolated, minimal project with a standard PyPI dependency and exercises actual uv locking. CI enables it. These commands do not install or import the country model. -For the SPM integration, `spm-calculator` 1.0.0 reached PyPI on 2026-09-11, and +For the SPM integration, `spm-calculator` 1.1.0 reached PyPI on 2026-11-02, and the committed country lock resolves it from `https://pypi.org/simple` with sdist and wheel SHA-256 hashes, so `--committed` passes on the committed files. -`pyproject.toml` requires `>=1.0.0,<=1.0.0.post1` and the lock pins 1.0.0; -moving that pin to the post-release is an ordinary reviewed lock change. This +`pyproject.toml` requires `>=1.1.0,<1.2` and the lock pins 1.1.0; moving to a +later 1.1.x release is an ordinary reviewed lock change. This guard validates dependency sources, artifact URLs and hashes, never a version string, so the calculator requirement is held by `pyproject.toml` and review rather than by `release_lock.py`. A stale root version or an older calculator diff --git a/README.md b/README.md index e23aa43a1da..c01b82139f7 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ PolicyEngine US is a microsimulation model of the US state and federal tax and b PolicyEngine US supports Python 3.11 through 3.14 and requires pandas 3 or later. To install, run `pip install policyengine-us`. -This release requires `spm-calculator>=1.0.0,<=1.0.0.post1` (the canonical SPM threshold API). +This release requires `spm-calculator>=1.1.0,<1.2` (the canonical SPM threshold API, covering 2022–2036). Previously published country versions retain their original dependency metadata; when reproducing one of those versions, include the compatible calculator explicitly, for example: diff --git a/changelog.d/spm-calculator-1.1.changed.md b/changelog.d/spm-calculator-1.1.changed.md new file mode 100644 index 00000000000..685e07e1de3 --- /dev/null +++ b/changelog.d/spm-calculator-1.1.changed.md @@ -0,0 +1 @@ +Require spm-calculator 1.1, whose canonical forecast covers 2022–2036, so SPM thresholds and SPM resources calculate for 2036. diff --git a/docs/spm.md b/docs/spm.md index bc5fbe9b5a9..ea182d20972 100644 --- a/docs/spm.md +++ b/docs/spm.md @@ -3,7 +3,7 @@ The country model uses the installed `spm-calculator` artifact for all five SPM measurement amounts. It does not extrapolate thresholds with country CPI or income parameters. Each supported year and scenario comes from the verified artifact. -The installed 1.0.0 artifact covers 2022 through 2035; unavailable years fail. +The installed 1.1.0 artifact covers 2022 through 2036; unavailable years fail. Taxes, benefits, resources and the housing-assistance cap remain country-model formulas. General household benefits and CBO transfer aggregates count actual `housing_assistance`; only SPM resources count diff --git a/policyengine_us/tests/policy/baseline/household/income/spm_unit/test_spm_unit_spm_threshold.py b/policyengine_us/tests/policy/baseline/household/income/spm_unit/test_spm_unit_spm_threshold.py index 4849d9f7086..a61f7e95669 100644 --- a/policyengine_us/tests/policy/baseline/household/income/spm_unit/test_spm_unit_spm_threshold.py +++ b/policyengine_us/tests/policy/baseline/household/income/spm_unit/test_spm_unit_spm_threshold.py @@ -74,7 +74,7 @@ def canonical(forecast, year, tenure, *, adults=2, children=2, county=None): ) def test_every_amount_uses_canonical_final_value_for_every_supported_year(tenure): forecast = load_forecast() - assert list(forecast.years) == list(range(2022, 2036)) + assert list(forecast.years) == list(range(2022, 2037)) # Explicit period inputs cover the artifact, including its historical years. situation = household(county="06037", tenure=tenure) for person in situation["people"].values(): @@ -164,10 +164,11 @@ def test_unavailable_selection_or_consumer_extrapolation_rejected(config): create_spm_provider(config) -def test_unknown_year_has_no_country_cpi_extrapolation(): +@pytest.mark.parametrize("year", [2037, 2099]) +def test_unknown_year_has_no_country_cpi_extrapolation(year): simulation = Simulation(situation=household(county="06037")) with pytest.raises(ValueError, match="no entry"): - simulation.calculate("spm_unit_spm_threshold", 2099) + simulation.calculate("spm_unit_spm_threshold", year) @pytest.mark.parametrize( diff --git a/pyproject.toml b/pyproject.toml index 9174dee48fe..e79d10854c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ dependencies = [ # extension and is available on every supported Python version. "pandas>=3.0", "policyengine-core>=3.33.1", - "spm-calculator>=1.0.0,<=1.0.0.post1", + "spm-calculator>=1.1.0,<1.2", "tables>=3.9", "tqdm>=4.67.1", ] diff --git a/uv.lock b/uv.lock index 8fe7a0c709b..8286a917f93 100644 --- a/uv.lock +++ b/uv.lock @@ -1848,7 +1848,7 @@ requires-dist = [ { name = "policyengine-core", specifier = ">=3.33.1" }, { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.9.0" }, { name = "setuptools", marker = "extra == 'dev'", specifier = ">=80.9.0" }, - { name = "spm-calculator", specifier = ">=1.0.0,<=1.0.0.post1" }, + { name = "spm-calculator", specifier = ">=1.1.0,<1.2" }, { name = "tables", specifier = ">=3.9" }, { name = "towncrier", marker = "extra == 'dev'", specifier = ">=24.8.0" }, { name = "tqdm", specifier = ">=4.67.1" }, @@ -2528,7 +2528,7 @@ wheels = [ [[package]] name = "spm-calculator" -version = "1.0.0" +version = "1.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "census" }, @@ -2538,9 +2538,9 @@ dependencies = [ { name = "requests" }, { name = "us" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c2/5e/3f28b212c401795990250bb3daec741f699a8c7dc2a23a3db7005df6feb6/spm_calculator-1.0.0.tar.gz", hash = "sha256:a99aac8c2c0bf81a9455105bbf872366f1ea9066a99cd793714881cd3db846dc", size = 7150825, upload-time = "2026-09-11T15:45:12.732Z" } +sdist = { url = "https://files.pythonhosted.org/packages/33/84/19057fa04e6c8cce85494458b2164d473ce82cbf54608a3b2951af2c36b1/spm_calculator-1.1.0.tar.gz", hash = "sha256:1be418c6a1baaba86f63c3287b38efecf7f94af563b864a73934ecbf60a01f38", size = 9625137, upload-time = "2026-11-02T18:48:45.002Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/19/a0/c484f69a0ebf88a9b9fd0ac28176f8df66550f46714f1b0c5ec0b360824e/spm_calculator-1.0.0-py3-none-any.whl", hash = "sha256:e354937a5e1a4045d4966ed594a528d8b02866fabaac9bb5672017004b627305", size = 7395384, upload-time = "2026-09-11T15:45:10.492Z" }, + { url = "https://files.pythonhosted.org/packages/35/91/12a744eb7f3445db8899dc187bbd193c884f9fe4c373851c0ddb24cc0f5e/spm_calculator-1.1.0-py3-none-any.whl", hash = "sha256:c528ec0f06eeb1188b7d6cf456ff3a9b73a0fd8856b3938358251cc5f9be83e8", size = 9881340, upload-time = "2026-11-02T18:48:42.665Z" }, ] [[package]]