diff --git a/changelog_entry.yaml b/changelog_entry.yaml index e69de29bb2d..b12a0a80aad 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -0,0 +1,4 @@ +- bump: minor + changes: + added: + - Illinois I-PASS Assist program eligibility. diff --git a/policyengine_us/parameters/gov/states/il/tollway/ipass_assist/eligibility/fpg_limit.yaml b/policyengine_us/parameters/gov/states/il/tollway/ipass_assist/eligibility/fpg_limit.yaml new file mode 100644 index 00000000000..fc98f557557 --- /dev/null +++ b/policyengine_us/parameters/gov/states/il/tollway/ipass_assist/eligibility/fpg_limit.yaml @@ -0,0 +1,13 @@ +description: Illinois limits income to this share of the federal poverty level under the I-PASS Assist program. +values: + 2021-01-01: 2.5 + +metadata: + unit: /1 + period: year + label: Illinois I-PASS Assist income limit as share of FPL + reference: + - title: Illinois Tollway I-PASS Assist - 250% of Federal Poverty Guidelines + href: https://agency.illinoistollway.com/assist + - title: Illinois Tollway Expansion Announcement (June 2021) - 250% FPG threshold + href: https://agency.illinoistollway.com/-/illinois-tollway-significantly-expands-i-pass-assist-program-1 diff --git a/policyengine_us/tests/policy/baseline/gov/states/il/tollway/ipass_assist/il_ipass_assist_categorical_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/il/tollway/ipass_assist/il_ipass_assist_categorical_eligible.yaml new file mode 100644 index 00000000000..88d02b0a1df --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/il/tollway/ipass_assist/il_ipass_assist_categorical_eligible.yaml @@ -0,0 +1,20 @@ +# Illinois I-PASS Assist categorical eligibility tests +# Formula: snap > 0 +# +# Source: https://www.dhs.state.il.us/page.aspx?item=150431 + +- name: Case 1, has SNAP. + period: 2025 + input: + state_code: IL + snap: 1 + output: + il_ipass_assist_categorical_eligible: true + +- name: Case 2, no SNAP. + period: 2025 + input: + state_code: IL + snap: 0 + output: + il_ipass_assist_categorical_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/il/tollway/ipass_assist/il_ipass_assist_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/il/tollway/ipass_assist/il_ipass_assist_eligible.yaml new file mode 100644 index 00000000000..6f724269def --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/il/tollway/ipass_assist/il_ipass_assist_eligible.yaml @@ -0,0 +1,49 @@ +# Illinois I-PASS Assist eligibility unit tests +# Formula: (income_eligible | categorical_eligible) with defined_for = StateCode.IL +# +# Source: https://agency.illinoistollway.com/assist + +- name: Case 1, income eligible only. + period: 2025 + input: + state_code: IL + il_ipass_assist_income_eligible: true + il_ipass_assist_categorical_eligible: false + output: + il_ipass_assist_eligible: true + +- name: Case 2, categorical eligible only. + period: 2025 + input: + state_code: IL + il_ipass_assist_income_eligible: false + il_ipass_assist_categorical_eligible: true + output: + il_ipass_assist_eligible: true + +- name: Case 3, both pathways. + period: 2025 + input: + state_code: IL + il_ipass_assist_income_eligible: true + il_ipass_assist_categorical_eligible: true + output: + il_ipass_assist_eligible: true + +- name: Case 4, neither pathway. + period: 2025 + input: + state_code: IL + il_ipass_assist_income_eligible: false + il_ipass_assist_categorical_eligible: false + output: + il_ipass_assist_eligible: false + +- name: Case 5, non-IL resident. + period: 2025 + input: + state_code: WI + il_ipass_assist_income_eligible: true + il_ipass_assist_categorical_eligible: true + output: + il_ipass_assist_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/il/tollway/ipass_assist/il_ipass_assist_income_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/il/tollway/ipass_assist/il_ipass_assist_income_eligible.yaml new file mode 100644 index 00000000000..ba59907a159 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/il/tollway/ipass_assist/il_ipass_assist_income_eligible.yaml @@ -0,0 +1,302 @@ +# Illinois I-PASS Assist income eligibility tests +# Income limit: 250% of Federal Poverty Guidelines +# +# 2025 FPL for contiguous US (Illinois): +# - 1 person: $15,650 * 2.5 = $39,125 +# - 2 person: $21,150 * 2.5 = $52,875 +# - 3 person: $26,650 * 2.5 = $66,625 +# - 4 person: $32,150 * 2.5 = $80,375 +# - 8 person: $48,650 * 2.5 = $121,625 +# +# Source: https://agency.illinoistollway.com/assist + +- name: Case 1, single person below 250% FPL. + period: 2025-01 + input: + state_code: IL + employment_income: 36_000 # $36,000/year < $39,125 limit + output: + il_ipass_assist_income_eligible: true + +- name: Case 2, single person above 250% FPL. + period: 2025-01 + input: + state_code: IL + employment_income: 48_000 # $48,000/year > $39,125 limit + output: + il_ipass_assist_income_eligible: false + +- name: Case 3, single person at exactly 250% FPL. + period: 2025-01 + input: + state_code: IL + employment_income: 39_125 # Exactly at 250% FPL for 1 person + output: + il_ipass_assist_income_eligible: true + +- name: Case 4, family of 4 below 250% FPL. + period: 2025-01 + input: + people: + person1: + age: 35 + employment_income: 48_000 + person2: + age: 32 + employment_income: 24_000 + person3: + age: 10 + person4: + age: 7 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + households: + household: + members: [person1, person2, person3, person4] + state_code: IL + output: + # Total income: $72,000/year < $80,375 limit for 4-person household + il_ipass_assist_income_eligible: true + +- name: Case 5, family of 4 above 250% FPL. + period: 2025-01 + input: + people: + person1: + age: 40 + employment_income: 60_000 + person2: + age: 38 + employment_income: 30_000 + person3: + age: 12 + person4: + age: 8 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + households: + household: + members: [person1, person2, person3, person4] + state_code: IL + output: + # Total income: $90,000/year > $80,375 limit for 4-person household + il_ipass_assist_income_eligible: false + +- name: Case 6, two-person household just below threshold. + period: 2025-01 + input: + people: + person1: + age: 45 + employment_income: 50_000 + person2: + age: 42 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: IL + output: + # Total income: $50,000/year < $52,875 limit for 2-person household + il_ipass_assist_income_eligible: true + +# ============================================================================ +# EDGE CASE TESTS +# ============================================================================ + +- name: Case 7, boundary one dollar above threshold. + # Tests that <= operator works correctly (at threshold+1 should fail) + period: 2025-01 + input: + state_code: IL + employment_income: 39_126 # $39,125 + 1 = one dollar above 250% FPL + output: + # Threshold: $15,650 * 2.5 = $39,125 + # Income $39,126 > $39,125 -> NOT eligible + il_ipass_assist_income_eligible: false + +- name: Case 8, zero income household. + # Tests minimum bound - should always be eligible + period: 2025-01 + input: + state_code: IL + employment_income: 0 + output: + # $0 <= $39,125 -> eligible + il_ipass_assist_income_eligible: true + +- name: Case 9, very large household 8 members at threshold. + # Tests FPG scaling with large household at exact boundary + # 2025 FPL for 8 people: $15,650 + 7 * $5,500 = $54,150 + # 250% FPL = $54,150 * 2.5 = $135,375 + period: 2025-01 + input: + people: + person1: + age: 45 + employment_income: 75_000 + person2: + age: 43 + employment_income: 60_375 # Total = $135,375 (exactly at threshold) + person3: + age: 20 + person4: + age: 18 + person5: + age: 15 + person6: + age: 12 + person7: + age: 8 + person8: + age: 5 + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5, person6, person7, person8] + households: + household: + members: [person1, person2, person3, person4, person5, person6, person7, person8] + state_code: IL + output: + # 8-person 250% FPL = $135,375 + # Total income: $75,000 + $60,375 = $135,375 (exactly at threshold) + il_ipass_assist_income_eligible: true + +- name: Case 10, very large household one dollar over. + # Tests boundary for large household + # 2025 FPL for 8 people: $15,650 + 7 * $5,500 = $54,150 + # 250% FPL = $54,150 * 2.5 = $135,375 + period: 2025-01 + input: + people: + person1: + age: 45 + employment_income: 75_000 + person2: + age: 43 + employment_income: 60_376 # Total = $135,376 (one dollar over) + person3: + age: 20 + person4: + age: 18 + person5: + age: 15 + person6: + age: 12 + person7: + age: 8 + person8: + age: 5 + spm_units: + spm_unit: + members: [person1, person2, person3, person4, person5, person6, person7, person8] + households: + household: + members: [person1, person2, person3, person4, person5, person6, person7, person8] + state_code: IL + output: + # 8-person 250% FPL = $135,375 + # Total income: $75,000 + $60,376 = $135,376 > $135,375 + il_ipass_assist_income_eligible: false + +- name: Case 11, single person with non-employment income. + # Tests that net income includes various sources + period: 2025-01 + input: + people: + person1: + age: 68 + social_security: 24_000 + interest_income: 5_000 + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: IL + output: + # Total income: $24,000 + $5,000 = $29,000 + # 250% FPL for 1 person: $39,125 + # $29,000 < $39,125 -> eligible + il_ipass_assist_income_eligible: true + +- name: Case 12, two-person at exact threshold. + # Tests boundary for 2-person household + period: 2025-01 + input: + people: + person1: + age: 55 + employment_income: 52_875 # Exactly at 2-person 250% FPL + person2: + age: 52 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: IL + output: + # 2-person 250% FPL = $21,150 * 2.5 = $52,875 + # Income $52,875 <= $52,875 -> eligible + il_ipass_assist_income_eligible: true + +- name: Case 13, two-person one dollar above threshold. + period: 2025-01 + input: + people: + person1: + age: 55 + employment_income: 52_876 # One dollar above 2-person 250% FPL + person2: + age: 52 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: IL + output: + # 2-person 250% FPL = $52,875 + # Income $52,876 > $52,875 -> NOT eligible + il_ipass_assist_income_eligible: false + +- name: Case 14, extremely high income. + # Tests behavior with very large income values + period: 2025-01 + input: + state_code: IL + employment_income: 1_000_000 # $1 million income + output: + # Obviously above 250% FPL ($39,125) + il_ipass_assist_income_eligible: false + +- name: Case 15, three-person at exact threshold. + period: 2025-01 + input: + people: + person1: + age: 40 + employment_income: 66_625 # Exactly at 3-person 250% FPL + person2: + age: 38 + person3: + age: 10 + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: IL + output: + # 3-person 250% FPL = $26,650 * 2.5 = $66,625 + # Income $66,625 <= $66,625 -> eligible + il_ipass_assist_income_eligible: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/il/tollway/ipass_assist/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/il/tollway/ipass_assist/integration.yaml new file mode 100644 index 00000000000..5b51e11212c --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/il/tollway/ipass_assist/integration.yaml @@ -0,0 +1,80 @@ +# Illinois I-PASS Assist integration tests +# End-to-end scenarios testing full calculation chain +# +# Eligibility: IL resident AND (income <= 250% FPL OR SNAP recipient) +# +# Source: https://agency.illinoistollway.com/assist + +- name: Case 1, moderate income worker eligible via income. + period: 2025 + input: + state_code: IL + employment_income: 35_000 + output: + # 1-person 250% FPL = $39,125 + # $35,000 < $39,125 -> income eligible + # $35,000 > SNAP limit -> no SNAP + il_ipass_assist_income_eligible: true + il_ipass_assist_categorical_eligible: false + il_ipass_assist_eligible: true + +- name: Case 2, low income family eligible via both pathways. + period: 2025 + input: + people: + person1: + age: 32 + employment_income: 24_000 + person2: + age: 30 + person3: + age: 8 + person4: + age: 5 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + households: + household: + members: [person1, person2, person3, person4] + state_code: IL + output: + # 4-person 250% FPL = $80,375 + # $24,000 < $80,375 -> income eligible + # $24,000 < SNAP limit -> gets SNAP -> categorical eligible + il_ipass_assist_income_eligible: true + il_ipass_assist_categorical_eligible: true + il_ipass_assist_eligible: true + +- name: Case 3, high income not eligible. + period: 2025 + input: + people: + person1: + age: 45 + employment_income: 60_000 + person2: + age: 42 + employment_income: 40_000 + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: IL + output: + # 2-person 250% FPL = $52,875 + # $100,000 > $52,875 -> NOT income eligible + # $100,000 > SNAP limit -> no SNAP + il_ipass_assist_income_eligible: false + il_ipass_assist_categorical_eligible: false + il_ipass_assist_eligible: false + +- name: Case 4, non-IL resident not eligible. + period: 2025 + input: + state_code: WI + employment_income: 20_000 + output: + il_ipass_assist_eligible: false diff --git a/policyengine_us/variables/gov/states/il/tollway/ipass_assist/il_ipass_assist_categorical_eligible.py b/policyengine_us/variables/gov/states/il/tollway/ipass_assist/il_ipass_assist_categorical_eligible.py new file mode 100644 index 00000000000..2054c40e370 --- /dev/null +++ b/policyengine_us/variables/gov/states/il/tollway/ipass_assist/il_ipass_assist_categorical_eligible.py @@ -0,0 +1,17 @@ +from policyengine_us.model_api import * + + +class il_ipass_assist_categorical_eligible(Variable): + value_type = bool + entity = SPMUnit + definition_period = YEAR + label = "Illinois I-PASS Assist categorically eligible" + reference = [ + "https://agency.illinoistollway.com/assist", + "https://www.dhs.state.il.us/page.aspx?item=150431", + ] + defined_for = StateCode.IL + + def formula(spm_unit, period, parameters): + # Per IL DHS: "Customers getting SNAP may be eligible for I-PASS Assist" + return spm_unit("snap", period) > 0 diff --git a/policyengine_us/variables/gov/states/il/tollway/ipass_assist/il_ipass_assist_eligible.py b/policyengine_us/variables/gov/states/il/tollway/ipass_assist/il_ipass_assist_eligible.py new file mode 100644 index 00000000000..31d95da76e1 --- /dev/null +++ b/policyengine_us/variables/gov/states/il/tollway/ipass_assist/il_ipass_assist_eligible.py @@ -0,0 +1,20 @@ +from policyengine_us.model_api import * + + +class il_ipass_assist_eligible(Variable): + value_type = bool + entity = SPMUnit + definition_period = YEAR + label = "Illinois I-PASS Assist eligible" + reference = [ + "https://agency.illinoistollway.com/assist", + "https://www.dhs.state.il.us/page.aspx?item=150431", + ] + defined_for = StateCode.IL + + def formula(spm_unit, period, parameters): + income_eligible = spm_unit("il_ipass_assist_income_eligible", period) + categorical_eligible = spm_unit( + "il_ipass_assist_categorical_eligible", period + ) + return income_eligible | categorical_eligible diff --git a/policyengine_us/variables/gov/states/il/tollway/ipass_assist/il_ipass_assist_income_eligible.py b/policyengine_us/variables/gov/states/il/tollway/ipass_assist/il_ipass_assist_income_eligible.py new file mode 100644 index 00000000000..60b27458568 --- /dev/null +++ b/policyengine_us/variables/gov/states/il/tollway/ipass_assist/il_ipass_assist_income_eligible.py @@ -0,0 +1,20 @@ +from policyengine_us.model_api import * + + +class il_ipass_assist_income_eligible(Variable): + value_type = bool + entity = SPMUnit + definition_period = YEAR + label = "Illinois I-PASS Assist income eligible" + reference = [ + "https://agency.illinoistollway.com/assist", + "https://www.dhs.state.il.us/page.aspx?item=150431", + ] + defined_for = StateCode.IL + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.il.tollway.ipass_assist.eligibility + fpg = spm_unit("spm_unit_fpg", period) + # Income verified by Illinois Department of Revenue + gross_income = add(spm_unit, period, ["irs_gross_income"]) + return gross_income <= fpg * p.fpg_limit