From 3bfc10e82434f3dabf89fd97579b5edf2ee56346 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 11 Oct 2026 11:58:06 -0400 Subject: [PATCH 1/8] Keep Iowa's 2018 miscellaneous itemized deduction Iowa did not adopt the federal suspension of miscellaneous itemized deductions subject to the 2% floor for 2018 (2018 IA 1040 expanded instructions p.54; Schedule A lines 19-24, floor 2% of federal AGI) and conformed from 2019 (2019 instructions p.49). Main built Iowa's 2018 itemized deductions from the federal amounts, where the deduction is zero. Co-Authored-By: Claude Opus 5.5 --- changelog.d/ia-2018-misc-deduction.fixed.md | 1 + .../itemized/misc_deduction_retained.yaml | 16 ++++++++++++ .../income/ia_itemized_deductions_unit.yaml | 26 +++++++++++++++++++ .../deductions/ia_itemized_deductions_unit.py | 22 +++++++++++----- 4 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 changelog.d/ia-2018-misc-deduction.fixed.md create mode 100644 policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/misc_deduction_retained.yaml diff --git a/changelog.d/ia-2018-misc-deduction.fixed.md b/changelog.d/ia-2018-misc-deduction.fixed.md new file mode 100644 index 00000000000..751c70e6279 --- /dev/null +++ b/changelog.d/ia-2018-misc-deduction.fixed.md @@ -0,0 +1 @@ +Keep Iowa's 2018 miscellaneous itemized deduction above 2% of federal AGI, which Iowa did not suspend until 2019. diff --git a/policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/misc_deduction_retained.yaml b/policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/misc_deduction_retained.yaml new file mode 100644 index 00000000000..871c504fd7a --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/misc_deduction_retained.yaml @@ -0,0 +1,16 @@ +description: Iowa keeps the miscellaneous itemized deduction above 2% of adjusted gross income, which federal law suspends from 2018, if this is true. +values: + 2017-01-01: false + 2018-01-01: true + 2019-01-01: false +metadata: + unit: bool + period: year + label: Iowa retains the miscellaneous itemized deduction + reference: + - title: 2018 IA 1040 expanded instructions, Schedule A job expenses and miscellaneous deductions + href: https://revenue.iowa.gov/media/2228/download?inline=#page=54 + - title: 2018 IA Schedule A, lines 19 to 24 + href: https://revenue.iowa.gov/media/2298/download?inline=#page=1 + - title: 2019 IA 1040 expanded instructions, Schedule A + href: https://revenue.iowa.gov/media/2492/download?inline=#page=49 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_itemized_deductions_unit.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_itemized_deductions_unit.yaml index ad62b3d64e5..1bbc7a88f7d 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_itemized_deductions_unit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_itemized_deductions_unit.yaml @@ -39,3 +39,29 @@ filing_status: JOINT output: ia_itemized_deductions_unit: 11_000 + +# 2018 IA Schedule A lines 19 to 24: unreimbursed employee expenses and tax +# preparation fees above 2% of federal AGI, which the federal return suspends. +- name: 2018 Iowa keeps the miscellaneous deduction above 2% of federal AGI + period: 2018 + input: + unreimbursed_business_employee_expenses: 3_000 + tax_preparation_fees: 500 + adjusted_gross_income: 50_000 + real_estate_taxes: 2_000 + state_code: IA + output: + misc_deduction: 0 + # 2,000 + (3,500 - 2% * 50,000) + ia_itemized_deductions_unit: 4_500 + +- name: 2019 Iowa follows the federal suspension of the miscellaneous deduction + period: 2019 + input: + unreimbursed_business_employee_expenses: 3_000 + tax_preparation_fees: 500 + adjusted_gross_income: 50_000 + real_estate_taxes: 2_000 + state_code: IA + output: + ia_itemized_deductions_unit: 2_000 diff --git a/policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_unit.py b/policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_unit.py index 83b5c86c38b..e57d5650179 100644 --- a/policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_unit.py +++ b/policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_unit.py @@ -12,6 +12,8 @@ class ia_itemized_deductions_unit(Variable): "https://revenue.iowa.gov/media/2650/download?inline", "https://revenue.iowa.gov/sites/default/files/2023-01/2022IA1040%2841001%29.pdf", "https://revenue.iowa.gov/media/2721/download?inline", + # 2018 IA Schedule A, lines 19 to 24. + "https://revenue.iowa.gov/media/2298/download?inline=#page=1", ) defined_for = StateCode.IA @@ -28,9 +30,17 @@ def formula(tax_unit, period, parameters): if p.applies_federal: p_fed = parameters(period).gov.irs.deductions return add(tax_unit, period, p_fed.itemized_deductions) - else: - return add( - tax_unit, - period, - ["itemized_deductions_less_salt", "real_estate_taxes"], - ) + deductions = add( + tax_unit, + period, + ["itemized_deductions_less_salt", "real_estate_taxes"], + ) + if p.misc_deduction_retained: + # 2018: Iowa did not adopt the federal suspension of miscellaneous + # deductions above 2% of federal AGI (Schedule A lines 19 to 24), + # so they are added to the federal amounts, which leave them out. + p_misc = parameters(period).gov.irs.deductions.itemized.misc + expenses = tax_unit("total_misc_deductions", period) + floor = p_misc.floor * tax_unit("positive_agi", period) + deductions += max_(0, expenses - floor) + return deductions From 91f9b39d1d528fcbd1ade30a7aef81c6ef1814d6 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 23:51:10 +0000 Subject: [PATCH 2/8] Test Iowa's 2018 misc floor and 2017 single counting The PR's cases covered only expenses above the 2% floor. Add one where 2% of federal AGI exceeds the expenses (2018 IA Schedule A lines 22-24 give zero), and a 2017 case showing that Iowa takes the federal miscellaneous deduction once, since the 2018-only addition is off then. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01WR8LHcshhFmRX1625AZ5yw --- .../income/ia_itemized_deductions_unit.yaml | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_itemized_deductions_unit.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_itemized_deductions_unit.yaml index 7bda994e334..ccf3b010c3c 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_itemized_deductions_unit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_itemized_deductions_unit.yaml @@ -121,3 +121,30 @@ state_code: IA output: ia_itemized_deductions_unit: 2_000 + +- name: 2018 Iowa miscellaneous deduction is zero when 2% of federal AGI exceeds the expenses + period: 2018 + input: + filing_status: SINGLE + unreimbursed_business_employee_expenses: 3_000 + tax_preparation_fees: 500 + adjusted_gross_income: 200_000 + real_estate_taxes: 2_000 + state_code: IA + output: + # 2018 IA Schedule A lines 22 to 24: 3,500 - 2% * 200,000 = -500, so 0. + # Federal AGI is below the $266,700 single amount on the IA 104, line 8. + ia_itemized_deductions_unit: 2_000 + +- name: 2017 Iowa takes the federal miscellaneous deduction once + period: 2017 + input: + unreimbursed_business_employee_expenses: 3_000 + tax_preparation_fees: 500 + adjusted_gross_income: 50_000 + real_estate_taxes: 2_000 + state_code: IA + output: + # The federal deduction still applies in 2017: 3,500 - 2% * 50,000. + misc_deduction: 2_500 + ia_itemized_deductions_unit: 4_500 From 4ec5d02c6e7bda8c382a2d28d1d2776c363df986 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 23:51:10 +0000 Subject: [PATCH 3/8] Apply Iowa's 2018 limitation on high-income itemized deductions For 2018 Iowa defined the Internal Revenue Code as of January 1, 2015 (Iowa Code 422.3(5), 2018), so section 68 still limited itemized deductions for Iowa although federal law suspended it. The 2018 IA Schedule A line 26 sends filers with federal AGI over $160,000 to the IA 104 worksheet, which applies the pre-2018 section 68 amounts ($266,700 single, $320,000 joint, $293,350 head of household, $160,000 separate) and then removes Iowa income tax in proportion (701 IAC 41.11). Main had no 2018 Iowa limitation. Split the Schedule A total (lines 1-25) into ia_itemized_deductions_pre_limitation, add ia_itemized_deductions_reduction for IA 104 lines 1-21, and make ia_itemized_deductions_unit their difference. The rates and amounts are dated Iowa parameters under deductions/itemized/limitation, true only for 2018; the section 68(c) exclusions reuse the federal list. Also stop adding into the summed deductions array in place. Add worksheet cases on both sides of $160,000 and a Hypothesis file checking that the limitation never raises the deduction, stays within the 80% cap, starts above the applicable amount and is monotone in AGI and Iowa income tax. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01WR8LHcshhFmRX1625AZ5yw --- changelog.d/ia-2018-misc-deduction.fixed.md | 2 +- .../itemized/limitation/agi_rate.yaml | 12 ++ .../limitation/applicable_amount.yaml | 24 +++ .../limitation/itemized_deduction_rate.yaml | 12 ++ .../itemized/limitation/retained.yaml | 20 ++ .../income/ia_itemized_deductions_indiv.yaml | 35 ++++ .../ia_itemized_deductions_reduction.yaml | 181 ++++++++++++++++++ ...temized_deductions_reduction_invariants.py | 172 +++++++++++++++++ .../ia_itemized_deductions_pre_limitation.py | 53 +++++ .../ia_itemized_deductions_reduction.py | 60 ++++++ .../deductions/ia_itemized_deductions_unit.py | 39 +--- 11 files changed, 573 insertions(+), 37 deletions(-) create mode 100644 policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/limitation/agi_rate.yaml create mode 100644 policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/limitation/applicable_amount.yaml create mode 100644 policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/limitation/itemized_deduction_rate.yaml create mode 100644 policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/limitation/retained.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_itemized_deductions_reduction.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/test_ia_itemized_deductions_reduction_invariants.py create mode 100644 policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_pre_limitation.py create mode 100644 policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_reduction.py diff --git a/changelog.d/ia-2018-misc-deduction.fixed.md b/changelog.d/ia-2018-misc-deduction.fixed.md index 751c70e6279..13ce8ba8d49 100644 --- a/changelog.d/ia-2018-misc-deduction.fixed.md +++ b/changelog.d/ia-2018-misc-deduction.fixed.md @@ -1 +1 @@ -Keep Iowa's 2018 miscellaneous itemized deduction above 2% of federal AGI, which Iowa did not suspend until 2019. +Keep Iowa's 2018 miscellaneous itemized deduction above 2% of federal AGI, which Iowa did not suspend until 2019, and apply Iowa's 2018 limitation on the itemized deductions of filers with federal AGI above the IA 104 applicable amounts. diff --git a/policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/limitation/agi_rate.yaml b/policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/limitation/agi_rate.yaml new file mode 100644 index 00000000000..47f9f3a975b --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/limitation/agi_rate.yaml @@ -0,0 +1,12 @@ +description: Iowa reduces itemized deductions by this fraction of federal adjusted gross income above the applicable amount, up to the itemized deductions cap. +values: + 2018-01-01: 0.03 +metadata: + unit: /1 + period: year + label: Iowa itemized deductions limitation adjusted gross income rate + reference: + - title: 2018 IA 104 Iowa Itemized Deductions Worksheet, line 10 + href: https://revenue.iowa.gov/media/2335/download?inline=#page=1 + - title: 26 U.S. Code § 68(a)(1) (2014 edition), in effect on January 1, 2015 + href: https://www.govinfo.gov/content/pkg/USCODE-2014-title26/html/USCODE-2014-title26-subtitleA-chap1-subchapB-partI-sec68.htm diff --git a/policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/limitation/applicable_amount.yaml b/policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/limitation/applicable_amount.yaml new file mode 100644 index 00000000000..e5e9ab9b803 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/limitation/applicable_amount.yaml @@ -0,0 +1,24 @@ +description: Iowa limits the itemized deductions of filers whose federal adjusted gross income exceeds this amount, based on federal filing status. +metadata: + propagate_metadata_to_children: true + label: Iowa itemized deductions limitation applicable amount + period: year + unit: currency-USD + breakdown: + - filing_status + reference: + - title: 2018 IA 104 Iowa Itemized Deductions Worksheet, line 8 + href: https://revenue.iowa.gov/media/2335/download?inline=#page=1 + - title: Rev. Proc. 2017-58, section 3.15, applicable amounts under section 68(b) for 2018 + href: https://www.irs.gov/pub/irs-drop/rp-17-58.pdf#page=14 + +SINGLE: + 2018-01-01: 266_700 +JOINT: + 2018-01-01: 320_000 +SEPARATE: + 2018-01-01: 160_000 +HEAD_OF_HOUSEHOLD: + 2018-01-01: 293_350 +SURVIVING_SPOUSE: + 2018-01-01: 320_000 diff --git a/policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/limitation/itemized_deduction_rate.yaml b/policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/limitation/itemized_deduction_rate.yaml new file mode 100644 index 00000000000..288542624dd --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/limitation/itemized_deduction_rate.yaml @@ -0,0 +1,12 @@ +description: Iowa caps the reduction of itemized deductions at this fraction of the itemized deductions subject to the limitation. +values: + 2018-01-01: 0.8 +metadata: + unit: /1 + period: year + label: Iowa itemized deductions limitation cap rate + reference: + - title: 2018 IA 104 Iowa Itemized Deductions Worksheet, line 6 + href: https://revenue.iowa.gov/media/2335/download?inline=#page=1 + - title: 26 U.S. Code § 68(a)(2) (2014 edition), in effect on January 1, 2015 + href: https://www.govinfo.gov/content/pkg/USCODE-2014-title26/html/USCODE-2014-title26-subtitleA-chap1-subchapB-partI-sec68.htm diff --git a/policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/limitation/retained.yaml b/policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/limitation/retained.yaml new file mode 100644 index 00000000000..64121beb9a6 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/limitation/retained.yaml @@ -0,0 +1,20 @@ +description: Iowa keeps the limitation on the itemized deductions of high-income filers, which federal law suspends from 2018, if this is true. +values: + 2017-01-01: false + 2018-01-01: true + 2019-01-01: false +metadata: + unit: bool + period: year + label: Iowa retains the itemized deductions limitation + reference: + - title: Iowa Code 422.3(5) (2018), Internal Revenue Code as amended and in effect on January 1, 2015 + href: https://www.legis.iowa.gov/docs/code/2018/422.3.pdf#page=1 + - title: 2018 Iowa Acts, chapter 1161, sections 63, 66 and 69 + href: https://www.legis.iowa.gov/docs/acts/2018/CH1161.pdf#page=14 + - title: 2018 IA 1040 expanded instructions, Schedule A line 26 + href: https://revenue.iowa.gov/media/2228/download?inline=#page=55 + - title: 2018 IA 104 Iowa Itemized Deductions Worksheet + href: https://revenue.iowa.gov/media/2335/download?inline=#page=1 + - title: 2019 IA 1040 Schedule A, line 21 + href: https://revenue.iowa.gov/media/2490/download?inline=#page=1 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_itemized_deductions_indiv.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_itemized_deductions_indiv.yaml index d12afc7a38e..5bd746ec661 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_itemized_deductions_indiv.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_itemized_deductions_indiv.yaml @@ -21,3 +21,38 @@ state_code: IA output: ia_itemized_deductions_indiv: [24_000, 6_000] + +- name: 2018 spouses filing separately on a combined return split the limited total + period: 2018 + input: + people: + person1: + is_tax_unit_head: true + ia_net_income: 300_000 + real_estate_taxes: 8_000 + deductible_mortgage_interest: 14_000 + ia_withheld_income_tax: 15_000 + person2: + is_tax_unit_spouse: true + ia_net_income: 100_000 + ia_withheld_income_tax: 5_000 + tax_units: + tax_unit: + members: [person1, person2] + filing_status: JOINT + adjusted_gross_income: 400_000 + charitable_deduction: 6_000 + medical_expense_deduction: 4_000 + state_sales_tax: 0 + local_sales_tax: 0 + households: + household: + members: [person1, person2] + state_code: IA + output: + # 2018 IA 104 line 21 with the $320,000 joint amount: 30,600 (see + # ia_itemized_deductions_reduction.yaml). 2018 IA Schedule A lines 29 to + # 33 split it by net income: 300,000 / 400,000 = 75.0%, so 22,950 and + # 7,650: https://revenue.iowa.gov/media/2298/download?inline=#page=1 + ia_itemized_deductions_unit: 30_600 + ia_itemized_deductions_indiv: [22_950, 7_650] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_itemized_deductions_reduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_itemized_deductions_reduction.yaml new file mode 100644 index 00000000000..cdeee1d7d07 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_itemized_deductions_reduction.yaml @@ -0,0 +1,181 @@ +# 2018 IA 104 Iowa Itemized Deductions Worksheet: +# https://revenue.iowa.gov/media/2335/download?inline=#page=1 +# 2018 IA Schedule A line 26 sends filers with federal AGI over $160,000 to +# the worksheet: https://revenue.iowa.gov/media/2298/download?inline=#page=1 +# Line 1 is Schedule A lines 1 to 25, line 2 the Iowa income tax on federal +# Schedule A line 5a, line 4 medical, investment interest and casualty losses. + +- name: 2018 single filer at $150,000 is not limited + period: 2018 + input: + filing_status: SINGLE + adjusted_gross_income: 150_000 + real_estate_taxes: 6_000 + deductible_mortgage_interest: 9_000 + charitable_deduction: 3_000 + ia_withheld_income_tax: 0 + state_code: IA + output: + # Schedule A line 26: federal AGI is not over $160,000, so add the lines. + ia_itemized_deductions_pre_limitation: 18_000 + ia_itemized_deductions_reduction: 0 + ia_itemized_deductions_unit: 18_000 + +- name: 2018 separate filer at exactly $160,000 is not limited + period: 2018 + input: + filing_status: SEPARATE + adjusted_gross_income: 160_000 + real_estate_taxes: 6_000 + deductible_mortgage_interest: 9_000 + charitable_deduction: 3_000 + ia_withheld_income_tax: 0 + state_code: IA + output: + # IA 104 line 9: line 8 ($160,000) is not less than line 7 ($160,000). + ia_itemized_deductions_reduction: 0 + ia_itemized_deductions_unit: 18_000 + +- name: 2018 separate filer above $160,000 is limited + period: 2018 + input: + filing_status: SEPARATE + adjusted_gross_income: 170_000 + real_estate_taxes: 6_000 + deductible_mortgage_interest: 9_000 + charitable_deduction: 3_000 + ia_withheld_income_tax: 0 + state_code: IA + output: + # Lines 1, 3 and 5: 18,000; line 6: 0.8 * 18,000 = 14,400. + # Line 9: 170,000 - 160,000 = 10,000; line 10: 3% = 300; line 11: 300. + # Line 12: 18,000 - 300 = 17,700; line 2 is zero, so line 21 = 17,700. + ia_itemized_deductions_reduction: 300 + ia_itemized_deductions_unit: 17_700 + +- name: 2018 single filer above $160,000 but below $266,700 is not limited + period: 2018 + input: + filing_status: SINGLE + adjusted_gross_income: 170_000 + real_estate_taxes: 6_000 + deductible_mortgage_interest: 9_000 + charitable_deduction: 3_000 + ia_withheld_income_tax: 0 + state_code: IA + output: + # IA 104 line 9: line 8 ($266,700 for single) is not less than line 7. + ia_itemized_deductions_reduction: 0 + ia_itemized_deductions_unit: 18_000 + +- name: 2018 joint filer shares the reduction with Iowa income tax + period: 2018 + input: + filing_status: JOINT + adjusted_gross_income: 400_000 + real_estate_taxes: 8_000 + deductible_mortgage_interest: 14_000 + charitable_deduction: 6_000 + medical_expense_deduction: 4_000 + ia_withheld_income_tax: 20_000 + state_sales_tax: 0 + local_sales_tax: 0 + state_code: IA + output: + # Line 1: 8,000 + 14,000 + 6,000 + 4,000 = 32,000; line 2: 20,000; + # line 3: 52,000; line 4: 4,000 (medical); line 5: 48,000; + # line 6: 38,400; line 9: 400,000 - 320,000 = 80,000; line 10: 2,400; + # line 11: 2,400; line 12: 49,600; line 15: 49,600 - 4,000 = 45,600; + # line 17: 52,000 - 4,000 = 48,000; line 18: 95.0%; + # line 20: 20,000 * 95% = 19,000; line 21: 49,600 - 19,000 = 30,600. + ia_itemized_deductions_pre_limitation: 32_000 + ia_itemized_deductions_reduction: 1_400 + ia_itemized_deductions_unit: 30_600 + +- name: 2018 joint filer with sales tax on federal Schedule A has no Iowa income tax on line 2 + period: 2018 + input: + filing_status: JOINT + adjusted_gross_income: 400_000 + real_estate_taxes: 8_000 + deductible_mortgage_interest: 14_000 + charitable_deduction: 6_000 + medical_expense_deduction: 4_000 + ia_withheld_income_tax: 20_000 + state_sales_tax: 25_000 + local_sales_tax: 0 + state_code: IA + output: + # Federal line 5a holds the larger sales tax, so line 2 is zero. + # Line 3: 32,000; line 5: 28,000; line 6: 22,400; line 11: 2,400; + # line 12 = line 21: 32,000 - 2,400 = 29,600. + ia_itemized_deductions_reduction: 2_400 + ia_itemized_deductions_unit: 29_600 + +- name: 2018 cap of 80% of the limited deductions binds + period: 2018 + input: + filing_status: SINGLE + adjusted_gross_income: 1_000_000 + real_estate_taxes: 4_000 + deductible_mortgage_interest: 6_000 + ia_withheld_income_tax: 0 + state_code: IA + output: + # Line 5: 10,000; line 6: 8,000; line 9: 1,000,000 - 266,700 = 733,300; + # line 10: 21,999; line 11: 8,000; line 21: 10,000 - 8,000 = 2,000. + ia_itemized_deductions_reduction: 8_000 + ia_itemized_deductions_unit: 2_000 + +- name: 2018 Iowa income tax absorbs the reduction when only excluded deductions remain + period: 2018 + input: + filing_status: JOINT + adjusted_gross_income: 500_000 + medical_expense_deduction: 12_000 + investment_interest_expense: 8_000 + ia_withheld_income_tax: 10_000 + state_sales_tax: 0 + local_sales_tax: 0 + state_code: IA + output: + # Line 1: 20,000; line 3: 30,000; line 4: 12,000 + 8,000 = 20,000; + # line 5: 10,000; line 6: 8,000; line 9: 180,000; line 10: 5,400; + # line 11: 5,400; line 12: 24,600; line 15: 4,600; line 17: 10,000; + # line 18: 46.0%; line 20: 4,600; line 21: 24,600 - 4,600 = 20,000. + ia_itemized_deductions_pre_limitation: 20_000 + ia_itemized_deductions_reduction: 0 + ia_itemized_deductions_unit: 20_000 + +- name: 2018 limitation applies after the miscellaneous deduction + period: 2018 + input: + filing_status: SEPARATE + adjusted_gross_income: 200_000 + unreimbursed_business_employee_expenses: 10_000 + real_estate_taxes: 6_000 + deductible_mortgage_interest: 9_000 + ia_withheld_income_tax: 0 + state_code: IA + output: + # Schedule A line 24: 10,000 - 2% * 200,000 = 6,000; line 1: 21,000. + # Line 9: 40,000; line 10: 1,200; line 6: 16,800; line 11: 1,200. + # Line 21: 21,000 - 1,200 = 19,800. + ia_itemized_deductions_pre_limitation: 21_000 + ia_itemized_deductions_reduction: 1_200 + ia_itemized_deductions_unit: 19_800 + +- name: 2019 Iowa follows the federal suspension of the limitation + period: 2019 + input: + filing_status: SINGLE + adjusted_gross_income: 1_000_000 + real_estate_taxes: 4_000 + deductible_mortgage_interest: 6_000 + ia_withheld_income_tax: 0 + state_code: IA + output: + # 2019 IA Schedule A line 21 adds the lines with no worksheet. + ia_itemized_deductions_reduction: 0 + ia_itemized_deductions_unit: 10_000 + diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/test_ia_itemized_deductions_reduction_invariants.py b/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/test_ia_itemized_deductions_reduction_invariants.py new file mode 100644 index 00000000000..ae23d82828e --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/test_ia_itemized_deductions_reduction_invariants.py @@ -0,0 +1,172 @@ +"""Invariants of Iowa's 2018 limitation on itemized deductions. + +The 2018 IA 104 worksheet limits the 2018 IA Schedule A total of a filer whose +federal adjusted gross income exceeds the applicable amount for the federal +filing status (line 8): +https://revenue.iowa.gov/media/2335/download?inline=#page=1 +Line 11 is the smaller of 80% of the deductions subject to the limitation +(line 6) and 3% of the excess income (line 10). Lines 12 to 21 then remove +the Iowa income tax on federal Schedule A line 5a in proportion, as +701 IAC 41.11(1) does: +https://www.legis.iowa.gov/docs/iac/chapter/03-28-2018.701.41.pdf#page=10 + +Hypothesis draws deduction inputs, Iowa income tax, sales tax and federal +AGI. Each draw becomes one simulation with four tax units per case: the drawn +case; the case without miscellaneous expenses ("fixed"), whose Schedule A +total does not depend on AGI; "fixed" with higher AGI; and "fixed" with more +Iowa income tax. For every tax unit: + +1. Accounting: ia_itemized_deductions_unit is the pre-limitation total minus + the reduction, because Schedule A line 26 takes IA 104 line 21. +2. Never raises: the reduction is never negative, so the limitation never + raises the deduction, because line 11 is the smaller of two nonnegative + amounts and line 18 is at most 100%. +3. Bounded: the reduction is at most 80% of the deductions other than + medical expenses and investment interest, because line 6 caps line 11 at + 80% of line 5 and Schedule A bears only its line-1 share of line 11. +4. Threshold: no reduction when federal AGI is at most the applicable + amount, because line 9 then stops the worksheet. +5. Monotone in AGI: with the deductions fixed, higher AGI never lowers the + reduction, because line 9 grows with line 7. +6. Monotone in Iowa income tax: more Iowa income tax never raises the + reduction, because lines 15 to 20 give the Iowa income tax its share of + line 11. + +Comparisons allow ten cents: float32 spacing is under seven cents below +$1 million. +""" + +import hypothesis +import hypothesis.strategies as st +import numpy as np +import pytest + +from policyengine_us import Simulation + +YEAR = 2018 +# 2018 IA 104 line 8, by federal filing status. +APPLICABLE_AMOUNT = { + "SINGLE": 266_700, + "JOINT": 320_000, + "SEPARATE": 160_000, + "HEAD_OF_HOUSEHOLD": 293_350, + "SURVIVING_SPOUSE": 320_000, +} +# 2018 IA 104 line 6. +CAP_RATE = 0.8 +TOLERANCE = 0.1 +VARIANTS = ("drawn", "fixed", "higher_agi", "more_iowa_tax") + +amount = st.integers(min_value=0, max_value=60_000) +case_strategy = st.fixed_dictionaries( + { + "filing_status": st.sampled_from(sorted(APPLICABLE_AMOUNT)), + "agi": st.integers(min_value=0, max_value=1_500_000), + "agi_increase": st.integers(min_value=0, max_value=500_000), + "real_estate_taxes": amount, + "mortgage_interest": amount, + "charity": amount, + "medical": amount, + "investment_interest": amount, + "misc_expenses": amount, + "iowa_income_tax": st.integers(min_value=0, max_value=120_000), + "iowa_income_tax_increase": st.integers(min_value=0, max_value=60_000), + "sales_tax": amount, + } +) + + +def build_situation(cases): + people, tax_units, households = {}, {}, {} + for i, case in enumerate(cases): + for variant in VARIANTS: + key = f"{variant}_{i}" + person = f"person_{key}" + misc_expenses = case["misc_expenses"] if variant == "drawn" else 0 + iowa_income_tax = case["iowa_income_tax"] + if variant == "more_iowa_tax": + iowa_income_tax += case["iowa_income_tax_increase"] + agi = case["agi"] + if variant == "higher_agi": + agi += case["agi_increase"] + people[person] = { + "age": {YEAR: 45}, + "real_estate_taxes": {YEAR: case["real_estate_taxes"]}, + "deductible_mortgage_interest": {YEAR: case["mortgage_interest"]}, + "investment_interest_expense": {YEAR: case["investment_interest"]}, + "unreimbursed_business_employee_expenses": {YEAR: misc_expenses}, + "ia_withheld_income_tax": {YEAR: iowa_income_tax}, + } + tax_units[f"tax_unit_{key}"] = { + "members": [person], + "filing_status": {YEAR: case["filing_status"]}, + "adjusted_gross_income": {YEAR: agi}, + "charitable_deduction": {YEAR: case["charity"]}, + "medical_expense_deduction": {YEAR: case["medical"]}, + "state_sales_tax": {YEAR: case["sales_tax"]}, + "local_sales_tax": {YEAR: 0}, + } + households[f"household_{key}"] = { + "members": [person], + "state_code": {YEAR: "IA"}, + } + return {"people": people, "tax_units": tax_units, "households": households} + + +def calculate(cases): + simulation = Simulation(situation=build_situation(cases)) + shape = (len(cases), len(VARIANTS)) + return { + name: np.asarray(simulation.calculate(name, YEAR)).reshape(shape) + for name in ( + "adjusted_gross_income", + "ia_itemized_deductions_pre_limitation", + "ia_itemized_deductions_reduction", + "ia_itemized_deductions_unit", + ) + } + + +@hypothesis.settings( + max_examples=20, + deadline=None, + derandomize=True, + suppress_health_check=[hypothesis.HealthCheck.too_slow], +) +@hypothesis.given(st.lists(case_strategy, min_size=1, max_size=8)) +def test_ia_itemized_deductions_reduction_invariants(cases): + values = calculate(cases) + agi = values["adjusted_gross_income"] + pre = values["ia_itemized_deductions_pre_limitation"] + reduction = values["ia_itemized_deductions_reduction"] + unit = values["ia_itemized_deductions_unit"] + fixed, higher_agi, more_iowa_tax = ( + VARIANTS.index(variant) for variant in ("fixed", "higher_agi", "more_iowa_tax") + ) + for i, case in enumerate(cases): + excluded = case["medical"] + case["investment_interest"] + for j, variant in enumerate(VARIANTS): + context = (variant, case) + # 1. Accounting. + assert unit[i, j] == pytest.approx( + pre[i, j] - reduction[i, j], abs=TOLERANCE + ), context + # 2. Never raises. + assert reduction[i, j] >= 0, context + assert unit[i, j] <= pre[i, j] + TOLERANCE, context + # 3. Bounded by line 6. + assert ( + reduction[i, j] <= CAP_RATE * max(0, pre[i, j] - excluded) + TOLERANCE + ), context + # 4. Threshold. + if agi[i, j] <= APPLICABLE_AMOUNT[case["filing_status"]]: + assert reduction[i, j] == 0, context + # The fixed variants share one Schedule A total. + assert pre[i, higher_agi] == pytest.approx(pre[i, fixed], abs=TOLERANCE), case + assert pre[i, more_iowa_tax] == pytest.approx(pre[i, fixed], abs=TOLERANCE), ( + case + ) + # 5. Monotone in AGI. + assert reduction[i, higher_agi] >= reduction[i, fixed] - TOLERANCE, case + # 6. Monotone in Iowa income tax. + assert reduction[i, more_iowa_tax] <= reduction[i, fixed] + TOLERANCE, case diff --git a/policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_pre_limitation.py b/policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_pre_limitation.py new file mode 100644 index 00000000000..cdc1f9adb0c --- /dev/null +++ b/policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_pre_limitation.py @@ -0,0 +1,53 @@ +from policyengine_us.model_api import * + + +class ia_itemized_deductions_pre_limitation(Variable): + value_type = float + entity = TaxUnit + label = "Iowa itemized deductions for tax unit before the high-income limitation" + unit = USD + definition_period = YEAR + reference = ( + "https://revenue.iowa.gov/sites/default/files/2022-01/IA1040%2841-001%29.pdf", + "https://revenue.iowa.gov/media/2650/download?inline", + "https://revenue.iowa.gov/sites/default/files/2023-01/2022IA1040%2841001%29.pdf", + "https://revenue.iowa.gov/media/2721/download?inline", + "https://revenue.iowa.gov/taxes/tax-guidance/individual-income-tax/iowa-nonconformity-taxpayer-certainty-and-disaster-tax-relief-act-2019", + # 2018 IA Schedule A, lines 1 to 25. + "https://revenue.iowa.gov/media/2298/download?inline=#page=1", + ) + defined_for = StateCode.IA + + def formula(tax_unit, period, parameters): + """ + FROM THE 2021 INSTRUCTIONS (PAGE 46): + The $10,000 federal cap on the itemized deduction for state and + local taxes does not apply for Iowa purposes. Taxpayers may still + deduct eligible state and local taxes paid, independent of the + federal dollar limitation. + """ + # compute tax unit's itemized deductions + p = parameters(period).gov.states.ia.tax.income.deductions.itemized + if p.applies_federal: + p_fed = parameters(period).gov.irs.deductions + return add(tax_unit, period, p_fed.itemized_deductions) + deductions = add( + tax_unit, + period, + ["itemized_deductions_less_salt", "real_estate_taxes"], + ) + # Iowa did not adopt the retroactive extension of the mortgage + # insurance premium deduction for 2018 and 2019. + if p.disallowed_interest_components: + deductions = deductions - add( + tax_unit, period, p.disallowed_interest_components + ) + if p.misc_deduction_retained: + # 2018: Iowa did not adopt the federal suspension of miscellaneous + # deductions above 2% of federal AGI (Schedule A lines 19 to 24), + # so they are added to the federal amounts, which leave them out. + p_misc = parameters(period).gov.irs.deductions.itemized.misc + expenses = tax_unit("total_misc_deductions", period) + floor = p_misc.floor * tax_unit("positive_agi", period) + deductions = deductions + max_(0, expenses - floor) + return deductions diff --git a/policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_reduction.py b/policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_reduction.py new file mode 100644 index 00000000000..f9a538b8257 --- /dev/null +++ b/policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_reduction.py @@ -0,0 +1,60 @@ +from policyengine_us.model_api import * + + +class ia_itemized_deductions_reduction(Variable): + value_type = float + entity = TaxUnit + label = "Iowa itemized deductions reduction for high-income filers" + unit = USD + definition_period = YEAR + reference = ( + # 2018 IA 104 Iowa Itemized Deductions Worksheet. + "https://revenue.iowa.gov/media/2335/download?inline=#page=1", + # 701 IAC 41.11 (IAC 2/28/18), PDF pages 9-10. + "https://www.legis.iowa.gov/docs/iac/chapter/03-28-2018.701.41.pdf#page=9", + # Iowa Code 422.9(2) (2019). + "https://www.legis.iowa.gov/docs/code/2019/422.9.pdf#page=1", + ) + defined_for = StateCode.IA + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.states.ia.tax.income.deductions.itemized + if not p.limitation.retained: + return 0 + # IA 104 line 1: Iowa Schedule A lines 1 to 25. + deductions = tax_unit("ia_itemized_deductions_pre_limitation", period) + # IA 104 line 2: Iowa income tax on federal Schedule A line 5a, which + # holds state income taxes only when they exceed sales taxes. + income_taxes = add( + tax_unit, period, ["state_withheld_income_tax", "local_income_tax"] + ) + sales_taxes = add(tax_unit, period, ["state_sales_tax", "local_sales_tax"]) + iowa_income_tax = where( + income_taxes >= sales_taxes, + add(tax_unit, period, ["ia_withheld_income_tax"]), + 0, + ) + # IA 104 line 4: medical expenses, investment interest, and casualty, + # theft and gambling losses, which section 68(c) leaves unlimited. + p_fed = parameters(period).gov.irs.deductions.itemized.limitation + excluded = add(tax_unit, period, p_fed.excluded_deductions) + # IA 104 lines 3 and 5. + reducible = max_(0, deductions + iowa_income_tax - excluded) + # IA 104 lines 7 to 9, with the federal filing status. + agi = tax_unit("adjusted_gross_income", period) + filing_status = tax_unit("filing_status", period) + excess_agi = max_(0, agi - p.limitation.applicable_amount[filing_status]) + # IA 104 lines 6, 10 and 11. + reduction = min_( + p.limitation.itemized_deduction_rate * reducible, + p.limitation.agi_rate * excess_agi, + ) + # IA 104 lines 12 to 21 subtract the Iowa income tax in the share of + # line 5 that the reduction leaves (701 IAC 41.11(1)), so line 1 bears + # the rest: line 1 - line 21 = line 11 * (line 1 - line 4) / line 5. + return np.divide( + reduction * max_(0, deductions - excluded), + reducible, + out=np.zeros_like(reducible), + where=reducible > 0, + ) diff --git a/policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_unit.py b/policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_unit.py index f3f948f928d..43c365ab70f 100644 --- a/policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_unit.py +++ b/policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_unit.py @@ -12,42 +12,9 @@ class ia_itemized_deductions_unit(Variable): "https://revenue.iowa.gov/media/2650/download?inline", "https://revenue.iowa.gov/sites/default/files/2023-01/2022IA1040%2841001%29.pdf", "https://revenue.iowa.gov/media/2721/download?inline", - "https://revenue.iowa.gov/taxes/tax-guidance/individual-income-tax/iowa-nonconformity-taxpayer-certainty-and-disaster-tax-relief-act-2019", - # 2018 IA Schedule A, lines 19 to 24. + # 2018 IA Schedule A, line 26. "https://revenue.iowa.gov/media/2298/download?inline=#page=1", ) defined_for = StateCode.IA - - def formula(tax_unit, period, parameters): - """ - FROM THE 2021 INSTRUCTIONS (PAGE 46): - The $10,000 federal cap on the itemized deduction for state and - local taxes does not apply for Iowa purposes. Taxpayers may still - deduct eligible state and local taxes paid, independent of the - federal dollar limitation. - """ - # compute tax unit's itemized deductions - p = parameters(period).gov.states.ia.tax.income.deductions.itemized - if p.applies_federal: - p_fed = parameters(period).gov.irs.deductions - return add(tax_unit, period, p_fed.itemized_deductions) - deductions = add( - tax_unit, - period, - ["itemized_deductions_less_salt", "real_estate_taxes"], - ) - # Iowa did not adopt the retroactive extension of the mortgage - # insurance premium deduction for 2018 and 2019. - if p.disallowed_interest_components: - deductions = deductions - add( - tax_unit, period, p.disallowed_interest_components - ) - if p.misc_deduction_retained: - # 2018: Iowa did not adopt the federal suspension of miscellaneous - # deductions above 2% of federal AGI (Schedule A lines 19 to 24), - # so they are added to the federal amounts, which leave them out. - p_misc = parameters(period).gov.irs.deductions.itemized.misc - expenses = tax_unit("total_misc_deductions", period) - floor = p_misc.floor * tax_unit("positive_agi", period) - deductions += max_(0, expenses - floor) - return deductions + adds = ["ia_itemized_deductions_pre_limitation"] + subtracts = ["ia_itemized_deductions_reduction"] From 2b020dab11e0d0878372fd27dc6471bfe26e2439 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 23:58:46 +0000 Subject: [PATCH 4/8] Log the two Iowa itemized limitation readings as conflict candidates The 2018 limitation takes two readings another source could contradict. 701 IAC 41.11 ties the Iowa reduction to the years section 68 applies federally, while Iowa Code 422.3(5) and the 2018 IA 104 apply it in 2018. The IA 104 also rounds the line 18 Iowa income tax percentage, while 422.9(2)(a) and rule 41.11(1) subtract the exact share. The model follows the statute in both. Record them in candidates.yaml until they reach the shared register; no marker until then, since markers must name registered entries. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01WR8LHcshhFmRX1625AZ5yw --- source_conflicts/candidates.yaml | 73 ++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index 025bb5dd42b..236487b0147 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -2458,6 +2458,79 @@ candidates: state: ruled note: Ruled statute-first in October 2026; the rule requires a model change after confirming that the exclusion reaches alternate tax. +- id: us-ia:itemized-limitation-2018-federal-suspension + program: income-tax + years: + from: 2018 + to: 2018 + type: tax + question: Did Iowa limit the 2018 itemized deductions of high-income filers, although section 68(f) suspended the + federal limitation for 2018 and 701 IAC 41.11 ties the Iowa reduction to the years it applies federally? + sources: + - kind: statute + citation: Iowa Code 422.3(5) (2018) + url: https://www.legis.iowa.gov/docs/code/2018/422.3.pdf#page=1 + - kind: statute + citation: Iowa Code 422.9(2) (2018) + url: https://www.legis.iowa.gov/docs/code/2018/422.9.pdf#page=1 + - kind: regulation + citation: 701 IAC 41.11 (IAC 2/28/18) + url: https://www.legis.iowa.gov/docs/iac/chapter/03-28-2018.701.41.pdf#page=9 + - kind: worksheet + citation: 2018 IA 104 Iowa Itemized Deductions Worksheet + url: https://revenue.iowa.gov/media/2335/download?inline=#page=1 + readings: + statute: For 2018 the Internal Revenue Code is the one in effect on January 1, 2015, so section 68 still limits the + deductions Iowa Code 422.9(2) allows; the 2018 IA 104 and instructions apply the limitation. + other: Rule 41.11 applies the reduction "for the same tax years that the provision applies for federal income tax + purposes"; read against federal law as amended, which suspends section 68 for 2018, Iowa would not limit 2018 + deductions. + example: A joint filer with federal AGI of $400,000, $32,000 of Schedule A deductions ($4,000 of them medical) and + $20,000 of Iowa income tax deducts $30,600 under the IA 104, versus $32,000 with no limitation. + pe_us: + follows: statute + code: + - policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/limitation/retained.yaml + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10101 + state: unreviewed + note: Read through Iowa's own definition of the Internal Revenue Code, under which section 68 applies to 2018, the + rule agrees with the statute and the form. +- id: us-ia:itemized-limitation-iowa-tax-share-rounding + program: income-tax + years: + from: 2018 + to: 2018 + type: tax + question: Does Iowa subtract the Iowa income tax at its exact share of the limited deductions, or at the IA 104 line + 18 percentage rounded to the nearest tenth of a percent? + sources: + - kind: statute + citation: Iowa Code 422.9(2)(a) (2018) + url: https://www.legis.iowa.gov/docs/code/2018/422.9.pdf#page=1 + - kind: regulation + citation: 701 IAC 41.5(1) and 41.11(1) (IAC 2/28/18) + url: https://www.legis.iowa.gov/docs/iac/chapter/03-28-2018.701.41.pdf#page=10 + - kind: worksheet + citation: 2018 IA 104 Iowa Itemized Deductions Worksheet, line 18 + url: https://revenue.iowa.gov/media/2335/download?inline=#page=1 + readings: + statute: Iowa subtracts "the deduction for Iowa income taxes", the Iowa income tax still included after the + limitation; rule 41.11(1) divides without rounding, and rule 41.5(1) subtracts it "to the extent" included. + other: The IA 104 enters line 15 divided by line 17 "to the nearest tenth of a percent" before applying it to the + Iowa income tax. + example: A joint filer with federal AGI of $400,000, $30,000 of Schedule A deductions ($4,000 of them medical) and + $20,000 of Iowa income tax enters 94.8% and deducts $28,640; the exact share, 94.78%, gives $28,643.48. + pe_us: + follows: statute + code: + - policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_reduction.py + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10101 + state: unreviewed + note: Rounding moves the result by at most 0.05% of the Iowa income tax, but it can raise line 21 above line 1. A + joint filer at $500,000 with only $20,000 of medical expenses and investment interest and $9,700 of Iowa income + tax enters 44.3% and gets $20,002.90; the exact share gives $20,000. - id: us-ia:low-income-exemption-claimant-income program: income-tax years: From 1306cde6c65615ee51acccb431830abb125d1766 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 20:37:40 +0000 Subject: [PATCH 5/8] Cite the 2018 Iowa Code for the 2018 itemized limitation The reduction variable cited Iowa Code 422.9(2) from the 2019 edition, but the limitation applies only to 2018 and the conflict candidate cites the 2018 edition. Point both at the same year. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01LwQq6FnFJqpsH5LpjC4KZf --- .../deductions/ia_itemized_deductions_reduction.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_reduction.py b/policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_reduction.py index f9a538b8257..53d90cab817 100644 --- a/policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_reduction.py +++ b/policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_reduction.py @@ -12,8 +12,8 @@ class ia_itemized_deductions_reduction(Variable): "https://revenue.iowa.gov/media/2335/download?inline=#page=1", # 701 IAC 41.11 (IAC 2/28/18), PDF pages 9-10. "https://www.legis.iowa.gov/docs/iac/chapter/03-28-2018.701.41.pdf#page=9", - # Iowa Code 422.9(2) (2019). - "https://www.legis.iowa.gov/docs/code/2019/422.9.pdf#page=1", + # Iowa Code 422.9(2) (2018). + "https://www.legis.iowa.gov/docs/code/2018/422.9.pdf#page=1", ) defined_for = StateCode.IA From 25739641759f85754e9c110e173d56dffdc138d9 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 5 Nov 2026 00:46:06 -0500 Subject: [PATCH 6/8] Follow IA 104 line 18's rounding and test the worksheet directly Iowa Code 422.9(2) and rule 41.11 leave the rounding of the Iowa income tax share open, so ia_itemized_deductions_reduction now enters the share to a tenth of a percent, as line 18 does. Add head of household and surviving spouse cases and two line 18 cases. Draw Hypothesis AGI relative to each applicable amount, allow for rounding in the properties it can move, and add a property that restates the worksheet from the draw. Cite 2018 Iowa Acts chapter 1161 on the federal suspension candidate and the Iowa Code on misc_deduction_retained. Co-Authored-By: Claude Opus 5.5 --- .../itemized/misc_deduction_retained.yaml | 4 + .../ia_itemized_deductions_reduction.yaml | 79 +++++++++++++++++ ...temized_deductions_reduction_invariants.py | 88 ++++++++++++++----- .../ia_itemized_deductions_reduction.py | 20 +++-- source_conflicts/candidates.yaml | 24 +++-- 5 files changed, 179 insertions(+), 36 deletions(-) diff --git a/policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/misc_deduction_retained.yaml b/policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/misc_deduction_retained.yaml index 871c504fd7a..e562cacd79f 100644 --- a/policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/misc_deduction_retained.yaml +++ b/policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/misc_deduction_retained.yaml @@ -8,6 +8,10 @@ metadata: period: year label: Iowa retains the miscellaneous itemized deduction reference: + - title: Iowa Code 422.3(5) (2018), Internal Revenue Code as amended and in effect on January 1, 2015 + href: https://www.legis.iowa.gov/docs/code/2018/422.3.pdf#page=1 + - title: Iowa Code 422.9(2) (2018), miscellaneous expenses deductible under the Internal Revenue Code + href: https://www.legis.iowa.gov/docs/code/2018/422.9.pdf#page=1 - title: 2018 IA 1040 expanded instructions, Schedule A job expenses and miscellaneous deductions href: https://revenue.iowa.gov/media/2228/download?inline=#page=54 - title: 2018 IA Schedule A, lines 19 to 24 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_itemized_deductions_reduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_itemized_deductions_reduction.yaml index cdeee1d7d07..d9565730263 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_itemized_deductions_reduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_itemized_deductions_reduction.yaml @@ -68,8 +68,41 @@ ia_itemized_deductions_reduction: 0 ia_itemized_deductions_unit: 18_000 +- name: 2018 head of household above $293,350 is limited + period: 2018 + input: + filing_status: HEAD_OF_HOUSEHOLD + adjusted_gross_income: 303_350 + real_estate_taxes: 6_000 + deductible_mortgage_interest: 9_000 + charitable_deduction: 3_000 + ia_withheld_income_tax: 0 + state_code: IA + output: + # Line 9: 303,350 - 293,350 = 10,000; line 10: 300; line 6: 14,400. + # Line 11: 300; line 21: 18,000 - 300 = 17,700. + ia_itemized_deductions_reduction: 300 + ia_itemized_deductions_unit: 17_700 + +- name: 2018 surviving spouse above $320,000 is limited + period: 2018 + input: + filing_status: SURVIVING_SPOUSE + adjusted_gross_income: 330_000 + real_estate_taxes: 6_000 + deductible_mortgage_interest: 9_000 + charitable_deduction: 3_000 + ia_withheld_income_tax: 0 + state_code: IA + output: + # Line 9: 330,000 - 320,000 = 10,000; line 10: 300; line 6: 14,400. + # Line 11: 300; line 21: 18,000 - 300 = 17,700. + ia_itemized_deductions_reduction: 300 + ia_itemized_deductions_unit: 17_700 + - name: 2018 joint filer shares the reduction with Iowa income tax period: 2018 + absolute_error_margin: 0.01 input: filing_status: JOINT adjusted_gross_income: 400_000 @@ -92,6 +125,51 @@ ia_itemized_deductions_reduction: 1_400 ia_itemized_deductions_unit: 30_600 +- name: 2018 line 18 rounds the Iowa income tax share to a tenth of a percent + period: 2018 + absolute_error_margin: 0.01 + input: + filing_status: JOINT + adjusted_gross_income: 400_000 + real_estate_taxes: 8_000 + deductible_mortgage_interest: 14_000 + charitable_deduction: 4_000 + medical_expense_deduction: 4_000 + ia_withheld_income_tax: 20_000 + state_sales_tax: 0 + local_sales_tax: 0 + state_code: IA + output: + # Line 1: 30,000; line 2: 20,000; line 3: 50,000; line 4: 4,000; + # line 5: 46,000; line 6: 36,800; line 10: 2,400; line 11: 2,400; + # line 12: 47,600; line 15: 43,600; line 17: 46,000; + # line 18: 94.78% enters as 94.8%; line 20: 18,960; + # line 21: 47,600 - 18,960 = 28,640. The exact share gives 28,643.48. + ia_itemized_deductions_pre_limitation: 30_000 + ia_itemized_deductions_reduction: 1_360 + ia_itemized_deductions_unit: 28_640 + +- name: 2018 rounding on line 18 can raise line 21 above line 1 + period: 2018 + absolute_error_margin: 0.01 + input: + filing_status: JOINT + adjusted_gross_income: 500_000 + medical_expense_deduction: 12_000 + investment_interest_expense: 8_000 + ia_withheld_income_tax: 9_700 + state_sales_tax: 0 + local_sales_tax: 0 + state_code: IA + output: + # Line 1: 20,000; line 3: 29,700; line 4: 20,000; line 5: 9,700; + # line 6: 7,760; line 10: 5,400; line 11: 5,400; line 12: 24,300; + # line 15: 4,300; line 17: 9,700; line 18: 44.33% enters as 44.3%; + # line 20: 4,297.10; line 21: 24,300 - 4,297.10 = 20,002.90. + ia_itemized_deductions_pre_limitation: 20_000 + ia_itemized_deductions_reduction: -2.90 + ia_itemized_deductions_unit: 20_002.90 + - name: 2018 joint filer with sales tax on federal Schedule A has no Iowa income tax on line 2 period: 2018 input: @@ -129,6 +207,7 @@ - name: 2018 Iowa income tax absorbs the reduction when only excluded deductions remain period: 2018 + absolute_error_margin: 0.01 input: filing_status: JOINT adjusted_gross_income: 500_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/test_ia_itemized_deductions_reduction_invariants.py b/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/test_ia_itemized_deductions_reduction_invariants.py index ae23d82828e..705ea3ae292 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/test_ia_itemized_deductions_reduction_invariants.py +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/test_ia_itemized_deductions_reduction_invariants.py @@ -7,23 +7,28 @@ Line 11 is the smaller of 80% of the deductions subject to the limitation (line 6) and 3% of the excess income (line 10). Lines 12 to 21 then remove the Iowa income tax on federal Schedule A line 5a in proportion, as -701 IAC 41.11(1) does: +701 IAC 41.11(1) does, at a share that line 18 rounds to a tenth of a percent: https://www.legis.iowa.gov/docs/iac/chapter/03-28-2018.701.41.pdf#page=10 -Hypothesis draws deduction inputs, Iowa income tax, sales tax and federal -AGI. Each draw becomes one simulation with four tax units per case: the drawn +So each bound below allows ROUNDING of the Iowa income tax on line 2, the most +that line 18's rounding moves line 21 from the exact share. + +Hypothesis draws deduction inputs, Iowa income tax, sales tax and federal AGI +relative to the applicable amount, so every filing status gets limited cases. +Each draw becomes one simulation with four tax units per case: the drawn case; the case without miscellaneous expenses ("fixed"), whose Schedule A total does not depend on AGI; "fixed" with higher AGI; and "fixed" with more Iowa income tax. For every tax unit: 1. Accounting: ia_itemized_deductions_unit is the pre-limitation total minus the reduction, because Schedule A line 26 takes IA 104 line 21. -2. Never raises: the reduction is never negative, so the limitation never - raises the deduction, because line 11 is the smaller of two nonnegative - amounts and line 18 is at most 100%. -3. Bounded: the reduction is at most 80% of the deductions other than - medical expenses and investment interest, because line 6 caps line 11 at - 80% of line 5 and Schedule A bears only its line-1 share of line 11. +2. Never raises: apart from rounding, the reduction is never negative, so + the limitation never raises the deduction, because line 11 is the smaller + of two nonnegative amounts and the exact share is at most 100%. +3. Bounded: apart from rounding, the reduction is at most 80% of the + deductions other than medical expenses and investment interest, because + line 6 caps line 11 at 80% of line 5 and Schedule A bears only its line-1 + share of line 11. 4. Threshold: no reduction when federal AGI is at most the applicable amount, because line 9 then stops the worksheet. 5. Monotone in AGI: with the deductions fixed, higher AGI never lowers the @@ -31,6 +36,9 @@ 6. Monotone in Iowa income tax: more Iowa income tax never raises the reduction, because lines 15 to 20 give the Iowa income tax its share of line 11. +7. Worksheet: the reduction stays within rounding of line 11 times the + Schedule A share, (line 1 - line 4) / line 5, with every line restated + here from the draw. Comparisons allow ten cents: float32 spacing is under seven cents below $1 million. @@ -54,6 +62,10 @@ } # 2018 IA 104 line 6. CAP_RATE = 0.8 +# 2018 IA 104 line 10. +AGI_RATE = 0.03 +# 2018 IA 104 line 18 rounds to a tenth of a percent, half of which is 0.05%. +ROUNDING = 0.0005 TOLERANCE = 0.1 VARIANTS = ("drawn", "fixed", "higher_agi", "more_iowa_tax") @@ -61,7 +73,7 @@ case_strategy = st.fixed_dictionaries( { "filing_status": st.sampled_from(sorted(APPLICABLE_AMOUNT)), - "agi": st.integers(min_value=0, max_value=1_500_000), + "agi_offset": st.integers(min_value=-50_000, max_value=1_000_000), "agi_increase": st.integers(min_value=0, max_value=500_000), "real_estate_taxes": amount, "mortgage_interest": amount, @@ -76,6 +88,18 @@ ) +def base_agi(case): + return max(0, APPLICABLE_AMOUNT[case["filing_status"]] + case["agi_offset"]) + + +def line_2(case, variant): + """Iowa income tax on federal Schedule A line 5a, as the draw sets it.""" + iowa_income_tax = case["iowa_income_tax"] + if variant == "more_iowa_tax": + iowa_income_tax += case["iowa_income_tax_increase"] + return iowa_income_tax if iowa_income_tax >= case["sales_tax"] else 0 + + def build_situation(cases): people, tax_units, households = {}, {}, {} for i, case in enumerate(cases): @@ -86,7 +110,7 @@ def build_situation(cases): iowa_income_tax = case["iowa_income_tax"] if variant == "more_iowa_tax": iowa_income_tax += case["iowa_income_tax_increase"] - agi = case["agi"] + agi = base_agi(case) if variant == "higher_agi": agi += case["agi_increase"] people[person] = { @@ -145,28 +169,50 @@ def test_ia_itemized_deductions_reduction_invariants(cases): ) for i, case in enumerate(cases): excluded = case["medical"] + case["investment_interest"] + applicable_amount = APPLICABLE_AMOUNT[case["filing_status"]] for j, variant in enumerate(VARIANTS): context = (variant, case) + # Line 2 and the most that rounding moves the result. + iowa_tax = line_2(case, variant) + rounding = ROUNDING * iowa_tax # 1. Accounting. assert unit[i, j] == pytest.approx( pre[i, j] - reduction[i, j], abs=TOLERANCE ), context - # 2. Never raises. - assert reduction[i, j] >= 0, context - assert unit[i, j] <= pre[i, j] + TOLERANCE, context - # 3. Bounded by line 6. + # 2. Never raises, apart from rounding. + assert reduction[i, j] >= -rounding - TOLERANCE, context + assert unit[i, j] <= pre[i, j] + rounding + TOLERANCE, context + # 3. Bounded by line 6, apart from rounding. assert ( - reduction[i, j] <= CAP_RATE * max(0, pre[i, j] - excluded) + TOLERANCE + reduction[i, j] + <= CAP_RATE * max(0, pre[i, j] - excluded) + rounding + TOLERANCE ), context # 4. Threshold. - if agi[i, j] <= APPLICABLE_AMOUNT[case["filing_status"]]: + if agi[i, j] <= applicable_amount: assert reduction[i, j] == 0, context + # 7. Worksheet: lines 3, 5, 6, 9, 10 and 11, then the exact share. + line_5 = max(0, pre[i, j] + iowa_tax - excluded) + line_11 = min( + CAP_RATE * line_5, AGI_RATE * max(0, agi[i, j] - applicable_amount) + ) + exact = line_11 * max(0, pre[i, j] - excluded) / line_5 if line_5 else 0 + assert reduction[i, j] == pytest.approx(exact, abs=rounding + TOLERANCE), ( + context + ) # The fixed variants share one Schedule A total. assert pre[i, higher_agi] == pytest.approx(pre[i, fixed], abs=TOLERANCE), case assert pre[i, more_iowa_tax] == pytest.approx(pre[i, fixed], abs=TOLERANCE), ( case ) - # 5. Monotone in AGI. - assert reduction[i, higher_agi] >= reduction[i, fixed] - TOLERANCE, case - # 6. Monotone in Iowa income tax. - assert reduction[i, more_iowa_tax] <= reduction[i, fixed] + TOLERANCE, case + # 5. Monotone in AGI, apart from rounding at each of the two points. + fixed_rounding = ROUNDING * line_2(case, "fixed") + assert ( + reduction[i, higher_agi] + >= reduction[i, fixed] - 2 * fixed_rounding - TOLERANCE + ), case + # 6. Monotone in Iowa income tax, apart from rounding at each point. + more_rounding = ROUNDING * line_2(case, "more_iowa_tax") + assert ( + reduction[i, more_iowa_tax] + <= reduction[i, fixed] + fixed_rounding + more_rounding + TOLERANCE + ), case diff --git a/policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_reduction.py b/policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_reduction.py index 53d90cab817..7f31c928942 100644 --- a/policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_reduction.py +++ b/policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_reduction.py @@ -50,11 +50,17 @@ def formula(tax_unit, period, parameters): p.limitation.agi_rate * excess_agi, ) # IA 104 lines 12 to 21 subtract the Iowa income tax in the share of - # line 5 that the reduction leaves (701 IAC 41.11(1)), so line 1 bears - # the rest: line 1 - line 21 = line 11 * (line 1 - line 4) / line 5. - return np.divide( - reduction * max_(0, deductions - excluded), - reducible, - out=np.zeros_like(reducible), - where=reducible > 0, + # line 5 that the reduction leaves. Line 18 enters that share to the + # nearest tenth of a percent; neither Iowa Code 422.9(2) nor 701 IAC + # 41.11(1) says how to round it. So line 1 - line 21 = + # line 11 - line 2 * (1 - line 18), which can fall slightly below 0. + line_18 = np.round( + np.divide( + reducible - reduction, + reducible, + out=np.ones_like(reducible), + where=reducible > 0, + ), + 3, ) + return reduction - iowa_income_tax * (1 - line_18) diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index 60eed530205..a40fbc63fb3 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -2534,12 +2534,18 @@ candidates: - kind: regulation citation: 701 IAC 41.11 (IAC 2/28/18) url: https://www.legis.iowa.gov/docs/iac/chapter/03-28-2018.701.41.pdf#page=9 + - kind: statute + citation: 2018 Iowa Acts, chapter 1161, sections 58, 63, 66 and 69 + url: https://www.legis.iowa.gov/docs/acts/2018/CH1161.pdf#page=14 - kind: worksheet citation: 2018 IA 104 Iowa Itemized Deductions Worksheet url: https://revenue.iowa.gov/media/2335/download?inline=#page=1 readings: - statute: For 2018 the Internal Revenue Code is the one in effect on January 1, 2015, so section 68 still limits the - deductions Iowa Code 422.9(2) allows; the 2018 IA 104 and instructions apply the limitation. + statute: For 2018 Iowa Code 422.3(5) defines the Internal Revenue Code as the one in effect on January 1, 2015, so + section 68 still limits the deductions Iowa Code 422.9(2) allows. In 2018 Iowa Acts chapter 1161, the legislature + moved that date only from 2019 (section 69) and adopted named Pub. L. 115-97 sections for 2018 (sections 58 and + 63), none of them section 11045 or 11046. The Iowa Department of Revenue applies the limitation in the 2018 IA 104 + and instructions. other: Rule 41.11 applies the reduction "for the same tax years that the provision applies for federal income tax purposes"; read against federal law as amended, which suspends section 68 for 2018, Iowa would not limit 2018 deductions. @@ -2552,8 +2558,8 @@ candidates: prs: - https://github.com/PolicyEngine/policyengine-us/pull/10101 state: unreviewed - note: Read through Iowa's own definition of the Internal Revenue Code, under which section 68 applies to 2018, the - rule agrees with the statute and the form. + note: Under Iowa's own definition of the Internal Revenue Code, section 68 applies to 2018, so the rule agrees with + the statute and the form. - id: us-ia:itemized-limitation-iowa-tax-share-rounding program: income-tax years: @@ -2573,14 +2579,15 @@ candidates: citation: 2018 IA 104 Iowa Itemized Deductions Worksheet, line 18 url: https://revenue.iowa.gov/media/2335/download?inline=#page=1 readings: - statute: Iowa subtracts "the deduction for Iowa income taxes", the Iowa income tax still included after the - limitation; rule 41.11(1) divides without rounding, and rule 41.5(1) subtracts it "to the extent" included. + statute: Iowa Code 422.9(2)(a) subtracts "the deduction for Iowa income taxes" and doesn't say how much of it stays + deducted after the overall reduction. Rule 41.11(1)'s worksheet gives the share with no rounding instruction (line + 6), and rule 41.5(1) subtracts the tax "to the extent" included. other: The IA 104 enters line 15 divided by line 17 "to the nearest tenth of a percent" before applying it to the Iowa income tax. example: A joint filer with federal AGI of $400,000, $30,000 of Schedule A deductions ($4,000 of them medical) and $20,000 of Iowa income tax enters 94.8% and deducts $28,640; the exact share, 94.78%, gives $28,643.48. pe_us: - follows: statute + follows: gap-fill code: - policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_reduction.py prs: @@ -2588,7 +2595,8 @@ candidates: state: unreviewed note: Rounding moves the result by at most 0.05% of the Iowa income tax, but it can raise line 21 above line 1. A joint filer at $500,000 with only $20,000 of medical expenses and investment interest and $9,700 of Iowa income - tax enters 44.3% and gets $20,002.90; the exact share gives $20,000. + tax enters 44.3% and gets $20,002.90; the exact share gives $20,000. The statute and the rule leave the rounding + open, so the model follows the form. - id: us-ia:low-income-exemption-claimant-income program: income-tax years: From 2a61eebd686e598c324e56a5d3a0dce5d6500d9f Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 5 Nov 2026 06:38:58 -0500 Subject: [PATCH 7/8] Round Iowa's 2018 line 18 half up in float64, and tighten citations np.round rounds an exact half to even, so a 93.25% share entered as 93.2% and missed the IA 104 by $20 on $20,000 of Iowa income tax. Lines 3 to 11 now run in float64 and line 18 rounds a half up, with a YAML case on the half. Chapter 1161 section 58 applies the section 179 allowance as Pub. L. 115-97 section 13101 amended it, so the retained parameter, the candidate and the reading cite sections 58, 63, 66 and 69 in the same words. Rule 41.11(1) requires Form 41-104, and the rounding candidate now says so. Co-Authored-By: Claude Opus 5.5 --- .../itemized/limitation/retained.yaml | 2 +- .../ia_itemized_deductions_reduction.yaml | 21 ++++++++++++++++ ...temized_deductions_reduction_invariants.py | 7 +++--- .../ia_itemized_deductions_reduction.py | 24 ++++++++++--------- source_conflicts/candidates.yaml | 16 +++++++------ 5 files changed, 48 insertions(+), 22 deletions(-) diff --git a/policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/limitation/retained.yaml b/policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/limitation/retained.yaml index 64121beb9a6..438b363bc0a 100644 --- a/policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/limitation/retained.yaml +++ b/policyengine_us/parameters/gov/states/ia/tax/income/deductions/itemized/limitation/retained.yaml @@ -10,7 +10,7 @@ metadata: reference: - title: Iowa Code 422.3(5) (2018), Internal Revenue Code as amended and in effect on January 1, 2015 href: https://www.legis.iowa.gov/docs/code/2018/422.3.pdf#page=1 - - title: 2018 Iowa Acts, chapter 1161, sections 63, 66 and 69 + - title: 2018 Iowa Acts, chapter 1161, sections 58, 63, 66 and 69 href: https://www.legis.iowa.gov/docs/acts/2018/CH1161.pdf#page=14 - title: 2018 IA 1040 expanded instructions, Schedule A line 26 href: https://revenue.iowa.gov/media/2228/download?inline=#page=55 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_itemized_deductions_reduction.yaml b/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_itemized_deductions_reduction.yaml index d9565730263..efaa2011ac1 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_itemized_deductions_reduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/ia_itemized_deductions_reduction.yaml @@ -170,6 +170,27 @@ ia_itemized_deductions_reduction: -2.90 ia_itemized_deductions_unit: 20_002.90 +- name: 2018 line 18 rounds an exact half up + period: 2018 + absolute_error_margin: 0.01 + input: + filing_status: JOINT + adjusted_gross_income: 410_000 + real_estate_taxes: 10_000 + deductible_mortgage_interest: 10_000 + ia_withheld_income_tax: 20_000 + state_sales_tax: 0 + local_sales_tax: 0 + state_code: IA + output: + # Line 1: 20,000; line 2: 20,000; line 3: 40,000; line 4: 0; line 5: 40,000; + # line 6: 32,000; line 9: 90,000; line 10: 2,700; line 11: 2,700; + # line 12: 37,300; line 15: 37,300; line 17: 40,000; + # line 18: 93.25% enters as 93.3%; line 20: 18,660; + # line 21: 37,300 - 18,660 = 18,640. + ia_itemized_deductions_reduction: 1_360 + ia_itemized_deductions_unit: 18_640 + - name: 2018 joint filer with sales tax on federal Schedule A has no Iowa income tax on line 2 period: 2018 input: diff --git a/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/test_ia_itemized_deductions_reduction_invariants.py b/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/test_ia_itemized_deductions_reduction_invariants.py index 705ea3ae292..53a9372ea5d 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/test_ia_itemized_deductions_reduction_invariants.py +++ b/policyengine_us/tests/policy/baseline/gov/states/ia/tax/income/test_ia_itemized_deductions_reduction_invariants.py @@ -7,7 +7,8 @@ Line 11 is the smaller of 80% of the deductions subject to the limitation (line 6) and 3% of the excess income (line 10). Lines 12 to 21 then remove the Iowa income tax on federal Schedule A line 5a in proportion, as -701 IAC 41.11(1) does, at a share that line 18 rounds to a tenth of a percent: +701 IAC 41.11(1) does, at a share that line 18 enters to the nearest tenth of +a percent: https://www.legis.iowa.gov/docs/iac/chapter/03-28-2018.701.41.pdf#page=10 So each bound below allows ROUNDING of the Iowa income tax on line 2, the most @@ -37,8 +38,8 @@ reduction, because lines 15 to 20 give the Iowa income tax its share of line 11. 7. Worksheet: the reduction stays within rounding of line 11 times the - Schedule A share, (line 1 - line 4) / line 5, with every line restated - here from the draw. + Schedule A share, (line 1 - line 4) / line 5, with line 1 and AGI taken + from the model and lines 2 to 11 restated here from the draw. Comparisons allow ten cents: float32 spacing is under seven cents below $1 million. diff --git a/policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_reduction.py b/policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_reduction.py index 7f31c928942..9acd938930f 100644 --- a/policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_reduction.py +++ b/policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_itemized_deductions_reduction.py @@ -38,12 +38,15 @@ def formula(tax_unit, period, parameters): # theft and gambling losses, which section 68(c) leaves unlimited. p_fed = parameters(period).gov.irs.deductions.itemized.limitation excluded = add(tax_unit, period, p_fed.excluded_deductions) - # IA 104 lines 3 and 5. - reducible = max_(0, deductions + iowa_income_tax - excluded) + # IA 104 lines 3 and 5. Lines 3 to 11 run in float64, so float32 error + # can't move line 18 across a half. + reducible = max_(0, deductions + iowa_income_tax - excluded).astype(np.float64) # IA 104 lines 7 to 9, with the federal filing status. agi = tax_unit("adjusted_gross_income", period) filing_status = tax_unit("filing_status", period) - excess_agi = max_(0, agi - p.limitation.applicable_amount[filing_status]) + excess_agi = max_( + 0, agi - p.limitation.applicable_amount[filing_status] + ).astype(np.float64) # IA 104 lines 6, 10 and 11. reduction = min_( p.limitation.itemized_deduction_rate * reducible, @@ -54,13 +57,12 @@ def formula(tax_unit, period, parameters): # nearest tenth of a percent; neither Iowa Code 422.9(2) nor 701 IAC # 41.11(1) says how to round it. So line 1 - line 21 = # line 11 - line 2 * (1 - line 18), which can fall slightly below 0. - line_18 = np.round( - np.divide( - reducible - reduction, - reducible, - out=np.ones_like(reducible), - where=reducible > 0, - ), - 3, + share = np.divide( + reducible - reduction, + reducible, + out=np.ones_like(reducible), + where=reducible > 0, ) + # A half rounds up; the offset absorbs float64 error. + line_18 = np.floor(share * 1_000 + 0.5 + 1e-9) / 1_000 return reduction - iowa_income_tax * (1 - line_18) diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index a40fbc63fb3..d605a51114d 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -2543,9 +2543,9 @@ candidates: readings: statute: For 2018 Iowa Code 422.3(5) defines the Internal Revenue Code as the one in effect on January 1, 2015, so section 68 still limits the deductions Iowa Code 422.9(2) allows. In 2018 Iowa Acts chapter 1161, the legislature - moved that date only from 2019 (section 69) and adopted named Pub. L. 115-97 sections for 2018 (sections 58 and - 63), none of them section 11045 or 11046. The Iowa Department of Revenue applies the limitation in the 2018 IA 104 - and instructions. + moved that date only from 2019 (section 69). For 2018, it applied named sections of Pub. L. 115-97 (section 63) + and the section 179 allowance as section 13101 amended it (section 58), none of them section 11045 or 11046. The + Iowa Department of Revenue applies the limitation in the 2018 IA 104 and instructions. other: Rule 41.11 applies the reduction "for the same tax years that the provision applies for federal income tax purposes"; read against federal law as amended, which suspends section 68 for 2018, Iowa would not limit 2018 deductions. @@ -2580,8 +2580,9 @@ candidates: url: https://revenue.iowa.gov/media/2335/download?inline=#page=1 readings: statute: Iowa Code 422.9(2)(a) subtracts "the deduction for Iowa income taxes" and doesn't say how much of it stays - deducted after the overall reduction. Rule 41.11(1)'s worksheet gives the share with no rounding instruction (line - 6), and rule 41.5(1) subtracts the tax "to the extent" included. + deducted after the overall reduction. Rule 41.11(1) requires high-income filers to complete Form 41-104. The copy + of the form it prints gives the share with no rounding instruction (line 6), and rule 41.5(1) subtracts the tax + "to the extent" included. other: The IA 104 enters line 15 divided by line 17 "to the nearest tenth of a percent" before applying it to the Iowa income tax. example: A joint filer with federal AGI of $400,000, $30,000 of Schedule A deductions ($4,000 of them medical) and @@ -2595,8 +2596,9 @@ candidates: state: unreviewed note: Rounding moves the result by at most 0.05% of the Iowa income tax, but it can raise line 21 above line 1. A joint filer at $500,000 with only $20,000 of medical expenses and investment interest and $9,700 of Iowa income - tax enters 44.3% and gets $20,002.90; the exact share gives $20,000. The statute and the rule leave the rounding - open, so the model follows the form. + tax enters 44.3% and gets $20,002.90; the exact share gives $20,000. The statute and the rule's copy of the form + leave the rounding open, and the rule requires the form, so the model follows the 2018 form. The form gives no tie + rule, so the model rounds a half up: a 93.25% share enters as 93.3%. - id: us-ia:low-income-exemption-claimant-income program: income-tax years: From 5459765bf2f91d2f4ddf76708a8506f87dc6727e Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 5 Nov 2026 07:06:46 -0500 Subject: [PATCH 8/8] Keep the Iowa rounding note in plain YAML The colon in the note broke the candidates file for the source-conflict tests. Co-Authored-By: Claude Opus 5.5 --- source_conflicts/candidates.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index 114c249cb9d..77c7ef56302 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -2637,7 +2637,7 @@ candidates: joint filer at $500,000 with only $20,000 of medical expenses and investment interest and $9,700 of Iowa income tax enters 44.3% and gets $20,002.90; the exact share gives $20,000. The statute and the rule's copy of the form leave the rounding open, and the rule requires the form, so the model follows the 2018 form. The form gives no tie - rule, so the model rounds a half up: a 93.25% share enters as 93.3%. + rule, so the model rounds a half up, and a 93.25% share enters as 93.3%. - id: us-ia:low-income-exemption-claimant-income program: income-tax years: