From 75bbf59d54df925df4b38c332b348a17ddcaaa56 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 25 Nov 2025 09:15:16 -0500 Subject: [PATCH 1/2] Implement SECURE 2.0 enhanced 401(k) catch-up contributions for ages 60-63 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SECURE 2.0 Act introduced an enhanced "super" catch-up contribution limit for 401(k), 403(b), and 457(b) plan participants aged 60-63, effective for tax years beginning after December 31, 2024. Changes: - Added k401_catch_up_limit variable that returns the applicable catch-up limit based on age - Standard catch-up ($7,500 for 2025): Ages 50-59 and 64+ - Enhanced catch-up ($11,250 for 2025): Ages 60-63 only - Added parameters for enhanced catch-up age range and limit - Renamed 401k.yaml to k401.yaml for Python attribute access compatibility - Added comprehensive tests for all age scenarios Closes #6712 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- changelog_entry.yaml | 4 + .../catch_up/enhanced_age_max.yaml | 11 +++ .../catch_up/enhanced_age_min.yaml | 11 +++ .../catch_up/limit/{401k.yaml => k401.yaml} | 0 .../catch_up/limit/k401_enhanced.yaml | 25 +++++++ .../retirement/k401_catch_up_limit.yaml | 74 +++++++++++++++++++ .../retirement/k401_catch_up_limit.py | 40 ++++++++++ 7 files changed, 165 insertions(+) create mode 100644 policyengine_us/parameters/gov/irs/gross_income/retirement_contributions/catch_up/enhanced_age_max.yaml create mode 100644 policyengine_us/parameters/gov/irs/gross_income/retirement_contributions/catch_up/enhanced_age_min.yaml rename policyengine_us/parameters/gov/irs/gross_income/retirement_contributions/catch_up/limit/{401k.yaml => k401.yaml} (100%) create mode 100644 policyengine_us/parameters/gov/irs/gross_income/retirement_contributions/catch_up/limit/k401_enhanced.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/retirement/k401_catch_up_limit.yaml create mode 100644 policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/retirement/k401_catch_up_limit.py diff --git a/changelog_entry.yaml b/changelog_entry.yaml index e69de29bb2d..6b1598617ee 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -0,0 +1,4 @@ +- bump: minor + changes: + added: + - Implement SECURE 2.0 enhanced 401(k) catch-up contributions for ages 60-63 (starting 2025). diff --git a/policyengine_us/parameters/gov/irs/gross_income/retirement_contributions/catch_up/enhanced_age_max.yaml b/policyengine_us/parameters/gov/irs/gross_income/retirement_contributions/catch_up/enhanced_age_max.yaml new file mode 100644 index 00000000000..1f0d6444206 --- /dev/null +++ b/policyengine_us/parameters/gov/irs/gross_income/retirement_contributions/catch_up/enhanced_age_max.yaml @@ -0,0 +1,11 @@ +description: Maximum age for enhanced 401(k) catch-up contributions under SECURE 2.0 (inclusive). +values: + # Effective for taxable years beginning after December 31, 2024 + 2025-01-01: 63 +metadata: + unit: year + period: year + label: Enhanced catch-up maximum age + reference: + - title: IRC § 414(v)(2)(E)(ii) - Applicable age range + href: https://www.law.cornell.edu/uscode/text/26/414#v_2_E diff --git a/policyengine_us/parameters/gov/irs/gross_income/retirement_contributions/catch_up/enhanced_age_min.yaml b/policyengine_us/parameters/gov/irs/gross_income/retirement_contributions/catch_up/enhanced_age_min.yaml new file mode 100644 index 00000000000..905deb189a6 --- /dev/null +++ b/policyengine_us/parameters/gov/irs/gross_income/retirement_contributions/catch_up/enhanced_age_min.yaml @@ -0,0 +1,11 @@ +description: Minimum age for enhanced 401(k) catch-up contributions under SECURE 2.0. +values: + # Effective for taxable years beginning after December 31, 2024 + 2025-01-01: 60 +metadata: + unit: year + period: year + label: Enhanced catch-up minimum age + reference: + - title: IRC § 414(v)(2)(E)(ii) - Applicable age range + href: https://www.law.cornell.edu/uscode/text/26/414#v_2_E diff --git a/policyengine_us/parameters/gov/irs/gross_income/retirement_contributions/catch_up/limit/401k.yaml b/policyengine_us/parameters/gov/irs/gross_income/retirement_contributions/catch_up/limit/k401.yaml similarity index 100% rename from policyengine_us/parameters/gov/irs/gross_income/retirement_contributions/catch_up/limit/401k.yaml rename to policyengine_us/parameters/gov/irs/gross_income/retirement_contributions/catch_up/limit/k401.yaml diff --git a/policyengine_us/parameters/gov/irs/gross_income/retirement_contributions/catch_up/limit/k401_enhanced.yaml b/policyengine_us/parameters/gov/irs/gross_income/retirement_contributions/catch_up/limit/k401_enhanced.yaml new file mode 100644 index 00000000000..ee3fdfadc33 --- /dev/null +++ b/policyengine_us/parameters/gov/irs/gross_income/retirement_contributions/catch_up/limit/k401_enhanced.yaml @@ -0,0 +1,25 @@ +description: Enhanced 401(k) catch-up contribution limit for individuals aged 60-63, introduced by SECURE 2.0 Act. +values: + # Before 2025, enhanced catch-up did not exist, so we use the standard limit + 2018-01-01: 6_000 + 2020-01-01: 6_500 + 2023-01-01: 7_500 + 2024-01-01: 7_500 + # SECURE 2.0 enhanced catch-up begins in 2025 (150% of standard base) + 2025-01-01: 11_250 +metadata: + unit: currency-USD + period: year + label: 401(k) enhanced catch-up amount (ages 60-63) + uprating: + parameter: gov.irs.uprating + rounding: + type: downwards + interval: 500 + reference: + - title: IRC § 414(v)(2)(E) - Increased catch-up contribution limit for individuals aged 60-63 + 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: Federal Register - Catch-up Contributions Final Regulations + href: https://www.federalregister.gov/documents/2025/09/16/2025-17865/catch-up-contributions diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/retirement/k401_catch_up_limit.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/retirement/k401_catch_up_limit.yaml new file mode 100644 index 00000000000..2a136375f08 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/retirement/k401_catch_up_limit.yaml @@ -0,0 +1,74 @@ +# Tests for 401(k) catch-up contribution limits +# Standard catch-up: $7,500 for ages 50+ (2024-2025) +# Enhanced catch-up: $11,250 for ages 60-63 (2025+) + +- name: No catch-up limit for age under 50 + period: 2025 + input: + age: 45 + output: + k401_catch_up_limit: 0 + +- name: Standard catch-up limit for age 50 + period: 2025 + input: + age: 50 + output: + k401_catch_up_limit: 7_500 + +- name: Standard catch-up limit for age 55 + period: 2025 + input: + age: 55 + output: + k401_catch_up_limit: 7_500 + +- name: Standard catch-up limit for age 59 + period: 2025 + input: + age: 59 + output: + k401_catch_up_limit: 7_500 + +- name: Enhanced catch-up limit for age 60 (SECURE 2.0) + period: 2025 + input: + age: 60 + output: + k401_catch_up_limit: 11_250 + +- name: Enhanced catch-up limit for age 62 (SECURE 2.0) + period: 2025 + input: + age: 62 + output: + k401_catch_up_limit: 11_250 + +- name: Enhanced catch-up limit for age 63 (SECURE 2.0) + period: 2025 + input: + age: 63 + output: + k401_catch_up_limit: 11_250 + +- name: Standard catch-up limit for age 64 (past enhanced window) + period: 2025 + input: + age: 64 + output: + k401_catch_up_limit: 7_500 + +- name: Standard catch-up limit for age 70 + period: 2025 + input: + age: 70 + output: + k401_catch_up_limit: 7_500 + +# Test 2024 (before SECURE 2.0 enhanced catch-up) +- name: No enhanced catch-up in 2024 for age 60 + period: 2024 + input: + age: 60 + output: + k401_catch_up_limit: 7_500 diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/retirement/k401_catch_up_limit.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/retirement/k401_catch_up_limit.py new file mode 100644 index 00000000000..1d7a8de1627 --- /dev/null +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/retirement/k401_catch_up_limit.py @@ -0,0 +1,40 @@ +from policyengine_us.model_api import * + + +class k401_catch_up_limit(Variable): + value_type = float + entity = Person + label = "401(k) catch-up contribution limit" + unit = USD + documentation = ( + "Maximum additional 401(k) catch-up contribution for individuals " + "age 50 or older. SECURE 2.0 enhanced limit applies for ages 60-63." + ) + definition_period = YEAR + reference = [ + "https://www.law.cornell.edu/cfr/text/26/1.414(v)-1", + "https://www.law.cornell.edu/uscode/text/26/414#v_2_E", + ] + + def formula(person, period, parameters): + age = person("age", period) + p = parameters(period).gov.irs.gross_income.retirement_contributions + + # Standard catch-up eligibility (age 50+) + catch_up_eligible = age >= p.catch_up.age_threshold + + # Enhanced catch-up eligibility (ages 60-63, starting 2025) + enhanced_min = p.catch_up.enhanced_age_min + enhanced_max = p.catch_up.enhanced_age_max + enhanced_eligible = (age >= enhanced_min) & (age <= enhanced_max) + + # Get applicable limits + standard_limit = p.catch_up.limit.k401 + enhanced_limit = p.catch_up.limit.k401_enhanced + + # Apply enhanced limit for ages 60-63, standard for others 50+ + return where( + enhanced_eligible, + enhanced_limit, + where(catch_up_eligible, standard_limit, 0), + ) From 38b4f89dcd96c45caacc43aba2466b5e376f1f57 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 25 Nov 2025 09:45:04 -0500 Subject: [PATCH 2/2] Refactor to use p.* directly and update CLAUDE.md guideline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use parameters directly instead of intermediate variables - Add .catch_up to parameter path for cleaner access - Update CLAUDE.md to clarify parameter access guideline 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CLAUDE.md | 2 +- .../retirement/k401_catch_up_limit.py | 20 +++++++++---------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 0d9f69d65a3..63106f7aab0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -49,7 +49,7 @@ make documentation - **Variable Naming**: Use snake_case for variable names and function names; use UPPER_CASE for constants - **Error Handling**: Use np.divide with out/where parameters to avoid divide-by-zero errors - **Documentation**: Add docstrings to classes and functions; include description, parameters, returns -- **Parameter Access**: Always use `p = parameters(period).gov.` pattern and call parameters as `p.*` to make parameter tree origin clear +- **Parameter Access**: Always use `p = parameters(period).gov.` pattern and call parameters as `p.*` to make parameter tree origin clear. Do not assign parameters to intermediate Python variables (e.g., avoid `rate = p.rate`; instead use `p.rate` directly in expressions) - **Constants**: Use UPPERCASE only for constants defined in code, not for parameters from the parameter tree - **Income Combination**: Use `add(person, period, ["income1", "income2"])` instead of manual addition for combining income sources - **Negative Values**: Use `max_(value, 0)` to clip negative values to zero (prevents counterintuitive behavior in economic models) diff --git a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/retirement/k401_catch_up_limit.py b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/retirement/k401_catch_up_limit.py index 1d7a8de1627..8c57a3ceb2b 100644 --- a/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/retirement/k401_catch_up_limit.py +++ b/policyengine_us/variables/gov/irs/income/taxable_income/adjusted_gross_income/above_the_line_deductions/retirement/k401_catch_up_limit.py @@ -18,23 +18,21 @@ class k401_catch_up_limit(Variable): def formula(person, period, parameters): age = person("age", period) - p = parameters(period).gov.irs.gross_income.retirement_contributions + p = parameters( + period + ).gov.irs.gross_income.retirement_contributions.catch_up # Standard catch-up eligibility (age 50+) - catch_up_eligible = age >= p.catch_up.age_threshold + catch_up_eligible = age >= p.age_threshold # Enhanced catch-up eligibility (ages 60-63, starting 2025) - enhanced_min = p.catch_up.enhanced_age_min - enhanced_max = p.catch_up.enhanced_age_max - enhanced_eligible = (age >= enhanced_min) & (age <= enhanced_max) - - # Get applicable limits - standard_limit = p.catch_up.limit.k401 - enhanced_limit = p.catch_up.limit.k401_enhanced + enhanced_eligible = (age >= p.enhanced_age_min) & ( + age <= p.enhanced_age_max + ) # Apply enhanced limit for ages 60-63, standard for others 50+ return where( enhanced_eligible, - enhanced_limit, - where(catch_up_eligible, standard_limit, 0), + p.limit.k401_enhanced, + where(catch_up_eligible, p.limit.k401, 0), )