Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
9ce6a94
Document disjoint employee health premium inputs
MaxGhenis Oct 10, 2026
e44a898
Count disjoint payroll premiums according to each consumer rule
MaxGhenis Oct 10, 2026
2344a1e
Keep generated premium households in independent family groups
MaxGhenis Oct 10, 2026
0073f9c
Reuse reference policy safely in premium transfer property
MaxGhenis Oct 10, 2026
1d142d9
Exclude qualified premium salary reductions from SSI wages
MaxGhenis Oct 10, 2026
37f5e57
Preserve gross work earnings for the SSI SGA test
MaxGhenis Oct 10, 2026
5a036d2
Point premium consumer citations to the relevant PDF pages
MaxGhenis Oct 10, 2026
a50b156
Isolate the Kansas premium spenddown regression from SSI coverage
MaxGhenis Oct 10, 2026
59690fa
Exercise Kansas premium spenddown from raw wage inputs
MaxGhenis Oct 10, 2026
f523c2a
Revert CRFB premium addback change for separate methodology review
MaxGhenis Oct 10, 2026
023cdb0
Cover pretax premium exclusions across SSI income consumers
MaxGhenis Oct 10, 2026
de43bf5
Preserve gross premium income for Minnesota MSA eligibility and guard…
MaxGhenis Oct 10, 2026
16ab034
Define unmarried parental income fixtures with singleton marital units
MaxGhenis Oct 10, 2026
70c8e5a
Clarify the statutory basis for MSA gross premium treatment
MaxGhenis Oct 10, 2026
c7d2709
Supply SSI disability criteria in student premium fixtures
MaxGhenis Oct 10, 2026
d939e63
Merge main into premium input contract branch
MaxGhenis Oct 13, 2026
b5a0b3c
Merge updated main after premium contract validation
MaxGhenis Oct 13, 2026
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/hub-premium-input-contract-1010.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Document disjoint pretax and non-pretax health premium inputs, include employee-paid pretax premiums in applicable benefit and New Jersey medical deductions, exclude pretax premiums from SSI wages and North Dakota renter-refund income. Preserve Minnesota Supplemental Aid gross income for eligibility and guardian fees while applying SSI salary-reduction exclusions to countable income.
232 changes: 232 additions & 0 deletions policyengine_us/tests/core/test_premium_input_contract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
"""Premium payment methods are disjoint inputs with consumer-specific treatment.

Moving employee-paid premiums from non-pretax payments to pretax payroll
deductions leaves total medical spending unchanged. SNAP, HUD,
child-care deductions, Orange County General Relief and New Jersey
medical expenses count both payment methods. Michigan's wage exclusion
and non-pretax premium deduction likewise leave household resources unchanged
with other benefit amounts held fixed.
North Dakota excludes pretax premiums from income and deducts non-pretax
premiums as medical expenses, also leaving renters' refund income unchanged.

Federal taxable and FICA wages, ACA/Medicaid MAGI, and tax deductions that
exclude pretax premiums distinguish the payment methods. SSI also excludes
qualified salary-reduction premiums from wages before its earned-income
disregard. The SGA work-earnings test retains payroll premium deductions.
Program premiums and
eligibility are held fixed here: changing MAGI can otherwise legitimately change
Marketplace, CHIP or Medicaid premiums, obscuring the spending invariant.

Each generated batch uses fresh vectorized simulations across several states;
no calculated arrays are shared between the two situations or across examples.
"""

import numpy as np
from hypothesis import HealthCheck, example, given, settings
from hypothesis import strategies as st

from policyengine_us import Simulation
from policyengine_us.system import system


YEAR = 2026
MONTH = "2026-01"
STATES = ("CA", "MO", "PA", "NJ", "OH", "MI", "ND")


@st.composite
def premium_transfers(draw):
total = draw(st.integers(0, 12_000))
pretax = draw(st.integers(0, total))
transfer = draw(st.integers(0, total - pretax))
return total, pretax, transfer


def _situation(batch, moved):
people = {}
tax_units = {}
spm_units = {}
households = {}
marital_units = {}
for row, (total, initial_pretax, transfer) in enumerate(batch):
pretax = initial_pretax + (transfer if moved else 0)
non_pretax = total - pretax
for state, size in ((state, size) for state in STATES for size in (1, 2)):
key = f"{state}_{size}_{row}"
people[key] = {
"age": {YEAR: 40},
"is_tax_unit_head": {YEAR: True},
"is_ssi_aged_blind_disabled": {YEAR: True},
"employment_income": {YEAR: 250_000},
"pre_tax_health_insurance_premiums": {YEAR: pretax},
# These are alternative representations used by different
# consumers, not additive premiums within the non-pretax set.
"health_insurance_premiums": {YEAR: non_pretax},
"health_insurance_premiums_without_medicare_part_b": {YEAR: non_pretax},
"other_health_insurance_premiums": {YEAR: non_pretax},
"other_medical_expenses": {YEAR: 300},
"over_the_counter_health_expenses": {YEAR: 100},
"medicare_enrolled": {YEAR: False},
"is_medicare_eligible": {YEAR: False},
"medicare_part_a_premium": {YEAR: 0},
"medicare_part_b_premium": {YEAR: 0},
"income_adjusted_part_d_premium_surcharge": {YEAR: 0},
"employer_sponsored_insurance_premiums": {YEAR: 0},
"self_employed_health_insurance_ald_person": {YEAR: 0},
"general_assistance": {YEAR: 0},
# Hold other benefits fixed for the Michigan invariant.
"ssi": {YEAR: 0},
"ca_oc_general_relief_countable_earned_income": {MONTH: 5_000},
"ca_oc_general_relief_gross_unearned_income": {MONTH: 0},
"ca_oc_general_relief_receives_other_cash_assistance": {MONTH: False},
}
members = [key]
if size == 2:
spouse = f"{key}_spouse"
people[spouse] = {
**people[key],
"is_tax_unit_head": {YEAR: False},
"is_tax_unit_spouse": {YEAR: True},
}
members.append(spouse)
marital_units[key] = {"members": members}
tax_units[key] = {
"members": members,
"chip_premium": {YEAR: 0},
"medicaid_premium": {YEAR: 0},
"marketplace_net_premium": {YEAR: 0},
"above_the_line_deductions": {YEAR: 0},
"self_employed_health_insurance_ald": {YEAR: 0},
"dependents_self_employed_health_insurance_ald": {YEAR: 0},
"medical_expense_deduction": {YEAR: 0},
"tax_unit_itemizes": {YEAR: False},
"taxable_income": {YEAR: 300_000},
"nj_agi": {YEAR: 50_000},
"oh_employer_subsidized_health_plan_eligible": {YEAR: True},
}
spm_units[key] = {
"members": members,
"tanf": {YEAR: 0},
"mo_ccs_countable_income": {MONTH: 5_000},
"pa_ccw_countable_income": {YEAR: 60_000},
"pa_ccw_stepparent_deduction": {MONTH: 0},
}
households[key] = {
"members": members,
"state_code": {YEAR: state},
"in_oc": {YEAR: state == "CA"},
}
return {
"people": people,
"tax_units": tax_units,
"spm_units": spm_units,
"households": households,
"marital_units": marital_units,
"families": {
key: {"members": unit["members"]} for key, unit in marital_units.items()
},
}


@settings(
max_examples=8,
deadline=None,
suppress_health_check=[HealthCheck.too_slow],
)
@example(batch=[(0, 0, 0), (12_000, 0, 12_000), (12_000, 6_000, 6_000)])
@example(batch=[(12_000, 12_000, 0), (1, 0, 1)])
@given(batch=st.lists(premium_transfers(), min_size=1, max_size=3))
def test_premium_transfer_respects_each_consumers_tax_treatment(batch):
# Each batch contains both single adults and two-person married units.
# Reusing the reference system avoids rebuilding the country per example.
baseline = Simulation(situation=_situation(batch, False), tax_benefit_system=system)
moved = Simulation(situation=_situation(batch, True), tax_benefit_system=system)
sizes = np.tile((1, 2), len(batch) * len(STATES))
transfer = np.repeat([row[2] for row in batch], len(STATES) * 2)
total = np.repeat([row[0] for row in batch], len(STATES) * 2) * sizes
states = np.tile(np.repeat(STATES, 2), len(batch))
person_transfer = np.repeat(transfer, sizes)
person_states = np.repeat(states, sizes)
unit_transfer = transfer * sizes

np.testing.assert_allclose(
baseline.calculate("spm_unit_medical_out_of_pocket_expenses", YEAR),
total + 400 * sizes,
rtol=0,
atol=0.02,
)
# North Dakota starts from raw market wages, excludes the pretax
# contribution once, and deducts only the remaining medical expenses.
np.testing.assert_allclose(
baseline.calculate("nd_renters_refund_income", YEAR),
(250_000 * sizes - total - 300 * sizes) * (states == "ND"),
rtol=0,
atol=0.02,
)

invariant_consumers = (
("spm_unit_health_insurance_premiums", YEAR),
("spm_unit_medical_out_of_pocket_expenses", YEAR),
("snap_allowable_medical_expenses", YEAR),
("hud_medical_expenses", YEAR),
("mo_ccs_adjusted_income", MONTH),
("pa_ccw_medical_expenses", YEAR),
("pa_ccw_adjusted_income", YEAR),
("ca_oc_general_relief_countable_income_person", MONTH),
("nj_medical_expense_deduction", YEAR),
("mi_household_resources", YEAR),
("nd_renters_refund_income", YEAR),
("ssi_engaged_in_sga", YEAR),
)
for variable, period in invariant_consumers:
np.testing.assert_allclose(
moved.calculate(variable, period),
baseline.calculate(variable, period),
rtol=0,
atol=0.02,
err_msg=variable,
)

tax_status_sensitive_consumers = (
"irs_employment_income",
"payroll_tax_gross_wages",
"ssi_earned_income",
"adjusted_gross_income",
"aca_magi",
"medicaid_magi",
"medicaid_magi_person",
"medical_expense_health_insurance_premiums",
"medicaid_medically_needy_medical_expenses",
)
for variable in tax_status_sensitive_consumers:
np.testing.assert_allclose(
baseline.calculate(variable, YEAR) - moved.calculate(variable, YEAR),
person_transfer
if system.variables[variable].entity.key == "person"
else unit_transfer,
rtol=0,
atol=0.02,
err_msg=variable,
)

# These categorically eligible individuals remain above the earned-income
# exclusions. SSI's 50% earned-income disregard halves the wage change.
np.testing.assert_allclose(
baseline.calculate("ssi_countable_income", YEAR)
- moved.calculate("ssi_countable_income", YEAR),
person_transfer / 2,
rtol=0,
atol=0.02,
)

