Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/id-worksheet-dependent-medical.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Count every tax-unit member's other medical expenses, including bills the filers paid for dependents, in Idaho's Form 39R claimant medical worksheet, as federal Schedule A does.
212 changes: 194 additions & 18 deletions policyengine_us/tests/core/test_id_health_insurance_premiums.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,11 +231,16 @@ def test_actual_medical_deduction_is_scoped_to_period_and_branch():
strict=True,
reason=(
"policyengine-core<3.32.27 retains deleted input provenance and "
"misclassifies a federal medical deduction recalculated before the "
"Idaho helper as supplied; fixed by policyengine-core#561."
"misclassifies a federal itemized total recalculated before the "
"Idaho formula as supplied; fixed by policyengine-core#561."
),
)
def test_deleted_medical_input_recalculated_federal_first():
def test_deleted_itemized_input_recalculated_federal_first():
# From derived inputs Idaho's claimant worksheet equals the federal medical
# deduction, so a deleted federal medical input cannot change any Idaho
# amount. Idaho's itemized total still tells a supplied federal total,
# used as given, from a derived one, whose medical component it replaces
# with an explicit Idaho claimant deduction.
year = 2025
simulation = Simulation(
tax_benefit_system=SYSTEM,
Expand All @@ -249,19 +254,17 @@ def test_deleted_medical_input_recalculated_federal_first():
"dependent": {
"age": {year: 20},
"is_tax_unit_dependent": {year: True},
# Other costs, which the federal subtotal counts for every
# member and Idaho's claimant worksheet leaves out, keep
# the two amounts apart. A dependent's own premiums would
# not: neither counts them.
"other_medical_expenses": {year: 500},
},
},
"tax_units": {
"tax_unit": {
"members": ["head", "dependent"],
"adjusted_gross_income": {year: 30_000},
"standard_deduction": {year: 15_750},
"id_itemized_deductions": {year: 20_000},
"interest_deduction": {year: 20_000},
"salt_deduction": {year: 0},
"id_salt_deduction": {year: 0},
"id_health_insurance_premiums_medical_deduction": {year: 250},
}
},
"households": {
Expand All @@ -272,14 +275,187 @@ def test_deleted_medical_input_recalculated_federal_first():
},
},
)
simulation.set_input("medical_expense_deduction", year, np.array([3_000]))
simulation.delete_arrays("medical_expense_deduction", year)
simulation.set_input("itemized_taxable_income_deductions", year, np.array([30_000]))
simulation.delete_arrays("itemized_taxable_income_deductions", year)

# Federal first: 3,000 + 500 - 30,000 * 7.5% = 1,250.
assert simulation.calculate("medical_expense_deduction", year).tolist() == [1_250]
# Idaho excludes the dependent's costs: medical overlap is
# 3,000 - 30,000 * 7.5% = 750, leaving 3,000 - 750 = 2,250.
# Core's provenance defect before 3.32.27 uses 1,250 and returns 1,750.
# Federal first: 20,000 interest + (3,000 + 500 - 30,000 * 7.5%) medical
# = 21,250.
assert simulation.calculate(
"id_health_insurance_premiums_subtraction", year
).tolist() == [2_250]
"itemized_taxable_income_deductions", year
).tolist() == [21_250]
# Idaho replaces the 1,250 federal medical deduction with the explicit 250:
# 21,250 - 1,000 = 20,250. Core's provenance defect before 3.32.27 takes the
# recalculated total as supplied and keeps 21,250.
assert simulation.calculate("id_itemized_deductions", year).tolist() == [20_250]


# Form 39R worksheet lines 1-6 take the medical expenses claimed on federal
# Schedule A, which counts bills the filers paid for dependents. Premium inputs
# sit on the payer and other medical expenses on the person cared for, so the
# claimant worksheet counts the filers' premiums and every member's other
# costs. Each drawn return runs in one vectorized simulation per year with
# these variants:
VARIANTS = (
"drawn",
# every member's other costs recorded on the head;
"bills_on_head",
# every member's other costs recorded on one dependent;
"bills_on_dependent",
# one dependent's other costs raised;
"dependent_bills_raised",
# every dependent's own premiums set to 0.
"no_dependent_premiums",
)
# The drawn high incomes reach the 2026 itemized limitation (IRC 68).
CLAIMANT_YEARS = (2025, 2026)
IDAHO_OUTPUTS = (
"id_health_insurance_premiums_medical_deduction",
"id_qualified_health_insurance_premiums",
"id_itemized_deductions",
"id_itemizes",
"id_health_insurance_premiums_subtraction",
"id_deductions",
)
CENT = 0.01


