From 931520956e3bcc988f8ec5bf86ec0d6824928ede Mon Sep 17 00:00:00 2001 From: David Trimmer Date: Fri, 19 Jun 2026 16:09:25 -0400 Subject: [PATCH 1/4] Separate the dependent portion of 11 states' personal exemptions Adds Child Poverty Impact Dashboard contrib reforms that split the dependent portion of each state's per-person personal exemption (or credit) into its own variable + parameters, mirroring the existing RI/DE/OR/VA reforms. Each defaults to a no-op (in_effect=false; default amount reproduces current law) and lets the per-dependent amount be adjusted or zeroed independently of the head/spouse exemption. States and how they separate: - Bundled per-person exemption split into head/spouse + dependent: HI (preserves the aged add-on), IN (base; the additional per-child exemption is untouched), MI, NE, OK, VT, WI, WV. - AGI-stepped per-person amount (MD, OH): the dependent amount defaults to the baseline income-stepped schedule via a negative-amount sentinel, with an optional flat override and income phase-out group. - AR: redirects the existing person-level dependent slice of the personal credit to a contributed amount (its base is otherwise shared with head/spouse). No age limit, since it is summed via `adds`. Each reform exposes in_effect + amount (+ age_limit for the exemption states; + phaseout for MD/OH). Indiana's module dir is the Python keyword `in`, so it is imported dynamically in reforms.py and uses getattr for parameter access. Parameters under gov.contrib.states.{st}.dependent_exemption (AR: dependent_credit). Registered in reforms.py. Each state has YAML tests covering the default no-op, amount reduction/elimination, childless-filer no-op, and age-limit fallback; all pass. System smoke-imports cleanly. Co-Authored-By: Claude Opus 4.8 (1M context) --- SEP_MSG.tmp | 30 ++++ ...parate-state-dependent-exemptions.added.md | 1 + .../states/ar/dependent_credit/amount.yaml | 10 ++ .../states/ar/dependent_credit/in_effect.yaml | 7 + .../age_limit/in_effect.yaml | 9 ++ .../age_limit/threshold.yaml | 9 ++ .../states/hi/dependent_exemption/amount.yaml | 9 ++ .../hi/dependent_exemption/in_effect.yaml | 9 ++ .../age_limit/in_effect.yaml | 9 ++ .../age_limit/threshold.yaml | 9 ++ .../states/in/dependent_exemption/amount.yaml | 9 ++ .../in/dependent_exemption/in_effect.yaml | 9 ++ .../age_limit/in_effect.yaml | 7 + .../age_limit/threshold.yaml | 7 + .../states/md/dependent_exemption/amount.yaml | 7 + .../md/dependent_exemption/in_effect.yaml | 7 + .../md/dependent_exemption/phaseout/rate.yaml | 7 + .../phaseout/threshold.yaml | 17 +++ .../age_limit/in_effect.yaml | 9 ++ .../age_limit/threshold.yaml | 9 ++ .../states/mi/dependent_exemption/amount.yaml | 9 ++ .../mi/dependent_exemption/in_effect.yaml | 9 ++ .../age_limit/in_effect.yaml | 9 ++ .../age_limit/threshold.yaml | 9 ++ .../states/ne/dependent_exemption/amount.yaml | 9 ++ .../ne/dependent_exemption/in_effect.yaml | 9 ++ .../age_limit/in_effect.yaml | 7 + .../age_limit/threshold.yaml | 7 + .../states/oh/dependent_exemption/amount.yaml | 7 + .../oh/dependent_exemption/in_effect.yaml | 7 + .../oh/dependent_exemption/phaseout/rate.yaml | 7 + .../phaseout/threshold.yaml | 17 +++ .../age_limit/in_effect.yaml | 9 ++ .../age_limit/threshold.yaml | 9 ++ .../states/ok/dependent_exemption/amount.yaml | 9 ++ .../ok/dependent_exemption/in_effect.yaml | 9 ++ .../age_limit/in_effect.yaml | 9 ++ .../age_limit/threshold.yaml | 9 ++ .../states/vt/dependent_exemption/amount.yaml | 15 ++ .../vt/dependent_exemption/in_effect.yaml | 9 ++ .../age_limit/in_effect.yaml | 9 ++ .../age_limit/threshold.yaml | 9 ++ .../states/wi/dependent_exemption/amount.yaml | 9 ++ .../wi/dependent_exemption/in_effect.yaml | 9 ++ .../age_limit/in_effect.yaml | 9 ++ .../age_limit/threshold.yaml | 9 ++ .../states/wv/dependent_exemption/amount.yaml | 9 ++ .../wv/dependent_exemption/in_effect.yaml | 9 ++ policyengine_us/reforms/reforms.py | 60 ++++++++ .../states/ar/dependent_credit/__init__.py | 5 + .../ar_dependent_credit_reform.py | 55 +++++++ .../states/hi/dependent_exemption/__init__.py | 5 + .../hi_dependent_exemption_reform.py | 127 ++++++++++++++++ policyengine_us/reforms/states/in/__init__.py | 1 + .../states/in/dependent_exemption/__init__.py | 5 + .../in_dependent_exemption_reform.py | 120 +++++++++++++++ .../states/md/dependent_exemption/__init__.py | 5 + .../md_dependent_exemption_reform.py | 142 ++++++++++++++++++ .../states/mi/dependent_exemption/__init__.py | 5 + .../mi_dependent_exemption_reform.py | 121 +++++++++++++++ .../ne_dependent_exemption_reform.py | 117 +++++++++++++++ .../states/oh/dependent_exemption/__init__.py | 5 + .../oh_dependent_exemption_reform.py | 129 ++++++++++++++++ policyengine_us/reforms/states/ok/__init__.py | 1 + .../states/ok/dependent_exemption/__init__.py | 5 + .../ok_dependent_exemption_reform.py | 119 +++++++++++++++ policyengine_us/reforms/states/vt/__init__.py | 1 + .../states/vt/dependent_exemption/__init__.py | 5 + .../vt_dependent_exemption_reform.py | 120 +++++++++++++++ .../wi_dependent_exemption_reform.py | 121 +++++++++++++++ .../states/wv/dependent_exemption/__init__.py | 5 + .../wv_dependent_exemption_reform.py | 121 +++++++++++++++ .../ar/dependent_credit_reform_test.yaml | 51 +++++++ .../hi/dependent_exemption_reform_test.yaml | 66 ++++++++ .../in/dependent_exemption_reform_test.yaml | 106 +++++++++++++ .../md/dependent_exemption_reform_test.yaml | 53 +++++++ .../mi/dependent_exemption_reform_test.yaml | 66 ++++++++ .../ne/dependent_exemption_reform_test.yaml | 53 +++++++ .../oh/dependent_exemption_reform_test.yaml | 53 +++++++ .../ok/dependent_exemption_reform_test.yaml | 106 +++++++++++++ .../vt/dependent_exemption_reform_test.yaml | 106 +++++++++++++ .../wi/dependent_exemption_reform_test.yaml | 53 +++++++ .../wv/dependent_exemption_reform_test.yaml | 53 +++++++ 83 files changed, 2612 insertions(+) create mode 100644 SEP_MSG.tmp create mode 100644 changelog.d/separate-state-dependent-exemptions.added.md create mode 100644 policyengine_us/parameters/gov/contrib/states/ar/dependent_credit/amount.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/ar/dependent_credit/in_effect.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/hi/dependent_exemption/age_limit/in_effect.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/hi/dependent_exemption/age_limit/threshold.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/hi/dependent_exemption/amount.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/hi/dependent_exemption/in_effect.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/in/dependent_exemption/age_limit/in_effect.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/in/dependent_exemption/age_limit/threshold.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/in/dependent_exemption/amount.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/in/dependent_exemption/in_effect.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/md/dependent_exemption/age_limit/in_effect.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/md/dependent_exemption/age_limit/threshold.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/md/dependent_exemption/amount.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/md/dependent_exemption/in_effect.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/md/dependent_exemption/phaseout/rate.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/md/dependent_exemption/phaseout/threshold.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/mi/dependent_exemption/age_limit/in_effect.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/mi/dependent_exemption/age_limit/threshold.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/mi/dependent_exemption/amount.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/mi/dependent_exemption/in_effect.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/ne/dependent_exemption/age_limit/in_effect.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/ne/dependent_exemption/age_limit/threshold.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/ne/dependent_exemption/amount.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/ne/dependent_exemption/in_effect.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/oh/dependent_exemption/age_limit/in_effect.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/oh/dependent_exemption/age_limit/threshold.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/oh/dependent_exemption/amount.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/oh/dependent_exemption/in_effect.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/oh/dependent_exemption/phaseout/rate.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/oh/dependent_exemption/phaseout/threshold.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/ok/dependent_exemption/age_limit/in_effect.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/ok/dependent_exemption/age_limit/threshold.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/ok/dependent_exemption/amount.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/ok/dependent_exemption/in_effect.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/vt/dependent_exemption/age_limit/in_effect.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/vt/dependent_exemption/age_limit/threshold.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/vt/dependent_exemption/amount.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/vt/dependent_exemption/in_effect.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/wi/dependent_exemption/age_limit/in_effect.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/wi/dependent_exemption/age_limit/threshold.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/wi/dependent_exemption/amount.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/wi/dependent_exemption/in_effect.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/wv/dependent_exemption/age_limit/in_effect.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/wv/dependent_exemption/age_limit/threshold.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/wv/dependent_exemption/amount.yaml create mode 100644 policyengine_us/parameters/gov/contrib/states/wv/dependent_exemption/in_effect.yaml create mode 100644 policyengine_us/reforms/states/ar/dependent_credit/__init__.py create mode 100644 policyengine_us/reforms/states/ar/dependent_credit/ar_dependent_credit_reform.py create mode 100644 policyengine_us/reforms/states/hi/dependent_exemption/__init__.py create mode 100644 policyengine_us/reforms/states/hi/dependent_exemption/hi_dependent_exemption_reform.py create mode 100644 policyengine_us/reforms/states/in/__init__.py create mode 100644 policyengine_us/reforms/states/in/dependent_exemption/__init__.py create mode 100644 policyengine_us/reforms/states/in/dependent_exemption/in_dependent_exemption_reform.py create mode 100644 policyengine_us/reforms/states/md/dependent_exemption/__init__.py create mode 100644 policyengine_us/reforms/states/md/dependent_exemption/md_dependent_exemption_reform.py create mode 100644 policyengine_us/reforms/states/mi/dependent_exemption/__init__.py create mode 100644 policyengine_us/reforms/states/mi/dependent_exemption/mi_dependent_exemption_reform.py create mode 100644 policyengine_us/reforms/states/ne/dependent_exemption/ne_dependent_exemption_reform.py create mode 100644 policyengine_us/reforms/states/oh/dependent_exemption/__init__.py create mode 100644 policyengine_us/reforms/states/oh/dependent_exemption/oh_dependent_exemption_reform.py create mode 100644 policyengine_us/reforms/states/ok/__init__.py create mode 100644 policyengine_us/reforms/states/ok/dependent_exemption/__init__.py create mode 100644 policyengine_us/reforms/states/ok/dependent_exemption/ok_dependent_exemption_reform.py create mode 100644 policyengine_us/reforms/states/vt/__init__.py create mode 100644 policyengine_us/reforms/states/vt/dependent_exemption/__init__.py create mode 100644 policyengine_us/reforms/states/vt/dependent_exemption/vt_dependent_exemption_reform.py create mode 100644 policyengine_us/reforms/states/wi/dependent_exemption/wi_dependent_exemption_reform.py create mode 100644 policyengine_us/reforms/states/wv/dependent_exemption/__init__.py create mode 100644 policyengine_us/reforms/states/wv/dependent_exemption/wv_dependent_exemption_reform.py create mode 100644 policyengine_us/tests/policy/contrib/states/ar/dependent_credit_reform_test.yaml create mode 100644 policyengine_us/tests/policy/contrib/states/hi/dependent_exemption_reform_test.yaml create mode 100644 policyengine_us/tests/policy/contrib/states/in/dependent_exemption_reform_test.yaml create mode 100644 policyengine_us/tests/policy/contrib/states/md/dependent_exemption_reform_test.yaml create mode 100644 policyengine_us/tests/policy/contrib/states/mi/dependent_exemption_reform_test.yaml create mode 100644 policyengine_us/tests/policy/contrib/states/ne/dependent_exemption_reform_test.yaml create mode 100644 policyengine_us/tests/policy/contrib/states/oh/dependent_exemption_reform_test.yaml create mode 100644 policyengine_us/tests/policy/contrib/states/ok/dependent_exemption_reform_test.yaml create mode 100644 policyengine_us/tests/policy/contrib/states/vt/dependent_exemption_reform_test.yaml create mode 100644 policyengine_us/tests/policy/contrib/states/wi/dependent_exemption_reform_test.yaml create mode 100644 policyengine_us/tests/policy/contrib/states/wv/dependent_exemption_reform_test.yaml diff --git a/SEP_MSG.tmp b/SEP_MSG.tmp new file mode 100644 index 00000000000..fa4b5e7f334 --- /dev/null +++ b/SEP_MSG.tmp @@ -0,0 +1,30 @@ +Separate the dependent portion of 11 states' personal exemptions + +Adds Child Poverty Impact Dashboard contrib reforms that split the dependent +portion of each state's per-person personal exemption (or credit) into its own +variable + parameters, mirroring the existing RI/DE/OR/VA reforms. Each defaults +to a no-op (in_effect=false; default amount reproduces current law) and lets the +per-dependent amount be adjusted or zeroed independently of the head/spouse +exemption. + +States and how they separate: +- Bundled per-person exemption split into head/spouse + dependent: HI (preserves + the aged add-on), IN (base; the additional per-child exemption is untouched), + MI, NE, OK, VT, WI, WV. +- AGI-stepped per-person amount (MD, OH): the dependent amount defaults to the + baseline income-stepped schedule via a negative-amount sentinel, with an + optional flat override and income phase-out group. +- AR: redirects the existing person-level dependent slice of the personal credit + to a contributed amount (its base is otherwise shared with head/spouse). No + age limit, since it is summed via `adds`. + +Each reform exposes in_effect + amount (+ age_limit for the exemption states; ++ phaseout for MD/OH). Indiana's module dir is the Python keyword `in`, so it is +imported dynamically in reforms.py and uses getattr for parameter access. + +Parameters under gov.contrib.states.{st}.dependent_exemption (AR: +dependent_credit). Registered in reforms.py. Each state has YAML tests covering +the default no-op, amount reduction/elimination, childless-filer no-op, and +age-limit fallback; all pass. System smoke-imports cleanly. + +Co-Authored-By: Claude Opus 4.8 (1M context) diff --git a/changelog.d/separate-state-dependent-exemptions.added.md b/changelog.d/separate-state-dependent-exemptions.added.md new file mode 100644 index 00000000000..04f3ff4cdb4 --- /dev/null +++ b/changelog.d/separate-state-dependent-exemptions.added.md @@ -0,0 +1 @@ +Added contributed reforms that separate the dependent portion of the personal exemption (or credit) for 11 states (AR, HI, IN, MD, MI, NE, OH, OK, VT, WI, WV), so the per-dependent amount can be adjusted or eliminated independently of the head/spouse exemption. diff --git a/policyengine_us/parameters/gov/contrib/states/ar/dependent_credit/amount.yaml b/policyengine_us/parameters/gov/contrib/states/ar/dependent_credit/amount.yaml new file mode 100644 index 00000000000..bde0981c8a5 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/ar/dependent_credit/amount.yaml @@ -0,0 +1,10 @@ +description: Arkansas provides this personal tax credit amount per dependent under the dependent credit reform. +values: + 2021-01-01: 29 +metadata: + unit: currency-USD + period: year + label: Arkansas dependent credit amount + reference: + - title: Ark. Code 26-51-501 + href: https://casetext.com/statute/arkansas-code-of-1987/title-26-taxation/subtitle-5-state-taxes/chapter-51-income-taxes/subchapter-5-tax-credits-generally/section-26-51-501-personal-tax-credits diff --git a/policyengine_us/parameters/gov/contrib/states/ar/dependent_credit/in_effect.yaml b/policyengine_us/parameters/gov/contrib/states/ar/dependent_credit/in_effect.yaml new file mode 100644 index 00000000000..c1022aedba7 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/ar/dependent_credit/in_effect.yaml @@ -0,0 +1,7 @@ +description: Arkansas applies the dependent credit reform, if this is true (separates the dependent portion of the personal tax credit so its amount can be set independently). +values: + 0000-01-01: false +metadata: + unit: bool + period: year + label: Arkansas dependent credit reform in effect diff --git a/policyengine_us/parameters/gov/contrib/states/hi/dependent_exemption/age_limit/in_effect.yaml b/policyengine_us/parameters/gov/contrib/states/hi/dependent_exemption/age_limit/in_effect.yaml new file mode 100644 index 00000000000..da2dd81826d --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/hi/dependent_exemption/age_limit/in_effect.yaml @@ -0,0 +1,9 @@ +description: Hawaii limits the dependent exemption to dependents under a certain age, if this is true. + +values: + 0000-01-01: false + +metadata: + unit: bool + period: year + label: Hawaii dependent exemption age limit in effect diff --git a/policyengine_us/parameters/gov/contrib/states/hi/dependent_exemption/age_limit/threshold.yaml b/policyengine_us/parameters/gov/contrib/states/hi/dependent_exemption/age_limit/threshold.yaml new file mode 100644 index 00000000000..a30bed43d49 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/hi/dependent_exemption/age_limit/threshold.yaml @@ -0,0 +1,9 @@ +description: Hawaii limits the dependent exemption to dependents under this age. + +values: + 2025-01-01: 18 + +metadata: + label: Hawaii dependent exemption age threshold + period: year + unit: year diff --git a/policyengine_us/parameters/gov/contrib/states/hi/dependent_exemption/amount.yaml b/policyengine_us/parameters/gov/contrib/states/hi/dependent_exemption/amount.yaml new file mode 100644 index 00000000000..f6f9c3ea6a0 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/hi/dependent_exemption/amount.yaml @@ -0,0 +1,9 @@ +description: Hawaii provides this dependent exemption amount. + +values: + 2021-01-01: 1_144 + +metadata: + label: Hawaii dependent exemption amount + period: year + unit: currency-USD diff --git a/policyengine_us/parameters/gov/contrib/states/hi/dependent_exemption/in_effect.yaml b/policyengine_us/parameters/gov/contrib/states/hi/dependent_exemption/in_effect.yaml new file mode 100644 index 00000000000..c9a48b5b246 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/hi/dependent_exemption/in_effect.yaml @@ -0,0 +1,9 @@ +description: Hawaii applies the dependent exemption reform, if this is true (modifies only dependent exemptions, not personal exemptions). + +values: + 0000-01-01: false + +metadata: + unit: bool + period: year + label: Hawaii dependent exemption reform in effect diff --git a/policyengine_us/parameters/gov/contrib/states/in/dependent_exemption/age_limit/in_effect.yaml b/policyengine_us/parameters/gov/contrib/states/in/dependent_exemption/age_limit/in_effect.yaml new file mode 100644 index 00000000000..f10efd71d67 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/in/dependent_exemption/age_limit/in_effect.yaml @@ -0,0 +1,9 @@ +description: Indiana limits the dependent exemption to dependents under a certain age, if this is true. + +values: + 0000-01-01: false + +metadata: + unit: bool + period: year + label: Indiana dependent exemption age limit in effect diff --git a/policyengine_us/parameters/gov/contrib/states/in/dependent_exemption/age_limit/threshold.yaml b/policyengine_us/parameters/gov/contrib/states/in/dependent_exemption/age_limit/threshold.yaml new file mode 100644 index 00000000000..aaf3607b915 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/in/dependent_exemption/age_limit/threshold.yaml @@ -0,0 +1,9 @@ +description: Indiana limits the dependent exemption to dependents under this age. + +values: + 2025-01-01: 18 + +metadata: + label: Indiana dependent exemption age threshold + period: year + unit: year diff --git a/policyengine_us/parameters/gov/contrib/states/in/dependent_exemption/amount.yaml b/policyengine_us/parameters/gov/contrib/states/in/dependent_exemption/amount.yaml new file mode 100644 index 00000000000..a7dc0b468af --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/in/dependent_exemption/amount.yaml @@ -0,0 +1,9 @@ +description: Indiana provides this dependent exemption amount. + +values: + 2021-01-01: 1_000 + +metadata: + label: Indiana dependent exemption amount + period: year + unit: currency-USD diff --git a/policyengine_us/parameters/gov/contrib/states/in/dependent_exemption/in_effect.yaml b/policyengine_us/parameters/gov/contrib/states/in/dependent_exemption/in_effect.yaml new file mode 100644 index 00000000000..fd9fdfefc25 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/in/dependent_exemption/in_effect.yaml @@ -0,0 +1,9 @@ +description: Indiana applies the dependent exemption reform, if this is true (modifies only dependent exemptions, not personal exemptions). + +values: + 0000-01-01: false + +metadata: + unit: bool + period: year + label: Indiana dependent exemption reform in effect diff --git a/policyengine_us/parameters/gov/contrib/states/md/dependent_exemption/age_limit/in_effect.yaml b/policyengine_us/parameters/gov/contrib/states/md/dependent_exemption/age_limit/in_effect.yaml new file mode 100644 index 00000000000..5a93afc83f3 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/md/dependent_exemption/age_limit/in_effect.yaml @@ -0,0 +1,7 @@ +description: Maryland limits the dependent exemption to dependents under a certain age, if this is true. +values: + 0000-01-01: false +metadata: + unit: bool + period: year + label: Maryland dependent exemption age limit in effect diff --git a/policyengine_us/parameters/gov/contrib/states/md/dependent_exemption/age_limit/threshold.yaml b/policyengine_us/parameters/gov/contrib/states/md/dependent_exemption/age_limit/threshold.yaml new file mode 100644 index 00000000000..a5f6186062c --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/md/dependent_exemption/age_limit/threshold.yaml @@ -0,0 +1,7 @@ +description: Maryland limits the dependent exemption to dependents under this age. +values: + 0000-01-01: 18 +metadata: + unit: year + period: year + label: Maryland dependent exemption age threshold diff --git a/policyengine_us/parameters/gov/contrib/states/md/dependent_exemption/amount.yaml b/policyengine_us/parameters/gov/contrib/states/md/dependent_exemption/amount.yaml new file mode 100644 index 00000000000..793d690454d --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/md/dependent_exemption/amount.yaml @@ -0,0 +1,7 @@ +description: Maryland dependent exemption amount per dependent. A negative value is a sentinel meaning the baseline per-person (income-stepped) amount is used, so the reform at default is a no-op; set a non-negative value to apply a flat per-dependent amount. +values: + 0000-01-01: -1 +metadata: + unit: currency-USD + period: year + label: Maryland dependent exemption amount diff --git a/policyengine_us/parameters/gov/contrib/states/md/dependent_exemption/in_effect.yaml b/policyengine_us/parameters/gov/contrib/states/md/dependent_exemption/in_effect.yaml new file mode 100644 index 00000000000..0a45f121dcf --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/md/dependent_exemption/in_effect.yaml @@ -0,0 +1,7 @@ +description: Maryland applies the dependent exemption reform, if this is true (separates the dependent portion of the personal exemption). +values: + 0000-01-01: false +metadata: + unit: bool + period: year + label: Maryland dependent exemption reform in effect diff --git a/policyengine_us/parameters/gov/contrib/states/md/dependent_exemption/phaseout/rate.yaml b/policyengine_us/parameters/gov/contrib/states/md/dependent_exemption/phaseout/rate.yaml new file mode 100644 index 00000000000..9debb6871fc --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/md/dependent_exemption/phaseout/rate.yaml @@ -0,0 +1,7 @@ +description: Maryland phases out the dependent exemption at this rate per dollar of adjusted gross income above the threshold. +values: + 0000-01-01: 0 +metadata: + unit: /1 + period: year + label: Maryland dependent exemption phaseout rate diff --git a/policyengine_us/parameters/gov/contrib/states/md/dependent_exemption/phaseout/threshold.yaml b/policyengine_us/parameters/gov/contrib/states/md/dependent_exemption/phaseout/threshold.yaml new file mode 100644 index 00000000000..2e03cf9a945 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/md/dependent_exemption/phaseout/threshold.yaml @@ -0,0 +1,17 @@ +description: Maryland phases the dependent exemption out for filers with adjusted gross income above this threshold, by filing status. +metadata: + unit: currency-USD + period: year + label: Maryland dependent exemption phaseout threshold + breakdown: + - filing_status +JOINT: + 0000-01-01: 0 +HEAD_OF_HOUSEHOLD: + 0000-01-01: 0 +SURVIVING_SPOUSE: + 0000-01-01: 0 +SINGLE: + 0000-01-01: 0 +SEPARATE: + 0000-01-01: 0 diff --git a/policyengine_us/parameters/gov/contrib/states/mi/dependent_exemption/age_limit/in_effect.yaml b/policyengine_us/parameters/gov/contrib/states/mi/dependent_exemption/age_limit/in_effect.yaml new file mode 100644 index 00000000000..1d79b5b74c7 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/mi/dependent_exemption/age_limit/in_effect.yaml @@ -0,0 +1,9 @@ +description: Michigan limits the dependent exemption to dependents under a certain age, if this is true. + +values: + 0000-01-01: false + +metadata: + unit: bool + period: year + label: Michigan dependent exemption age limit in effect diff --git a/policyengine_us/parameters/gov/contrib/states/mi/dependent_exemption/age_limit/threshold.yaml b/policyengine_us/parameters/gov/contrib/states/mi/dependent_exemption/age_limit/threshold.yaml new file mode 100644 index 00000000000..41bf2302bd1 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/mi/dependent_exemption/age_limit/threshold.yaml @@ -0,0 +1,9 @@ +description: Michigan limits the dependent exemption to dependents under this age. + +values: + 2025-01-01: 18 + +metadata: + label: Michigan dependent exemption age threshold + period: year + unit: year diff --git a/policyengine_us/parameters/gov/contrib/states/mi/dependent_exemption/amount.yaml b/policyengine_us/parameters/gov/contrib/states/mi/dependent_exemption/amount.yaml new file mode 100644 index 00000000000..e2f76b582b2 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/mi/dependent_exemption/amount.yaml @@ -0,0 +1,9 @@ +description: Michigan provides this dependent exemption amount. + +values: + 2025-01-01: 5_800 + +metadata: + label: Michigan dependent exemption amount + period: year + unit: currency-USD diff --git a/policyengine_us/parameters/gov/contrib/states/mi/dependent_exemption/in_effect.yaml b/policyengine_us/parameters/gov/contrib/states/mi/dependent_exemption/in_effect.yaml new file mode 100644 index 00000000000..0a37d7efd86 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/mi/dependent_exemption/in_effect.yaml @@ -0,0 +1,9 @@ +description: Michigan applies the dependent exemption reform, if this is true (modifies only dependent exemptions, not personal exemptions). + +values: + 0000-01-01: false + +metadata: + unit: bool + period: year + label: Michigan dependent exemption reform in effect diff --git a/policyengine_us/parameters/gov/contrib/states/ne/dependent_exemption/age_limit/in_effect.yaml b/policyengine_us/parameters/gov/contrib/states/ne/dependent_exemption/age_limit/in_effect.yaml new file mode 100644 index 00000000000..5adb41b487f --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/ne/dependent_exemption/age_limit/in_effect.yaml @@ -0,0 +1,9 @@ +description: Nebraska limits the dependent exemption to dependents under a certain age, if this is true. + +values: + 0000-01-01: false + +metadata: + unit: bool + period: year + label: Nebraska dependent exemption age limit in effect diff --git a/policyengine_us/parameters/gov/contrib/states/ne/dependent_exemption/age_limit/threshold.yaml b/policyengine_us/parameters/gov/contrib/states/ne/dependent_exemption/age_limit/threshold.yaml new file mode 100644 index 00000000000..d7345e7fbe6 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/ne/dependent_exemption/age_limit/threshold.yaml @@ -0,0 +1,9 @@ +description: Nebraska limits the dependent exemption to dependents under this age. + +values: + 2025-01-01: 18 + +metadata: + label: Nebraska dependent exemption age threshold + period: year + unit: year diff --git a/policyengine_us/parameters/gov/contrib/states/ne/dependent_exemption/amount.yaml b/policyengine_us/parameters/gov/contrib/states/ne/dependent_exemption/amount.yaml new file mode 100644 index 00000000000..c9822e3a920 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/ne/dependent_exemption/amount.yaml @@ -0,0 +1,9 @@ +description: Nebraska provides this dependent exemption amount. + +values: + 2025-01-01: 171 + +metadata: + label: Nebraska dependent exemption amount + period: year + unit: currency-USD diff --git a/policyengine_us/parameters/gov/contrib/states/ne/dependent_exemption/in_effect.yaml b/policyengine_us/parameters/gov/contrib/states/ne/dependent_exemption/in_effect.yaml new file mode 100644 index 00000000000..48f1679e4a6 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/ne/dependent_exemption/in_effect.yaml @@ -0,0 +1,9 @@ +description: Nebraska applies the dependent exemption reform, if this is true (modifies only dependent exemptions, not personal exemptions). + +values: + 0000-01-01: false + +metadata: + unit: bool + period: year + label: Nebraska dependent exemption reform in effect diff --git a/policyengine_us/parameters/gov/contrib/states/oh/dependent_exemption/age_limit/in_effect.yaml b/policyengine_us/parameters/gov/contrib/states/oh/dependent_exemption/age_limit/in_effect.yaml new file mode 100644 index 00000000000..18a81e993ec --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/oh/dependent_exemption/age_limit/in_effect.yaml @@ -0,0 +1,7 @@ +description: Ohio limits the dependent exemption to dependents under a certain age, if this is true. +values: + 0000-01-01: false +metadata: + unit: bool + period: year + label: Ohio dependent exemption age limit in effect diff --git a/policyengine_us/parameters/gov/contrib/states/oh/dependent_exemption/age_limit/threshold.yaml b/policyengine_us/parameters/gov/contrib/states/oh/dependent_exemption/age_limit/threshold.yaml new file mode 100644 index 00000000000..a87f264de83 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/oh/dependent_exemption/age_limit/threshold.yaml @@ -0,0 +1,7 @@ +description: Ohio limits the dependent exemption to dependents under this age. +values: + 0000-01-01: 18 +metadata: + unit: year + period: year + label: Ohio dependent exemption age threshold diff --git a/policyengine_us/parameters/gov/contrib/states/oh/dependent_exemption/amount.yaml b/policyengine_us/parameters/gov/contrib/states/oh/dependent_exemption/amount.yaml new file mode 100644 index 00000000000..66009d5ccf0 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/oh/dependent_exemption/amount.yaml @@ -0,0 +1,7 @@ +description: Ohio dependent exemption amount per dependent. A negative value is a sentinel meaning the baseline per-person (income-stepped) amount is used, so the reform at default is a no-op; set a non-negative value to apply a flat per-dependent amount. +values: + 0000-01-01: -1 +metadata: + unit: currency-USD + period: year + label: Ohio dependent exemption amount diff --git a/policyengine_us/parameters/gov/contrib/states/oh/dependent_exemption/in_effect.yaml b/policyengine_us/parameters/gov/contrib/states/oh/dependent_exemption/in_effect.yaml new file mode 100644 index 00000000000..1cdd4bdf45f --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/oh/dependent_exemption/in_effect.yaml @@ -0,0 +1,7 @@ +description: Ohio applies the dependent exemption reform, if this is true (separates the dependent portion of the personal exemption). +values: + 0000-01-01: false +metadata: + unit: bool + period: year + label: Ohio dependent exemption reform in effect diff --git a/policyengine_us/parameters/gov/contrib/states/oh/dependent_exemption/phaseout/rate.yaml b/policyengine_us/parameters/gov/contrib/states/oh/dependent_exemption/phaseout/rate.yaml new file mode 100644 index 00000000000..63bef5fb5c6 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/oh/dependent_exemption/phaseout/rate.yaml @@ -0,0 +1,7 @@ +description: Ohio phases out the dependent exemption at this rate per dollar of adjusted gross income above the threshold. +values: + 0000-01-01: 0 +metadata: + unit: /1 + period: year + label: Ohio dependent exemption phaseout rate diff --git a/policyengine_us/parameters/gov/contrib/states/oh/dependent_exemption/phaseout/threshold.yaml b/policyengine_us/parameters/gov/contrib/states/oh/dependent_exemption/phaseout/threshold.yaml new file mode 100644 index 00000000000..e6a39710141 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/oh/dependent_exemption/phaseout/threshold.yaml @@ -0,0 +1,17 @@ +description: Ohio phases the dependent exemption out for filers with adjusted gross income above this threshold, by filing status. +metadata: + unit: currency-USD + period: year + label: Ohio dependent exemption phaseout threshold + breakdown: + - filing_status +JOINT: + 0000-01-01: 0 +HEAD_OF_HOUSEHOLD: + 0000-01-01: 0 +SURVIVING_SPOUSE: + 0000-01-01: 0 +SINGLE: + 0000-01-01: 0 +SEPARATE: + 0000-01-01: 0 diff --git a/policyengine_us/parameters/gov/contrib/states/ok/dependent_exemption/age_limit/in_effect.yaml b/policyengine_us/parameters/gov/contrib/states/ok/dependent_exemption/age_limit/in_effect.yaml new file mode 100644 index 00000000000..5d9907a1f64 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/ok/dependent_exemption/age_limit/in_effect.yaml @@ -0,0 +1,9 @@ +description: Oklahoma limits the dependent exemption to dependents under a certain age, if this is true. + +values: + 0000-01-01: false + +metadata: + unit: bool + period: year + label: Oklahoma dependent exemption age limit in effect diff --git a/policyengine_us/parameters/gov/contrib/states/ok/dependent_exemption/age_limit/threshold.yaml b/policyengine_us/parameters/gov/contrib/states/ok/dependent_exemption/age_limit/threshold.yaml new file mode 100644 index 00000000000..a5e2ca8f31a --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/ok/dependent_exemption/age_limit/threshold.yaml @@ -0,0 +1,9 @@ +description: Oklahoma limits the dependent exemption to dependents under this age. + +values: + 2025-01-01: 18 + +metadata: + label: Oklahoma dependent exemption age threshold + period: year + unit: year diff --git a/policyengine_us/parameters/gov/contrib/states/ok/dependent_exemption/amount.yaml b/policyengine_us/parameters/gov/contrib/states/ok/dependent_exemption/amount.yaml new file mode 100644 index 00000000000..3fcb9541c1e --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/ok/dependent_exemption/amount.yaml @@ -0,0 +1,9 @@ +description: Oklahoma provides this dependent exemption amount. + +values: + 2021-01-01: 1_000 + +metadata: + label: Oklahoma dependent exemption amount + period: year + unit: currency-USD diff --git a/policyengine_us/parameters/gov/contrib/states/ok/dependent_exemption/in_effect.yaml b/policyengine_us/parameters/gov/contrib/states/ok/dependent_exemption/in_effect.yaml new file mode 100644 index 00000000000..273e9e210ca --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/ok/dependent_exemption/in_effect.yaml @@ -0,0 +1,9 @@ +description: Oklahoma applies the dependent exemption reform, if this is true (modifies only dependent exemptions, not personal exemptions). + +values: + 0000-01-01: false + +metadata: + unit: bool + period: year + label: Oklahoma dependent exemption reform in effect diff --git a/policyengine_us/parameters/gov/contrib/states/vt/dependent_exemption/age_limit/in_effect.yaml b/policyengine_us/parameters/gov/contrib/states/vt/dependent_exemption/age_limit/in_effect.yaml new file mode 100644 index 00000000000..7247eebadd7 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/vt/dependent_exemption/age_limit/in_effect.yaml @@ -0,0 +1,9 @@ +description: Vermont limits the dependent exemption to dependents under a certain age, if this is true. + +values: + 0000-01-01: false + +metadata: + unit: bool + period: year + label: Vermont dependent exemption age limit in effect diff --git a/policyengine_us/parameters/gov/contrib/states/vt/dependent_exemption/age_limit/threshold.yaml b/policyengine_us/parameters/gov/contrib/states/vt/dependent_exemption/age_limit/threshold.yaml new file mode 100644 index 00000000000..e75174cf3c4 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/vt/dependent_exemption/age_limit/threshold.yaml @@ -0,0 +1,9 @@ +description: Vermont limits the dependent exemption to dependents under this age. + +values: + 2025-01-01: 18 + +metadata: + label: Vermont dependent exemption age threshold + period: year + unit: year diff --git a/policyengine_us/parameters/gov/contrib/states/vt/dependent_exemption/amount.yaml b/policyengine_us/parameters/gov/contrib/states/vt/dependent_exemption/amount.yaml new file mode 100644 index 00000000000..ac479fe1da6 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/vt/dependent_exemption/amount.yaml @@ -0,0 +1,15 @@ +description: Vermont provides this dependent exemption amount. + +values: + 2025-01-01: 5_300 +# Will uprate identically as the personal exemption + +metadata: + label: Vermont dependent exemption amount + period: year + unit: currency-USD + uprating: + parameter: gov.irs.uprating + rounding: + type: downwards + interval: 50 diff --git a/policyengine_us/parameters/gov/contrib/states/vt/dependent_exemption/in_effect.yaml b/policyengine_us/parameters/gov/contrib/states/vt/dependent_exemption/in_effect.yaml new file mode 100644 index 00000000000..46597e6ecfb --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/vt/dependent_exemption/in_effect.yaml @@ -0,0 +1,9 @@ +description: Vermont applies the dependent exemption reform, if this is true (modifies only dependent exemptions, not personal exemptions). + +values: + 0000-01-01: false + +metadata: + unit: bool + period: year + label: Vermont dependent exemption reform in effect diff --git a/policyengine_us/parameters/gov/contrib/states/wi/dependent_exemption/age_limit/in_effect.yaml b/policyengine_us/parameters/gov/contrib/states/wi/dependent_exemption/age_limit/in_effect.yaml new file mode 100644 index 00000000000..2bf825efa83 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/wi/dependent_exemption/age_limit/in_effect.yaml @@ -0,0 +1,9 @@ +description: Wisconsin limits the dependent exemption to dependents under a certain age, if this is true. + +values: + 0000-01-01: false + +metadata: + unit: bool + period: year + label: Wisconsin dependent exemption age limit in effect diff --git a/policyengine_us/parameters/gov/contrib/states/wi/dependent_exemption/age_limit/threshold.yaml b/policyengine_us/parameters/gov/contrib/states/wi/dependent_exemption/age_limit/threshold.yaml new file mode 100644 index 00000000000..02d266d4fec --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/wi/dependent_exemption/age_limit/threshold.yaml @@ -0,0 +1,9 @@ +description: Wisconsin limits the dependent exemption to dependents under this age. + +values: + 2025-01-01: 18 + +metadata: + label: Wisconsin dependent exemption age threshold + period: year + unit: year diff --git a/policyengine_us/parameters/gov/contrib/states/wi/dependent_exemption/amount.yaml b/policyengine_us/parameters/gov/contrib/states/wi/dependent_exemption/amount.yaml new file mode 100644 index 00000000000..04f1f92c369 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/wi/dependent_exemption/amount.yaml @@ -0,0 +1,9 @@ +description: Wisconsin provides this dependent exemption amount. + +values: + 2001-01-01: 700 + +metadata: + label: Wisconsin dependent exemption amount + period: year + unit: currency-USD diff --git a/policyengine_us/parameters/gov/contrib/states/wi/dependent_exemption/in_effect.yaml b/policyengine_us/parameters/gov/contrib/states/wi/dependent_exemption/in_effect.yaml new file mode 100644 index 00000000000..84783faa633 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/wi/dependent_exemption/in_effect.yaml @@ -0,0 +1,9 @@ +description: Wisconsin applies the dependent exemption reform, if this is true (modifies only dependent exemptions, not personal exemptions). + +values: + 0000-01-01: false + +metadata: + unit: bool + period: year + label: Wisconsin dependent exemption reform in effect diff --git a/policyengine_us/parameters/gov/contrib/states/wv/dependent_exemption/age_limit/in_effect.yaml b/policyengine_us/parameters/gov/contrib/states/wv/dependent_exemption/age_limit/in_effect.yaml new file mode 100644 index 00000000000..9c9bf0d03ad --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/wv/dependent_exemption/age_limit/in_effect.yaml @@ -0,0 +1,9 @@ +description: West Virginia limits the dependent exemption to dependents under a certain age, if this is true. + +values: + 0000-01-01: false + +metadata: + unit: bool + period: year + label: West Virginia dependent exemption age limit in effect diff --git a/policyengine_us/parameters/gov/contrib/states/wv/dependent_exemption/age_limit/threshold.yaml b/policyengine_us/parameters/gov/contrib/states/wv/dependent_exemption/age_limit/threshold.yaml new file mode 100644 index 00000000000..75c91c39a46 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/wv/dependent_exemption/age_limit/threshold.yaml @@ -0,0 +1,9 @@ +description: West Virginia limits the dependent exemption to dependents under this age. + +values: + 2025-01-01: 18 + +metadata: + label: West Virginia dependent exemption age threshold + period: year + unit: year diff --git a/policyengine_us/parameters/gov/contrib/states/wv/dependent_exemption/amount.yaml b/policyengine_us/parameters/gov/contrib/states/wv/dependent_exemption/amount.yaml new file mode 100644 index 00000000000..9a8768f0218 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/wv/dependent_exemption/amount.yaml @@ -0,0 +1,9 @@ +description: West Virginia provides this dependent exemption amount. + +values: + 1987-01-01: 2_000 + +metadata: + label: West Virginia dependent exemption amount + period: year + unit: currency-USD diff --git a/policyengine_us/parameters/gov/contrib/states/wv/dependent_exemption/in_effect.yaml b/policyengine_us/parameters/gov/contrib/states/wv/dependent_exemption/in_effect.yaml new file mode 100644 index 00000000000..3173be8e0d6 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/wv/dependent_exemption/in_effect.yaml @@ -0,0 +1,9 @@ +description: West Virginia applies the dependent exemption reform, if this is true (modifies only dependent exemptions, not personal exemptions). + +values: + 0000-01-01: false + +metadata: + unit: bool + period: year + label: West Virginia dependent exemption reform in effect diff --git a/policyengine_us/reforms/reforms.py b/policyengine_us/reforms/reforms.py index 1cb63204dce..65283bb4628 100644 --- a/policyengine_us/reforms/reforms.py +++ b/policyengine_us/reforms/reforms.py @@ -141,6 +141,44 @@ from .states.ri.exemption.ri_exemption_reform import ( create_ri_exemption_reform_fn, ) +from .states.ok.dependent_exemption.ok_dependent_exemption_reform import ( + create_ok_dependent_exemption_reform_fn, +) +from .states.vt.dependent_exemption.vt_dependent_exemption_reform import ( + create_vt_dependent_exemption_reform_fn, +) +from .states.mi.dependent_exemption.mi_dependent_exemption_reform import ( + create_mi_dependent_exemption_reform_fn, +) +from .states.ne.dependent_exemption.ne_dependent_exemption_reform import ( + create_ne_dependent_exemption_reform_fn, +) +from .states.wv.dependent_exemption.wv_dependent_exemption_reform import ( + create_wv_dependent_exemption_reform_fn, +) +from .states.wi.dependent_exemption.wi_dependent_exemption_reform import ( + create_wi_dependent_exemption_reform_fn, +) +from .states.hi.dependent_exemption.hi_dependent_exemption_reform import ( + create_hi_dependent_exemption_reform_fn, +) +from .states.md.dependent_exemption.md_dependent_exemption_reform import ( + create_md_dependent_exemption_reform_fn, +) +from .states.oh.dependent_exemption.oh_dependent_exemption_reform import ( + create_oh_dependent_exemption_reform_fn, +) +from .states.ar.dependent_credit.ar_dependent_credit_reform import ( + create_ar_dependent_credit_reform_fn, +) +import importlib as _importlib + +# Indiana's module dir is `in`, a Python keyword, so it can't be imported with +# a dotted ``from .states.in...`` path; load it dynamically instead. +create_in_dependent_exemption_reform_fn = _importlib.import_module( + "policyengine_us.reforms.states.in.dependent_exemption." + "in_dependent_exemption_reform" +).create_in_dependent_exemption_reform_fn from .states.de.dependent_credit.de_dependent_credit_reform import ( create_de_dependent_credit_reform_fn, ) @@ -402,6 +440,17 @@ def create_structural_reforms_from_parameters(parameters, period): ) ri_ctc = create_ri_ctc_reform(parameters, period) ri_exemption = create_ri_exemption_reform_fn(parameters, period) + ok_dependent_exemption = create_ok_dependent_exemption_reform_fn(parameters, period) + vt_dependent_exemption = create_vt_dependent_exemption_reform_fn(parameters, period) + in_dependent_exemption = create_in_dependent_exemption_reform_fn(parameters, period) + mi_dependent_exemption = create_mi_dependent_exemption_reform_fn(parameters, period) + ne_dependent_exemption = create_ne_dependent_exemption_reform_fn(parameters, period) + wv_dependent_exemption = create_wv_dependent_exemption_reform_fn(parameters, period) + wi_dependent_exemption = create_wi_dependent_exemption_reform_fn(parameters, period) + hi_dependent_exemption = create_hi_dependent_exemption_reform_fn(parameters, period) + md_dependent_exemption = create_md_dependent_exemption_reform_fn(parameters, period) + oh_dependent_exemption = create_oh_dependent_exemption_reform_fn(parameters, period) + ar_dependent_credit = create_ar_dependent_credit_reform_fn(parameters, period) de_dependent_credit = create_de_dependent_credit_reform_fn(parameters, period) or_dependent_exemption_credit = create_or_dependent_exemption_credit_reform_fn( parameters, period @@ -529,6 +578,17 @@ def create_structural_reforms_from_parameters(parameters, period): ctc_minimum_refundable_amount, ri_ctc, ri_exemption, + ok_dependent_exemption, + vt_dependent_exemption, + in_dependent_exemption, + mi_dependent_exemption, + ne_dependent_exemption, + wv_dependent_exemption, + wi_dependent_exemption, + hi_dependent_exemption, + md_dependent_exemption, + oh_dependent_exemption, + ar_dependent_credit, de_dependent_credit, or_dependent_exemption_credit, va_dependent_exemption, diff --git a/policyengine_us/reforms/states/ar/dependent_credit/__init__.py b/policyengine_us/reforms/states/ar/dependent_credit/__init__.py new file mode 100644 index 00000000000..b5e3f728066 --- /dev/null +++ b/policyengine_us/reforms/states/ar/dependent_credit/__init__.py @@ -0,0 +1,5 @@ +from .ar_dependent_credit_reform import ( + create_ar_dependent_credit, + create_ar_dependent_credit_reform_fn, + ar_dependent_credit_reform, +) diff --git a/policyengine_us/reforms/states/ar/dependent_credit/ar_dependent_credit_reform.py b/policyengine_us/reforms/states/ar/dependent_credit/ar_dependent_credit_reform.py new file mode 100644 index 00000000000..e09314a848a --- /dev/null +++ b/policyengine_us/reforms/states/ar/dependent_credit/ar_dependent_credit_reform.py @@ -0,0 +1,55 @@ +from policyengine_us.model_api import * +from policyengine_core.periods import period as period_ + + +def create_ar_dependent_credit() -> Reform: + class ar_personal_credit_dependent(Variable): + value_type = float + entity = Person + label = "Arkansas personal tax credit dependent amount" + unit = USD + definition_period = YEAR + defined_for = StateCode.AR + + def formula(person, period, parameters): + # Separate the dependent portion of Arkansas's personal tax credit + # so its per-dependent amount can be set independently of the + # head/spouse credit (which keeps reading personal.amount.base). + # No age limit: this person-level credit is consumed via ``adds`` + # in ar_personal_credits_potential, which only reflects uniform + # per-dependent amounts, so a per-dependent age gate is not exposed. + p = parameters(period).gov.contrib.states.ar.dependent_credit + is_dependent = person("is_tax_unit_dependent", period) + return is_dependent * p.amount + + class reform(Reform): + def apply(self): + self.update_variable(ar_personal_credit_dependent) + + return reform + + +def create_ar_dependent_credit_reform_fn(parameters, period, bypass: bool = False): + if bypass: + return create_ar_dependent_credit() + + p = parameters.gov.contrib.states.ar.dependent_credit + + reform_active = False + current_period = period_(period) + + for i in range(5): + if p(current_period).in_effect: + reform_active = True + break + current_period = current_period.offset(1, "year") + + if reform_active: + return create_ar_dependent_credit() + else: + return None + + +ar_dependent_credit_reform = create_ar_dependent_credit_reform_fn( + None, None, bypass=True +) diff --git a/policyengine_us/reforms/states/hi/dependent_exemption/__init__.py b/policyengine_us/reforms/states/hi/dependent_exemption/__init__.py new file mode 100644 index 00000000000..b6fde2835d8 --- /dev/null +++ b/policyengine_us/reforms/states/hi/dependent_exemption/__init__.py @@ -0,0 +1,5 @@ +from .hi_dependent_exemption_reform import ( + create_hi_dependent_exemption, + create_hi_dependent_exemption_reform_fn, + hi_dependent_exemption_reform, +) diff --git a/policyengine_us/reforms/states/hi/dependent_exemption/hi_dependent_exemption_reform.py b/policyengine_us/reforms/states/hi/dependent_exemption/hi_dependent_exemption_reform.py new file mode 100644 index 00000000000..aae373f44d8 --- /dev/null +++ b/policyengine_us/reforms/states/hi/dependent_exemption/hi_dependent_exemption_reform.py @@ -0,0 +1,127 @@ +from policyengine_us.model_api import * +from policyengine_core.periods import period as period_ +from policyengine_core.periods import instant + + +def create_hi_dependent_exemption() -> Reform: + class hi_eligible_dependents_count(Variable): + value_type = int + entity = TaxUnit + label = "Hawaii eligible dependents count" + definition_period = YEAR + defined_for = StateCode.HI + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.contrib.states.hi.dependent_exemption + + person = tax_unit.members + age = person("age", period) + is_dependent = person("is_tax_unit_dependent", period) + + # Apply age limit if in effect + if p.age_limit.in_effect: + age_threshold = p.age_limit.threshold + eligible_dependents = is_dependent & (age < age_threshold) + else: + eligible_dependents = is_dependent + + return tax_unit.sum(eligible_dependents) + + class hi_dependent_exemption(Variable): + value_type = float + entity = TaxUnit + label = "Hawaii dependent exemption" + unit = USD + definition_period = YEAR + defined_for = StateCode.HI + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.contrib.states.hi.dependent_exemption + + dependents_count = tax_unit("hi_eligible_dependents_count", period) + return dependents_count * p.amount + + class hi_older_dependents_count(Variable): + value_type = float + entity = TaxUnit + label = "Hawaii older dependents count" + unit = USD + definition_period = YEAR + defined_for = StateCode.HI + + def formula(tax_unit, period, parameters): + person = tax_unit.members + is_dependent = person("is_tax_unit_dependent", period) + total_dependents = tax_unit.sum(is_dependent) + eligible_dependent_exemptions = tax_unit( + "hi_eligible_dependents_count", period + ) + return max_(0, total_dependents - eligible_dependent_exemptions) + + class hi_regular_exemptions(Variable): + value_type = float + entity = TaxUnit + label = "Hawaii regular exemptions" + unit = USD + documentation = "https://files.hawaii.gov/tax/forms/2022/n11ins.pdf#page=20" + definition_period = YEAR + defined_for = StateCode.HI + + def formula(tax_unit, period, parameters): + exemptions_count = tax_unit("exemptions_count", period) + p = parameters(period).gov.states.hi.tax.income.exemptions + + # Personal exemptions exclude the dependent portion. Over-age + # dependents fall back to the personal count. + dependents = tax_unit("tax_unit_dependents", period) + older_dependents = tax_unit("hi_older_dependents_count", period) + personal_count = exemptions_count - dependents + older_dependents + + # Aged heads and spouses get an extra base exemption (preserved + # exactly as the baseline computes it). + person = tax_unit.members + head_or_spouse = person("is_tax_unit_head_or_spouse", period) + aged = person("age", period) >= p.aged_threshold + aged_head_spouse_count = tax_unit.sum(aged & head_or_spouse) + + personal_amount = (personal_count + aged_head_spouse_count) * p.base + + # Add separated dependent exemption. + dependent_exemption_amount = tax_unit("hi_dependent_exemption", period) + + return personal_amount + dependent_exemption_amount + + class reform(Reform): + def apply(self): + self.update_variable(hi_eligible_dependents_count) + self.update_variable(hi_dependent_exemption) + self.update_variable(hi_older_dependents_count) + self.update_variable(hi_regular_exemptions) + + return reform + + +def create_hi_dependent_exemption_reform_fn(parameters, period, bypass: bool = False): + if bypass: + return create_hi_dependent_exemption() + + p = parameters.gov.contrib.states.hi.dependent_exemption + + reform_active = False + current_period = period_(period) + + for i in range(5): + if p(current_period).in_effect: + reform_active = True + break + current_period = current_period.offset(1, "year") + + if reform_active: + return create_hi_dependent_exemption() + else: + return None + + +hi_dependent_exemption_reform = create_hi_dependent_exemption_reform_fn( + None, None, bypass=True +) diff --git a/policyengine_us/reforms/states/in/__init__.py b/policyengine_us/reforms/states/in/__init__.py new file mode 100644 index 00000000000..583dbc086a1 --- /dev/null +++ b/policyengine_us/reforms/states/in/__init__.py @@ -0,0 +1 @@ +from .dependent_exemption import create_in_dependent_exemption_reform_fn diff --git a/policyengine_us/reforms/states/in/dependent_exemption/__init__.py b/policyengine_us/reforms/states/in/dependent_exemption/__init__.py new file mode 100644 index 00000000000..be4a2802f3d --- /dev/null +++ b/policyengine_us/reforms/states/in/dependent_exemption/__init__.py @@ -0,0 +1,5 @@ +from .in_dependent_exemption_reform import ( + create_in_dependent_exemption, + create_in_dependent_exemption_reform_fn, + in_dependent_exemption_reform, +) diff --git a/policyengine_us/reforms/states/in/dependent_exemption/in_dependent_exemption_reform.py b/policyengine_us/reforms/states/in/dependent_exemption/in_dependent_exemption_reform.py new file mode 100644 index 00000000000..f0f5e7397e3 --- /dev/null +++ b/policyengine_us/reforms/states/in/dependent_exemption/in_dependent_exemption_reform.py @@ -0,0 +1,120 @@ +from policyengine_us.model_api import * +from policyengine_core.periods import period as period_ +from policyengine_core.periods import instant + + +def create_in_dependent_exemption() -> Reform: + class in_eligible_dependents_count(Variable): + value_type = int + entity = TaxUnit + label = "Indiana eligible dependents count" + definition_period = YEAR + defined_for = StateCode.IN + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.contrib.states["in"].dependent_exemption + + person = tax_unit.members + age = person("age", period) + is_dependent = person("is_tax_unit_dependent", period) + + # Apply age limit if in effect + if p.age_limit.in_effect: + age_threshold = p.age_limit.threshold + eligible_dependents = is_dependent & (age < age_threshold) + else: + eligible_dependents = is_dependent + + return tax_unit.sum(eligible_dependents) + + class in_dependent_exemption(Variable): + value_type = float + entity = TaxUnit + label = "Indiana dependent exemption" + unit = USD + definition_period = YEAR + defined_for = StateCode.IN + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.contrib.states["in"].dependent_exemption + + dependents_count = tax_unit("in_eligible_dependents_count", period) + return dependents_count * p.amount + + class in_older_dependents_count(Variable): + value_type = float + entity = TaxUnit + label = "Indiana older dependents count" + unit = USD + definition_period = YEAR + defined_for = StateCode.IN + + def formula(tax_unit, period, parameters): + person = tax_unit.members + is_dependent = person("is_tax_unit_dependent", period) + total_dependents = tax_unit.sum(is_dependent) + eligible_dependent_exemptions = tax_unit( + "in_eligible_dependents_count", period + ) + return max_(0, total_dependents - eligible_dependent_exemptions) + + class in_base_exemptions(Variable): + value_type = float + entity = TaxUnit + label = "Indiana base exemptions" + unit = USD + definition_period = YEAR + defined_for = StateCode.IN + + def formula(tax_unit, period, parameters): + p_base = parameters(period).gov.states["in"].tax.income.exemptions + + # Personal portion excludes the dependent portion of tax unit size. + tax_unit_size = tax_unit("tax_unit_size", period) + dependents = tax_unit("tax_unit_dependents", period) + older_dependents = tax_unit("in_older_dependents_count", period) + personal_count = tax_unit_size - dependents + older_dependents + personal_exemption_amount = personal_count * p_base.base.amount + + # Add separated dependent exemption. + dependent_exemption_amount = tax_unit("in_dependent_exemption", period) + + return personal_exemption_amount + dependent_exemption_amount + + class reform(Reform): + def apply(self): + self.update_variable(in_eligible_dependents_count) + self.update_variable(in_dependent_exemption) + self.update_variable(in_older_dependents_count) + self.update_variable(in_base_exemptions) + + return reform + + +def create_in_dependent_exemption_reform_fn(parameters, period, bypass: bool = False): + if bypass: + return create_in_dependent_exemption() + + # ``in`` is a Python keyword, so the contrib node can't be reached with + # attribute access; the build-time ParameterNode is not subscriptable + # either, so use getattr. + p = getattr(parameters.gov.contrib.states, "in").dependent_exemption + + reform_active = False + current_period = period_(period) + + for i in range(5): + if p(current_period).in_effect: + reform_active = True + break + current_period = current_period.offset(1, "year") + + if reform_active: + return create_in_dependent_exemption() + else: + return None + + +in_dependent_exemption_reform = create_in_dependent_exemption_reform_fn( + None, None, bypass=True +) diff --git a/policyengine_us/reforms/states/md/dependent_exemption/__init__.py b/policyengine_us/reforms/states/md/dependent_exemption/__init__.py new file mode 100644 index 00000000000..7157081e309 --- /dev/null +++ b/policyengine_us/reforms/states/md/dependent_exemption/__init__.py @@ -0,0 +1,5 @@ +from .md_dependent_exemption_reform import ( + create_md_dependent_exemption, + create_md_dependent_exemption_reform_fn, + md_dependent_exemption_reform, +) diff --git a/policyengine_us/reforms/states/md/dependent_exemption/md_dependent_exemption_reform.py b/policyengine_us/reforms/states/md/dependent_exemption/md_dependent_exemption_reform.py new file mode 100644 index 00000000000..5de77847de0 --- /dev/null +++ b/policyengine_us/reforms/states/md/dependent_exemption/md_dependent_exemption_reform.py @@ -0,0 +1,142 @@ +from policyengine_us.model_api import * +from policyengine_core.periods import period as period_ + + +def create_md_dependent_exemption() -> Reform: + class md_eligible_dependents_count(Variable): + value_type = int + entity = TaxUnit + label = "Maryland eligible dependents count" + definition_period = YEAR + defined_for = StateCode.MD + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.contrib.states.md.dependent_exemption + person = tax_unit.members + age = person("age", period) + is_dependent = person("is_tax_unit_dependent", period) + if p.age_limit.in_effect: + eligible = is_dependent & (age < p.age_limit.threshold) + else: + eligible = is_dependent + return tax_unit.sum(eligible) + + class md_dependent_exemption_maximum(Variable): + value_type = float + entity = TaxUnit + label = "Maryland dependent exemption maximum before phaseout" + unit = USD + definition_period = YEAR + defined_for = StateCode.MD + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.contrib.states.md.dependent_exemption + count = tax_unit("md_eligible_dependents_count", period) + # Negative amount is a sentinel meaning "use the baseline per-person + # AGI-stepped amount" (so in_effect=true at default is a no-op). A + # value >= 0 applies a flat per-dependent amount instead. + baseline_per = tax_unit("md_personal_exemption", period) + per_dependent = where(p.amount < 0, baseline_per, p.amount) + return count * per_dependent + + class md_dependent_exemption_phaseout(Variable): + value_type = float + entity = TaxUnit + label = "Maryland dependent exemption phaseout amount" + unit = USD + definition_period = YEAR + defined_for = StateCode.MD + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.contrib.states.md.dependent_exemption + filing_status = tax_unit("filing_status", period) + agi = tax_unit("adjusted_gross_income", period) + threshold = p.phaseout.threshold[filing_status] + excess = max_(agi - threshold, 0) + return excess * p.phaseout.rate + + class md_dependent_exemption(Variable): + value_type = float + entity = TaxUnit + label = "Maryland dependent exemption" + unit = USD + definition_period = YEAR + defined_for = StateCode.MD + + def formula(tax_unit, period, parameters): + maximum = tax_unit("md_dependent_exemption_maximum", period) + phaseout = tax_unit("md_dependent_exemption_phaseout", period) + return max_(maximum - phaseout, 0) + + class md_older_dependents_count(Variable): + value_type = float + entity = TaxUnit + label = "Maryland older dependents count" + unit = USD + definition_period = YEAR + defined_for = StateCode.MD + + def formula(tax_unit, period, parameters): + person = tax_unit.members + is_dependent = person("is_tax_unit_dependent", period) + total_dependents = tax_unit.sum(is_dependent) + eligible = tax_unit("md_eligible_dependents_count", period) + return max_(0, total_dependents - eligible) + + class md_total_personal_exemptions(Variable): + value_type = float + entity = TaxUnit + label = "MD total personal exemptions" + unit = USD + definition_period = YEAR + defined_for = StateCode.MD + + def formula(tax_unit, period, parameters): + # Personal portion keeps the baseline per-person (AGI-stepped) + # amount; dependents are separated out. + per_person = tax_unit("md_personal_exemption", period) + tax_unit_size = tax_unit("tax_unit_size", period) + dependents = tax_unit("tax_unit_dependents", period) + older_dependents = tax_unit("md_older_dependents_count", period) + personal_count = tax_unit_size - dependents + older_dependents + personal_amount = personal_count * per_person + + dependent_exemption_amount = tax_unit("md_dependent_exemption", period) + return personal_amount + dependent_exemption_amount + + class reform(Reform): + def apply(self): + self.update_variable(md_eligible_dependents_count) + self.update_variable(md_dependent_exemption_maximum) + self.update_variable(md_dependent_exemption_phaseout) + self.update_variable(md_dependent_exemption) + self.update_variable(md_older_dependents_count) + self.update_variable(md_total_personal_exemptions) + + return reform + + +def create_md_dependent_exemption_reform_fn(parameters, period, bypass: bool = False): + if bypass: + return create_md_dependent_exemption() + + p = parameters.gov.contrib.states.md.dependent_exemption + + reform_active = False + current_period = period_(period) + + for i in range(5): + if p(current_period).in_effect: + reform_active = True + break + current_period = current_period.offset(1, "year") + + if reform_active: + return create_md_dependent_exemption() + else: + return None + + +md_dependent_exemption_reform = create_md_dependent_exemption_reform_fn( + None, None, bypass=True +) diff --git a/policyengine_us/reforms/states/mi/dependent_exemption/__init__.py b/policyengine_us/reforms/states/mi/dependent_exemption/__init__.py new file mode 100644 index 00000000000..65243945c34 --- /dev/null +++ b/policyengine_us/reforms/states/mi/dependent_exemption/__init__.py @@ -0,0 +1,5 @@ +from .mi_dependent_exemption_reform import ( + create_mi_dependent_exemption, + create_mi_dependent_exemption_reform_fn, + mi_dependent_exemption_reform, +) diff --git a/policyengine_us/reforms/states/mi/dependent_exemption/mi_dependent_exemption_reform.py b/policyengine_us/reforms/states/mi/dependent_exemption/mi_dependent_exemption_reform.py new file mode 100644 index 00000000000..2b450c2abb1 --- /dev/null +++ b/policyengine_us/reforms/states/mi/dependent_exemption/mi_dependent_exemption_reform.py @@ -0,0 +1,121 @@ +from policyengine_us.model_api import * +from policyengine_core.periods import period as period_ +from policyengine_core.periods import instant + + +def create_mi_dependent_exemption() -> Reform: + class mi_eligible_dependents_count(Variable): + value_type = int + entity = TaxUnit + label = "Michigan eligible dependents count" + definition_period = YEAR + defined_for = StateCode.MI + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.contrib.states.mi.dependent_exemption + + person = tax_unit.members + age = person("age", period) + is_dependent = person("is_tax_unit_dependent", period) + + # Apply age limit if in effect + if p.age_limit.in_effect: + age_threshold = p.age_limit.threshold + eligible_dependents = is_dependent & (age < age_threshold) + else: + eligible_dependents = is_dependent + + return tax_unit.sum(eligible_dependents) + + class mi_dependent_exemption(Variable): + value_type = float + entity = TaxUnit + label = "Michigan dependent exemption" + unit = USD + definition_period = YEAR + defined_for = StateCode.MI + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.contrib.states.mi.dependent_exemption + + dependents_count = tax_unit("mi_eligible_dependents_count", period) + return dependents_count * p.amount + + class mi_older_dependents_count(Variable): + value_type = float + entity = TaxUnit + label = "Michigan older dependents count" + unit = USD + definition_period = YEAR + defined_for = StateCode.MI + + def formula(tax_unit, period, parameters): + person = tax_unit.members + is_dependent = person("is_tax_unit_dependent", period) + total_dependents = tax_unit.sum(is_dependent) + eligible_dependent_exemptions = tax_unit( + "mi_eligible_dependents_count", period + ) + return max_(0, total_dependents - eligible_dependent_exemptions) + + class mi_personal_exemptions(Variable): + value_type = float + entity = TaxUnit + label = "Michigan personal and stillborn exemptions" + defined_for = StateCode.MI + unit = USD + definition_period = YEAR + + def formula(tax_unit, period, parameters): + p_base = parameters(period).gov.states.mi.tax.income.exemptions + + # Personal exemptions exclude the dependent portion (head + spouse), + # plus stillborn children, which remain in the personal portion. + tax_unit_size = tax_unit("tax_unit_size", period) + dependents = tax_unit("tax_unit_dependents", period) + older_dependents = tax_unit("mi_older_dependents_count", period) + stillborn_children = tax_unit("tax_unit_stillborn_children", period) + personal_exemptions_count = ( + tax_unit_size - dependents + older_dependents + stillborn_children + ) + personal_exemption_amount = personal_exemptions_count * p_base.personal + + # Add separated dependent exemption. + dependent_exemption_amount = tax_unit("mi_dependent_exemption", period) + + return personal_exemption_amount + dependent_exemption_amount + + class reform(Reform): + def apply(self): + self.update_variable(mi_eligible_dependents_count) + self.update_variable(mi_dependent_exemption) + self.update_variable(mi_older_dependents_count) + self.update_variable(mi_personal_exemptions) + + return reform + + +def create_mi_dependent_exemption_reform_fn(parameters, period, bypass: bool = False): + if bypass: + return create_mi_dependent_exemption() + + p = parameters.gov.contrib.states.mi.dependent_exemption + + reform_active = False + current_period = period_(period) + + for i in range(5): + if p(current_period).in_effect: + reform_active = True + break + current_period = current_period.offset(1, "year") + + if reform_active: + return create_mi_dependent_exemption() + else: + return None + + +mi_dependent_exemption_reform = create_mi_dependent_exemption_reform_fn( + None, None, bypass=True +) diff --git a/policyengine_us/reforms/states/ne/dependent_exemption/ne_dependent_exemption_reform.py b/policyengine_us/reforms/states/ne/dependent_exemption/ne_dependent_exemption_reform.py new file mode 100644 index 00000000000..4cb56406e70 --- /dev/null +++ b/policyengine_us/reforms/states/ne/dependent_exemption/ne_dependent_exemption_reform.py @@ -0,0 +1,117 @@ +from policyengine_us.model_api import * +from policyengine_core.periods import period as period_ +from policyengine_core.periods import instant + + +def create_ne_dependent_exemption() -> Reform: + class ne_eligible_dependents_count(Variable): + value_type = int + entity = TaxUnit + label = "Nebraska eligible dependents count" + definition_period = YEAR + defined_for = StateCode.NE + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.contrib.states.ne.dependent_exemption + + person = tax_unit.members + age = person("age", period) + is_dependent = person("is_tax_unit_dependent", period) + + # Apply age limit if in effect + if p.age_limit.in_effect: + age_threshold = p.age_limit.threshold + eligible_dependents = is_dependent & (age < age_threshold) + else: + eligible_dependents = is_dependent + + return tax_unit.sum(eligible_dependents) + + class ne_dependent_exemption(Variable): + value_type = float + entity = TaxUnit + label = "Nebraska dependent exemption" + unit = USD + definition_period = YEAR + defined_for = StateCode.NE + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.contrib.states.ne.dependent_exemption + + dependents_count = tax_unit("ne_eligible_dependents_count", period) + return dependents_count * p.amount + + class ne_older_dependents_count(Variable): + value_type = float + entity = TaxUnit + label = "Nebraska older dependents count" + unit = USD + definition_period = YEAR + defined_for = StateCode.NE + + def formula(tax_unit, period, parameters): + person = tax_unit.members + is_dependent = person("is_tax_unit_dependent", period) + total_dependents = tax_unit.sum(is_dependent) + eligible_dependent_exemptions = tax_unit( + "ne_eligible_dependents_count", period + ) + return max_(0, total_dependents - eligible_dependent_exemptions) + + class ne_exemptions(Variable): + value_type = float + entity = TaxUnit + label = "Nebraska exemptions amount" + unit = USD + definition_period = YEAR + defined_for = StateCode.NE + + def formula(tax_unit, period, parameters): + p_base = parameters(period).gov.states.ne.tax.income.exemptions + + # Personal exemptions exclude the dependent portion. + tax_unit_size = tax_unit("tax_unit_size", period) + dependents = tax_unit("tax_unit_dependents", period) + older_dependents = tax_unit("ne_older_dependents_count", period) + personal_exemptions_count = tax_unit_size - dependents + older_dependents + personal_exemption_amount = personal_exemptions_count * p_base.amount + + # Add separated dependent exemption. + dependent_exemption_amount = tax_unit("ne_dependent_exemption", period) + + return personal_exemption_amount + dependent_exemption_amount + + class reform(Reform): + def apply(self): + self.update_variable(ne_eligible_dependents_count) + self.update_variable(ne_dependent_exemption) + self.update_variable(ne_older_dependents_count) + self.update_variable(ne_exemptions) + + return reform + + +def create_ne_dependent_exemption_reform_fn(parameters, period, bypass: bool = False): + if bypass: + return create_ne_dependent_exemption() + + p = parameters.gov.contrib.states.ne.dependent_exemption + + reform_active = False + current_period = period_(period) + + for i in range(5): + if p(current_period).in_effect: + reform_active = True + break + current_period = current_period.offset(1, "year") + + if reform_active: + return create_ne_dependent_exemption() + else: + return None + + +ne_dependent_exemption_reform = create_ne_dependent_exemption_reform_fn( + None, None, bypass=True +) diff --git a/policyengine_us/reforms/states/oh/dependent_exemption/__init__.py b/policyengine_us/reforms/states/oh/dependent_exemption/__init__.py new file mode 100644 index 00000000000..0b279bcf61d --- /dev/null +++ b/policyengine_us/reforms/states/oh/dependent_exemption/__init__.py @@ -0,0 +1,5 @@ +from .oh_dependent_exemption_reform import ( + create_oh_dependent_exemption, + create_oh_dependent_exemption_reform_fn, + oh_dependent_exemption_reform, +) diff --git a/policyengine_us/reforms/states/oh/dependent_exemption/oh_dependent_exemption_reform.py b/policyengine_us/reforms/states/oh/dependent_exemption/oh_dependent_exemption_reform.py new file mode 100644 index 00000000000..61d1dd3cbb0 --- /dev/null +++ b/policyengine_us/reforms/states/oh/dependent_exemption/oh_dependent_exemption_reform.py @@ -0,0 +1,129 @@ +from policyengine_us.model_api import * +from policyengine_core.periods import period as period_ + + +def create_oh_dependent_exemption() -> Reform: + class oh_eligible_dependents_count(Variable): + value_type = int + entity = TaxUnit + label = "Ohio eligible dependents count" + definition_period = YEAR + defined_for = StateCode.OH + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.contrib.states.oh.dependent_exemption + person = tax_unit.members + eligible_person = person("oh_personal_exemptions_eligible_person", period) + is_dependent = person("is_tax_unit_dependent", period) + eligible = eligible_person & is_dependent + if p.age_limit.in_effect: + eligible = eligible & (person("age", period) < p.age_limit.threshold) + return tax_unit.sum(eligible) + + class oh_dependent_exemption_maximum(Variable): + value_type = float + entity = TaxUnit + label = "Ohio dependent exemption maximum before phaseout" + unit = USD + definition_period = YEAR + defined_for = StateCode.OH + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.contrib.states.oh.dependent_exemption + count = tax_unit("oh_eligible_dependents_count", period) + # Negative amount sentinel = use the baseline per-person AGI-stepped + # amount (no-op default); a value >= 0 applies a flat amount. + p_base = parameters(period).gov.states.oh.tax.income.exemptions.personal + agi = tax_unit("oh_agi", period) + baseline_per = p_base.amount.calc(agi) + per_dependent = where(p.amount < 0, baseline_per, p.amount) + return count * per_dependent + + class oh_dependent_exemption_phaseout(Variable): + value_type = float + entity = TaxUnit + label = "Ohio dependent exemption phaseout amount" + unit = USD + definition_period = YEAR + defined_for = StateCode.OH + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.contrib.states.oh.dependent_exemption + filing_status = tax_unit("filing_status", period) + agi = tax_unit("oh_agi", period) + threshold = p.phaseout.threshold[filing_status] + return max_(agi - threshold, 0) * p.phaseout.rate + + class oh_dependent_exemption(Variable): + value_type = float + entity = TaxUnit + label = "Ohio dependent exemption" + unit = USD + definition_period = YEAR + defined_for = StateCode.OH + + def formula(tax_unit, period, parameters): + maximum = tax_unit("oh_dependent_exemption_maximum", period) + phaseout = tax_unit("oh_dependent_exemption_phaseout", period) + return max_(maximum - phaseout, 0) + + class oh_personal_exemptions(Variable): + value_type = float + entity = TaxUnit + label = "Ohio personal exemptions" + unit = USD + definition_period = YEAR + defined_for = StateCode.OH + + def formula(tax_unit, period, parameters): + p_base = parameters(period).gov.states.oh.tax.income.exemptions.personal + agi = tax_unit("oh_agi", period) + per_person = p_base.amount.calc(agi) + + # Personal portion = all eligible persons minus the dependents that + # are separated out (over-age dependents stay in the personal count). + total_eligible = add( + tax_unit, period, ["oh_personal_exemptions_eligible_person"] + ) + eligible_dependents = tax_unit("oh_eligible_dependents_count", period) + personal_count = total_eligible - eligible_dependents + personal_amount = personal_count * per_person + + dependent_exemption_amount = tax_unit("oh_dependent_exemption", period) + return personal_amount + dependent_exemption_amount + + class reform(Reform): + def apply(self): + self.update_variable(oh_eligible_dependents_count) + self.update_variable(oh_dependent_exemption_maximum) + self.update_variable(oh_dependent_exemption_phaseout) + self.update_variable(oh_dependent_exemption) + self.update_variable(oh_personal_exemptions) + + return reform + + +def create_oh_dependent_exemption_reform_fn(parameters, period, bypass: bool = False): + if bypass: + return create_oh_dependent_exemption() + + p = parameters.gov.contrib.states.oh.dependent_exemption + + reform_active = False + current_period = period_(period) + + for i in range(5): + if p(current_period).in_effect: + reform_active = True + break + current_period = current_period.offset(1, "year") + + if reform_active: + return create_oh_dependent_exemption() + else: + return None + + +oh_dependent_exemption_reform = create_oh_dependent_exemption_reform_fn( + None, None, bypass=True +) diff --git a/policyengine_us/reforms/states/ok/__init__.py b/policyengine_us/reforms/states/ok/__init__.py new file mode 100644 index 00000000000..07e428a483e --- /dev/null +++ b/policyengine_us/reforms/states/ok/__init__.py @@ -0,0 +1 @@ +from .dependent_exemption import create_ok_dependent_exemption_reform_fn diff --git a/policyengine_us/reforms/states/ok/dependent_exemption/__init__.py b/policyengine_us/reforms/states/ok/dependent_exemption/__init__.py new file mode 100644 index 00000000000..f9e41d73801 --- /dev/null +++ b/policyengine_us/reforms/states/ok/dependent_exemption/__init__.py @@ -0,0 +1,5 @@ +from .ok_dependent_exemption_reform import ( + create_ok_dependent_exemption, + create_ok_dependent_exemption_reform_fn, + ok_dependent_exemption_reform, +) diff --git a/policyengine_us/reforms/states/ok/dependent_exemption/ok_dependent_exemption_reform.py b/policyengine_us/reforms/states/ok/dependent_exemption/ok_dependent_exemption_reform.py new file mode 100644 index 00000000000..d8f2903265d --- /dev/null +++ b/policyengine_us/reforms/states/ok/dependent_exemption/ok_dependent_exemption_reform.py @@ -0,0 +1,119 @@ +from policyengine_us.model_api import * +from policyengine_core.periods import period as period_ +from policyengine_core.periods import instant + + +def create_ok_dependent_exemption() -> Reform: + class ok_eligible_dependents_count(Variable): + value_type = int + entity = TaxUnit + label = "Oklahoma eligible dependents count" + definition_period = YEAR + defined_for = StateCode.OK + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.contrib.states.ok.dependent_exemption + + person = tax_unit.members + age = person("age", period) + is_dependent = person("is_tax_unit_dependent", period) + + # Apply age limit if in effect + if p.age_limit.in_effect: + age_threshold = p.age_limit.threshold + eligible_dependents = is_dependent & (age < age_threshold) + else: + eligible_dependents = is_dependent + + return tax_unit.sum(eligible_dependents) + + class ok_dependent_exemption(Variable): + value_type = float + entity = TaxUnit + label = "Oklahoma dependent exemption" + unit = USD + definition_period = YEAR + defined_for = StateCode.OK + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.contrib.states.ok.dependent_exemption + + dependents_count = tax_unit("ok_eligible_dependents_count", period) + return dependents_count * p.amount + + class ok_older_dependents_count(Variable): + value_type = float + entity = TaxUnit + label = "Oklahoma older dependents count" + unit = USD + definition_period = YEAR + defined_for = StateCode.OK + + def formula(tax_unit, period, parameters): + person = tax_unit.members + is_dependent = person("is_tax_unit_dependent", period) + total_dependents = tax_unit.sum(is_dependent) + eligible_dependent_exemptions = tax_unit( + "ok_eligible_dependents_count", period + ) + return max_(0, total_dependents - eligible_dependent_exemptions) + + class ok_exemptions(Variable): + value_type = float + entity = TaxUnit + label = "Oklahoma exemptions amount" + unit = USD + definition_period = YEAR + defined_for = StateCode.OK + + def formula(tax_unit, period, parameters): + p_base = parameters(period).gov.states.ok.tax.income.exemptions + + # Personal exemptions exclude the dependent portion. + total_exemptions_count = tax_unit("ok_count_exemptions", period) + dependents = tax_unit("tax_unit_dependents", period) + older_dependents = tax_unit("ok_older_dependents_count", period) + personal_exemptions_count = ( + total_exemptions_count - dependents + older_dependents + ) + personal_exemption_amount = personal_exemptions_count * p_base.amount + + # Add separated dependent exemption. + dependent_exemption_amount = tax_unit("ok_dependent_exemption", period) + + return personal_exemption_amount + dependent_exemption_amount + + class reform(Reform): + def apply(self): + self.update_variable(ok_eligible_dependents_count) + self.update_variable(ok_dependent_exemption) + self.update_variable(ok_older_dependents_count) + self.update_variable(ok_exemptions) + + return reform + + +def create_ok_dependent_exemption_reform_fn(parameters, period, bypass: bool = False): + if bypass: + return create_ok_dependent_exemption() + + p = parameters.gov.contrib.states.ok.dependent_exemption + + reform_active = False + current_period = period_(period) + + for i in range(5): + if p(current_period).in_effect: + reform_active = True + break + current_period = current_period.offset(1, "year") + + if reform_active: + return create_ok_dependent_exemption() + else: + return None + + +ok_dependent_exemption_reform = create_ok_dependent_exemption_reform_fn( + None, None, bypass=True +) diff --git a/policyengine_us/reforms/states/vt/__init__.py b/policyengine_us/reforms/states/vt/__init__.py new file mode 100644 index 00000000000..f5bf090f9ec --- /dev/null +++ b/policyengine_us/reforms/states/vt/__init__.py @@ -0,0 +1 @@ +from .dependent_exemption import create_vt_dependent_exemption_reform_fn diff --git a/policyengine_us/reforms/states/vt/dependent_exemption/__init__.py b/policyengine_us/reforms/states/vt/dependent_exemption/__init__.py new file mode 100644 index 00000000000..39015f4593e --- /dev/null +++ b/policyengine_us/reforms/states/vt/dependent_exemption/__init__.py @@ -0,0 +1,5 @@ +from .vt_dependent_exemption_reform import ( + create_vt_dependent_exemption, + create_vt_dependent_exemption_reform_fn, + vt_dependent_exemption_reform, +) diff --git a/policyengine_us/reforms/states/vt/dependent_exemption/vt_dependent_exemption_reform.py b/policyengine_us/reforms/states/vt/dependent_exemption/vt_dependent_exemption_reform.py new file mode 100644 index 00000000000..a11f050a9b8 --- /dev/null +++ b/policyengine_us/reforms/states/vt/dependent_exemption/vt_dependent_exemption_reform.py @@ -0,0 +1,120 @@ +from policyengine_us.model_api import * +from policyengine_core.periods import period as period_ +from policyengine_core.periods import instant + + +def create_vt_dependent_exemption() -> Reform: + class vt_eligible_dependents_count(Variable): + value_type = int + entity = TaxUnit + label = "Vermont eligible dependents count" + definition_period = YEAR + defined_for = StateCode.VT + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.contrib.states.vt.dependent_exemption + + person = tax_unit.members + age = person("age", period) + is_dependent = person("is_tax_unit_dependent", period) + + # Apply age limit if in effect + if p.age_limit.in_effect: + age_threshold = p.age_limit.threshold + eligible_dependents = is_dependent & (age < age_threshold) + else: + eligible_dependents = is_dependent + + return tax_unit.sum(eligible_dependents) + + class vt_dependent_exemption(Variable): + value_type = float + entity = TaxUnit + label = "Vermont dependent exemption" + unit = USD + definition_period = YEAR + defined_for = StateCode.VT + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.contrib.states.vt.dependent_exemption + + dependents_count = tax_unit("vt_eligible_dependents_count", period) + return dependents_count * p.amount + + class vt_older_dependents_count(Variable): + value_type = float + entity = TaxUnit + label = "Vermont older dependents count" + unit = USD + definition_period = YEAR + defined_for = StateCode.VT + + def formula(tax_unit, period, parameters): + person = tax_unit.members + is_dependent = person("is_tax_unit_dependent", period) + total_dependents = tax_unit.sum(is_dependent) + eligible_dependent_exemptions = tax_unit( + "vt_eligible_dependents_count", period + ) + return max_(0, total_dependents - eligible_dependent_exemptions) + + class vt_personal_exemptions(Variable): + value_type = float + entity = TaxUnit + label = "Vermont personal exemptions" + unit = USD + definition_period = YEAR + defined_for = StateCode.VT + + def formula(tax_unit, period, parameters): + p_base = parameters(period).gov.states.vt.tax.income.exemption + is_joint = tax_unit("tax_unit_is_joint", period) + elsewhere_head = tax_unit("head_is_dependent_elsewhere", period) + elsewhere_spouse = tax_unit("spouse_is_dependent_elsewhere", period) + eligible_head = (~elsewhere_head).astype(int) + eligible_spouse = (~elsewhere_spouse).astype(int) + eligible_count = eligible_head + (eligible_spouse * is_joint) + # Older dependents fall back to the personal exemption count. + older_dependents = tax_unit("vt_older_dependents_count", period) + personal_count = eligible_count + older_dependents + personal_exemption_amount = personal_count * p_base.personal + + # Add separated dependent exemption. + dependent_exemption_amount = tax_unit("vt_dependent_exemption", period) + + return personal_exemption_amount + dependent_exemption_amount + + class reform(Reform): + def apply(self): + self.update_variable(vt_eligible_dependents_count) + self.update_variable(vt_dependent_exemption) + self.update_variable(vt_older_dependents_count) + self.update_variable(vt_personal_exemptions) + + return reform + + +def create_vt_dependent_exemption_reform_fn(parameters, period, bypass: bool = False): + if bypass: + return create_vt_dependent_exemption() + + p = parameters.gov.contrib.states.vt.dependent_exemption + + reform_active = False + current_period = period_(period) + + for i in range(5): + if p(current_period).in_effect: + reform_active = True + break + current_period = current_period.offset(1, "year") + + if reform_active: + return create_vt_dependent_exemption() + else: + return None + + +vt_dependent_exemption_reform = create_vt_dependent_exemption_reform_fn( + None, None, bypass=True +) diff --git a/policyengine_us/reforms/states/wi/dependent_exemption/wi_dependent_exemption_reform.py b/policyengine_us/reforms/states/wi/dependent_exemption/wi_dependent_exemption_reform.py new file mode 100644 index 00000000000..d117060a5df --- /dev/null +++ b/policyengine_us/reforms/states/wi/dependent_exemption/wi_dependent_exemption_reform.py @@ -0,0 +1,121 @@ +from policyengine_us.model_api import * +from policyengine_core.periods import period as period_ +from policyengine_core.periods import instant + + +def create_wi_dependent_exemption() -> Reform: + class wi_eligible_dependents_count(Variable): + value_type = int + entity = TaxUnit + label = "Wisconsin eligible dependents count" + definition_period = YEAR + defined_for = StateCode.WI + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.contrib.states.wi.dependent_exemption + + person = tax_unit.members + age = person("age", period) + is_dependent = person("is_tax_unit_dependent", period) + + # Apply age limit if in effect + if p.age_limit.in_effect: + age_threshold = p.age_limit.threshold + eligible_dependents = is_dependent & (age < age_threshold) + else: + eligible_dependents = is_dependent + + return tax_unit.sum(eligible_dependents) + + class wi_dependent_exemption(Variable): + value_type = float + entity = TaxUnit + label = "Wisconsin dependent exemption" + unit = USD + definition_period = YEAR + defined_for = StateCode.WI + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.contrib.states.wi.dependent_exemption + + dependents_count = tax_unit("wi_eligible_dependents_count", period) + return dependents_count * p.amount + + class wi_older_dependents_count(Variable): + value_type = float + entity = TaxUnit + label = "Wisconsin older dependents count" + unit = USD + definition_period = YEAR + defined_for = StateCode.WI + + def formula(tax_unit, period, parameters): + person = tax_unit.members + is_dependent = person("is_tax_unit_dependent", period) + total_dependents = tax_unit.sum(is_dependent) + eligible_dependent_exemptions = tax_unit( + "wi_eligible_dependents_count", period + ) + return max_(0, total_dependents - eligible_dependent_exemptions) + + class wi_base_exemption(Variable): + value_type = float + entity = TaxUnit + label = "Wisconsin base exemption" + unit = USD + definition_period = YEAR + defined_for = StateCode.WI + + def formula(tax_unit, period, parameters): + p_base = parameters(period).gov.states.wi.tax.income + + # Personal exemptions exclude the dependent portion. + total_exemptions_count = tax_unit("exemptions_count", period) + dependents = tax_unit("tax_unit_dependents", period) + older_dependents = tax_unit("wi_older_dependents_count", period) + personal_exemptions_count = ( + total_exemptions_count - dependents + older_dependents + ) + personal_exemption_amount = ( + personal_exemptions_count * p_base.exemption.base + ) + + # Add separated dependent exemption. + dependent_exemption_amount = tax_unit("wi_dependent_exemption", period) + + return personal_exemption_amount + dependent_exemption_amount + + class reform(Reform): + def apply(self): + self.update_variable(wi_eligible_dependents_count) + self.update_variable(wi_dependent_exemption) + self.update_variable(wi_older_dependents_count) + self.update_variable(wi_base_exemption) + + return reform + + +def create_wi_dependent_exemption_reform_fn(parameters, period, bypass: bool = False): + if bypass: + return create_wi_dependent_exemption() + + p = parameters.gov.contrib.states.wi.dependent_exemption + + reform_active = False + current_period = period_(period) + + for i in range(5): + if p(current_period).in_effect: + reform_active = True + break + current_period = current_period.offset(1, "year") + + if reform_active: + return create_wi_dependent_exemption() + else: + return None + + +wi_dependent_exemption_reform = create_wi_dependent_exemption_reform_fn( + None, None, bypass=True +) diff --git a/policyengine_us/reforms/states/wv/dependent_exemption/__init__.py b/policyengine_us/reforms/states/wv/dependent_exemption/__init__.py new file mode 100644 index 00000000000..b6ee87dc0ed --- /dev/null +++ b/policyengine_us/reforms/states/wv/dependent_exemption/__init__.py @@ -0,0 +1,5 @@ +from .wv_dependent_exemption_reform import ( + create_wv_dependent_exemption, + create_wv_dependent_exemption_reform_fn, + wv_dependent_exemption_reform, +) diff --git a/policyengine_us/reforms/states/wv/dependent_exemption/wv_dependent_exemption_reform.py b/policyengine_us/reforms/states/wv/dependent_exemption/wv_dependent_exemption_reform.py new file mode 100644 index 00000000000..06229e42b94 --- /dev/null +++ b/policyengine_us/reforms/states/wv/dependent_exemption/wv_dependent_exemption_reform.py @@ -0,0 +1,121 @@ +from policyengine_us.model_api import * +from policyengine_core.periods import period as period_ + + +def create_wv_dependent_exemption() -> Reform: + class wv_eligible_dependents_count(Variable): + value_type = int + entity = TaxUnit + label = "West Virginia eligible dependents count" + definition_period = YEAR + defined_for = StateCode.WV + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.contrib.states.wv.dependent_exemption + + person = tax_unit.members + age = person("age", period) + is_dependent = person("is_tax_unit_dependent", period) + + # Apply age limit if in effect + if p.age_limit.in_effect: + age_threshold = p.age_limit.threshold + eligible_dependents = is_dependent & (age < age_threshold) + else: + eligible_dependents = is_dependent + + return tax_unit.sum(eligible_dependents) + + class wv_dependent_exemption(Variable): + value_type = float + entity = TaxUnit + label = "West Virginia dependent exemption" + unit = USD + definition_period = YEAR + defined_for = StateCode.WV + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.contrib.states.wv.dependent_exemption + + dependents_count = tax_unit("wv_eligible_dependents_count", period) + return dependents_count * p.amount + + class wv_older_dependents_count(Variable): + value_type = float + entity = TaxUnit + label = "West Virginia older dependents count" + unit = USD + definition_period = YEAR + defined_for = StateCode.WV + + def formula(tax_unit, period, parameters): + person = tax_unit.members + is_dependent = person("is_tax_unit_dependent", period) + total_dependents = tax_unit.sum(is_dependent) + eligible_dependent_exemptions = tax_unit( + "wv_eligible_dependents_count", period + ) + return max_(0, total_dependents - eligible_dependent_exemptions) + + class wv_personal_exemption(Variable): + value_type = float + entity = TaxUnit + label = "West Virginia personal exemption" + unit = USD + definition_period = YEAR + defined_for = StateCode.WV + + def formula(tax_unit, period, parameters): + p_base = parameters(period).gov.states.wv.tax.income.exemptions + + # Personal exemptions exclude the dependent portion (head + spouse + + # any over-age dependents that fall back to the personal count). + tax_unit_size = tax_unit("tax_unit_size", period) + dependents = tax_unit("tax_unit_dependents", period) + older_dependents = tax_unit("wv_older_dependents_count", period) + personal_count = tax_unit_size - dependents + older_dependents + personal_amount = where( + tax_unit_size == 0, + p_base.base_personal, + p_base.personal * personal_count, + ) + + # Add separated dependent exemption. + dependent_exemption_amount = tax_unit("wv_dependent_exemption", period) + + return personal_amount + dependent_exemption_amount + + class reform(Reform): + def apply(self): + self.update_variable(wv_eligible_dependents_count) + self.update_variable(wv_dependent_exemption) + self.update_variable(wv_older_dependents_count) + self.update_variable(wv_personal_exemption) + + return reform + + +def create_wv_dependent_exemption_reform_fn(parameters, period, bypass: bool = False): + if bypass: + return create_wv_dependent_exemption() + + p = parameters.gov.contrib.states.wv.dependent_exemption + + reform_active = False + current_period = period_(period) + + for i in range(5): + if p(current_period).in_effect: + reform_active = True + break + current_period = current_period.offset(1, "year") + + if reform_active: + return create_wv_dependent_exemption() + else: + return None + + +wv_dependent_exemption_reform = create_wv_dependent_exemption_reform_fn( + None, None, bypass=True +) diff --git a/policyengine_us/tests/policy/contrib/states/ar/dependent_credit_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/ar/dependent_credit_reform_test.yaml new file mode 100644 index 00000000000..64e436a988a --- /dev/null +++ b/policyengine_us/tests/policy/contrib/states/ar/dependent_credit_reform_test.yaml @@ -0,0 +1,51 @@ +- name: AR dependent credit reform - default reproduces baseline (no-op) + period: 2025 + reforms: policyengine_us.reforms.states.ar.dependent_credit.ar_dependent_credit_reform.ar_dependent_credit_reform + input: + gov.contrib.states.ar.dependent_credit.in_effect: true + people: + parent: {age: 40, employment_income: 50_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: AR}} + output: {ar_personal_credits: 87} + +- name: AR dependent credit reform - amount zero removes dependent portion only + period: 2025 + reforms: policyengine_us.reforms.states.ar.dependent_credit.ar_dependent_credit_reform.ar_dependent_credit_reform + input: + gov.contrib.states.ar.dependent_credit.in_effect: true + gov.contrib.states.ar.dependent_credit.amount: 0 + people: + parent: {age: 40, employment_income: 50_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: AR}} + output: {ar_personal_credits: 29} + +- name: AR dependent credit reform - reduced amount partially repeals + period: 2025 + reforms: policyengine_us.reforms.states.ar.dependent_credit.ar_dependent_credit_reform.ar_dependent_credit_reform + input: + gov.contrib.states.ar.dependent_credit.in_effect: true + gov.contrib.states.ar.dependent_credit.amount: 10 + people: + parent: {age: 40, employment_income: 50_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: AR}} + output: {ar_personal_credits: 49} # head 29 + 2 deps * 10 + +- name: AR dependent credit reform - childless single unaffected + period: 2025 + reforms: policyengine_us.reforms.states.ar.dependent_credit.ar_dependent_credit_reform.ar_dependent_credit_reform + input: + gov.contrib.states.ar.dependent_credit.in_effect: true + gov.contrib.states.ar.dependent_credit.amount: 0 + people: {parent: {age: 40, employment_income: 50_000}} + tax_units: {tax_unit: {members: [parent], filing_status: SINGLE}} + households: {household: {members: [parent], state_name: AR}} + output: {ar_personal_credits: 29} diff --git a/policyengine_us/tests/policy/contrib/states/hi/dependent_exemption_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/hi/dependent_exemption_reform_test.yaml new file mode 100644 index 00000000000..1c1632310f8 --- /dev/null +++ b/policyengine_us/tests/policy/contrib/states/hi/dependent_exemption_reform_test.yaml @@ -0,0 +1,66 @@ +- name: HI dependent exemption reform - default amount reproduces baseline (no-op) + period: 2025 + reforms: policyengine_us.reforms.states.hi.dependent_exemption.hi_dependent_exemption_reform.hi_dependent_exemption_reform + input: + gov.contrib.states.hi.dependent_exemption.in_effect: true + people: + parent: {age: 40, employment_income: 60_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: HI}} + output: {hi_regular_exemptions: 3_432} # 3 * 1,144 + +- name: HI dependent exemption reform - amount zero removes dependent portion only + period: 2025 + reforms: policyengine_us.reforms.states.hi.dependent_exemption.hi_dependent_exemption_reform.hi_dependent_exemption_reform + input: + gov.contrib.states.hi.dependent_exemption.in_effect: true + gov.contrib.states.hi.dependent_exemption.amount: 0 + people: + parent: {age: 40, employment_income: 60_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: HI}} + output: {hi_regular_exemptions: 1_144} # personal only + +- name: HI dependent exemption reform - childless single unaffected + period: 2025 + reforms: policyengine_us.reforms.states.hi.dependent_exemption.hi_dependent_exemption_reform.hi_dependent_exemption_reform + input: + gov.contrib.states.hi.dependent_exemption.in_effect: true + gov.contrib.states.hi.dependent_exemption.amount: 0 + people: {parent: {age: 40, employment_income: 60_000}} + tax_units: {tax_unit: {members: [parent], filing_status: SINGLE}} + households: {household: {members: [parent], state_name: HI}} + output: {hi_regular_exemptions: 1_144} + +- name: HI dependent exemption reform - age limit excludes over-age dependent + period: 2025 + reforms: policyengine_us.reforms.states.hi.dependent_exemption.hi_dependent_exemption_reform.hi_dependent_exemption_reform + input: + gov.contrib.states.hi.dependent_exemption.in_effect: true + gov.contrib.states.hi.dependent_exemption.amount: 0 + gov.contrib.states.hi.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.hi.dependent_exemption.age_limit.threshold: 18 + people: + parent: {age: 40, employment_income: 60_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 20, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: HI}} + output: {hi_regular_exemptions: 2_288} # 2 personal units (over-age dep falls back) * 1,144 + +- name: HI dependent exemption reform - aged head add-on preserved at default amount + period: 2025 + reforms: policyengine_us.reforms.states.hi.dependent_exemption.hi_dependent_exemption_reform.hi_dependent_exemption_reform + input: + gov.contrib.states.hi.dependent_exemption.in_effect: true + people: + parent: {age: 67, employment_income: 60_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: HI}} + output: {hi_regular_exemptions: 4_576} # (3 + 1 aged) * 1,144 diff --git a/policyengine_us/tests/policy/contrib/states/in/dependent_exemption_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/in/dependent_exemption_reform_test.yaml new file mode 100644 index 00000000000..4f967bb37cd --- /dev/null +++ b/policyengine_us/tests/policy/contrib/states/in/dependent_exemption_reform_test.yaml @@ -0,0 +1,106 @@ +- name: IN dependent exemption reform - default amount reproduces baseline (no-op) + period: 2025 + reforms: policyengine_us.reforms.states.in.dependent_exemption.in_dependent_exemption_reform.in_dependent_exemption_reform + input: + gov.contrib.states.in.dependent_exemption.in_effect: true + people: + parent: + age: 40 + employment_income: 60_000 + child1: + age: 10 + is_tax_unit_dependent: true + child2: + age: 12 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child1, child2] + filing_status: SINGLE + households: + household: + members: [parent, child1, child2] + state_name: IN + output: + # 1 personal + 2 dependents, all at $1,000 = $3,000 (matches baseline) + in_base_exemptions: 3_000 + +- name: IN dependent exemption reform - amount zero removes dependent portion only + period: 2025 + reforms: policyengine_us.reforms.states.in.dependent_exemption.in_dependent_exemption_reform.in_dependent_exemption_reform + input: + gov.contrib.states.in.dependent_exemption.in_effect: true + gov.contrib.states.in.dependent_exemption.amount: 0 + people: + parent: + age: 40 + employment_income: 60_000 + child1: + age: 10 + is_tax_unit_dependent: true + child2: + age: 12 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child1, child2] + filing_status: SINGLE + households: + household: + members: [parent, child1, child2] + state_name: IN + output: + # Personal exemption kept ($1,000), dependent portion removed + in_base_exemptions: 1_000 + +- name: IN dependent exemption reform - childless single unaffected + period: 2025 + reforms: policyengine_us.reforms.states.in.dependent_exemption.in_dependent_exemption_reform.in_dependent_exemption_reform + input: + gov.contrib.states.in.dependent_exemption.in_effect: true + gov.contrib.states.in.dependent_exemption.amount: 0 + people: + parent: + age: 40 + employment_income: 60_000 + tax_units: + tax_unit: + members: [parent] + filing_status: SINGLE + households: + household: + members: [parent] + state_name: IN + output: + in_base_exemptions: 1_000 + +- name: IN dependent exemption reform - age limit excludes over-age dependent + period: 2025 + reforms: policyengine_us.reforms.states.in.dependent_exemption.in_dependent_exemption_reform.in_dependent_exemption_reform + input: + gov.contrib.states.in.dependent_exemption.in_effect: true + gov.contrib.states.in.dependent_exemption.amount: 0 + gov.contrib.states.in.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.in.dependent_exemption.age_limit.threshold: 18 + people: + parent: + age: 40 + employment_income: 60_000 + child1: + age: 10 + is_tax_unit_dependent: true + child2: + age: 20 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child1, child2] + filing_status: SINGLE + households: + household: + members: [parent, child1, child2] + state_name: IN + output: + # Over-age dependent (20) falls back to personal count: 2 personal units * $1,000 + # Eligible dependent (10) gets $0 dependent exemption + in_base_exemptions: 2_000 diff --git a/policyengine_us/tests/policy/contrib/states/md/dependent_exemption_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/md/dependent_exemption_reform_test.yaml new file mode 100644 index 00000000000..2996fd19314 --- /dev/null +++ b/policyengine_us/tests/policy/contrib/states/md/dependent_exemption_reform_test.yaml @@ -0,0 +1,53 @@ +- name: MD dependent exemption reform - default reproduces baseline (no-op) + period: 2025 + reforms: policyengine_us.reforms.states.md.dependent_exemption.md_dependent_exemption_reform.md_dependent_exemption_reform + input: + gov.contrib.states.md.dependent_exemption.in_effect: true + people: + parent: {age: 40, employment_income: 40_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: MD}} + output: {md_total_personal_exemptions: 9_600} + +- name: MD dependent exemption reform - amount zero removes dependent portion + period: 2025 + reforms: policyengine_us.reforms.states.md.dependent_exemption.md_dependent_exemption_reform.md_dependent_exemption_reform + input: + gov.contrib.states.md.dependent_exemption.in_effect: true + gov.contrib.states.md.dependent_exemption.amount: 0 + people: + parent: {age: 40, employment_income: 40_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: MD}} + output: {md_total_personal_exemptions: 3_200} + +- name: MD dependent exemption reform - childless single unaffected + period: 2025 + reforms: policyengine_us.reforms.states.md.dependent_exemption.md_dependent_exemption_reform.md_dependent_exemption_reform + input: + gov.contrib.states.md.dependent_exemption.in_effect: true + gov.contrib.states.md.dependent_exemption.amount: 0 + people: {parent: {age: 40, employment_income: 40_000}} + tax_units: {tax_unit: {members: [parent], filing_status: SINGLE}} + households: {household: {members: [parent], state_name: MD}} + output: {md_total_personal_exemptions: 3_200} + +- name: MD dependent exemption reform - age limit excludes over-age dependent + period: 2025 + reforms: policyengine_us.reforms.states.md.dependent_exemption.md_dependent_exemption_reform.md_dependent_exemption_reform + input: + gov.contrib.states.md.dependent_exemption.in_effect: true + gov.contrib.states.md.dependent_exemption.amount: 0 + gov.contrib.states.md.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.md.dependent_exemption.age_limit.threshold: 18 + people: + parent: {age: 40, employment_income: 40_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 20, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: MD}} + output: {md_total_personal_exemptions: 6_400} diff --git a/policyengine_us/tests/policy/contrib/states/mi/dependent_exemption_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/mi/dependent_exemption_reform_test.yaml new file mode 100644 index 00000000000..e1d0787cae8 --- /dev/null +++ b/policyengine_us/tests/policy/contrib/states/mi/dependent_exemption_reform_test.yaml @@ -0,0 +1,66 @@ +- name: MI dependent exemption reform - default amount reproduces baseline (no-op) + period: 2025 + reforms: policyengine_us.reforms.states.mi.dependent_exemption.mi_dependent_exemption_reform.mi_dependent_exemption_reform + input: + gov.contrib.states.mi.dependent_exemption.in_effect: true + people: + parent: {age: 40, employment_income: 60_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: + tax_unit: {members: [parent, child1, child2], filing_status: SINGLE} + households: + household: {members: [parent, child1, child2], state_name: MI} + output: + mi_personal_exemptions: 17_400 # 3 * 5,800 + +- name: MI dependent exemption reform - amount zero removes dependent portion only + period: 2025 + reforms: policyengine_us.reforms.states.mi.dependent_exemption.mi_dependent_exemption_reform.mi_dependent_exemption_reform + input: + gov.contrib.states.mi.dependent_exemption.in_effect: true + gov.contrib.states.mi.dependent_exemption.amount: 0 + people: + parent: {age: 40, employment_income: 60_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: + tax_unit: {members: [parent, child1, child2], filing_status: SINGLE} + households: + household: {members: [parent, child1, child2], state_name: MI} + output: + mi_personal_exemptions: 5_800 # personal only + +- name: MI dependent exemption reform - childless single unaffected + period: 2025 + reforms: policyengine_us.reforms.states.mi.dependent_exemption.mi_dependent_exemption_reform.mi_dependent_exemption_reform + input: + gov.contrib.states.mi.dependent_exemption.in_effect: true + gov.contrib.states.mi.dependent_exemption.amount: 0 + people: + parent: {age: 40, employment_income: 60_000} + tax_units: + tax_unit: {members: [parent], filing_status: SINGLE} + households: + household: {members: [parent], state_name: MI} + output: + mi_personal_exemptions: 5_800 + +- name: MI dependent exemption reform - age limit excludes over-age dependent + period: 2025 + reforms: policyengine_us.reforms.states.mi.dependent_exemption.mi_dependent_exemption_reform.mi_dependent_exemption_reform + input: + gov.contrib.states.mi.dependent_exemption.in_effect: true + gov.contrib.states.mi.dependent_exemption.amount: 0 + gov.contrib.states.mi.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.mi.dependent_exemption.age_limit.threshold: 18 + people: + parent: {age: 40, employment_income: 60_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 20, is_tax_unit_dependent: true} + tax_units: + tax_unit: {members: [parent, child1, child2], filing_status: SINGLE} + households: + household: {members: [parent, child1, child2], state_name: MI} + output: + mi_personal_exemptions: 11_600 # 2 personal units (over-age dep falls back) * 5,800 diff --git a/policyengine_us/tests/policy/contrib/states/ne/dependent_exemption_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/ne/dependent_exemption_reform_test.yaml new file mode 100644 index 00000000000..ac088de2fb3 --- /dev/null +++ b/policyengine_us/tests/policy/contrib/states/ne/dependent_exemption_reform_test.yaml @@ -0,0 +1,53 @@ +- name: NE dependent exemption reform - default amount reproduces baseline (no-op) + period: 2025 + reforms: policyengine_us.reforms.states.ne.dependent_exemption.ne_dependent_exemption_reform.ne_dependent_exemption_reform + input: + gov.contrib.states.ne.dependent_exemption.in_effect: true + people: + parent: {age: 40, employment_income: 60_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: NE}} + output: {ne_exemptions: 513} + +- name: NE dependent exemption reform - amount zero removes dependent portion only + period: 2025 + reforms: policyengine_us.reforms.states.ne.dependent_exemption.ne_dependent_exemption_reform.ne_dependent_exemption_reform + input: + gov.contrib.states.ne.dependent_exemption.in_effect: true + gov.contrib.states.ne.dependent_exemption.amount: 0 + people: + parent: {age: 40, employment_income: 60_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: NE}} + output: {ne_exemptions: 171} + +- name: NE dependent exemption reform - childless single unaffected + period: 2025 + reforms: policyengine_us.reforms.states.ne.dependent_exemption.ne_dependent_exemption_reform.ne_dependent_exemption_reform + input: + gov.contrib.states.ne.dependent_exemption.in_effect: true + gov.contrib.states.ne.dependent_exemption.amount: 0 + people: {parent: {age: 40, employment_income: 60_000}} + tax_units: {tax_unit: {members: [parent], filing_status: SINGLE}} + households: {household: {members: [parent], state_name: NE}} + output: {ne_exemptions: 171} + +- name: NE dependent exemption reform - age limit excludes over-age dependent + period: 2025 + reforms: policyengine_us.reforms.states.ne.dependent_exemption.ne_dependent_exemption_reform.ne_dependent_exemption_reform + input: + gov.contrib.states.ne.dependent_exemption.in_effect: true + gov.contrib.states.ne.dependent_exemption.amount: 0 + gov.contrib.states.ne.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.ne.dependent_exemption.age_limit.threshold: 18 + people: + parent: {age: 40, employment_income: 60_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 20, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: NE}} + output: {ne_exemptions: 342} diff --git a/policyengine_us/tests/policy/contrib/states/oh/dependent_exemption_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/oh/dependent_exemption_reform_test.yaml new file mode 100644 index 00000000000..44a6ca0110c --- /dev/null +++ b/policyengine_us/tests/policy/contrib/states/oh/dependent_exemption_reform_test.yaml @@ -0,0 +1,53 @@ +- name: OH dependent exemption reform - default reproduces baseline (no-op) + period: 2025 + reforms: policyengine_us.reforms.states.oh.dependent_exemption.oh_dependent_exemption_reform.oh_dependent_exemption_reform + input: + gov.contrib.states.oh.dependent_exemption.in_effect: true + people: + parent: {age: 40, employment_income: 30_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: OH}} + output: {oh_personal_exemptions: 7_200} + +- name: OH dependent exemption reform - amount zero removes dependent portion + period: 2025 + reforms: policyengine_us.reforms.states.oh.dependent_exemption.oh_dependent_exemption_reform.oh_dependent_exemption_reform + input: + gov.contrib.states.oh.dependent_exemption.in_effect: true + gov.contrib.states.oh.dependent_exemption.amount: 0 + people: + parent: {age: 40, employment_income: 30_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: OH}} + output: {oh_personal_exemptions: 2_400} + +- name: OH dependent exemption reform - childless single unaffected + period: 2025 + reforms: policyengine_us.reforms.states.oh.dependent_exemption.oh_dependent_exemption_reform.oh_dependent_exemption_reform + input: + gov.contrib.states.oh.dependent_exemption.in_effect: true + gov.contrib.states.oh.dependent_exemption.amount: 0 + people: {parent: {age: 40, employment_income: 30_000}} + tax_units: {tax_unit: {members: [parent], filing_status: SINGLE}} + households: {household: {members: [parent], state_name: OH}} + output: {oh_personal_exemptions: 2_400} + +- name: OH dependent exemption reform - age limit excludes over-age dependent + period: 2025 + reforms: policyengine_us.reforms.states.oh.dependent_exemption.oh_dependent_exemption_reform.oh_dependent_exemption_reform + input: + gov.contrib.states.oh.dependent_exemption.in_effect: true + gov.contrib.states.oh.dependent_exemption.amount: 0 + gov.contrib.states.oh.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.oh.dependent_exemption.age_limit.threshold: 18 + people: + parent: {age: 40, employment_income: 30_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 20, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: OH}} + output: {oh_personal_exemptions: 4_800} diff --git a/policyengine_us/tests/policy/contrib/states/ok/dependent_exemption_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/ok/dependent_exemption_reform_test.yaml new file mode 100644 index 00000000000..d58387abad2 --- /dev/null +++ b/policyengine_us/tests/policy/contrib/states/ok/dependent_exemption_reform_test.yaml @@ -0,0 +1,106 @@ +- name: OK dependent exemption reform - default amount reproduces baseline (no-op) + period: 2025 + reforms: policyengine_us.reforms.states.ok.dependent_exemption.ok_dependent_exemption_reform.ok_dependent_exemption_reform + input: + gov.contrib.states.ok.dependent_exemption.in_effect: true + people: + parent: + age: 40 + employment_income: 60_000 + child1: + age: 10 + is_tax_unit_dependent: true + child2: + age: 12 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child1, child2] + filing_status: SINGLE + households: + household: + members: [parent, child1, child2] + state_name: OK + output: + # 1 personal + 2 dependents, all at $1,000 = $3,000 (matches baseline) + ok_exemptions: 3_000 + +- name: OK dependent exemption reform - amount zero removes dependent portion only + period: 2025 + reforms: policyengine_us.reforms.states.ok.dependent_exemption.ok_dependent_exemption_reform.ok_dependent_exemption_reform + input: + gov.contrib.states.ok.dependent_exemption.in_effect: true + gov.contrib.states.ok.dependent_exemption.amount: 0 + people: + parent: + age: 40 + employment_income: 60_000 + child1: + age: 10 + is_tax_unit_dependent: true + child2: + age: 12 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child1, child2] + filing_status: SINGLE + households: + household: + members: [parent, child1, child2] + state_name: OK + output: + # Personal exemption kept ($1,000), dependent portion removed + ok_exemptions: 1_000 + +- name: OK dependent exemption reform - childless single unaffected + period: 2025 + reforms: policyengine_us.reforms.states.ok.dependent_exemption.ok_dependent_exemption_reform.ok_dependent_exemption_reform + input: + gov.contrib.states.ok.dependent_exemption.in_effect: true + gov.contrib.states.ok.dependent_exemption.amount: 0 + people: + parent: + age: 40 + employment_income: 60_000 + tax_units: + tax_unit: + members: [parent] + filing_status: SINGLE + households: + household: + members: [parent] + state_name: OK + output: + ok_exemptions: 1_000 + +- name: OK dependent exemption reform - age limit excludes over-age dependent + period: 2025 + reforms: policyengine_us.reforms.states.ok.dependent_exemption.ok_dependent_exemption_reform.ok_dependent_exemption_reform + input: + gov.contrib.states.ok.dependent_exemption.in_effect: true + gov.contrib.states.ok.dependent_exemption.amount: 0 + gov.contrib.states.ok.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.ok.dependent_exemption.age_limit.threshold: 18 + people: + parent: + age: 40 + employment_income: 60_000 + child1: + age: 10 + is_tax_unit_dependent: true + child2: + age: 20 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child1, child2] + filing_status: SINGLE + households: + household: + members: [parent, child1, child2] + state_name: OK + output: + # Over-age dependent (20) falls back to personal count: 2 personal units * $1,000 + # Eligible dependent (10) gets $0 dependent exemption + ok_exemptions: 2_000 diff --git a/policyengine_us/tests/policy/contrib/states/vt/dependent_exemption_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/vt/dependent_exemption_reform_test.yaml new file mode 100644 index 00000000000..dbf0457af71 --- /dev/null +++ b/policyengine_us/tests/policy/contrib/states/vt/dependent_exemption_reform_test.yaml @@ -0,0 +1,106 @@ +- name: VT dependent exemption reform - default amount reproduces baseline (no-op) + period: 2025 + reforms: policyengine_us.reforms.states.vt.dependent_exemption.vt_dependent_exemption_reform.vt_dependent_exemption_reform + input: + gov.contrib.states.vt.dependent_exemption.in_effect: true + people: + parent: + age: 40 + employment_income: 60_000 + child1: + age: 10 + is_tax_unit_dependent: true + child2: + age: 12 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child1, child2] + filing_status: SINGLE + households: + household: + members: [parent, child1, child2] + state_name: VT + output: + # 1 personal + 2 dependents, all at $5,300 = $15,900 (matches baseline) + vt_personal_exemptions: 15_900 + +- name: VT dependent exemption reform - amount zero removes dependent portion only + period: 2025 + reforms: policyengine_us.reforms.states.vt.dependent_exemption.vt_dependent_exemption_reform.vt_dependent_exemption_reform + input: + gov.contrib.states.vt.dependent_exemption.in_effect: true + gov.contrib.states.vt.dependent_exemption.amount: 0 + people: + parent: + age: 40 + employment_income: 60_000 + child1: + age: 10 + is_tax_unit_dependent: true + child2: + age: 12 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child1, child2] + filing_status: SINGLE + households: + household: + members: [parent, child1, child2] + state_name: VT + output: + # Personal exemption kept ($5,300), dependent portion removed + vt_personal_exemptions: 5_300 + +- name: VT dependent exemption reform - childless single unaffected + period: 2025 + reforms: policyengine_us.reforms.states.vt.dependent_exemption.vt_dependent_exemption_reform.vt_dependent_exemption_reform + input: + gov.contrib.states.vt.dependent_exemption.in_effect: true + gov.contrib.states.vt.dependent_exemption.amount: 0 + people: + parent: + age: 40 + employment_income: 60_000 + tax_units: + tax_unit: + members: [parent] + filing_status: SINGLE + households: + household: + members: [parent] + state_name: VT + output: + vt_personal_exemptions: 5_300 + +- name: VT dependent exemption reform - age limit excludes over-age dependent + period: 2025 + reforms: policyengine_us.reforms.states.vt.dependent_exemption.vt_dependent_exemption_reform.vt_dependent_exemption_reform + input: + gov.contrib.states.vt.dependent_exemption.in_effect: true + gov.contrib.states.vt.dependent_exemption.amount: 0 + gov.contrib.states.vt.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.vt.dependent_exemption.age_limit.threshold: 18 + people: + parent: + age: 40 + employment_income: 60_000 + child1: + age: 10 + is_tax_unit_dependent: true + child2: + age: 20 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child1, child2] + filing_status: SINGLE + households: + household: + members: [parent, child1, child2] + state_name: VT + output: + # Over-age dependent (20) falls back to personal count: 2 personal units * $5,300 + # Eligible dependent (10) gets $0 dependent exemption + vt_personal_exemptions: 10_600 diff --git a/policyengine_us/tests/policy/contrib/states/wi/dependent_exemption_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/wi/dependent_exemption_reform_test.yaml new file mode 100644 index 00000000000..c0b7211c1b1 --- /dev/null +++ b/policyengine_us/tests/policy/contrib/states/wi/dependent_exemption_reform_test.yaml @@ -0,0 +1,53 @@ +- name: WI dependent exemption reform - default amount reproduces baseline (no-op) + period: 2025 + reforms: policyengine_us.reforms.states.wi.dependent_exemption.wi_dependent_exemption_reform.wi_dependent_exemption_reform + input: + gov.contrib.states.wi.dependent_exemption.in_effect: true + people: + parent: {age: 40, employment_income: 60_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: WI}} + output: {wi_base_exemption: 2100} + +- name: WI dependent exemption reform - amount zero removes dependent portion only + period: 2025 + reforms: policyengine_us.reforms.states.wi.dependent_exemption.wi_dependent_exemption_reform.wi_dependent_exemption_reform + input: + gov.contrib.states.wi.dependent_exemption.in_effect: true + gov.contrib.states.wi.dependent_exemption.amount: 0 + people: + parent: {age: 40, employment_income: 60_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: WI}} + output: {wi_base_exemption: 700} + +- name: WI dependent exemption reform - childless single unaffected + period: 2025 + reforms: policyengine_us.reforms.states.wi.dependent_exemption.wi_dependent_exemption_reform.wi_dependent_exemption_reform + input: + gov.contrib.states.wi.dependent_exemption.in_effect: true + gov.contrib.states.wi.dependent_exemption.amount: 0 + people: {parent: {age: 40, employment_income: 60_000}} + tax_units: {tax_unit: {members: [parent], filing_status: SINGLE}} + households: {household: {members: [parent], state_name: WI}} + output: {wi_base_exemption: 700} + +- name: WI dependent exemption reform - age limit excludes over-age dependent + period: 2025 + reforms: policyengine_us.reforms.states.wi.dependent_exemption.wi_dependent_exemption_reform.wi_dependent_exemption_reform + input: + gov.contrib.states.wi.dependent_exemption.in_effect: true + gov.contrib.states.wi.dependent_exemption.amount: 0 + gov.contrib.states.wi.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.wi.dependent_exemption.age_limit.threshold: 18 + people: + parent: {age: 40, employment_income: 60_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 20, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: WI}} + output: {wi_base_exemption: 1400} diff --git a/policyengine_us/tests/policy/contrib/states/wv/dependent_exemption_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/wv/dependent_exemption_reform_test.yaml new file mode 100644 index 00000000000..fab69461aeb --- /dev/null +++ b/policyengine_us/tests/policy/contrib/states/wv/dependent_exemption_reform_test.yaml @@ -0,0 +1,53 @@ +- name: WV dependent exemption reform - default amount reproduces baseline (no-op) + period: 2025 + reforms: policyengine_us.reforms.states.wv.dependent_exemption.wv_dependent_exemption_reform.wv_dependent_exemption_reform + input: + gov.contrib.states.wv.dependent_exemption.in_effect: true + people: + parent: {age: 40, employment_income: 60_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: WV}} + output: {wv_personal_exemption: 6000} + +- name: WV dependent exemption reform - amount zero removes dependent portion only + period: 2025 + reforms: policyengine_us.reforms.states.wv.dependent_exemption.wv_dependent_exemption_reform.wv_dependent_exemption_reform + input: + gov.contrib.states.wv.dependent_exemption.in_effect: true + gov.contrib.states.wv.dependent_exemption.amount: 0 + people: + parent: {age: 40, employment_income: 60_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: WV}} + output: {wv_personal_exemption: 2000} + +- name: WV dependent exemption reform - childless single unaffected + period: 2025 + reforms: policyengine_us.reforms.states.wv.dependent_exemption.wv_dependent_exemption_reform.wv_dependent_exemption_reform + input: + gov.contrib.states.wv.dependent_exemption.in_effect: true + gov.contrib.states.wv.dependent_exemption.amount: 0 + people: {parent: {age: 40, employment_income: 60_000}} + tax_units: {tax_unit: {members: [parent], filing_status: SINGLE}} + households: {household: {members: [parent], state_name: WV}} + output: {wv_personal_exemption: 2000} + +- name: WV dependent exemption reform - age limit excludes over-age dependent + period: 2025 + reforms: policyengine_us.reforms.states.wv.dependent_exemption.wv_dependent_exemption_reform.wv_dependent_exemption_reform + input: + gov.contrib.states.wv.dependent_exemption.in_effect: true + gov.contrib.states.wv.dependent_exemption.amount: 0 + gov.contrib.states.wv.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.wv.dependent_exemption.age_limit.threshold: 18 + people: + parent: {age: 40, employment_income: 60_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 20, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: WV}} + output: {wv_personal_exemption: 4000} From a8d952ab16eea6998e4f9ed0a1f55dd43b85a016 Mon Sep 17 00:00:00 2001 From: David Trimmer Date: Mon, 22 Jun 2026 10:36:33 -0400 Subject: [PATCH 2/4] Fix review findings: remove stray file, HI reference field, add test coverage - Remove accidentally committed SEP_MSG.tmp scratch file from repo root - HI dependent-exemption reform: use `reference` field instead of `documentation` - Add MD/OH flat-amount and phaseout test cases (previously untested paths) - Add joint-filing test cases (MD, OH, MI, AR, HI, IN) covering the head/spouse-vs-dependent count split in 2-adult units - Strengthen MD/OH no-op case documentation Co-Authored-By: Claude Opus 4.8 (1M context) --- SEP_MSG.tmp | 30 ---------- .../hi_dependent_exemption_reform.py | 2 +- .../ar/dependent_credit_reform_test.yaml | 17 ++++++ .../hi/dependent_exemption_reform_test.yaml | 17 ++++++ .../in/dependent_exemption_reform_test.yaml | 31 ++++++++++ .../md/dependent_exemption_reform_test.yaml | 57 +++++++++++++++++++ .../mi/dependent_exemption_reform_test.yaml | 20 +++++++ .../oh/dependent_exemption_reform_test.yaml | 57 +++++++++++++++++++ 8 files changed, 200 insertions(+), 31 deletions(-) delete mode 100644 SEP_MSG.tmp diff --git a/SEP_MSG.tmp b/SEP_MSG.tmp deleted file mode 100644 index fa4b5e7f334..00000000000 --- a/SEP_MSG.tmp +++ /dev/null @@ -1,30 +0,0 @@ -Separate the dependent portion of 11 states' personal exemptions - -Adds Child Poverty Impact Dashboard contrib reforms that split the dependent -portion of each state's per-person personal exemption (or credit) into its own -variable + parameters, mirroring the existing RI/DE/OR/VA reforms. Each defaults -to a no-op (in_effect=false; default amount reproduces current law) and lets the -per-dependent amount be adjusted or zeroed independently of the head/spouse -exemption. - -States and how they separate: -- Bundled per-person exemption split into head/spouse + dependent: HI (preserves - the aged add-on), IN (base; the additional per-child exemption is untouched), - MI, NE, OK, VT, WI, WV. -- AGI-stepped per-person amount (MD, OH): the dependent amount defaults to the - baseline income-stepped schedule via a negative-amount sentinel, with an - optional flat override and income phase-out group. -- AR: redirects the existing person-level dependent slice of the personal credit - to a contributed amount (its base is otherwise shared with head/spouse). No - age limit, since it is summed via `adds`. - -Each reform exposes in_effect + amount (+ age_limit for the exemption states; -+ phaseout for MD/OH). Indiana's module dir is the Python keyword `in`, so it is -imported dynamically in reforms.py and uses getattr for parameter access. - -Parameters under gov.contrib.states.{st}.dependent_exemption (AR: -dependent_credit). Registered in reforms.py. Each state has YAML tests covering -the default no-op, amount reduction/elimination, childless-filer no-op, and -age-limit fallback; all pass. System smoke-imports cleanly. - -Co-Authored-By: Claude Opus 4.8 (1M context) diff --git a/policyengine_us/reforms/states/hi/dependent_exemption/hi_dependent_exemption_reform.py b/policyengine_us/reforms/states/hi/dependent_exemption/hi_dependent_exemption_reform.py index aae373f44d8..9b65ed51638 100644 --- a/policyengine_us/reforms/states/hi/dependent_exemption/hi_dependent_exemption_reform.py +++ b/policyengine_us/reforms/states/hi/dependent_exemption/hi_dependent_exemption_reform.py @@ -63,7 +63,7 @@ class hi_regular_exemptions(Variable): entity = TaxUnit label = "Hawaii regular exemptions" unit = USD - documentation = "https://files.hawaii.gov/tax/forms/2022/n11ins.pdf#page=20" + reference = "https://files.hawaii.gov/tax/forms/2022/n11ins.pdf#page=20" definition_period = YEAR defined_for = StateCode.HI diff --git a/policyengine_us/tests/policy/contrib/states/ar/dependent_credit_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/ar/dependent_credit_reform_test.yaml index 64e436a988a..95404537ecd 100644 --- a/policyengine_us/tests/policy/contrib/states/ar/dependent_credit_reform_test.yaml +++ b/policyengine_us/tests/policy/contrib/states/ar/dependent_credit_reform_test.yaml @@ -49,3 +49,20 @@ tax_units: {tax_unit: {members: [parent], filing_status: SINGLE}} households: {household: {members: [parent], state_name: AR}} output: {ar_personal_credits: 29} + +- name: AR dependent credit reform - joint two-adult unit splits head/spouse from dependents + period: 2025 + reforms: policyengine_us.reforms.states.ar.dependent_credit.ar_dependent_credit_reform.ar_dependent_credit_reform + input: + gov.contrib.states.ar.dependent_credit.in_effect: true + gov.contrib.states.ar.dependent_credit.amount: 0 + people: + parent1: {age: 40, employment_income: 50_000} + parent2: {age: 38} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent1, parent2, child1, child2], filing_status: JOINT}} + households: {household: {members: [parent1, parent2, child1, child2], state_name: AR}} + # Head + spouse each receive the base personal credit: 2 * 29 = 58. + # Dependent portion removed (amount 0). Confirms no off-by-one in a 2-adult unit. + output: {ar_personal_credits: 58} diff --git a/policyengine_us/tests/policy/contrib/states/hi/dependent_exemption_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/hi/dependent_exemption_reform_test.yaml index 1c1632310f8..e9a9ecaaa36 100644 --- a/policyengine_us/tests/policy/contrib/states/hi/dependent_exemption_reform_test.yaml +++ b/policyengine_us/tests/policy/contrib/states/hi/dependent_exemption_reform_test.yaml @@ -64,3 +64,20 @@ tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} households: {household: {members: [parent, child1, child2], state_name: HI}} output: {hi_regular_exemptions: 4_576} # (3 + 1 aged) * 1,144 + +- name: HI dependent exemption reform - joint two-adult unit splits head/spouse from dependents + period: 2025 + reforms: policyengine_us.reforms.states.hi.dependent_exemption.hi_dependent_exemption_reform.hi_dependent_exemption_reform + input: + gov.contrib.states.hi.dependent_exemption.in_effect: true + gov.contrib.states.hi.dependent_exemption.amount: 0 + people: + parent1: {age: 40, employment_income: 60_000} + parent2: {age: 38} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent1, parent2, child1, child2], filing_status: JOINT}} + households: {household: {members: [parent1, parent2, child1, child2], state_name: HI}} + # 2 head/spouse personal units (exemptions_count 4 - 2 dependents) * 1,144 = 2,288. + # Dependent portion removed (amount 0). Confirms no off-by-one in a 2-adult unit. + output: {hi_regular_exemptions: 2_288} diff --git a/policyengine_us/tests/policy/contrib/states/in/dependent_exemption_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/in/dependent_exemption_reform_test.yaml index 4f967bb37cd..481dd9ef4a8 100644 --- a/policyengine_us/tests/policy/contrib/states/in/dependent_exemption_reform_test.yaml +++ b/policyengine_us/tests/policy/contrib/states/in/dependent_exemption_reform_test.yaml @@ -104,3 +104,34 @@ # Over-age dependent (20) falls back to personal count: 2 personal units * $1,000 # Eligible dependent (10) gets $0 dependent exemption in_base_exemptions: 2_000 + +- name: IN dependent exemption reform - joint two-adult unit splits head/spouse from dependents + period: 2025 + reforms: policyengine_us.reforms.states.in.dependent_exemption.in_dependent_exemption_reform.in_dependent_exemption_reform + input: + gov.contrib.states.in.dependent_exemption.in_effect: true + gov.contrib.states.in.dependent_exemption.amount: 0 + people: + parent1: + age: 40 + employment_income: 60_000 + parent2: + age: 38 + child1: + age: 10 + is_tax_unit_dependent: true + child2: + age: 12 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent1, parent2, child1, child2] + filing_status: JOINT + households: + household: + members: [parent1, parent2, child1, child2] + state_name: IN + output: + # 2 head/spouse personal units (size 4 - 2 dependents) * $1,000 = $2,000. + # Dependent portion removed (amount 0). Confirms no off-by-one in a 2-adult unit. + in_base_exemptions: 2_000 diff --git a/policyengine_us/tests/policy/contrib/states/md/dependent_exemption_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/md/dependent_exemption_reform_test.yaml index 2996fd19314..d3cb16483a2 100644 --- a/policyengine_us/tests/policy/contrib/states/md/dependent_exemption_reform_test.yaml +++ b/policyengine_us/tests/policy/contrib/states/md/dependent_exemption_reform_test.yaml @@ -9,6 +9,9 @@ child2: {age: 12, is_tax_unit_dependent: true} tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} households: {household: {members: [parent, child1, child2], state_name: MD}} + # No-op guard: at the default amount sentinel (-1) the reform reproduces + # current law. 9,600 = 3 units * 3,200 is the baseline md_total_personal_exemptions + # for this household (1 personal + 2 dependents at the AGI-stepped per-person amount). output: {md_total_personal_exemptions: 9_600} - name: MD dependent exemption reform - amount zero removes dependent portion @@ -51,3 +54,57 @@ tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} households: {household: {members: [parent, child1, child2], state_name: MD}} output: {md_total_personal_exemptions: 6_400} + +- name: MD dependent exemption reform - positive flat per-dependent amount + period: 2025 + reforms: policyengine_us.reforms.states.md.dependent_exemption.md_dependent_exemption_reform.md_dependent_exemption_reform + input: + gov.contrib.states.md.dependent_exemption.in_effect: true + gov.contrib.states.md.dependent_exemption.amount: 1_000 + people: + parent: {age: 40, employment_income: 40_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: MD}} + # Personal portion: 1 personal unit * 3,200 (single, AGI 40,000) = 3,200. + # Dependent portion (flat): 2 deps * 1,000 = 2,000; phaseout rate defaults to 0. + # Total = 3,200 + 2,000 = 5,200. + output: {md_total_personal_exemptions: 5_200} + +- name: MD dependent exemption reform - phaseout reduces dependent portion + period: 2025 + reforms: policyengine_us.reforms.states.md.dependent_exemption.md_dependent_exemption_reform.md_dependent_exemption_reform + input: + gov.contrib.states.md.dependent_exemption.in_effect: true + gov.contrib.states.md.dependent_exemption.amount: 2_000 + gov.contrib.states.md.dependent_exemption.phaseout.threshold.SINGLE: 40_000 + gov.contrib.states.md.dependent_exemption.phaseout.rate: 0.1 + people: + parent: {age: 40, employment_income: 50_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: MD}} + # Personal portion: 1 personal unit * 3,200 (single, AGI 50,000) = 3,200. + # Dependent maximum (flat): 2 deps * 2,000 = 4,000. + # Phaseout: (50,000 - 40,000) * 0.1 = 1,000; max_(4,000 - 1,000, 0) = 3,000. + # Total = 3,200 + 3,000 = 6,200. + output: {md_total_personal_exemptions: 6_200} + +- name: MD dependent exemption reform - joint two-adult unit splits head/spouse from dependents + period: 2025 + reforms: policyengine_us.reforms.states.md.dependent_exemption.md_dependent_exemption_reform.md_dependent_exemption_reform + input: + gov.contrib.states.md.dependent_exemption.in_effect: true + gov.contrib.states.md.dependent_exemption.amount: 0 + people: + parent1: {age: 40, employment_income: 40_000} + parent2: {age: 38} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent1, parent2, child1, child2], filing_status: JOINT}} + households: {household: {members: [parent1, parent2, child1, child2], state_name: MD}} + # 2 head/spouse personal units (size 4 - 2 dependents) * 3,200 (joint, AGI 40,000) = 6,400. + # Dependent portion removed (amount 0). Confirms no off-by-one in a 2-adult unit. + output: {md_total_personal_exemptions: 6_400} diff --git a/policyengine_us/tests/policy/contrib/states/mi/dependent_exemption_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/mi/dependent_exemption_reform_test.yaml index e1d0787cae8..cecbb8967a7 100644 --- a/policyengine_us/tests/policy/contrib/states/mi/dependent_exemption_reform_test.yaml +++ b/policyengine_us/tests/policy/contrib/states/mi/dependent_exemption_reform_test.yaml @@ -64,3 +64,23 @@ household: {members: [parent, child1, child2], state_name: MI} output: mi_personal_exemptions: 11_600 # 2 personal units (over-age dep falls back) * 5,800 + +- name: MI dependent exemption reform - joint two-adult unit splits head/spouse from dependents + period: 2025 + reforms: policyengine_us.reforms.states.mi.dependent_exemption.mi_dependent_exemption_reform.mi_dependent_exemption_reform + input: + gov.contrib.states.mi.dependent_exemption.in_effect: true + gov.contrib.states.mi.dependent_exemption.amount: 0 + people: + parent1: {age: 40, employment_income: 60_000} + parent2: {age: 38} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: + tax_unit: {members: [parent1, parent2, child1, child2], filing_status: JOINT} + households: + household: {members: [parent1, parent2, child1, child2], state_name: MI} + output: + # 2 head/spouse personal units (size 4 - 2 dependents) * 5,800 = 11,600. + # Dependent portion removed (amount 0). Confirms no off-by-one in a 2-adult unit. + mi_personal_exemptions: 11_600 diff --git a/policyengine_us/tests/policy/contrib/states/oh/dependent_exemption_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/oh/dependent_exemption_reform_test.yaml index 44a6ca0110c..ffbb727e11d 100644 --- a/policyengine_us/tests/policy/contrib/states/oh/dependent_exemption_reform_test.yaml +++ b/policyengine_us/tests/policy/contrib/states/oh/dependent_exemption_reform_test.yaml @@ -9,6 +9,9 @@ child2: {age: 12, is_tax_unit_dependent: true} tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} households: {household: {members: [parent, child1, child2], state_name: OH}} + # No-op guard: at the default amount sentinel (-1) the reform reproduces + # current law. 7,200 = 3 units * 2,400 is the baseline oh_personal_exemptions + # for this household (1 personal + 2 dependents at the AGI-stepped per-person amount). output: {oh_personal_exemptions: 7_200} - name: OH dependent exemption reform - amount zero removes dependent portion @@ -51,3 +54,57 @@ tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} households: {household: {members: [parent, child1, child2], state_name: OH}} output: {oh_personal_exemptions: 4_800} + +- name: OH dependent exemption reform - positive flat per-dependent amount + period: 2025 + reforms: policyengine_us.reforms.states.oh.dependent_exemption.oh_dependent_exemption_reform.oh_dependent_exemption_reform + input: + gov.contrib.states.oh.dependent_exemption.in_effect: true + gov.contrib.states.oh.dependent_exemption.amount: 1_000 + people: + parent: {age: 40, employment_income: 30_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: OH}} + # Personal portion: 1 personal unit * 2,400 (oh_agi 30,000) = 2,400. + # Dependent portion (flat): 2 deps * 1,000 = 2,000; phaseout rate defaults to 0. + # Total = 2,400 + 2,000 = 4,400. + output: {oh_personal_exemptions: 4_400} + +- name: OH dependent exemption reform - phaseout reduces dependent portion + period: 2025 + reforms: policyengine_us.reforms.states.oh.dependent_exemption.oh_dependent_exemption_reform.oh_dependent_exemption_reform + input: + gov.contrib.states.oh.dependent_exemption.in_effect: true + gov.contrib.states.oh.dependent_exemption.amount: 2_000 + gov.contrib.states.oh.dependent_exemption.phaseout.threshold.SINGLE: 20_000 + gov.contrib.states.oh.dependent_exemption.phaseout.rate: 0.05 + people: + parent: {age: 40, employment_income: 30_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: OH}} + # Personal portion: 1 personal unit * 2,400 (oh_agi 30,000) = 2,400. + # Dependent maximum (flat): 2 deps * 2,000 = 4,000. + # Phaseout: (30,000 - 20,000) * 0.05 = 500; max_(4,000 - 500, 0) = 3,500. + # Total = 2,400 + 3,500 = 5,900. + output: {oh_personal_exemptions: 5_900} + +- name: OH dependent exemption reform - joint two-adult unit splits head/spouse from dependents + period: 2025 + reforms: policyengine_us.reforms.states.oh.dependent_exemption.oh_dependent_exemption_reform.oh_dependent_exemption_reform + input: + gov.contrib.states.oh.dependent_exemption.in_effect: true + gov.contrib.states.oh.dependent_exemption.amount: 0 + people: + parent1: {age: 40, employment_income: 30_000} + parent2: {age: 38} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent1, parent2, child1, child2], filing_status: JOINT}} + households: {household: {members: [parent1, parent2, child1, child2], state_name: OH}} + # 2 head/spouse personal units (4 eligible persons - 2 eligible dependents) * 2,400 = 4,800. + # Dependent portion removed (amount 0). Confirms no off-by-one in a 2-adult unit. + output: {oh_personal_exemptions: 4_800} From 51149b57a84ccf3d31b11cb0ba2b774aaffb4a8e Mon Sep 17 00:00:00 2001 From: David Trimmer Date: Mon, 22 Jun 2026 22:48:43 -0400 Subject: [PATCH 3/4] Address review: add downstream/joint/boundary tests, module cleanups Resolves PavelMakarchuk review on PR #8696: - Add downstream-tax/net-income assertions (AR, MI, MD, OH) so tests no longer stop at intermediate exemption/credit variables - Add married-filing-joint 2-adult cases for NE, OK, VT, WI, WV - Add age-17 (eligible) / age-18 (excluded) boundary cases for all 10 age-limit states - Enrich AR dependent credit reference (AR1000F Line 7A + statute subsections); fix WI/WV test literal underscores - Remove unused `instant` imports (7 reform modules); add ne/wi re-export __init__.py; type dependent-count helpers as int (drop USD) Co-Authored-By: Claude Opus 4.8 (1M context) --- .../states/ar/dependent_credit/amount.yaml | 9 +- .../hi_dependent_exemption_reform.py | 4 +- .../in_dependent_exemption_reform.py | 4 +- .../md_dependent_exemption_reform.py | 3 +- .../mi_dependent_exemption_reform.py | 4 +- .../states/ne/dependent_exemption/__init__.py | 5 ++ .../ne_dependent_exemption_reform.py | 4 +- .../ok_dependent_exemption_reform.py | 4 +- .../vt_dependent_exemption_reform.py | 4 +- .../states/wi/dependent_exemption/__init__.py | 5 ++ .../wi_dependent_exemption_reform.py | 4 +- .../wv_dependent_exemption_reform.py | 3 +- .../ar/dependent_credit_reform_test.yaml | 34 +++++++ .../hi/dependent_exemption_reform_test.yaml | 34 +++++++ .../in/dependent_exemption_reform_test.yaml | 56 ++++++++++++ .../md/dependent_exemption_reform_test.yaml | 67 ++++++++++++++ .../mi/dependent_exemption_reform_test.yaml | 80 +++++++++++++++++ .../ne/dependent_exemption_reform_test.yaml | 51 +++++++++++ .../oh/dependent_exemption_reform_test.yaml | 67 ++++++++++++++ .../ok/dependent_exemption_reform_test.yaml | 87 ++++++++++++++++++ .../vt/dependent_exemption_reform_test.yaml | 88 +++++++++++++++++++ .../wi/dependent_exemption_reform_test.yaml | 55 +++++++++++- .../wv/dependent_exemption_reform_test.yaml | 59 ++++++++++++- 23 files changed, 699 insertions(+), 32 deletions(-) create mode 100644 policyengine_us/reforms/states/ne/dependent_exemption/__init__.py create mode 100644 policyengine_us/reforms/states/wi/dependent_exemption/__init__.py diff --git a/policyengine_us/parameters/gov/contrib/states/ar/dependent_credit/amount.yaml b/policyengine_us/parameters/gov/contrib/states/ar/dependent_credit/amount.yaml index bde0981c8a5..19230c8ca77 100644 --- a/policyengine_us/parameters/gov/contrib/states/ar/dependent_credit/amount.yaml +++ b/policyengine_us/parameters/gov/contrib/states/ar/dependent_credit/amount.yaml @@ -6,5 +6,12 @@ metadata: period: year label: Arkansas dependent credit amount reference: - - title: Ark. Code 26-51-501 + - title: Arkansas 2022 Individual Income Tax Forms and Instructions, LINE 7A. + href: https://www.dfa.arkansas.gov/images/uploads/incomeTaxOffice/2022_AR1000F_and_AR1000NR_Instructions.pdf#page=12 + - title: 2022 AR1000F ARKANSAS INDIVIDUAL INCOME TAX RETURN Full Year Resident + href: https://www.dfa.arkansas.gov/images/uploads/incomeTaxOffice/2022_AR1000F_FullYearResidentIndividualIncomeTaxReturn.pdf#page=1 + # § 26-51-501(a)(1)(A) assigns the "adjusted individual credit". + # § 26-51-501(d)(1) assigns the $20 amount + # § 26-51-501(e) increases it with inflation relative to 2001 starting in 2003 + - title: Ark. Code § 26-51-501(a)(1)(A), (d)(1), (e) href: https://casetext.com/statute/arkansas-code-of-1987/title-26-taxation/subtitle-5-state-taxes/chapter-51-income-taxes/subchapter-5-tax-credits-generally/section-26-51-501-personal-tax-credits diff --git a/policyengine_us/reforms/states/hi/dependent_exemption/hi_dependent_exemption_reform.py b/policyengine_us/reforms/states/hi/dependent_exemption/hi_dependent_exemption_reform.py index 9b65ed51638..c2ec738773b 100644 --- a/policyengine_us/reforms/states/hi/dependent_exemption/hi_dependent_exemption_reform.py +++ b/policyengine_us/reforms/states/hi/dependent_exemption/hi_dependent_exemption_reform.py @@ -1,6 +1,5 @@ from policyengine_us.model_api import * from policyengine_core.periods import period as period_ -from policyengine_core.periods import instant def create_hi_dependent_exemption() -> Reform: @@ -42,10 +41,9 @@ def formula(tax_unit, period, parameters): return dependents_count * p.amount class hi_older_dependents_count(Variable): - value_type = float + value_type = int entity = TaxUnit label = "Hawaii older dependents count" - unit = USD definition_period = YEAR defined_for = StateCode.HI diff --git a/policyengine_us/reforms/states/in/dependent_exemption/in_dependent_exemption_reform.py b/policyengine_us/reforms/states/in/dependent_exemption/in_dependent_exemption_reform.py index f0f5e7397e3..d0443121227 100644 --- a/policyengine_us/reforms/states/in/dependent_exemption/in_dependent_exemption_reform.py +++ b/policyengine_us/reforms/states/in/dependent_exemption/in_dependent_exemption_reform.py @@ -1,6 +1,5 @@ from policyengine_us.model_api import * from policyengine_core.periods import period as period_ -from policyengine_core.periods import instant def create_in_dependent_exemption() -> Reform: @@ -42,10 +41,9 @@ def formula(tax_unit, period, parameters): return dependents_count * p.amount class in_older_dependents_count(Variable): - value_type = float + value_type = int entity = TaxUnit label = "Indiana older dependents count" - unit = USD definition_period = YEAR defined_for = StateCode.IN diff --git a/policyengine_us/reforms/states/md/dependent_exemption/md_dependent_exemption_reform.py b/policyengine_us/reforms/states/md/dependent_exemption/md_dependent_exemption_reform.py index 5de77847de0..f3686d6ff3b 100644 --- a/policyengine_us/reforms/states/md/dependent_exemption/md_dependent_exemption_reform.py +++ b/policyengine_us/reforms/states/md/dependent_exemption/md_dependent_exemption_reform.py @@ -69,10 +69,9 @@ def formula(tax_unit, period, parameters): return max_(maximum - phaseout, 0) class md_older_dependents_count(Variable): - value_type = float + value_type = int entity = TaxUnit label = "Maryland older dependents count" - unit = USD definition_period = YEAR defined_for = StateCode.MD diff --git a/policyengine_us/reforms/states/mi/dependent_exemption/mi_dependent_exemption_reform.py b/policyengine_us/reforms/states/mi/dependent_exemption/mi_dependent_exemption_reform.py index 2b450c2abb1..dd3a6c87cf7 100644 --- a/policyengine_us/reforms/states/mi/dependent_exemption/mi_dependent_exemption_reform.py +++ b/policyengine_us/reforms/states/mi/dependent_exemption/mi_dependent_exemption_reform.py @@ -1,6 +1,5 @@ from policyengine_us.model_api import * from policyengine_core.periods import period as period_ -from policyengine_core.periods import instant def create_mi_dependent_exemption() -> Reform: @@ -42,10 +41,9 @@ def formula(tax_unit, period, parameters): return dependents_count * p.amount class mi_older_dependents_count(Variable): - value_type = float + value_type = int entity = TaxUnit label = "Michigan older dependents count" - unit = USD definition_period = YEAR defined_for = StateCode.MI diff --git a/policyengine_us/reforms/states/ne/dependent_exemption/__init__.py b/policyengine_us/reforms/states/ne/dependent_exemption/__init__.py new file mode 100644 index 00000000000..b6ed4e0e6e9 --- /dev/null +++ b/policyengine_us/reforms/states/ne/dependent_exemption/__init__.py @@ -0,0 +1,5 @@ +from .ne_dependent_exemption_reform import ( + create_ne_dependent_exemption, + create_ne_dependent_exemption_reform_fn, + ne_dependent_exemption_reform, +) diff --git a/policyengine_us/reforms/states/ne/dependent_exemption/ne_dependent_exemption_reform.py b/policyengine_us/reforms/states/ne/dependent_exemption/ne_dependent_exemption_reform.py index 4cb56406e70..84cd5b077cc 100644 --- a/policyengine_us/reforms/states/ne/dependent_exemption/ne_dependent_exemption_reform.py +++ b/policyengine_us/reforms/states/ne/dependent_exemption/ne_dependent_exemption_reform.py @@ -1,6 +1,5 @@ from policyengine_us.model_api import * from policyengine_core.periods import period as period_ -from policyengine_core.periods import instant def create_ne_dependent_exemption() -> Reform: @@ -42,10 +41,9 @@ def formula(tax_unit, period, parameters): return dependents_count * p.amount class ne_older_dependents_count(Variable): - value_type = float + value_type = int entity = TaxUnit label = "Nebraska older dependents count" - unit = USD definition_period = YEAR defined_for = StateCode.NE diff --git a/policyengine_us/reforms/states/ok/dependent_exemption/ok_dependent_exemption_reform.py b/policyengine_us/reforms/states/ok/dependent_exemption/ok_dependent_exemption_reform.py index d8f2903265d..9478fd10a62 100644 --- a/policyengine_us/reforms/states/ok/dependent_exemption/ok_dependent_exemption_reform.py +++ b/policyengine_us/reforms/states/ok/dependent_exemption/ok_dependent_exemption_reform.py @@ -1,6 +1,5 @@ from policyengine_us.model_api import * from policyengine_core.periods import period as period_ -from policyengine_core.periods import instant def create_ok_dependent_exemption() -> Reform: @@ -42,10 +41,9 @@ def formula(tax_unit, period, parameters): return dependents_count * p.amount class ok_older_dependents_count(Variable): - value_type = float + value_type = int entity = TaxUnit label = "Oklahoma older dependents count" - unit = USD definition_period = YEAR defined_for = StateCode.OK diff --git a/policyengine_us/reforms/states/vt/dependent_exemption/vt_dependent_exemption_reform.py b/policyengine_us/reforms/states/vt/dependent_exemption/vt_dependent_exemption_reform.py index a11f050a9b8..35ba6e2a210 100644 --- a/policyengine_us/reforms/states/vt/dependent_exemption/vt_dependent_exemption_reform.py +++ b/policyengine_us/reforms/states/vt/dependent_exemption/vt_dependent_exemption_reform.py @@ -1,6 +1,5 @@ from policyengine_us.model_api import * from policyengine_core.periods import period as period_ -from policyengine_core.periods import instant def create_vt_dependent_exemption() -> Reform: @@ -42,10 +41,9 @@ def formula(tax_unit, period, parameters): return dependents_count * p.amount class vt_older_dependents_count(Variable): - value_type = float + value_type = int entity = TaxUnit label = "Vermont older dependents count" - unit = USD definition_period = YEAR defined_for = StateCode.VT diff --git a/policyengine_us/reforms/states/wi/dependent_exemption/__init__.py b/policyengine_us/reforms/states/wi/dependent_exemption/__init__.py new file mode 100644 index 00000000000..5613adcd280 --- /dev/null +++ b/policyengine_us/reforms/states/wi/dependent_exemption/__init__.py @@ -0,0 +1,5 @@ +from .wi_dependent_exemption_reform import ( + create_wi_dependent_exemption, + create_wi_dependent_exemption_reform_fn, + wi_dependent_exemption_reform, +) diff --git a/policyengine_us/reforms/states/wi/dependent_exemption/wi_dependent_exemption_reform.py b/policyengine_us/reforms/states/wi/dependent_exemption/wi_dependent_exemption_reform.py index d117060a5df..cf44d6a86f2 100644 --- a/policyengine_us/reforms/states/wi/dependent_exemption/wi_dependent_exemption_reform.py +++ b/policyengine_us/reforms/states/wi/dependent_exemption/wi_dependent_exemption_reform.py @@ -1,6 +1,5 @@ from policyengine_us.model_api import * from policyengine_core.periods import period as period_ -from policyengine_core.periods import instant def create_wi_dependent_exemption() -> Reform: @@ -42,10 +41,9 @@ def formula(tax_unit, period, parameters): return dependents_count * p.amount class wi_older_dependents_count(Variable): - value_type = float + value_type = int entity = TaxUnit label = "Wisconsin older dependents count" - unit = USD definition_period = YEAR defined_for = StateCode.WI diff --git a/policyengine_us/reforms/states/wv/dependent_exemption/wv_dependent_exemption_reform.py b/policyengine_us/reforms/states/wv/dependent_exemption/wv_dependent_exemption_reform.py index 06229e42b94..933e8deed81 100644 --- a/policyengine_us/reforms/states/wv/dependent_exemption/wv_dependent_exemption_reform.py +++ b/policyengine_us/reforms/states/wv/dependent_exemption/wv_dependent_exemption_reform.py @@ -41,10 +41,9 @@ def formula(tax_unit, period, parameters): return dependents_count * p.amount class wv_older_dependents_count(Variable): - value_type = float + value_type = int entity = TaxUnit label = "West Virginia older dependents count" - unit = USD definition_period = YEAR defined_for = StateCode.WV diff --git a/policyengine_us/tests/policy/contrib/states/ar/dependent_credit_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/ar/dependent_credit_reform_test.yaml index 95404537ecd..6ef5da136b8 100644 --- a/policyengine_us/tests/policy/contrib/states/ar/dependent_credit_reform_test.yaml +++ b/policyengine_us/tests/policy/contrib/states/ar/dependent_credit_reform_test.yaml @@ -66,3 +66,37 @@ # Head + spouse each receive the base personal credit: 2 * 29 = 58. # Dependent portion removed (amount 0). Confirms no off-by-one in a 2-adult unit. output: {ar_personal_credits: 58} + +- name: AR dependent credit reform - default flows through to non-refundable credits (downstream) + period: 2025 + reforms: policyengine_us.reforms.states.ar.dependent_credit.ar_dependent_credit_reform.ar_dependent_credit_reform + input: + gov.contrib.states.ar.dependent_credit.in_effect: true + people: + parent: {age: 40, employment_income: 50_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: AR}} + # Downstream: the personal credit (head 29 + 2 deps * 29 = 87) flows into the + # aggregate non-refundable credits subtracted from AR income tax. At $50,000 the + # other AR non-refundable credits (CDCC, additional qualified-individual credit) + # are $0 and AR tax before credits exceeds 87, so the credit applies uncapped. + output: {ar_non_refundable_credits: 87} + +- name: AR dependent credit reform - amount zero reduces non-refundable credits (downstream) + period: 2025 + reforms: policyengine_us.reforms.states.ar.dependent_credit.ar_dependent_credit_reform.ar_dependent_credit_reform + input: + gov.contrib.states.ar.dependent_credit.in_effect: true + gov.contrib.states.ar.dependent_credit.amount: 0 + people: + parent: {age: 40, employment_income: 50_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: AR}} + # Downstream: removing the dependent portion (amount 0) drops the aggregate + # non-refundable credits to the head-only personal credit of 29. The 58-dollar + # reduction (87 -> 29) increases AR income tax by the same amount. + output: {ar_non_refundable_credits: 29} diff --git a/policyengine_us/tests/policy/contrib/states/hi/dependent_exemption_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/hi/dependent_exemption_reform_test.yaml index e9a9ecaaa36..3625120c075 100644 --- a/policyengine_us/tests/policy/contrib/states/hi/dependent_exemption_reform_test.yaml +++ b/policyengine_us/tests/policy/contrib/states/hi/dependent_exemption_reform_test.yaml @@ -81,3 +81,37 @@ # 2 head/spouse personal units (exemptions_count 4 - 2 dependents) * 1,144 = 2,288. # Dependent portion removed (amount 0). Confirms no off-by-one in a 2-adult unit. output: {hi_regular_exemptions: 2_288} + +- name: HI dependent exemption reform - dependent at age limit boundary minus one is eligible + period: 2025 + reforms: policyengine_us.reforms.states.hi.dependent_exemption.hi_dependent_exemption_reform.hi_dependent_exemption_reform + input: + gov.contrib.states.hi.dependent_exemption.in_effect: true + gov.contrib.states.hi.dependent_exemption.amount: 0 + gov.contrib.states.hi.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.hi.dependent_exemption.age_limit.threshold: 18 + people: + parent: {age: 40, employment_income: 60_000} + child1: {age: 17, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1], filing_status: SINGLE}} + households: {household: {members: [parent, child1], state_name: HI}} + # age 17 < 18: dependent is eligible, gets the (zeroed) dependent exemption and + # is removed from the personal count. Personal = parent only = 1 * 1,144. + output: {hi_regular_exemptions: 1_144} + +- name: HI dependent exemption reform - dependent at age limit boundary is excluded + period: 2025 + reforms: policyengine_us.reforms.states.hi.dependent_exemption.hi_dependent_exemption_reform.hi_dependent_exemption_reform + input: + gov.contrib.states.hi.dependent_exemption.in_effect: true + gov.contrib.states.hi.dependent_exemption.amount: 0 + gov.contrib.states.hi.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.hi.dependent_exemption.age_limit.threshold: 18 + people: + parent: {age: 40, employment_income: 60_000} + child1: {age: 18, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1], filing_status: SINGLE}} + households: {household: {members: [parent, child1], state_name: HI}} + # age 18 is not < 18: dependent is excluded and falls back to the personal + # count. Personal = parent + over-age dependent = 2 * 1,144 = 2,288. + output: {hi_regular_exemptions: 2_288} diff --git a/policyengine_us/tests/policy/contrib/states/in/dependent_exemption_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/in/dependent_exemption_reform_test.yaml index 481dd9ef4a8..f562cbb6666 100644 --- a/policyengine_us/tests/policy/contrib/states/in/dependent_exemption_reform_test.yaml +++ b/policyengine_us/tests/policy/contrib/states/in/dependent_exemption_reform_test.yaml @@ -135,3 +135,59 @@ # 2 head/spouse personal units (size 4 - 2 dependents) * $1,000 = $2,000. # Dependent portion removed (amount 0). Confirms no off-by-one in a 2-adult unit. in_base_exemptions: 2_000 + +- name: IN dependent exemption reform - dependent at age limit boundary minus one is eligible + period: 2025 + reforms: policyengine_us.reforms.states.in.dependent_exemption.in_dependent_exemption_reform.in_dependent_exemption_reform + input: + gov.contrib.states.in.dependent_exemption.in_effect: true + gov.contrib.states.in.dependent_exemption.amount: 0 + gov.contrib.states.in.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.in.dependent_exemption.age_limit.threshold: 18 + people: + parent: + age: 40 + employment_income: 60_000 + child1: + age: 17 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child1] + filing_status: SINGLE + households: + household: + members: [parent, child1] + state_name: IN + output: + # age 17 < 18: dependent is eligible, gets the (zeroed) dependent exemption and + # is removed from the personal count. Personal = parent only = 1 * $1,000. + in_base_exemptions: 1_000 + +- name: IN dependent exemption reform - dependent at age limit boundary is excluded + period: 2025 + reforms: policyengine_us.reforms.states.in.dependent_exemption.in_dependent_exemption_reform.in_dependent_exemption_reform + input: + gov.contrib.states.in.dependent_exemption.in_effect: true + gov.contrib.states.in.dependent_exemption.amount: 0 + gov.contrib.states.in.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.in.dependent_exemption.age_limit.threshold: 18 + people: + parent: + age: 40 + employment_income: 60_000 + child1: + age: 18 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child1] + filing_status: SINGLE + households: + household: + members: [parent, child1] + state_name: IN + output: + # age 18 is not < 18: dependent is excluded and falls back to the personal + # count. Personal = parent + over-age dependent = 2 * $1,000. + in_base_exemptions: 2_000 diff --git a/policyengine_us/tests/policy/contrib/states/md/dependent_exemption_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/md/dependent_exemption_reform_test.yaml index d3cb16483a2..ce2e1309624 100644 --- a/policyengine_us/tests/policy/contrib/states/md/dependent_exemption_reform_test.yaml +++ b/policyengine_us/tests/policy/contrib/states/md/dependent_exemption_reform_test.yaml @@ -108,3 +108,70 @@ # 2 head/spouse personal units (size 4 - 2 dependents) * 3,200 (joint, AGI 40,000) = 6,400. # Dependent portion removed (amount 0). Confirms no off-by-one in a 2-adult unit. output: {md_total_personal_exemptions: 6_400} + +- name: MD dependent exemption reform - default flows through to MD exemptions (downstream) + period: 2025 + reforms: policyengine_us.reforms.states.md.dependent_exemption.md_dependent_exemption_reform.md_dependent_exemption_reform + input: + gov.contrib.states.md.dependent_exemption.in_effect: true + people: + parent: {age: 40, employment_income: 40_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: MD}} + # Downstream: md_exemptions = md_total_personal_exemptions + md_aged_blind_exemptions. + # No aged/blind here, so md_exemptions = md_total_personal_exemptions = 9,600. + # md_exemptions is the value subtracted in md_taxable_income, so the separated + # dependent portion flows into the MD tax base. + output: {md_exemptions: 9_600} + +- name: MD dependent exemption reform - amount zero reduces MD exemptions (downstream) + period: 2025 + reforms: policyengine_us.reforms.states.md.dependent_exemption.md_dependent_exemption_reform.md_dependent_exemption_reform + input: + gov.contrib.states.md.dependent_exemption.in_effect: true + gov.contrib.states.md.dependent_exemption.amount: 0 + people: + parent: {age: 40, employment_income: 40_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: MD}} + # Downstream: removing the dependent portion drops md_exemptions to 3,200 + # (personal only). The 6,400 reduction raises MD taxable income and tax. + output: {md_exemptions: 3_200} + +- name: MD dependent exemption reform - dependent at age limit boundary minus one is eligible + period: 2025 + reforms: policyengine_us.reforms.states.md.dependent_exemption.md_dependent_exemption_reform.md_dependent_exemption_reform + input: + gov.contrib.states.md.dependent_exemption.in_effect: true + gov.contrib.states.md.dependent_exemption.amount: 0 + gov.contrib.states.md.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.md.dependent_exemption.age_limit.threshold: 18 + people: + parent: {age: 40, employment_income: 40_000} + child1: {age: 17, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1], filing_status: SINGLE}} + households: {household: {members: [parent, child1], state_name: MD}} + # age 17 < 18: dependent is eligible, gets the (zeroed) dependent exemption and + # is removed from the personal count. Personal = parent only = 1 * 3,200. + output: {md_total_personal_exemptions: 3_200} + +- name: MD dependent exemption reform - dependent at age limit boundary is excluded + period: 2025 + reforms: policyengine_us.reforms.states.md.dependent_exemption.md_dependent_exemption_reform.md_dependent_exemption_reform + input: + gov.contrib.states.md.dependent_exemption.in_effect: true + gov.contrib.states.md.dependent_exemption.amount: 0 + gov.contrib.states.md.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.md.dependent_exemption.age_limit.threshold: 18 + people: + parent: {age: 40, employment_income: 40_000} + child1: {age: 18, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1], filing_status: SINGLE}} + households: {household: {members: [parent, child1], state_name: MD}} + # age 18 is not < 18: dependent is excluded and falls back to the personal + # count. Personal = parent + over-age dependent = 2 * 3,200 = 6,400. + output: {md_total_personal_exemptions: 6_400} diff --git a/policyengine_us/tests/policy/contrib/states/mi/dependent_exemption_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/mi/dependent_exemption_reform_test.yaml index cecbb8967a7..bb2eb50f3e7 100644 --- a/policyengine_us/tests/policy/contrib/states/mi/dependent_exemption_reform_test.yaml +++ b/policyengine_us/tests/policy/contrib/states/mi/dependent_exemption_reform_test.yaml @@ -84,3 +84,83 @@ # 2 head/spouse personal units (size 4 - 2 dependents) * 5,800 = 11,600. # Dependent portion removed (amount 0). Confirms no off-by-one in a 2-adult unit. mi_personal_exemptions: 11_600 + +- name: MI dependent exemption reform - default flows through to taxable income (downstream) + period: 2025 + reforms: policyengine_us.reforms.states.mi.dependent_exemption.mi_dependent_exemption_reform.mi_dependent_exemption_reform + input: + gov.contrib.states.mi.dependent_exemption.in_effect: true + people: + parent: {age: 40, employment_income: 60_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: + tax_unit: {members: [parent, child1, child2], filing_status: SINGLE} + households: + household: {members: [parent, child1, child2], state_name: MI} + # Downstream: mi_taxable_income = AGI + additions - subtractions - exemptions. + # Plain wages: AGI 60,000, additions 0, subtractions 0. + # Exemptions = mi_personal_exemptions = 3 * 5,800 = 17,400 (default no-op). + # 60,000 - 17,400 = 42,600. + output: + mi_taxable_income: 42_600 + +- name: MI dependent exemption reform - amount zero raises taxable income (downstream) + period: 2025 + reforms: policyengine_us.reforms.states.mi.dependent_exemption.mi_dependent_exemption_reform.mi_dependent_exemption_reform + input: + gov.contrib.states.mi.dependent_exemption.in_effect: true + gov.contrib.states.mi.dependent_exemption.amount: 0 + people: + parent: {age: 40, employment_income: 60_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: + tax_unit: {members: [parent, child1, child2], filing_status: SINGLE} + households: + household: {members: [parent, child1, child2], state_name: MI} + # Downstream: removing the dependent portion drops exemptions to 5,800 + # (personal only), so taxable income rises: 60,000 - 5,800 = 54,200. + # The 11,600 taxable-income increase is taxed at MI's flat rate. + output: + mi_taxable_income: 54_200 + +- name: MI dependent exemption reform - dependent at age limit boundary minus one is eligible + period: 2025 + reforms: policyengine_us.reforms.states.mi.dependent_exemption.mi_dependent_exemption_reform.mi_dependent_exemption_reform + input: + gov.contrib.states.mi.dependent_exemption.in_effect: true + gov.contrib.states.mi.dependent_exemption.amount: 0 + gov.contrib.states.mi.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.mi.dependent_exemption.age_limit.threshold: 18 + people: + parent: {age: 40, employment_income: 60_000} + child1: {age: 17, is_tax_unit_dependent: true} + tax_units: + tax_unit: {members: [parent, child1], filing_status: SINGLE} + households: + household: {members: [parent, child1], state_name: MI} + # age 17 < 18: dependent is eligible, gets the (zeroed) dependent exemption and + # is removed from the personal count. Personal = parent only = 1 * 5,800. + output: + mi_personal_exemptions: 5_800 + +- name: MI dependent exemption reform - dependent at age limit boundary is excluded + period: 2025 + reforms: policyengine_us.reforms.states.mi.dependent_exemption.mi_dependent_exemption_reform.mi_dependent_exemption_reform + input: + gov.contrib.states.mi.dependent_exemption.in_effect: true + gov.contrib.states.mi.dependent_exemption.amount: 0 + gov.contrib.states.mi.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.mi.dependent_exemption.age_limit.threshold: 18 + people: + parent: {age: 40, employment_income: 60_000} + child1: {age: 18, is_tax_unit_dependent: true} + tax_units: + tax_unit: {members: [parent, child1], filing_status: SINGLE} + households: + household: {members: [parent, child1], state_name: MI} + # age 18 is not < 18: dependent is excluded and falls back to the personal + # count. Personal = parent + over-age dependent = 2 * 5,800 = 11,600. + output: + mi_personal_exemptions: 11_600 diff --git a/policyengine_us/tests/policy/contrib/states/ne/dependent_exemption_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/ne/dependent_exemption_reform_test.yaml index ac088de2fb3..58ff3eeadc8 100644 --- a/policyengine_us/tests/policy/contrib/states/ne/dependent_exemption_reform_test.yaml +++ b/policyengine_us/tests/policy/contrib/states/ne/dependent_exemption_reform_test.yaml @@ -51,3 +51,54 @@ tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} households: {household: {members: [parent, child1, child2], state_name: NE}} output: {ne_exemptions: 342} + +- name: NE dependent exemption reform - joint two-adult unit splits head/spouse from dependents + period: 2025 + reforms: policyengine_us.reforms.states.ne.dependent_exemption.ne_dependent_exemption_reform.ne_dependent_exemption_reform + input: + gov.contrib.states.ne.dependent_exemption.in_effect: true + gov.contrib.states.ne.dependent_exemption.amount: 0 + people: + parent1: {age: 40, employment_income: 60_000} + parent2: {age: 38} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent1, parent2, child1, child2], filing_status: JOINT}} + households: {household: {members: [parent1, parent2, child1, child2], state_name: NE}} + # 2 head/spouse personal units (size 4 - 2 dependents) * 171 = 342. + # Dependent portion removed (amount 0). Confirms no off-by-one in a 2-adult unit. + output: {ne_exemptions: 342} + +- name: NE dependent exemption reform - dependent at age limit boundary minus one is eligible + period: 2025 + reforms: policyengine_us.reforms.states.ne.dependent_exemption.ne_dependent_exemption_reform.ne_dependent_exemption_reform + input: + gov.contrib.states.ne.dependent_exemption.in_effect: true + gov.contrib.states.ne.dependent_exemption.amount: 0 + gov.contrib.states.ne.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.ne.dependent_exemption.age_limit.threshold: 18 + people: + parent: {age: 40, employment_income: 60_000} + child1: {age: 17, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1], filing_status: SINGLE}} + households: {household: {members: [parent, child1], state_name: NE}} + # age 17 < 18: dependent is eligible, gets the (zeroed) dependent exemption and + # is removed from the personal count. Personal = parent only = 1 * 171 = 171. + output: {ne_exemptions: 171} + +- name: NE dependent exemption reform - dependent at age limit boundary is excluded + period: 2025 + reforms: policyengine_us.reforms.states.ne.dependent_exemption.ne_dependent_exemption_reform.ne_dependent_exemption_reform + input: + gov.contrib.states.ne.dependent_exemption.in_effect: true + gov.contrib.states.ne.dependent_exemption.amount: 0 + gov.contrib.states.ne.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.ne.dependent_exemption.age_limit.threshold: 18 + people: + parent: {age: 40, employment_income: 60_000} + child1: {age: 18, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1], filing_status: SINGLE}} + households: {household: {members: [parent, child1], state_name: NE}} + # age 18 is not < 18: dependent is excluded and falls back to the personal + # count. Personal = parent + over-age dependent = 2 * 171 = 342. + output: {ne_exemptions: 342} diff --git a/policyengine_us/tests/policy/contrib/states/oh/dependent_exemption_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/oh/dependent_exemption_reform_test.yaml index ffbb727e11d..3a51d15994b 100644 --- a/policyengine_us/tests/policy/contrib/states/oh/dependent_exemption_reform_test.yaml +++ b/policyengine_us/tests/policy/contrib/states/oh/dependent_exemption_reform_test.yaml @@ -108,3 +108,70 @@ # 2 head/spouse personal units (4 eligible persons - 2 eligible dependents) * 2,400 = 4,800. # Dependent portion removed (amount 0). Confirms no off-by-one in a 2-adult unit. output: {oh_personal_exemptions: 4_800} + +- name: OH dependent exemption reform - default flows through to taxable income (downstream) + period: 2025 + reforms: policyengine_us.reforms.states.oh.dependent_exemption.oh_dependent_exemption_reform.oh_dependent_exemption_reform + input: + gov.contrib.states.oh.dependent_exemption.in_effect: true + people: + parent: {age: 40, employment_income: 30_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: OH}} + # Downstream: oh_taxable_income = max(0, oh_agi - oh_personal_exemptions). + # Plain wages: oh_agi = 30,000 (no OH additions/deductions apply). + # Exemptions = 3 * 2,400 = 7,200 (default no-op). 30,000 - 7,200 = 22,800. + output: {oh_taxable_income: 22_800} + +- name: OH dependent exemption reform - amount zero raises taxable income (downstream) + period: 2025 + reforms: policyengine_us.reforms.states.oh.dependent_exemption.oh_dependent_exemption_reform.oh_dependent_exemption_reform + input: + gov.contrib.states.oh.dependent_exemption.in_effect: true + gov.contrib.states.oh.dependent_exemption.amount: 0 + people: + parent: {age: 40, employment_income: 30_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: OH}} + # Downstream: removing the dependent portion drops exemptions to 2,400 + # (personal only), so taxable income rises: 30,000 - 2,400 = 27,600. + # The 4,800 taxable-income increase raises OH income tax. + output: {oh_taxable_income: 27_600} + +- name: OH dependent exemption reform - dependent at age limit boundary minus one is eligible + period: 2025 + reforms: policyengine_us.reforms.states.oh.dependent_exemption.oh_dependent_exemption_reform.oh_dependent_exemption_reform + input: + gov.contrib.states.oh.dependent_exemption.in_effect: true + gov.contrib.states.oh.dependent_exemption.amount: 0 + gov.contrib.states.oh.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.oh.dependent_exemption.age_limit.threshold: 18 + people: + parent: {age: 40, employment_income: 30_000} + child1: {age: 17, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1], filing_status: SINGLE}} + households: {household: {members: [parent, child1], state_name: OH}} + # age 17 < 18: dependent is eligible, gets the (zeroed) dependent exemption and + # is removed from the personal count. Personal = parent only = 1 * 2,400. + output: {oh_personal_exemptions: 2_400} + +- name: OH dependent exemption reform - dependent at age limit boundary is excluded + period: 2025 + reforms: policyengine_us.reforms.states.oh.dependent_exemption.oh_dependent_exemption_reform.oh_dependent_exemption_reform + input: + gov.contrib.states.oh.dependent_exemption.in_effect: true + gov.contrib.states.oh.dependent_exemption.amount: 0 + gov.contrib.states.oh.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.oh.dependent_exemption.age_limit.threshold: 18 + people: + parent: {age: 40, employment_income: 30_000} + child1: {age: 18, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1], filing_status: SINGLE}} + households: {household: {members: [parent, child1], state_name: OH}} + # age 18 is not < 18: dependent is excluded and falls back to the personal + # count. Personal = parent + over-age dependent = 2 * 2,400 = 4,800. + output: {oh_personal_exemptions: 4_800} diff --git a/policyengine_us/tests/policy/contrib/states/ok/dependent_exemption_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/ok/dependent_exemption_reform_test.yaml index d58387abad2..da69402ce33 100644 --- a/policyengine_us/tests/policy/contrib/states/ok/dependent_exemption_reform_test.yaml +++ b/policyengine_us/tests/policy/contrib/states/ok/dependent_exemption_reform_test.yaml @@ -104,3 +104,90 @@ # Over-age dependent (20) falls back to personal count: 2 personal units * $1,000 # Eligible dependent (10) gets $0 dependent exemption ok_exemptions: 2_000 + +- name: OK dependent exemption reform - joint two-adult unit splits head/spouse from dependents + period: 2025 + reforms: policyengine_us.reforms.states.ok.dependent_exemption.ok_dependent_exemption_reform.ok_dependent_exemption_reform + input: + gov.contrib.states.ok.dependent_exemption.in_effect: true + gov.contrib.states.ok.dependent_exemption.amount: 0 + people: + parent1: + age: 40 + employment_income: 60_000 + parent2: + age: 38 + child1: + age: 10 + is_tax_unit_dependent: true + child2: + age: 12 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent1, parent2, child1, child2] + filing_status: JOINT + households: + household: + members: [parent1, parent2, child1, child2] + state_name: OK + output: + # 2 head/spouse personal units (count 4 - 2 dependents) * $1,000 = $2,000. + # Dependent portion removed (amount 0). Confirms no off-by-one in a 2-adult unit. + ok_exemptions: 2_000 + +- name: OK dependent exemption reform - dependent at age limit boundary minus one is eligible + period: 2025 + reforms: policyengine_us.reforms.states.ok.dependent_exemption.ok_dependent_exemption_reform.ok_dependent_exemption_reform + input: + gov.contrib.states.ok.dependent_exemption.in_effect: true + gov.contrib.states.ok.dependent_exemption.amount: 0 + gov.contrib.states.ok.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.ok.dependent_exemption.age_limit.threshold: 18 + people: + parent: + age: 40 + employment_income: 60_000 + child1: + age: 17 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child1] + filing_status: SINGLE + households: + household: + members: [parent, child1] + state_name: OK + output: + # age 17 < 18: dependent is eligible, gets the (zeroed) dependent exemption and + # is removed from the personal count. Personal = parent only = 1 * $1,000. + ok_exemptions: 1_000 + +- name: OK dependent exemption reform - dependent at age limit boundary is excluded + period: 2025 + reforms: policyengine_us.reforms.states.ok.dependent_exemption.ok_dependent_exemption_reform.ok_dependent_exemption_reform + input: + gov.contrib.states.ok.dependent_exemption.in_effect: true + gov.contrib.states.ok.dependent_exemption.amount: 0 + gov.contrib.states.ok.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.ok.dependent_exemption.age_limit.threshold: 18 + people: + parent: + age: 40 + employment_income: 60_000 + child1: + age: 18 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child1] + filing_status: SINGLE + households: + household: + members: [parent, child1] + state_name: OK + output: + # age 18 is not < 18: dependent is excluded and falls back to the personal + # count. Personal = parent + over-age dependent = 2 * $1,000. + ok_exemptions: 2_000 diff --git a/policyengine_us/tests/policy/contrib/states/vt/dependent_exemption_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/vt/dependent_exemption_reform_test.yaml index dbf0457af71..ad0e8e6febd 100644 --- a/policyengine_us/tests/policy/contrib/states/vt/dependent_exemption_reform_test.yaml +++ b/policyengine_us/tests/policy/contrib/states/vt/dependent_exemption_reform_test.yaml @@ -104,3 +104,91 @@ # Over-age dependent (20) falls back to personal count: 2 personal units * $5,300 # Eligible dependent (10) gets $0 dependent exemption vt_personal_exemptions: 10_600 + +- name: VT dependent exemption reform - joint two-adult unit splits head/spouse from dependents + period: 2025 + reforms: policyengine_us.reforms.states.vt.dependent_exemption.vt_dependent_exemption_reform.vt_dependent_exemption_reform + input: + gov.contrib.states.vt.dependent_exemption.in_effect: true + gov.contrib.states.vt.dependent_exemption.amount: 0 + people: + parent1: + age: 40 + employment_income: 60_000 + parent2: + age: 38 + child1: + age: 10 + is_tax_unit_dependent: true + child2: + age: 12 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent1, parent2, child1, child2] + filing_status: JOINT + households: + household: + members: [parent1, parent2, child1, child2] + state_name: VT + output: + # Head + spouse each get a personal exemption (eligible_head + eligible_spouse + # on a joint return): 2 * $5,300 = $10,600. Dependent portion removed (amount 0). + # Confirms no off-by-one in a 2-adult unit. + vt_personal_exemptions: 10_600 + +- name: VT dependent exemption reform - dependent at age limit boundary minus one is eligible + period: 2025 + reforms: policyengine_us.reforms.states.vt.dependent_exemption.vt_dependent_exemption_reform.vt_dependent_exemption_reform + input: + gov.contrib.states.vt.dependent_exemption.in_effect: true + gov.contrib.states.vt.dependent_exemption.amount: 0 + gov.contrib.states.vt.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.vt.dependent_exemption.age_limit.threshold: 18 + people: + parent: + age: 40 + employment_income: 60_000 + child1: + age: 17 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child1] + filing_status: SINGLE + households: + household: + members: [parent, child1] + state_name: VT + output: + # age 17 < 18: dependent is eligible, gets the (zeroed) dependent exemption + # and is removed from the personal count. Personal = head only = 1 * $5,300. + vt_personal_exemptions: 5_300 + +- name: VT dependent exemption reform - dependent at age limit boundary is excluded + period: 2025 + reforms: policyengine_us.reforms.states.vt.dependent_exemption.vt_dependent_exemption_reform.vt_dependent_exemption_reform + input: + gov.contrib.states.vt.dependent_exemption.in_effect: true + gov.contrib.states.vt.dependent_exemption.amount: 0 + gov.contrib.states.vt.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.vt.dependent_exemption.age_limit.threshold: 18 + people: + parent: + age: 40 + employment_income: 60_000 + child1: + age: 18 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child1] + filing_status: SINGLE + households: + household: + members: [parent, child1] + state_name: VT + output: + # age 18 is not < 18: dependent is excluded and falls back to the personal + # count. Personal = head + over-age dependent = 2 * $5,300. + vt_personal_exemptions: 10_600 diff --git a/policyengine_us/tests/policy/contrib/states/wi/dependent_exemption_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/wi/dependent_exemption_reform_test.yaml index c0b7211c1b1..235834c1142 100644 --- a/policyengine_us/tests/policy/contrib/states/wi/dependent_exemption_reform_test.yaml +++ b/policyengine_us/tests/policy/contrib/states/wi/dependent_exemption_reform_test.yaml @@ -9,7 +9,7 @@ child2: {age: 12, is_tax_unit_dependent: true} tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} households: {household: {members: [parent, child1, child2], state_name: WI}} - output: {wi_base_exemption: 2100} + output: {wi_base_exemption: 2_100} - name: WI dependent exemption reform - amount zero removes dependent portion only period: 2025 @@ -50,4 +50,55 @@ child2: {age: 20, is_tax_unit_dependent: true} tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} households: {household: {members: [parent, child1, child2], state_name: WI}} - output: {wi_base_exemption: 1400} + output: {wi_base_exemption: 1_400} + +- name: WI dependent exemption reform - joint two-adult unit splits head/spouse from dependents + period: 2025 + reforms: policyengine_us.reforms.states.wi.dependent_exemption.wi_dependent_exemption_reform.wi_dependent_exemption_reform + input: + gov.contrib.states.wi.dependent_exemption.in_effect: true + gov.contrib.states.wi.dependent_exemption.amount: 0 + people: + parent1: {age: 40, employment_income: 60_000} + parent2: {age: 38} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent1, parent2, child1, child2], filing_status: JOINT}} + households: {household: {members: [parent1, parent2, child1, child2], state_name: WI}} + # 2 head/spouse personal units (count 4 - 2 dependents) * 700 = 1,400. + # Dependent portion removed (amount 0). Confirms no off-by-one in a 2-adult unit. + output: {wi_base_exemption: 1_400} + +- name: WI dependent exemption reform - dependent at age limit boundary minus one is eligible + period: 2025 + reforms: policyengine_us.reforms.states.wi.dependent_exemption.wi_dependent_exemption_reform.wi_dependent_exemption_reform + input: + gov.contrib.states.wi.dependent_exemption.in_effect: true + gov.contrib.states.wi.dependent_exemption.amount: 0 + gov.contrib.states.wi.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.wi.dependent_exemption.age_limit.threshold: 18 + people: + parent: {age: 40, employment_income: 60_000} + child1: {age: 17, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1], filing_status: SINGLE}} + households: {household: {members: [parent, child1], state_name: WI}} + # age 17 < 18: dependent is eligible, gets the (zeroed) dependent exemption and + # is removed from the personal count. Personal = parent only = 1 * 700 = 700. + output: {wi_base_exemption: 700} + +- name: WI dependent exemption reform - dependent at age limit boundary is excluded + period: 2025 + reforms: policyengine_us.reforms.states.wi.dependent_exemption.wi_dependent_exemption_reform.wi_dependent_exemption_reform + input: + gov.contrib.states.wi.dependent_exemption.in_effect: true + gov.contrib.states.wi.dependent_exemption.amount: 0 + gov.contrib.states.wi.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.wi.dependent_exemption.age_limit.threshold: 18 + people: + parent: {age: 40, employment_income: 60_000} + child1: {age: 18, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1], filing_status: SINGLE}} + households: {household: {members: [parent, child1], state_name: WI}} + # age 18 is not < 18: dependent is excluded and falls back to the personal + # count. Personal = parent + over-age dependent = 2 * 700 = 1,400. + output: {wi_base_exemption: 1_400} diff --git a/policyengine_us/tests/policy/contrib/states/wv/dependent_exemption_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/wv/dependent_exemption_reform_test.yaml index fab69461aeb..e8bd0191da2 100644 --- a/policyengine_us/tests/policy/contrib/states/wv/dependent_exemption_reform_test.yaml +++ b/policyengine_us/tests/policy/contrib/states/wv/dependent_exemption_reform_test.yaml @@ -9,7 +9,7 @@ child2: {age: 12, is_tax_unit_dependent: true} tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} households: {household: {members: [parent, child1, child2], state_name: WV}} - output: {wv_personal_exemption: 6000} + output: {wv_personal_exemption: 6_000} - name: WV dependent exemption reform - amount zero removes dependent portion only period: 2025 @@ -23,7 +23,7 @@ child2: {age: 12, is_tax_unit_dependent: true} tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} households: {household: {members: [parent, child1, child2], state_name: WV}} - output: {wv_personal_exemption: 2000} + output: {wv_personal_exemption: 2_000} - name: WV dependent exemption reform - childless single unaffected period: 2025 @@ -34,7 +34,7 @@ people: {parent: {age: 40, employment_income: 60_000}} tax_units: {tax_unit: {members: [parent], filing_status: SINGLE}} households: {household: {members: [parent], state_name: WV}} - output: {wv_personal_exemption: 2000} + output: {wv_personal_exemption: 2_000} - name: WV dependent exemption reform - age limit excludes over-age dependent period: 2025 @@ -50,4 +50,55 @@ child2: {age: 20, is_tax_unit_dependent: true} tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} households: {household: {members: [parent, child1, child2], state_name: WV}} - output: {wv_personal_exemption: 4000} + output: {wv_personal_exemption: 4_000} + +- name: WV dependent exemption reform - joint two-adult unit splits head/spouse from dependents + period: 2025 + reforms: policyengine_us.reforms.states.wv.dependent_exemption.wv_dependent_exemption_reform.wv_dependent_exemption_reform + input: + gov.contrib.states.wv.dependent_exemption.in_effect: true + gov.contrib.states.wv.dependent_exemption.amount: 0 + people: + parent1: {age: 40, employment_income: 60_000} + parent2: {age: 38} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent1, parent2, child1, child2], filing_status: JOINT}} + households: {household: {members: [parent1, parent2, child1, child2], state_name: WV}} + # 2 head/spouse personal units (size 4 - 2 dependents) * 2,000 = 4,000. + # Dependent portion removed (amount 0). Confirms no off-by-one in a 2-adult unit. + output: {wv_personal_exemption: 4_000} + +- name: WV dependent exemption reform - dependent at age limit boundary minus one is eligible + period: 2025 + reforms: policyengine_us.reforms.states.wv.dependent_exemption.wv_dependent_exemption_reform.wv_dependent_exemption_reform + input: + gov.contrib.states.wv.dependent_exemption.in_effect: true + gov.contrib.states.wv.dependent_exemption.amount: 0 + gov.contrib.states.wv.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.wv.dependent_exemption.age_limit.threshold: 18 + people: + parent: {age: 40, employment_income: 60_000} + child1: {age: 17, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1], filing_status: SINGLE}} + households: {household: {members: [parent, child1], state_name: WV}} + # age 17 < 18: dependent is eligible, gets the (zeroed) dependent exemption and + # is removed from the personal count. Personal = parent only = 1 * 2,000 = 2,000. + output: {wv_personal_exemption: 2_000} + +- name: WV dependent exemption reform - dependent at age limit boundary is excluded + period: 2025 + reforms: policyengine_us.reforms.states.wv.dependent_exemption.wv_dependent_exemption_reform.wv_dependent_exemption_reform + input: + gov.contrib.states.wv.dependent_exemption.in_effect: true + gov.contrib.states.wv.dependent_exemption.amount: 0 + gov.contrib.states.wv.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.wv.dependent_exemption.age_limit.threshold: 18 + people: + parent: {age: 40, employment_income: 60_000} + child1: {age: 18, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1], filing_status: SINGLE}} + households: {household: {members: [parent, child1], state_name: WV}} + # age 18 is not < 18: dependent is excluded and falls back to the personal + # count. Personal = parent + over-age dependent = 2 * 2,000 = 4,000. + output: {wv_personal_exemption: 4_000} From 9ca3ee237ca352f0686a42a588dc05b34bb8ebdf Mon Sep 17 00:00:00 2001 From: David Trimmer Date: Tue, 23 Jun 2026 09:08:52 -0400 Subject: [PATCH 4/4] Add end-to-end AR income-tax assertions for dependent credit reform AR uses the shared-credit-base-redirect pattern (credit consumed via adds), so it was the one state lacking a downstream tax assertion. Add a default vs amount:0 pair on ar_income_tax proving the dependent credit is wired through to the actual tax: removing the dependent portion raises AR income tax by exactly 58 (1346.67 -> 1404.67), matching the credit drop from 87 to 29. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../ar/dependent_credit_reform_test.yaml | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/policyengine_us/tests/policy/contrib/states/ar/dependent_credit_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/ar/dependent_credit_reform_test.yaml index 6ef5da136b8..22cb7167244 100644 --- a/policyengine_us/tests/policy/contrib/states/ar/dependent_credit_reform_test.yaml +++ b/policyengine_us/tests/policy/contrib/states/ar/dependent_credit_reform_test.yaml @@ -100,3 +100,39 @@ # non-refundable credits to the head-only personal credit of 29. The 58-dollar # reduction (87 -> 29) increases AR income tax by the same amount. output: {ar_non_refundable_credits: 29} + +- name: AR dependent credit reform - default flows through to income tax (downstream end-to-end) + period: 2025 + reforms: policyengine_us.reforms.states.ar.dependent_credit.ar_dependent_credit_reform.ar_dependent_credit_reform + absolute_error_margin: 0.1 + input: + gov.contrib.states.ar.dependent_credit.in_effect: true + people: + parent: {age: 40, employment_income: 50_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: AR}} + # End-to-end: with the default (no-op) amount, the personal credit (head 29 + + # 2 deps * 29 = 87) is consumed via `adds` into ar_personal_credits and reduces + # AR income tax. This proves the shared-base-redirect dependent credit is wired + # through to the actual tax, not just the credit aggregate. + output: {ar_income_tax: 1346.67} + +- name: AR dependent credit reform - amount zero raises income tax (downstream end-to-end) + period: 2025 + reforms: policyengine_us.reforms.states.ar.dependent_credit.ar_dependent_credit_reform.ar_dependent_credit_reform + absolute_error_margin: 0.1 + input: + gov.contrib.states.ar.dependent_credit.in_effect: true + gov.contrib.states.ar.dependent_credit.amount: 0 + people: + parent: {age: 40, employment_income: 50_000} + child1: {age: 10, is_tax_unit_dependent: true} + child2: {age: 12, is_tax_unit_dependent: true} + tax_units: {tax_unit: {members: [parent, child1, child2], filing_status: SINGLE}} + households: {household: {members: [parent, child1, child2], state_name: AR}} + # End-to-end: removing the dependent portion (amount 0) drops the personal + # credit from 87 to 29, raising AR income tax by exactly 58 (1346.67 -> 1404.67). + # Confirms the dependent-credit -> tax wiring under the shared-base-redirect pattern. + output: {ar_income_tax: 1404.67}