diff --git a/changelog_entry.yaml b/changelog_entry.yaml index e69de29bb2d..c8b73e1aabc 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -0,0 +1,4 @@ +- bump: minor + changes: + added: + - Create reform to separate dependent children from Virginia personal exemption. diff --git a/policyengine_us/parameters/gov/contrib/states/va/dependent_exemption/age_limit/in_effect.yaml b/policyengine_us/parameters/gov/contrib/states/va/dependent_exemption/age_limit/in_effect.yaml new file mode 100644 index 00000000000..ec50ab1cbfb --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/va/dependent_exemption/age_limit/in_effect.yaml @@ -0,0 +1,9 @@ +description: Virginia limits the dependent exemption to dependents under a certain age, if this is true. + +values: + 0000-01-01: false + +metadata: + unit: bool + period: year + label: Virginia dependent exemption age limit in effect diff --git a/policyengine_us/parameters/gov/contrib/states/va/dependent_exemption/age_limit/threshold.yaml b/policyengine_us/parameters/gov/contrib/states/va/dependent_exemption/age_limit/threshold.yaml new file mode 100644 index 00000000000..7be83cca320 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/va/dependent_exemption/age_limit/threshold.yaml @@ -0,0 +1,9 @@ +description: Virginia limits the dependent exemption to dependents under this age. + +values: + 2025-01-01: 18 + +metadata: + label: Virginia dependent exemption age threshold + period: year + unit: year diff --git a/policyengine_us/parameters/gov/contrib/states/va/dependent_exemption/amount.yaml b/policyengine_us/parameters/gov/contrib/states/va/dependent_exemption/amount.yaml new file mode 100644 index 00000000000..e603486433f --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/va/dependent_exemption/amount.yaml @@ -0,0 +1,9 @@ +description: Virginia provides this dependent exemption amount per eligible dependent child under the reform. + +values: + 2025-01-01: 930 + +metadata: + label: Virginia dependent exemption amount + period: year + unit: currency-USD diff --git a/policyengine_us/parameters/gov/contrib/states/va/dependent_exemption/in_effect.yaml b/policyengine_us/parameters/gov/contrib/states/va/dependent_exemption/in_effect.yaml new file mode 100644 index 00000000000..893d27540fe --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/va/dependent_exemption/in_effect.yaml @@ -0,0 +1,9 @@ +description: Virginia applies the dependent exemption reform, if this is true (separates dependent children from the personal exemption with configurable amounts and phaseout). + +values: + 0000-01-01: false + +metadata: + unit: bool + period: year + label: Virginia dependent exemption reform in effect diff --git a/policyengine_us/parameters/gov/contrib/states/va/dependent_exemption/phaseout/rate.yaml b/policyengine_us/parameters/gov/contrib/states/va/dependent_exemption/phaseout/rate.yaml new file mode 100644 index 00000000000..e782da349a5 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/va/dependent_exemption/phaseout/rate.yaml @@ -0,0 +1,9 @@ +description: Virginia phases out the dependent exemption at this rate per dollar of excess AGI. + +values: + 2025-01-01: 0 + +metadata: + label: Virginia dependent exemption phaseout rate + period: year + unit: /1 diff --git a/policyengine_us/parameters/gov/contrib/states/va/dependent_exemption/phaseout/threshold.yaml b/policyengine_us/parameters/gov/contrib/states/va/dependent_exemption/phaseout/threshold.yaml new file mode 100644 index 00000000000..b6464cb3fb0 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/va/dependent_exemption/phaseout/threshold.yaml @@ -0,0 +1,23 @@ +description: Virginia phases the dependent exemption out for filers with adjusted gross income above this threshold, based on filing status. + +metadata: + label: Virginia dependent exemption phaseout threshold + period: year + unit: currency-USD + breakdown: + - filing_status + +JOINT: + 2025-01-01: 0 + +HEAD_OF_HOUSEHOLD: + 2025-01-01: 0 + +SURVIVING_SPOUSE: + 2025-01-01: 0 + +SINGLE: + 2025-01-01: 0 + +SEPARATE: + 2025-01-01: 0 diff --git a/policyengine_us/reforms/reforms.py b/policyengine_us/reforms/reforms.py index 4549627136e..5c0a65cf3bf 100644 --- a/policyengine_us/reforms/reforms.py +++ b/policyengine_us/reforms/reforms.py @@ -124,6 +124,9 @@ from .states.de.dependent_credit.de_dependent_credit_reform import ( create_de_dependent_credit_reform_fn, ) +from .states.va.dependent_exemption.va_dependent_exemption_reform import ( + create_va_dependent_exemption_reform_fn, +) from .aca import ( create_aca_ptc_additional_bracket_reform, create_aca_ptc_simplified_bracket_reform, @@ -283,6 +286,9 @@ def create_structural_reforms_from_parameters(parameters, period): de_dependent_credit = create_de_dependent_credit_reform_fn( parameters, period ) + va_dependent_exemption = create_va_dependent_exemption_reform_fn( + parameters, period + ) aca_ptc_additional_bracket = create_aca_ptc_additional_bracket_reform( parameters, period ) @@ -348,6 +354,7 @@ def create_structural_reforms_from_parameters(parameters, period): ri_ctc, ri_exemption, de_dependent_credit, + va_dependent_exemption, aca_ptc_additional_bracket, aca_ptc_simplified_bracket, aca_ptc_700_fpl_cliff, diff --git a/policyengine_us/reforms/states/va/__init__.py b/policyengine_us/reforms/states/va/__init__.py new file mode 100644 index 00000000000..9062918ae8c --- /dev/null +++ b/policyengine_us/reforms/states/va/__init__.py @@ -0,0 +1,3 @@ +from .dependent_exemption.va_dependent_exemption_reform import ( + create_va_dependent_exemption_reform_fn, +) diff --git a/policyengine_us/reforms/states/va/dependent_exemption/va_dependent_exemption_reform.py b/policyengine_us/reforms/states/va/dependent_exemption/va_dependent_exemption_reform.py new file mode 100644 index 00000000000..a1003a5d288 --- /dev/null +++ b/policyengine_us/reforms/states/va/dependent_exemption/va_dependent_exemption_reform.py @@ -0,0 +1,164 @@ +from policyengine_us.model_api import * +from policyengine_core.periods import period as period_ +from policyengine_core.periods import instant + + +def create_va_dependent_exemption_reform() -> Reform: + class va_eligible_dependents_count(Variable): + value_type = int + entity = TaxUnit + label = "Virginia eligible dependents count" + definition_period = YEAR + defined_for = StateCode.VA + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.contrib.states.va.dependent_exemption + + person = tax_unit.members + age = person("age", period) + is_dependent = person("is_tax_unit_dependent", period) + + # Apply age limit if in effect + if p.age_limit.in_effect: + age_threshold = p.age_limit.threshold + eligible_dependents = is_dependent & (age < age_threshold) + else: + eligible_dependents = is_dependent + + return tax_unit.sum(eligible_dependents) + + class va_dependent_exemption_maximum(Variable): + value_type = float + entity = TaxUnit + label = "Virginia dependent exemption maximum before phaseout" + unit = USD + definition_period = YEAR + defined_for = StateCode.VA + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.contrib.states.va.dependent_exemption + + dependents_count = tax_unit("va_eligible_dependents_count", period) + return dependents_count * p.amount + + class va_dependent_exemption_phaseout(Variable): + value_type = float + entity = TaxUnit + label = "Virginia dependent exemption phaseout amount" + unit = USD + definition_period = YEAR + defined_for = StateCode.VA + + def formula(tax_unit, period, parameters): + p = parameters(period).gov.contrib.states.va.dependent_exemption + + filing_status = tax_unit("filing_status", period) + agi = tax_unit("va_agi", period) + + threshold = p.phaseout.threshold[filing_status] + excess_income = max_(agi - threshold, 0) + return excess_income * p.phaseout.rate + + class va_dependent_exemption(Variable): + value_type = float + entity = TaxUnit + label = "Virginia dependent exemption" + unit = USD + definition_period = YEAR + defined_for = StateCode.VA + + def formula(tax_unit, period, parameters): + + maximum = tax_unit("va_dependent_exemption_maximum", period) + phaseout = tax_unit("va_dependent_exemption_phaseout", period) + + return max_(maximum - phaseout, 0) + + class va_older_dependents_count(Variable): + value_type = float + entity = TaxUnit + label = "Virginia older dependents count" + unit = USD + definition_period = YEAR + defined_for = StateCode.VA + + def formula(tax_unit, period, parameters): + person = tax_unit.members + is_dependent = person("is_tax_unit_dependent", period) + total_dependents = tax_unit.sum(is_dependent) + eligible_dependent_exemptions = tax_unit( + "va_eligible_dependents_count", period + ) + return max_(0, total_dependents - eligible_dependent_exemptions) + + class va_personal_exemption(Variable): + value_type = float + entity = TaxUnit + label = "Virginia personal exemption" + unit = USD + definition_period = YEAR + defined_for = StateCode.VA + + def formula(tax_unit, period, parameters): + p_base = parameters( + period + ).gov.states.va.tax.income.exemptions.personal + + # Calculate personal exemptions base amount (filer + spouse + older dependents) + filing_status = tax_unit("filing_status", period) + older_dependents = tax_unit("va_older_dependents_count", period) + personal_count = ( + where( + filing_status == filing_status.possible_values.JOINT, + 2, + 1, + ) + + older_dependents + ) + personal_exemption_amount = personal_count * p_base + + # Add dependent exemption (has its own phase-out logic) + dependent_exemption_amount = tax_unit( + "va_dependent_exemption", period + ) + + return personal_exemption_amount + dependent_exemption_amount + + class reform(Reform): + def apply(self): + self.update_variable(va_eligible_dependents_count) + self.update_variable(va_dependent_exemption_maximum) + self.update_variable(va_dependent_exemption_phaseout) + self.update_variable(va_dependent_exemption) + self.update_variable(va_older_dependents_count) + self.update_variable(va_personal_exemption) + + return reform + + +def create_va_dependent_exemption_reform_fn( + parameters, period, bypass: bool = False +): + if bypass: + return create_va_dependent_exemption_reform() + + p = parameters.gov.contrib.states.va.dependent_exemption + + reform_active = False + current_period = period_(period) + + for i in range(5): + if p(current_period).in_effect: + reform_active = True + break + current_period = current_period.offset(1, "year") + + if reform_active: + return create_va_dependent_exemption_reform() + else: + return None + + +va_dependent_exemption_reform = create_va_dependent_exemption_reform_fn( + None, None, bypass=True +) diff --git a/policyengine_us/tests/policy/contrib/states/va/exemption_reform_test.yaml b/policyengine_us/tests/policy/contrib/states/va/exemption_reform_test.yaml new file mode 100644 index 00000000000..345b8cd67b0 --- /dev/null +++ b/policyengine_us/tests/policy/contrib/states/va/exemption_reform_test.yaml @@ -0,0 +1,182 @@ +- name: VA exemption reform - restrict to children under 18 + period: 2025 + reforms: policyengine_us.reforms.states.va.dependent_exemption.va_dependent_exemption_reform.va_dependent_exemption_reform + input: + gov.contrib.states.va.dependent_exemption.in_effect: true + gov.contrib.states.va.dependent_exemption.amount: 930 + gov.contrib.states.va.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.va.dependent_exemption.age_limit.threshold: 18 + people: + parent: + age: 40 + employment_income: 60_000 + child1: + age: 16 + is_tax_unit_dependent: true + child2: + age: 20 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child1, child2] + filing_status: SINGLE + households: + household: + members: [parent, child1, child2] + state_name: VA + output: + # 1 personal (930) + 1 eligible dependent (930) + 1 older dependent (930) + va_personal_exemption: 2_790 + +- name: VA exemption reform - restrict to children under 13 + period: 2025 + reforms: policyengine_us.reforms.states.va.dependent_exemption.va_dependent_exemption_reform.va_dependent_exemption_reform + input: + gov.contrib.states.va.dependent_exemption.in_effect: true + gov.contrib.states.va.dependent_exemption.amount: 930 + gov.contrib.states.va.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.va.dependent_exemption.age_limit.threshold: 13 + people: + parent: + age: 40 + employment_income: 60_000 + child1: + age: 10 + is_tax_unit_dependent: true + child2: + age: 16 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child1, child2] + filing_status: SINGLE + households: + household: + members: [parent, child1, child2] + state_name: VA + output: + # 1 personal (930) + 1 eligible dependent (930) + 1 older dependent (930) + va_personal_exemption: 2_790 + +- name: VA exemption reform - different amounts for dependent children + period: 2025 + reforms: policyengine_us.reforms.states.va.dependent_exemption.va_dependent_exemption_reform.va_dependent_exemption_reform + input: + gov.contrib.states.va.dependent_exemption.in_effect: true + gov.contrib.states.va.dependent_exemption.amount: 2_000 + gov.contrib.states.va.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.va.dependent_exemption.age_limit.threshold: 18 + people: + parent: + age: 40 + employment_income: 60_000 + child1: + age: 10 + is_tax_unit_dependent: true + child2: + age: 16 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child1, child2] + filing_status: SINGLE + households: + household: + members: [parent, child1, child2] + state_name: VA + output: + # 1 personal (930) + 2 eligible dependents (2_000 each = 4_000) = 4_930 + va_personal_exemption: 4_930 + +- name: VA exemption reform - AGI phaseout + period: 2025 + reforms: policyengine_us.reforms.states.va.dependent_exemption.va_dependent_exemption_reform.va_dependent_exemption_reform + input: + gov.contrib.states.va.dependent_exemption.in_effect: true + gov.contrib.states.va.dependent_exemption.amount: 930 + gov.contrib.states.va.dependent_exemption.phaseout.threshold.SINGLE: 100_000 + gov.contrib.states.va.dependent_exemption.phaseout.rate: 0.05 + people: + parent: + age: 40 + employment_income: 120_000 + child1: + age: 10 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child1] + filing_status: SINGLE + households: + household: + members: [parent, child1] + state_name: VA + output: + # Personal exemption: 930 (baseline) + # Dependent exemption before phaseout: 930 + # Phaseout: (120_000 - 100_000) * 0.05 = 1_000 (only applies to dependent exemption) + # Dependent exemption after phaseout: max(930 - 1_000, 0) = 0 + # Final: 930 (personal) + 0 (dependent) = 930 + va_personal_exemption: 930 + +- name: VA exemption reform - joint filing + period: 2025 + reforms: policyengine_us.reforms.states.va.dependent_exemption.va_dependent_exemption_reform.va_dependent_exemption_reform + input: + gov.contrib.states.va.dependent_exemption.in_effect: true + gov.contrib.states.va.dependent_exemption.amount: 2_000 + gov.contrib.states.va.dependent_exemption.age_limit.in_effect: true + gov.contrib.states.va.dependent_exemption.age_limit.threshold: 18 + people: + parent1: + age: 40 + is_tax_unit_head: true + employment_income: 60_000 + parent2: + age: 38 + is_tax_unit_spouse: true + employment_income: 40_000 + child1: + age: 10 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent1, parent2, child1] + filing_status: JOINT + households: + household: + members: [parent1, parent2, child1] + state_name: VA + output: + # 2 personal exemptions (930 * 2 = 1_860) + 1 eligible dependent (2_000) = 3_860 + va_personal_exemption: 3_860 + +- name: VA exemption reform - no age limit (all dependents eligible) + period: 2025 + reforms: policyengine_us.reforms.states.va.dependent_exemption.va_dependent_exemption_reform.va_dependent_exemption_reform + input: + gov.contrib.states.va.dependent_exemption.in_effect: true + gov.contrib.states.va.dependent_exemption.amount: 2_000 + gov.contrib.states.va.dependent_exemption.age_limit.in_effect: false + people: + parent: + age: 40 + employment_income: 60_000 + child1: + age: 10 + is_tax_unit_dependent: true + child2: + age: 22 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child1, child2] + filing_status: SINGLE + households: + household: + members: [parent, child1, child2] + state_name: VA + output: + # 1 personal (930) + 2 eligible dependents (2_000 each = 4_000) = 4_930 + # No older dependents since age limit is not in effect + va_personal_exemption: 4_930