@st.composite
def claimant_returns(draw):
filers = draw(st.sampled_from([1, 2]))
dependents = draw(st.integers(1, 2))
members = filers + dependents
return {
"filers": filers,
"premiums": [draw(st.integers(0, 20_000)) for _ in range(members)],
"other": [draw(st.integers(0, 40_000)) for _ in range(members)],
"agi": draw(st.integers(0, 150_000) | st.integers(600_000, 900_000)),
"interest": draw(st.integers(0, 40_000)),
"standard": draw(st.integers(0, 35_000)),
"above_line": draw(st.integers(0, 25_000)),
"receiver": filers + draw(st.integers(0, dependents - 1)),
"raise": draw(st.integers(1, 10_000)),
}


def _claimant_inputs(case, variant):
premiums = list(case["premiums"])
other = list(case["other"])
if variant == "bills_on_head":
other = [sum(other)] + [0] * (len(other) - 1)
elif variant == "bills_on_dependent":
moved = [0] * len(other)
moved[case["receiver"]] = sum(other)
other = moved
elif variant == "dependent_bills_raised":
other[case["receiver"]] += case["raise"]
elif variant == "no_dependent_premiums":
premiums[case["filers"] :] = [0] * (len(premiums) - case["filers"])
return premiums, other


def _claimant_simulation(cases, year):
people, tax_units, households, rows = {}, {}, {}, []
for index, case in enumerate(cases):
for variant in VARIANTS:
unit = f"case_{index}_{variant}"
premiums, other = _claimant_inputs(case, variant)
members = [f"{unit}_{member}" for member in range(len(premiums))]
for member, person in enumerate(members):
filer = member < case["filers"]
people[person] = {
"age": {year: 40 if filer else 10},
"is_tax_unit_head": {year: member == 0},
"is_tax_unit_spouse": {year: member == 1 and filer},
"is_tax_unit_dependent": {year: not filer},
"health_insurance_premiums": {year: premiums[member]},
"other_medical_expenses": {year: other[member]},
}
tax_units[unit] = {
"members": members,
"adjusted_gross_income": {year: case["agi"]},
"self_employed_health_insurance_ald": {year: case["above_line"]},
"interest_deduction": {year: case["interest"]},
"salt_deduction": {year: 0},
"id_salt_deduction": {year: 0},
# Supplied to keep the model graph small: neither depends on
# medical expenses.
"standard_deduction": {year: case["standard"]},
"taxable_income_before_itemized_deductions": {year: case["agi"]},
}
households[unit] = {"members": members, "state_code": {year: "ID"}}
rows.append((index, variant))
simulation = Simulation(
tax_benefit_system=SYSTEM,
situation={
"people": people,
"tax_units": tax_units,
"households": households,
},
)
return simulation, rows


@settings(max_examples=8, deadline=None, derandomize=True)
@given(cases=st.lists(claimant_returns(), min_size=2, max_size=3))
def test_claimant_worksheet_counts_bills_paid_for_dependents(cases):
for year in CLAIMANT_YEARS:
simulation, rows = _claimant_simulation(cases, year)
floor = SYSTEM.parameters(year).gov.irs.deductions.itemized.medical.floor
values = {
# id_itemizes compares as 0 or 1.
variable: dict(
zip(rows, simulation.calculate(variable, year).astype(float))
)
for variable in IDAHO_OUTPUTS
+ ("medical_expense_deduction", "itemized_taxable_income_deductions")
}
for index, case in enumerate(cases):

def value(variable, variant="drawn"):
return values[variable][(index, variant)]

worksheet = value("id_health_insurance_premiums_medical_deduction")
# 1. Differential: an independent oracle, the federal Schedule A
# deduction, and Idaho's worksheet agree.
filer_premiums = sum(case["premiums"][: case["filers"]])
oracle = max(
0,
max(0, filer_premiums - case["above_line"])
+ sum(case["other"])
- floor * max(0, case["agi"]),
)
assert worksheet == pytest.approx(oracle, abs=CENT), case
assert value("medical_expense_deduction") == pytest.approx(
worksheet, abs=CENT
), case
# 2. Idaho's medical correction leaves a derived federal itemized
# total unchanged: the two medical deductions agree.
assert value("id_itemized_deductions") == pytest.approx(
value("itemized_taxable_income_deductions"), abs=CENT
), case
# 3. Relocation: whose input records a bill changes no Idaho
# amount, and neither do a dependent's own premiums.
for variant in (
"bills_on_head",
"bills_on_dependent",
"no_dependent_premiums",
):
for variable in IDAHO_OUTPUTS:
assert value(variable, variant) == pytest.approx(
value(variable), abs=CENT
), (variable, variant, case)
# 4. Monotonicity: more bills for a dependent never lower the
# worksheet.
assert (
value(
"id_health_insurance_premiums_medical_deduction",
"dependent_bills_raised",
)
>= worksheet - CENT
), case
# 5. Bounds: the subtraction stays within the qualified premiums.
assert (
-CENT
<= value("id_health_insurance_premiums_subtraction")
<= value("id_qualified_health_insurance_premiums") + CENT
), case
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,66 @@
id_health_insurance_premiums_subtraction: 2_250
id_taxable_income: 7_000

