diff --git a/changelog.d/sc-h4216-baseline.added.md b/changelog.d/sc-h4216-baseline.added.md new file mode 100644 index 00000000000..420e050bcf4 --- /dev/null +++ b/changelog.d/sc-h4216-baseline.added.md @@ -0,0 +1 @@ +Move SC H4216 income tax reform from contributed reform to baseline, effective for tax years 2026 and later. This implements new tax rates (1.99%/5.21%), the SCIAD deduction with phaseout, and the 00 EITC cap. diff --git a/policyengine_us/parameters/gov/contrib/states/sc/h4216/eitc/max.yaml b/policyengine_us/parameters/gov/contrib/states/sc/h4216/eitc/max.yaml deleted file mode 100644 index 09cdbfde892..00000000000 --- a/policyengine_us/parameters/gov/contrib/states/sc/h4216/eitc/max.yaml +++ /dev/null @@ -1,12 +0,0 @@ -description: South Carolina H.4216 caps the state EITC at this amount. - -metadata: - unit: currency-USD - period: year - label: South Carolina H.4216 EITC maximum cap - reference: - - title: South Carolina H.4216 Section 7 - Section 12-6-3632 - href: https://www.scstatehouse.gov/sess126_2025-2026/prever/4216_20260224.htm - -values: - 2026-01-01: 200 diff --git a/policyengine_us/parameters/gov/contrib/states/sc/h4216/in_effect.yaml b/policyengine_us/parameters/gov/contrib/states/sc/h4216/in_effect.yaml deleted file mode 100644 index e4965bb07e9..00000000000 --- a/policyengine_us/parameters/gov/contrib/states/sc/h4216/in_effect.yaml +++ /dev/null @@ -1,12 +0,0 @@ -description: The South Carolina H.4216 tax reform applies if this is true. - -metadata: - unit: bool - period: year - label: South Carolina H.4216 tax reform in effect - reference: - - title: South Carolina H.4216 (126th Session, 2025-2026) - href: https://www.scstatehouse.gov/sess126_2025-2026/prever/4216_20260224.htm - -values: - 0000-01-01: false diff --git a/policyengine_us/parameters/gov/contrib/states/sc/h4216/rates.yaml b/policyengine_us/parameters/gov/contrib/states/sc/h4216/rates.yaml deleted file mode 100644 index 31fbf8e8c8f..00000000000 --- a/policyengine_us/parameters/gov/contrib/states/sc/h4216/rates.yaml +++ /dev/null @@ -1,24 +0,0 @@ -# Note: H.4216 Items 2-3 also include automatic rate reductions beginning -# in 2027 if individual income tax revenues project to grow at least 5% -# year-over-year. These future rate reductions are not modeled. -description: South Carolina H.4216 establishes new income tax rates. - -metadata: - reference: - - title: South Carolina H.4216 Section 1 - Section 12-6-510(C) - href: https://www.scstatehouse.gov/sess126_2025-2026/prever/4216_20260224.htm - label: South Carolina H.4216 income tax rates - type: marginal_rate - threshold_unit: currency-USD - rate_unit: /1 - period: year - -brackets: - - threshold: - 2026-01-01: 0 - rate: - 2026-01-01: 0.0199 - - threshold: - 2026-01-01: 30_000 - rate: - 2026-01-01: 0.0521 diff --git a/policyengine_us/parameters/gov/contrib/states/sc/h4216/sciad/amount.yaml b/policyengine_us/parameters/gov/contrib/states/sc/h4216/sciad/amount.yaml deleted file mode 100644 index a24c8cc57df..00000000000 --- a/policyengine_us/parameters/gov/contrib/states/sc/h4216/sciad/amount.yaml +++ /dev/null @@ -1,26 +0,0 @@ -description: South Carolina H.4216 provides the SC Income Adjusted Deduction (SCIAD) at these base amounts by filing status. - -metadata: - label: South Carolina H.4216 SCIAD base amount - period: year - unit: currency-USD - breakdown: - - filing_status - reference: - - title: South Carolina H.4216 Section 3 - Section 12-6-1140(15)(a) - href: https://www.scstatehouse.gov/sess126_2025-2026/prever/4216_20260224.htm - -SINGLE: - 2026-01-01: 15_000 - -SEPARATE: - 2026-01-01: 15_000 - -HEAD_OF_HOUSEHOLD: - 2026-01-01: 22_500 - -JOINT: - 2026-01-01: 30_000 - -SURVIVING_SPOUSE: - 2026-01-01: 30_000 diff --git a/policyengine_us/parameters/gov/contrib/states/sc/h4216/sciad/phase_out/start.yaml b/policyengine_us/parameters/gov/contrib/states/sc/h4216/sciad/phase_out/start.yaml deleted file mode 100644 index ceaa430ce56..00000000000 --- a/policyengine_us/parameters/gov/contrib/states/sc/h4216/sciad/phase_out/start.yaml +++ /dev/null @@ -1,26 +0,0 @@ -description: South Carolina H.4216 begins phasing out the SCIAD when AGI exceeds this threshold. - -metadata: - label: South Carolina H.4216 SCIAD phase-out start - period: year - unit: currency-USD - breakdown: - - filing_status - reference: - - title: South Carolina H.4216 Section 3 - Section 12-6-1140(15)(b) - href: https://www.scstatehouse.gov/sess126_2025-2026/prever/4216_20260224.htm - -SINGLE: - 2026-01-01: 40_000 - -SEPARATE: - 2026-01-01: 40_000 - -HEAD_OF_HOUSEHOLD: - 2026-01-01: 60_000 - -JOINT: - 2026-01-01: 80_000 - -SURVIVING_SPOUSE: - 2026-01-01: 80_000 diff --git a/policyengine_us/parameters/gov/contrib/states/sc/h4216/sciad/phase_out/width.yaml b/policyengine_us/parameters/gov/contrib/states/sc/h4216/sciad/phase_out/width.yaml deleted file mode 100644 index deae09c68c0..00000000000 --- a/policyengine_us/parameters/gov/contrib/states/sc/h4216/sciad/phase_out/width.yaml +++ /dev/null @@ -1,26 +0,0 @@ -description: South Carolina H.4216 fully phases out the SCIAD over this income range (the denominator in the phase-out formula). - -metadata: - label: South Carolina H.4216 SCIAD phase-out width - period: year - unit: currency-USD - breakdown: - - filing_status - reference: - - title: South Carolina H.4216 Section 3 - Section 12-6-1140(15)(b) - href: https://www.scstatehouse.gov/sess126_2025-2026/prever/4216_20260224.htm - -SINGLE: - 2026-01-01: 55_000 - -SEPARATE: - 2026-01-01: 55_000 - -HEAD_OF_HOUSEHOLD: - 2026-01-01: 82_500 - -JOINT: - 2026-01-01: 110_000 - -SURVIVING_SPOUSE: - 2026-01-01: 110_000 diff --git a/policyengine_us/parameters/gov/states/sc/tax/income/credits/eitc/max.yaml b/policyengine_us/parameters/gov/states/sc/tax/income/credits/eitc/max.yaml new file mode 100644 index 00000000000..7e10dc655b3 --- /dev/null +++ b/policyengine_us/parameters/gov/states/sc/tax/income/credits/eitc/max.yaml @@ -0,0 +1,13 @@ +description: South Carolina caps the state EITC at this amount. + +metadata: + unit: currency-USD + period: year + label: South Carolina EITC maximum cap + reference: + - title: SC H.4216 Section 7 - Section 12-6-3632 + href: https://www.scstatehouse.gov/sess126_2025-2026/bills/4216.htm + +values: + 0001-01-01: .inf + 2026-01-01: 200 diff --git a/policyengine_us/parameters/gov/states/sc/tax/income/deductions/sciad/amount.yaml b/policyengine_us/parameters/gov/states/sc/tax/income/deductions/sciad/amount.yaml new file mode 100644 index 00000000000..d0aa6750f1e --- /dev/null +++ b/policyengine_us/parameters/gov/states/sc/tax/income/deductions/sciad/amount.yaml @@ -0,0 +1,26 @@ +description: South Carolina provides the SC Income Adjusted Deduction (SCIAD) at these base amounts by filing status. + +metadata: + label: South Carolina SCIAD base amount + period: year + unit: currency-USD + breakdown: + - filing_status + reference: + - title: SC H.4216 Section 3 - Section 12-6-1140(15)(a) + href: https://www.scstatehouse.gov/sess126_2025-2026/bills/4216.htm + +SINGLE: + 2026-01-01: 15_000 + +SEPARATE: + 2026-01-01: 15_000 + +HEAD_OF_HOUSEHOLD: + 2026-01-01: 22_500 + +JOINT: + 2026-01-01: 30_000 + +SURVIVING_SPOUSE: + 2026-01-01: 30_000 diff --git a/policyengine_us/parameters/gov/states/sc/tax/income/deductions/sciad/in_effect.yaml b/policyengine_us/parameters/gov/states/sc/tax/income/deductions/sciad/in_effect.yaml new file mode 100644 index 00000000000..57ddf1c5d92 --- /dev/null +++ b/policyengine_us/parameters/gov/states/sc/tax/income/deductions/sciad/in_effect.yaml @@ -0,0 +1,13 @@ +description: Whether the SC Income Adjusted Deduction (SCIAD) is in effect. + +metadata: + unit: bool + period: year + label: SC SCIAD in effect + reference: + - title: SC H.4216 Section 3 - Section 12-6-1140(15) + href: https://www.scstatehouse.gov/sess126_2025-2026/bills/4216.htm + +values: + 0000-01-01: false + 2026-01-01: true diff --git a/policyengine_us/parameters/gov/states/sc/tax/income/deductions/sciad/phase_out/start.yaml b/policyengine_us/parameters/gov/states/sc/tax/income/deductions/sciad/phase_out/start.yaml new file mode 100644 index 00000000000..662f2f1bbd8 --- /dev/null +++ b/policyengine_us/parameters/gov/states/sc/tax/income/deductions/sciad/phase_out/start.yaml @@ -0,0 +1,26 @@ +description: South Carolina begins phasing out the SCIAD when federal AGI exceeds this threshold. + +metadata: + label: South Carolina SCIAD phase-out start + period: year + unit: currency-USD + breakdown: + - filing_status + reference: + - title: SC H.4216 Section 3 - Section 12-6-1140(15)(b) + href: https://www.scstatehouse.gov/sess126_2025-2026/bills/4216.htm + +SINGLE: + 2026-01-01: 40_000 + +SEPARATE: + 2026-01-01: 40_000 + +HEAD_OF_HOUSEHOLD: + 2026-01-01: 60_000 + +JOINT: + 2026-01-01: 80_000 + +SURVIVING_SPOUSE: + 2026-01-01: 80_000 diff --git a/policyengine_us/parameters/gov/states/sc/tax/income/deductions/sciad/phase_out/width.yaml b/policyengine_us/parameters/gov/states/sc/tax/income/deductions/sciad/phase_out/width.yaml new file mode 100644 index 00000000000..c5828c7373f --- /dev/null +++ b/policyengine_us/parameters/gov/states/sc/tax/income/deductions/sciad/phase_out/width.yaml @@ -0,0 +1,26 @@ +description: South Carolina fully phases out the SCIAD over this income range (the denominator in the phase-out formula). + +metadata: + label: South Carolina SCIAD phase-out width + period: year + unit: currency-USD + breakdown: + - filing_status + reference: + - title: SC H.4216 Section 3 - Section 12-6-1140(15)(b) + href: https://www.scstatehouse.gov/sess126_2025-2026/bills/4216.htm + +SINGLE: + 2026-01-01: 55_000 + +SEPARATE: + 2026-01-01: 55_000 + +HEAD_OF_HOUSEHOLD: + 2026-01-01: 82_500 + +JOINT: + 2026-01-01: 110_000 + +SURVIVING_SPOUSE: + 2026-01-01: 110_000 diff --git a/policyengine_us/parameters/gov/states/sc/tax/income/rates.yaml b/policyengine_us/parameters/gov/states/sc/tax/income/rates.yaml index b96a0427263..916aee34dc3 100644 --- a/policyengine_us/parameters/gov/states/sc/tax/income/rates.yaml +++ b/policyengine_us/parameters/gov/states/sc/tax/income/rates.yaml @@ -1,10 +1,14 @@ description: South Carolina taxes the income of individuals at this rate. +# Note: H.4216 Items 2-3 also include automatic rate reductions beginning +# in 2027 if individual income tax revenues project to grow at least 5% +# year-over-year. These future rate reductions are not modeled. brackets: - threshold: 1994-01-01: 0 rate: 1994-01-01: 0.025 2021-01-01: 0 + 2026-01-01: 0.0199 - threshold: 1994-01-01: 2_220 2021-01-01: 3_110 @@ -12,6 +16,7 @@ brackets: 2023-01-01: 3_330 2024-01-01: 3_460 2025-01-01: 3_560 + 2026-01-01: 30_000 metadata: uprating: gov.irs.uprating rounding: @@ -19,6 +24,7 @@ brackets: interval: 10 rate: 1994-01-01: 0.03 + 2026-01-01: 0.0521 - threshold: 1994-01-01: 4_440 2021-01-01: 6_220 @@ -26,6 +32,7 @@ brackets: 2023-01-01: 16_680 2024-01-01: 17_330 2025-01-01: 17_830 + 2026-01-01: .inf metadata: uprating: gov.irs.uprating rounding: @@ -63,6 +70,8 @@ brackets: metadata: reference: + - title: SC H.4216 Section 1 - Section 12-6-510(C) (2026+) + href: https://www.scstatehouse.gov/sess126_2025-2026/bills/4216.htm - title: SC 1040 Instructions 2025 href: https://dor.sc.gov/sites/dor/files/forms/SC1040Instr_2025.pdf#page=1 - title: SC 2024 Income Tax Chart diff --git a/policyengine_us/parameters/gov/states/sc/tax/income/subtractions/subtractions.yaml b/policyengine_us/parameters/gov/states/sc/tax/income/subtractions/subtractions.yaml index 45e7c22ec84..6cafb26addd 100644 --- a/policyengine_us/parameters/gov/states/sc/tax/income/subtractions/subtractions.yaml +++ b/policyengine_us/parameters/gov/states/sc/tax/income/subtractions/subtractions.yaml @@ -4,28 +4,44 @@ values: - salt_refund_income - disability_benefits #include tax disability benefits - sc_net_capital_gain_deduction - - investment_in_529_plan + - investment_in_529_plan - us_govt_interest - taxable_social_security - sc_military_deduction - sc_retirement_deduction - sc_senior_exemption - sc_young_child_deduction - - sc_dependent_exemption + - sc_dependent_exemption # - Out of state income/gain - # - Volunteer deductions + # - Volunteer deductions # - Active Trade or Business Income deduction # - Certain nontaxable National Guard or Reserve pay # - Negative amount of federal taxable income - # - Subsistence allowance + # - Subsistence allowance # - Consumer Protection Services - # - Other subtractions + # - Other subtractions + # H.4216 adds the SCIAD starting 2026 + 2026-01-01: + - salt_refund_income + - disability_benefits + - sc_net_capital_gain_deduction + - investment_in_529_plan + - us_govt_interest + - taxable_social_security + - sc_military_deduction + - sc_retirement_deduction + - sc_senior_exemption + - sc_young_child_deduction + - sc_dependent_exemption + - sc_sciad metadata: unit: list period: year - label: South Carolina federal taxable income subtractions + label: South Carolina federal taxable income subtractions reference: + - title: SC H.4216 Section 3 - Section 12-6-1140(15) (2026+) + href: https://www.scstatehouse.gov/sess126_2025-2026/bills/4216.htm - title: SC 1040 tax form instructions (2025) href: https://dor.sc.gov/sites/dor/files/forms/SC1040Instr_2025.pdf#page=8 - title: SC 1040 tax form (2021) diff --git a/policyengine_us/reforms/reforms.py b/policyengine_us/reforms/reforms.py index f774002d35c..2c2b6d2029a 100644 --- a/policyengine_us/reforms/reforms.py +++ b/policyengine_us/reforms/reforms.py @@ -25,7 +25,6 @@ from .states.ny.a04038 import create_ny_a04038_enhanced_escc_infants_reform from .states.ny.s9110 import create_ny_s9110_reform from .states.sc.h3492 import create_sc_h3492_eitc_refundable_reform -from .states.sc.h4216 import create_sc_h4216_reform from .states.ny.a06774 import create_ny_a06774_enhanced_cdcc_reform from .states.ny.s04487 import create_ny_s04487_newborn_credit_reform from .harris.lift.middle_class_tax_credit import ( @@ -280,7 +279,6 @@ def create_structural_reforms_from_parameters(parameters, period): sc_h3492_eitc_refundable = create_sc_h3492_eitc_refundable_reform( parameters, period ) - sc_h4216 = create_sc_h4216_reform(parameters, period) ny_a06774_enhanced_cdcc = create_ny_a06774_enhanced_cdcc_reform(parameters, period) ny_s04487_newborn_credit = create_ny_s04487_newborn_credit_reform( parameters, period @@ -445,7 +443,6 @@ def create_structural_reforms_from_parameters(parameters, period): ny_a04038_enhanced_escc_infants, ny_s9110, sc_h3492_eitc_refundable, - sc_h4216, ny_a06774_enhanced_cdcc, ny_s04487_newborn_credit, middle_class_tax_credit, diff --git a/policyengine_us/reforms/states/sc/h4216/__init__.py b/policyengine_us/reforms/states/sc/h4216/__init__.py deleted file mode 100644 index 3dba992e8d2..00000000000 --- a/policyengine_us/reforms/states/sc/h4216/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .sc_h4216 import create_sc_h4216_reform, sc_h4216 diff --git a/policyengine_us/reforms/states/sc/h4216/sc_h4216.py b/policyengine_us/reforms/states/sc/h4216/sc_h4216.py deleted file mode 100644 index f65bfd1ebe3..00000000000 --- a/policyengine_us/reforms/states/sc/h4216/sc_h4216.py +++ /dev/null @@ -1,137 +0,0 @@ -from policyengine_us.model_api import * -from policyengine_core.periods import period as period_ - - -def create_sc_h4216() -> Reform: - """ - South Carolina H.4216 - Comprehensive Income Tax Reform - - This bill makes several changes to SC income tax: - 1. Caps the SC EITC at $200 (Section 7) - 2. Creates the SC Income Adjusted Deduction (SCIAD) replacing federal - standard deduction (Section 3) - 3. Establishes new tax rates: 1.99% up to $30k, 5.21% above (Section 1) - - Reference: https://www.scstatehouse.gov/sess126_2025-2026/prever/4216_20260224.htm - """ - - class sc_eitc(Variable): - value_type = float - entity = TaxUnit - label = "South Carolina EITC under H.4216" - unit = USD - definition_period = YEAR - reference = ( - "https://www.scstatehouse.gov/sess126_2025-2026/prever/4216_20260224.htm", - "https://dor.sc.gov/forms-site/Forms/TC60_2021.pdf", - ) - defined_for = StateCode.SC - - def formula(tax_unit, period, parameters): - federal_eitc = tax_unit("eitc", period) - p = parameters(period).gov.states.sc.tax.income.credits.eitc - p_h4216 = parameters(period).gov.contrib.states.sc.h4216.eitc - # Calculate 125% of federal EITC, then cap at $200 - uncapped = np.round(federal_eitc * p.rate, 1) - return min_(uncapped, p_h4216.max) - - class sc_sciad(Variable): - value_type = float - entity = TaxUnit - label = "South Carolina Income Adjusted Deduction (SCIAD)" - unit = USD - definition_period = YEAR - reference = ( - "https://www.scstatehouse.gov/sess126_2025-2026/prever/4216_20260224.htm" - ) - defined_for = StateCode.SC - - def formula(tax_unit, period, parameters): - p = parameters(period).gov.contrib.states.sc.h4216.sciad - filing_status = tax_unit("filing_status", period) - agi = tax_unit("adjusted_gross_income", period) - - # Get base amount and phase-out parameters by filing status - base_amount = p.amount[filing_status] - phase_out_start = p.phase_out.start[filing_status] - phase_out_width = p.phase_out.width[filing_status] - - # Calculate phase-out reduction - # Formula: deduction * (1 - (AGI - phase_out_start) / width) - excess_agi = max_(agi - phase_out_start, 0) - phase_out_fraction = min_(excess_agi / phase_out_width, 1) - raw_sciad = max_(base_amount * (1 - phase_out_fraction), 0) - # Per H.4216 Section 3, round down to nearest $10 - return np.floor(raw_sciad / 10) * 10 - - class sc_taxable_income(Variable): - value_type = float - entity = TaxUnit - label = "South Carolina taxable income under H.4216" - defined_for = StateCode.SC - unit = USD - definition_period = YEAR - reference = ( - "https://www.scstatehouse.gov/sess126_2025-2026/prever/4216_20260224.htm" - ) - - def formula(tax_unit, period, parameters): - # Start with federal AGI (H.4216 replaces federal std/itemized - # with SCIAD). Baseline sc_additions (QBI addback, SALT addback) - # exist to undo federal deductions already embedded in federal - # taxable_income; since we start from AGI those deductions were - # never taken, so additions must be excluded. - agi = tax_unit("adjusted_gross_income", period) - subtractions = tax_unit("sc_subtractions", period) - sciad = tax_unit("sc_sciad", period) - return max_(0, agi - subtractions - sciad) - - class sc_income_tax_before_non_refundable_credits(Variable): - value_type = float - entity = TaxUnit - label = "South Carolina income tax before non-refundable credits under H.4216" - defined_for = StateCode.SC - unit = USD - definition_period = YEAR - reference = ( - "https://www.scstatehouse.gov/sess126_2025-2026/prever/4216_20260224.htm" - ) - - def formula(tax_unit, period, parameters): - taxable_income = tax_unit("sc_taxable_income", period) - # Use H.4216 rates instead of baseline rates - p = parameters(period).gov.contrib.states.sc.h4216.rates - return p.calc(taxable_income) - - class reform(Reform): - def apply(self): - self.update_variable(sc_eitc) - self.update_variable(sc_sciad) - self.update_variable(sc_taxable_income) - self.update_variable(sc_income_tax_before_non_refundable_credits) - - return reform - - -def create_sc_h4216_reform(parameters, period, bypass: bool = False): - if bypass: - return create_sc_h4216() - - p = parameters.gov.contrib.states.sc.h4216 - - reform_active = False - current_period = period_(period) - - for _ in range(5): - if p(current_period).in_effect: - reform_active = True - break - current_period = current_period.offset(1, "year") - - if reform_active: - return create_sc_h4216() - else: - return None - - -sc_h4216 = create_sc_h4216_reform(None, None, bypass=True) diff --git a/policyengine_us/tests/policy/baseline/gov/states/sc/tax/income/sc_h4216_baseline.yaml b/policyengine_us/tests/policy/baseline/gov/states/sc/tax/income/sc_h4216_baseline.yaml new file mode 100644 index 00000000000..e8c839c01c0 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/sc/tax/income/sc_h4216_baseline.yaml @@ -0,0 +1,326 @@ +# SC H.4216 Baseline Tests (2026+) +# Tests the comprehensive tax reform in baseline: +# 1. EITC cap at $200 +# 2. SC Income Adjusted Deduction (SCIAD) +# 3. New tax rates (1.99% / 5.21%) +# Reference: https://www.scstatehouse.gov/sess126_2025-2026/bills/4216.htm + +# EITC Cap Tests + +- name: SC EITC capped at $200 for high federal EITC (2026) + period: 2026 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 30 + is_tax_unit_head: true + tax_units: + tax_unit: + members: [person1] + eitc: 500 + households: + household: + members: [person1] + state_code: SC + output: + # Federal EITC = $500 + # SC EITC = min($500 * 1.25, $200) = min($625, $200) = $200 + sc_eitc: 200 + +- name: SC EITC below cap (2026) + period: 2026 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 30 + is_tax_unit_head: true + tax_units: + tax_unit: + members: [person1] + eitc: 100 + households: + household: + members: [person1] + state_code: SC + output: + # Federal EITC = $100 + # SC EITC = min($100 * 1.25, $200) = min($125, $200) = $125 + sc_eitc: 125 + +- name: SC EITC has no cap before 2026 + period: 2025 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 30 + is_tax_unit_head: true + tax_units: + tax_unit: + members: [person1] + eitc: 500 + households: + household: + members: [person1] + state_code: SC + output: + # Federal EITC = $500 + # SC EITC = $500 * 1.25 = $625 (no cap in 2025) + sc_eitc: 625 + +# SCIAD Tests + +- name: SCIAD for single filer below phase-out (2026) + period: 2026 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 30 + is_tax_unit_head: true + employment_income: 35_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: SC + output: + # AGI $35k < phase-out start $40k, full SCIAD + sc_sciad: 15_000 + +- name: SCIAD for single filer in phase-out range (2026) + period: 2026 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 30 + is_tax_unit_head: true + employment_income: 67_500 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: SC + output: + # AGI $67,500, phase-out start $40k, width $55k + # Excess = $67,500 - $40,000 = $27,500 + # Phase-out fraction = $27,500 / $55,000 = 0.5 + # Reduction = $15,000 * 0.5 = $7,500 + # SCIAD = $15,000 - $7,500 = $7,500 + sc_sciad: 7_500 + +- name: SCIAD fully phased out for high income single (2026) + period: 2026 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 30 + is_tax_unit_head: true + employment_income: 100_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: SC + output: + # AGI $100k > phase-out end $95k ($40k + $55k) + # SCIAD fully phased out + sc_sciad: 0 + +- name: SCIAD for joint filer below phase-out (2026) + period: 2026 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 35 + is_tax_unit_head: true + employment_income: 40_000 + person2: + age: 33 + is_tax_unit_spouse: true + employment_income: 35_000 + tax_units: + tax_unit: + members: [person1, person2] + filing_status: JOINT + households: + household: + members: [person1, person2] + state_code: SC + output: + # AGI $75k < phase-out start $80k, full SCIAD + sc_sciad: 30_000 + +- name: SCIAD for head of household (2026) + period: 2026 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 35 + is_tax_unit_head: true + employment_income: 55_000 + person2: + age: 10 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2] + filing_status: HEAD_OF_HOUSEHOLD + households: + household: + members: [person1, person2] + state_code: SC + output: + # AGI $55k < phase-out start $60k, full SCIAD + sc_sciad: 22_500 + +- name: SCIAD is zero before 2026 + period: 2025 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 30 + is_tax_unit_head: true + employment_income: 35_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: SC + output: + # SCIAD doesn't exist before 2026 + sc_sciad: 0 + +# Tax Rate Tests + +- name: Tax at 1.99% rate for income under $30k (2026) + period: 2026 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 30 + is_tax_unit_head: true + tax_units: + tax_unit: + members: [person1] + adjusted_gross_income: 20_000 + households: + household: + members: [person1] + state_code: SC + output: + # AGI $20k < SCIAD $15k phase-out, so SCIAD = $15k (below $40k AGI) + # Actually AGI $20k means full SCIAD of $15k + # SC taxable income = $20k - $15k = $5k + # Tax = $5k * 1.99% = $99.50 + sc_income_tax_before_non_refundable_credits: 99.5 + +- name: Tax calculation with new rates above $30k threshold (2026) + period: 2026 + absolute_error_margin: 1 + input: + people: + person1: + age: 30 + is_tax_unit_head: true + employment_income: 100_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: SC + output: + # AGI $100k, SCIAD $0 (fully phased out) + # Taxable income = $100k + # Tax = $30k * 1.99% + $70k * 5.21% = $597 + $3,647 = $4,244 + sc_income_tax_before_non_refundable_credits: 4_244 + +# Taxable Income Tests + +- name: SC taxable income starts from AGI in 2026 + period: 2026 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 30 + is_tax_unit_head: true + employment_income: 50_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: SC + output: + # AGI $50k, SCIAD phased out partially + # Excess = $50k - $40k = $10k + # Phase-out fraction = $10k / $55k = 0.18182 + # Reduction = $15,000 * 0.18182 = $2,727.27, rounded to $2,720 + # SCIAD = $15,000 - $2,720 = $12,280 + # But wait - formula is: reduction = base * fraction, then round DOWN to $10 + # reduction = $15,000 * 0.18182 = $2,727.27 -> floor($2,727.27/10)*10 = $2,720 + # SCIAD = max(0, $15,000 - $2,720) = $12,280 + sc_sciad: 12_280 + +- name: Zero income results in zero tax (2026) + period: 2026 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 30 + is_tax_unit_head: true + employment_income: 0 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: SC + output: + sc_sciad: 15_000 + sc_taxable_income: 0 + sc_income_tax_before_non_refundable_credits: 0 + +- name: Non-SC resident gets zero for SC variables + period: 2026 + absolute_error_margin: 0.1 + input: + people: + person1: + age: 30 + is_tax_unit_head: true + employment_income: 50_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: NY + output: + sc_sciad: 0 + sc_eitc: 0 + sc_taxable_income: 0 + sc_income_tax_before_non_refundable_credits: 0 diff --git a/policyengine_us/tests/policy/contrib/states/sc/h4216/sc_h4216.yaml b/policyengine_us/tests/policy/contrib/states/sc/h4216/sc_h4216.yaml deleted file mode 100644 index 46d544f0668..00000000000 --- a/policyengine_us/tests/policy/contrib/states/sc/h4216/sc_h4216.yaml +++ /dev/null @@ -1,1020 +0,0 @@ -# SC H.4216 Tax Reform Tests -# Tests the comprehensive tax reform including: -# 1. EITC cap at $200 -# 2. SC Income Adjusted Deduction (SCIAD) -# 3. New tax rates (1.99% / 5.21%) - -# EITC Cap Tests - -- name: Case 1, EITC capped at $200 for high federal EITC. - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 30 - is_tax_unit_head: true - employment_income: 20_000 - person2: - age: 5 - is_tax_unit_dependent: true - tax_units: - tax_unit: - members: [person1, person2] - households: - household: - members: [person1, person2] - state_code: SC - output: - # Federal EITC at $20k with 1 child is substantial (~$3,733) - # SC EITC = min($3,733 * 1.25, $200) = min($4,666, $200) = $200 - sc_eitc: 200 - -- name: Case 2, EITC genuinely below cap with direct eitc input. - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 30 - is_tax_unit_head: true - tax_units: - tax_unit: - members: [person1] - eitc: 100 - households: - household: - members: [person1] - state_code: SC - output: - # Federal EITC = $100 (set directly) - # SC EITC = min($100 * 1.25, $200) = min($125, $200) = $125 - sc_eitc: 125 - -- name: Case 3, zero federal EITC produces zero state EITC. - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 30 - is_tax_unit_head: true - tax_units: - tax_unit: - members: [person1] - eitc: 0 - households: - household: - members: [person1] - state_code: SC - output: - # Federal EITC = $0 - # SC EITC = min($0 * 1.25, $200) = $0 - sc_eitc: 0 - -- name: Case 4, EITC exactly at cap boundary. - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 30 - is_tax_unit_head: true - tax_units: - tax_unit: - members: [person1] - eitc: 160 - households: - household: - members: [person1] - state_code: SC - output: - # Federal EITC = $160 - # SC EITC = min($160 * 1.25, $200) = min($200, $200) = $200 - # Exactly at cap - sc_eitc: 200 - -# SCIAD Tests - -- name: Case 5, SCIAD for single filer below phase-out. - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 30 - is_tax_unit_head: true - employment_income: 35_000 - tax_units: - tax_unit: - members: [person1] - households: - household: - members: [person1] - state_code: SC - output: - # AGI $35k < phase-out start $40k, full SCIAD - sc_sciad: 15_000 - -- name: Case 6, SCIAD for single filer in phase-out range. - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 30 - is_tax_unit_head: true - employment_income: 67_500 - tax_units: - tax_unit: - members: [person1] - households: - household: - members: [person1] - state_code: SC - output: - # AGI $67,500, phase-out start $40k, width $55k - # Excess = $67,500 - $40,000 = $27,500 - # Phase-out fraction = $27,500 / $55,000 = 0.5 - # SCIAD = $15,000 * (1 - 0.5) = $7,500 - sc_sciad: 7_500 - -- name: Case 7, SCIAD fully phased out for high income single. - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 30 - is_tax_unit_head: true - employment_income: 100_000 - tax_units: - tax_unit: - members: [person1] - households: - household: - members: [person1] - state_code: SC - output: - # AGI $100k > phase-out end $95k ($40k + $55k) - # SCIAD fully phased out - sc_sciad: 0 - -- name: Case 8, SCIAD for joint filer below phase-out. - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 35 - is_tax_unit_head: true - employment_income: 40_000 - person2: - age: 33 - is_tax_unit_spouse: true - employment_income: 35_000 - tax_units: - tax_unit: - members: [person1, person2] - filing_status: JOINT - households: - household: - members: [person1, person2] - state_code: SC - output: - # AGI $75k < phase-out start $80k, full SCIAD - sc_sciad: 30_000 - -- name: Case 9, SCIAD for joint filer in phase-out range. - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 35 - is_tax_unit_head: true - employment_income: 75_000 - person2: - age: 33 - is_tax_unit_spouse: true - employment_income: 60_000 - tax_units: - tax_unit: - members: [person1, person2] - filing_status: JOINT - households: - household: - members: [person1, person2] - state_code: SC - output: - # AGI $135k, phase-out start $80k, width $110k - # Excess = $135,000 - $80,000 = $55,000 - # Phase-out fraction = $55,000 / $110,000 = 0.5 - # SCIAD = $30,000 * (1 - 0.5) = $15,000 - sc_sciad: 15_000 - -- name: Case 10, SCIAD for head of household in phase-out range. - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 35 - is_tax_unit_head: true - employment_income: 101_250 - person2: - age: 10 - is_tax_unit_dependent: true - tax_units: - tax_unit: - members: [person1, person2] - filing_status: HEAD_OF_HOUSEHOLD - households: - household: - members: [person1, person2] - state_code: SC - output: - # AGI $101,250, phase-out start $60k, width $82,500 - # Excess = $101,250 - $60,000 = $41,250 - # Phase-out fraction = $41,250 / $82,500 = 0.5 - # SCIAD = $22,500 * (1 - 0.5) = $11,250 - sc_sciad: 11_250 - -# Tax Rate Tests - -- name: Case 11, tax calculation with new rates below $30k threshold. - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 30 - is_tax_unit_head: true - employment_income: 35_000 - tax_units: - tax_unit: - members: [person1] - households: - household: - members: [person1] - state_code: SC - output: - # AGI $35k, SCIAD $15k (below phase-out) - # Taxable income = $35k - $15k = $20k - # Tax = $20k * 1.99% = $398 - sc_income_tax_before_non_refundable_credits: 398 - -- name: Case 12, tax calculation with new rates above $30k threshold. - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 30 - is_tax_unit_head: true - employment_income: 100_000 - tax_units: - tax_unit: - members: [person1] - households: - household: - members: [person1] - state_code: SC - output: - # AGI $100k, SCIAD $0 (fully phased out) - # Taxable income = $100k - # Tax = $30k * 1.99% + $70k * 5.21% = $597 + $3,647 = $4,244 - sc_income_tax_before_non_refundable_credits: 4_244 - -- name: Case 13, non-SC resident gets zero for all reform variables. - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 30 - is_tax_unit_head: true - employment_income: 50_000 - tax_units: - tax_unit: - members: [person1] - households: - household: - members: [person1] - state_code: NY - output: - sc_sciad: 0 - sc_eitc: 0 - sc_taxable_income: 0 - sc_income_tax_before_non_refundable_credits: 0 - -# Boundary Tests - SCIAD Phase-out - -- name: Case 14, SCIAD at exactly phase-out start (single). - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 30 - is_tax_unit_head: true - employment_income: 40_000 - tax_units: - tax_unit: - members: [person1] - households: - household: - members: [person1] - state_code: SC - output: - # AGI $40k = phase-out start, full SCIAD - sc_sciad: 15_000 - -- name: Case 15, SCIAD one dollar above phase-out start (single). - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 30 - is_tax_unit_head: true - employment_income: 40_001 - tax_units: - tax_unit: - members: [person1] - households: - household: - members: [person1] - state_code: SC - output: - # AGI $40,001, excess = $1 - # Phase-out fraction = $1 / $55,000 = 0.0000182 - # Raw SCIAD = $15,000 * (1 - 0.0000182) = $14,999.73 - # Rounded down to nearest $10: $14,990 - sc_sciad: 14_990 - -- name: Case 16, SCIAD at exactly phase-out end (single). - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 30 - is_tax_unit_head: true - employment_income: 95_000 - tax_units: - tax_unit: - members: [person1] - households: - household: - members: [person1] - state_code: SC - output: - # AGI $95k = phase-out end ($40k + $55k) - # Phase-out fraction = $55,000 / $55,000 = 1.0 - # SCIAD = $15,000 * (1 - 1) = $0 - sc_sciad: 0 - -- name: Case 17, SCIAD one dollar below phase-out end (single). - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 30 - is_tax_unit_head: true - employment_income: 94_999 - tax_units: - tax_unit: - members: [person1] - households: - household: - members: [person1] - state_code: SC - output: - # AGI $94,999, excess = $94,999 - $40,000 = $54,999 - # Phase-out fraction = $54,999 / $55,000 = 0.9999818 - # Raw SCIAD = $15,000 * (1 - 0.9999818) = $0.27 - # Rounded down to nearest $10: $0 - sc_sciad: 0 - -- name: Case 18, SCIAD at exactly phase-out start (joint). - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 35 - is_tax_unit_head: true - employment_income: 50_000 - person2: - age: 33 - is_tax_unit_spouse: true - employment_income: 30_000 - tax_units: - tax_unit: - members: [person1, person2] - filing_status: JOINT - households: - household: - members: [person1, person2] - state_code: SC - output: - # AGI $80k = phase-out start for joint, full SCIAD - sc_sciad: 30_000 - -- name: Case 19, SCIAD at exactly phase-out end (joint). - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 35 - is_tax_unit_head: true - employment_income: 100_000 - person2: - age: 33 - is_tax_unit_spouse: true - employment_income: 90_000 - tax_units: - tax_unit: - members: [person1, person2] - filing_status: JOINT - households: - household: - members: [person1, person2] - state_code: SC - output: - # AGI $190k = phase-out end ($80k + $110k) - # SCIAD fully phased out - sc_sciad: 0 - -- name: Case 20, SCIAD at exactly phase-out start (HOH). - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 35 - is_tax_unit_head: true - employment_income: 60_000 - person2: - age: 10 - is_tax_unit_dependent: true - tax_units: - tax_unit: - members: [person1, person2] - filing_status: HEAD_OF_HOUSEHOLD - households: - household: - members: [person1, person2] - state_code: SC - output: - # AGI $60k = phase-out start for HOH, full SCIAD - sc_sciad: 22_500 - -- name: Case 21, SCIAD at exactly phase-out end (HOH). - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 35 - is_tax_unit_head: true - employment_income: 142_500 - person2: - age: 10 - is_tax_unit_dependent: true - tax_units: - tax_unit: - members: [person1, person2] - filing_status: HEAD_OF_HOUSEHOLD - households: - household: - members: [person1, person2] - state_code: SC - output: - # AGI $142,500 = phase-out end ($60k + $82,500) - # SCIAD fully phased out - sc_sciad: 0 - -# Filing Status Tests - -- name: Case 22, SCIAD for married filing separately. - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 35 - is_tax_unit_head: true - employment_income: 35_000 - tax_units: - tax_unit: - members: [person1] - filing_status: SEPARATE - households: - household: - members: [person1] - state_code: SC - output: - # AGI $35k < phase-out start $40k for SEPARATE - # Full SCIAD = $15,000 - sc_sciad: 15_000 - -- name: Case 23, SCIAD for separate filer in phase-out range. - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 35 - is_tax_unit_head: true - employment_income: 67_500 - tax_units: - tax_unit: - members: [person1] - filing_status: SEPARATE - households: - household: - members: [person1] - state_code: SC - output: - # AGI $67,500, phase-out start $40k, width $55k - # Excess = $67,500 - $40,000 = $27,500 - # Phase-out fraction = $27,500 / $55,000 = 0.5 - # SCIAD = $15,000 * (1 - 0.5) = $7,500 - sc_sciad: 7_500 - -- name: Case 24, SCIAD fully phased out for separate filer. - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 35 - is_tax_unit_head: true - employment_income: 100_000 - tax_units: - tax_unit: - members: [person1] - filing_status: SEPARATE - households: - household: - members: [person1] - state_code: SC - output: - # AGI $100k > phase-out end $95k ($40k + $55k) - # SCIAD fully phased out - sc_sciad: 0 - -- name: Case 25, SCIAD for surviving spouse below phase-out. - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 35 - is_tax_unit_head: true - employment_income: 75_000 - person2: - age: 10 - is_tax_unit_dependent: true - tax_units: - tax_unit: - members: [person1, person2] - filing_status: SURVIVING_SPOUSE - households: - household: - members: [person1, person2] - state_code: SC - output: - # AGI $75k < phase-out start $80k for SURVIVING_SPOUSE - # Full SCIAD = $30,000 - sc_sciad: 30_000 - -- name: Case 26, SCIAD for surviving spouse in phase-out range. - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 35 - is_tax_unit_head: true - employment_income: 135_000 - person2: - age: 10 - is_tax_unit_dependent: true - tax_units: - tax_unit: - members: [person1, person2] - filing_status: SURVIVING_SPOUSE - households: - household: - members: [person1, person2] - state_code: SC - output: - # AGI $135k, phase-out start $80k, width $110k - # Excess = $135,000 - $80,000 = $55,000 - # Phase-out fraction = $55,000 / $110,000 = 0.5 - # SCIAD = $30,000 * (1 - 0.5) = $15,000 - sc_sciad: 15_000 - -- name: Case 27, SCIAD fully phased out for surviving spouse. - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 35 - is_tax_unit_head: true - employment_income: 200_000 - person2: - age: 10 - is_tax_unit_dependent: true - tax_units: - tax_unit: - members: [person1, person2] - filing_status: SURVIVING_SPOUSE - households: - household: - members: [person1, person2] - state_code: SC - output: - # AGI $200k > phase-out end $190k ($80k + $110k) - # SCIAD fully phased out - sc_sciad: 0 - -# Standard/Itemized Deduction Elimination Tests - -- name: Case 28, federal standard deduction ignored - uses SCIAD not federal deductions. - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 30 - is_tax_unit_head: true - employment_income: 50_000 - tax_units: - tax_unit: - members: [person1] - households: - household: - members: [person1] - state_code: SC - output: - # AGI = $50,000 - # Federal standard deduction would be ~$15,200 (2026) - # But H.4216 ignores federal deductions and uses SCIAD instead - # SCIAD at $50k AGI: excess = $50k - $40k = $10k - # Phase-out fraction = $10k / $55k = 0.18182 - # Raw SCIAD = $15,000 * (1 - 0.18182) = $12,272.73 - # Rounded down to nearest $10: $12,270 - # SC taxable income = $50,000 - $12,270 = $37,730 - sc_sciad: 12_270 - sc_taxable_income: 37_730 - -- name: Case 29, high income with no SCIAD (fully phased out). - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 30 - is_tax_unit_head: true - employment_income: 150_000 - tax_units: - tax_unit: - members: [person1] - households: - household: - members: [person1] - state_code: SC - output: - # AGI = $150,000 - # H.4216 starts from AGI (ignores federal standard deduction) - # SCIAD at $150k AGI: AGI > phase-out end ($95k), SCIAD = $0 - # SC taxable income = AGI - SCIAD = $150,000 - sc_sciad: 0 - sc_taxable_income: 150_000 - -# Rate Bracket Boundary Tests - -- name: Case 30, taxable income exactly at $30k bracket boundary. - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 30 - is_tax_unit_head: true - tax_units: - tax_unit: - members: [person1] - adjusted_gross_income: 30_000 - sc_additions: 0 - sc_subtractions: 0 - sc_sciad: 0 - households: - household: - members: [person1] - state_code: SC - output: - # SC taxable income = $30,000 (directly set via AGI with no deductions) - # Tax = $30,000 * 1.99% = $597 - # All income in first bracket only - sc_taxable_income: 30_000 - sc_income_tax_before_non_refundable_credits: 597 - -- name: Case 31, taxable income one dollar above $30k bracket. - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 30 - is_tax_unit_head: true - tax_units: - tax_unit: - members: [person1] - adjusted_gross_income: 30_001 - sc_additions: 0 - sc_subtractions: 0 - sc_sciad: 0 - households: - household: - members: [person1] - state_code: SC - output: - # SC taxable income = $30,001 - # Tax = $30,000 * 1.99% + $1 * 5.21% = $597 + $0.0521 = $597.05 - sc_taxable_income: 30_001 - sc_income_tax_before_non_refundable_credits: 597.1 - -- name: Case 32, high income tax calculation. - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 30 - is_tax_unit_head: true - employment_income: 125_000 - tax_units: - tax_unit: - members: [person1] - households: - household: - members: [person1] - state_code: SC - output: - # AGI $125k, SCIAD $0 (fully phased out above $95k) - # SC taxable income = $125k - # Tax = $30k * 1.99% + $95k * 5.21% = $597 + $4,949.50 = $5,546.50 - sc_income_tax_before_non_refundable_credits: 5_546.5 - -# Zero/Low Income Edge Cases - -- name: Case 33, zero income results in zero tax. - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 30 - is_tax_unit_head: true - employment_income: 0 - tax_units: - tax_unit: - members: [person1] - households: - household: - members: [person1] - state_code: SC - output: - sc_sciad: 15_000 - sc_taxable_income: 0 - sc_income_tax_before_non_refundable_credits: 0 - -- name: Case 34, income below SCIAD results in zero taxable income. - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 30 - is_tax_unit_head: true - employment_income: 10_000 - tax_units: - tax_unit: - members: [person1] - households: - household: - members: [person1] - state_code: SC - output: - # AGI $10k < SCIAD $15k - # SC taxable income = max(0, $10k - $15k) = $0 - sc_sciad: 15_000 - sc_taxable_income: 0 - sc_income_tax_before_non_refundable_credits: 0 - -# Negative AGI Edge Case - -- name: Case 35, negative AGI with self-employment loss. - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 30 - is_tax_unit_head: true - self_employment_income: -20_000 - tax_units: - tax_unit: - members: [person1] - households: - household: - members: [person1] - state_code: SC - output: - # AGI is negative from SE loss - # SCIAD: excess_agi = max_(negative - $40k, 0) = 0, full SCIAD = $15,000 - # SC taxable income = max_(0, negative - $15,000) = $0 - sc_sciad: 15_000 - sc_taxable_income: 0 - sc_income_tax_before_non_refundable_credits: 0 - sc_eitc: 0 - -# SC Subtractions Interaction -# Note: sc_additions (QBI addback, SALT addback) are excluded from the -# reform formula because it starts from AGI where those federal deductions -# were never taken. Only sc_subtractions (retirement, military) apply. - -- name: Case 36, SC subtractions reduce taxable income (additions excluded). - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 30 - is_tax_unit_head: true - tax_units: - tax_unit: - members: [person1] - adjusted_gross_income: 50_000 - sc_additions: 5_000 - sc_subtractions: 2_000 - sc_sciad: 0 - households: - household: - members: [person1] - state_code: SC - output: - # sc_additions ignored (AGI base already excludes federal deductions) - # SC taxable income = $50,000 - $2,000 - $0 = $48,000 - # Tax = $30k * 1.99% + $18k * 5.21% = $597 + $937.80 = $1,534.80 - sc_taxable_income: 48_000 - sc_income_tax_before_non_refundable_credits: 1_534.8 - -# Joint Filer SCIAD Greater Than AGI - -- name: Case 37, joint filer SCIAD exceeds AGI. - period: 2026 - absolute_error_margin: 0.1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 35 - is_tax_unit_head: true - employment_income: 15_000 - person2: - age: 33 - is_tax_unit_spouse: true - employment_income: 5_000 - tax_units: - tax_unit: - members: [person1, person2] - filing_status: JOINT - households: - household: - members: [person1, person2] - state_code: SC - output: - # AGI = $20,000 < phase-out start $80k - # Full SCIAD = $30,000 - # SC taxable income = max(0, $20,000 - $30,000) = $0 - sc_sciad: 30_000 - sc_taxable_income: 0 - sc_income_tax_before_non_refundable_credits: 0 - -# End-to-End Tax Calculation with EITC - -- name: Case 38, end-to-end tax with EITC and all components. - period: 2026 - absolute_error_margin: 1 - reforms: policyengine_us.reforms.states.sc.h4216.sc_h4216.sc_h4216 - input: - gov.contrib.states.sc.h4216.in_effect: true - people: - person1: - age: 30 - is_tax_unit_head: true - tax_units: - tax_unit: - members: [person1] - adjusted_gross_income: 50_000 - eitc: 500 - sc_additions: 0 - sc_subtractions: 0 - households: - household: - members: [person1] - state_code: SC - output: - # EITC: min($500 * 1.25, $200) = min($625, $200) = $200 - sc_eitc: 200 - # SCIAD for SINGLE: AGI $50k, phase-out start $40k, width $55k - # Excess = $10k, fraction = $10k/$55k = 0.18182 - # Raw SCIAD = $15,000 * (1 - 0.18182) = $12,272.73 - # Rounded down to nearest $10: $12,270 - sc_sciad: 12_270 - # SC taxable income = $50,000 - $12,270 = $37,730 - sc_taxable_income: 37_730 - # Tax = $30k * 1.99% + $7,730 * 5.21% = $597 + $402.73 = $999.73 - sc_income_tax_before_non_refundable_credits: 1_000 diff --git a/policyengine_us/variables/gov/states/sc/tax/income/credits/eitc/sc_eitc.py b/policyengine_us/variables/gov/states/sc/tax/income/credits/eitc/sc_eitc.py index 8e5ea5d3b07..4d5bd51f565 100644 --- a/policyengine_us/variables/gov/states/sc/tax/income/credits/eitc/sc_eitc.py +++ b/policyengine_us/variables/gov/states/sc/tax/income/credits/eitc/sc_eitc.py @@ -7,10 +7,15 @@ class sc_eitc(Variable): label = "South Carolina EITC" unit = USD definition_period = YEAR - reference = "https://dor.sc.gov/forms-site/Forms/TC60_2021.pdf" + reference = ( + "https://dor.sc.gov/forms-site/Forms/TC60_2021.pdf", + "https://www.scstatehouse.gov/sess126_2025-2026/bills/4216.htm", + ) defined_for = StateCode.SC def formula(tax_unit, period, parameters): federal_eitc = tax_unit("eitc", period) - rate = parameters(period).gov.states.sc.tax.income.credits.eitc.rate - return np.round(federal_eitc * rate, 1) + p = parameters(period).gov.states.sc.tax.income.credits.eitc + uncapped = np.round(federal_eitc * p.rate, 1) + # Apply cap (infinite before 2026, $200 from 2026+) + return min_(uncapped, p.max) diff --git a/policyengine_us/variables/gov/states/sc/tax/income/deductions/sciad/sc_sciad.py b/policyengine_us/variables/gov/states/sc/tax/income/deductions/sciad/sc_sciad.py new file mode 100644 index 00000000000..b5350fd49ab --- /dev/null +++ b/policyengine_us/variables/gov/states/sc/tax/income/deductions/sciad/sc_sciad.py @@ -0,0 +1,34 @@ +from policyengine_us.model_api import * + + +class sc_sciad(Variable): + value_type = float + entity = TaxUnit + label = "South Carolina Income Adjusted Deduction (SCIAD)" + unit = USD + definition_period = YEAR + reference = ("https://www.scstatehouse.gov/sess126_2025-2026/bills/4216.htm",) + defined_for = StateCode.SC + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.states.sc.tax.income.deductions.sciad + # SCIAD only exists starting 2026 + if not p.in_effect: + return tax_unit("adjusted_gross_income", period) * 0 + filing_status = tax_unit("filing_status", period) + # Get the base amount by filing status + base_amount = p.amount[filing_status] + # Get federal AGI for phase-out calculation + agi = tax_unit("adjusted_gross_income", period) + # Get phase-out parameters by filing status + phase_out_start = p.phase_out.start[filing_status] + phase_out_width = p.phase_out.width[filing_status] + # Calculate phase-out fraction + # fraction = (AGI - start) / width + excess_agi = max_(0, agi - phase_out_start) + phase_out_fraction = min_(1, excess_agi / phase_out_width) + # Calculate reduction + reduction = base_amount * phase_out_fraction + # Round reduction down to nearest $10 + reduction_rounded = np.floor(reduction / 10) * 10 + return max_(0, base_amount - reduction_rounded) diff --git a/policyengine_us/variables/gov/states/sc/tax/income/sc_taxable_income.py b/policyengine_us/variables/gov/states/sc/tax/income/sc_taxable_income.py index 433fc8d0d16..3bba844b471 100644 --- a/policyengine_us/variables/gov/states/sc/tax/income/sc_taxable_income.py +++ b/policyengine_us/variables/gov/states/sc/tax/income/sc_taxable_income.py @@ -8,10 +8,23 @@ class sc_taxable_income(Variable): defined_for = StateCode.SC unit = USD definition_period = YEAR - reference = "https://dor.sc.gov/forms-site/Forms/IITPacket_2022.pdf#page=33" + reference = ( + "https://dor.sc.gov/forms-site/Forms/IITPacket_2022.pdf#page=33", + "https://www.scstatehouse.gov/sess126_2025-2026/bills/4216.htm", + ) def formula(tax_unit, period, parameters): - taxable_income = tax_unit("taxable_income", period) - additions = tax_unit("sc_additions", period) + p = parameters(period).gov.states.sc.tax.income.deductions.sciad subtractions = tax_unit("sc_subtractions", period) - return max_(0, taxable_income + additions - subtractions) + + if p.in_effect: + # H4216: Start from AGI (SC ignores federal std/itemized deductions) + # Additions are not needed since they correct for federal deductions + # that are no longer relevant when starting from AGI + agi = tax_unit("adjusted_gross_income", period) + return max_(0, agi - subtractions) + else: + # Traditional calculation from federal taxable income + taxable_income = tax_unit("taxable_income", period) + additions = tax_unit("sc_additions", period) + return max_(0, taxable_income + additions - subtractions)