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/enforce-401k-elective-deferral-limits.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Cap traditional and Roth 401(k) and 403(b) contributions at the IRC Section 402(g) elective deferral limit, including age-based catch-ups.
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ values:
2010-01-01:
# Retirement contributions (IRA, 401k, 403b)
- capped_traditional_ira_contributions
- traditional_401k_contributions
- traditional_403b_contributions
- capped_traditional_401k_contributions
- capped_traditional_403b_contributions
# HSA contributions (no Archer MSA variable exists)
- health_savings_account_ald
# Deductible student loan interest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@ description: The IRA sums the following elements as qualified retirement savings
values:
2018-01-01:
- capped_traditional_ira_contributions
- traditional_401k_contributions
- traditional_403b_contributions
- capped_roth_ira_contributions
- roth_401k_contributions
- roth_403b_contributions
- capped_traditional_401k_contributions
- capped_traditional_403b_contributions
- capped_roth_401k_contributions
- capped_roth_403b_contributions
- self_employed_pension_contributions
- able_contributions_person

2026-01-01:
- capped_traditional_ira_contributions
- traditional_401k_contributions
- traditional_403b_contributions
- capped_roth_ira_contributions
- roth_401k_contributions
- roth_403b_contributions
- capped_traditional_401k_contributions
- capped_traditional_403b_contributions
- capped_roth_401k_contributions
- capped_roth_403b_contributions
- self_employed_pension_contributions