for variable, state in (
("oh_insured_unreimbursed_medical_care_expense_amount", "OH"),
("mo_qualified_health_insurance_premiums", "MO"),
):
np.testing.assert_allclose(
baseline.calculate(variable, YEAR) - moved.calculate(variable, YEAR),
person_transfer * (person_states == state),
rtol=0,
atol=0.02,
err_msg=variable,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
- name: SSI-based spenddown expenses do not repeat the pretax wage exclusion.
period: 2026
input:
health_insurance_premiums: 1_800
pre_tax_health_insurance_premiums: 2_400
other_medical_expenses: 300
medicare_enrolled: false
output:
medicaid_medically_needy_medical_expenses: 2_100

- name: SSI-based spenddown retains decomposed non-pretax Medicare premiums.
period: 2026
input:
health_insurance_premiums_without_medicare_part_b: 600
medicare_part_b_premium: 1_200
medicare_enrolled: true
pre_tax_health_insurance_premiums: 2_400
other_medical_expenses: 300
output:
medicaid_medically_needy_medical_expenses: 2_100

- name: Kansas spenddown does not deduct pretax premiums again from countable income.
period: 2026
input:
age: 65
is_in_medicaid_medically_needy_category: true
is_adult_for_medicaid: false
is_optional_senior_or_disabled_for_medicaid: false
# Isolate spenddown from the separate SSI-recipient Medicaid pathway.
is_ssi_recipient_for_medicaid: false
employment_income: 27_020
ssi_countable_resources: 1_000
state_code: KS
health_insurance_premiums: 2_000
pre_tax_health_insurance_premiums: 4_000
medicare_enrolled: false
output:
# (27,020 - 4,000 - 12 * (20 + 65)) / 2 = 11,000.
is_ssi_aged_blind_disabled: true
ssi_earned_income: 23_020
ssi_countable_income: 11_000
medicaid_medically_needy_medical_expenses: 2_000
is_ssi_recipient_for_medicaid: false
is_medically_needy_for_medicaid: false
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,50 @@
output:
medicaid_optional_senior_or_disabled_unearned_income_deemed_from_ineligible_parent: [6_006, 0]

- name: Optional Medicaid parental deeming inherits the SSI exclusion for qualified pretax premiums.
period: 2025
absolute_error_margin: 0.01
input:
people:
recipient:
age: 12
is_ssi_disabled: true
sibling:
age: 14
employment_income: 2_400
pre_tax_health_insurance_premiums: 1_200
parent:
age: 40
is_parent: true
employment_income: 36_000
pre_tax_health_insurance_premiums: 3_600
tax_units:
tax_unit:
members: [recipient, sibling, parent]
# A parent and children are not a married couple. Explicit singleton
# units prevent default grouping from also applying spousal deeming.
marital_units:
recipient_unit:
members: [recipient]
sibling_unit:
members: [sibling]
parent_unit:
members: [parent]
households:
household:
members: [recipient, sibling, parent]
state_code: NC
output:
# 42 USC 1396a(m) uses SSI methodology for this optional pathway.
# POMS SI 00820.102C.2: parent wages 32,400; sibling wages 1,200.
# 20 CFR 416.1165(b), (d): sibling allocation = 4,596, so
# (32,400 - 4,596 - 20 * 12 - 65 * 12) / 2 - 967 * 12 = 1,788 deemed.
# Under (e)(1), apply child's general exclusion: 1,788 - 20 * 12 = 1,548.
medicaid_optional_senior_or_disabled_unearned_income_deemed_from_ineligible_parent: [1_788, 0, 0]
is_ssi_spousal_deeming_applies: [false, false, false]
medicaid_optional_senior_or_disabled_income_deemed_from_ineligible_spouse: [0, 0, 0]
medicaid_optional_senior_or_disabled_countable_income: [1_548, 0, 0]

# Missouri MHDC deems only a natural or adoptive parent's income, never a
# stepparent's (DSS MHABD Manual § 0805.020.15), and divides the deemed
# amount equally among the disabled children applying. Parent status
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,39 @@
# 1,450 - 20 = 1,430 monthly, or 17,160 annually.
medicaid_optional_senior_or_disabled_countable_income: 17_160
is_optional_senior_or_disabled_income_eligible: false

- name: Optional Medicaid spouse deeming inherits the SSI exclusion for qualified pretax premiums.
period: 2025
absolute_error_margin: 0.01
input:
people:
recipient:
age: 70
is_tax_unit_head: true
spouse:
age: 60
is_tax_unit_spouse: true
employment_income: 24_000
pre_tax_health_insurance_premiums: 2_400
child:
age: 14
employment_income: 2_400
pre_tax_health_insurance_premiums: 1_200
tax_units:
tax_unit:
members: [recipient, spouse, child]
marital_units:
marital_unit:
members: [recipient, spouse]
households:
household:
members: [recipient, spouse, child]
state_code: NC
output:
# 42 USC 1396a(m) uses SSI methodology for this optional pathway.
# POMS SI 00820.102C.2: spouse wages 21,600; child wages 1,200.
# 20 CFR 416.1163(b), (d): allocation = (1,450 - 967) * 12
# - 1,200 = 4,596; remaining spouse wages = 17,004 > 5,796.
# (17,004 - 20 * 12 - 65 * 12) / 2 = 7,992 deemed and countable.
medicaid_optional_senior_or_disabled_income_deemed_from_ineligible_spouse: [7_992, 0, 0]
medicaid_optional_senior_or_disabled_countable_income: [7_992, 0, 0]
Loading