diff --git a/changelog_entry.yaml b/changelog_entry.yaml index e69de29bb2d..223126755cd 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -0,0 +1,4 @@ +- bump: minor + changes: + fixed: + - Virginia spouse tax adjustment now correctly calculates eligibility by computing separate VAGI for each person instead of prorating combined VAGI diff --git a/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/spouse_tax_adjustment/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/spouse_tax_adjustment/integration.yaml new file mode 100644 index 00000000000..3ea6ca9ab41 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/spouse_tax_adjustment/integration.yaml @@ -0,0 +1,21 @@ +- name: VA Spouse Tax Adjustment - elderly head with low income + period: 2024 + absolute_error_margin: 1 + input: + people: + head: + age: 75 + employment_income: 11_752.73 + spouse: + age: 40 + employment_income: 55_728.48 + tax_units: + tax_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: VA + output: + va_spouse_tax_adjustment: 0 # Head's VAGI less exemptions <= 0, not eligible + va_income_tax: 1_402.22 # $1,802.22 - $400 rebate - $0 STA diff --git a/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/va_agi_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/va_agi_person.yaml index 23d360813f4..cbe2a1434a2 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/va_agi_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/va/tax/income/va_agi_person.yaml @@ -1,37 +1,68 @@ -- name: Attributing the state adjusted gross income based on federal AGI +# VA AGI for each person is calculated as: +# Federal AGI + VA additions - VA subtractions (including age deduction) +# This is the "Separate Virginia Adjusted Gross Income" used in the +# Spouse Tax Adjustment Worksheet (Form 760 instructions, page 12) + +- name: Separate VAGI - two earners without VA subtractions period: 2021 input: people: person1: + age: 40 adjusted_gross_income_person: 10_000 person2: + age: 40 adjusted_gross_income_person: 30_000 tax_units: tax_unit: members: [person1, person2] - va_agi: 30_000 households: household: members: [person1, person2] state_code: VA output: - va_agi_person: [7_500, 22_500] + # Each person's VA AGI = their federal AGI (no additions/subtractions) + va_agi_person: [10_000, 30_000] -- name: One person without income +- name: Separate VAGI - one person without income period: 2021 input: people: person1: + age: 40 adjusted_gross_income_person: 10_000 person2: + age: 40 adjusted_gross_income_person: 0 tax_units: tax_unit: members: [person1, person2] - va_agi: 8_000 households: household: members: [person1, person2] state_code: VA output: - va_agi_person: [8_000, 0] + # Person 1 VA AGI = $10K, Person 2 VA AGI = $0 + va_agi_person: [10_000, 0] + +- name: Separate VAGI - elderly with age deduction + period: 2021 + input: + people: + person1: + age: 75 + adjusted_gross_income_person: 20_000 + person2: + age: 40 + adjusted_gross_income_person: 30_000 + tax_units: + tax_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: VA + output: + # Person 1: $20K - $12K age deduction = $8K + # Person 2: $30K (no age deduction) + va_agi_person: [8_000, 30_000] diff --git a/policyengine_us/variables/gov/states/va/tax/income/additions/va_additions_person.py b/policyengine_us/variables/gov/states/va/tax/income/additions/va_additions_person.py new file mode 100644 index 00000000000..a56b8f4e954 --- /dev/null +++ b/policyengine_us/variables/gov/states/va/tax/income/additions/va_additions_person.py @@ -0,0 +1,22 @@ +from policyengine_us.model_api import * + + +class va_additions_person(Variable): + value_type = float + entity = Person + label = ( + "Virginia additions to federal adjusted gross income for each person" + ) + unit = USD + definition_period = YEAR + reference = ( + "https://law.lis.virginia.gov/vacodefull/title58.1/chapter3/article2/", + "https://www.tax.virginia.gov/sites/default/files/vatax-pdf/2022-760-instructions.pdf#page=24", + ) + defined_for = StateCode.VA + + def formula(person, period, parameters): + # Currently no person-specific additions are implemented + # When additions are added, they should be calculated here + # at the person level + return 0 diff --git a/policyengine_us/variables/gov/states/va/tax/income/subtractions/age_deduction/va_age_deduction_person.py b/policyengine_us/variables/gov/states/va/tax/income/subtractions/age_deduction/va_age_deduction_person.py new file mode 100644 index 00000000000..5fc178bd85c --- /dev/null +++ b/policyengine_us/variables/gov/states/va/tax/income/subtractions/age_deduction/va_age_deduction_person.py @@ -0,0 +1,42 @@ +from policyengine_us.model_api import * + + +class va_age_deduction_person(Variable): + value_type = float + entity = Person + label = "Virginia age deduction for each person" + unit = USD + definition_period = YEAR + reference = "https://www.tax.virginia.gov/sites/default/files/vatax-pdf/2022-760-instructions.pdf#page=16" + defined_for = StateCode.VA + + def formula(person, period, parameters): + p = parameters( + period + ).gov.states.va.tax.income.subtractions.age_deduction + + filing_status = person.tax_unit("filing_status", period) + + age = person("age", period) + birth_year = period.start.year - age + + agi = person("adjusted_gross_income_person", period) + + # Check if person is eligible for an age deduction + eligible = age >= p.age_minimum + + # Check if person is eligible for full deduction (no income limit) + eligible_for_full_deduction = ( + birth_year < p.birth_year_limit_for_full_amount + ) + + # Calculate the maximum allowable deduction amount per person + maximum_allowable_deduction = p.amount * eligible + + # Calculate the amount that the adjusted federal AGI exceeds the threshold + excess = max_(agi - p.threshold[filing_status], 0) + + # Reduce by the entire excess, unless eligible for the full deduction + reduction = excess * where(eligible_for_full_deduction, 0, 1) + + return max_(maximum_allowable_deduction - reduction, 0) diff --git a/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_subtractions_person.py b/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_subtractions_person.py new file mode 100644 index 00000000000..af37fc0b009 --- /dev/null +++ b/policyengine_us/variables/gov/states/va/tax/income/subtractions/va_subtractions_person.py @@ -0,0 +1,27 @@ +from policyengine_us.model_api import * + + +class va_subtractions_person(Variable): + value_type = float + entity = Person + label = "Virginia subtractions from federal adjusted gross income for each person" + unit = USD + definition_period = YEAR + reference = ( + "https://law.lis.virginia.gov/vacodefull/title58.1/chapter3/article2/", + "https://www.tax.virginia.gov/sites/default/files/vatax-pdf/2022-760-instructions.pdf#page=16", + ) + defined_for = StateCode.VA + + def formula(person, period, parameters): + # Calculate person-specific subtractions + # The main subtraction that varies by person is the age deduction + age_deduction = person("va_age_deduction_person", period) + + # Other subtractions (disability, federal employee, military, etc.) + # are calculated at the tax unit level by summing person-level amounts + # For the purpose of calculating separate VAGI per person, we focus on + # the age deduction which is the key subtraction that affects the + # spouse tax adjustment calculation + + return age_deduction diff --git a/policyengine_us/variables/gov/states/va/tax/income/va_agi_person.py b/policyengine_us/variables/gov/states/va/tax/income/va_agi_person.py index 2b2d5bcf0a8..0b8f9d6da7e 100644 --- a/policyengine_us/variables/gov/states/va/tax/income/va_agi_person.py +++ b/policyengine_us/variables/gov/states/va/tax/income/va_agi_person.py @@ -12,11 +12,19 @@ class va_agi_person(Variable): defined_for = StateCode.VA def formula(person, period, parameters): - total_agi = person.tax_unit("va_agi", period) - person_agi = person("adjusted_gross_income_person", period) - total_federal_agi = person.tax_unit.sum(person_agi) + # Calculate separate Virginia AGI for each person + # This follows the VA Form 760 Spouse Tax Adjustment Worksheet + # which shows "Separate Virginia Adjusted Gross Income" calculation + # on page 12 of the instructions - prorate = np.zeros_like(total_agi) - mask = total_federal_agi > 0 - prorate[mask] = person_agi[mask] / total_federal_agi[mask] - return total_agi * prorate + # Start with federal AGI for this person + federal_agi = person("adjusted_gross_income_person", period) + + # Add Virginia-specific additions for this person + va_additions = person("va_additions_person", period) + + # Subtract Virginia-specific subtractions for this person + va_subtractions = person("va_subtractions_person", period) + + # Calculate separate VAGI for this person + return federal_agi + va_additions - va_subtractions diff --git a/uv.lock b/uv.lock index 728df755b14..bef6217d61e 100644 --- a/uv.lock +++ b/uv.lock @@ -2327,11 +2327,11 @@ wheels = [ [[package]] name = "tzdata" -version = "2025.2" +version = "2025.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, ] [[package]]