metadata:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ values:
2010-01-01:
# Assumes all are pre-tax.
# Roth 401(k)/403(b) contributions are post-tax and intentionally excluded.
- traditional_401k_contributions
- traditional_403b_contributions
- capped_traditional_401k_contributions
- capped_traditional_403b_contributions
# Only explicit pre-tax payroll health premiums reduce taxable wages.
- pre_tax_health_insurance_premiums
# HSA contributions can be either through pre-tax.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ brackets:
2023-01-01: 7_500
2024-01-01: 7_500
2025-01-01: 7_500
2026-01-01: 8_000
metadata:
uprating:
parameter: gov.irs.uprating
Expand All @@ -35,6 +36,7 @@ brackets:
# SECURE 2.0 enhanced catch-up begins in 2025
# The greater of $10,000 or 150% of the standard catch-up
2025-01-01: 11_250
2026-01-01: 11_250
metadata:
uprating:
parameter: gov.irs.uprating
Expand All @@ -51,6 +53,7 @@ brackets:
2023-01-01: 7_500
2024-01-01: 7_500
2025-01-01: 7_500
2026-01-01: 8_000
metadata:
uprating:
parameter: gov.irs.uprating
Expand All @@ -70,5 +73,7 @@ metadata:
href: https://www.law.cornell.edu/uscode/text/26/414#v_2_E
- title: IRS announcement - 401(k) limit increases for 2025
href: https://www.irs.gov/newsroom/401k-limit-increases-to-23500-for-2025-ira-limit-remains-7000
- title: IRS announcement - 401(k) limit increases for 2026
href: https://www.irs.gov/newsroom/401k-limit-increases-to-24500-for-2026-ira-limit-increases-to-7500
- title: Cost-of-Living Adjustments for Retirement Items
href: https://www.irs.gov/pub/irs-tege/cola-table.pdf#page=1
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ values:
- tax_exempt_interest_income
- tax_exempt_pension_income
- tax_exempt_retirement_distributions
- traditional_401k_contributions
- traditional_403b_contributions
- capped_traditional_401k_contributions
- capped_traditional_403b_contributions
- capped_traditional_ira_contributions
- self_employed_pension_contributions
- veterans_benefits
Expand Down
2 changes: 1 addition & 1 deletion policyengine_us/tests/core/test_payroll_contributions.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def make_simulation(
PERIOD: pre_tax_health_insurance_premiums
},
"tip_income": {PERIOD: tip_income},
"traditional_401k_contributions": {
"traditional_401k_contributions_reported": {
PERIOD: traditional_401k_contributions
},
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,24 @@
output:
# No enhanced catch-up before 2025
k401_catch_up_limit: 7_500

- name: Case 15, age 50 in 2026 receives 2026 standard limit.
period: 2026
input:
age: 50
output:
k401_catch_up_limit: 8_000

- name: Case 16, age 62 in 2026 receives 2026 enhanced limit.
period: 2026
input:
age: 62
output:
k401_catch_up_limit: 11_250

- name: Case 17, age 64 in 2026 reverts to 2026 standard limit.
period: 2026
input:
age: 64
output:
k401_catch_up_limit: 8_000
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
- name: Case 1, reported deferrals below the elective deferral limit.
period: 2025
input:
age: 35
traditional_401k_contributions: 12_000
roth_401k_contributions: 8_000
output:
elective_deferral_limit: 23_500
traditional_401k_contributions: 12_000
roth_401k_contributions: 8_000
capped_traditional_401k_contributions: 12_000
capped_roth_401k_contributions: 8_000

- name: Case 2, legacy deferral inputs above the base elective deferral limit.
period: 2025
absolute_error_margin: 0.01
input:
age: 35
traditional_401k_contributions: 20_000
roth_401k_contributions: 10_000
output:
elective_deferral_limit: 23_500
traditional_401k_contributions: 20_000
roth_401k_contributions: 10_000
capped_traditional_401k_contributions: 15_666.67
capped_roth_401k_contributions: 7_833.33
pre_tax_contributions: 15_666.67

- name: Case 3, reported inputs use the enhanced catch-up limit across 401(k) and 403(b) deferrals.
period: 2025
absolute_error_margin: 0.01
input:
age: 62
traditional_401k_contributions_reported: 20_000
roth_401k_contributions_reported: 10_000
traditional_403b_contributions_reported: 5_000
roth_403b_contributions_reported: 5_000
output:
elective_deferral_limit: 34_750
capped_traditional_401k_contributions: 17_375
capped_roth_401k_contributions: 8_687.50
capped_traditional_403b_contributions: 4_343.75
capped_roth_403b_contributions: 4_343.75

- name: Case 4, SECURE 2.0 enhanced catch-up applies from ages 60 through 63.
period: 2025
input:
people:
person1:
age: 49
person2:
age: 50
person3:
age: 59
person4:
age: 60
person5:
age: 63
person6:
age: 64
output:
elective_deferral_limit: [23_500, 31_000, 31_000, 34_750, 34_750, 31_000]

- name: Case 5, capped traditional deferrals reduce income tax wages.
period: 2025
input:
age: 40
employment_income: 100_000
traditional_401k_contributions: 50_000
output:
traditional_401k_contributions: 50_000
capped_traditional_401k_contributions: 23_500
pre_tax_contributions: 23_500
irs_employment_income: 76_500

- name: Case 6, capped traditional deferrals do not reduce FICA wages.
period: 2025
input:
age: 40
employment_income: 100_000
traditional_401k_contributions: 50_000
output:
capped_traditional_401k_contributions: 23_500
payroll_tax_gross_wages: 100_000

- name: Case 7, capped Roth deferrals count for the saver's credit.
period: 2025
input:
age: 40
traditional_401k_contributions: 30_000
roth_401k_contributions: 17_000
retirement_distributions: 0
output:
capped_traditional_401k_contributions: 15_000
capped_roth_401k_contributions: 8_500
savers_credit_qualified_contributions: 23_500

- name: Case 8, the 2026 standard catch-up applies from ages 50 through 59.
period: 2026
input:
age: 55
traditional_401k_contributions: 32_500
output:
elective_deferral_limit: 32_500
capped_traditional_401k_contributions: 32_500

- name: Case 9, the SECURE 2.0 enhanced catch-up applies in 2026.
period: 2026
input:
age: 62
traditional_401k_contributions: 35_750
output:
elective_deferral_limit: 35_750
capped_traditional_401k_contributions: 35_750

- name: Case 10, age 64 returns to the standard catch-up in 2026.
period: 2026
input:
age: 64
traditional_401k_contributions: 35_750
output:
elective_deferral_limit: 32_500
capped_traditional_401k_contributions: 32_500
6 changes: 3 additions & 3 deletions policyengine_us/tests/policy/contrib/crfb/agi_surtax.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@
traditional_401k_contributions: 30_000
filing_status: SINGLE
output:
# AGI 80k + 401k 30k = 110k expanded base
# (110k - 100k) * 1% = 100
agi_surtax: 100
# AGI 80k + capped 401k 24.5k = 104.5k expanded base
# (104.5k - 100k) * 1% = 45
agi_surtax: 45

- name: Expanded base with tax-exempt interest
period: 2026
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from policyengine_us.model_api import *


class capped_roth_401k_contributions(Variable):
value_type = float
entity = Person
label = "Capped Roth 401(k) contributions"
unit = USD
documentation = (
"Roth 401(k) contributions after applying the combined 401(k) and "
"403(b) elective deferral limit."
)
definition_period = YEAR
reference = "https://www.law.cornell.edu/uscode/text/26/402#g"

def formula(person, period, parameters):
raw = person("uncapped_roth_401k_contributions", period)
total_reported = add(
person,
period,
[
"uncapped_traditional_401k_contributions",
"uncapped_roth_401k_contributions",
"uncapped_traditional_403b_contributions",
"uncapped_roth_403b_contributions",
],
)
scale = min_(
person("elective_deferral_limit", period) / max_(total_reported, 1), 1
)
return raw * scale
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from policyengine_us.model_api import *


class capped_roth_403b_contributions(Variable):
value_type = float
entity = Person
label = "Capped Roth 403(b) contributions"
unit = USD
documentation = (
"Roth 403(b) contributions after applying the combined 401(k) and "
"403(b) elective deferral limit."
)
definition_period = YEAR
reference = "https://www.law.cornell.edu/uscode/text/26/402#g"

def formula(person, period, parameters):
raw = person("uncapped_roth_403b_contributions", period)
total_reported = add(
person,
period,
[
"uncapped_traditional_401k_contributions",
"uncapped_roth_401k_contributions",
"uncapped_traditional_403b_contributions",
"uncapped_roth_403b_contributions",
],
)
scale = min_(
person("elective_deferral_limit", period) / max_(total_reported, 1), 1
)
return raw * scale
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from policyengine_us.model_api import *


class capped_traditional_401k_contributions(Variable):
value_type = float
entity = Person
label = "Capped traditional 401(k) contributions"
unit = USD
documentation = (
"Traditional 401(k) contributions after applying the combined "
"401(k) and 403(b) elective deferral limit."
)
definition_period = YEAR
reference = "https://www.law.cornell.edu/uscode/text/26/402#g"

def formula(person, period, parameters):
raw = person("uncapped_traditional_401k_contributions", period)
total_reported = add(
person,
period,
[
"uncapped_traditional_401k_contributions",
"uncapped_roth_401k_contributions",
"uncapped_traditional_403b_contributions",
"uncapped_roth_403b_contributions",
],
)
scale = min_(
person("elective_deferral_limit", period) / max_(total_reported, 1), 1
)
return raw * scale
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from policyengine_us.model_api import *


class capped_traditional_403b_contributions(Variable):
value_type = float
entity = Person
label = "Capped traditional 403(b) contributions"
unit = USD
documentation = (
"Traditional 403(b) contributions after applying the combined "
"401(k) and 403(b) elective deferral limit."
)
definition_period = YEAR
reference = "https://www.law.cornell.edu/uscode/text/26/402#g"

def formula(person, period, parameters):
raw = person("uncapped_traditional_403b_contributions", period)
total_reported = add(
person,
period,
[
"uncapped_traditional_401k_contributions",
"uncapped_roth_401k_contributions",
"uncapped_traditional_403b_contributions",
"uncapped_roth_403b_contributions",
],
)
scale = min_(
person("elective_deferral_limit", period) / max_(total_reported, 1), 1
)
return raw * scale
Loading
Loading