- name: A bill the parent paid counts the same on the parent's input or the child's
# Other medical expenses sit on the person cared for, so unit a records the
# child's 3,000 bill on the parent and unit b on the child. Schedule A
# counts both, and Form 39R worksheet lines 3-6 take the Schedule A amounts.
# Medical deduction: 1,000 + 3,000 - 7.5% of 30,000 = 1,750.
# Itemized: 20,000 + 1,750 = 21,750; it beats standard 15,750 + 1,000.
# Subtraction: 1,000 - min(1,000, 1,750) = 0.
# Taxable income: 30,000 - 21,750 - 0 = 8,250.
# Leaving out the child's bill would take 1,750 off unit b's itemized
# deductions and restore its 1,000 subtraction: taxable income 9,000.
period: 2025
input:
people:
a_parent:
age: 40
health_insurance_premiums: 1_000
other_medical_expenses: 3_000
is_tax_unit_head: true
a_child:
age: 10
is_tax_unit_dependent: true
b_parent:
age: 40
health_insurance_premiums: 1_000
is_tax_unit_head: true
b_child:
age: 10
other_medical_expenses: 3_000
is_tax_unit_dependent: true
tax_units:
a:
members: [a_parent, a_child]
adjusted_gross_income: 30_000
interest_deduction: 20_000
salt_deduction: 0
id_salt_deduction: 0
standard_deduction: 15_750
b:
members: [b_parent, b_child]
adjusted_gross_income: 30_000
interest_deduction: 20_000
salt_deduction: 0
id_salt_deduction: 0
standard_deduction: 15_750
households:
a:
members: [a_parent, a_child]
state_code: ID
b:
members: [b_parent, b_child]
state_code: ID
output:
medical_expense_deduction: [1_750, 1_750]
id_health_insurance_premiums_medical_deduction: [1_750, 1_750]
itemized_taxable_income_deductions: [21_750, 21_750]
id_itemized_deductions: [21_750, 21_750]
id_itemizes: [true, true]
id_health_insurance_premiums_subtraction: [0, 0]
id_taxable_income: [8_250, 8_250]

- name: Corrected Idaho medical costs also reduce the 2026 itemized limitation
# P.L. 119-21 sec. 70111: $70,000 * (1 - 2/37) = $66,216.21622.
# Subtracting $500 from the already-limited federal total would be too low.
Expand Down Expand Up @@ -92,6 +152,44 @@
id_itemizes: true
id_health_insurance_premiums_subtraction: 60_000

- name: A dependent's bills stay in the 2026 limited Idaho itemized total
# Medical deduction: 40,000 + the child's 30,000 - 7.5% of 800,000 = 10,000.
# P.L. 119-21 sec. 70111: (30,000 + 10,000) * (1 - 2/37) = 37,837.83784.
# Leaving out the child's bills would take the 10,000 off before the
# limitation and give 28,378.37838.
# Subtraction: 40,000 - min(40,000, 10,000) = 30,000.
absolute_error_margin: 0.01
period: 2026
input:
people:
head:
age: 40
health_insurance_premiums: 40_000
is_tax_unit_head: true
child:
age: 10
other_medical_expenses: 30_000
is_tax_unit_dependent: true
tax_units:
tax_unit:
members: [head, child]
filing_status: SINGLE
adjusted_gross_income: 800_000
interest_deduction: 30_000
salt_deduction: 0
id_salt_deduction: 0
households:
household:
members: [head, child]
state_code: ID
output:
medical_expense_deduction: 10_000
id_health_insurance_premiums_medical_deduction: 10_000
itemized_taxable_income_deductions: 37_837.83784
id_itemized_deductions: 37_837.83784
id_itemizes: true
id_health_insurance_premiums_subtraction: 30_000

- name: Idaho does not add back the federal foreign tax credit when it is claimed as a credit
period: 2025
input:
Expand Down
Loading