Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/use-pe-us-retirement-limits.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Read IRS retirement contribution limits from policyengine-us parameters instead of hard-coding them.
48 changes: 4 additions & 44 deletions policyengine_us_data/datasets/cps/cps.py
Original file line number Diff line number Diff line change
Expand Up @@ -649,51 +649,11 @@ def add_personal_income_variables(
# Disregard reported pension contributions from people who report neither wage and salary
# nor self-employment income.
# Assume no 403(b) or 457 contributions for now.
# IRS retirement contribution limits by year.
RETIREMENT_LIMITS = {
2020: {
"401k": 19_500,
"401k_catch_up": 6_500,
"ira": 6_000,
"ira_catch_up": 1_000,
},
2021: {
"401k": 19_500,
"401k_catch_up": 6_500,
"ira": 6_000,
"ira_catch_up": 1_000,
},
2022: {
"401k": 20_500,
"401k_catch_up": 6_500,
"ira": 6_000,
"ira_catch_up": 1_000,
},
2023: {
"401k": 22_500,
"401k_catch_up": 7_500,
"ira": 6_500,
"ira_catch_up": 1_000,
},
2024: {
"401k": 23_000,
"401k_catch_up": 7_500,
"ira": 7_000,
"ira_catch_up": 1_000,
},
2025: {
"401k": 23_500,
"401k_catch_up": 7_500,
"ira": 7_000,
"ira_catch_up": 1_000,
},
}
# Clamp to the nearest available year for out-of-range values.
clamped_year = max(
min(year, max(RETIREMENT_LIMITS)),
min(RETIREMENT_LIMITS),
from policyengine_us_data.utils.retirement_limits import (
get_retirement_limits,
)
limits = RETIREMENT_LIMITS[clamped_year]

limits = get_retirement_limits(year)
LIMIT_401K = limits["401k"]
LIMIT_401K_CATCH_UP = limits["401k_catch_up"]
LIMIT_IRA = limits["ira"]
Expand Down
35 changes: 35 additions & 0 deletions policyengine_us_data/tests/test_retirement_limits.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Tests for retirement contribution limits utility."""

import pytest
from policyengine_us_data.utils.retirement_limits import (
get_retirement_limits,
)

# Expected values sourced from IRS announcements and policyengine-us
# parameter tree.
EXPECTED = {
2020: {
"401k": 19_500,
"401k_catch_up": 6_500,
"ira": 6_000,
"ira_catch_up": 1_000,
},
2023: {
"401k": 22_500,
"401k_catch_up": 7_500,
"ira": 6_500,
"ira_catch_up": 1_000,
},
2025: {
"401k": 23_500,
"401k_catch_up": 7_500,
"ira": 7_000,
"ira_catch_up": 1_000,
},
}


@pytest.mark.parametrize("year", EXPECTED.keys())
def test_retirement_limits(year):
limits = get_retirement_limits(year)
assert limits == EXPECTED[year]
36 changes: 36 additions & 0 deletions policyengine_us_data/utils/retirement_limits.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""Retirement contribution limits from policyengine-us parameters.

Reads IRS contribution limits from the policyengine-us parameter tree
instead of hard-coding them.
"""

from functools import lru_cache


@lru_cache(maxsize=16)
def get_retirement_limits(year: int) -> dict:
"""Return contribution limits for the given tax year.

Reads from policyengine-us parameters at:
gov.irs.gross_income.retirement_contributions.limit.{401k, ira}
gov.irs.gross_income.retirement_contributions.catch_up.limit.{k401, ira}

The k401 catch-up parameter is a SingleAmountTaxScale with age
brackets (SECURE 2.0); we use the age-50 bracket for the standard
catch-up amount.

Returns:
Dict with keys: 401k, 401k_catch_up, ira, ira_catch_up.
"""
from policyengine_us import CountryTaxBenefitSystem

tbs = CountryTaxBenefitSystem()
p = tbs.parameters.gov.irs.gross_income.retirement_contributions
d = f"{year}-01-01"

return {
"401k": int(p.limit.children["401k"](d)),
"401k_catch_up": int(p.catch_up.limit.children["k401"](d).calc(50)),
"ira": int(p.limit.ira(d)),
"ira_catch_up": int(p.catch_up.limit.ira(d)),
}
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ classifiers = [
"Programming Language :: Python :: 3.13",
]
dependencies = [
"policyengine-us>=1.516.0",
"policyengine-us>=1.572.5",
"policyengine-core>=3.23.6",
"pandas>=2.3.1",
"requests>=2.25.0",
Expand Down
10 changes: 6 additions & 4 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.