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/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..8c57a3ceb2b --- /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,38 @@ +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.catch_up + + # Standard catch-up eligibility (age 50+) + catch_up_eligible = age >= p.age_threshold + + # Enhanced catch-up eligibility (ages 60-63, starting 2025) + 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, + p.limit.k401_enhanced, + where(catch_up_eligible, p.limit.k401, 0), + )