From 4789532d76e3632d8b0ec52a6bf9854d1a931e03 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 19 Oct 2026 23:05:00 -0400 Subject: [PATCH 1/5] Follow state statutes on NM, VT, NY/NYC and PR income tax items - New Mexico, NMSA 7-2-5.9(A): the senior medical exemption needs expenses that "exceed" $28,000; the PIT-ADJ instructions say "$28,000 or more". - Vermont, 32 V.S.A. 5828c (through 2021): AGI "less than" $30,000/$40,000; the 2021 Schedule IN-112 says "or less". - New York, Tax Law 606(b), and New York City, 1310(d): a married couple's household credit counts the exemptions it is entitled to; IT-201 lines 40 and 48 deny the credit when either spouse can be claimed. - Puerto Rico, section 1052.01(i): the earned income credit is indexed; add Hacienda's 2024 (BI RI 24-04) and 2025 (BI RI 26-01) schedules, and pin the 2023-value cases to 2023. Co-Authored-By: Claude Opus 5.5 --- .../statute-first-state-income-tax.fixed.md | 1 + .../min_expenses.yaml | 2 +- .../credits/earned_income/max_amount.yaml | 15 +++- .../phase_out/threshold/joint.yaml | 15 +++- .../phase_out/threshold/single.yaml | 15 +++- ...st_medical_expense_premium_conservation.py | 5 +- ...c_children_medical_exemption_invariants.py | 7 +- ...st_state_medical_expense_input_deletion.py | 9 ++- .../household/nyc_household_credit.yaml | 11 ++- .../nm_medical_expense_exemption.yaml | 30 +++++-- .../income/credits/ny_household_credit.yaml | 13 +-- .../cdcc/vt_low_income_cdcc_eligible.yaml | 30 +++++-- .../credits/earned_income/integration.yaml | 4 +- .../pr_earned_income_credit.yaml | 79 +++++++++++++++++-- .../credits/household/nyc_household_credit.py | 23 ++++-- .../nm_medical_expense_exemption.py | 6 +- .../credits/ny_household_credit_potential.py | 26 ++++-- .../cdcc/vt_low_income_cdcc_eligible.py | 9 ++- 18 files changed, 239 insertions(+), 61 deletions(-) create mode 100644 changelog.d/statute-first-state-income-tax.fixed.md diff --git a/changelog.d/statute-first-state-income-tax.fixed.md b/changelog.d/statute-first-state-income-tax.fixed.md new file mode 100644 index 00000000000..c88c93ad797 --- /dev/null +++ b/changelog.d/statute-first-state-income-tax.fixed.md @@ -0,0 +1 @@ +Follow the statute where state forms differ: New Mexico's senior medical exemption needs expenses over $28,000; Vermont's 2021 low-income child care credit needs AGI under the threshold; New York and New York City household credits count a joint return's federal exemptions rather than denying the credit when one spouse can be claimed; and Puerto Rico's earned income credit uses Hacienda's 2024 and 2025 inflation-adjusted schedules. diff --git a/policyengine_us/parameters/gov/states/nm/tax/income/exemptions/unreimbursed_medical_care_expense/min_expenses.yaml b/policyengine_us/parameters/gov/states/nm/tax/income/exemptions/unreimbursed_medical_care_expense/min_expenses.yaml index 5aaca843301..d02feaad554 100644 --- a/policyengine_us/parameters/gov/states/nm/tax/income/exemptions/unreimbursed_medical_care_expense/min_expenses.yaml +++ b/policyengine_us/parameters/gov/states/nm/tax/income/exemptions/unreimbursed_medical_care_expense/min_expenses.yaml @@ -1,4 +1,4 @@ -description: New Mexico provides the unreimbursed medical care expense exemption for filers with expenses at or above this amount. +description: New Mexico provides the unreimbursed medical care expense exemption for filers with expenses above this amount. values: 2005-01-01: 28_000 metadata: diff --git a/policyengine_us/parameters/gov/territories/pr/tax/income/credits/earned_income/max_amount.yaml b/policyengine_us/parameters/gov/territories/pr/tax/income/credits/earned_income/max_amount.yaml index ce5d03dd662..324c3e19ee4 100644 --- a/policyengine_us/parameters/gov/territories/pr/tax/income/credits/earned_income/max_amount.yaml +++ b/policyengine_us/parameters/gov/territories/pr/tax/income/credits/earned_income/max_amount.yaml @@ -4,18 +4,26 @@ brackets: 2023-01-01: 0 amount: 2023-01-01: 1_656 + 2024-01-01: 1_745 + 2025-01-01: 1_794 - threshold: 2023-01-01: 1 amount: 2023-01-01: 3_864 + 2024-01-01: 4_071 + 2025-01-01: 4_186 - threshold: 2023-01-01: 2 amount: 2023-01-01: 6_072 + 2024-01-01: 6_400 + 2025-01-01: 6_576 - threshold: 2023-01-01: 3 amount: 2023-01-01: 7_173 + 2024-01-01: 7_563 + 2025-01-01: 7_774 metadata: amount_unit: currency-USD threshold_unit: child @@ -23,7 +31,12 @@ metadata: label: Puerto Rico earned income credit max amount period: year reference: - # discrepancy: legal statute max amount doesn't correspond with tax form values + - title: "Hacienda Boletín Informativo de Rentas Internas 24-04, inflation adjustment to the earned income credit for 2024 #page 1-3" + href: https://hacienda.pr.gov/sites/default/files/publicaciones/2024/12/bi_ri_24-04_-_credito_por_trabajo_-_ajuste_por_inflacion_2024_firmada_23-dic-2024.pdf#page=1 + - title: "Hacienda Boletín Informativo de Rentas Internas 26-01, inflation adjustment to the earned income credit for 2025 #page 1-3" + href: https://hacienda.pr.gov/sites/default/files/publicaciones/2026/02/bi_ri_26-01_ajuste_por_inflacion_al_credito_por_trabajo_para_el_ano_2025_signed.pdf#page=1 + - title: P.R. Internal Revenue Code § 1052.01(i), 13 L.P.R.A. § 30211(i) (inflation adjustment as adjusted by the federal IRS) + href: https://docs.pr.gov/files/OCIF/LEYES-REGLAMENTOS-CARTAS-CIRCULARES/Leyes-delegan-Facultad-OCIF/Ley%20N%C3%BAm.%201-2011%20-%20C%C3%B3digo%20de%20Rentas%20Internas%20para%20un%20Nuevo%20Puerto%20Rico.pdf#page=300 - title: P.R. Laws tit. 13, § 30211 (1)-(6) href: https://law.justia.com/codes/puerto-rico/title-thirteen/subtitle-17/part-ii/chapter-1007/subchapter-b/30211/ - title: Schedule CT Individual, Earned Income Credit, 2023 diff --git a/policyengine_us/parameters/gov/territories/pr/tax/income/credits/earned_income/phase_out/threshold/joint.yaml b/policyengine_us/parameters/gov/territories/pr/tax/income/credits/earned_income/phase_out/threshold/joint.yaml index b8742b1404a..4d136070e9a 100644 --- a/policyengine_us/parameters/gov/territories/pr/tax/income/credits/earned_income/phase_out/threshold/joint.yaml +++ b/policyengine_us/parameters/gov/territories/pr/tax/income/credits/earned_income/phase_out/threshold/joint.yaml @@ -5,18 +5,26 @@ brackets: 2023-01-01: 0 amount: 2023-01-01: 19_870 + 2024-01-01: 20_940 + 2025-01-01: 21_530 - threshold: 2023-01-01: 1 amount: 2023-01-01: 24_280 + 2024-01-01: 25_590 + 2025-01-01: 26_310 - threshold: 2023-01-01: 2 amount: 2023-01-01: 27_590 + 2024-01-01: 29_080 + 2025-01-01: 29_900 - threshold: 2023-01-01: 3 amount: 2023-01-01: 27_590 + 2024-01-01: 29_080 + 2025-01-01: 29_900 metadata: amount_unit: currency-USD threshold_unit: child @@ -24,8 +32,13 @@ metadata: label: Puerto Rico earned income credit phase out threshold period: year reference: + - title: "Hacienda Boletín Informativo de Rentas Internas 24-04, inflation adjustment to the earned income credit for 2024 #page 1-3" + href: https://hacienda.pr.gov/sites/default/files/publicaciones/2024/12/bi_ri_24-04_-_credito_por_trabajo_-_ajuste_por_inflacion_2024_firmada_23-dic-2024.pdf#page=1 + - title: "Hacienda Boletín Informativo de Rentas Internas 26-01, inflation adjustment to the earned income credit for 2025 #page 1-3" + href: https://hacienda.pr.gov/sites/default/files/publicaciones/2026/02/bi_ri_26-01_ajuste_por_inflacion_al_credito_por_trabajo_para_el_ano_2025_signed.pdf#page=1 + - title: P.R. Internal Revenue Code § 1052.01(i), 13 L.P.R.A. § 30211(i) (inflation adjustment as adjusted by the federal IRS) + href: https://docs.pr.gov/files/OCIF/LEYES-REGLAMENTOS-CARTAS-CIRCULARES/Leyes-delegan-Facultad-OCIF/Ley%20N%C3%BAm.%201-2011%20-%20C%C3%B3digo%20de%20Rentas%20Internas%20para%20un%20Nuevo%20Puerto%20Rico.pdf#page=300 - title: Schedule CT Individual, Earned Income Credit, 2023 href: https://hacienda.pr.gov/sites/default/files/schedule_ct_rev._jul_5_23_informative_-_instructions.pdf#page=2 - # discrepancy: legal statute phase out thresholds do not correspond with tax form values - title: P.R. Laws tit. 13, § 30211 (1) - (6) href: https://law.justia.com/codes/puerto-rico/title-thirteen/subtitle-17/part-ii/chapter-1007/subchapter-b/30211/ diff --git a/policyengine_us/parameters/gov/territories/pr/tax/income/credits/earned_income/phase_out/threshold/single.yaml b/policyengine_us/parameters/gov/territories/pr/tax/income/credits/earned_income/phase_out/threshold/single.yaml index 3f25534d252..0446792fce9 100644 --- a/policyengine_us/parameters/gov/territories/pr/tax/income/credits/earned_income/phase_out/threshold/single.yaml +++ b/policyengine_us/parameters/gov/territories/pr/tax/income/credits/earned_income/phase_out/threshold/single.yaml @@ -5,18 +5,26 @@ brackets: 2023-01-01: 0 amount: 2023-01-01: 17_660 + 2024-01-01: 18_610 + 2025-01-01: 19_130 - threshold: 2023-01-01: 1 amount: 2023-01-01: 19_870 + 2024-01-01: 20_940 + 2025-01-01: 21_530 - threshold: 2023-01-01: 2 amount: 2023-01-01: 23_180 + 2024-01-01: 24_430 + 2025-01-01: 25_110 - threshold: 2023-01-01: 3 amount: 2023-01-01: 23_180 + 2024-01-01: 24_430 + 2025-01-01: 25_110 metadata: amount_unit: currency-USD threshold_unit: child @@ -24,8 +32,13 @@ metadata: label: Puerto Rico earned income credit phase out threshold period: year reference: + - title: "Hacienda Boletín Informativo de Rentas Internas 24-04, inflation adjustment to the earned income credit for 2024 #page 1-3" + href: https://hacienda.pr.gov/sites/default/files/publicaciones/2024/12/bi_ri_24-04_-_credito_por_trabajo_-_ajuste_por_inflacion_2024_firmada_23-dic-2024.pdf#page=1 + - title: "Hacienda Boletín Informativo de Rentas Internas 26-01, inflation adjustment to the earned income credit for 2025 #page 1-3" + href: https://hacienda.pr.gov/sites/default/files/publicaciones/2026/02/bi_ri_26-01_ajuste_por_inflacion_al_credito_por_trabajo_para_el_ano_2025_signed.pdf#page=1 + - title: P.R. Internal Revenue Code § 1052.01(i), 13 L.P.R.A. § 30211(i) (inflation adjustment as adjusted by the federal IRS) + href: https://docs.pr.gov/files/OCIF/LEYES-REGLAMENTOS-CARTAS-CIRCULARES/Leyes-delegan-Facultad-OCIF/Ley%20N%C3%BAm.%201-2011%20-%20C%C3%B3digo%20de%20Rentas%20Internas%20para%20un%20Nuevo%20Puerto%20Rico.pdf#page=300 - title: Schedule CT Individual, Earned Income Credit, 2023 href: https://hacienda.pr.gov/sites/default/files/schedule_ct_rev._jul_5_23_informative_-_instructions.pdf#page=2 - # discrepancy: legal statute phase out thresholds do not correspond with tax form values - title: P.R. Laws tit. 13, § 30211 (1) - (6) href: https://law.justia.com/codes/puerto-rico/title-thirteen/subtitle-17/part-ii/chapter-1007/subchapter-b/30211/ diff --git a/policyengine_us/tests/core/test_medical_expense_premium_conservation.py b/policyengine_us/tests/core/test_medical_expense_premium_conservation.py index 5734523889c..1618a6dc584 100644 --- a/policyengine_us/tests/core/test_medical_expense_premium_conservation.py +++ b/policyengine_us/tests/core/test_medical_expense_premium_conservation.py @@ -395,7 +395,10 @@ def _state_simulation(cases, reference_system, with_se_deduction): # deduction. The oracle preserves the supplied amount exactly as main # does; only the computed path restores an applied federal exclusion. main_base = case["subtotal"] if supplied else gross + # The credit (NMSA 7-2-18.13) takes expenses of $28,000 or more; the + # exemption (NMSA 7-2-5.9(A)) takes expenses that "exceed" $28,000. senior_eligible = case["age"] >= 65 and main_base >= 28_000 + senior_exemption_eligible = case["age"] >= 65 and main_base > 28_000 denominator = 2 if case["filing_status"] == "SEPARATE" else 1 for state in ("ND", "NM"): @@ -464,7 +467,7 @@ def _state_simulation(cases, reference_system, with_se_deduction): ) # NMSA 7-2-5.9 and PIT-ADJ line 18 allow the full exemption. expectations["nm_medical_expense_exemption"].append( - 3_000 if state == "NM" and senior_eligible else 0 + 3_000 if state == "NM" and senior_exemption_eligible else 0 ) simulation = Simulation( tax_benefit_system=reference_system, diff --git a/policyengine_us/tests/core/test_nm_ctc_children_medical_exemption_invariants.py b/policyengine_us/tests/core/test_nm_ctc_children_medical_exemption_invariants.py index 96154868af8..1a71feb44d5 100644 --- a/policyengine_us/tests/core/test_nm_ctc_children_medical_exemption_invariants.py +++ b/policyengine_us/tests/core/test_nm_ctc_children_medical_exemption_invariants.py @@ -182,7 +182,7 @@ def unit(adults, dependents, expenses=0.0): unit([adult(25), adult(20)], [dependent(22, student=True)]), # The only 65+ person is a dependent parent. unit([adult(40)], [elderly_parent], expenses=30_000.0), - # A 65+ filer with exactly $28,000 of expenses. + # A 65+ filer with exactly $28,000 of expenses (not over $28,000). unit([adult(70)], [], expenses=28_000.0), ] @@ -268,7 +268,8 @@ def _reference_exemption(unit): if unit["state"] != "NM": return 0 filer_65 = any(a["age"] >= 65 for a in unit["adults"]) - return EXEMPTION * (filer_65 and unit["medical_expenses"] >= MIN_EXPENSES) + # NMSA 7-2-5.9(A): expenses that "exceed" $28,000. + return EXEMPTION * (filer_65 and unit["medical_expenses"] > MIN_EXPENSES) def _describe(unit): @@ -336,7 +337,7 @@ def _assert_coverage(drawn_units, credit, exemption): u["state"] == "NM" and all(a["age"] < 65 for a in u["adults"]) and any(d["age"] >= 65 for d in u["dependents"]) - and u["medical_expenses"] >= MIN_EXPENSES + and u["medical_expenses"] > MIN_EXPENSES for u in drawn_units ] ) diff --git a/policyengine_us/tests/core/test_state_medical_expense_input_deletion.py b/policyengine_us/tests/core/test_state_medical_expense_input_deletion.py index 8f700ae0cae..c48a55b7835 100644 --- a/policyengine_us/tests/core/test_state_medical_expense_input_deletion.py +++ b/policyengine_us/tests/core/test_state_medical_expense_input_deletion.py @@ -69,13 +69,14 @@ def test_nd_paid_cost_base_after_medical_subtotal_deletion(delete_subtotal): @pytest.mark.parametrize("delete_subtotal", SUBTOTAL_DELETION_CASES) def test_nm_senior_benefits_after_medical_subtotal_deletion(delete_subtotal): - simulation = _simulation("NM", other_expenses=22_000) + simulation = _simulation("NM", other_expenses=22_001) if delete_subtotal: simulation.delete_arrays("itemized_medical_expenses", PERIOD) - # The recomputed federal subtotal is 27,000, but restoring its 1,000 - # exclusion meets NM's 28,000 paid-cost threshold. Calculate both before - # asserting to preserve evidence. + # The recomputed federal subtotal is 27,001, but restoring its 1,000 + # exclusion gives 28,001 of paid costs, which meets the credit's $28,000 + # threshold and exceeds the exemption's. Calculate both before asserting + # to preserve evidence. benefits = tuple( simulation.calculate(variable, PERIOD).item() for variable in ( diff --git a/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.yaml b/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.yaml index 43eeabb49b8..039a0ed9eda 100644 --- a/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.yaml @@ -273,7 +273,7 @@ output: nyc_household_credit: 15 -- name: 2025 joint return with one claimable spouse gets no NYC household credit (the claimable spouse is the head) +- name: 2025 joint return with one claimable spouse counts the other spouse's exemption for the NYC credit (the claimable spouse is the head) period: 2025 input: people: @@ -298,9 +298,12 @@ state_code: NY in_nyc: true output: - nyc_household_credit: 0 + # Tax Law 1310(d) counts the exemptions the couple is entitled to: one. + # $30 x 1. IT-201 line 48 would deny the credit; the model follows the + # statute. + nyc_household_credit: 30 -- name: 2025 joint return with one claimable spouse gets no NYC household credit (the claimable spouse is the spouse) +- name: 2025 joint return with one claimable spouse counts the other spouse's exemption for the NYC credit (the claimable spouse is the spouse) period: 2025 input: people: @@ -325,7 +328,7 @@ state_code: NY in_nyc: true output: - nyc_household_credit: 0 + nyc_household_credit: 30 - name: 2025 joint return with neither spouse claimable gets the NYC household credit period: 2025 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.yaml index fbc36a3d88e..c0abdd891c8 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.yaml @@ -15,10 +15,12 @@ age: 65 medicare_enrolled: false health_insurance_premiums: 6_000 - other_medical_expenses: 22_000 + other_medical_expenses: 22_001 self_employed_health_insurance_ald: 1_000 output: - itemized_medical_expenses: 27_000 + # Gross paid: 6,000 + 22,001 = 28,001, over $28,000; the federal + # subtotal (27,001) excludes the 1,000 deducted premium. + itemized_medical_expenses: 27_001 nm_medical_expense_exemption: 3_000 - name: New Mexico senior exemption preserves caller-supplied medical expenses @@ -27,7 +29,7 @@ state_code: NM age: 65 medicare_enrolled: false - itemized_medical_expenses: 28_000 + itemized_medical_expenses: 28_001 output: nm_medical_expense_exemption: 3_000 @@ -58,7 +60,7 @@ input: state_code: NM age: 65 - other_medical_expenses: 28_000 + other_medical_expenses: 28_001 filing_status: JOINT output: nm_medical_expense_exemption: 3_000 @@ -68,7 +70,7 @@ input: state_code: NM age: 65 - other_medical_expenses: 28_000 + other_medical_expenses: 28_001 filing_status: SEPARATE output: # Neither NMSA 7-2-5.9 nor the PIT-ADJ line 18 instructions halve the @@ -87,7 +89,7 @@ # PIT-ADJ line 18: "enter $3,000 on line 18 to claim the exemption". nm_medical_expense_exemption: 3_000 -- name: 2025 expenses of exactly $28,000 qualify under the PIT-ADJ instructions +- name: 2025 expenses of exactly $28,000 do not qualify under the statute period: 2025 input: state_code: NM @@ -96,8 +98,20 @@ other_medical_expenses: 28_000 filing_status: SINGLE output: - # PIT-ADJ line 18: expenses "of $28,000 or more". NMSA 7-2-5.9(A) says - # expenses that "exceed" $28,000, which would give 0 here. + # NMSA 7-2-5.9(A): expenses that "exceed twenty-eight thousand dollars". + # The PIT-ADJ line 18 instructions say "of $28,000 or more", which would + # give 3,000; the model follows the statute. + nm_medical_expense_exemption: 0 + +- name: 2025 expenses one dollar over $28,000 qualify + period: 2025 + input: + state_code: NM + age: 70 + medicare_enrolled: false + other_medical_expenses: 28_001 + filing_status: SINGLE + output: nm_medical_expense_exemption: 3_000 - name: 2025 expenses one dollar under $28,000 do not qualify diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/credits/ny_household_credit.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/credits/ny_household_credit.yaml index 75ecd16039e..4f0b9b35809 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/credits/ny_household_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/credits/ny_household_credit.yaml @@ -127,7 +127,7 @@ output: ny_household_credit_potential: 45 -- name: 2025 joint return with one claimable spouse gets no household credit (the claimable spouse is the head) +- name: 2025 joint return with one claimable spouse counts the other spouse's exemption (the claimable spouse is the head) period: 2025 input: people: @@ -151,10 +151,13 @@ members: [person_a, person_b] state_code: NY output: - # Item C is one box per return; either spouse marks it. - ny_household_credit_potential: 0 + # Tax Law 606(b) counts the exemptions the couple is entitled to: one, + # for the spouse who cannot be claimed. $60 base at $10,000 of AGI plus + # $15 x (1 - 1). IT-201 line 40 would deny the credit because item C is + # marked; the model follows the statute. + ny_household_credit_potential: 60 -- name: 2025 joint return with one claimable spouse gets no household credit (the claimable spouse is the spouse) +- name: 2025 joint return with one claimable spouse counts the other spouse's exemption (the claimable spouse is the spouse) period: 2025 input: people: @@ -178,7 +181,7 @@ members: [person_b, person_a] state_code: NY output: - ny_household_credit_potential: 0 + ny_household_credit_potential: 60 - name: 2025 joint return with neither spouse claimable gets the household credit period: 2025 diff --git a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/credits/cdcc/vt_low_income_cdcc_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/credits/cdcc/vt_low_income_cdcc_eligible.yaml index 2bd8ce741df..0dc4cac1263 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/credits/cdcc/vt_low_income_cdcc_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/vt/tax/income/credits/cdcc/vt_low_income_cdcc_eligible.yaml @@ -1,3 +1,8 @@ +# 32 V.S.A. 5828c as in effect for 2021: federal adjusted gross income "less +# than $30,000.00 (or $40,000.00 for married, filing jointly)". The 2021 +# Schedule IN-112 says "$30,000 (or $40,000 for Married Filing Jointly) or +# less"; the model follows the statute, so income at the threshold does not +# qualify. - name: Vermont low-income CDCC single filer not eligible period: 2021 input: @@ -7,12 +12,21 @@ output: vt_low_income_cdcc_eligible: false -- name: Vermont low-income CDCC single filer eligible +- name: Vermont low-income CDCC single filer at the threshold not eligible period: 2021 input: state_code: VT adjusted_gross_income: 30_000 filing_status: SINGLE + output: + vt_low_income_cdcc_eligible: false + +- name: Vermont low-income CDCC single filer below the threshold eligible + period: 2021 + input: + state_code: VT + adjusted_gross_income: 29_999 + filing_status: SINGLE output: vt_low_income_cdcc_eligible: true @@ -25,14 +39,20 @@ output: vt_low_income_cdcc_eligible: false -- name: Vermont low-income CDCC joint filer eligible +- name: Vermont low-income CDCC joint filer at the threshold not eligible period: 2021 input: state_code: VT adjusted_gross_income: 40_000 filing_status: JOINT output: - vt_low_income_cdcc_eligible: true + vt_low_income_cdcc_eligible: false - - +- name: Vermont low-income CDCC joint filer below the threshold eligible + period: 2021 + input: + state_code: VT + adjusted_gross_income: 39_999 + filing_status: JOINT + output: + vt_low_income_cdcc_eligible: true diff --git a/policyengine_us/tests/policy/baseline/gov/territories/pr/tax/income/credits/earned_income/integration.yaml b/policyengine_us/tests/policy/baseline/gov/territories/pr/tax/income/credits/earned_income/integration.yaml index 218cfb9da86..90853e2571d 100644 --- a/policyengine_us/tests/policy/baseline/gov/territories/pr/tax/income/credits/earned_income/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/territories/pr/tax/income/credits/earned_income/integration.yaml @@ -1,5 +1,5 @@ - name: integration test 1, a married couple with 2 dependents - period: 2024 + period: 2023 input: people: person1: @@ -29,7 +29,7 @@ pr_earned_income_credit: 6_072 - name: integration test 2, individual with 4 dependents - period: 2024 + period: 2023 input: people: person1: diff --git a/policyengine_us/tests/policy/baseline/gov/territories/pr/tax/income/credits/earned_income/pr_earned_income_credit.yaml b/policyengine_us/tests/policy/baseline/gov/territories/pr/tax/income/credits/earned_income/pr_earned_income_credit.yaml index 78abde891bf..92fbdcf688d 100644 --- a/policyengine_us/tests/policy/baseline/gov/territories/pr/tax/income/credits/earned_income/pr_earned_income_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/territories/pr/tax/income/credits/earned_income/pr_earned_income_credit.yaml @@ -25,7 +25,7 @@ pr_earned_income_credit: 0 - name: Eligible person, 0 children with max credit - period: 2024 + period: 2023 input: people: person1: @@ -45,7 +45,7 @@ pr_earned_income_credit: 1_656 - name: Eligible person, 1 child and higher bracket - period: 2024 + period: 2023 input: people: person1: @@ -68,7 +68,7 @@ pr_earned_income_credit: 2_483.004 - name: Eligible person, 0 child and filing jointly - period: 2024 + period: 2023 input: people: person1: @@ -91,7 +91,7 @@ pr_earned_income_credit: 1_656 - name: Eligible person, 1 child, filing jointly, higher bracket - period: 2024 + period: 2023 input: people: person1: @@ -152,7 +152,7 @@ pr_earned_income_credit: 0 - name: Eligible HEAD_OF_HOUSEHOLD filer gets the individual phase-out threshold - period: 2024 + period: 2023 input: people: person1: @@ -176,7 +176,7 @@ pr_earned_income_credit: 1_656 - name: Eligible SURVIVING_SPOUSE filer gets the joint phase-out threshold - period: 2024 + period: 2023 input: people: person1: @@ -198,3 +198,70 @@ # (joint threshold = 19_870), so the full max credit applies. output: pr_earned_income_credit: 1_656 + +# Section 1052.01(i) indexes the credit "según ajustado por el Servicio de +# Rentas Internas Federal"; Hacienda publishes the adjusted amounts in BI RI +# 24-04 (2024) and BI RI 26-01 (2025). +- name: 2024 three or more children, single, phase-out under the 2024 threshold + period: 2024 + input: + people: + person1: + pr_gross_income_person: 25_000 + is_tax_unit_head_or_spouse: true + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + eitc_child_count: 3 + pr_earned_income_credit_eligible: true + households: + household: + members: [person1] + state_code: PR + output: + # 2024 maximum $7,563 less 34.21% of ($25,000 - $24,430) = $194.997. + pr_earned_income_credit: 7_368.003 + +- name: 2025 three or more children, single, under the 2025 threshold + period: 2025 + input: + people: + person1: + pr_gross_income_person: 25_000 + is_tax_unit_head_or_spouse: true + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + eitc_child_count: 3 + pr_earned_income_credit_eligible: true + households: + household: + members: [person1] + state_code: PR + output: + # $25,000 is under the $25,110 threshold, so the $7,774 maximum applies + # (the 2023 schedule would give $7,173 - 34.21% x $1,820 = $6,550.378). + pr_earned_income_credit: 7_774 + +- name: 2025 one child, joint, phase-out over the 2025 threshold + period: 2025 + input: + people: + person1: + pr_gross_income_person: 30_000 + is_tax_unit_head_or_spouse: true + tax_units: + tax_unit: + members: [person1] + filing_status: JOINT + eitc_child_count: 1 + pr_earned_income_credit_eligible: true + households: + household: + members: [person1] + state_code: PR + output: + # $4,186 less 26.92% of ($30,000 - $26,310) = $993.348. + pr_earned_income_credit: 3_192.652 diff --git a/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.py b/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.py index c919d1626d0..8fd375430f3 100644 --- a/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.py +++ b/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.py @@ -8,6 +8,10 @@ class nyc_household_credit(Variable): unit = USD definition_period = YEAR defined_for = "in_nyc" + reference = ( + "https://www.nysenate.gov/legislation/laws/TAX/1310", + "https://www.tax.ny.gov/pdf/2025/inc/it201i_2025.pdf#page=13", + ) def formula(tax_unit, period, parameters): # First get their federal AGI. @@ -25,10 +29,15 @@ def formula(tax_unit, period, parameters): filing_statuses = filing_status.possible_values - # Tax Law 1310(d) excludes an individual whom another taxpayer can - # claim as a dependent; IT-201 line 48: "If you marked the Yes box at - # item C ... you do not qualify for this credit". + # Tax Law 1310(d)(3)(B) excludes, among unmarried individuals, one whom + # another taxpayer can claim as a dependent; for a married couple, + # 1310(d)(2)(B) counts the exemptions the couple is entitled to. + # IT-201 line 48 says "If you marked the Yes box at item C ... you do + # not qualify for this credit"; the model follows the statute on a + # joint return and keeps the exclusion for other returns. dependent_filer = tax_unit("head_or_spouse_is_dependent_elsewhere", period) + joint = filing_status == filing_statuses.JOINT + exemptions = tax_unit("exemptions_count", period) amount = select( [ filing_status == filing_statuses.SINGLE, @@ -42,7 +51,9 @@ def formula(tax_unit, period, parameters): p.separate_per_dependent.calc(federal_agi, right=True) * tax_unit_size, ], # Joint, head of household, and surviving spouse filers have a different - # amount per person, varying with AGI. - default=p.other_per_dependent.calc(federal_agi, right=True) * tax_unit_size, + # amount per person, varying with AGI; a joint return counts its + # federal exemptions. + default=p.other_per_dependent.calc(federal_agi, right=True) + * where(joint, exemptions, tax_unit_size), ) - return where(dependent_filer, 0, amount) + return where(dependent_filer & ~joint, 0, amount) diff --git a/policyengine_us/variables/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.py b/policyengine_us/variables/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.py index 1efcc1a8ac3..0e6a54aac29 100644 --- a/policyengine_us/variables/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.py +++ b/policyengine_us/variables/gov/states/nm/tax/income/exemptions/nm_medical_expense_exemption.py @@ -51,9 +51,9 @@ def formula(tax_unit, period, parameters): age_eligible = tax_unit.any(filer & (age >= p.age_eligibility)) # NMSA 7-2-5.9(A) requires expenses that "exceed twenty-eight thousand # dollars ($28,000)"; PIT-ADJ line 18 instructions allow expenses "of - # $28,000 or more". This keeps the instructions' reading, under which - # expenses of exactly $28,000 qualify, until the conflict is resolved. - expense_eligible = medical_expense >= p.min_expenses + # $28,000 or more". The model follows the statute, so expenses of + # exactly $28,000 do not qualify. + expense_eligible = medical_expense > p.min_expenses eligible = age_eligible & expense_eligible # Neither 7-2-5.9 nor the PIT-ADJ line 18 instructions halve the # exemption for married individuals filing separately. Sections that diff --git a/policyengine_us/variables/gov/states/ny/tax/income/credits/ny_household_credit_potential.py b/policyengine_us/variables/gov/states/ny/tax/income/credits/ny_household_credit_potential.py index b71524d37c8..b163f59696f 100644 --- a/policyengine_us/variables/gov/states/ny/tax/income/credits/ny_household_credit_potential.py +++ b/policyengine_us/variables/gov/states/ny/tax/income/credits/ny_household_credit_potential.py @@ -33,19 +33,29 @@ def formula(tax_unit, period, parameters): ["tax_unit_size", "spouse_separate_tax_unit_size"], ) separate = filing_status == filing_statuses.SEPARATE + joint = filing_status == filing_statuses.JOINT + # Tax Law 606(b)(2)(B) and (3)(B) count "the number of exemptions for + # which the taxpayer (or in the case of a husband and wife, taxpayers) + # is entitled to a deduction" for federal purposes. On a joint return + # that excludes a spouse whom another taxpayer can claim + # (exemptions_count). Other statuses keep the tax unit size. + size = where(joint, tax_unit("exemptions_count", period), size) non_single_base = p.non_single.base.calc(agi, right=True) non_single_additional = p.non_single.additional.calc(agi, right=True) - total_amount_if_not_single = non_single_base + non_single_additional * ( - size - 1 + total_amount_if_not_single = non_single_base + non_single_additional * max_( + size - 1, 0 ) amount_if_not_single = np.ceil( total_amount_if_not_single / where(separate, 2, 1) ) non_single_amount = amount_if_not_single * ~single - # Tax Law 606(b)(3)(B) excludes an individual for whom a deduction - # under IRC 151(c) "is allowable to another taxpayer", and IT-201 line - # 40 says "If you marked the Yes box at item C ... you do not qualify - # for this credit". Item C is one box per return; this formula reads - # it as marked when either spouse of a joint return can be claimed. + # Tax Law 606(b)(3)(B) excludes, among unmarried individuals, one for + # whom a deduction under IRC 151(c) "is allowable to another + # taxpayer"; a married couple is a household in its own right, and its + # credit counts only the exemptions the couple is entitled to. IT-201 + # line 40 says "If you marked the Yes box at item C ... you do not + # qualify for this credit", which denies a joint return with one + # claimable spouse; the model follows the statute and keeps the + # exclusion for other returns. dependent_filer = tax_unit("head_or_spouse_is_dependent_elsewhere", period) - return where(dependent_filer, 0, single_amount + non_single_amount) + return where(dependent_filer & ~joint, 0, single_amount + non_single_amount) diff --git a/policyengine_us/variables/gov/states/vt/tax/income/credits/cdcc/vt_low_income_cdcc_eligible.py b/policyengine_us/variables/gov/states/vt/tax/income/credits/cdcc/vt_low_income_cdcc_eligible.py index 5299936f491..f62072dfe6f 100644 --- a/policyengine_us/variables/gov/states/vt/tax/income/credits/cdcc/vt_low_income_cdcc_eligible.py +++ b/policyengine_us/variables/gov/states/vt/tax/income/credits/cdcc/vt_low_income_cdcc_eligible.py @@ -9,6 +9,8 @@ class vt_low_income_cdcc_eligible(Variable): reference = ( "https://tax.vermont.gov/sites/tax/files/documents/IN-112-2021.pdf#page=2", "https://law.justia.com/codes/vermont/2021/title-32/chapter-151/section-5828c/", + # 2022 Act 138 section 3 reproduces the pre-2022 text it amended. + "https://legislature.vermont.gov/Documents/2022/Docs/ACTS/ACT138/ACT138%20As%20Enacted.pdf#page=3", ) defined_for = StateCode.VT @@ -17,5 +19,8 @@ def formula(tax_unit, period, parameters): filing_status = tax_unit("filing_status", period) federal_agi_threshold = p.income_threshold[filing_status] federal_agi = tax_unit("adjusted_gross_income", period) - # Law says "less than" but tax form says "$x or less". We apply form rules. - return federal_agi <= federal_agi_threshold + # 32 V.S.A. 5828c (as in effect through 2021) requires federal adjusted + # gross income "less than $30,000.00 (or $40,000.00 for married, + # filing jointly)"; the 2021 Schedule IN-112 says "$30,000 (or $40,000 + # for Married Filing Jointly) or less". The model follows the statute. + return federal_agi < federal_agi_threshold From af34291657b12572dabf8fdcf3e1d1b8f874af9d Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 19 Oct 2026 23:25:00 -0400 Subject: [PATCH 2/5] Apply NY/NYC household definitions to every married and head status (review r1) Review r1: Tax Law 606(b)(3)(B) and 1310(d) make a husband and wife, a head of household and a surviving spouse households in their own right; only an unmarried individual is excluded for being claimable. Every non-single return now counts its federal exemptions (a separate return adds the other spouse's unit at its size) and keeps the credit; the statute's "one less than the number of exemptions" is applied literally when there are none, with the credit floored at zero. Also fixes stale NM, VT and PR comments. Co-Authored-By: Claude Opus 5.5 --- .../credits/cdcc/income_limit_in_effect.yaml | 2 +- .../earned_income/unearned_income/limit.yaml | 4 +- ...c_children_medical_exemption_invariants.py | 9 ++-- .../household/nyc_household_credit.yaml | 47 +++++++++++++++++ .../income/credits/ny_household_credit.yaml | 50 ++++++++++++++++++ .../credits/household/nyc_household_credit.py | 31 +++++------ .../credits/ny_household_credit_potential.py | 51 ++++++++++--------- .../vt/tax/income/credits/cdcc/vt_cdcc.py | 8 +-- 8 files changed, 149 insertions(+), 53 deletions(-) diff --git a/policyengine_us/parameters/gov/states/vt/tax/income/credits/cdcc/income_limit_in_effect.yaml b/policyengine_us/parameters/gov/states/vt/tax/income/credits/cdcc/income_limit_in_effect.yaml index 3e736d244b6..3e7124523e0 100644 --- a/policyengine_us/parameters/gov/states/vt/tax/income/credits/cdcc/income_limit_in_effect.yaml +++ b/policyengine_us/parameters/gov/states/vt/tax/income/credits/cdcc/income_limit_in_effect.yaml @@ -1,4 +1,4 @@ -description: Vermont limits the Child and Dependent Care Credit to filers with adjusted gross income at or below the low-income threshold when this is in effect. +description: Vermont limits the Child and Dependent Care Credit to filers with adjusted gross income below the low-income threshold when this is in effect. values: # Through 2021 the Vermont credit under 32 V.S.A. § 5828c was the low-income # child and dependent care credit, limited to filers with AGI below the diff --git a/policyengine_us/parameters/gov/territories/pr/tax/income/credits/earned_income/unearned_income/limit.yaml b/policyengine_us/parameters/gov/territories/pr/tax/income/credits/earned_income/unearned_income/limit.yaml index 6ed10b22989..c173e04814c 100644 --- a/policyengine_us/parameters/gov/territories/pr/tax/income/credits/earned_income/unearned_income/limit.yaml +++ b/policyengine_us/parameters/gov/territories/pr/tax/income/credits/earned_income/unearned_income/limit.yaml @@ -6,7 +6,9 @@ metadata: label: Puerto Rico earned income credit unearned income limit period: year reference: - # discrepancy: legal statute lists unearned income as 2_200 + # Section 1052.01(e) keeps older $2,200 wording but adds a proviso for + # taxable years after 2020 allowing other income, including exempt + # unearned income, equal to or below $10,000. - title: P.R. Laws tit. 13, § 30211 (6)(e) href: https://law.justia.com/codes/puerto-rico/title-thirteen/subtitle-17/part-ii/chapter-1007/subchapter-b/30211/ - title: Schedule CT Individual, Earned Income Credit, 2023 diff --git a/policyengine_us/tests/core/test_nm_ctc_children_medical_exemption_invariants.py b/policyengine_us/tests/core/test_nm_ctc_children_medical_exemption_invariants.py index 1a71feb44d5..c778364bfcb 100644 --- a/policyengine_us/tests/core/test_nm_ctc_children_medical_exemption_invariants.py +++ b/policyengine_us/tests/core/test_nm_ctc_children_medical_exemption_invariants.py @@ -4,8 +4,9 @@ by IRC 152(c), which has no taxpayer identification number requirement, so `nm_ctc_qualifying_children` counts 152(c) children whatever their Social Security number status. NMSA 7-2-5.9 gives $3,000 to a filer or spouse 65 or -older with at least $28,000 of medical care expenses (the PIT-ADJ reading of -the threshold), with no halving for married filing separately. +older with more than $28,000 of medical care expenses (NMSA 7-2-5.9(A); the +PIT-ADJ instructions' "$28,000 or more" is not followed), with no halving for +married filing separately. For households drawn by Hypothesis and a seeded population, in 2023-2026: @@ -25,8 +26,8 @@ 5. Separate filing: a single adult filing separately gets half of `nm_ctc` (7-2-18.34(G)) and the whole `nm_medical_expense_exemption`. 6. Reference exemption: `nm_medical_expense_exemption` is $3,000 exactly when - a filer (not a dependent) is 65 or older and medical expenses are at least - $28,000, and 0 otherwise. + a filer (not a dependent) is 65 or older and medical expenses are more than + $28,000 (NMSA 7-2-5.9(A)), and 0 otherwise. """ import numpy as np diff --git a/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.yaml b/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.yaml index 039a0ed9eda..351bbe2c710 100644 --- a/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.yaml @@ -352,3 +352,50 @@ output: # $30 x 2. nyc_household_credit: 60 + +# Tax Law 1310(d): only an unmarried individual is excluded for being +# claimable; other households count the exemptions they are entitled to. +- name: 2025 separate filer who can be claimed has no exemption for the NYC credit + period: 2025 + input: + people: + person_a: + age: 22 + employment_income: 5_000 + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [person_a] + filing_status: SEPARATE + households: + household: + members: [person_a] + state_code: NY + in_nyc: true + output: + # The separate amount per exemption times zero exemptions. + nyc_household_credit: 0 + +- name: 2025 joint return where both spouses can be claimed has no exemptions for the NYC credit + period: 2025 + input: + people: + person_a: + age: 22 + employment_income: 5_000 + claimed_as_dependent_on_another_return: true + person_b: + age: 22 + employment_income: 5_000 + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + households: + household: + members: [person_a, person_b] + state_code: NY + in_nyc: true + output: + nyc_household_credit: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/credits/ny_household_credit.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/credits/ny_household_credit.yaml index 4f0b9b35809..e21fae0a614 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/credits/ny_household_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/credits/ny_household_credit.yaml @@ -204,3 +204,53 @@ output: # $60 + $15 x (2 - 1). ny_household_credit_potential: 75 + +# Tax Law 606(b)(3)(B): a husband and wife, a head of household and a +# surviving spouse are households in their own right; only an unmarried +# individual is excluded for being claimable. IT-201 line 40 denies every +# return with item C marked; the model follows the statute. +- name: 2025 separate filer who can be claimed counts the couple's exemptions + period: 2025 + input: + people: + person_a: + age: 22 + employment_income: 5_000 + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [person_a] + filing_status: SEPARATE + households: + household: + members: [person_a] + state_code: NY + output: + # Combined AGI $10,000 (the spouse's separate return is assumed alike); + # exemptions: 0 for the claimable filer plus 1 for the spouse's unit. + # $60 base + $15 x (1 - 1) = $60, halved for a separate return. + ny_household_credit_potential: 30 + +- name: 2025 joint return where both spouses can be claimed reduces the base by one additional amount + period: 2025 + input: + people: + person_a: + age: 22 + employment_income: 5_000 + claimed_as_dependent_on_another_return: true + person_b: + age: 22 + employment_income: 5_000 + claimed_as_dependent_on_another_return: true + tax_units: + tax_unit: + members: [person_a, person_b] + filing_status: JOINT + households: + household: + members: [person_a, person_b] + state_code: NY + output: + # No exemptions: $60 base + $15 x (0 - 1) = $45. + ny_household_credit_potential: 45 diff --git a/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.py b/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.py index 8fd375430f3..f019722035b 100644 --- a/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.py +++ b/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.py @@ -24,19 +24,16 @@ def formula(tax_unit, period, parameters): # Then get the household credit part of the parameter tree. p = parameters(period).gov.local.ny.nyc.tax.income.credits.household - # Then get their number of people. - tax_unit_size = tax_unit("tax_unit_size", period) - filing_statuses = filing_status.possible_values - # Tax Law 1310(d)(3)(B) excludes, among unmarried individuals, one whom - # another taxpayer can claim as a dependent; for a married couple, - # 1310(d)(2)(B) counts the exemptions the couple is entitled to. - # IT-201 line 48 says "If you marked the Yes box at item C ... you do - # not qualify for this credit"; the model follows the statute on a - # joint return and keeps the exclusion for other returns. + # Tax Law 1310(d) counts the exemptions each household is entitled to + # and excludes, among unmarried individuals, one whom another taxpayer + # can claim; a husband and wife, a head of household and a surviving + # spouse are households in their own right. IT-201 line 48 says "If + # you marked the Yes box at item C ... you do not qualify for this + # credit" for every return; the model follows the statute. dependent_filer = tax_unit("head_or_spouse_is_dependent_elsewhere", period) - joint = filing_status == filing_statuses.JOINT + single = filing_status == filing_statuses.SINGLE exemptions = tax_unit("exemptions_count", period) amount = select( [ @@ -46,14 +43,12 @@ def formula(tax_unit, period, parameters): [ # Single filers get a flat amount. p.flat_amount.calc(federal_agi, right=True), - # Separate filers get an amount for each person in the tax - # unit, varying with AGI. - p.separate_per_dependent.calc(federal_agi, right=True) * tax_unit_size, + # Separate filers get an amount for each exemption, varying + # with AGI. + p.separate_per_dependent.calc(federal_agi, right=True) * exemptions, ], # Joint, head of household, and surviving spouse filers have a different - # amount per person, varying with AGI; a joint return counts its - # federal exemptions. - default=p.other_per_dependent.calc(federal_agi, right=True) - * where(joint, exemptions, tax_unit_size), + # amount per exemption, varying with AGI. + default=p.other_per_dependent.calc(federal_agi, right=True) * exemptions, ) - return where(dependent_filer & ~joint, 0, amount) + return where(dependent_filer & single, 0, amount) diff --git a/policyengine_us/variables/gov/states/ny/tax/income/credits/ny_household_credit_potential.py b/policyengine_us/variables/gov/states/ny/tax/income/credits/ny_household_credit_potential.py index b163f59696f..a5045e0f2df 100644 --- a/policyengine_us/variables/gov/states/ny/tax/income/credits/ny_household_credit_potential.py +++ b/policyengine_us/variables/gov/states/ny/tax/income/credits/ny_household_credit_potential.py @@ -25,37 +25,38 @@ def formula(tax_unit, period, parameters): # Use `right=True` to reflect "over...but not over". amount_if_single = p.single.calc(agi, right=True) single_amount = single * amount_if_single - # All others are based on AGI & size. - # In the case of married filing separate, based on the combined AGI and size. - size = add( - tax_unit, - period, - ["tax_unit_size", "spouse_separate_tax_unit_size"], - ) + # All others are based on AGI and the number of exemptions. Tax Law + # 606(b)(2)(B) and (3)(B) count "the number of exemptions for which + # the taxpayer (or in the case of a husband and wife, taxpayers) is + # entitled to a deduction" for federal purposes, which leaves out a + # filer whom another taxpayer can claim (exemptions_count). A married + # couple filing separately uses the couple's combined AGI and + # exemptions; the model has no record of the other spouse's + # claimability, so the spouse's separate unit counts at its size. separate = filing_status == filing_statuses.SEPARATE - joint = filing_status == filing_statuses.JOINT - # Tax Law 606(b)(2)(B) and (3)(B) count "the number of exemptions for - # which the taxpayer (or in the case of a husband and wife, taxpayers) - # is entitled to a deduction" for federal purposes. On a joint return - # that excludes a spouse whom another taxpayer can claim - # (exemptions_count). Other statuses keep the tax unit size. - size = where(joint, tax_unit("exemptions_count", period), size) + size = tax_unit("exemptions_count", period) + tax_unit( + "spouse_separate_tax_unit_size", period + ) non_single_base = p.non_single.base.calc(agi, right=True) non_single_additional = p.non_single.additional.calc(agi, right=True) - total_amount_if_not_single = non_single_base + non_single_additional * max_( - size - 1, 0 + # The statute adds the additional amount for each exemption beyond one. + # With no exemptions (both spouses of a refund-only joint return can + # be claimed) that is one less than none, so the base is reduced; + # the credit is not negative. + total_amount_if_not_single = max_( + non_single_base + non_single_additional * (size - 1), 0 ) amount_if_not_single = np.ceil( total_amount_if_not_single / where(separate, 2, 1) ) non_single_amount = amount_if_not_single * ~single - # Tax Law 606(b)(3)(B) excludes, among unmarried individuals, one for - # whom a deduction under IRC 151(c) "is allowable to another - # taxpayer"; a married couple is a household in its own right, and its - # credit counts only the exemptions the couple is entitled to. IT-201 - # line 40 says "If you marked the Yes box at item C ... you do not - # qualify for this credit", which denies a joint return with one - # claimable spouse; the model follows the statute and keeps the - # exclusion for other returns. + # Tax Law 606(b)(3)(B) defines a household as a husband and wife, a + # head of household, a surviving spouse, or an unmarried individual + # for whom a deduction under IRC 151(c) is not "allowable to another + # taxpayer". Only a single filer is excluded for being claimable; the + # other households count only the exemptions they are entitled to. + # IT-201 line 40 says "If you marked the Yes box at item C ... you do + # not qualify for this credit" for every return; the model follows the + # statute. dependent_filer = tax_unit("head_or_spouse_is_dependent_elsewhere", period) - return where(dependent_filer & ~joint, 0, single_amount + non_single_amount) + return where(dependent_filer & single, 0, single_amount + non_single_amount) diff --git a/policyengine_us/variables/gov/states/vt/tax/income/credits/cdcc/vt_cdcc.py b/policyengine_us/variables/gov/states/vt/tax/income/credits/cdcc/vt_cdcc.py index 6b613164326..a3da549d124 100644 --- a/policyengine_us/variables/gov/states/vt/tax/income/credits/cdcc/vt_cdcc.py +++ b/policyengine_us/variables/gov/states/vt/tax/income/credits/cdcc/vt_cdcc.py @@ -27,10 +27,10 @@ def formula(tax_unit, period, parameters): federal_cdcc = tax_unit("pre_obbba_capped_cdcc", period) else: federal_cdcc = tax_unit("capped_cdcc", period) - # 2022 Act 138 raised the 5828c match to 72 percent but retained the - # low-income adjusted gross income cap and the certified-facility - # requirement for 2022; 2023 Act 72 removed them effective 2023. We - # do not track facility certification or in-state care at the moment. + # 2022 Act 138 raised the 5828c match to 72 percent from 2022, and the + # 2022 Schedule IN-112 applies it with no income test (see + # income_limit_in_effect). We do not track facility certification or + # in-state care at the moment. if p.income_limit_in_effect: income_eligible = tax_unit("vt_low_income_cdcc_eligible", period) return income_eligible * federal_cdcc * p.rate From ef2a69ff36dc21dce6f93f589363e8b21881f3eb Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 19 Oct 2026 23:32:20 -0400 Subject: [PATCH 3/5] Use the couple's AGI and exemptions for NYC separate filers (review r2) Review r2: Tax Law 1310(d) aggregates a husband and wife's federal AGIs "irrespective of whether joint or separate city income tax returns are filed" and gives each spouse half the household credit; IT-201 Table 6 keys on AGI and dependents "from both returns". The NYC credit for a separate return now uses own plus spouse AGI and exemptions (the spouse's return assumed alike, as for New York State); the older separate cases are restated on household AGI. Adds head-of-household cases for NY and NYC and widens the changelog. Co-Authored-By: Claude Opus 5.5 --- .../statute-first-state-income-tax.fixed.md | 2 +- .../household/nyc_household_credit.yaml | 89 +++++++++++++++---- .../income/credits/ny_household_credit.yaml | 25 ++++++ .../credits/household/nyc_household_credit.py | 21 ++++- 4 files changed, 117 insertions(+), 20 deletions(-) diff --git a/changelog.d/statute-first-state-income-tax.fixed.md b/changelog.d/statute-first-state-income-tax.fixed.md index c88c93ad797..854195d277d 100644 --- a/changelog.d/statute-first-state-income-tax.fixed.md +++ b/changelog.d/statute-first-state-income-tax.fixed.md @@ -1 +1 @@ -Follow the statute where state forms differ: New Mexico's senior medical exemption needs expenses over $28,000; Vermont's 2021 low-income child care credit needs AGI under the threshold; New York and New York City household credits count a joint return's federal exemptions rather than denying the credit when one spouse can be claimed; and Puerto Rico's earned income credit uses Hacienda's 2024 and 2025 inflation-adjusted schedules. +Follow the statute where state forms differ: New Mexico's senior medical exemption needs expenses over $28,000; Vermont's 2021 low-income child care credit needs AGI under the threshold; the New York and New York City household credits count the federal exemptions of married, head-of-household and surviving-spouse filers instead of denying the credit when a filer can be claimed (Tax Law 606(b), 1310(d)), and the New York City credit for a married couple filing separately uses the couple's AGI and exemptions; and Puerto Rico's earned income credit uses Hacienda's 2024 and 2025 inflation-adjusted schedules. diff --git a/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.yaml b/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.yaml index 351bbe2c710..ac66461bdcc 100644 --- a/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.yaml @@ -186,52 +186,81 @@ output: nyc_household_credit: 0 -- name: Separate filers making $15k or less get $15 per tax unit dependent (size=2). +# Tax Law 1310(d) and IT-201 Table 6: a married couple filing separately is +# one household, keyed on federal AGI and dependents "from both returns" plus +# one for each spouse, and each spouse gets half the household amount. +- name: Separate filers with household AGI of $15k or less get $15 per exemption of the couple. period: 2022 absolute_error_margin: 0 input: state_code: NY in_nyc: true - adjusted_gross_income: 15_000 + adjusted_gross_income: 7_500 + spouse_separate_adjusted_gross_income: 7_500 filing_status: SEPARATE tax_unit_size: 2 + spouse_separate_tax_unit_size: 2 output: - nyc_household_credit: 30 + # Household AGI $15,000; 2 + 2 exemptions x $15. + nyc_household_credit: 60 -- name: Separate filers making $18k get $8 per tax unit dependent (size=4). +- name: Separate filers with household AGI of $18k get $8 per exemption of the couple. period: 2022 - absolute_error_margin: 3 # Table has some rounding. + absolute_error_margin: 0 input: state_code: NY in_nyc: true - adjusted_gross_income: 18_000 + adjusted_gross_income: 9_000 + spouse_separate_adjusted_gross_income: 9_000 filing_status: SEPARATE tax_unit_size: 4 + spouse_separate_tax_unit_size: 4 output: - nyc_household_credit: 32 + # Household AGI $18,000; 4 + 4 exemptions x $8. + nyc_household_credit: 64 -- name: Separate filers making $21k get $5 per tax unit dependent. +- name: Separate filers with household AGI of $21k get $5 per exemption of the couple. period: 2022 - absolute_error_margin: 3 # Table has some rounding. + absolute_error_margin: 0 input: state_code: NY in_nyc: true - adjusted_gross_income: 21_000 + adjusted_gross_income: 10_500 + spouse_separate_adjusted_gross_income: 10_500 filing_status: SEPARATE tax_unit_size: 6 + spouse_separate_tax_unit_size: 6 output: - nyc_household_credit: 30 + # Household AGI $21,000; 6 + 6 exemptions x $5. + nyc_household_credit: 60 -- name: Separate filers making over $22.5k gets no credit. +- name: Separate filers with household AGI over $22.5k get no credit. period: 2022 absolute_error_margin: 0 input: state_code: NY in_nyc: true - adjusted_gross_income: 22_501 + adjusted_gross_income: 11_251 + spouse_separate_adjusted_gross_income: 11_251 filing_status: SEPARATE tax_unit_size: 4 + spouse_separate_tax_unit_size: 4 + output: + nyc_household_credit: 0 + +- name: A separate filer's own AGI alone does not set the bracket. + period: 2022 + absolute_error_margin: 0 + input: + state_code: NY + in_nyc: true + adjusted_gross_income: 15_000 + spouse_separate_adjusted_gross_income: 15_000 + filing_status: SEPARATE + tax_unit_size: 2 + spouse_separate_tax_unit_size: 2 output: + # Household AGI $30,000 is over $22,500. nyc_household_credit: 0 @@ -355,7 +384,7 @@ # Tax Law 1310(d): only an unmarried individual is excluded for being # claimable; other households count the exemptions they are entitled to. -- name: 2025 separate filer who can be claimed has no exemption for the NYC credit +- name: 2025 separate filer who can be claimed gets half the household credit for the spouse's exemption period: 2025 input: people: @@ -373,8 +402,10 @@ state_code: NY in_nyc: true output: - # The separate amount per exemption times zero exemptions. - nyc_household_credit: 0 + # Household AGI $10,000 (the spouse's return is assumed alike); the + # couple's exemptions: 0 for the claimable filer plus 1 for the spouse. + # $15 x 1. + nyc_household_credit: 15 - name: 2025 joint return where both spouses can be claimed has no exemptions for the NYC credit period: 2025 @@ -399,3 +430,29 @@ in_nyc: true output: nyc_household_credit: 0 + +- name: 2025 head of household who can be claimed counts the dependent's exemption + period: 2025 + input: + people: + parent: + age: 22 + employment_income: 10_000 + claimed_as_dependent_on_another_return: true + child: + age: 2 + tax_units: + tax_unit: + members: [parent, child] + filing_status: HEAD_OF_HOUSEHOLD + # The would-be claimant need not file, so the parent may still claim + # the child (Publication 501, Dependent Taxpayer Test). + head_or_spouse_is_dependent_elsewhere_without_filing_exception: false + households: + household: + members: [parent, child] + state_code: NY + in_nyc: true + output: + # One exemption (the child); $30 x 1. + nyc_household_credit: 30 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/credits/ny_household_credit.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/credits/ny_household_credit.yaml index e21fae0a614..f73505ce3ad 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/credits/ny_household_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/tax/income/credits/ny_household_credit.yaml @@ -254,3 +254,28 @@ output: # No exemptions: $60 base + $15 x (0 - 1) = $45. ny_household_credit_potential: 45 + +- name: 2025 head of household who can be claimed counts the dependent's exemption + period: 2025 + input: + people: + parent: + age: 22 + employment_income: 10_000 + claimed_as_dependent_on_another_return: true + child: + age: 2 + tax_units: + tax_unit: + members: [parent, child] + filing_status: HEAD_OF_HOUSEHOLD + # The would-be claimant need not file, so the parent may still claim + # the child (Publication 501, Dependent Taxpayer Test). + head_or_spouse_is_dependent_elsewhere_without_filing_exception: false + households: + household: + members: [parent, child] + state_code: NY + output: + # One exemption: $60 base + $15 x (1 - 1). + ny_household_credit_potential: 60 diff --git a/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.py b/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.py index f019722035b..7713cb7bca4 100644 --- a/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.py +++ b/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.py @@ -35,6 +35,20 @@ def formula(tax_unit, period, parameters): dependent_filer = tax_unit("head_or_spouse_is_dependent_elsewhere", period) single = filing_status == filing_statuses.SINGLE exemptions = tax_unit("exemptions_count", period) + # A husband and wife filing separately are one household: 1310(d) + # aggregates "their federal adjusted gross incomes ... irrespective + # of whether joint or separate city income tax returns are filed", + # and each spouse gets "one-half the credit otherwise allowed his or + # her household" (IT-201 Table 6: AGI and dependents "from both + # returns", plus one for each spouse). The model has no record of the + # other spouse's return, so it is assumed alike, as for New York + # State: its AGI and its unit's size are added. + separate_agi = federal_agi + tax_unit( + "spouse_separate_adjusted_gross_income", period + ) + separate_exemptions = exemptions + tax_unit( + "spouse_separate_tax_unit_size", period + ) amount = select( [ filing_status == filing_statuses.SINGLE, @@ -43,9 +57,10 @@ def formula(tax_unit, period, parameters): [ # Single filers get a flat amount. p.flat_amount.calc(federal_agi, right=True), - # Separate filers get an amount for each exemption, varying - # with AGI. - p.separate_per_dependent.calc(federal_agi, right=True) * exemptions, + # Separate filers get half the household amount for each of the + # couple's exemptions, varying with the couple's AGI. + p.separate_per_dependent.calc(separate_agi, right=True) + * separate_exemptions, ], # Joint, head of household, and surviving spouse filers have a different # amount per exemption, varying with AGI. From 1bc6af0c093368a54a6019afbfae99e5568038e6 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 19 Oct 2026 23:41:23 -0400 Subject: [PATCH 4/5] Halve the NYC household credit for separate filers after the household amount (review r3) Review r3: Tax Law 1310(d) gives a separate filer half the household's credit. The model multiplied IT-201 Table 6's rounded per-exemption half amount by the couple's exemptions ($8 x 8 = $64 at $18,000); it now takes the married amount per exemption times the couple's exemptions, halved ($15 x 8 / 2 = $60). Table 6's separate amounts are kept as a parameter for reference but no longer used. Co-Authored-By: Claude Opus 5.5 --- .../household/separate_per_dependent.yaml | 6 +++++- .../credits/household/nyc_household_credit.yaml | 17 ++++++++++------- .../credits/household/nyc_household_credit.py | 12 ++++++++---- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/policyengine_us/parameters/gov/local/ny/nyc/tax/income/credits/household/separate_per_dependent.yaml b/policyengine_us/parameters/gov/local/ny/nyc/tax/income/credits/household/separate_per_dependent.yaml index 93b9f764442..5f86166d977 100644 --- a/policyengine_us/parameters/gov/local/ny/nyc/tax/income/credits/household/separate_per_dependent.yaml +++ b/policyengine_us/parameters/gov/local/ny/nyc/tax/income/credits/household/separate_per_dependent.yaml @@ -1,4 +1,8 @@ -description: NYC provides a household credit of this amount per dependent for separate filers. +description: NYC provides a household credit of this amount per dependent for separate filers under IT-201 Table 6. +# Not used by nyc_household_credit: Tax Law 1310(d) gives a separate filer +# "one-half the credit otherwise allowed his or her household", which the +# model computes from the married amounts (other_per_dependent). Table 6 +# rounds the half amount per exemption, which can differ by a dollar or more. metadata: type: single_amount threshold_unit: currency-USD diff --git a/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.yaml b/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.yaml index ac66461bdcc..11b8c7c5e50 100644 --- a/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.yaml @@ -189,7 +189,7 @@ # Tax Law 1310(d) and IT-201 Table 6: a married couple filing separately is # one household, keyed on federal AGI and dependents "from both returns" plus # one for each spouse, and each spouse gets half the household amount. -- name: Separate filers with household AGI of $15k or less get $15 per exemption of the couple. +- name: Separate filers with household AGI of $15k or less get half of $30 per exemption of the couple. period: 2022 absolute_error_margin: 0 input: @@ -201,10 +201,10 @@ tax_unit_size: 2 spouse_separate_tax_unit_size: 2 output: - # Household AGI $15,000; 2 + 2 exemptions x $15. + # Household AGI $15,000: $30 x (2 + 2) = $120, halved. nyc_household_credit: 60 -- name: Separate filers with household AGI of $18k get $8 per exemption of the couple. +- name: Separate filers with household AGI of $18k get half of $15 per exemption of the couple. period: 2022 absolute_error_margin: 0 input: @@ -216,10 +216,13 @@ tax_unit_size: 4 spouse_separate_tax_unit_size: 4 output: - # Household AGI $18,000; 4 + 4 exemptions x $8. - nyc_household_credit: 64 + # Household AGI $18,000: the married amount is $15 per exemption, so the + # household credit is $15 x 8 = $120 and each spouse gets half, $60. + # IT-201 Table 6 rounds the half amount to $8 per exemption ($53 + $8 = + # $61 with its over-seven note); the model follows Tax Law 1310(d). + nyc_household_credit: 60 -- name: Separate filers with household AGI of $21k get $5 per exemption of the couple. +- name: Separate filers with household AGI of $21k get half of $10 per exemption of the couple. period: 2022 absolute_error_margin: 0 input: @@ -231,7 +234,7 @@ tax_unit_size: 6 spouse_separate_tax_unit_size: 6 output: - # Household AGI $21,000; 6 + 6 exemptions x $5. + # Household AGI $21,000: $10 x (6 + 6) = $120, halved. nyc_household_credit: 60 - name: Separate filers with household AGI over $22.5k get no credit. diff --git a/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.py b/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.py index 7713cb7bca4..0ae037ce399 100644 --- a/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.py +++ b/policyengine_us/variables/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.py @@ -57,10 +57,14 @@ def formula(tax_unit, period, parameters): [ # Single filers get a flat amount. p.flat_amount.calc(federal_agi, right=True), - # Separate filers get half the household amount for each of the - # couple's exemptions, varying with the couple's AGI. - p.separate_per_dependent.calc(separate_agi, right=True) - * separate_exemptions, + # Separate filers get "one-half the credit otherwise allowed his + # or her household": the married amount per exemption at the + # couple's AGI, times the couple's exemptions, halved. IT-201 + # Table 6 instead rounds a per-exemption half amount, which can + # differ by a dollar or more; the model follows the statute. + p.other_per_dependent.calc(separate_agi, right=True) + * separate_exemptions + / 2, ], # Joint, head of household, and surviving spouse filers have a different # amount per exemption, varying with AGI. From 396044ce482e4b4ab868ac08fbcdc49e1c1cdeea Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 19 Oct 2026 23:44:16 -0400 Subject: [PATCH 5/5] Correct an NYC test comment and the Table 6 parameter description (review r4) Co-Authored-By: Claude Opus 5.5 --- .../tax/income/credits/household/separate_per_dependent.yaml | 2 +- .../nyc/tax/income/credits/household/nyc_household_credit.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/policyengine_us/parameters/gov/local/ny/nyc/tax/income/credits/household/separate_per_dependent.yaml b/policyengine_us/parameters/gov/local/ny/nyc/tax/income/credits/household/separate_per_dependent.yaml index 5f86166d977..00bfeece18e 100644 --- a/policyengine_us/parameters/gov/local/ny/nyc/tax/income/credits/household/separate_per_dependent.yaml +++ b/policyengine_us/parameters/gov/local/ny/nyc/tax/income/credits/household/separate_per_dependent.yaml @@ -1,4 +1,4 @@ -description: NYC provides a household credit of this amount per dependent for separate filers under IT-201 Table 6. +description: IT-201 Table 6 lists this NYC household credit amount per dependent for separate filers. # Not used by nyc_household_credit: Tax Law 1310(d) gives a separate filer # "one-half the credit otherwise allowed his or her household", which the # model computes from the married amounts (other_per_dependent). Table 6 diff --git a/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.yaml b/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.yaml index 11b8c7c5e50..35d0c19d297 100644 --- a/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/local/ny/nyc/tax/income/credits/household/nyc_household_credit.yaml @@ -407,7 +407,7 @@ output: # Household AGI $10,000 (the spouse's return is assumed alike); the # couple's exemptions: 0 for the claimable filer plus 1 for the spouse. - # $15 x 1. + # $30 x 1, halved. nyc_household_credit: 15 - name: 2025 joint return where both spouses can be claimed has no exemptions for the NYC credit