From 71d4ee2a2c0a7fe9ec512a87083992b2566510f7 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 16 Apr 2026 22:52:27 -0400 Subject: [PATCH] Bump spm-calculator to >=0.2.1 for Betson equivalence scale fix `policyengine_us_data/utils/spm.py` already consumes `spm-calculator` for base thresholds and the equivalence scale, but the dependency range was pinned at `>=0.1.0` so the lockfile resolved to `spm-calculator==0.1.0` -- which shipped a buggy linear OECD-modified equivalence scale (reference family raw = 2.1) instead of the Betson three-parameter scale with the 0.7 exponent (reference family raw = 3**0.7 approx 2.158) that BLS Garner (2021) documents and that Census uses in its published SPM thresholds. Version 0.2.0 of `spm-calculator` replaced the scale with the correct Betson form; 0.2.1 then tightened the (shadow) CE recomputation path toward BLS methodology (annualization, survey weights, mortgage principal, internet services, modern CUTENURE split). Bumping to `>=0.2.1` picks up both. Impact on computed SPM thresholds (CPS input pipeline, unchanged code, dependency bump only) - 2 adult / 2 child reference family: unchanged (normalization anchor). - 1 adult / 2 child owner-with-mortgage: $29,767 -> $32,437 (+9.0%). - 2 adult / 0 child owner-with-mortgage: $27,906 -> $25,530 (-8.5%). - Direction: single-parent-with-children units shift higher, larger childless-adult units shift lower -- matching the well-known Betson economies-of-scale properties. This changes the SPM thresholds that `cps.py` writes onto the CPS input dataset, which feeds every downstream SPM-based calibration target and poverty classification. The update is a correctness fix, not a policy change. Validation - All 714 unit tests pass (`uv run pytest tests/unit/`). - `calculate_spm_thresholds_with_geoadj` output verified by hand against Betson math for 1A2C / 2A2C / 2A0C at owner-with-mortgage. - Public API of `policyengine_us_data/utils/spm.py` unchanged across the version bump; no call-site updates required in `cps.py` or `calibration_utils.py`. Co-Authored-By: Claude Opus 4.6 --- changelog.d/migrate-utils-spm-to-spm-calculator.fixed.md | 1 + pyproject.toml | 2 +- uv.lock | 9 +++++---- 3 files changed, 7 insertions(+), 5 deletions(-) create mode 100644 changelog.d/migrate-utils-spm-to-spm-calculator.fixed.md diff --git a/changelog.d/migrate-utils-spm-to-spm-calculator.fixed.md b/changelog.d/migrate-utils-spm-to-spm-calculator.fixed.md new file mode 100644 index 000000000..6d0d81fbc --- /dev/null +++ b/changelog.d/migrate-utils-spm-to-spm-calculator.fixed.md @@ -0,0 +1 @@ +Bump `spm-calculator` dependency from `>=0.1.0` to `>=0.2.1` so the SPM thresholds computed for the CPS input dataset use the Betson three-parameter equivalence scale (with the 0.7 exponent) published by Census, not the linear OECD-modified scale shipped in `spm-calculator==0.1.0`. Also picks up tenure-specific GEOADJ shares (renter 0.443, owner-with-mortgage 0.434, owner-without-mortgage 0.323) and the BLS-fidelity improvements in the CE recomputation path. For a 2-adult 2-child reference family the threshold is unchanged; non-reference families shift materially (1 adult / 2 children owner-with-mortgage: $29,767 -> $32,437, +9.0%; 2 adults / 0 children owner-with-mortgage: $27,906 -> $25,530, -8.5%). No code changes in `policyengine_us_data/utils/spm.py`; the package-level API is unchanged across the version bump. diff --git a/pyproject.toml b/pyproject.toml index b313cce8c..fd5014a56 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ dependencies = [ "sqlalchemy>=2.0.41", "sqlmodel>=0.0.24", "xlrd>=2.0.2", - "spm-calculator>=0.1.0", + "spm-calculator>=0.2.1", "tenacity>=8.0.0", ] diff --git a/uv.lock b/uv.lock index 938b4ee4f..9f3020988 100644 --- a/uv.lock +++ b/uv.lock @@ -2238,7 +2238,7 @@ requires-dist = [ { name = "samplics", marker = "extra == 'calibration'" }, { name = "scipy", specifier = ">=1.15.3" }, { name = "setuptools", specifier = ">=60" }, - { name = "spm-calculator", specifier = ">=0.1.0" }, + { name = "spm-calculator", specifier = ">=0.2.1" }, { name = "sqlalchemy", specifier = ">=2.0.41" }, { name = "sqlmodel", specifier = ">=0.0.24" }, { name = "statsmodels", specifier = ">=0.14.5" }, @@ -3203,18 +3203,19 @@ wheels = [ [[package]] name = "spm-calculator" -version = "0.1.0" +version = "0.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "census" }, { name = "numpy" }, + { name = "openpyxl" }, { name = "pandas" }, { name = "requests" }, { name = "us" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/38/d0/f96e42ab45515e491b654e46d59318002a18e5268d3264b26566a71f8c43/spm_calculator-0.1.0.tar.gz", hash = "sha256:539ee27cccec20fd80e25627b8f1d1f7a65663187f79bdd336a873110cfae240", size = 24992, upload-time = "2025-12-19T03:20:43.76Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/70/75799ea650df0cf38b227fc4a8b7cba5a5d090adaeed21c234898dffb871/spm_calculator-0.2.1.tar.gz", hash = "sha256:b9656b333085e8f9d4cfd3c5a3bb488f230ee7712c84624badbfb41c02c64a5e", size = 62985, upload-time = "2026-04-17T02:24:39.554Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/55/06/596f4a1012984f5011ee8e6e522c0d83ae27eea74cecee42f75892aa6fb5/spm_calculator-0.1.0-py3-none-any.whl", hash = "sha256:ef713361aac5bf764d7892be724eb69a99ec61631a75d8904a361b73fbc5b0f7", size = 19889, upload-time = "2025-12-19T03:20:42.27Z" }, + { url = "https://files.pythonhosted.org/packages/04/76/e19d0bff7c78a2fe33f5d139590b16dd7cb2ddb28f3c5c472099eb1181eb/spm_calculator-0.2.1-py3-none-any.whl", hash = "sha256:edad3651623f6f305d23bcd2a86f5a2b3b2a2cbd2328b0a47b21e6a016923f2e", size = 53625, upload-time = "2026-04-17T02:24:38.465Z" }, ] [[package]]