From 762645e82a7beb1070bb7a685fd70a3a9f4f4098 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 20 Oct 2026 16:02:57 -0400 Subject: [PATCH 1/7] Document dependent role inference and preserve adult dependent examples --- .../tax-unit-role-documentation.changed.md | 1 + .../tax_unit/is_tax_unit_dependent.yaml | 137 ++++++++++++++++++ .../irs/tax_unit/tax_unit_count_dependents.py | 8 + .../tax_unit/is_tax_unit_dependent.py | 8 + .../tax_unit/tax_unit_dependents.py | 8 + 5 files changed, 162 insertions(+) create mode 100644 changelog.d/tax-unit-role-documentation.changed.md create mode 100644 policyengine_us/tests/policy/baseline/household/demographic/tax_unit/is_tax_unit_dependent.yaml diff --git a/changelog.d/tax-unit-role-documentation.changed.md b/changelog.d/tax-unit-role-documentation.changed.md new file mode 100644 index 00000000000..86c5aa723d0 --- /dev/null +++ b/changelog.d/tax-unit-role-documentation.changed.md @@ -0,0 +1 @@ +Clarify tax-unit dependent and spouse inference for callers and add explicit-role regression examples. diff --git a/policyengine_us/tests/policy/baseline/household/demographic/tax_unit/is_tax_unit_dependent.yaml b/policyengine_us/tests/policy/baseline/household/demographic/tax_unit/is_tax_unit_dependent.yaml new file mode 100644 index 00000000000..a195825359e --- /dev/null +++ b/policyengine_us/tests/policy/baseline/household/demographic/tax_unit/is_tax_unit_dependent.yaml @@ -0,0 +1,137 @@ +# Explicit dependency preserves adult dependents despite default spouse inference. +# Eligibility and state amounts are independently specified in the sources below. +# 35 ILCS 5/204(b)(6), (c): $2,425 per dependent in 2023. +# https://www.ilga.gov/Documents/legislation/ilcs/documents/003500050K204.htm +- name: Illinois keeps an explicitly claimed nonstudent dependent at age 18. + period: 2023 + input: + people: + parent: + age: 40 + employment_income: 50000 + child: + age: 18 + is_full_time_student: false + is_tax_unit_dependent: true + tax_units: + unit: + members: [parent, child] + filing_status: SINGLE + households: + household: + members: [parent, child] + state_code: IL + output: + is_tax_unit_head: [true, false] + is_tax_unit_spouse: [false, false] + is_tax_unit_dependent: [false, true] + is_qualifying_child_dependent: [false, true] + tax_unit_dependents: 1 + il_dependent_exemption: 2425 + +# 2023 California Form 540, lines 7 and 10: $144 personal + $446 dependent. +# https://www.ftb.ca.gov/forms/2023/2023-540-booklet.pdf#page=40 +- name: California keeps an explicitly claimed full-time student at age 23. + period: 2023 + input: + people: + parent: + age: 40 + employment_income: 50000 + child: + age: 23 + employment_income: 5000 + is_full_time_student: true + is_tax_unit_dependent: true + tax_units: + unit: + members: [parent, child] + filing_status: SINGLE + households: + household: + members: [parent, child] + state_code: CA + output: + is_tax_unit_spouse: [false, false] + is_qualifying_child_dependent: [false, true] + tax_unit_dependents: 1 + # Single personal credit $144 + dependent credit $446. + ca_exemptions: 590 + +# 2023 Utah Code 59-10-1018: $1,941 exemption component per dependent. +# https://le.utah.gov/xcode/Title59/Chapter10/C59-10-S1018_2023050320230503.pdf#page=1 +- name: Utah keeps an explicitly claimed zero-income qualifying relative at age 23. + period: 2023 + input: + people: + parent: + age: 40 + employment_income: 60000 + child: + age: 23 + is_full_time_student: false + is_tax_unit_dependent: true + tax_units: + unit: + members: [parent, child] + filing_status: SINGLE + households: + household: + members: [parent, child] + state_code: UT + output: + is_tax_unit_spouse: [false, false] + is_qualifying_child_dependent: [false, false] + is_qualifying_relative_dependent: [false, true] + tax_unit_dependents: 1 + ut_personal_exemption: 1941 + +# 2023 New York IT-201 instructions, line 36: $1,000 per dependent. +# https://www.tax.ny.gov/forms/html-instructions/2023/it/it201i-2023.htm#step-5 +- name: New York includes an adult qualifying relative in its dependent exemption. + period: 2023 + input: + people: + parent: + age: 40 + employment_income: 60000 + child: + age: 23 + is_full_time_student: false + is_tax_unit_dependent: true + tax_units: + unit: + members: [parent, child] + filing_status: HEAD_OF_HOUSEHOLD + households: + household: + members: [parent, child] + state_code: NY + output: + tax_unit_child_dependents: 1 + ny_exemptions: 1000 + +# Arizona Revised Statutes 43-1073.01(B)(2): $25 per dependent age 17 or older. +# https://www.azleg.gov/ars/43/01073-01.htm +- name: Arizona expressly gives the other-dependent tier to an 18-year-old. + period: 2023 + input: + people: + parent: + age: 40 + employment_income: 60000 + child: + age: 18 + is_full_time_student: false + is_tax_unit_dependent: true + tax_units: + unit: + members: [parent, child] + filing_status: HEAD_OF_HOUSEHOLD + households: + household: + members: [parent, child] + state_code: AZ + output: + tax_unit_dependents: 1 + az_dependent_tax_credit_potential: 25 diff --git a/policyengine_us/variables/gov/irs/tax_unit/tax_unit_count_dependents.py b/policyengine_us/variables/gov/irs/tax_unit/tax_unit_count_dependents.py index 38f0fd09f3f..b70f6fdcd42 100644 --- a/policyengine_us/variables/gov/irs/tax_unit/tax_unit_count_dependents.py +++ b/policyengine_us/variables/gov/irs/tax_unit/tax_unit_count_dependents.py @@ -6,5 +6,13 @@ class tax_unit_count_dependents(Variable): entity = TaxUnit label = "Number of dependents" definition_period = YEAR + documentation = ( + "The number of members with is_tax_unit_dependent set or inferred as true. " + "Tax-unit membership alone does not establish dependency. Without explicit " + "roles, a member aged 18 or older can be inferred as the spouse. Supplying " + "filing_status alone, including SINGLE or HEAD_OF_HOUSEHOLD, does not prevent " + "spouse inference. Supply is_tax_unit_dependent for the dependent, or provide " + "complete tax_unit_role_input roles for every tax-unit member." + ) adds = ["is_tax_unit_dependent"] diff --git a/policyengine_us/variables/household/demographic/tax_unit/is_tax_unit_dependent.py b/policyengine_us/variables/household/demographic/tax_unit/is_tax_unit_dependent.py index aedb24f4e8a..ee2715260aa 100644 --- a/policyengine_us/variables/household/demographic/tax_unit/is_tax_unit_dependent.py +++ b/policyengine_us/variables/household/demographic/tax_unit/is_tax_unit_dependent.py @@ -6,6 +6,14 @@ class is_tax_unit_dependent(Variable): entity = Person label = "Is a dependent in the tax unit" definition_period = YEAR + documentation = ( + "Whether this person is a dependent in their tax unit. Tax-unit membership " + "alone does not establish dependency. Without explicit roles, a member aged " + "18 or older can be inferred as the spouse. Supplying filing_status alone, " + "including SINGLE or HEAD_OF_HOUSEHOLD, does not prevent spouse inference. " + "Supply is_tax_unit_dependent for the dependent, or provide complete " + "tax_unit_role_input roles for every tax-unit member." + ) def formula(person, period, parameters): head = person("is_tax_unit_head", period) diff --git a/policyengine_us/variables/household/demographic/tax_unit/tax_unit_dependents.py b/policyengine_us/variables/household/demographic/tax_unit/tax_unit_dependents.py index 02ef8f47562..2fd0c6eb9d0 100644 --- a/policyengine_us/variables/household/demographic/tax_unit/tax_unit_dependents.py +++ b/policyengine_us/variables/household/demographic/tax_unit/tax_unit_dependents.py @@ -6,5 +6,13 @@ class tax_unit_dependents(Variable): entity = TaxUnit label = "Number of dependents in the tax unit" definition_period = YEAR + documentation = ( + "The number of members with is_tax_unit_dependent set or inferred as true. " + "Tax-unit membership alone does not establish dependency. Without explicit " + "roles, a member aged 18 or older can be inferred as the spouse. Supplying " + "filing_status alone, including SINGLE or HEAD_OF_HOUSEHOLD, does not prevent " + "spouse inference. Supply is_tax_unit_dependent for the dependent, or provide " + "complete tax_unit_role_input roles for every tax-unit member." + ) adds = ["is_tax_unit_dependent"] From 5bcfce1256018520ca556dcd4605fb86b9811bdd Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 20 Oct 2026 16:07:22 -0400 Subject: [PATCH 2/7] Add explicit California municipal bond interest source input --- ...alifornia-municipal-bond-interest.added.md | 1 + ..._out_of_state_municipal_bond_interest.yaml | 107 ++++++++++++++++++ .../gov/states/ca/tax/income/ca_additions.py | 1 + ...ca_out_of_state_municipal_bond_interest.py | 28 +++++ .../interest/tax_exempt_interest_income.py | 8 ++ 5 files changed, 145 insertions(+) create mode 100644 changelog.d/california-municipal-bond-interest.added.md create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/ca_out_of_state_municipal_bond_interest.yaml create mode 100644 policyengine_us/variables/gov/states/ca/tax/income/ca_out_of_state_municipal_bond_interest.py diff --git a/changelog.d/california-municipal-bond-interest.added.md b/changelog.d/california-municipal-bond-interest.added.md new file mode 100644 index 00000000000..3978521eb64 --- /dev/null +++ b/changelog.d/california-municipal-bond-interest.added.md @@ -0,0 +1 @@ +Add an optional California out-of-state municipal bond interest input and document the zero-addback default. diff --git a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/ca_out_of_state_municipal_bond_interest.yaml b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/ca_out_of_state_municipal_bond_interest.yaml new file mode 100644 index 00000000000..1493a78a84c --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/ca_out_of_state_municipal_bond_interest.yaml @@ -0,0 +1,107 @@ +# FTB Publication 1001: 2023 page 7 and 2025 page 8, Non-California bonds. +# CA AGI equals federal AGI plus the interest from other states' bonds. +- name: California bonds have no addback without an out-of-state amount in 2023 + period: 2023 + input: + state_code: CA + age: 40 + employment_income: 100_000 + tax_exempt_interest_income: 50_000 + output: + ca_out_of_state_municipal_bond_interest: 0 + adjusted_gross_income: 100_000 + ca_additions: 0 + ca_agi: 100_000 + +- name: Mixed holdings add only the out-of-state share in 2023 + period: 2023 + input: + state_code: CA + age: 40 + employment_income: 100_000 + tax_exempt_interest_income: 50_000 + ca_out_of_state_municipal_bond_interest: 12_500 + output: + adjusted_gross_income: 100_000 + ca_additions: 12_500 + ca_agi: 112_500 + +- name: All other-state municipal bond interest is added back in 2023 + period: 2023 + input: + state_code: CA + age: 40 + employment_income: 100_000 + tax_exempt_interest_income: 50_000 + ca_out_of_state_municipal_bond_interest: 50_000 + output: + adjusted_gross_income: 100_000 + ca_additions: 50_000 + ca_agi: 150_000 + +- name: California bonds have no addback with an explicit zero in 2025 + period: 2025 + input: + state_code: CA + age: 40 + employment_income: 100_000 + tax_exempt_interest_income: 50_000 + ca_out_of_state_municipal_bond_interest: 0 + output: + adjusted_gross_income: 100_000 + ca_additions: 0 + ca_agi: 100_000 + +- name: Mixed holdings add only the out-of-state share in 2025 + period: 2025 + input: + state_code: CA + age: 40 + employment_income: 100_000 + tax_exempt_interest_income: 50_000 + ca_out_of_state_municipal_bond_interest: 12_500 + output: + adjusted_gross_income: 100_000 + ca_additions: 12_500 + ca_agi: 112_500 + +- name: All other-state municipal bond interest is added back in 2025 + period: 2025 + input: + state_code: CA + age: 40 + employment_income: 100_000 + tax_exempt_interest_income: 50_000 + ca_out_of_state_municipal_bond_interest: 50_000 + output: + adjusted_gross_income: 100_000 + ca_additions: 50_000 + ca_agi: 150_000 + +- name: Tax unit sums each member's out-of-state interest alongside other additions + period: 2025 + input: + people: + head: + age: 40 + employment_income: 60_000 + tax_exempt_interest_income: 20_000 + ca_out_of_state_municipal_bond_interest: 5_000 + spouse: + age: 40 + employment_income: 40_000 + tax_exempt_interest_income: 30_000 + ca_out_of_state_municipal_bond_interest: 7_500 + tax_units: + tax_unit: + members: [head, spouse] + filing_status: JOINT + ca_other_additions: 1_000 + households: + household: + members: [head, spouse] + state_code: CA + output: + adjusted_gross_income: 100_000 + ca_additions: 13_500 + ca_agi: 113_500 diff --git a/policyengine_us/variables/gov/states/ca/tax/income/ca_additions.py b/policyengine_us/variables/gov/states/ca/tax/income/ca_additions.py index 4a3d173fefa..944e27d50e9 100644 --- a/policyengine_us/variables/gov/states/ca/tax/income/ca_additions.py +++ b/policyengine_us/variables/gov/states/ca/tax/income/ca_additions.py @@ -13,4 +13,5 @@ class ca_additions(Variable): "ca_hsa_addition", "ca_educator_expense_addition", "ca_other_additions", + "ca_out_of_state_municipal_bond_interest", ] diff --git a/policyengine_us/variables/gov/states/ca/tax/income/ca_out_of_state_municipal_bond_interest.py b/policyengine_us/variables/gov/states/ca/tax/income/ca_out_of_state_municipal_bond_interest.py new file mode 100644 index 00000000000..29e86f9e1b9 --- /dev/null +++ b/policyengine_us/variables/gov/states/ca/tax/income/ca_out_of_state_municipal_bond_interest.py @@ -0,0 +1,28 @@ +from policyengine_us.model_api import * + + +class ca_out_of_state_municipal_bond_interest(Variable): + value_type = float + entity = Person + label = "Municipal bond interest taxable in California from other states" + unit = USD + definition_period = YEAR + default_value = 0 + min_value = 0 + documentation = ( + "Nonnegative portion of tax_exempt_interest_income from state or local " + "bonds issued outside California, added to California AGI on Schedule " + "CA (540), Part I, Section A, line 2, column C under R&TC 17143. " + "Include this amount in tax_exempt_interest_income too: it is a subset " + "of that total, not additional income, and does not increase federal " + "AGI. The default is zero, so absent issuer-source data all tax-exempt " + "interest is treated as California-source for this adjustment. " + "California state and local bond interest remains exempt under " + "R&TC 17133." + ) + reference = ( + "https://leginfo.legislature.ca.gov/faces/codes_displaySection.xhtml?lawCode=RTC§ionNum=17143", + "https://leginfo.legislature.ca.gov/faces/codes_displaySection.xhtml?lawCode=RTC§ionNum=17133", + "https://www.ftb.ca.gov/forms/2023/2023-1001-publication.pdf#page=7", + "https://www.ftb.ca.gov/forms/2025/2025-1001-publication.pdf#page=8", + ) diff --git a/policyengine_us/variables/household/income/person/interest/tax_exempt_interest_income.py b/policyengine_us/variables/household/income/person/interest/tax_exempt_interest_income.py index abdea0775c5..bfb5d5347b9 100644 --- a/policyengine_us/variables/household/income/person/interest/tax_exempt_interest_income.py +++ b/policyengine_us/variables/household/income/person/interest/tax_exempt_interest_income.py @@ -7,5 +7,13 @@ class tax_exempt_interest_income(Variable): label = "tax-exempt interest income" unit = USD definition_period = YEAR + documentation = ( + "Interest income exempt from federal income tax. For California income " + "tax, this is assumed to be from California state or local bonds unless " + "ca_out_of_state_municipal_bond_interest is supplied. Enter the portion " + "from other states' municipal bonds in that input as well as in this " + "total; it is a subset of this income, not additional income. Without " + "issuer-source data, the default California addback is zero." + ) reference = "https://www.law.cornell.edu/uscode/text/26/subtitle-A/chapter-1/subchapter-B/part-III" uprating = "calibration.gov.irs.soi.tax_exempt_interest_income" From 65c0a4c325880e2b109ac1b2775151cc0d27bad7 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 20 Oct 2026 16:08:14 -0400 Subject: [PATCH 3/7] Document automatic Alaska dividend calculations and overrides --- ...ka-pfd-comparison-documentation.changed.md | 1 + docs/validation/alaska-pfd.md | 65 +++++++++++++++++++ .../gov/states/ak/dor/ak_energy_relief.py | 13 ++++ .../ak/dor/ak_permanent_fund_dividend.py | 12 ++++ 4 files changed, 91 insertions(+) create mode 100644 changelog.d/alaska-pfd-comparison-documentation.changed.md create mode 100644 docs/validation/alaska-pfd.md diff --git a/changelog.d/alaska-pfd-comparison-documentation.changed.md b/changelog.d/alaska-pfd-comparison-documentation.changed.md new file mode 100644 index 00000000000..edd2ef6d8f4 --- /dev/null +++ b/changelog.d/alaska-pfd-comparison-documentation.changed.md @@ -0,0 +1 @@ +Document automatic Alaska PFD and energy relief calculations, person-level overrides, and the 2022 payment split. diff --git a/docs/validation/alaska-pfd.md b/docs/validation/alaska-pfd.md new file mode 100644 index 00000000000..b9034e715c7 --- /dev/null +++ b/docs/validation/alaska-pfd.md @@ -0,0 +1,65 @@ +# Alaska Permanent Fund Dividend in model comparisons + +PolicyEngine automatically calculates `ak_permanent_fund_dividend` and +`ak_energy_relief` for each person whose household is in Alaska. These amounts +are calculated benefits, rather than imputed income or fixed default inputs. +Current Alaska residence is an eligibility proxy: the model does not check an +application, prior-year residence, or actual receipt. Supplying wages and other +known income does not disable either calculation. + +The regular PFD is included in federal gross income for nondependent people. +A dependent child's PFD is not automatically added to the parent's adjusted +gross income (AGI). Tax-unit membership alone does not determine dependent +status; supply explicit roles or dependent status when constructing a household. +The regular dividend appears on [2022 Schedule 1, line +8g](https://www.irs.gov/pub/irs-prior/f1040s1--2022.pdf#page=1). + +## Override calculated payments + +Supply either variable as an input on the person to replace its calculated +amount. To exclude both transfers from a controlled comparison, set both to zero +for **every person**. Overriding only `ak_permanent_fund_dividend` leaves energy +relief in place. Overriding only tax-unit AGI leaves the underlying person income +and benefit amounts unchanged. + +This complete situation retains Alaska residence while excluding both payments: + +```yaml +people: + person: + age: {2022: 40} + employment_income: {2022: 50000} + ak_permanent_fund_dividend: {2022: 0} + ak_energy_relief: {2022: 0} +tax_units: + tax_unit: + members: [person] +households: + household: + members: [person] + state_code: {2022: AK} +``` + +With only the two payment inputs removed, this person's calculated 2022 regular +PFD is $2,622, energy relief is $662, and federal AGI is $52,622. With both inputs +set to zero as above, federal AGI is $50,000. + +## The 2022 regular dividend and energy relief split + +Alaska paid $3,284 in total: $2,621.81 of regular PFD and $662.19 of supplemental +energy relief. PolicyEngine rounds these 2022 components to $2,622 and $662, +respectively. Putting the full $3,284 in `ak_permanent_fund_dividend` would mix +the components and increase the modeled taxable amount. The split is described +in [Alaska DOR's February 17, 2023 +release](https://dor.alaska.gov/docs/default-source/press-releases/2023_02_17-dor-response-to-irs-guidance-on-2022-pfd.pdf#page=1). + +The model excludes the separate energy payment from federal gross income. For +2022, this follows [IRS IR-2023-23, including its Alaska +footnote](https://www.irs.gov/newsroom/irs-issues-guidance-on-state-tax-payments-to-help-taxpayers): +the IRS said it would not challenge exclusion of the supplemental energy +payment, while the regular PFD remained taxable. This was a 2022 administrative +nonchallenge policy, rather than a statutory determination for every recipient. +An exclusion under IRC 139 depends on a qualifying disaster and the connection +between the payment and that disaster; see [Notice 2023-56, section +3.04](https://www.irs.gov/pub/irs-drop/n-23-56.pdf#page=6). This model convention +does not establish those facts or extend the 2022 IRS policy to other years. diff --git a/policyengine_us/variables/gov/states/ak/dor/ak_energy_relief.py b/policyengine_us/variables/gov/states/ak/dor/ak_energy_relief.py index d369bb4bdd8..845c9a3efc3 100644 --- a/policyengine_us/variables/gov/states/ak/dor/ak_energy_relief.py +++ b/policyengine_us/variables/gov/states/ak/dor/ak_energy_relief.py @@ -5,6 +5,19 @@ class ak_energy_relief(Variable): value_type = float entity = Person label = "Alaska One Time Energy Relief" + documentation = ( + "Energy relief calculated automatically for each Alaska resident, " + "using current residence as an eligibility proxy without checking an " + "application, prior-year residence, or actual receipt. Supplying " + "other income does not disable the calculation. Override this " + "person-level variable directly; excluding both Alaska transfers " + "requires also overriding ak_permanent_fund_dividend for each person. " + "The rounded 2022 energy payment is $662, separate from the $2,622 " + "regular PFD. Its exclusion from federal gross income follows the " + "IRS's 2022 nonchallenge convention, which does not establish a " + "statutory disaster-relief exclusion for every recipient or year. " + "See docs/validation/alaska-pfd.md." + ) unit = USD definition_period = YEAR reference = "https://pfd.alaska.gov" diff --git a/policyengine_us/variables/gov/states/ak/dor/ak_permanent_fund_dividend.py b/policyengine_us/variables/gov/states/ak/dor/ak_permanent_fund_dividend.py index 38490f2a0b5..b07a5fa303d 100644 --- a/policyengine_us/variables/gov/states/ak/dor/ak_permanent_fund_dividend.py +++ b/policyengine_us/variables/gov/states/ak/dor/ak_permanent_fund_dividend.py @@ -5,6 +5,18 @@ class ak_permanent_fund_dividend(Variable): value_type = float entity = Person label = "Alaska Permanent Fund Dividend" + documentation = ( + "Regular PFD calculated automatically for each Alaska resident, using " + "current residence as an eligibility proxy without checking an " + "application, prior-year residence, or actual receipt. Supplying wages " + "or other income does not disable this calculation. Supply this " + "person-level variable to override it; to exclude both Alaska " + "transfers, also supply ak_energy_relief as zero for every person. " + "A dependent child's PFD is not automatically included in a parent's " + "AGI. In 2022 this variable is the rounded $2,622 taxable regular " + "dividend; the separate $662 energy payment follows the IRS's 2022 " + "nonchallenge convention. See docs/validation/alaska-pfd.md." + ) unit = USD definition_period = YEAR reference = "https://pfd.alaska.gov" From b1849a3a8cba9ceb76d01b2c3bf50f5468588cb6 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 20 Oct 2026 16:11:25 -0400 Subject: [PATCH 4/7] Add additive pre-rebate state income tax comparison outputs --- .../state-income-tax-before-rebates.added.md | 1 + docs/_toc.yml | 2 + docs/gov/states/index.md | 3 + docs/validation/state-rebates.md | 133 ++++++ docs/validation/taxsim.md | 18 +- .../state_income_tax_rebate_adjustments.yaml | 30 ++ ...te_income_tax_before_one_time_rebates.yaml | 82 ++++ .../state_income_tax_rebate_adjustment.yaml | 396 ++++++++++++++++++ .../income/az_income_tax_rebate_adjustment.py | 21 + .../income/co_income_tax_rebate_adjustment.py | 21 + .../income/ct_income_tax_rebate_adjustment.py | 21 + .../income/de_income_tax_rebate_adjustment.py | 21 + .../income/ga_income_tax_rebate_adjustment.py | 21 + .../income/hi_income_tax_rebate_adjustment.py | 21 + .../income/id_income_tax_rebate_adjustment.py | 21 + .../income/il_income_tax_rebate_adjustment.py | 21 + .../income/in_income_tax_rebate_adjustment.py | 21 + .../income/ma_income_tax_rebate_adjustment.py | 24 ++ .../income/me_income_tax_rebate_adjustment.py | 21 + .../income/mt_income_tax_rebate_adjustment.py | 16 + .../income/nm_income_tax_rebate_adjustment.py | 25 ++ .../income/ny_income_tax_rebate_adjustment.py | 21 + .../income/ri_income_tax_rebate_adjustment.py | 21 + .../income/sc_income_tax_rebate_adjustment.py | 33 ++ ...tate_income_tax_before_one_time_rebates.py | 20 + .../state_income_tax_rebate_adjustment.py | 18 + .../income/va_income_tax_rebate_adjustment.py | 34 ++ 27 files changed, 1086 insertions(+), 1 deletion(-) create mode 100644 changelog.d/state-income-tax-before-rebates.added.md create mode 100644 docs/validation/state-rebates.md create mode 100644 policyengine_us/parameters/gov/states/household/state_income_tax_rebate_adjustments.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/tax/income/state_income_tax_before_one_time_rebates.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/tax/income/state_income_tax_rebate_adjustment.yaml create mode 100644 policyengine_us/variables/gov/states/az/tax/income/az_income_tax_rebate_adjustment.py create mode 100644 policyengine_us/variables/gov/states/co/tax/income/co_income_tax_rebate_adjustment.py create mode 100644 policyengine_us/variables/gov/states/ct/tax/income/ct_income_tax_rebate_adjustment.py create mode 100644 policyengine_us/variables/gov/states/de/tax/income/de_income_tax_rebate_adjustment.py create mode 100644 policyengine_us/variables/gov/states/ga/tax/income/ga_income_tax_rebate_adjustment.py create mode 100644 policyengine_us/variables/gov/states/hi/tax/income/hi_income_tax_rebate_adjustment.py create mode 100644 policyengine_us/variables/gov/states/id/tax/income/id_income_tax_rebate_adjustment.py create mode 100644 policyengine_us/variables/gov/states/il/tax/income/il_income_tax_rebate_adjustment.py create mode 100644 policyengine_us/variables/gov/states/in/tax/income/in_income_tax_rebate_adjustment.py create mode 100644 policyengine_us/variables/gov/states/ma/tax/income/ma_income_tax_rebate_adjustment.py create mode 100644 policyengine_us/variables/gov/states/me/tax/income/me_income_tax_rebate_adjustment.py create mode 100644 policyengine_us/variables/gov/states/mt/tax/income/mt_income_tax_rebate_adjustment.py create mode 100644 policyengine_us/variables/gov/states/nm/tax/income/nm_income_tax_rebate_adjustment.py create mode 100644 policyengine_us/variables/gov/states/ny/tax/income/ny_income_tax_rebate_adjustment.py create mode 100644 policyengine_us/variables/gov/states/ri/tax/income/ri_income_tax_rebate_adjustment.py create mode 100644 policyengine_us/variables/gov/states/sc/tax/income/sc_income_tax_rebate_adjustment.py create mode 100644 policyengine_us/variables/gov/states/tax/income/state_income_tax_before_one_time_rebates.py create mode 100644 policyengine_us/variables/gov/states/tax/income/state_income_tax_rebate_adjustment.py create mode 100644 policyengine_us/variables/gov/states/va/tax/income/va_income_tax_rebate_adjustment.py diff --git a/changelog.d/state-income-tax-before-rebates.added.md b/changelog.d/state-income-tax-before-rebates.added.md new file mode 100644 index 00000000000..de40a0425b4 --- /dev/null +++ b/changelog.d/state-income-tax-before-rebates.added.md @@ -0,0 +1 @@ +Add state income tax rebate adjustments and a pre-rebate comparison output that preserves ordinary credits and existing tax calculations. diff --git a/docs/_toc.yml b/docs/_toc.yml index b6496e344f2..373cc1b7f45 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -61,6 +61,8 @@ parts: - caption: Validation chapters: - file: validation/taxsim + - file: validation/state-rebates + - file: validation/alaska-pfd - caption: Usage chapters: - file: usage/microsimulation diff --git a/docs/gov/states/index.md b/docs/gov/states/index.md index 5f18abe7bbd..c5a73d68e67 100644 --- a/docs/gov/states/index.md +++ b/docs/gov/states/index.md @@ -1,3 +1,6 @@ # States States administer tax and benefit programs, which we show here. + +For model comparisons, see [state rebate attribution and pre-rebate liability](../../validation/state-rebates.md) +and [Alaska Permanent Fund Dividend inputs and overrides](../../validation/alaska-pfd.md). diff --git a/docs/validation/state-rebates.md b/docs/validation/state-rebates.md new file mode 100644 index 00000000000..913e9ebc08b --- /dev/null +++ b/docs/validation/state-rebates.md @@ -0,0 +1,133 @@ +# State rebates in model comparisons + +PolicyEngine attributes one-time state rebates to the tax year whose return +determines eligibility and amount, even when checks arrive in a later calendar +year. Rebates included in state credit totals reduce `state_income_tax` and +increase household net income in that eligibility year. This attribution +convention does not describe the payment calendar. Some standalone rebate +formulas can calculate an amount after the rebate ends; use the dated credit +total to establish whether that amount enters tax or net income. Do not add +back every positive raw rebate column in every year. Massachusetts premium pay +is also modeled through state refundable credits. A payment modeled solely as a +benefit would affect household net income without reducing state income tax. + +For comparison, restore only the rebate amounts that actually reduced that +year's reported state tax, including liability limits on nonrefundable credits. +Ordinary recurring refundable credits remain part of liability. +`state_income_tax_before_refundable_credits` removes those ordinary credits too, +so it is not a pre-rebate liability measure. Colorado Cash Back and the residual +TABOR sales tax refund are separate mechanisms; count each only through its +stated aggregation path and eligibility year. + +## Comparison outputs + +Two tax-unit outputs provide this reconciliation: + +```text +state_income_tax_before_one_time_rebates + = state_income_tax + state_income_tax_rebate_adjustment +``` + +`state_income_tax_rebate_adjustment` restores the registered rebate amounts +actually included in the year's modeled state tax. The comparison liability +retains ordinary credits, including refundable credits such as state EITCs. +It can therefore be negative. Neither output changes canonical +`state_income_tax`, existing credit totals, or household net income. + +The [rebate registry](https://github.com/PolicyEngine/policyengine-us/blob/main/policyengine_us/parameters/gov/states/household/state_income_tax_rebate_adjustments.yaml) +identifies the state effects within the comparison's scope. A registry entry +does not establish a payment in every year: each state effect checks the +state's dated credit list and the calculated amounts, including formula dates +and amount gates. For example, a positive raw `il_income_tax_rebate` after 2021 +is excluded from Illinois's refundable-credit list and contributes zero to the +adjustment. Amounts that are zero contribute zero even if a dated list retains +the variable. + +Despite the comparison output's name, its scope includes Colorado's residual +TABOR sales tax refund, which is a recurring statutory mechanism. It also +includes Massachusetts essential employee premium pay and Maine's 2025 +affordability payment, because these payments currently reduce modeled state +income tax through refundable-credit aggregation. + +This is an additive reconciliation with all existing filing choices and other +credit bases held fixed. It does not rerun taxes under a counterfactual policy +with the rebates removed. In particular: + +- Massachusetts Chapter 62F uses liability after other refundable credits, + including premium pay, as its base. The adjustment restores the included + amounts while retaining that calculated 62F base. +- Montana's separate-versus-joint filing election is determined before its + 2021 rebate by model assumption. The comparison retains that election. +- Virginia's face rebate can exceed its effect on final tax. The adjustment + respects other nonrefundable credits, the subsequent spouse adjustment, + and the final zero floor. With before-credit tax `T`, other applied credits + `C`, rebate `R`, and spouse adjustment `S`, its effect is + `max(T - C - S, 0) - max(T - C - R - S, 0)`. +- South Carolina's rebate is already limited to liability remaining after + other credits. Georgia and Montana rebates are modeled as refundable. + +The adjustment follows canonical credit paths. If a caller overrides an aggregate +credit total or final tax directly, it must also supply a consistent rebate +adjustment; the model cannot infer the rebate share of an arbitrary total. + +## Included payments and current timing + +The following inventory describes contributions to **current modeled tax +totals**, including existing historical backdating. It is not a statement that +every listed year has a legal entitlement. `R` denotes a state's refundable +credit total; `N` denotes its nonrefundable total. All these payments flow +through state credits, rather than a second direct household benefit payment. + +| Payment and raw variable(s) | Years with a possible nonzero contribution to current tax totals | Aggregation and timing | +|---|---|---| +| Colorado residual TABOR sales refund: `co_sales_tax_refund` | 2015–2025; 2026 is zero; 2027 onward under carried-forward parameters | R; amount, eligible-filer count, and `in_effect` gate. This is separate from Cash Back. Historical and future limitations are below. | +| Colorado Cash Back: `co_tabor_cash_back` | 2021 only | R; based on the 2021 return and paid in 2022. Both the amount and dated credit list end after 2021. | +| Illinois income and property: `il_income_tax_rebate`, `il_property_tax_rebate` | 2015–2021; intended eligibility year 2021 | R; paid in 2022, based on the 2021 return. The list removes both in 2022; missing earlier guards allow pre-2021 contributions. | +| New York inflation refund: `ny_inflation_refund_credit` | 2023 only | R; paid in 2025, based on the 2023 return. Formula dates reset it to zero after 2023. | +| Virginia: `va_rebate` | 2015–2022 and 2024; intended eligibility years 2021, 2022, and 2024 | N; the intended return years correspond to payments in 2022, 2023, and 2025. Amounts are zero in 2023 and from 2025; missing earlier guards allow pre-2021 contributions. The adjustment uses the final-tax effect. | +| Georgia surplus: `ga_surplus_tax_rebate` | 2021 only | R; modeled under O.C.G.A. 48-7-20.2 from 2021 liability, paid later. It is capped at income tax before credits and has zero amounts outside 2021. | +| Arizona families: `az_families_tax_rebate` | 2021 only | R; paid in 2023, based on the 2021 return. The dated list controls inclusion even when a standalone formula returns a positive value elsewhere. | +| New Mexico: `nm_2021_income_rebate`, `nm_additional_2021_income_rebate`, `nm_supplemental_2021_income_rebate` | 2015–2021; intended eligibility year 2021 | R; enacted in 2022 or 2023 but based on the 2021 return. The list removes all three in 2022; missing earlier guards allow pre-2021 contributions. | +| Connecticut child: `ct_child_tax_rebate` | 2021 only | R; paid in 2022, based on the 2021 return. Amount and child-cap gates are zero outside 2021. Full take-up is assumed; 2021 residence proxies for domicile at application. | +| Rhode Island child: `ri_child_tax_rebate` | 2021 only | R; paid in 2022, based on the 2021 return. The amount is zero outside 2021. | +| Hawaii Act 115: `hi_act_115_rebate` | 2015–2021; intended eligibility year 2021 | R; paid in 2022, based on the 2021 return. The list removes it in 2022; missing earlier guards allow pre-2021 contributions. | +| Maine $850 relief: `me_relief_rebate` | 2021 only | R; paid in 2022, based on the 2021 return. The amount is zero outside 2021. | +| Massachusetts Chapter 62F: `ma_taxpayer_refund_rebate` | 2021 only | R; paid in 2022, based on 2021 liability. The rate persists in standalone calculations, but the dated list includes it only in 2021. | +| Montana HB 192: `mt_income_tax_rebate` | 2021 only | R; person amounts are added directly by `mt_refundable_credits`. Paid in 2023, based on 2021 liability. The amount is zero outside 2021 and capped once per return. | +| South Carolina: `sc_2022_rebate` | 2021 only | N; paid in 2022, based on the 2021 return. Capped at $800 and residual liability; the amount is zero outside 2021. | +| Massachusetts premium pay: `ma_covid_19_essential_employee_premium_pay_program` | 2015–2021; intended eligibility years 2020 and 2021 | R; person amounts for two modeled $500 rounds paid in 2022. Missing earlier guards allow pre-2020 contributions. The variable remains listed later, with a zero amount from 2022. | +| Maine affordability: `me_affordability_payment` | 2025 only | R; $300 per eligible head or spouse, based on the 2025 return. Both dated inclusion and amount gates end in 2026. The payment is legally a direct payment; this model exposes it through refundable credits. | +| Delaware relief: `de_relief_rebate` | 2020 only | R; $300 per modeled head or spouse, based on the 2020 eligibility return and paid in 2022. Both dated inclusion and amount gates restrict it to 2020. | +| Idaho: `id_2022_rebate`, `id_special_season_rebate` | 2020 only in the current model | R; the 2020 list includes both; the 2021 list removes them. Their standalone amount/rate formulas persist. This inventory records current model attribution without revising the mapping of these named rebates to return years. | +| Indiana automatic refund: `in_automatic_refund_rebate` | 2020 only | R; modeled $125 plus $200 per head or spouse, based on the 2020 eligibility return and paid in 2022. Both dated inclusion and amount gates restrict it to 2020. The separate $200 credit for certain Social Security recipients on the 2022 return is outside this modeled component. | + +The Maine affordability, Delaware, Idaho, and Indiana rows supplement the +original comparison inventory. For Maine, the model assumes full-year residence +and does not model the return-filing deadline; recipient-level dependent +disqualifications still apply. For Massachusetts premium pay, this inventory +describes the two modeled $500 rounds and does not claim coverage of the later +$250 round. + +## Historical and future coverage limits + +Model parameter construction backdates each parameter's first value to 2015, +the first modeled year. Without an earlier zero amount or a credit-list entry +excluding a rebate, this can produce a contribution before its intended +eligibility year. Existing examples include Illinois, New Mexico, Hawaii, and +Virginia before 2021, and Massachusetts premium pay before 2020. Colorado's +sales refund also remains active in the modeled 2016–2020 period despite the +historical availability restriction. The comparison output restores these +amounts when they reduced modeled tax; it does not correct those historical +eligibility errors or change the attribution convention. + +The 2015 start in the table reflects model coverage, not a program's enactment +date. Colorado's backdated amount schedule is not a verified 2015 refund table. +Its 2027-and-later inclusion reflects current carried-forward parameters; +post-2026 amounts use placeholders pending published tables. These entries do +not verify future payments. + +A positive raw amount or eligibility flag alone is insufficient to establish +an effect on tax. Illinois, Arizona, New Mexico, Hawaii, Massachusetts 62F, +and Idaho have standalone formulas that can remain positive outside their +included years. Use the comparison adjustment or inspect dated credit-list +membership and amount gates together when reconciling external model outputs. diff --git a/docs/validation/taxsim.md b/docs/validation/taxsim.md index a3a0318fef0..d6707a87c36 100644 --- a/docs/validation/taxsim.md +++ b/docs/validation/taxsim.md @@ -201,7 +201,23 @@ The table below is loaded from the results the dashboard publishes, so it always Each test compares PolicyEngine with TAXSIM for one record at a time, separately for federal income tax and state income tax. A cell in the table is the share of records that pass. - **Within 1% of income** (the dashboard's default view): the two amounts differ by less than 1% of the record's gross income. Gross income here is wages, self-employment income, interest, dividends, other property income, non-property income, pensions and unemployment compensation, plus short- and long-term capital gains when positive and 85% of Social Security benefits. Records with zero or negative gross income use the \$15 test instead. -- **Within 1%, net of rebates** (state only): the same test applied to state income tax plus one-time state rebates on both sides. It is meant to remove a timing difference: TAXSIM counts a rebate in the year it is paid and PolicyEngine in the tax year it relates to. It is not exact, because TAXSIM also reports some rebates that its state tax does not reflect (Virginia's in 2022 and 2025, for example), so this column can be lower than the plain 1% column. +- **Within 1%, net of rebates** (state only): the same test applied to state income tax plus one-time state rebates on both sides. It is meant to remove a timing difference: TAXSIM counts a rebate in the year it is paid and PolicyEngine in the tax year it relates to. It is not exact, because TAXSIM also reports some rebates that its state tax does not reflect (Virginia's in 2022 and 2025, for example), so this column can be lower than the plain 1% column. See [state rebate attribution and the pre-rebate comparison output](state-rebates.md) for the model's reconciliation contract. - **Within \$15**: the two amounts differ by \$15 or less. These rules are implemented in `match_flags` in [`scripts/refresh_dashboard.py`](https://github.com/PolicyEngine/policyengine-taxsim/blob/main/scripts/refresh_dashboard.py). Each year's published summary records the commit and script hash that produced it. + +## Constructing comparison inputs + +Supply `is_tax_unit_dependent` for dependents or complete `tax_unit_role_input` +roles for all members. Tax-unit membership and a `filing_status` input alone do +not prevent an adult member from being inferred as a spouse. + +For California, `tax_exempt_interest_income` has no issuer-state split by +itself. The default treats it all as California-source for the state addback. +Supply the other-state portion as `ca_out_of_state_municipal_bond_interest` +on each person, and include that portion in `tax_exempt_interest_income` too. +The new input defaults to zero and does not change federal AGI. + +Alaska residence automatically calculates the regular PFD and any energy relief, +even when other income is supplied. See [Alaska PFD comparisons](alaska-pfd.md) +for per-person overrides and the separate 2022 taxable and energy components. diff --git a/policyengine_us/parameters/gov/states/household/state_income_tax_rebate_adjustments.yaml b/policyengine_us/parameters/gov/states/household/state_income_tax_rebate_adjustments.yaml new file mode 100644 index 00000000000..d78bed3097b --- /dev/null +++ b/policyengine_us/parameters/gov/states/household/state_income_tax_rebate_adjustments.yaml @@ -0,0 +1,30 @@ +description: >- + Registry of state tax effects restored for the pre-rebate comparison output. + Each effect follows its canonical dated credit list and calculated amount; + registry membership alone does not imply a payment in every year. Existing + historical backdating is preserved, not corrected by this comparison output. + Colorado's residual TABOR sales refund and Massachusetts premium pay are in + scope. Ordinary recurring credits remain in liability. +values: + 0000-01-01: + - az_income_tax_rebate_adjustment + - co_income_tax_rebate_adjustment + - ct_income_tax_rebate_adjustment + - de_income_tax_rebate_adjustment + - ga_income_tax_rebate_adjustment + - hi_income_tax_rebate_adjustment + - id_income_tax_rebate_adjustment + - il_income_tax_rebate_adjustment + - in_income_tax_rebate_adjustment + - ma_income_tax_rebate_adjustment + - me_income_tax_rebate_adjustment + - mt_income_tax_rebate_adjustment + - nm_income_tax_rebate_adjustment + - ny_income_tax_rebate_adjustment + - ri_income_tax_rebate_adjustment + - sc_income_tax_rebate_adjustment + - va_income_tax_rebate_adjustment +metadata: + unit: list + period: year + label: State income tax rebate adjustments diff --git a/policyengine_us/tests/policy/baseline/gov/states/tax/income/state_income_tax_before_one_time_rebates.yaml b/policyengine_us/tests/policy/baseline/gov/states/tax/income/state_income_tax_before_one_time_rebates.yaml new file mode 100644 index 00000000000..ce15c05e048 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/tax/income/state_income_tax_before_one_time_rebates.yaml @@ -0,0 +1,82 @@ +- name: Colorado comparison preserves ordinary refundable credits + period: 2021 + input: + state_code: CO + co_income_tax_before_refundable_credits: 1000 + co_amt: 0 + co_cdcc: 0 + co_low_income_cdcc: 0 + co_eitc: 100 + co_sales_tax_refund: 56 + co_tabor_cash_back: 750 + output: + state_income_tax_before_refundable_credits: 1000 + state_refundable_credits: 906 + state_income_tax: 94 + state_income_tax_rebate_adjustment: 806 + state_income_tax_before_one_time_rebates: 900 + +- name: Pre-rebate tax can remain negative with ordinary refundable credits + period: 2021 + input: + state_code: CO + co_income_tax_before_refundable_credits: 0 + co_amt: 0 + co_cdcc: 0 + co_low_income_cdcc: 0 + co_eitc: 100 + co_sales_tax_refund: 56 + co_tabor_cash_back: 750 + output: + state_income_tax: -906 + state_income_tax_rebate_adjustment: 806 + state_income_tax_before_one_time_rebates: -100 + +- name: Virginia comparison restores only the final tax effect + period: 2021 + input: + state_code: VA + va_income_tax_before_non_refundable_credits: 120 + va_non_refundable_eitc: 20 + va_spouse_tax_adjustment: 30 + va_rebate: 250 + va_refundable_credits: 10 + output: + state_income_tax: -10 + state_income_tax_rebate_adjustment: 70 + state_income_tax_before_one_time_rebates: 60 + +- name: Massachusetts reconciliation holds the 62F premium-pay interaction fixed + # 62F = 14.0312% x (2000 - 500 premium pay - 100 ordinary EITC). + # Restore the calculated 196.4368 refund plus 500, leaving tax at 1900. + period: 2021 + absolute_error_margin: 0.01 + input: + state_code: MA + ma_income_tax_before_refundable_credits: 2000 + ma_eitc: 100 + ma_child_and_family_credit_or_dependent_care_credit: 0 + ma_senior_circuit_breaker: 0 + ma_covid_19_essential_employee_premium_pay_program: 500 + output: + ma_taxpayer_refund_rebate: 196.4368 + state_income_tax: 1203.5632 + state_income_tax_rebate_adjustment: 696.4368 + state_income_tax_before_one_time_rebates: 1900 + +- name: Comparison leaves existing household income and tax decomposition unchanged + # 2022 Colorado example from the comparison audit: 100000 wages, + # federal taxable income 87050, 4.4% state tax 3830.20, sales refund 234. + period: 2022 + absolute_error_margin: 0.01 + input: + age: 40 + employment_income: 100000 + state_code: CO + output: + co_sales_tax_refund: 234 + co_tabor_cash_back: 0 + state_income_tax: 3596.20 + state_income_tax_rebate_adjustment: 234 + state_income_tax_before_one_time_rebates: 3830.20 + household_net_income: 73535.80 diff --git a/policyengine_us/tests/policy/baseline/gov/states/tax/income/state_income_tax_rebate_adjustment.yaml b/policyengine_us/tests/policy/baseline/gov/states/tax/income/state_income_tax_rebate_adjustment.yaml new file mode 100644 index 00000000000..18bf6af584d --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/tax/income/state_income_tax_rebate_adjustment.yaml @@ -0,0 +1,396 @@ +# Test the comparison registry and actual inclusion, not statutory rebate amounts. +- name: Arizona families rebate + period: 2021 + input: + state_code: AZ + az_families_tax_rebate: 250 + output: + state_income_tax_rebate_adjustment: 250 + +- name: Colorado residual TABOR and Cash Back + period: 2021 + input: + state_code: CO + co_sales_tax_refund: 56 + co_tabor_cash_back: 750 + output: + state_income_tax_rebate_adjustment: 806 + +- name: Connecticut child rebate + period: 2021 + input: + state_code: CT + ct_child_tax_rebate: 500 + output: + state_income_tax_rebate_adjustment: 500 + +- name: Delaware relief rebate + period: 2020 + input: + state_code: DE + de_relief_rebate: 300 + output: + state_income_tax_rebate_adjustment: 300 + +- name: Georgia refundable surplus rebate + period: 2021 + input: + state_code: GA + ga_surplus_tax_rebate: 250 + output: + state_income_tax_rebate_adjustment: 250 + +- name: Hawaii Act 115 rebate + period: 2021 + input: + state_code: HI + hi_act_115_rebate: 300 + output: + state_income_tax_rebate_adjustment: 300 + +- name: Idaho two included rebates + period: 2020 + input: + state_code: ID + id_2022_rebate: 75 + id_special_season_rebate: 300 + output: + state_income_tax_rebate_adjustment: 375 + +- name: Illinois income and property rebates + period: 2021 + input: + state_code: IL + il_income_tax_rebate: 50 + il_property_tax_rebate: 300 + output: + state_income_tax_rebate_adjustment: 350 + +- name: Indiana automatic refund + period: 2020 + input: + state_code: IN + in_automatic_refund_rebate: 325 + output: + state_income_tax_rebate_adjustment: 325 + +- name: Maine relief rebate + period: 2021 + input: + state_code: ME + me_relief_rebate: 850 + output: + state_income_tax_rebate_adjustment: 850 + +- name: Maine newer affordability payment + period: 2025 + input: + state_code: ME + me_affordability_payment: 300 + output: + state_income_tax_rebate_adjustment: 300 + +- name: New Mexico three rebates + period: 2021 + input: + state_code: NM + nm_2021_income_rebate: 250 + nm_additional_2021_income_rebate: 500 + nm_supplemental_2021_income_rebate: 500 + output: + state_income_tax_rebate_adjustment: 1250 + +- name: New York inflation refund + period: 2023 + input: + state_code: NY + ny_inflation_refund_credit: 200 + output: + state_income_tax_rebate_adjustment: 200 + +- name: Rhode Island child rebate + period: 2021 + input: + state_code: RI + ri_child_tax_rebate: 250 + output: + state_income_tax_rebate_adjustment: 250 + +- name: Virginia final floor after spouse adjustment + period: 2021 + input: + state_code: VA + va_income_tax_before_non_refundable_credits: 120 + va_non_refundable_eitc: 20 + va_spouse_tax_adjustment: 30 + va_rebate: 250 + output: + state_income_tax_rebate_adjustment: 70 + +- name: Virginia other credits exhaust liability + period: 2022 + input: + state_code: VA + va_income_tax_before_non_refundable_credits: 100 + va_non_refundable_eitc: 120 + va_spouse_tax_adjustment: 0 + va_rebate: 200 + output: + state_income_tax_rebate_adjustment: 0 + +- name: Virginia rebate fully applied + period: 2024 + input: + state_code: VA + va_income_tax_before_non_refundable_credits: 1000 + va_non_refundable_eitc: 100 + va_spouse_tax_adjustment: 50 + va_rebate: 200 + output: + state_income_tax_rebate_adjustment: 200 + +- name: Virginia inactive rebate list + period: 2023 + input: + state_code: VA + va_rebate: 200 + output: + state_income_tax_rebate_adjustment: 0 + +- name: South Carolina rebate limited to residual tax after ordinary credits + period: 2021 + input: + state_code: SC + sc_income_tax_before_non_refundable_credits: 500 + sc_cdcc: 100 + sc_eitc: 0 + sc_two_wage_earner_credit: 0 + sc_refundable_credits: 50 + output: + state_income_tax_rebate_adjustment: 350 + +- name: South Carolina raw override capped by nonrefundable aggregation + period: 2021 + input: + state_code: SC + sc_income_tax_before_non_refundable_credits: 100 + sc_cdcc: 80 + sc_eitc: 0 + sc_two_wage_earner_credit: 0 + sc_2022_rebate: 800 + output: + state_income_tax_rebate_adjustment: 20 + +- name: South Carolina rebate outside inclusion year + period: 2022 + input: + state_code: SC + sc_2022_rebate: 800 + output: + state_income_tax_rebate_adjustment: 0 + +- name: Colorado sales refund remains without Cash Back in 2022 + period: 2022 + input: + state_code: CO + co_sales_tax_refund: 234 + output: + state_income_tax_rebate_adjustment: 234 + +- name: Colorado amount gate zeroes refund in 2026 + period: 2026 + input: + state_code: CO + age: 40 + adjusted_gross_income: 100000 + output: + state_income_tax_rebate_adjustment: 0 + +- name: Montana amount gate zeroes rebate after 2021 + period: 2022 + input: + state_code: MT + output: + state_income_tax_rebate_adjustment: 0 + +- name: State without registered rebate + period: 2021 + input: + state_code: FL + output: + state_income_tax_rebate_adjustment: 0 + +- name: Existing historical Illinois backdating is reconciled + period: 2020 + input: + state_code: IL + il_income_tax_rebate: 50 + il_property_tax_rebate: 300 + output: + state_income_tax_rebate_adjustment: 350 + +- name: AZ positive raw amounts excluded by dated credit list + period: 2022 + input: + state_code: AZ + az_families_tax_rebate: 250 + output: + state_income_tax_rebate_adjustment: 0 + +- name: HI positive raw amounts excluded by dated credit list + period: 2022 + input: + state_code: HI + hi_act_115_rebate: 300 + output: + state_income_tax_rebate_adjustment: 0 + +- name: IL positive raw amounts excluded by dated credit list + period: 2022 + input: + state_code: IL + il_income_tax_rebate: 50 + il_property_tax_rebate: 300 + output: + state_income_tax_rebate_adjustment: 0 + +- name: NM positive raw amounts excluded by dated credit list + period: 2022 + input: + state_code: NM + nm_2021_income_rebate: 250 + nm_additional_2021_income_rebate: 500 + nm_supplemental_2021_income_rebate: 500 + output: + state_income_tax_rebate_adjustment: 0 + +- name: ID positive raw amounts excluded by dated credit list + period: 2022 + input: + state_code: ID + id_2022_rebate: 75 + id_special_season_rebate: 300 + output: + state_income_tax_rebate_adjustment: 0 + +- name: DE positive raw amounts excluded by dated credit list + period: 2022 + input: + state_code: DE + de_relief_rebate: 300 + output: + state_income_tax_rebate_adjustment: 0 + +- name: IN positive raw amounts excluded by dated credit list + period: 2022 + input: + state_code: IN + in_automatic_refund_rebate: 325 + output: + state_income_tax_rebate_adjustment: 0 + +- name: Massachusetts person premium pay and 62F refund aggregate to each tax unit + period: 2021 + input: + people: + head: + ma_covid_19_essential_employee_premium_pay_program: 500 + spouse: + ma_covid_19_essential_employee_premium_pay_program: 500 + other: + ma_covid_19_essential_employee_premium_pay_program: 500 + tax_units: + first: + members: [head, spouse] + ma_taxpayer_refund_rebate: 100 + second: + members: [other] + ma_taxpayer_refund_rebate: 200 + households: + household: + members: [head, spouse, other] + state_code: MA + output: + state_income_tax_rebate_adjustment: [1100, 700] + +- name: Massachusetts positive standalone 62F is not included after 2021 + period: 2022 + input: + state_code: MA + ma_taxpayer_refund_rebate: 100 + output: + state_income_tax_rebate_adjustment: 0 + +- name: Montana individual rebates sum within the return + period: 2021 + input: + people: + head: + mt_income_tax_rebate: 1250 + spouse: + mt_income_tax_rebate: 750 + tax_units: + unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: MT + output: + state_income_tax_rebate_adjustment: 2000 + +- name: Massachusetts premium pay without 62F before 2021 + period: 2020 + input: + state_code: MA + ma_covid_19_essential_employee_premium_pay_program: 500 + ma_taxpayer_refund_rebate: 100 + output: + state_income_tax_rebate_adjustment: 500 + +- name: Maine positive raw affordability payment excluded after its credit-list year + period: 2026 + input: + state_code: ME + me_affordability_payment: 300 + output: + state_income_tax_rebate_adjustment: 0 + +- name: Mixed-state households restore only their own rebates + period: 2021 + input: + people: + colorado: + ma_covid_19_essential_employee_premium_pay_program: 500 + massachusetts: + ma_covid_19_essential_employee_premium_pay_program: 500 + florida: + ma_covid_19_essential_employee_premium_pay_program: 500 + tax_units: + co_unit: + members: [colorado] + co_sales_tax_refund: 56 + co_tabor_cash_back: 750 + ma_taxpayer_refund_rebate: 100 + ma_unit: + members: [massachusetts] + co_sales_tax_refund: 56 + co_tabor_cash_back: 750 + ma_taxpayer_refund_rebate: 100 + fl_unit: + members: [florida] + co_sales_tax_refund: 56 + co_tabor_cash_back: 750 + ma_taxpayer_refund_rebate: 100 + households: + co_household: + members: [colorado] + state_code: CO + ma_household: + members: [massachusetts] + state_code: MA + fl_household: + members: [florida] + state_code: FL + output: + state_income_tax_rebate_adjustment: [806, 600, 0] diff --git a/policyengine_us/variables/gov/states/az/tax/income/az_income_tax_rebate_adjustment.py b/policyengine_us/variables/gov/states/az/tax/income/az_income_tax_rebate_adjustment.py new file mode 100644 index 00000000000..d3f08832925 --- /dev/null +++ b/policyengine_us/variables/gov/states/az/tax/income/az_income_tax_rebate_adjustment.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class az_income_tax_rebate_adjustment(Variable): + value_type = float + entity = TaxUnit + label = "AZ income tax rebate adjustment" + documentation = ( + "Rebates included in this year's AZ refundable-credit total. " + "Only registered rebates in the dated credit list are restored; " + "ordinary credits and other credit calculations remain unchanged." + ) + unit = USD + definition_period = YEAR + defined_for = StateCode.AZ + + def formula(tax_unit, period, parameters): + credits = parameters(period).gov.states.az.tax.income.credits.refundable + rebates = ["az_families_tax_rebate"] + included = [rebate for rebate in rebates if rebate in credits] + return add(tax_unit, period, included) if included else 0 diff --git a/policyengine_us/variables/gov/states/co/tax/income/co_income_tax_rebate_adjustment.py b/policyengine_us/variables/gov/states/co/tax/income/co_income_tax_rebate_adjustment.py new file mode 100644 index 00000000000..122beddeb99 --- /dev/null +++ b/policyengine_us/variables/gov/states/co/tax/income/co_income_tax_rebate_adjustment.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class co_income_tax_rebate_adjustment(Variable): + value_type = float + entity = TaxUnit + label = "CO income tax rebate adjustment" + documentation = ( + "Rebates included in this year's CO refundable-credit total. " + "Only registered rebates in the dated credit list are restored; " + "ordinary credits and other credit calculations remain unchanged." + ) + unit = USD + definition_period = YEAR + defined_for = StateCode.CO + + def formula(tax_unit, period, parameters): + credits = parameters(period).gov.states.co.tax.income.credits.refundable + rebates = ["co_sales_tax_refund", "co_tabor_cash_back"] + included = [rebate for rebate in rebates if rebate in credits] + return add(tax_unit, period, included) if included else 0 diff --git a/policyengine_us/variables/gov/states/ct/tax/income/ct_income_tax_rebate_adjustment.py b/policyengine_us/variables/gov/states/ct/tax/income/ct_income_tax_rebate_adjustment.py new file mode 100644 index 00000000000..09061227eab --- /dev/null +++ b/policyengine_us/variables/gov/states/ct/tax/income/ct_income_tax_rebate_adjustment.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class ct_income_tax_rebate_adjustment(Variable): + value_type = float + entity = TaxUnit + label = "CT income tax rebate adjustment" + documentation = ( + "Rebates included in this year's CT refundable-credit total. " + "Only registered rebates in the dated credit list are restored; " + "ordinary credits and other credit calculations remain unchanged." + ) + unit = USD + definition_period = YEAR + defined_for = StateCode.CT + + def formula(tax_unit, period, parameters): + credits = parameters(period).gov.states.ct.tax.income.credits.refundable + rebates = ["ct_child_tax_rebate"] + included = [rebate for rebate in rebates if rebate in credits] + return add(tax_unit, period, included) if included else 0 diff --git a/policyengine_us/variables/gov/states/de/tax/income/de_income_tax_rebate_adjustment.py b/policyengine_us/variables/gov/states/de/tax/income/de_income_tax_rebate_adjustment.py new file mode 100644 index 00000000000..48b8668bae6 --- /dev/null +++ b/policyengine_us/variables/gov/states/de/tax/income/de_income_tax_rebate_adjustment.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class de_income_tax_rebate_adjustment(Variable): + value_type = float + entity = TaxUnit + label = "DE income tax rebate adjustment" + documentation = ( + "Rebates included in this year's DE refundable-credit total. " + "Only registered rebates in the dated credit list are restored; " + "ordinary credits and other credit calculations remain unchanged." + ) + unit = USD + definition_period = YEAR + defined_for = StateCode.DE + + def formula(tax_unit, period, parameters): + credits = parameters(period).gov.states["de"].tax.income.credits.refundable + rebates = ["de_relief_rebate"] + included = [rebate for rebate in rebates if rebate in credits] + return add(tax_unit, period, included) if included else 0 diff --git a/policyengine_us/variables/gov/states/ga/tax/income/ga_income_tax_rebate_adjustment.py b/policyengine_us/variables/gov/states/ga/tax/income/ga_income_tax_rebate_adjustment.py new file mode 100644 index 00000000000..ebdac050cf4 --- /dev/null +++ b/policyengine_us/variables/gov/states/ga/tax/income/ga_income_tax_rebate_adjustment.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class ga_income_tax_rebate_adjustment(Variable): + value_type = float + entity = TaxUnit + label = "GA income tax rebate adjustment" + documentation = ( + "Rebates included in this year's GA refundable-credit total. " + "Only registered rebates in the dated credit list are restored; " + "ordinary credits and other credit calculations remain unchanged." + ) + unit = USD + definition_period = YEAR + defined_for = StateCode.GA + + def formula(tax_unit, period, parameters): + credits = parameters(period).gov.states.ga.tax.income.credits.refundable + rebates = ["ga_surplus_tax_rebate"] + included = [rebate for rebate in rebates if rebate in credits] + return add(tax_unit, period, included) if included else 0 diff --git a/policyengine_us/variables/gov/states/hi/tax/income/hi_income_tax_rebate_adjustment.py b/policyengine_us/variables/gov/states/hi/tax/income/hi_income_tax_rebate_adjustment.py new file mode 100644 index 00000000000..336bd6d7d52 --- /dev/null +++ b/policyengine_us/variables/gov/states/hi/tax/income/hi_income_tax_rebate_adjustment.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class hi_income_tax_rebate_adjustment(Variable): + value_type = float + entity = TaxUnit + label = "HI income tax rebate adjustment" + documentation = ( + "Rebates included in this year's HI refundable-credit total. " + "Only registered rebates in the dated credit list are restored; " + "ordinary credits and other credit calculations remain unchanged." + ) + unit = USD + definition_period = YEAR + defined_for = StateCode.HI + + def formula(tax_unit, period, parameters): + credits = parameters(period).gov.states.hi.tax.income.credits.refundable + rebates = ["hi_act_115_rebate"] + included = [rebate for rebate in rebates if rebate in credits] + return add(tax_unit, period, included) if included else 0 diff --git a/policyengine_us/variables/gov/states/id/tax/income/id_income_tax_rebate_adjustment.py b/policyengine_us/variables/gov/states/id/tax/income/id_income_tax_rebate_adjustment.py new file mode 100644 index 00000000000..d5a44072250 --- /dev/null +++ b/policyengine_us/variables/gov/states/id/tax/income/id_income_tax_rebate_adjustment.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class id_income_tax_rebate_adjustment(Variable): + value_type = float + entity = TaxUnit + label = "ID income tax rebate adjustment" + documentation = ( + "Rebates included in this year's ID refundable-credit total. " + "Only registered rebates in the dated credit list are restored; " + "ordinary credits and other credit calculations remain unchanged." + ) + unit = USD + definition_period = YEAR + defined_for = StateCode.ID + + def formula(tax_unit, period, parameters): + credits = parameters(period).gov.states["id"].tax.income.credits.refundable + rebates = ["id_2022_rebate", "id_special_season_rebate"] + included = [rebate for rebate in rebates if rebate in credits] + return add(tax_unit, period, included) if included else 0 diff --git a/policyengine_us/variables/gov/states/il/tax/income/il_income_tax_rebate_adjustment.py b/policyengine_us/variables/gov/states/il/tax/income/il_income_tax_rebate_adjustment.py new file mode 100644 index 00000000000..f2a876c4585 --- /dev/null +++ b/policyengine_us/variables/gov/states/il/tax/income/il_income_tax_rebate_adjustment.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class il_income_tax_rebate_adjustment(Variable): + value_type = float + entity = TaxUnit + label = "IL income tax rebate adjustment" + documentation = ( + "Rebates included in this year's IL refundable-credit total. " + "Only registered rebates in the dated credit list are restored; " + "ordinary credits and other credit calculations remain unchanged." + ) + unit = USD + definition_period = YEAR + defined_for = StateCode.IL + + def formula(tax_unit, period, parameters): + credits = parameters(period).gov.states.il.tax.income.credits.refundable + rebates = ["il_income_tax_rebate", "il_property_tax_rebate"] + included = [rebate for rebate in rebates if rebate in credits] + return add(tax_unit, period, included) if included else 0 diff --git a/policyengine_us/variables/gov/states/in/tax/income/in_income_tax_rebate_adjustment.py b/policyengine_us/variables/gov/states/in/tax/income/in_income_tax_rebate_adjustment.py new file mode 100644 index 00000000000..5baca1e6c9a --- /dev/null +++ b/policyengine_us/variables/gov/states/in/tax/income/in_income_tax_rebate_adjustment.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class in_income_tax_rebate_adjustment(Variable): + value_type = float + entity = TaxUnit + label = "IN income tax rebate adjustment" + documentation = ( + "Rebates included in this year's IN refundable-credit total. " + "Only registered rebates in the dated credit list are restored; " + "ordinary credits and other credit calculations remain unchanged." + ) + unit = USD + definition_period = YEAR + defined_for = StateCode.IN + + def formula(tax_unit, period, parameters): + credits = parameters(period).gov.states["in"].tax.income.credits.refundable + rebates = ["in_automatic_refund_rebate"] + included = [rebate for rebate in rebates if rebate in credits] + return add(tax_unit, period, included) if included else 0 diff --git a/policyengine_us/variables/gov/states/ma/tax/income/ma_income_tax_rebate_adjustment.py b/policyengine_us/variables/gov/states/ma/tax/income/ma_income_tax_rebate_adjustment.py new file mode 100644 index 00000000000..3a88fe54443 --- /dev/null +++ b/policyengine_us/variables/gov/states/ma/tax/income/ma_income_tax_rebate_adjustment.py @@ -0,0 +1,24 @@ +from policyengine_us.model_api import * + + +class ma_income_tax_rebate_adjustment(Variable): + value_type = float + entity = TaxUnit + label = "MA income tax rebate adjustment" + documentation = ( + "Rebates included in this year's MA refundable-credit total. " + "Only registered rebates in the dated credit list are restored; " + "ordinary credits and other credit calculations remain unchanged." + ) + unit = USD + definition_period = YEAR + defined_for = StateCode.MA + + def formula(tax_unit, period, parameters): + credits = parameters(period).gov.states.ma.tax.income.credits.refundable + rebates = [ + "ma_covid_19_essential_employee_premium_pay_program", + "ma_taxpayer_refund_rebate", + ] + included = [rebate for rebate in rebates if rebate in credits] + return add(tax_unit, period, included) if included else 0 diff --git a/policyengine_us/variables/gov/states/me/tax/income/me_income_tax_rebate_adjustment.py b/policyengine_us/variables/gov/states/me/tax/income/me_income_tax_rebate_adjustment.py new file mode 100644 index 00000000000..1748ef56161 --- /dev/null +++ b/policyengine_us/variables/gov/states/me/tax/income/me_income_tax_rebate_adjustment.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class me_income_tax_rebate_adjustment(Variable): + value_type = float + entity = TaxUnit + label = "ME income tax rebate adjustment" + documentation = ( + "Rebates included in this year's ME refundable-credit total. " + "Only registered rebates in the dated credit list are restored; " + "ordinary credits and other credit calculations remain unchanged." + ) + unit = USD + definition_period = YEAR + defined_for = StateCode.ME + + def formula(tax_unit, period, parameters): + credits = parameters(period).gov.states.me.tax.income.credits.refundable + rebates = ["me_relief_rebate", "me_affordability_payment"] + included = [rebate for rebate in rebates if rebate in credits] + return add(tax_unit, period, included) if included else 0 diff --git a/policyengine_us/variables/gov/states/mt/tax/income/mt_income_tax_rebate_adjustment.py b/policyengine_us/variables/gov/states/mt/tax/income/mt_income_tax_rebate_adjustment.py new file mode 100644 index 00000000000..885539d02db --- /dev/null +++ b/policyengine_us/variables/gov/states/mt/tax/income/mt_income_tax_rebate_adjustment.py @@ -0,0 +1,16 @@ +from policyengine_us.model_api import * + + +class mt_income_tax_rebate_adjustment(Variable): + value_type = float + entity = TaxUnit + label = "Montana income tax rebate adjustment" + documentation = ( + "Sum of the individual 2021 rebates included in mt_refundable_credits. " + "The existing separate-versus-joint election is held fixed, consistent " + "with mt_income_tax_before_2021_rebate." + ) + unit = USD + definition_period = YEAR + defined_for = StateCode.MT + adds = ["mt_income_tax_rebate"] diff --git a/policyengine_us/variables/gov/states/nm/tax/income/nm_income_tax_rebate_adjustment.py b/policyengine_us/variables/gov/states/nm/tax/income/nm_income_tax_rebate_adjustment.py new file mode 100644 index 00000000000..3e4ba4a7aaa --- /dev/null +++ b/policyengine_us/variables/gov/states/nm/tax/income/nm_income_tax_rebate_adjustment.py @@ -0,0 +1,25 @@ +from policyengine_us.model_api import * + + +class nm_income_tax_rebate_adjustment(Variable): + value_type = float + entity = TaxUnit + label = "NM income tax rebate adjustment" + documentation = ( + "Rebates included in this year's NM refundable-credit total. " + "Only registered rebates in the dated credit list are restored; " + "ordinary credits and other credit calculations remain unchanged." + ) + unit = USD + definition_period = YEAR + defined_for = StateCode.NM + + def formula(tax_unit, period, parameters): + credits = parameters(period).gov.states.nm.tax.income.credits.refundable + rebates = [ + "nm_2021_income_rebate", + "nm_additional_2021_income_rebate", + "nm_supplemental_2021_income_rebate", + ] + included = [rebate for rebate in rebates if rebate in credits] + return add(tax_unit, period, included) if included else 0 diff --git a/policyengine_us/variables/gov/states/ny/tax/income/ny_income_tax_rebate_adjustment.py b/policyengine_us/variables/gov/states/ny/tax/income/ny_income_tax_rebate_adjustment.py new file mode 100644 index 00000000000..f4986e73d19 --- /dev/null +++ b/policyengine_us/variables/gov/states/ny/tax/income/ny_income_tax_rebate_adjustment.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class ny_income_tax_rebate_adjustment(Variable): + value_type = float + entity = TaxUnit + label = "NY income tax rebate adjustment" + documentation = ( + "Rebates included in this year's NY refundable-credit total. " + "Only registered rebates in the dated credit list are restored; " + "ordinary credits and other credit calculations remain unchanged." + ) + unit = USD + definition_period = YEAR + defined_for = StateCode.NY + + def formula(tax_unit, period, parameters): + credits = parameters(period).gov.states.ny.tax.income.credits.refundable + rebates = ["ny_inflation_refund_credit"] + included = [rebate for rebate in rebates if rebate in credits] + return add(tax_unit, period, included) if included else 0 diff --git a/policyengine_us/variables/gov/states/ri/tax/income/ri_income_tax_rebate_adjustment.py b/policyengine_us/variables/gov/states/ri/tax/income/ri_income_tax_rebate_adjustment.py new file mode 100644 index 00000000000..965bff8ad23 --- /dev/null +++ b/policyengine_us/variables/gov/states/ri/tax/income/ri_income_tax_rebate_adjustment.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class ri_income_tax_rebate_adjustment(Variable): + value_type = float + entity = TaxUnit + label = "RI income tax rebate adjustment" + documentation = ( + "Rebates included in this year's RI refundable-credit total. " + "Only registered rebates in the dated credit list are restored; " + "ordinary credits and other credit calculations remain unchanged." + ) + unit = USD + definition_period = YEAR + defined_for = StateCode.RI + + def formula(tax_unit, period, parameters): + credits = parameters(period).gov.states.ri.tax.income.credits.refundable + rebates = ["ri_child_tax_rebate"] + included = [rebate for rebate in rebates if rebate in credits] + return add(tax_unit, period, included) if included else 0 diff --git a/policyengine_us/variables/gov/states/sc/tax/income/sc_income_tax_rebate_adjustment.py b/policyengine_us/variables/gov/states/sc/tax/income/sc_income_tax_rebate_adjustment.py new file mode 100644 index 00000000000..24da47fae3f --- /dev/null +++ b/policyengine_us/variables/gov/states/sc/tax/income/sc_income_tax_rebate_adjustment.py @@ -0,0 +1,33 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.states.tax.income.non_refundable_credit_cap import ( + ordered_capped_state_non_refundable_credits, +) + + +class sc_income_tax_rebate_adjustment(Variable): + value_type = float + entity = TaxUnit + label = "SC income tax rebate adjustment" + documentation = ( + "Actual reduction in SC income tax from its registered " + "nonrefundable rebate, after other credits and the final zero floor. " + "Other credit amounts and filing choices remain fixed." + ) + unit = USD + definition_period = YEAR + defined_for = StateCode.SC + + def formula(tax_unit, period, parameters): + credits = parameters(period).gov.states.sc.tax.income.credits.non_refundable + if "sc_2022_rebate" not in credits: + return 0 + other_credits = [credit for credit in credits if credit != "sc_2022_rebate"] + tax_variable = "sc_income_tax_before_non_refundable_credits" + without_rebate = ordered_capped_state_non_refundable_credits( + tax_unit, period, other_credits, tax_variable + ) + with_rebate = tax_unit("sc_non_refundable_credits", period) + tax_before_credits = tax_unit(tax_variable, period) + return max_(tax_before_credits - without_rebate, 0) - max_( + tax_before_credits - with_rebate, 0 + ) diff --git a/policyengine_us/variables/gov/states/tax/income/state_income_tax_before_one_time_rebates.py b/policyengine_us/variables/gov/states/tax/income/state_income_tax_before_one_time_rebates.py new file mode 100644 index 00000000000..3b16c4266c1 --- /dev/null +++ b/policyengine_us/variables/gov/states/tax/income/state_income_tax_before_one_time_rebates.py @@ -0,0 +1,20 @@ +from policyengine_us.model_api import * + + +class state_income_tax_before_one_time_rebates(Variable): + value_type = float + entity = TaxUnit + label = "State income tax before one-time rebates" + documentation = ( + "State income tax plus state_income_tax_rebate_adjustment. This " + "comparison output restores only registered rebates, including " + "Colorado's residual TABOR sales tax refund and Massachusetts premium " + "pay, while preserving ordinary refundable credits. It can be negative. " + "It is an additive reconciliation holding other credit calculations " + "and filing elections fixed, not a counterfactual recalculation of " + "taxes with every rebate removed. Canonical state tax and household " + "net income are unchanged. See docs/validation/state-rebates.md." + ) + unit = USD + definition_period = YEAR + adds = ["state_income_tax", "state_income_tax_rebate_adjustment"] diff --git a/policyengine_us/variables/gov/states/tax/income/state_income_tax_rebate_adjustment.py b/policyengine_us/variables/gov/states/tax/income/state_income_tax_rebate_adjustment.py new file mode 100644 index 00000000000..0bfaa021db5 --- /dev/null +++ b/policyengine_us/variables/gov/states/tax/income/state_income_tax_rebate_adjustment.py @@ -0,0 +1,18 @@ +from policyengine_us.model_api import * + + +class state_income_tax_rebate_adjustment(Variable): + value_type = float + entity = TaxUnit + label = "State income tax rebate adjustment" + documentation = ( + "Registered rebate amounts actually included in modeled state income " + "tax for this year, including Colorado's residual TABOR sales tax " + "refund and Massachusetts premium pay. Nonrefundable amounts reflect " + "remaining liability and any subsequent spouse adjustment. Ordinary " + "credits, other credit bases and filing elections remain fixed. " + "The dated registry defines coverage; see docs/validation/state-rebates.md." + ) + unit = USD + definition_period = YEAR + adds = "gov.states.household.state_income_tax_rebate_adjustments" diff --git a/policyengine_us/variables/gov/states/va/tax/income/va_income_tax_rebate_adjustment.py b/policyengine_us/variables/gov/states/va/tax/income/va_income_tax_rebate_adjustment.py new file mode 100644 index 00000000000..89ca5c5c4af --- /dev/null +++ b/policyengine_us/variables/gov/states/va/tax/income/va_income_tax_rebate_adjustment.py @@ -0,0 +1,34 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.states.tax.income.non_refundable_credit_cap import ( + ordered_capped_state_non_refundable_credits, +) + + +class va_income_tax_rebate_adjustment(Variable): + value_type = float + entity = TaxUnit + label = "VA income tax rebate adjustment" + documentation = ( + "Actual reduction in VA income tax from its registered " + "nonrefundable rebate, after other credits and the final zero floor. " + "Other credit amounts and filing choices remain fixed." + ) + unit = USD + definition_period = YEAR + defined_for = StateCode.VA + + def formula(tax_unit, period, parameters): + credits = parameters(period).gov.states.va.tax.income.credits.non_refundable + if "va_rebate" not in credits: + return 0 + other_credits = [credit for credit in credits if credit != "va_rebate"] + tax_variable = "va_income_tax_before_non_refundable_credits" + without_rebate = ordered_capped_state_non_refundable_credits( + tax_unit, period, other_credits, tax_variable + ) + with_rebate = tax_unit("va_non_refundable_credits", period) + tax_before_credits = tax_unit(tax_variable, period) + adjustment = tax_unit("va_spouse_tax_adjustment", period) + return max_(tax_before_credits - without_rebate - adjustment, 0) - max_( + tax_before_credits - with_rebate - adjustment, 0 + ) From f169fa9ae2f7d27841f889dffbc8807b2a7ddc20 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 20 Oct 2026 16:57:36 -0400 Subject: [PATCH 5/7] Keep dependent municipal interest on the dependent California return --- ...alifornia-municipal-bond-interest.added.md | 2 +- docs/validation/taxsim.md | 4 ++- ..._out_of_state_municipal_bond_interest.yaml | 30 +++++++++++++++++++ .../gov/states/ca/tax/income/ca_additions.py | 20 +++++++++---- ...ca_out_of_state_municipal_bond_interest.py | 6 +++- 5 files changed, 53 insertions(+), 9 deletions(-) diff --git a/changelog.d/california-municipal-bond-interest.added.md b/changelog.d/california-municipal-bond-interest.added.md index 3978521eb64..8e9941e1a9b 100644 --- a/changelog.d/california-municipal-bond-interest.added.md +++ b/changelog.d/california-municipal-bond-interest.added.md @@ -1 +1 @@ -Add an optional California out-of-state municipal bond interest input and document the zero-addback default. +Add an optional California out-of-state municipal bond interest input for nondependent filers and document the zero-addback default. diff --git a/docs/validation/taxsim.md b/docs/validation/taxsim.md index d6707a87c36..59464bbee4a 100644 --- a/docs/validation/taxsim.md +++ b/docs/validation/taxsim.md @@ -216,7 +216,9 @@ For California, `tax_exempt_interest_income` has no issuer-state split by itself. The default treats it all as California-source for the state addback. Supply the other-state portion as `ca_out_of_state_municipal_bond_interest` on each person, and include that portion in `tax_exempt_interest_income` too. -The new input defaults to zero and does not change federal AGI. +Only nondependent members' amounts enter the filer's California AGI; a parent +election to report a dependent's interest is not inferred. The new input +defaults to zero and does not change federal AGI. Alaska residence automatically calculates the regular PFD and any energy relief, even when other income is supplied. See [Alaska PFD comparisons](alaska-pfd.md) diff --git a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/ca_out_of_state_municipal_bond_interest.yaml b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/ca_out_of_state_municipal_bond_interest.yaml index 1493a78a84c..58a8c05b633 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/ca_out_of_state_municipal_bond_interest.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/ca_out_of_state_municipal_bond_interest.yaml @@ -105,3 +105,33 @@ adjusted_gross_income: 100_000 ca_additions: 13_500 ca_agi: 113_500 + +# FTB 3803 instructions, sections A and C: reporting a child's interest on +# the parent's California return requires an election. None is supplied here. +# https://www.ftb.ca.gov/forms/2025/2025-3803-instructions.html +- name: A dependent's own interest does not enter the parent's California AGI + period: 2025 + input: + people: + parent: + age: 40 + employment_income: 100_000 + tax_exempt_interest_income: 50_000 + ca_out_of_state_municipal_bond_interest: 12_500 + child: + age: 18 + is_tax_unit_dependent: true + tax_exempt_interest_income: 10_000 + ca_out_of_state_municipal_bond_interest: 7_500 + tax_units: + tax_unit: + members: [parent, child] + filing_status: SINGLE + households: + household: + members: [parent, child] + state_code: CA + output: + adjusted_gross_income: 100_000 + ca_additions: 12_500 + ca_agi: 112_500 diff --git a/policyengine_us/variables/gov/states/ca/tax/income/ca_additions.py b/policyengine_us/variables/gov/states/ca/tax/income/ca_additions.py index 944e27d50e9..65c6b0a5158 100644 --- a/policyengine_us/variables/gov/states/ca/tax/income/ca_additions.py +++ b/policyengine_us/variables/gov/states/ca/tax/income/ca_additions.py @@ -9,9 +9,17 @@ class ca_additions(Variable): definition_period = YEAR reference = "https://www.ftb.ca.gov/forms/2021/2021-540.pdf" defined_for = StateCode.CA - adds = [ - "ca_hsa_addition", - "ca_educator_expense_addition", - "ca_other_additions", - "ca_out_of_state_municipal_bond_interest", - ] + + def formula(tax_unit, period, parameters): + # Personal interest belongs on the member's own return. The existing + # tax-unit additions already describe this return and remain intact. + return tax_unit_non_dep_add( + tax_unit, + period, + [ + "ca_hsa_addition", + "ca_educator_expense_addition", + "ca_other_additions", + "ca_out_of_state_municipal_bond_interest", + ], + ) diff --git a/policyengine_us/variables/gov/states/ca/tax/income/ca_out_of_state_municipal_bond_interest.py b/policyengine_us/variables/gov/states/ca/tax/income/ca_out_of_state_municipal_bond_interest.py index 29e86f9e1b9..dde879060c6 100644 --- a/policyengine_us/variables/gov/states/ca/tax/income/ca_out_of_state_municipal_bond_interest.py +++ b/policyengine_us/variables/gov/states/ca/tax/income/ca_out_of_state_municipal_bond_interest.py @@ -15,7 +15,10 @@ class ca_out_of_state_municipal_bond_interest(Variable): "CA (540), Part I, Section A, line 2, column C under R&TC 17143. " "Include this amount in tax_exempt_interest_income too: it is a subset " "of that total, not additional income, and does not increase federal " - "AGI. The default is zero, so absent issuer-source data all tax-exempt " + "AGI. Only nondependent members' amounts enter their tax unit's AGI; " + "a dependent's interest belongs on their own return, and a parent " + "election to report it is not inferred. The default is zero, so " + "absent issuer-source data all tax-exempt " "interest is treated as California-source for this adjustment. " "California state and local bond interest remains exempt under " "R&TC 17133." @@ -25,4 +28,5 @@ class ca_out_of_state_municipal_bond_interest(Variable): "https://leginfo.legislature.ca.gov/faces/codes_displaySection.xhtml?lawCode=RTC§ionNum=17133", "https://www.ftb.ca.gov/forms/2023/2023-1001-publication.pdf#page=7", "https://www.ftb.ca.gov/forms/2025/2025-1001-publication.pdf#page=8", + "https://www.ftb.ca.gov/forms/2025/2025-3803-instructions.html", ) From 3c366637eaebc1bb9472e12c84d06cb39a7355d6 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 20 Oct 2026 17:23:33 -0400 Subject: [PATCH 6/7] Clarify direct Montana aggregation in rebate comparison documentation --- docs/validation/state-rebates.md | 8 +++++--- .../household/state_income_tax_rebate_adjustments.yaml | 5 +++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/validation/state-rebates.md b/docs/validation/state-rebates.md index 913e9ebc08b..1f1f4cb4d59 100644 --- a/docs/validation/state-rebates.md +++ b/docs/validation/state-rebates.md @@ -36,9 +36,11 @@ It can therefore be negative. Neither output changes canonical The [rebate registry](https://github.com/PolicyEngine/policyengine-us/blob/main/policyengine_us/parameters/gov/states/household/state_income_tax_rebate_adjustments.yaml) identifies the state effects within the comparison's scope. A registry entry -does not establish a payment in every year: each state effect checks the -state's dated credit list and the calculated amounts, including formula dates -and amount gates. For example, a positive raw `il_income_tax_rebate` after 2021 +does not establish a payment in every year. State effects follow canonical +credit aggregation and calculated amounts, including dated credit lists, +formula dates, and amount gates. Montana's rebate is added directly by +`mt_refundable_credits`, rather than through its credit list. For example, +a positive raw `il_income_tax_rebate` after 2021 is excluded from Illinois's refundable-credit list and contributes zero to the adjustment. Amounts that are zero contribute zero even if a dated list retains the variable. diff --git a/policyengine_us/parameters/gov/states/household/state_income_tax_rebate_adjustments.yaml b/policyengine_us/parameters/gov/states/household/state_income_tax_rebate_adjustments.yaml index d78bed3097b..0e9dec3440f 100644 --- a/policyengine_us/parameters/gov/states/household/state_income_tax_rebate_adjustments.yaml +++ b/policyengine_us/parameters/gov/states/household/state_income_tax_rebate_adjustments.yaml @@ -1,7 +1,8 @@ description: >- Registry of state tax effects restored for the pre-rebate comparison output. - Each effect follows its canonical dated credit list and calculated amount; - registry membership alone does not imply a payment in every year. Existing + Each effect follows canonical credit aggregation and calculated amounts; + dated credit lists and amount gates determine inclusion. Registry membership + alone does not imply a payment in every year. Existing historical backdating is preserved, not corrected by this comparison output. Colorado's residual TABOR sales refund and Massachusetts premium pay are in scope. Ordinary recurring credits remain in liability. From bda3538997b72dc1bcb87d7b693cba63b92b8ca3 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 20 Oct 2026 17:29:01 -0400 Subject: [PATCH 7/7] Render comparison documentation amounts as literal currency --- docs/validation/alaska-pfd.md | 10 +++++----- docs/validation/state-rebates.md | 16 ++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/validation/alaska-pfd.md b/docs/validation/alaska-pfd.md index b9034e715c7..6182b4f133e 100644 --- a/docs/validation/alaska-pfd.md +++ b/docs/validation/alaska-pfd.md @@ -41,14 +41,14 @@ households: ``` With only the two payment inputs removed, this person's calculated 2022 regular -PFD is $2,622, energy relief is $662, and federal AGI is $52,622. With both inputs -set to zero as above, federal AGI is $50,000. +PFD is \$2,622, energy relief is \$662, and federal AGI is \$52,622. With both inputs +set to zero as above, federal AGI is \$50,000. ## The 2022 regular dividend and energy relief split -Alaska paid $3,284 in total: $2,621.81 of regular PFD and $662.19 of supplemental -energy relief. PolicyEngine rounds these 2022 components to $2,622 and $662, -respectively. Putting the full $3,284 in `ak_permanent_fund_dividend` would mix +Alaska paid \$3,284 in total: \$2,621.81 of regular PFD and \$662.19 of supplemental +energy relief. PolicyEngine rounds these 2022 components to \$2,622 and \$662, +respectively. Putting the full \$3,284 in `ak_permanent_fund_dividend` would mix the components and increase the modeled taxable amount. The split is described in [Alaska DOR's February 17, 2023 release](https://dor.alaska.gov/docs/default-source/press-releases/2023_02_17-dor-response-to-irs-guidance-on-2022-pfd.pdf#page=1). diff --git a/docs/validation/state-rebates.md b/docs/validation/state-rebates.md index 1f1f4cb4d59..8189b67283f 100644 --- a/docs/validation/state-rebates.md +++ b/docs/validation/state-rebates.md @@ -93,22 +93,22 @@ through state credits, rather than a second direct household benefit payment. | Connecticut child: `ct_child_tax_rebate` | 2021 only | R; paid in 2022, based on the 2021 return. Amount and child-cap gates are zero outside 2021. Full take-up is assumed; 2021 residence proxies for domicile at application. | | Rhode Island child: `ri_child_tax_rebate` | 2021 only | R; paid in 2022, based on the 2021 return. The amount is zero outside 2021. | | Hawaii Act 115: `hi_act_115_rebate` | 2015–2021; intended eligibility year 2021 | R; paid in 2022, based on the 2021 return. The list removes it in 2022; missing earlier guards allow pre-2021 contributions. | -| Maine $850 relief: `me_relief_rebate` | 2021 only | R; paid in 2022, based on the 2021 return. The amount is zero outside 2021. | +| Maine \$850 relief: `me_relief_rebate` | 2021 only | R; paid in 2022, based on the 2021 return. The amount is zero outside 2021. | | Massachusetts Chapter 62F: `ma_taxpayer_refund_rebate` | 2021 only | R; paid in 2022, based on 2021 liability. The rate persists in standalone calculations, but the dated list includes it only in 2021. | | Montana HB 192: `mt_income_tax_rebate` | 2021 only | R; person amounts are added directly by `mt_refundable_credits`. Paid in 2023, based on 2021 liability. The amount is zero outside 2021 and capped once per return. | -| South Carolina: `sc_2022_rebate` | 2021 only | N; paid in 2022, based on the 2021 return. Capped at $800 and residual liability; the amount is zero outside 2021. | -| Massachusetts premium pay: `ma_covid_19_essential_employee_premium_pay_program` | 2015–2021; intended eligibility years 2020 and 2021 | R; person amounts for two modeled $500 rounds paid in 2022. Missing earlier guards allow pre-2020 contributions. The variable remains listed later, with a zero amount from 2022. | -| Maine affordability: `me_affordability_payment` | 2025 only | R; $300 per eligible head or spouse, based on the 2025 return. Both dated inclusion and amount gates end in 2026. The payment is legally a direct payment; this model exposes it through refundable credits. | -| Delaware relief: `de_relief_rebate` | 2020 only | R; $300 per modeled head or spouse, based on the 2020 eligibility return and paid in 2022. Both dated inclusion and amount gates restrict it to 2020. | +| South Carolina: `sc_2022_rebate` | 2021 only | N; paid in 2022, based on the 2021 return. Capped at \$800 and residual liability; the amount is zero outside 2021. | +| Massachusetts premium pay: `ma_covid_19_essential_employee_premium_pay_program` | 2015–2021; intended eligibility years 2020 and 2021 | R; person amounts for two modeled \$500 rounds paid in 2022. Missing earlier guards allow pre-2020 contributions. The variable remains listed later, with a zero amount from 2022. | +| Maine affordability: `me_affordability_payment` | 2025 only | R; \$300 per eligible head or spouse, based on the 2025 return. Both dated inclusion and amount gates end in 2026. The payment is legally a direct payment; this model exposes it through refundable credits. | +| Delaware relief: `de_relief_rebate` | 2020 only | R; \$300 per modeled head or spouse, based on the 2020 eligibility return and paid in 2022. Both dated inclusion and amount gates restrict it to 2020. | | Idaho: `id_2022_rebate`, `id_special_season_rebate` | 2020 only in the current model | R; the 2020 list includes both; the 2021 list removes them. Their standalone amount/rate formulas persist. This inventory records current model attribution without revising the mapping of these named rebates to return years. | -| Indiana automatic refund: `in_automatic_refund_rebate` | 2020 only | R; modeled $125 plus $200 per head or spouse, based on the 2020 eligibility return and paid in 2022. Both dated inclusion and amount gates restrict it to 2020. The separate $200 credit for certain Social Security recipients on the 2022 return is outside this modeled component. | +| Indiana automatic refund: `in_automatic_refund_rebate` | 2020 only | R; modeled \$125 plus \$200 per head or spouse, based on the 2020 eligibility return and paid in 2022. Both dated inclusion and amount gates restrict it to 2020. The separate \$200 credit for certain Social Security recipients on the 2022 return is outside this modeled component. | The Maine affordability, Delaware, Idaho, and Indiana rows supplement the original comparison inventory. For Maine, the model assumes full-year residence and does not model the return-filing deadline; recipient-level dependent disqualifications still apply. For Massachusetts premium pay, this inventory -describes the two modeled $500 rounds and does not claim coverage of the later -$250 round. +describes the two modeled \$500 rounds and does not claim coverage of the later +\$250 round. ## Historical and future coverage limits