diff --git a/changelog.d/fix-reform-household-tax-drift.fixed.md b/changelog.d/fix-reform-household-tax-drift.fixed.md new file mode 100644 index 00000000000..9bf0f555f9a --- /dev/null +++ b/changelog.d/fix-reform-household-tax-drift.fixed.md @@ -0,0 +1 @@ +With the abolish-federal-income-tax or abolish-payroll-tax parameter on, household tax and net income now count every other tax: the Idaho permanent building fund tax, the California, Indiana, North Carolina, Oklahoma and Pennsylvania use taxes, and every local income and occupational tax (only New York City's was counted). Pennsylvania refundable credits are no longer counted twice, and federal refundable credits leave household net income in every year the federal income tax is abolished, including when the parameter is first set after 2024. diff --git a/changelog.d/fix-reform-household-tax-drift.removed.md b/changelog.d/fix-reform-household-tax-drift.removed.md new file mode 100644 index 00000000000..3837245e7fe --- /dev/null +++ b/changelog.d/fix-reform-household-tax-drift.removed.md @@ -0,0 +1 @@ +Removed the abolish-federal-income-tax and abolish-payroll-tax structural reforms (`policyengine_us.reforms.federal`). Their parameters, `gov.contrib.ubi_center.flat_tax.abolish_federal_income_tax` and `abolish_payroll_tax`, still abolish the taxes through the baseline formulas. Also removed the parameters `gov.household.household_tax_before_refundable_credits`, which only those reforms read, and `gov.household.household_refundable_credits`, which nothing read. diff --git a/policyengine_us/parameters/gov/household/household_refundable_credits.yaml b/policyengine_us/parameters/gov/household/household_refundable_credits.yaml deleted file mode 100644 index 5938f3e3b76..00000000000 --- a/policyengine_us/parameters/gov/household/household_refundable_credits.yaml +++ /dev/null @@ -1,55 +0,0 @@ -description: All household refundable credits. -values: - 0000-01-01: - - income_tax_refundable_credits # Federal. - - al_refundable_credits # Alabama. - - ar_refundable_credits # Arkansas. - - az_refundable_credits # Arizona. - - ca_refundable_credits # California. - - co_refundable_credits # Colorado - - ct_refundable_credits # Connecticut. - - dc_refundable_credits # District of Columbia. - - de_refundable_credits # Delaware. - - ga_refundable_credits # Georgia. - - hi_refundable_credits # Hawaii. - - ia_refundable_credits # Iowa. - - id_refundable_credits # Idaho. - - il_refundable_credits # Illinois. - - in_refundable_credits # Indiana. - - ks_refundable_credits # Kansas. - - ky_refundable_credits # Kentucky. - - la_refundable_credits # Louisiana. - - ma_refundable_credits # Massachusetts. - - me_refundable_credits # Maine. - - md_refundable_credits # Maryland. - - mi_refundable_credits # Michigan. - - mn_refundable_credits # Minnesota. - - mo_refundable_credits # Missouri. - - ms_refundable_credits # Mississippi. - - mt_refundable_credits # Montana. - # Skip NC which has no refundable credits. - - nd_refundable_credits # North Dakota. - - ne_refundable_credits # Nebraska. - - nh_refundable_credits # New Hampshire. - - nm_refundable_credits # New Mexico. - - nj_refundable_credits # New Jersey. - - ny_refundable_credits # New York. - - oh_refundable_credits # Ohio. - - ok_refundable_credits # Oklahoma. - - or_refundable_credits # Oregon. - - pa_refundable_tax_credits # Pennsylvania. - - ri_refundable_credits # Rhode Island. - - sc_refundable_credits # South Carolina. - - wa_refundable_credits # Washington. - - nyc_refundable_credits # New York City. - - ut_refundable_credits # Utah. - - va_refundable_credits # Virginia. - - vt_refundable_credits # Vermont. - - wi_refundable_credits # Wisconsin. - - wv_refundable_credits # West Virginia. - - -metadata: - unit: list - period: year - label: Household refundable credits diff --git a/policyengine_us/parameters/gov/household/household_tax_before_refundable_credits.yaml b/policyengine_us/parameters/gov/household/household_tax_before_refundable_credits.yaml deleted file mode 100644 index 656d4b40bf3..00000000000 --- a/policyengine_us/parameters/gov/household/household_tax_before_refundable_credits.yaml +++ /dev/null @@ -1,57 +0,0 @@ -description: The sum of total tax before refundable credits -values: - 0000-01-01: - - al_income_tax_before_refundable_credits - - ar_income_tax_before_refundable_credits - - az_income_tax_before_refundable_credits - - ca_income_tax_before_refundable_credits - - co_income_tax_before_refundable_credits - - ct_income_tax_before_refundable_credits - - dc_income_tax_before_refundable_credits - - de_income_tax_before_refundable_credits - - employee_payroll_tax - - flat_tax - - ga_income_tax_before_refundable_credits - - hi_income_tax_before_refundable_credits - - ia_income_tax_before_refundable_credits - - id_income_tax_before_refundable_credits - - income_tax_before_refundable_credits - - il_total_tax - - in_income_tax_before_refundable_credits - - ks_income_tax_before_refundable_credits - - ky_income_tax_before_refundable_credits - - la_income_tax_before_refundable_credits - - me_income_tax_before_refundable_credits - - ma_income_tax_before_refundable_credits - - md_income_tax_before_refundable_credits - - mi_income_tax_before_refundable_credits - - mn_income_tax_before_refundable_credits - - mo_income_tax_before_refundable_credits - - ms_income_tax_before_credits_unit - - mt_income_tax_before_refundable_credits_unit - - nc_income_tax # NC has no refundable credits. - - nd_income_tax_before_refundable_credits - - ne_income_tax_before_refundable_credits - - nh_income_tax_before_refundable_credits - - nj_income_tax_before_refundable_credits - - nm_income_tax_before_refundable_credits - - ny_income_tax_before_refundable_credits - - oh_income_tax_before_refundable_credits - - or_income_tax_before_refundable_credits - - ok_income_tax_before_refundable_credits - - pa_income_tax # PA has no refundable credits. - - ri_income_tax_before_refundable_credits - - self_employment_tax - - sc_income_tax_before_refundable_credits - - wa_income_tax_before_refundable_credits - - nyc_income_tax_before_refundable_credits - - ut_income_tax_before_refundable_credits - - va_income_tax_before_refundable_credits - - vt_income_tax_before_refundable_credits - - wi_income_tax_before_refundable_credits - - wv_income_tax_before_refundable_credits - -metadata: - unit: list - period: year - label: Total tax before refundable credits diff --git a/policyengine_us/reforms/federal/__init__.py b/policyengine_us/reforms/federal/__init__.py deleted file mode 100644 index a19e1941be5..00000000000 --- a/policyengine_us/reforms/federal/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .abolish_federal_income_tax import ( - create_abolish_federal_income_tax_reform, -) -from .abolish_payroll_tax import create_abolish_payroll_tax_reform diff --git a/policyengine_us/reforms/federal/abolish_federal_income_tax.py b/policyengine_us/reforms/federal/abolish_federal_income_tax.py deleted file mode 100644 index d2cfd51036b..00000000000 --- a/policyengine_us/reforms/federal/abolish_federal_income_tax.py +++ /dev/null @@ -1,57 +0,0 @@ -from policyengine_us.model_api import * - - -def create_abolish_federal_income_tax() -> Reform: - class household_tax_before_refundable_credits(Variable): - value_type = float - entity = Household - label = "total tax before refundable credits" - documentation = "Total tax liability before refundable credits." - unit = USD - definition_period = YEAR - - def formula(household, period, parameters): - p = parameters(period) - added_components = p.gov.household.household_tax_before_refundable_credits - added_components = [ - c - for c in added_components - if c != "income_tax_before_refundable_credits" - ] - return add(household, period, added_components) - - class household_refundable_tax_credits(Variable): - value_type = float - entity = Household - label = "refundable tax credits" - definition_period = YEAR - unit = USD - - adds = [ - "household_refundable_state_tax_credits", - "household_refundable_local_tax_credits", - ] - - class reform(Reform): - def apply(self): - self.update_variable(household_tax_before_refundable_credits) - self.update_variable(household_refundable_tax_credits) - - return reform - - -def create_abolish_federal_income_tax_reform(parameters, period, bypass: bool = False): - if bypass: - return create_abolish_federal_income_tax() - - p = parameters(period).gov.contrib.ubi_center.flat_tax - - if p.abolish_federal_income_tax: - return create_abolish_federal_income_tax() - else: - return None - - -abolish_federal_income_tax = create_abolish_federal_income_tax_reform( - None, None, bypass=True -) diff --git a/policyengine_us/reforms/federal/abolish_payroll_tax.py b/policyengine_us/reforms/federal/abolish_payroll_tax.py deleted file mode 100644 index ab817e009de..00000000000 --- a/policyengine_us/reforms/federal/abolish_payroll_tax.py +++ /dev/null @@ -1,40 +0,0 @@ -from policyengine_us.model_api import * - - -def create_abolish_payroll_tax() -> Reform: - class household_tax_before_refundable_credits(Variable): - value_type = float - entity = Household - label = "total tax before refundable credits" - documentation = "Total tax liability before refundable credits." - unit = USD - definition_period = YEAR - - def formula(household, period, parameters): - p = parameters(period) - added_components = p.gov.household.household_tax_before_refundable_credits - added_components = [ - c for c in added_components if c != "employee_payroll_tax" - ] - return add(household, period, added_components) - - class reform(Reform): - def apply(self): - self.update_variable(household_tax_before_refundable_credits) - - return reform - - -def create_abolish_payroll_tax_reform(parameters, period, bypass: bool = False): - if bypass: - return create_abolish_payroll_tax() - - p = parameters(period).gov.contrib.ubi_center.flat_tax - - if p.abolish_payroll_tax: - return create_abolish_payroll_tax() - else: - return None - - -abolish_payroll_tax = create_abolish_payroll_tax_reform(None, None, bypass=True) diff --git a/policyengine_us/reforms/reforms.py b/policyengine_us/reforms/reforms.py index 46b0754e164..c5161d99244 100644 --- a/policyengine_us/reforms/reforms.py +++ b/policyengine_us/reforms/reforms.py @@ -12,8 +12,6 @@ create_increase_taxable_earnings_for_social_security_reform, ) from .congress.wyden_smith import create_ctc_expansion_reform -from .federal import create_abolish_federal_income_tax_reform -from .federal import create_abolish_payroll_tax_reform from .biden.budget_2025 import ( create_medicare_and_investment_tax_increase_reform, ) @@ -381,10 +379,6 @@ def create_structural_reforms_from_parameters(parameters, period): ) ctc_expansion = create_ctc_expansion_reform(parameters, period) - abolish_federal_income_tax = create_abolish_federal_income_tax_reform( - parameters, period - ) - abolish_payroll_tax = create_abolish_payroll_tax_reform(parameters, period) capital_gains_tax_increase = create_capital_gains_tax_increase_reform( parameters, period ) @@ -605,8 +599,6 @@ def create_structural_reforms_from_parameters(parameters, period): remove_head_of_household, increase_taxable_earnings_for_social_security_reform, ctc_expansion, - abolish_federal_income_tax, - abolish_payroll_tax, medicare_and_investment_tax_increase, capital_gains_tax_increase, halve_joint_eitc_phase_out_rate, diff --git a/policyengine_us/tests/policy/contrib/federal/abolish_federal_income_tax.yaml b/policyengine_us/tests/policy/contrib/federal/abolish_federal_income_tax.yaml index c9b93b42b24..ab110441d04 100644 --- a/policyengine_us/tests/policy/contrib/federal/abolish_federal_income_tax.yaml +++ b/policyengine_us/tests/policy/contrib/federal/abolish_federal_income_tax.yaml @@ -1,23 +1,26 @@ -- name: Abolish federal income tax reform +# With gov.contrib.ubi_center.flat_tax.abolish_federal_income_tax on, +# income_tax_before_refundable_credits is zero and household refundable tax +# credits leave out federal refundable credits. Every other component of the +# household totals stays. Most cases pin the other components and leave one +# to its own formula, with federal income tax left to its formula. + +- name: Abolishing federal income tax drops federal tax and refundable credits period: 2022 - reforms: policyengine_us.reforms.federal.abolish_federal_income_tax.abolish_federal_income_tax input: gov.contrib.ubi_center.flat_tax.abolish_federal_income_tax: true - income_tax_before_refundable_credits: 1_200 income_tax_refundable_credits: 800 co_income_tax_before_refundable_credits: 1_000 hi_refundable_credits: 600 output: + income_tax_before_refundable_credits: 0 household_refundable_tax_credits: 600 household_state_tax_before_refundable_credits: 1_000 household_tax_before_refundable_credits: 1_000 -- name: Multiple state refundable credits +- name: Abolishing federal income tax keeps several state refundable credits period: 2022 - reforms: policyengine_us.reforms.federal.abolish_federal_income_tax.abolish_federal_income_tax input: gov.contrib.ubi_center.flat_tax.abolish_federal_income_tax: true - income_tax_before_refundable_credits: 1_200 income_tax_refundable_credits: 800 co_income_tax_before_refundable_credits: 1_000 hi_refundable_credits: 600 @@ -27,7 +30,7 @@ household_state_tax_before_refundable_credits: 1_000 household_tax_before_refundable_credits: 1_000 -- name: No reform +- name: Federal income tax and refundable credits count when not abolished period: 2022 input: gov.contrib.ubi_center.flat_tax.abolish_federal_income_tax: false @@ -42,7 +45,6 @@ - name: Abolishing federal income tax preserves Montgomery County refundable EITC period: 2024 - reforms: policyengine_us.reforms.federal.abolish_federal_income_tax.abolish_federal_income_tax input: gov.contrib.ubi_center.flat_tax.abolish_federal_income_tax: true age: 30 @@ -59,7 +61,6 @@ - name: Abolishing federal income tax includes NYC refundable credits exactly once period: 2024 - reforms: policyengine_us.reforms.federal.abolish_federal_income_tax.abolish_federal_income_tax input: gov.contrib.ubi_center.flat_tax.abolish_federal_income_tax: true age: 30 @@ -73,3 +74,71 @@ household_refundable_local_tax_credits: 200 household_refundable_tax_credits: 300 household_tax: -300 + +- name: Abolishing federal income tax keeps the Idaho permanent building fund tax + period: 2025 + input: + gov.contrib.ubi_center.flat_tax.abolish_federal_income_tax: true + state_code: ID + employment_income: 60_000 + employee_payroll_tax: 4_590 + income_tax_refundable_credits: 800 + id_income_tax_before_refundable_credits: 2_000 + id_refundable_credits: 100 + household_market_income: 60_000 + household_benefits: 0 + output: + income_tax_before_refundable_credits: 0 + id_pbf: 10 + household_state_tax_before_refundable_credits: 2_010 + household_tax_before_refundable_credits: 6_600 # 4,590 + 2,000 + 10 + household_refundable_tax_credits: 100 + household_net_income: 53_500 # 60,000 + 100 - 6,600 + +- name: Abolishing federal income tax keeps California use tax + period: 2025 + input: + gov.contrib.ubi_center.flat_tax.abolish_federal_income_tax: true + state_code: CA + employment_income: 60_000 + employee_payroll_tax: 600 + ca_income_tax_before_refundable_credits: 1_000 + ca_use_tax: 20 + output: + income_tax_before_refundable_credits: 0 + state_use_tax: 20 + household_state_tax_before_refundable_credits: 1_020 + household_tax_before_refundable_credits: 1_620 # 600 + 1,000 + 20 + +- name: Abolishing federal income tax keeps the Wilmington earned income tax + period: 2025 + input: + gov.contrib.ubi_center.flat_tax.abolish_federal_income_tax: true + state_code: DE + in_wilmington: true + employment_income: 40_000 + employee_payroll_tax: 3_060 + de_income_tax_before_refundable_credits: 1_500 + output: + income_tax_before_refundable_credits: 0 + de_wilmington_earned_income_tax: 500 # 40,000 * 1.25% + local_income_tax_before_refundable_credits: 500 + household_tax_before_refundable_credits: 5_060 # 3,060 + 1,500 + 500 + +- name: Abolishing federal income tax counts Pennsylvania refundable credits once + period: 2025 + input: + gov.contrib.ubi_center.flat_tax.abolish_federal_income_tax: true + state_code: PA + income_tax_refundable_credits: 800 + pa_income_tax_after_forgiveness: 100 + pa_use_tax: 20 + pa_refundable_tax_credits: 5 + household_market_income: 1_000 + household_benefits: 0 + output: + pa_income_tax: 95 + household_tax_before_refundable_credits: 120 # 100 + 20, before the credit + household_refundable_tax_credits: 5 + household_tax: 115 + household_net_income: 885 # 1,000 + 5 - 120 diff --git a/policyengine_us/tests/policy/contrib/federal/abolish_payroll_tax.yaml b/policyengine_us/tests/policy/contrib/federal/abolish_payroll_tax.yaml index 79c6de97654..857ba88e072 100644 --- a/policyengine_us/tests/policy/contrib/federal/abolish_payroll_tax.yaml +++ b/policyengine_us/tests/policy/contrib/federal/abolish_payroll_tax.yaml @@ -1,16 +1,24 @@ -- name: Abolish federal income tax reform +# With gov.contrib.ubi_center.flat_tax.abolish_payroll_tax on, +# employee_payroll_tax is zero and every other component of the household +# total stays. Each case below leaves payroll tax to its formula on earnings, +# pins federal income tax, and leaves one other component to its own formula +# or input. + +- name: Abolishing payroll tax drops it from the household total period: 2022 - reforms: policyengine_us.reforms.federal.abolish_payroll_tax.abolish_payroll_tax input: gov.contrib.ubi_center.flat_tax.abolish_payroll_tax: true + state_code: CA + employment_income: 10_000 + income_tax_before_refundable_credits: 0 ca_income_tax_before_refundable_credits: 1_000 - employee_payroll_tax: 600 + ca_use_tax: 0 output: + employee_payroll_tax: 0 household_state_tax_before_refundable_credits: 1_000 household_tax_before_refundable_credits: 1_000 - -- name: No reform +- name: Payroll tax counts when not abolished period: 2022 input: gov.contrib.ubi_center.flat_tax.abolish_payroll_tax: false @@ -19,3 +27,67 @@ output: household_state_tax_before_refundable_credits: 1_000 household_tax_before_refundable_credits: 1_600 + +- name: Abolishing payroll tax keeps the Idaho permanent building fund tax + period: 2025 + input: + gov.contrib.ubi_center.flat_tax.abolish_payroll_tax: true + state_code: ID + employment_income: 60_000 + income_tax_before_refundable_credits: 5_000 + income_tax_refundable_credits: 800 + id_income_tax_before_refundable_credits: 2_000 + id_refundable_credits: 100 + household_market_income: 60_000 + household_benefits: 0 + output: + employee_payroll_tax: 0 + id_pbf: 10 + household_state_tax_before_refundable_credits: 2_010 + household_tax_before_refundable_credits: 7_010 # 5,000 + 2,000 + 10 + household_refundable_tax_credits: 900 + household_net_income: 53_890 # 60,000 + 900 - 7,010 + +- name: Abolishing payroll tax keeps North Carolina use tax + period: 2025 + input: + gov.contrib.ubi_center.flat_tax.abolish_payroll_tax: true + state_code: NC + employment_income: 60_000 + income_tax_before_refundable_credits: 0 + nc_income_tax: 1_000 + nc_use_tax: 20 + output: + employee_payroll_tax: 0 + state_use_tax: 20 + household_state_tax_before_refundable_credits: 1_020 + household_tax_before_refundable_credits: 1_020 + +- name: Abolishing payroll tax keeps the Jefferson County occupational tax + period: 2025 + input: + gov.contrib.ubi_center.flat_tax.abolish_payroll_tax: true + state_code: KY + county_str: JEFFERSON_COUNTY_KY + employment_income: 50_000 + income_tax_before_refundable_credits: 3_000 + ky_income_tax_before_refundable_credits: 1_500 + output: + employee_payroll_tax: 0 + ky_jefferson_occupational_tax: 1_100 # 50,000 * 2.20% + local_occupational_tax: 1_100 + household_tax_before_refundable_credits: 5_600 # 3,000 + 1,500 + 1,100 + +- name: Abolishing payroll tax keeps Maryland county income tax + period: 2025 + input: + gov.contrib.ubi_center.flat_tax.abolish_payroll_tax: true + state_code: MD + employment_income: 60_000 + income_tax_before_refundable_credits: 0 + md_income_tax_before_refundable_credits: 1_000 + md_local_income_tax_before_credits: 400 + output: + employee_payroll_tax: 0 + local_income_tax_before_refundable_credits: 400 + household_tax_before_refundable_credits: 1_400 diff --git a/policyengine_us/tests/test_aggregate_list_integrity.py b/policyengine_us/tests/test_aggregate_list_integrity.py index 0f8fface8ac..a61c3dad40a 100644 --- a/policyengine_us/tests/test_aggregate_list_integrity.py +++ b/policyengine_us/tests/test_aggregate_list_integrity.py @@ -14,6 +14,11 @@ hardcoded list, so a contributed program added to the baseline aggregates is silently dropped under those reforms unless it is added to every copy. That shipped with the Trump dividend, which #9430 added to the baseline lists only. + +The last test keeps reforms from copying the household tax totals at all. The +abolish-federal-income-tax and abolish-payroll-tax reforms once did, from a +separate list that lost the Idaho permanent building fund tax, five states' +use taxes and every local tax except New York City's. """ import ast @@ -225,3 +230,34 @@ def test_reform_aggregates_keep_baseline_contrib_entries( "here too, or record a deliberate omission in " "ALLOWED_REFORM_OMISSIONS in this test." ) + + +# --- Household tax totals ------------------------------------------------- + +HOUSEHOLD_TAX_TOTALS = { + "household_tax_before_refundable_credits", + "household_state_tax_before_refundable_credits", + "household_refundable_tax_credits", + "household_refundable_state_tax_credits", + "household_refundable_local_tax_credits", + "household_tax", + "household_net_income", +} + + +def test_no_reform_redefines_a_household_tax_total(): + """Change a component or the baseline formula instead of copying a total. + + A copy keeps the components it was written with, so it misses any that + the baseline total gains later. + """ + redefinitions = [] + for source in sorted((PACKAGE / "reforms").rglob("*.py")): + tree = ast.parse(source.read_text()) + for node in ast.walk(tree): + if isinstance(node, ast.ClassDef) and node.name in HOUSEHOLD_TAX_TOTALS: + redefinitions.append(f"{source.relative_to(PACKAGE)}::{node.name}") + assert not redefinitions, ( + "Reforms redefine household tax totals, which then drift from the " + f"baseline: {redefinitions}" + ) diff --git a/policyengine_us/variables/household/income/household/household_refundable_tax_credits.py b/policyengine_us/variables/household/income/household/household_refundable_tax_credits.py index b8a07fac41e..0ddedb14bb2 100644 --- a/policyengine_us/variables/household/income/household/household_refundable_tax_credits.py +++ b/policyengine_us/variables/household/income/household/household_refundable_tax_credits.py @@ -7,8 +7,20 @@ class household_refundable_tax_credits(Variable): label = "refundable tax credits" definition_period = YEAR unit = USD - adds = [ - "income_tax_refundable_credits", - "household_refundable_state_tax_credits", - "household_refundable_local_tax_credits", - ] + + def formula(household, period, parameters): + credits = add( + household, + period, + [ + "household_refundable_state_tax_credits", + "household_refundable_local_tax_credits", + ], + ) + # `income_tax` nets federal refundable credits and is zero when the + # federal income tax is abolished, so the credits go too. + if parameters( + period + ).gov.contrib.ubi_center.flat_tax.abolish_federal_income_tax: + return credits + return credits + add(household, period, ["income_tax_refundable_credits"])