Skip to content

Bump spm-calculator to >=0.2.1 for Betson equivalence scale fix#771

Merged
MaxGhenis merged 1 commit intomainfrom
migrate-utils-spm-to-spm-calculator
Apr 17, 2026
Merged

Bump spm-calculator to >=0.2.1 for Betson equivalence scale fix#771
MaxGhenis merged 1 commit intomainfrom
migrate-utils-spm-to-spm-calculator

Conversation

@MaxGhenis
Copy link
Copy Markdown
Contributor

Summary

policyengine_us_data/utils/spm.py already delegates to the spm-calculator package for base thresholds and the equivalence scale, but the dependency constraint was pinned at >=0.1.0 so uv sync resolved to spm-calculator==0.1.0 — the version that shipped a buggy linear OECD-modified equivalence scale (reference 2A2C raw = 2.1). Census SPM actually uses the Betson three-parameter scale with the 0.7 exponent (reference 2A2C raw = 3**0.7 ≈ 2.158). This mismatch silently produced the wrong SPM threshold for every non-2A2C family on the CPS input dataset.

spm-calculator v0.2.0 (merged and published earlier today) replaced the scale with the correct Betson form; v0.2.1 tightened the shadow CE-recomputation path toward BLS methodology (annualization, survey weights, mortgage principal, internet services, modern CUTENURE split). This PR bumps the floor to >=0.2.1, refreshes uv.lock, and adds a changelog fragment. No code changes in utils/spm.py — the package-level API (SPMCalculator, spm_equivalence_scale) is backward-compatible across the bump.

Impact on computed SPM thresholds

CPS input dataset, unchanged code, dependency bump only. All examples at geoadj=1.0, owner_with_mortgage, 2024.

Family v0.1.0 (old) v0.2.1 (new) Δ
2A 2C (reference) $39,068 $39,068 0
1A 2C (single parent) $29,767 $32,437 +9.0%
2A 0C (childless couple) $27,906 $25,530 −8.5%

Single-parent-with-children units shift higher, larger childless-adult units shift lower — 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. It's a correctness fix, not a policy change.

Test plan

  • uv run pytest tests/unit/ → 714 passed, 10 skipped
  • Verified calculate_spm_thresholds_with_geoadj against Betson math by hand for 1A2C / 2A2C / 2A0C at owner-with-mortgage
  • make format on Python passes (ruff clean)
  • Integration tests (automatic on this PR since policyengine_us_data/ changed)
  • Smoke test (automatic)

Related

  • Upstream fix: PolicyEngine/spm-calculator#5 (v0.2.0, Betson scale + tenure-specific GEOADJ) and #6 (v0.2.1, CE-path BLS fidelity) — both merged and published to PyPI.
  • Model-side companion: PolicyEngine/policyengine-us#8020 migrates spm_unit_spm_threshold to a formula using spm-calculator>=0.2.0 for base thresholds + Betson scale, preserving the per-unit geographic adjustment from the stored dataset.
  • The broader SPM-threshold alignment work in #702 (still draft) is orthogonal to this dep bump and can land independently.

🤖 Generated with Claude Code

`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 <noreply@anthropic.com>
@MaxGhenis MaxGhenis merged commit ae95b43 into main Apr 17, 2026
9 checks passed
@MaxGhenis MaxGhenis deleted the migrate-utils-spm-to-spm-calculator branch April 17, 2026 03:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant