From e1ea8799067abf797c046ba15f2a1db5ea393fbf Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Thu, 28 May 2026 00:40:38 -0400 Subject: [PATCH] Lower pension/SS split threshold from 60 to 55 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #933. Colorado allows a $20K pension subtraction per filer aged 55-64. PR #925 split pension/SS only when both spouses were ≥60, leaving 55-59 mixed-age couples allocating the full pension to one spouse and losing the per-person CO subtraction on the other. Drop the threshold to 55 (the lowest practical state age gate). Higher-threshold states (DE 60, GA 62, MD 65) are unaffected: a 55-59 split doesn't create a false exclusion because those filers fail the higher state age gates anyway. 3K eCPS 2025 sample: identical aggregate match rates (no regression). Per-record: #933 CO joint, page=58/sage=56, $65K pensions now matches TaxAct exactly (siitax $1,224.24 vs prior $2,104.24; $40K subtraction vs prior $20K). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../lower-pension-split-threshold.fixed.md | 1 + .../runners/policyengine_runner.py | 17 ++++++++++------- tests/test_spouse_income_splitting.py | 12 ++++++++++++ 3 files changed, 23 insertions(+), 7 deletions(-) create mode 100644 changelog.d/lower-pension-split-threshold.fixed.md diff --git a/changelog.d/lower-pension-split-threshold.fixed.md b/changelog.d/lower-pension-split-threshold.fixed.md new file mode 100644 index 0000000..20b75ae --- /dev/null +++ b/changelog.d/lower-pension-split-threshold.fixed.md @@ -0,0 +1 @@ +Lower the pension/SS age-aware split threshold from 60 to 55 to match Colorado's 55+ pension subtraction (so mixed-age couples ages 55-59 each claim the state per-person exclusion). Higher-threshold states (DE 60, GA 62, MD 65) are unaffected. diff --git a/policyengine_taxsim/runners/policyengine_runner.py b/policyengine_taxsim/runners/policyengine_runner.py index 9c9bbc8..c0c8d7a 100644 --- a/policyengine_taxsim/runners/policyengine_runner.py +++ b/policyengine_taxsim/runners/policyengine_runner.py @@ -195,16 +195,19 @@ def _initialize_dataset_structure(self) -> dict: } ) - # Pension and Social Security income are split only when both spouses - # meet the state-level age threshold (60, the lowest common threshold - # across states such as DE). In mixed-age households (e.g. spouse under - # 60 while filer is elderly), the income stays with the primary filer so - # age-based state exclusions aren't lost on the younger spouse's - # incorrectly-allocated share. + # Pension and Social Security income are split between spouses only + # when both meet the state-level age threshold (55, the lowest common + # threshold across states such as CO whose pension subtraction + # qualifies filers 55+). In mixed-age households the income stays + # with the older spouse so age-based state exclusions aren't lost on + # the younger spouse's incorrectly-allocated share. Higher-threshold + # states (DE 60, GA 62, MD 65) are unaffected: 55-59 splits don't + # create false exclusions because the filers fail those states' age + # gates anyway. _AGE_GATED_FIELDS = frozenset( {"taxable_private_pension_income", "social_security_retirement"} ) - _AGE_GATED_SPLIT_AGE = 60 + _AGE_GATED_SPLIT_AGE = 55 @staticmethod def _make_primary_split(source_field): diff --git a/tests/test_spouse_income_splitting.py b/tests/test_spouse_income_splitting.py index 4789641..ba50488 100644 --- a/tests/test_spouse_income_splitting.py +++ b/tests/test_spouse_income_splitting.py @@ -107,6 +107,18 @@ def test_pension_splits_when_both_spouses_are_60_plus(): np.testing.assert_allclose(values, [20000.0, 20000.0]) +def test_pension_splits_when_both_spouses_are_55_plus(): + """Pension: when both spouses ≥ 55 (CO's threshold), allocate 50/50 + so each spouse claims the per-person CO pension subtraction. See + taxsim issue #933: CO joint, page=58, sage=56 — both qualify for + CO's 55-64 $20K subtraction. Pre-fix PE put pension on primary + only, giving $20K total subtraction; correct behavior splits 50/50 + so each spouse claims $20K = $40K total (matching TaxAct).""" + df = pd.DataFrame([_base_mfj_record(page=58, sage=56, pensions=40000)]) + values = _run_allocation(df, "taxable_private_pension_income") + np.testing.assert_allclose(values, [20000.0, 20000.0]) + + def test_pension_goes_to_primary_when_primary_is_older(): """Pension: mixed-age, primary older — keep full pension on primary so they claim the per-person elderly exclusion. Splitting 50/50 would