From 4933997685c7f8c8353cf7d54730743f88cf17a6 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 11 Oct 2026 09:44:24 -0400 Subject: [PATCH 1/4] WIP: Oregon federal tax subtraction follows the OR-17 worksheet credits Co-Authored-By: Claude Opus 5.5 --- .../refundable_credits.yaml | 50 +++++++++++++++++++ .../or_federal_tax_liability_subtraction.py | 43 ++++++++++------ 2 files changed, 78 insertions(+), 15 deletions(-) create mode 100644 policyengine_us/parameters/gov/states/or/tax/income/subtractions/federal_tax_liability/refundable_credits.yaml diff --git a/policyengine_us/parameters/gov/states/or/tax/income/subtractions/federal_tax_liability/refundable_credits.yaml b/policyengine_us/parameters/gov/states/or/tax/income/subtractions/federal_tax_liability/refundable_credits.yaml new file mode 100644 index 00000000000..3829be75b83 --- /dev/null +++ b/policyengine_us/parameters/gov/states/or/tax/income/subtractions/federal_tax_liability/refundable_credits.yaml @@ -0,0 +1,50 @@ +description: Oregon reduces the federal income tax liability it subtracts by these federal refundable credits. +values: + # Federal tax worksheet, Part A, of each year's Publication OR-17. The earned + # income credit is never listed (ORS 316.685(1)(b)), and outside 2021 neither + # is the additional child tax credit. + 2017-01-01: + - refundable_american_opportunity_credit # Line 6 + - assigned_aca_ptc # Line 7 (Form 8962, line 24) + 2020-01-01: + - refundable_american_opportunity_credit # Line 6 + - assigned_aca_ptc # Line 7 (Form 8962, line 24) + - recovery_rebate_credit # Line 8, with the stimulus payments received + 2021-01-01: + # Line 8 is the child tax credit on Schedule 8812, line 14e. Line 2 adds + # back the nonrefundable part that line 1 already nets out. + - refundable_ctc + - refundable_american_opportunity_credit # Line 9 + - recovery_rebate_credit # Line 10, with the stimulus payments received + - assigned_aca_ptc # Line 11 (Form 8962, line 24) + - cdcc # Line 12 (Schedule 3, line 13g) + 2022-01-01: + - refundable_american_opportunity_credit # Line 6 + - assigned_aca_ptc # Line 7 (Form 8962, line 24) + # From 2025, line 7 is the refundable adoption credit (Form 1040, line 30), + # which is not modeled, and the premium tax credit moves to line 8. +metadata: + unit: list + period: year + label: Oregon federal tax liability subtraction refundable credits + reference: + - title: ORS 316.685(1)(b) + href: https://www.oregonlegislature.gov/bills_laws/ors/ors316.html + - title: 2017 Publication OR-17, Federal tax worksheet, Part A, lines 6 to 8 + href: https://www.oregon.gov/dor/forms/FormsPubs/publication-or-17_101-431_2017.pdf#page=63 + - title: 2018 Publication OR-17, Federal tax worksheet, Part A, lines 6 to 8 + href: https://www.oregon.gov/dor/forms/FormsPubs/publication-or-17_101-431_2018.pdf#page=68 + - title: 2019 Publication OR-17, Federal tax worksheet, Part A, lines 6 to 8 + href: https://www.oregon.gov/dor/forms/FormsPubs/publication-or-17_101-431_2019.pdf#page=63 + - title: 2020 Publication OR-17, Federal tax worksheet, Part A, lines 6 to 9 + href: https://www.oregon.gov/dor/forms/FormsPubs/publication-or-17_101-431_2020.pdf#page=66 + - title: 2021 Publication OR-17, Federal tax worksheet, Part A, lines 8 to 13 + href: https://www.oregon.gov/dor/forms/FormsPubs/publication-or-17_101-431_2021.pdf#page=72 + - title: 2022 Publication OR-17, Federal tax worksheet, Part A, lines 6 to 8 + href: https://www.oregon.gov/dor/forms/FormsPubs/publication-or-17_101-431_2022.pdf#page=73 + - title: 2023 Publication OR-17, Federal tax worksheet, Part A, lines 6 to 8 + href: https://www.oregon.gov/dor/forms/FormsPubs/publication-or-17_101-431_2023.pdf#page=73 + - title: 2024 Publication OR-17, Federal tax worksheet, Part A, lines 6 to 8 + href: https://www.oregon.gov/dor/forms/FormsPubs/publication-or-17_101-431_2024.pdf#page=71 + - title: 2025 Publication OR-17, Federal tax worksheet, Part A, lines 6 to 9 + href: https://www.oregon.gov/dor/forms/FormsPubs/publication-or-17_101-431_2025.pdf#page=71 diff --git a/policyengine_us/variables/gov/states/or/tax/income/subtractions/or_federal_tax_liability_subtraction.py b/policyengine_us/variables/gov/states/or/tax/income/subtractions/or_federal_tax_liability_subtraction.py index 43168c0596f..965331e3c5d 100644 --- a/policyengine_us/variables/gov/states/or/tax/income/subtractions/or_federal_tax_liability_subtraction.py +++ b/policyengine_us/variables/gov/states/or/tax/income/subtractions/or_federal_tax_liability_subtraction.py @@ -8,24 +8,37 @@ class or_federal_tax_liability_subtraction(Variable): unit = USD definition_period = YEAR reference = ( - "https://www.oregon.gov/dor/forms/FormsPubs/publication-or-17_101-431_2021.pdf#page=71", - "https://www.oregonlegislature.gov/bills_laws/ors/ors316.html", # Subsection 316.800 + # ORS 316.680(1)(b), 316.685 and 316.695(3). + "https://www.oregonlegislature.gov/bills_laws/ors/ors316.html", + # Federal tax worksheet, Part A. + "https://www.oregon.gov/dor/forms/FormsPubs/publication-or-17_101-431_2025.pdf#page=71", + "https://www.oregon.gov/dor/forms/FormsPubs/publication-or-17_101-431_2021.pdf#page=72", ) defined_for = StateCode.OR def formula(tax_unit, period, parameters): - # calculate Oregon concept of federal income tax - federal_itax = tax_unit("income_tax", period) - federal_eitc = tax_unit("eitc", period) - or_federal_income_tax = max_(0, federal_itax + federal_eitc) - # limit subtraction based on caps scaled to federal AGI - filing_status = tax_unit("filing_status", period) - status = filing_status.possible_values p = ( parameters(period) .gov.states["or"] - .tax.income.subtractions.federal_tax_liability.cap + .tax.income.subtractions.federal_tax_liability + ) + # Federal tax worksheet, line 5: federal income tax after nonrefundable + # credits plus the other taxes on Schedule 2, lines 8, 16 and 17. The + # worksheet leaves out the net investment income tax (Schedule 2, line + # 12) and Social Security and Medicare taxes on unreported tips and + # wages (lines 5 and 6; ORS 316.685(3)(a)). + tax_before_refundable_credits = tax_unit( + "income_tax_before_refundable_credits", period + ) - add(tax_unit, period, ["net_investment_income_tax", "unreported_payroll_tax"]) + # Only the refundable credits the worksheet lists reduce the liability. + # The earned income credit never does (ORS 316.685(1)(b)). + refundable_credits = add(tax_unit, period, p.refundable_credits) + or_federal_income_tax = max_( + 0, tax_before_refundable_credits - refundable_credits ) + # limit subtraction based on caps scaled to federal AGI + filing_status = tax_unit("filing_status", period) + status = filing_status.possible_values federal_agi = tax_unit("adjusted_gross_income", period) cap = select( [ @@ -36,11 +49,11 @@ def formula(tax_unit, period, parameters): filing_status == status.SURVIVING_SPOUSE, ], [ - p.single.calc(federal_agi), - p.joint.calc(federal_agi), - p.head_of_household.calc(federal_agi), - p.separate.calc(federal_agi), - p.surviving_spouse.calc(federal_agi), + p.cap.single.calc(federal_agi), + p.cap.joint.calc(federal_agi), + p.cap.head_of_household.calc(federal_agi), + p.cap.separate.calc(federal_agi), + p.cap.surviving_spouse.calc(federal_agi), ], ) return min_(or_federal_income_tax, cap) From cae52810adb9fea043e9370a97b61c5461fe2f72 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 11 Oct 2026 11:32:09 -0400 Subject: [PATCH 2/4] Reduce Oregon's federal tax subtraction by the OR-17 worksheet's refundable credits only ORS 316.680(1)(b) and 316.685; Publication OR-17 federal tax worksheet, Part A (2017-2025). The worksheet starts from Form 1040 line 22 plus the other income taxes on Schedule 2, lines 8, 16 and 17, and subtracts only the American Opportunity credit, the premium tax credit, and in 2020-2021 the recovery rebate credit and (2021) the refundable child tax and child care credits. Main used income_tax + eitc, so it subtracted the additional child tax credit in every year, never subtracted the premium tax credit, and kept the net investment income tax. Co-Authored-By: Claude Opus 5.5 --- ...federal-tax-subtraction-worksheet.fixed.md | 1 + .../or_federal_tax_liability_subtraction.yaml | 138 ++++++++++++++++-- .../or_federal_tax_liability_subtraction.py | 4 +- 3 files changed, 131 insertions(+), 12 deletions(-) create mode 100644 changelog.d/or-federal-tax-subtraction-worksheet.fixed.md diff --git a/changelog.d/or-federal-tax-subtraction-worksheet.fixed.md b/changelog.d/or-federal-tax-subtraction-worksheet.fixed.md new file mode 100644 index 00000000000..33f2b52f04b --- /dev/null +++ b/changelog.d/or-federal-tax-subtraction-worksheet.fixed.md @@ -0,0 +1 @@ +Reduce Oregon's federal tax liability subtraction by the premium tax credit and only the other refundable credits on the Publication OR-17 federal tax worksheet, and leave out the net investment income tax. diff --git a/policyengine_us/tests/policy/baseline/gov/states/or/tax/income/subtractions/or_federal_tax_liability_subtraction.yaml b/policyengine_us/tests/policy/baseline/gov/states/or/tax/income/subtractions/or_federal_tax_liability_subtraction.yaml index 4e8190d3f01..cd07ffd2cd2 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/or/tax/income/subtractions/or_federal_tax_liability_subtraction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/or/tax/income/subtractions/or_federal_tax_liability_subtraction.yaml @@ -4,7 +4,7 @@ state_code: OR filing_status: SINGLE adjusted_gross_income: 80_000 - income_tax: 10_000 + income_tax_before_refundable_credits: 10_000 eitc: 0 output: or_federal_tax_liability_subtraction: 7_050 @@ -15,7 +15,8 @@ state_code: OR filing_status: SINGLE adjusted_gross_income: 80_000 - income_tax: 6_000 + # 6,000 of federal income tax plus the 1,000 EITC it nets out. + income_tax_before_refundable_credits: 7_000 eitc: 1_000 output: or_federal_tax_liability_subtraction: 7_000 @@ -26,7 +27,7 @@ state_code: OR filing_status: SINGLE adjusted_gross_income: 145_000 - income_tax: 10_000 + income_tax_before_refundable_credits: 10_000 eitc: 0 output: or_federal_tax_liability_subtraction: 0 @@ -37,7 +38,7 @@ state_code: OR filing_status: SEPARATE adjusted_gross_income: 80_000 - income_tax: 10_000 + income_tax_before_refundable_credits: 10_000 eitc: 0 output: or_federal_tax_liability_subtraction: 3_525 @@ -48,7 +49,7 @@ state_code: OR filing_status: SEPARATE adjusted_gross_income: 145_000 - income_tax: 10_000 + income_tax_before_refundable_credits: 10_000 eitc: 0 output: or_federal_tax_liability_subtraction: 0 @@ -59,8 +60,8 @@ state_code: OR filing_status: JOINT adjusted_gross_income: 80_000 - income_tax: 10_000 - eitc: 0 + income_tax_before_refundable_credits: 10_000 + eitc: 0 output: or_federal_tax_liability_subtraction: 7_050 @@ -70,7 +71,7 @@ state_code: OR filing_status: JOINT adjusted_gross_income: 290_000 - income_tax: 10_000 + income_tax_before_refundable_credits: 10_000 eitc: 0 output: or_federal_tax_liability_subtraction: 0 @@ -85,7 +86,7 @@ state_code: OR filing_status: SINGLE adjusted_gross_income: 80_000 - income_tax: 10_000 + income_tax_before_refundable_credits: 10_000 eitc: 0 output: or_federal_tax_liability_subtraction: 8_750 @@ -96,7 +97,7 @@ state_code: OR filing_status: JOINT adjusted_gross_income: 255_000 - income_tax: 30_000 + income_tax_before_refundable_credits: 30_000 eitc: 0 output: or_federal_tax_liability_subtraction: 7_000 @@ -107,8 +108,123 @@ state_code: OR filing_status: SEPARATE adjusted_gross_income: 80_000 - income_tax: 10_000 + income_tax_before_refundable_credits: 10_000 eitc: 0 output: # 50% x 8,750 or_federal_tax_liability_subtraction: 4_375 + +# Federal tax worksheet, Part A, 2025 Publication OR-17, PDF pages 71-72: line 5 +# (federal tax after nonrefundable credits plus other income taxes) less the +# American Opportunity, refundable adoption and premium tax credits (lines 6 to +# 9), floored at zero (line 10), then capped (line 12). +- name: Case 11, 2025 premium tax credit reduces the subtraction. + period: 2025 + input: + state_code: OR + filing_status: SINGLE + adjusted_gross_income: 45_000 + income_tax_before_refundable_credits: 4_000 + assigned_aca_ptc: 1_500 + eitc: 0 + output: + # 4,000 - 1,500 + or_federal_tax_liability_subtraction: 2_500 + +- name: Case 12, 2025 additional child tax credit does not reduce the subtraction. + period: 2025 + input: + state_code: OR + filing_status: JOINT + adjusted_gross_income: 60_000 + # Form 1040 line 22 is 0; a 1,000 additional tax on an early retirement + # plan distribution is Schedule 2, line 8 (worksheet line 4). + income_tax_before_refundable_credits: 1_000 + refundable_ctc: 3_000 + assigned_aca_ptc: 0 + eitc: 500 + output: + or_federal_tax_liability_subtraction: 1_000 + +- name: Case 13, 2025 listed credits above the liability floor the subtraction at zero. + period: 2025 + input: + state_code: OR + filing_status: SINGLE + adjusted_gross_income: 40_000 + income_tax_before_refundable_credits: 2_000 + refundable_american_opportunity_credit: 1_000 + assigned_aca_ptc: 1_500 + eitc: 0 + output: + # max(0, 2,000 - 1,000 - 1,500) + or_federal_tax_liability_subtraction: 0 + +- name: Case 14, 2025 net investment income tax and unreported payroll tax are not subtracted. + period: 2025 + input: + state_code: OR + filing_status: SINGLE + adjusted_gross_income: 90_000 + income_tax_before_refundable_credits: 5_000 + # Schedule 2, lines 12 and 5, which worksheet line 4 leaves out. + net_investment_income_tax: 600 + unreported_payroll_tax: 400 + assigned_aca_ptc: 0 + eitc: 0 + output: + # 5,000 - 600 - 400 + or_federal_tax_liability_subtraction: 4_000 + +# 2021 Publication OR-17, PDF page 72: lines 8 to 12 subtract the refundable +# child tax credit, the American Opportunity credit, the recovery rebate credit +# with stimulus payments received, the premium tax credit and the refundable +# child and dependent care credit. +- name: Case 15, 2021 refundable child tax, recovery rebate and child care credits reduce the subtraction. + period: 2021 + input: + state_code: OR + filing_status: JOINT + adjusted_gross_income: 80_000 + income_tax_before_refundable_credits: 9_000 + refundable_ctc: 3_000 + recovery_rebate_credit: 2_800 + cdcc: 1_000 + assigned_aca_ptc: 0 + eitc: 0 + output: + # 9,000 - 3,000 - 2,800 - 1,000 + or_federal_tax_liability_subtraction: 2_200 + +# 2020 Publication OR-17, PDF page 66: line 8 subtracts the stimulus payments +# received plus the recovery rebate credit; the additional child tax credit is +# not listed. +- name: Case 16, 2020 recovery rebate credit reduces the subtraction but the additional child tax credit does not. + period: 2020 + input: + state_code: OR + filing_status: JOINT + adjusted_gross_income: 50_000 + income_tax_before_refundable_credits: 3_000 + recovery_rebate_credit: 2_400 + refundable_ctc: 1_000 + assigned_aca_ptc: 0 + eitc: 0 + output: + # 3,000 - 2,400 + or_federal_tax_liability_subtraction: 600 + +- name: Case 17, 2026 premium tax credit and American Opportunity credit reduce the subtraction. + period: 2026 + input: + state_code: OR + filing_status: HEAD_OF_HOUSEHOLD + adjusted_gross_income: 70_000 + income_tax_before_refundable_credits: 6_000 + refundable_american_opportunity_credit: 1_000 + assigned_aca_ptc: 2_000 + refundable_ctc: 1_700 + eitc: 0 + output: + # 6,000 - 1,000 - 2,000 + or_federal_tax_liability_subtraction: 3_000 diff --git a/policyengine_us/variables/gov/states/or/tax/income/subtractions/or_federal_tax_liability_subtraction.py b/policyengine_us/variables/gov/states/or/tax/income/subtractions/or_federal_tax_liability_subtraction.py index 965331e3c5d..9081a942735 100644 --- a/policyengine_us/variables/gov/states/or/tax/income/subtractions/or_federal_tax_liability_subtraction.py +++ b/policyengine_us/variables/gov/states/or/tax/income/subtractions/or_federal_tax_liability_subtraction.py @@ -29,7 +29,9 @@ def formula(tax_unit, period, parameters): # wages (lines 5 and 6; ORS 316.685(3)(a)). tax_before_refundable_credits = tax_unit( "income_tax_before_refundable_credits", period - ) - add(tax_unit, period, ["net_investment_income_tax", "unreported_payroll_tax"]) + ) - add( + tax_unit, period, ["net_investment_income_tax", "unreported_payroll_tax"] + ) # Only the refundable credits the worksheet lists reduce the liability. # The earned income credit never does (ORS 316.685(1)(b)). refundable_credits = add(tax_unit, period, p.refundable_credits) From 9c52f85e4b98341303b54d6e4534977db3d747ef Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 11 Oct 2026 11:32:42 -0400 Subject: [PATCH 3/4] Cite OR-17 worksheet pages per repo convention Co-Authored-By: Claude Opus 5.5 --- .../refundable_credits.yaml | 20 +++++++++---------- .../or_federal_tax_liability_subtraction.py | 3 ++- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/policyengine_us/parameters/gov/states/or/tax/income/subtractions/federal_tax_liability/refundable_credits.yaml b/policyengine_us/parameters/gov/states/or/tax/income/subtractions/federal_tax_liability/refundable_credits.yaml index 3829be75b83..402c412bdac 100644 --- a/policyengine_us/parameters/gov/states/or/tax/income/subtractions/federal_tax_liability/refundable_credits.yaml +++ b/policyengine_us/parameters/gov/states/or/tax/income/subtractions/federal_tax_liability/refundable_credits.yaml @@ -28,23 +28,23 @@ metadata: period: year label: Oregon federal tax liability subtraction refundable credits reference: - - title: ORS 316.685(1)(b) + - title: ORS 316.680(1)(b) and 316.685 href: https://www.oregonlegislature.gov/bills_laws/ors/ors316.html - - title: 2017 Publication OR-17, Federal tax worksheet, Part A, lines 6 to 8 + - title: "2017 Publication OR-17, federal tax worksheet, Part A, lines 6 to 8 #page 63" href: https://www.oregon.gov/dor/forms/FormsPubs/publication-or-17_101-431_2017.pdf#page=63 - - title: 2018 Publication OR-17, Federal tax worksheet, Part A, lines 6 to 8 + - title: "2018 Publication OR-17, federal tax worksheet, Part A, lines 6 to 8 #page 68" href: https://www.oregon.gov/dor/forms/FormsPubs/publication-or-17_101-431_2018.pdf#page=68 - - title: 2019 Publication OR-17, Federal tax worksheet, Part A, lines 6 to 8 + - title: "2019 Publication OR-17, federal tax worksheet, Part A, lines 6 to 8 #page 63" href: https://www.oregon.gov/dor/forms/FormsPubs/publication-or-17_101-431_2019.pdf#page=63 - - title: 2020 Publication OR-17, Federal tax worksheet, Part A, lines 6 to 9 + - title: "2020 Publication OR-17, federal tax worksheet, Part A, lines 6 to 9 #page 65-66" href: https://www.oregon.gov/dor/forms/FormsPubs/publication-or-17_101-431_2020.pdf#page=66 - - title: 2021 Publication OR-17, Federal tax worksheet, Part A, lines 8 to 13 + - title: "2021 Publication OR-17, federal tax worksheet, Part A, lines 2 and 8 to 13 #page 72-73" href: https://www.oregon.gov/dor/forms/FormsPubs/publication-or-17_101-431_2021.pdf#page=72 - - title: 2022 Publication OR-17, Federal tax worksheet, Part A, lines 6 to 8 + - title: "2022 Publication OR-17, federal tax worksheet, Part A, lines 6 to 8 #page 73" href: https://www.oregon.gov/dor/forms/FormsPubs/publication-or-17_101-431_2022.pdf#page=73 - - title: 2023 Publication OR-17, Federal tax worksheet, Part A, lines 6 to 8 + - title: "2023 Publication OR-17, federal tax worksheet, Part A, lines 6 to 8 #page 73" href: https://www.oregon.gov/dor/forms/FormsPubs/publication-or-17_101-431_2023.pdf#page=73 - - title: 2024 Publication OR-17, Federal tax worksheet, Part A, lines 6 to 8 + - title: "2024 Publication OR-17, federal tax worksheet, Part A, lines 6 to 8 #page 71" href: https://www.oregon.gov/dor/forms/FormsPubs/publication-or-17_101-431_2024.pdf#page=71 - - title: 2025 Publication OR-17, Federal tax worksheet, Part A, lines 6 to 9 + - title: "2025 Publication OR-17, federal tax worksheet, Part A, lines 6 to 9 #page 71-72" href: https://www.oregon.gov/dor/forms/FormsPubs/publication-or-17_101-431_2025.pdf#page=71 diff --git a/policyengine_us/variables/gov/states/or/tax/income/subtractions/or_federal_tax_liability_subtraction.py b/policyengine_us/variables/gov/states/or/tax/income/subtractions/or_federal_tax_liability_subtraction.py index 9081a942735..e2409c59d60 100644 --- a/policyengine_us/variables/gov/states/or/tax/income/subtractions/or_federal_tax_liability_subtraction.py +++ b/policyengine_us/variables/gov/states/or/tax/income/subtractions/or_federal_tax_liability_subtraction.py @@ -10,7 +10,8 @@ class or_federal_tax_liability_subtraction(Variable): reference = ( # ORS 316.680(1)(b), 316.685 and 316.695(3). "https://www.oregonlegislature.gov/bills_laws/ors/ors316.html", - # Federal tax worksheet, Part A. + # Publication OR-17, federal tax worksheet, Part A: PDF pages 71-72 + # in 2025 and 72-73 in 2021. "https://www.oregon.gov/dor/forms/FormsPubs/publication-or-17_101-431_2025.pdf#page=71", "https://www.oregon.gov/dor/forms/FormsPubs/publication-or-17_101-431_2021.pdf#page=72", ) From 9c97660168b03aa8aaf2f93a26c14d46d6e4a393 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 11 Oct 2026 12:00:28 -0400 Subject: [PATCH 4/4] Add invariants for Oregon's federal tax liability subtraction Bounds, independence from the EITC (and the ACTC outside 2021), and 1-Lipschitz responses to listed credits and federal tax, checked with Hypothesis on batches of twin tax units in one simulation per example. Co-Authored-By: Claude Opus 5.5 --- ...al_tax_liability_subtraction_invariants.py | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 policyengine_us/tests/core/test_or_federal_tax_liability_subtraction_invariants.py diff --git a/policyengine_us/tests/core/test_or_federal_tax_liability_subtraction_invariants.py b/policyengine_us/tests/core/test_or_federal_tax_liability_subtraction_invariants.py new file mode 100644 index 00000000000..a6244e1992b --- /dev/null +++ b/policyengine_us/tests/core/test_or_federal_tax_liability_subtraction_invariants.py @@ -0,0 +1,119 @@ +"""Oregon's federal tax liability subtraction follows the OR-17 worksheet. + +ORS 316.680(1)(b) and 316.685; Publication OR-17, federal tax worksheet, +Part A. The subtraction is federal tax after nonrefundable credits plus other +income taxes, less only the refundable credits the worksheet lists, floored at +zero and capped by federal AGI. These properties hold for every input: + +1. 0 <= subtraction <= min(federal tax before refundable credits, cap). +2. The earned income credit never changes it (ORS 316.685(1)(b)), and outside + 2021 neither does the additional child tax credit. +3. Raising a listed credit (the premium tax credit) by d lowers it by at most d. +4. Raising federal tax before refundable credits by d raises it by at most d. + +Each Hypothesis example evaluates a batch of tax units and their variants in +one vectorized simulation. +""" + +import numpy as np +import pytest +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_us import Simulation + +TOLERANCE = 0.01 +FILING_STATUSES = ("SINGLE", "JOINT", "HEAD_OF_HOUSEHOLD", "SEPARATE") +CREDITS = ( + "eitc", + "refundable_ctc", + "refundable_american_opportunity_credit", + "assigned_aca_ptc", + "recovery_rebate_credit", + "cdcc", +) +VARIANTS = ("base", "eitc", "refundable_ctc", "assigned_aca_ptc", "tax") + + +@st.composite +def tax_unit(draw): + return { + "filing_status": draw(st.sampled_from(FILING_STATUSES)), + "adjusted_gross_income": draw(st.integers(0, 300_000)), + "income_tax_before_refundable_credits": draw(st.integers(0, 20_000)), + "net_investment_income_tax": draw(st.integers(0, 2_000)), + "unreported_payroll_tax": draw(st.integers(0, 1_000)), + **{credit: draw(st.integers(0, 6_000)) for credit in CREDITS}, + "delta": draw(st.integers(1, 5_000)), + } + + +def _run(units, year): + situation = {"people": {}, "tax_units": {}, "households": {}} + rows = [] + for i, unit in enumerate(units): + for variant in VARIANTS: + inputs = {k: v for k, v in unit.items() if k != "delta"} + if variant == "tax": + inputs["income_tax_before_refundable_credits"] += unit["delta"] + elif variant != "base": + inputs[variant] += unit["delta"] + name = f"{i}_{variant}" + situation["people"][name] = {"age": {year: 40}} + situation["tax_units"][name] = { + "members": [name], + **{k: {year: v} for k, v in inputs.items()}, + } + situation["households"][name] = { + "members": [name], + "state_code": {year: "OR"}, + } + rows.append((i, variant)) + simulation = Simulation(situation=situation) + values = simulation.calculate("or_federal_tax_liability_subtraction", year) + result = {} + for (i, variant), value in zip(rows, values): + result.setdefault(i, {})[variant] = float(value) + # The largest cap, at zero AGI, from the simulation's own parameters. + p = ( + simulation.tax_benefit_system.parameters(f"{year}-01-01") + .gov.states["or"] + .tax.income.subtractions.federal_tax_liability.cap + ) + zero = np.array([0.0]) + cap = { + "SINGLE": p.single.calc(zero)[0], + "JOINT": p.joint.calc(zero)[0], + "HEAD_OF_HOUSEHOLD": p.head_of_household.calc(zero)[0], + "SEPARATE": p.separate.calc(zero)[0], + } + return result, cap + + +@pytest.mark.parametrize("year", [2020, 2021, 2025]) +@settings( + max_examples=4, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +@given(units=st.lists(tax_unit(), min_size=6, max_size=6)) +def test_or_federal_tax_liability_subtraction_invariants(year, units): + result, cap = _run(units, year) + for i, unit in enumerate(units): + r = result[i] + base, delta = r["base"], unit["delta"] + # 1. Bounds. + assert -TOLERANCE <= base + assert base <= unit["income_tax_before_refundable_credits"] + TOLERANCE + assert base <= cap[unit["filing_status"]] + TOLERANCE + # 2. Credits the worksheet never lists. + assert r["eitc"] == pytest.approx(base, abs=TOLERANCE) + if year == 2021: + assert base - delta - TOLERANCE <= r["refundable_ctc"] <= base + TOLERANCE + else: + assert r["refundable_ctc"] == pytest.approx(base, abs=TOLERANCE) + # 3. A listed credit lowers the subtraction by at most its increase. + assert base - delta - TOLERANCE <= r["assigned_aca_ptc"] <= base + TOLERANCE + # 4. More federal tax raises it by at most the increase. + assert base - TOLERANCE <= r["tax"] <= base + delta + TOLERANCE