diff --git a/changelog_entry.yaml b/changelog_entry.yaml index e69de29bb2d..797f57eedd9 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -0,0 +1,4 @@ +- bump: minor + changes: + added: + - Retirement Savings Contributions Credit (Saver’s Credit). diff --git a/policyengine_us/data/datasets/cps/enhanced_cps/process_puf.py b/policyengine_us/data/datasets/cps/enhanced_cps/process_puf.py index f14ba81048f..d35d7e213cd 100644 --- a/policyengine_us/data/datasets/cps/enhanced_cps/process_puf.py +++ b/policyengine_us/data/datasets/cps/enhanced_cps/process_puf.py @@ -58,7 +58,7 @@ "E07200": "elderly_disabled_credit", "E07220": "child_tax_credit", "E07230": "education_credits", - "E07240": "retirement_savings_credit", + "E07240": "savers_credit", "E07260": "residential_energy_credit", "E07300": "foreign_tax_credit", "E07400": "general_business_credit", @@ -302,7 +302,7 @@ "taxable_social_security", "taxable_ira_distributions", "casualty_loss", - "retirement_savings_credit", + "savers_credit", "misc_deduction", "interest_expense", "unrecaptured_section_1250_gain", diff --git a/policyengine_us/parameters/gov/irs/credits/non_refundable.yaml b/policyengine_us/parameters/gov/irs/credits/non_refundable.yaml index 8941b2cf40f..81794e71130 100644 --- a/policyengine_us/parameters/gov/irs/credits/non_refundable.yaml +++ b/policyengine_us/parameters/gov/irs/credits/non_refundable.yaml @@ -8,7 +8,7 @@ values: # Home mortgage credit - non_refundable_american_opportunity_credit - lifetime_learning_credit - - retirement_savings_credit + - savers_credit - residential_clean_energy_credit - energy_efficient_home_improvement_credit # CDCC became refundable for 2021 only. @@ -21,7 +21,7 @@ values: # Home mortgage credit - non_refundable_american_opportunity_credit - lifetime_learning_credit - - retirement_savings_credit + - savers_credit - residential_clean_energy_credit - energy_efficient_home_improvement_credit - new_clean_vehicle_credit @@ -34,7 +34,7 @@ values: # Home mortgage credit - non_refundable_american_opportunity_credit - lifetime_learning_credit - - retirement_savings_credit + - savers_credit - residential_clean_energy_credit - energy_efficient_home_improvement_credit - new_clean_vehicle_credit diff --git a/policyengine_us/parameters/gov/irs/credits/retirement_saving/age_threshold.yaml b/policyengine_us/parameters/gov/irs/credits/retirement_saving/age_threshold.yaml new file mode 100644 index 00000000000..7eb6bbd3ebb --- /dev/null +++ b/policyengine_us/parameters/gov/irs/credits/retirement_saving/age_threshold.yaml @@ -0,0 +1,16 @@ +description: The IRS limits the saver's credit to taxpayers at or over to following age. +values: + 2019-01-01: 18 +metadata: + unit: year + period: year + label: Retirement Savings Contributions Credit (Saver’s Credit) age threshold + reference: + - title: 26 U.S. Code § 25B(c)(1) + href: https://www.law.cornell.edu/uscode/text/26/25B#c + - title: IRS | Retirement Savings Contributions Credit (Saver’s Credit) + href: https://www.irs.gov/retirement-plans/plan-participant-employee/retirement-savings-contributions-savers-credit + - title: 2022 Publication 590-A + href: https://www.irs.gov/pub/irs-pdf/p590a.pdf#page=45 + - title: Form 8880 - 2023 Credit for Qualified Retirement Savings Contributions + href: https://www.irs.gov/pub/irs-pdf/f8880.pdf#page=1 diff --git a/policyengine_us/parameters/gov/irs/credits/retirement_saving/contributions_cap.yaml b/policyengine_us/parameters/gov/irs/credits/retirement_saving/contributions_cap.yaml new file mode 100644 index 00000000000..7d889c02290 --- /dev/null +++ b/policyengine_us/parameters/gov/irs/credits/retirement_saving/contributions_cap.yaml @@ -0,0 +1,16 @@ +description: The IRS caps the qualifying contributions at the following amount under the saver's credit. +values: + 2019-01-01: 2_000 +metadata: + unit: currency-USD + period: year + label: Saver’s Credit contributions cap + reference: + - title: 26 U.S. Code § 25B(a) + href: https://www.law.cornell.edu/uscode/text/26/25B + - title: IRS | Retirement Savings Contributions Credit (Saver’s Credit) + href: https://www.irs.gov/retirement-plans/plan-participant-employee/retirement-savings-contributions-savers-credit + - title: 2022 Publication 590-A + href: https://www.irs.gov/pub/irs-pdf/p590a.pdf#page=45 + - title: Form 8880 - 2023 Credit for Qualified Retirement Savings Contributions + href: https://www.irs.gov/pub/irs-pdf/f8880.pdf#page=1 diff --git a/policyengine_us/parameters/gov/irs/credits/retirement_saving/qualified_contributions.yaml b/policyengine_us/parameters/gov/irs/credits/retirement_saving/qualified_contributions.yaml new file mode 100644 index 00000000000..7c0c1dd9c84 --- /dev/null +++ b/policyengine_us/parameters/gov/irs/credits/retirement_saving/qualified_contributions.yaml @@ -0,0 +1,18 @@ +description: South Carolina subtracts the following elements from gross earned income when computing the two wage earner credit. +values: + 2021-01-01: + - self_employment_tax_ald_person + - self_employed_health_insurance_ald_person + - self_employed_pension_contribution_ald_person + +metadata: + unit: list + period: year + label: South Carolina two wage earner credit earned income subtractions + reference: + - title: SC 1040 tax form instructions (2021) + href: https://dor.sc.gov/forms-site/Forms/IITPacket_2021.pdf#page=22 + - title: SC 1040 tax form instructions (2023) + href: https://dor.sc.gov/forms-site/Forms/IITPacket_2023.pdf#page=23 + - title: SC Legal Code | Section 12-6-3330 (C)(b) + href: https://www.scstatehouse.gov/code/t12c006.php diff --git a/policyengine_us/parameters/gov/irs/credits/retirement_saving/rate/joint.yaml b/policyengine_us/parameters/gov/irs/credits/retirement_saving/rate/joint.yaml new file mode 100644 index 00000000000..0f2367d9015 --- /dev/null +++ b/policyengine_us/parameters/gov/irs/credits/retirement_saving/rate/joint.yaml @@ -0,0 +1,62 @@ +description: IRS multiplies the qualified contributions by the following rate for joint filers under the Saver's Credit, based on adjusted gross income. +metadata: + type: single_amount + threshold_unit: currency-USD + ramount_unit: /1 + period: year + label: Saver’s Credit joint rate + reference: + - title: 26 U.S. Code § 25B(b)(1) + href: https://www.law.cornell.edu/uscode/text/26/25B#b + - title: IRS | Retirement Savings Contributions Credit (Saver’s Credit) + href: https://www.irs.gov/retirement-plans/plan-participant-employee/retirement-savings-contributions-savers-credit + - title: Form 8880 - 2023 Credit for Qualified Retirement Savings Contributions + href: https://www.irs.gov/pub/irs-pdf/f8880.pdf#page=1 + +brackets: + - threshold: + 2019-01-01: 0 + amount: + 2019-01-01: 0.5 + - threshold: + values: + 2019-01-01: 38_500 + 2020-01-01: 39_000 + 2021-01-01: 39_500 + 2022-01-01: 41_000 + 2023-01-01: 43_500 + uprating: + parameter: gov.irs.uprating + rounding: + type: nearest + interval: 500 + amount: + 2019-01-01: 0.2 + - threshold: + values: + 2019-01-01: 41_500 + 2020-01-01: 42_500 + 2021-01-01: 43_000 + 2022-01-01: 44_000 + 2023-01-01: 47_500 + uprating: + parameter: gov.irs.uprating + rounding: + type: nearest + interval: 500 + amount: + 2019-01-01: 0.1 + - threshold: + values: + 2019-01-01: 64_000 + 2020-01-01: 65_000 + 2021-01-01: 66_000 + 2022-01-01: 68_000 + 2023-01-01: 73_000 + uprating: + parameter: gov.irs.uprating + rounding: + type: nearest + interval: 500 + amount: + 2019-01-01: 0 diff --git a/policyengine_us/parameters/gov/irs/credits/retirement_saving/rate/threshold_adjustment/head_of_household.yaml b/policyengine_us/parameters/gov/irs/credits/retirement_saving/rate/threshold_adjustment/head_of_household.yaml new file mode 100644 index 00000000000..1148e6febb1 --- /dev/null +++ b/policyengine_us/parameters/gov/irs/credits/retirement_saving/rate/threshold_adjustment/head_of_household.yaml @@ -0,0 +1,14 @@ +description: IRS adjusts the threshold of the Savor's credit for head of household filers by multiplying the joint threshold by this rate. +metadata: + unit: /1 + period: year + label: Retirement Savings Contributions Credit (Saver’s Credit) head of household threshold adjustment rate + reference: + - title: 26 U.S. Code § 25B(b)(2)(a) + href: https://www.law.cornell.edu/uscode/text/26/25B#b + - title: IRS | Retirement Savings Contributions Credit (Saver’s Credit) + href: https://www.irs.gov/retirement-plans/plan-participant-employee/retirement-savings-contributions-savers-credit + - title: Form 8880 - 2023 Credit for Qualified Retirement Savings Contributions + href: https://www.irs.gov/pub/irs-pdf/f8880.pdf#page=1 +values: + 2019-01-01: 0.75 diff --git a/policyengine_us/parameters/gov/irs/credits/retirement_saving/rate/threshold_adjustment/separate.yaml b/policyengine_us/parameters/gov/irs/credits/retirement_saving/rate/threshold_adjustment/separate.yaml new file mode 100644 index 00000000000..e9559921283 --- /dev/null +++ b/policyengine_us/parameters/gov/irs/credits/retirement_saving/rate/threshold_adjustment/separate.yaml @@ -0,0 +1,14 @@ +description: IRS adjusts the threshold of the Savor's credit for separate filers by multiplying the joint threshold by this rate. +metadata: + unit: /1 + period: year + label: Retirement Savings Contributions Credit (Saver’s Credit) separate threshold adjustment rate + reference: + - title: 26 U.S. Code § 25B(b)(2)(b) + href: https://www.law.cornell.edu/uscode/text/26/25B#b + - title: IRS | Retirement Savings Contributions Credit (Saver’s Credit) + href: https://www.irs.gov/retirement-plans/plan-participant-employee/retirement-savings-contributions-savers-credit + - title: Form 8880 - 2023 Credit for Qualified Retirement Savings Contributions + href: https://www.irs.gov/pub/irs-pdf/f8880.pdf#page=1 +values: + 2019-01-01: 0.5 diff --git a/policyengine_us/parameters/gov/irs/credits/retirement_saving/rate/threshold_adjustment/single.yaml b/policyengine_us/parameters/gov/irs/credits/retirement_saving/rate/threshold_adjustment/single.yaml new file mode 100644 index 00000000000..de650e165b4 --- /dev/null +++ b/policyengine_us/parameters/gov/irs/credits/retirement_saving/rate/threshold_adjustment/single.yaml @@ -0,0 +1,14 @@ +description: IRS adjusts the threshold of the Savor's credit for single filers by multiplying the joint threshold by this rate. +metadata: + unit: /1 + period: year + label: Retirement Savings Contributions Credit (Saver’s Credit) single threshold adjustment rate + reference: + - title: 26 U.S. Code § 25B(b)(2)(b) + href: https://www.law.cornell.edu/uscode/text/26/25B#b + - title: IRS | Retirement Savings Contributions Credit (Saver’s Credit) + href: https://www.irs.gov/retirement-plans/plan-participant-employee/retirement-savings-contributions-savers-credit + - title: Form 8880 - 2023 Credit for Qualified Retirement Savings Contributions + href: https://www.irs.gov/pub/irs-pdf/f8880.pdf#page=1 +values: + 2019-01-01: 0.5 diff --git a/policyengine_us/parameters/gov/irs/credits/retirement_saving/rate/threshold_adjustment/widow.yaml b/policyengine_us/parameters/gov/irs/credits/retirement_saving/rate/threshold_adjustment/widow.yaml new file mode 100644 index 00000000000..08715f4708b --- /dev/null +++ b/policyengine_us/parameters/gov/irs/credits/retirement_saving/rate/threshold_adjustment/widow.yaml @@ -0,0 +1,14 @@ +description: IRS adjusts the threshold of the Savor's credit for widow(er) filers by multiplying the joint threshold by this rate. +metadata: + unit: /1 + period: year + label: Retirement Savings Contributions Credit (Saver’s Credit) widow threshold adjustment rate + reference: + - title: 26 U.S. Code § 25B(b)(2)(b) + href: https://www.law.cornell.edu/uscode/text/26/25B#b + - title: IRS | Retirement Savings Contributions Credit (Saver’s Credit) + href: https://www.irs.gov/retirement-plans/plan-participant-employee/retirement-savings-contributions-savers-credit + - title: Form 8880 - 2023 Credit for Qualified Retirement Savings Contributions + href: https://www.irs.gov/pub/irs-pdf/f8880.pdf#page=1 +values: + 2019-01-01: 0.5 diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/retirement_saving/retirement_saving_eligible_person.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/retirement_saving/retirement_saving_eligible_person.yaml new file mode 100644 index 00000000000..7309a5c6dc6 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/retirement_saving/retirement_saving_eligible_person.yaml @@ -0,0 +1,19 @@ +- name: eligible person + period: 2021 + input: + age: 20 + is_tax_unit_head: true + claimed_as_dependent_on_another_return: false + is_full_time_student: false + output: + savers_credit_eligible_person: true + +- name: Not an eligible person, under age threshold + period: 2021 + input: + age: 17 + is_tax_unit_spouse: true + spouse_is_dependent_elsewhere: false + is_full_time_student: false + output: + savers_credit_eligible_person: false diff --git a/policyengine_us/tests/policy/baseline/gov/irs/credits/retirement_saving/savers_credit_person.yaml b/policyengine_us/tests/policy/baseline/gov/irs/credits/retirement_saving/savers_credit_person.yaml new file mode 100644 index 00000000000..068e9b6433a --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/irs/credits/retirement_saving/savers_credit_person.yaml @@ -0,0 +1,133 @@ +- name: joint with one eligible person + period: 2023 + input: + people: + person1: + savers_credit_eligible_person: true + traditional_ira_contributions: 3_000 + roth_ira_contributions: 2_000 + person2: + savers_credit_eligible_person: false + traditional_ira_contributions: 0 + roth_ira_contributions: 0 + tax_units: + tax_unit: + members: [person1, person2] + filing_status: JOINT + adjusted_gross_income: 39_000 + output: + savers_credit_person: [1_000, 0] + +- name: joint with AGI over a threshold + period: 2023 + input: + people: + person1: + savers_credit_eligible_person: true + adjusted_gross_income_person: 39_000 + traditional_ira_contributions: 3_000 + roth_ira_contributions: 2_000 + person2: + savers_credit_eligible_person: true + adjusted_gross_income_person: 50_000 + traditional_ira_contributions: 3_000 + roth_ira_contributions: 2_000 + tax_units: + tax_unit: + members: [person1, person2] + filing_status: JOINT + adjusted_gross_income: 73_000 + output: + savers_credit_person: [0, 0] + +- name: Head of household with AGI over the eligible threshold + period: 2023 + input: + people: + person1: + savers_credit_eligible_person: true + traditional_ira_contributions: 3_000 + roth_ira_contributions: 2_000 + person2: + savers_credit_eligible_person: true + traditional_ira_contributions: 3_000 + roth_ira_contributions: 2_000 + tax_units: + tax_unit: + members: [person1, person2] + filing_status: HEAD_OF_HOUSEHOLD + adjusted_gross_income: 54_750 + output: + savers_credit_person: [0, 0] + +- name: Head of household with AGI under the eligible threshold, capped contributions + period: 2023 + input: + people: + person1: + savers_credit_eligible_person: true + traditional_ira_contributions: 3_000 + roth_ira_contributions: 2_000 + person2: + savers_credit_eligible_person: true + traditional_ira_contributions: 3_000 + roth_ira_contributions: 2_000 + tax_units: + tax_unit: + members: [person1, person2] + filing_status: HEAD_OF_HOUSEHOLD + adjusted_gross_income: 33_000 + output: + savers_credit_person: [400, 400] + +- name: Single uncapped contributions + period: 2023 + input: + people: + person1: + savers_credit_eligible_person: true + roth_ira_contributions: 500 + roth_401k_contributions: 1_000 + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + adjusted_gross_income: 20_000 + output: + savers_credit_person: [750] + +- name: Single uncapped contributions, middle threshold + period: 2023 + input: + people: + person1: + savers_credit_eligible_person: true + roth_ira_contributions: 500 + roth_401k_contributions: 1_000 + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + adjusted_gross_income: 33_000 + output: + savers_credit_person: [150] + +- name: Joint household, no eligible people + period: 2023 + input: + people: + person1: + savers_credit_eligible_person: false + traditional_ira_contributions: 3_000 + roth_ira_contributions: 2_000 + person2: + savers_credit_eligible_person: false + traditional_ira_contributions: 0 + roth_ira_contributions: 0 + tax_units: + tax_unit: + members: [person1, person2] + filing_status: JOINT + adjusted_gross_income: 39_000 + output: + savers_credit_person: [0, 0] diff --git a/policyengine_us/variables/gov/irs/credits/retirement_savings/credit.py b/policyengine_us/variables/gov/irs/credits/retirement_savings/credit.py deleted file mode 100644 index ab5f05c2a3d..00000000000 --- a/policyengine_us/variables/gov/irs/credits/retirement_savings/credit.py +++ /dev/null @@ -1,10 +0,0 @@ -from policyengine_us.model_api import * - - -class retirement_savings_credit(Variable): - value_type = float - entity = TaxUnit - label = "Retirement savings credit" - definition_period = YEAR - documentation = "Retirement savings contributions credit from Form 8880" - unit = USD diff --git a/policyengine_us/variables/gov/irs/credits/retirement_savings/savers_credit.py b/policyengine_us/variables/gov/irs/credits/retirement_savings/savers_credit.py new file mode 100644 index 00000000000..32ccbbe455e --- /dev/null +++ b/policyengine_us/variables/gov/irs/credits/retirement_savings/savers_credit.py @@ -0,0 +1,15 @@ +from policyengine_us.model_api import * + + +class savers_credit(Variable): + value_type = float + entity = TaxUnit + definition_period = YEAR + label = "Retirement Savings Credit" + unit = USD + reference = ( + "https://www.irs.gov/pub/irs-pdf/f8880.pdf", + "https://www.law.cornell.edu/uscode/text/26/25B#c", + ) + + adds = ["savers_credit_person"] diff --git a/policyengine_us/variables/gov/irs/credits/retirement_savings/savers_credit_eligible_person.py b/policyengine_us/variables/gov/irs/credits/retirement_savings/savers_credit_eligible_person.py new file mode 100644 index 00000000000..4ac1ec3b173 --- /dev/null +++ b/policyengine_us/variables/gov/irs/credits/retirement_savings/savers_credit_eligible_person.py @@ -0,0 +1,29 @@ +from policyengine_us.model_api import * + + +class savers_credit_eligible_person(Variable): + entity = Person + definition_period = YEAR + label = "Eligible person for the retirement saving contributions credit" + value_type = bool + reference = ( + "https://www.irs.gov/pub/irs-pdf/f8880.pdf", + "https://www.law.cornell.edu/uscode/text/26/25B#c", + ) + + def formula(person, period, parameters): + p = parameters(period).gov.irs.credits.retirement_saving + age = person("age", period) + age_eligible = age >= p.threshold.age + full_time_student = person("is_full_time_student", period) + claimed_on_another_return = person( + "claimed_as_dependent_on_another_return", period + ) + head_or_spouse = person("is_tax_unit_head_or_spouse", period) + + return ( + age_eligible + & head_or_spouse + & ~full_time_student + & ~claimed_on_another_return + ) diff --git a/policyengine_us/variables/gov/irs/credits/retirement_savings/savers_credit_person.py b/policyengine_us/variables/gov/irs/credits/retirement_savings/savers_credit_person.py new file mode 100644 index 00000000000..a4d19f9add5 --- /dev/null +++ b/policyengine_us/variables/gov/irs/credits/retirement_savings/savers_credit_person.py @@ -0,0 +1,69 @@ +from policyengine_us.model_api import * + + +class savers_credit_person(Variable): + value_type = float + entity = Person + definition_period = YEAR + label = "Retirement Savings Credit for eahc individual person" + unit = USD + reference = ( + "https://www.irs.gov/pub/irs-pdf/f8880.pdf", + "https://www.law.cornell.edu/uscode/text/26/25B#c", + ) + defined_for = "savers_credit_eligible_person" + + def formula(person, period, parameters): + p = parameters(period).gov.irs.credits.retirement_saving + + qualified_contributions = add( + person, + period, + [ + "traditional_ira_contributions", + "roth_ira_contributions", + "roth_401k_contributions", + "traditional_401k_contributions", + ], + ) + capped_qualified_contributions = min_( + qualified_contributions, p.contributions_cap + ) + + agi = person.tax_unit("adjusted_gross_income", period) + + # AGI threshold for the rate + filing_status = person.tax_unit("filing_status", period) + statuses = filing_status.possible_values + # Joint as the base + + threshold_adjustment = select( + [ + filing_status == statuses.SINGLE, + filing_status == statuses.SEPARATE, + filing_status == statuses.SURVIVING_SPOUSE, + filing_status == statuses.HEAD_OF_HOUSEHOLD, + ], + [ + p.rate.threshold_adjustment.single, + p.rate.threshold_adjustment.separate, + p.rate.threshold_adjustment.widow, + p.rate.threshold_adjustment.head_of_household, + ], + default=1, + ) + # Credit rate + credit_rate = select( + [ + agi < p.rate.joint.thresholds[1] * threshold_adjustment, + agi < p.rate.joint.thresholds[2] * threshold_adjustment, + agi < p.rate.joint.thresholds[3] * threshold_adjustment, + ], + [ + p.rate.joint.amounts[0], + p.rate.joint.amounts[1], + p.rate.joint.amounts[2], + ], + default=0, + ) + return credit_rate * capped_qualified_contributions