From bebeeeb0dd3aba00b7495d9e8d9244d2f4be3e14 Mon Sep 17 00:00:00 2001 From: Ziming Date: Thu, 1 Oct 2026 16:13:59 -0400 Subject: [PATCH 1/7] Add New York HEAP regular heating assistance --- changelog.d/ny-heap.added.md | 1 + .../ny/otda/heap/eligibility/fpg_rate.yaml | 12 ++ .../otda/heap/eligibility/fpg_year_lag.yaml | 10 ++ .../ny/otda/heap/eligibility/smi_rate.yaml | 10 ++ .../otda/heap/income/dependent_child_age.yaml | 10 ++ .../ny/otda/heap/income/sources/earned.yaml | 15 +++ .../ny/otda/heap/income/sources/unearned.yaml | 23 ++++ .../gov/states/ny/otda/heap/payment/base.yaml | 32 ++++++ .../ny/otda/heap/payment/elderly_age.yaml | 10 ++ .../heap/payment/heat_included/nominal.yaml | 10 ++ .../heap/payment/heat_included/tier_one.yaml | 10 ++ .../heap/payment/heat_included/tier_two.yaml | 10 ++ .../otda/heap/payment/tier_one_fpg_rate.yaml | 10 ++ .../heap/payment/tier_one_supplement.yaml | 10 ++ .../heap/payment/vulnerable_supplement.yaml | 10 ++ .../ny/otda/heap/payment/young_child_age.yaml | 10 ++ policyengine_us/programs.yaml | 10 +- .../ny_heap_categorically_eligible.yaml | 20 ++++ .../eligibility/ny_heap_income_limit.yaml | 14 +++ .../ny_heap_countable_earned_income.yaml | 14 +++ .../heap/income/ny_heap_countable_income.yaml | 25 +++++ .../ny/otda/heap/income/ny_heap_fpg.yaml | 9 ++ .../gov/states/ny/otda/heap/integration.yaml | 78 +++++++++++++ .../gov/states/ny/otda/heap/ny_heap.yaml | 104 ++++++++++++++++++ .../otda/heap/payment/ny_heap_tier_one.yaml | 17 +++ .../ny_heap_categorically_eligible.py | 21 ++++ .../otda/heap/eligibility/ny_heap_eligible.py | 32 ++++++ .../eligibility/ny_heap_household_size.py | 13 +++ .../heap/eligibility/ny_heap_income_limit.py | 34 ++++++ .../income/ny_heap_countable_earned_income.py | 24 ++++ .../heap/income/ny_heap_countable_income.py | 28 +++++ .../states/ny/otda/heap/income/ny_heap_fpg.py | 21 ++++ .../gov/states/ny/otda/heap/ny_heap.py | 44 ++++++++ .../ny/otda/heap/ny_heap_dwelling_type.py | 20 ++++ .../ny/otda/heap/payment/ny_heap_tier_one.py | 25 +++++ .../otda/heap/payment/ny_heap_vulnerable.py | 25 +++++ 36 files changed, 770 insertions(+), 1 deletion(-) create mode 100644 changelog.d/ny-heap.added.md create mode 100644 policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/fpg_rate.yaml create mode 100644 policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/fpg_year_lag.yaml create mode 100644 policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/smi_rate.yaml create mode 100644 policyengine_us/parameters/gov/states/ny/otda/heap/income/dependent_child_age.yaml create mode 100644 policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/earned.yaml create mode 100644 policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/unearned.yaml create mode 100644 policyengine_us/parameters/gov/states/ny/otda/heap/payment/base.yaml create mode 100644 policyengine_us/parameters/gov/states/ny/otda/heap/payment/elderly_age.yaml create mode 100644 policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/nominal.yaml create mode 100644 policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/tier_one.yaml create mode 100644 policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/tier_two.yaml create mode 100644 policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_fpg_rate.yaml create mode 100644 policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_supplement.yaml create mode 100644 policyengine_us/parameters/gov/states/ny/otda/heap/payment/vulnerable_supplement.yaml create mode 100644 policyengine_us/parameters/gov/states/ny/otda/heap/payment/young_child_age.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_income_limit.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_earned_income.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_income.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_fpg.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/integration.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/ny_heap.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/payment/ny_heap_tier_one.yaml create mode 100644 policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.py create mode 100644 policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_eligible.py create mode 100644 policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.py create mode 100644 policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_income_limit.py create mode 100644 policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_earned_income.py create mode 100644 policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_income.py create mode 100644 policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_fpg.py create mode 100644 policyengine_us/variables/gov/states/ny/otda/heap/ny_heap.py create mode 100644 policyengine_us/variables/gov/states/ny/otda/heap/ny_heap_dwelling_type.py create mode 100644 policyengine_us/variables/gov/states/ny/otda/heap/payment/ny_heap_tier_one.py create mode 100644 policyengine_us/variables/gov/states/ny/otda/heap/payment/ny_heap_vulnerable.py diff --git a/changelog.d/ny-heap.added.md b/changelog.d/ny-heap.added.md new file mode 100644 index 00000000000..b008ee71556 --- /dev/null +++ b/changelog.d/ny-heap.added.md @@ -0,0 +1 @@ +Add New York HEAP regular heating assistance for FY2026. diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/fpg_rate.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/fpg_rate.yaml new file mode 100644 index 00000000000..a99ef3c1dfb --- /dev/null +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/fpg_rate.yaml @@ -0,0 +1,12 @@ +description: New York uses this poverty guideline rate when it exceeds the state median income limit under the Home Energy Assistance Program. +values: + '2025-10-01': 1.5 +metadata: + unit: /1 + period: year + label: New York HEAP poverty guideline rate + reference: + - title: HEAP Manual, Chapter 8 D.9(c) + href: https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=37 + - title: FY2026 LIHEAP State Plan, section 2.1 + href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9 diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/fpg_year_lag.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/fpg_year_lag.yaml new file mode 100644 index 00000000000..12736ca4f01 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/fpg_year_lag.yaml @@ -0,0 +1,10 @@ +description: New York uses the poverty guidelines from this many years before the model year under the Home Energy Assistance Program. +values: + '2025-10-01': 1 +metadata: + unit: year + period: year + label: New York HEAP poverty guideline year lag + reference: + - title: 2025-2026 HEAP monthly income guidelines + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/NY_BenefitMatrix_2026.docx diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/smi_rate.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/smi_rate.yaml new file mode 100644 index 00000000000..ff9f6e1668b --- /dev/null +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/smi_rate.yaml @@ -0,0 +1,10 @@ +description: New York limits noncategorical income using this share of state median income under the Home Energy Assistance Program. +values: + '2025-10-01': 0.6 +metadata: + unit: /1 + period: year + label: New York HEAP SMI rate + reference: + - title: FY2026 LIHEAP State Plan, regular heating assistance + href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9,10 diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/income/dependent_child_age.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/income/dependent_child_age.yaml new file mode 100644 index 00000000000..1410e01ef81 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/income/dependent_child_age.yaml @@ -0,0 +1,10 @@ +description: New York excludes earnings of dependent children below this age under the Home Energy Assistance Program. +values: + '2025-10-01': 18 +metadata: + unit: year + period: year + label: New York HEAP dependent child age + reference: + - title: HEAP Manual, Chapter 8 D.13(4)-(5) + href: https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=39 diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/earned.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/earned.yaml new file mode 100644 index 00000000000..b094c6906fe --- /dev/null +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/earned.yaml @@ -0,0 +1,15 @@ +description: New York uses these reported earnings inputs without an additional business expense deduction under the Home Energy Assistance Program. +values: + '2025-10-01': + - employment_income + - self_employment_income + - sstb_self_employment_income + - farm_operations_income + - partnership_s_corp_income +metadata: + unit: list + period: year + label: New York HEAP earned sources + reference: + - title: HEAP Manual, Chapter 8 D.12 and D.14 + href: https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=37,38,42,43 diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/unearned.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/unearned.yaml new file mode 100644 index 00000000000..f48ef932f9b --- /dev/null +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/unearned.yaml @@ -0,0 +1,23 @@ +description: New York counts these available unearned income sources under the Home Energy Assistance Program. +values: + '2025-10-01': + - ssi + - unemployment_compensation + - strike_benefits + - workers_compensation + - disability_benefits + - veterans_benefits + - pension_income + - alimony_income + - child_support_received + - dividend_income + - interest_income + - rental_income + - estate_income +metadata: + unit: list + period: year + label: New York HEAP unearned sources + reference: + - title: HEAP Manual, Chapter 8 D.12-D.14 + href: https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=37,38,39,40,41,42,43 diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/base.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/base.yaml new file mode 100644 index 00000000000..bd8cfb7612b --- /dev/null +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/base.yaml @@ -0,0 +1,32 @@ +description: New York provides this base annual direct heating payment under the Home Energy Assistance Program, by primary heating fuel. +ELECTRICITY: + '2025-10-01': 400 +NATURAL_GAS: + '2025-10-01': 400 +FUEL_OIL: + '2025-10-01': 900 +KEROSENE: + '2025-10-01': 900 +PROPANE: + '2025-10-01': 900 +WOOD: + '2025-10-01': 635 +COAL: + '2025-10-01': 635 +OTHER: + '2025-10-01': 635 +SOLAR: + '2025-10-01': 635 +UNSPECIFIED: + '2025-10-01': 0 +NONE: + '2025-10-01': 0 +metadata: + unit: currency-USD + period: year + label: New York HEAP direct heating base payment + breakdown: + - heating_type + reference: + - title: 2025-2026 HEAP regular benefit levels, other fuels include fuels outside the named utility and delivered-fuel groups + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/NY_BenefitMatrix_2026.docx diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/elderly_age.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/elderly_age.yaml new file mode 100644 index 00000000000..e88ebc911d6 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/elderly_age.yaml @@ -0,0 +1,10 @@ +description: New York treats people at or above this age as vulnerable under the Home Energy Assistance Program. +values: + '2025-10-01': 60 +metadata: + unit: year + period: year + label: New York HEAP elderly age + reference: + - title: FY2026 LIHEAP State Plan, regular heating assistance + href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9,10 diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/nominal.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/nominal.yaml new file mode 100644 index 00000000000..1916375278d --- /dev/null +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/nominal.yaml @@ -0,0 +1,10 @@ +description: New York pays this regular amount to specified group residences and subsidized households with heat included under the Home Energy Assistance Program. +values: + '2025-10-01': 21 +metadata: + unit: currency-USD + period: year + label: New York HEAP nominal regular payment + reference: + - title: 2025-2026 HEAP regular benefit levels + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/NY_BenefitMatrix_2026.docx diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/tier_one.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/tier_one.yaml new file mode 100644 index 00000000000..5831b538e9e --- /dev/null +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/tier_one.yaml @@ -0,0 +1,10 @@ +description: New York pays this regular amount to Tier I households with heat included in unsubsidized rent under the Home Energy Assistance Program. +values: + '2025-10-01': 50 +metadata: + unit: currency-USD + period: year + label: New York HEAP Tier I heat-included payment + reference: + - title: 2025-2026 HEAP regular benefit levels + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/NY_BenefitMatrix_2026.docx diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/tier_two.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/tier_two.yaml new file mode 100644 index 00000000000..57942aa5bac --- /dev/null +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/tier_two.yaml @@ -0,0 +1,10 @@ +description: New York pays this regular amount to Tier II households with heat included in unsubsidized rent under the Home Energy Assistance Program. +values: + '2025-10-01': 45 +metadata: + unit: currency-USD + period: year + label: New York HEAP Tier II heat-included payment + reference: + - title: 2025-2026 HEAP regular benefit levels + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/NY_BenefitMatrix_2026.docx diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_fpg_rate.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_fpg_rate.yaml new file mode 100644 index 00000000000..41ba0a53ed0 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_fpg_rate.yaml @@ -0,0 +1,10 @@ +description: New York assigns noncategorical households to Tier I at or below this share of the poverty guideline under the Home Energy Assistance Program. +values: + '2025-10-01': 1.3 +metadata: + unit: /1 + period: year + label: New York HEAP Tier I poverty rate + reference: + - title: FY2026 LIHEAP State Plan, regular heating assistance + href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9,10 diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_supplement.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_supplement.yaml new file mode 100644 index 00000000000..632e1fb23d1 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_supplement.yaml @@ -0,0 +1,10 @@ +description: New York adds this amount to direct heating payments for Tier I households under the Home Energy Assistance Program. +values: + '2025-10-01': 61 +metadata: + unit: currency-USD + period: year + label: New York HEAP Tier I supplement + reference: + - title: FY2026 LIHEAP State Plan, regular heating assistance + href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9,10 diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/vulnerable_supplement.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/vulnerable_supplement.yaml new file mode 100644 index 00000000000..c668685b468 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/vulnerable_supplement.yaml @@ -0,0 +1,10 @@ +description: New York adds this amount once per direct heating household with a qualified vulnerable member under the Home Energy Assistance Program. +values: + '2025-10-01': 35 +metadata: + unit: currency-USD + period: year + label: New York HEAP vulnerable supplement + reference: + - title: FY2026 LIHEAP State Plan, regular heating assistance + href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9,10 diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/young_child_age.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/young_child_age.yaml new file mode 100644 index 00000000000..730361573c1 --- /dev/null +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/young_child_age.yaml @@ -0,0 +1,10 @@ +description: New York treats children below this age as vulnerable under the Home Energy Assistance Program. +values: + '2025-10-01': 6 +metadata: + unit: year + period: year + label: New York HEAP young child age + reference: + - title: FY2026 LIHEAP State Plan, regular heating assistance + href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9,10 diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 81c10bc47b6..305406125fe 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -802,8 +802,16 @@ programs: category: Benefits agency: HHS status: partial - coverage: OR, DC, Riverside County, MA, IL, TX + coverage: OR, DC, Riverside County, MA, IL, TX, NY state_implementations: + - state: NY + status: partial + name: New York HEAP + full_name: New York Home Energy Assistance Program + variable: ny_heap + parameter_prefix: gov.states.ny.otda.heap + verified_years: "2026" + notes: Regular heating with categorical eligibility, qualified-member size, full excluded-member income, heating and rent-included payments and Tier I/vulnerability supplements. Annual income and existing net business inputs approximate HEAP accounting; SSI state codes, detailed member exclusions and special income/certification rules remain unsupported. Sizes 14+ follow the stated guideline rule despite an inconsistent published increment. Earlier years are unverified backfilled estimates. - state: OR status: in_progress name: LIHEAP diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.yaml new file mode 100644 index 00000000000..4be6215b273 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.yaml @@ -0,0 +1,20 @@ +- name: SNAP and federally funded TANF each confer categorical eligibility + period: 2026 + input: + state_code: [NY, NY, NY] + snap: [1, 0, 0] + ny_tanf: [0, 1, 0] + ssi: [0, 0, 0] + output: + ny_heap_categorically_eligible: [true, true, false] +- name: SSI own-household federal code approximates Code A + period: 2026 + input: + state_code: NY + snap: 0 + ny_tanf: 0 + ssi: 1200 + ssi_federal_living_arrangement: + 2026-01: OWN_HOUSEHOLD + output: + ny_heap_categorically_eligible: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_income_limit.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_income_limit.yaml new file mode 100644 index 00000000000..b8ee2df31ef --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_income_limit.yaml @@ -0,0 +1,14 @@ +- name: All published size one through thirteen income limits + period: 2026 + input: + state_code: [NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY] + ny_heap_household_size: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13] + output: + ny_heap_income_limit: [41676, 54504, 67332, 80160, 92988, 105816, 108216, 110616, 113028, 115428, 117840, 120240, 122652] +- name: Size fourteen follows the plan poverty rate rather than the inconsistent incremental row + period: 2026 + input: + state_code: NY + ny_heap_household_size: 14 + output: + ny_heap_income_limit: 130716 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_earned_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_earned_income.yaml new file mode 100644 index 00000000000..4c0175c4792 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_earned_income.yaml @@ -0,0 +1,14 @@ +- name: Dependent children and full-time dependent students have exempt earnings + period: 2026 + input: + state_code: [NY, NY, NY, NY] + age: [17, 18, 30, 30] + is_tax_unit_dependent: [true, true, false, false] + is_full_time_student: [false, true, true, false] + employment_income: [12000, 12000, 12000, 12000] + self_employment_income: [1000, 1000, 1000, -1000] + sstb_self_employment_income: [500, 500, 500, 500] + farm_operations_income: [600, 600, 600, 600] + partnership_s_corp_income: [700, 700, 700, 700] + output: + ny_heap_countable_earned_income: [0, 0, 14800, 13800] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_income.yaml new file mode 100644 index 00000000000..7af373c0aed --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_income.yaml @@ -0,0 +1,25 @@ +# 12,001 earnings + 11,400 net SSA/railroad + 4,080 other income = 27,481. +# floor(27,481 / 12) * 12 = 27,480. +- name: Deduct paid Medicare Part B premiums and round monthly household income down + period: 2026 + input: + state_code: NY + ny_heap_countable_earned_income: 12001 + social_security: 12000 + medicare_part_b_premium: 1200 + railroad_benefits: 600 + pension_income: 240 + interest_income: 120 + dividend_income: 240 + rental_income: 360 + alimony_income: 480 + child_support_received: 600 + unemployment_compensation: 720 + strike_benefits: 120 + workers_compensation: 240 + disability_benefits: 360 + veterans_benefits: 480 + estate_income: 120 + ssi: 0 + output: + ny_heap_countable_income: 27480 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_fpg.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_fpg.yaml new file mode 100644 index 00000000000..3a7755959f0 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_fpg.yaml @@ -0,0 +1,9 @@ +- name: Backfilled estimates still use the guideline one year before the requested period + period: 2022 + input: + state_code: NY + ny_heap_household_size: 2 + output: + # 2021 guideline: 12,880 + 4,540. This checks lag mechanics, not verified + # historical New York policy coverage. + ny_heap_fpg: 17_420 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/integration.yaml new file mode 100644 index 00000000000..171c5c484f7 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/integration.yaml @@ -0,0 +1,78 @@ +- name: Excluded adult income counts in full while the citizen child determines size and vulnerability + period: 2026 + input: + people: + parent: + age: 40 + employment_income: 24000 + immigration_status: UNDOCUMENTED + child: + age: 5 + immigration_status: CITIZEN + spm_units: + unit: + members: + - parent + - child + heating_type: NATURAL_GAS + gas_expense: 100 + ny_tanf: 0 + snap: 0 + households: + home: + members: + - parent + - child + state_code: NY + output: + ny_heap_household_size: 1 + ny_heap_countable_income: 24000 + ny_heap_income_limit: 41676 + ny_heap_tier_one: false + ny_heap_vulnerable: true + ny_heap_eligible: true + ny_heap: 435 +- name: No qualified household members means no benefit + period: 2026 + input: + state_code: NY + immigration_status: UNDOCUMENTED + age: 40 + employment_income: 0 + ssi: 0 + snap: 0 + ny_tanf: 0 + has_heating_expense: true + heating_type: NATURAL_GAS + output: + ny_heap_household_size: 0 + ny_heap_eligible: false + ny_heap: 0 +- name: Only a nonqualified elderly member does not confer the vulnerable supplement + period: 2026 + input: + people: + adult: + age: 40 + elder: + age: 70 + immigration_status: UNDOCUMENTED + spm_units: + unit: + members: + - adult + - elder + heating_type: NATURAL_GAS + has_heating_expense: true + ny_heap_countable_income: 24000 + ny_heap_categorically_eligible: false + households: + home: + members: + - adult + - elder + state_code: NY + output: + ny_heap_household_size: 1 + ny_heap_vulnerable: false + ny_heap: 400 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/ny_heap.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/ny_heap.yaml new file mode 100644 index 00000000000..28c2a7e33ff --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/ny_heap.yaml @@ -0,0 +1,104 @@ +- name: Every fuel with Tier I supplement + period: 2026 + input: + state_code: [NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY] + ny_heap_countable_income: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ny_heap_categorically_eligible: [false, false, false, false, false, false, false, false, false, false, false] + has_heating_expense: [true, true, true, true, true, true, true, true, true, true, true] + age: [40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40] + heating_type: [ELECTRICITY, NATURAL_GAS, FUEL_OIL, KEROSENE, PROPANE, WOOD, COAL, OTHER, SOLAR, UNSPECIFIED, NONE] + output: + ny_heap: [461, 461, 961, 961, 961, 696, 696, 696, 696, 0, 0] +- name: Age vulnerability boundaries, Tier I and Tier II + period: 2026 + input: + state_code: [NY, NY, NY, NY, NY, NY] + ny_heap_countable_income: [0, 0, 0, 0, 24000, 24000] + ny_heap_categorically_eligible: [false, false, false, false, false, false] + has_heating_expense: [true, true, true, true, true, true] + age: [5, 6, 59, 60, 40, 40] + heating_type: [FUEL_OIL, FUEL_OIL, FUEL_OIL, FUEL_OIL, FUEL_OIL, FUEL_OIL] + is_permanently_and_totally_disabled: [false, false, false, false, false, true] + output: + ny_heap: [996, 961, 961, 996, 900, 935] +- name: Heat included in rent by tier and housing assistance + period: 2026 + input: + state_code: [NY, NY, NY, NY] + ny_heap_countable_income: [0, 24000, 0, 24000] + ny_heap_categorically_eligible: [false, false, false, false] + has_heating_expense: [false, false, false, false] + age: [40, 40, 40, 40] + heat_expense_included_in_rent: [true, true, true, true] + heating_type: [UNSPECIFIED, UNSPECIFIED, UNSPECIFIED, UNSPECIFIED] + receives_housing_assistance: [false, false, true, true] + output: + ny_heap: [50, 45, 21, 21] +- name: Subsidized direct heaters receive ordinary fuel payments + period: 2026 + input: + state_code: NY + ny_heap_countable_income: 0 + ny_heap_categorically_eligible: false + has_heating_expense: true + age: 40 + heating_type: NATURAL_GAS + receives_housing_assistance: true + output: + ny_heap: 461 +- name: Income eligibility includes the published limit + period: 2026 + input: + state_code: [NY, NY] + ny_heap_countable_income: [41676, 41688] + ny_heap_categorically_eligible: [false, false] + has_heating_expense: [true, true] + age: [40, 40] + heating_type: [ELECTRICITY, ELECTRICITY] + output: + ny_heap: [400, 0] +- name: Regular benefit is not capped at the current heating bill + period: 2026 + input: + state_code: NY + ny_heap_countable_income: 0 + ny_heap_categorically_eligible: false + has_heating_expense: true + age: 40 + heating_type: FUEL_OIL + fuel_oil_expense: 1 + output: + ny_heap: 961 +- name: Households with no heat responsibility receive zero + period: 2026 + input: + state_code: NY + ny_heap_countable_income: 0 + ny_heap_categorically_eligible: false + has_heating_expense: false + age: 40 + heating_type: NATURAL_GAS + output: + ny_heap: 0 +- name: Other states receive no New York payment + period: 2026 + input: + state_code: NJ + ny_heap_countable_income: 0 + ny_heap_categorically_eligible: false + has_heating_expense: true + age: 40 + heating_type: NATURAL_GAS + output: + ny_heap: 0 +- name: Published group and ineligible residence categories + period: 2026 + input: + state_code: [NY, NY] + ny_heap_countable_income: [0, 0] + ny_heap_categorically_eligible: [false, false] + has_heating_expense: [false, false] + age: [40, 40] + ny_heap_dwelling_type: [ELIGIBLE_GROUP_RESIDENCE, INELIGIBLE_RESIDENCE] + output: + ny_heap: [21, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/payment/ny_heap_tier_one.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/payment/ny_heap_tier_one.yaml new file mode 100644 index 00000000000..5580397a2d2 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/payment/ny_heap_tier_one.yaml @@ -0,0 +1,17 @@ +- name: All published Tier I limits are inclusive + period: 2026 + input: + state_code: [NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY] + ny_heap_household_size: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13] + ny_heap_countable_income: [20340, 27492, 34644, 41784, 48936, 56088, 63240, 70392, 77544, 84684, 91836, 98988, 106140, 20352, 27504, 34656, 41796, 48948, 56100, 63252, 70404, 77556, 84696, 91848, 99000, 106152] + ny_heap_categorically_eligible: [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false] + output: + ny_heap_tier_one: [true, true, true, true, true, true, true, true, true, true, true, true, true, false, false, false, false, false, false, false, false, false, false, false, false, false] +- name: Categorical eligibility confers Tier I despite high income + period: 2026 + input: + state_code: NY + ny_heap_countable_income: 200000 + ny_heap_categorically_eligible: true + output: + ny_heap_tier_one: true diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.py b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.py new file mode 100644 index 00000000000..44536340e5a --- /dev/null +++ b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class ny_heap_categorically_eligible(Variable): + value_type = bool + entity = SPMUnit + definition_period = YEAR + label = "New York HEAP categorical income eligibility" + defined_for = StateCode.NY + reference = ( + "https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=5", + "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=37,110", + ) + documentation = "Receipt of SNAP, federally funded NY TANF or SSI in a federal own-household arrangement. State SSI Code A and recurring receipt on the application date are approximated; expedited/emergency-only benefits and roomer-only awards cannot be distinguished. Safety Net Assistance is not treated as federally funded TANF." + + def formula(spm_unit, period, parameters): + person = spm_unit.members + arrangement = person("ssi_federal_living_arrangement", period.first_month) + code_a = arrangement == arrangement.possible_values.OWN_HOUSEHOLD + qualifying_ssi = spm_unit.any((person("ssi", period) > 0) & code_a) + return (add(spm_unit, period, ["snap", "ny_tanf"]) > 0) | qualifying_ssi diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_eligible.py b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_eligible.py new file mode 100644 index 00000000000..affce5a2238 --- /dev/null +++ b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_eligible.py @@ -0,0 +1,32 @@ +from policyengine_us.model_api import * + + +class ny_heap_eligible(Variable): + value_type = bool + entity = SPMUnit + definition_period = YEAR + label = "New York HEAP regular heating eligibility" + defined_for = StateCode.NY + reference = ( + "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=34,37,44,45,46,47,48", + ) + documentation = "Regular heating has no resource test. SPM units approximate energy-sharing households. Application timing, SSN documentation, duplicate payments and the full set of excluded member categories are not modeled." + + def formula(spm_unit, period, parameters): + dwelling = spm_unit("ny_heap_dwelling_type", period) + types = dwelling.possible_values + heat = ( + spm_unit("has_heating_expense", period) + | spm_unit("heat_expense_included_in_rent", period) + | (dwelling == types.ELIGIBLE_GROUP_RESIDENCE) + ) + income_eligible = spm_unit("ny_heap_categorically_eligible", period) | ( + spm_unit("ny_heap_countable_income", period) + <= spm_unit("ny_heap_income_limit", period) + ) + return ( + heat + & income_eligible + & (spm_unit("ny_heap_household_size", period) > 0) + & (dwelling != types.INELIGIBLE_RESIDENCE) + ) diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.py b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.py new file mode 100644 index 00000000000..4eb1fc21278 --- /dev/null +++ b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.py @@ -0,0 +1,13 @@ +from policyengine_us.model_api import * + + +class ny_heap_household_size(Variable): + value_type = int + entity = SPMUnit + definition_period = YEAR + label = "New York HEAP qualified household size" + defined_for = StateCode.NY + reference = ("https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=34,37,44",) + documentation = "SPM members who are citizens or federally qualified noncitizens. Their excluded members' income still counts in full. Foster members, SSI Code C, roomers, employees and fleeing felons cannot be separately identified. The existing immigration enum does not separately identify every additional federally protected status." + + adds = ["is_citizen_or_legal_immigrant"] diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_income_limit.py b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_income_limit.py new file mode 100644 index 00000000000..e7f71d4756f --- /dev/null +++ b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_income_limit.py @@ -0,0 +1,34 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.hhs_smi import smi + + +class ny_heap_income_limit(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + label = "New York HEAP annualized income limit" + unit = USD + defined_for = StateCode.NY + reference = ( + "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=37", + "https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9", + "https://liheapch.acf.gov/docs/2026/benefits-matricies/NY_BenefitMatrix_2026.docx", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ny.otda.heap.eligibility + size = max_(spm_unit("ny_heap_household_size", period), 1) + state = spm_unit.household("state_code_str", period) + base = parameters(period).gov.hhs.smi.amount[state] + # Inferred ordering matching every published size 1-13 limit: + # floor the four-person 60% amount before the federal size adjustment. + adjusted_smi = smi(size, state, period, parameters) / base + monthly_smi = np.floor( + adjusted_smi * np.floor(base * p.smi_rate) / MONTHS_IN_YEAR + ) + monthly_fpg = np.floor( + spm_unit("ny_heap_fpg", period) * p.fpg_rate / MONTHS_IN_YEAR + ) + # Chapter 8 D.9(c) and the FY2026 plan control sizes 14+, where the + # matrix's additional-person shortcut differs from the stated FPG rate. + return max_(monthly_smi, monthly_fpg) * MONTHS_IN_YEAR diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_earned_income.py b/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_earned_income.py new file mode 100644 index 00000000000..d8c640346b0 --- /dev/null +++ b/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_earned_income.py @@ -0,0 +1,24 @@ +from policyengine_us.model_api import * + + +class ny_heap_countable_earned_income(Variable): + value_type = float + entity = Person + definition_period = YEAR + label = "New York HEAP countable earnings" + unit = USD + defined_for = StateCode.NY + reference = ("https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=37,39,42,43",) + documentation = "Existing net business income is used without further deductions. HEAP-specific depreciation add-backs, separate accounting periods and wage bonus exclusions are unsupported." + + def formula(person, period, parameters): + p = parameters(period).gov.states.ny.otda.heap.income + earnings = 0 + for source in p.sources.earned: + earnings = earnings + max_(person(source, period), 0) + dependent = person("is_tax_unit_dependent", period) + exempt = dependent & ( + (person("age", period) < p.dependent_child_age) + | person("is_full_time_student", period) + ) + return where(exempt, 0, earnings) diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_income.py b/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_income.py new file mode 100644 index 00000000000..18b579c5e3a --- /dev/null +++ b/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_income.py @@ -0,0 +1,28 @@ +from policyengine_us.model_api import * + + +class ny_heap_countable_income(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + label = "New York HEAP annualized countable household income" + unit = USD + defined_for = StateCode.NY + reference = ( + "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=37,38,39,40,41,42,43,44", + ) + documentation = "Annual inputs approximate application-month income; final monthly income is rounded down and annualized. Income of nonqualified members counts in full. Medicare Part D premiums, royalties, regular gifts, aid-and-attendance exclusions and irregular-income exclusions have no dedicated inputs. Rental income is assumed nonnegative. No new self-employment or work-expense deduction is applied." + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ny.otda.heap.income + person = spm_unit.members + retirement = add(person, period, ["social_security", "railroad_benefits"]) + net_retirement = spm_unit.sum( + max_(retirement - person("medicare_part_b_premium", period), 0) + ) + income = ( + add(spm_unit, period, ["ny_heap_countable_earned_income"]) + + add(spm_unit, period, p.sources.unearned) + + net_retirement + ) + return np.floor(max_(income, 0) / MONTHS_IN_YEAR) * MONTHS_IN_YEAR diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_fpg.py b/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_fpg.py new file mode 100644 index 00000000000..c909d07e03b --- /dev/null +++ b/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_fpg.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg + + +class ny_heap_fpg(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + label = "New York HEAP applicable federal poverty guideline" + unit = USD + defined_for = StateCode.NY + reference = ( + "https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9,10", + "https://liheapch.acf.gov/docs/2026/benefits-matricies/NY_BenefitMatrix_2026.docx", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ny.otda.heap.eligibility + size = max_(spm_unit("ny_heap_household_size", period), 1) + state_group = spm_unit.household("state_group_str", period) + return fpg(size, state_group, period, parameters, year_lag=p.fpg_year_lag) diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/ny_heap.py b/policyengine_us/variables/gov/states/ny/otda/heap/ny_heap.py new file mode 100644 index 00000000000..8fafd15853c --- /dev/null +++ b/policyengine_us/variables/gov/states/ny/otda/heap/ny_heap.py @@ -0,0 +1,44 @@ +from policyengine_us.model_api import * + + +class ny_heap(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + label = "New York HEAP regular heating benefit" + unit = USD + defined_for = "ny_heap_eligible" + reference = ( + "https://liheapch.acf.gov/docs/2026/benefits-matricies/NY_BenefitMatrix_2026.docx", + "https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=5,9,10", + "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=45,46,47,48,49,84,87,88", + ) + documentation = "Annual regular heating benefit and ordinary Tier I/vulnerability supplements, verified for FY2026. Earlier years are unverified backfilled estimates. Housing assistance approximates subsidized rent (market-rent voucher exceptions are unsupported). Unknown direct-heating fuel returns zero; solar falls within other fuels. No bill cap. Crisis, cooling and equipment components are excluded. Mid-year moves and previously advanced nominal payments are not separately tracked." + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ny.otda.heap.payment + tier_one = spm_unit("ny_heap_tier_one", period) + fuel = spm_unit("heating_type", period) + base = p.base[fuel] + direct = where( + base > 0, + base + + tier_one * p.tier_one_supplement + + spm_unit("ny_heap_vulnerable", period) * p.vulnerable_supplement, + 0, + ) + subsidized = spm_unit( + "receives_housing_assistance", period + ) | spm_unit.household("is_in_public_housing", period) + dwelling = spm_unit("ny_heap_dwelling_type", period) + group = dwelling == dwelling.possible_values.ELIGIBLE_GROUP_RESIDENCE + heat_included = where( + subsidized | group, + p.heat_included.nominal, + where(tier_one, p.heat_included.tier_one, p.heat_included.tier_two), + ) + return where( + spm_unit("heat_expense_included_in_rent", period) | group, + heat_included, + direct, + ) diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/ny_heap_dwelling_type.py b/policyengine_us/variables/gov/states/ny/otda/heap/ny_heap_dwelling_type.py new file mode 100644 index 00000000000..cbc3b3ffd26 --- /dev/null +++ b/policyengine_us/variables/gov/states/ny/otda/heap/ny_heap_dwelling_type.py @@ -0,0 +1,20 @@ +from policyengine_us.model_api import * + + +class NYHEAPDwellingType(Enum): + STANDARD = "Standard residence" + ELIGIBLE_GROUP_RESIDENCE = ( + "HEAP-eligible group residence receiving the nominal payment" + ) + INELIGIBLE_RESIDENCE = "HEAP-ineligible residence" + + +class ny_heap_dwelling_type(Variable): + value_type = Enum + possible_values = NYHEAPDwellingType + default_value = NYHEAPDwellingType.STANDARD + entity = SPMUnit + definition_period = YEAR + label = "New York HEAP dwelling arrangement" + reference = "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=45,46,47" + documentation = "Eligible group residences are the treatment, enriched housing, supervised/supportive living and other settings listed in Chapter 8 F.3 (group living facilities must have at most 16 residents). Ineligible residences include private roomers/boarders, hotels, motels, vehicles, dormitories and congregate care under F.4. Ordinary subsidized housing uses the existing housing-assistance and heat-in-rent inputs." diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/payment/ny_heap_tier_one.py b/policyengine_us/variables/gov/states/ny/otda/heap/payment/ny_heap_tier_one.py new file mode 100644 index 00000000000..ae1cd904e06 --- /dev/null +++ b/policyengine_us/variables/gov/states/ny/otda/heap/payment/ny_heap_tier_one.py @@ -0,0 +1,25 @@ +from policyengine_us.model_api import * + + +class ny_heap_tier_one(Variable): + value_type = bool + entity = SPMUnit + definition_period = YEAR + label = "New York HEAP Tier I status" + defined_for = StateCode.NY + reference = ( + "https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=10", + "https://liheapch.acf.gov/docs/2026/benefits-matricies/NY_BenefitMatrix_2026.docx", + ) + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ny.otda.heap.payment + threshold = ( + np.floor( + spm_unit("ny_heap_fpg", period) * p.tier_one_fpg_rate / MONTHS_IN_YEAR + ) + * MONTHS_IN_YEAR + ) + return spm_unit("ny_heap_categorically_eligible", period) | ( + spm_unit("ny_heap_countable_income", period) <= threshold + ) diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/payment/ny_heap_vulnerable.py b/policyengine_us/variables/gov/states/ny/otda/heap/payment/ny_heap_vulnerable.py new file mode 100644 index 00000000000..723ec364f30 --- /dev/null +++ b/policyengine_us/variables/gov/states/ny/otda/heap/payment/ny_heap_vulnerable.py @@ -0,0 +1,25 @@ +from policyengine_us.model_api import * + + +class ny_heap_vulnerable(Variable): + value_type = bool + entity = SPMUnit + definition_period = YEAR + label = "New York HEAP qualified vulnerable member" + defined_for = StateCode.NY + reference = ("https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=44,45,49",) + documentation = "Permanent-disability input and SSI/SSDI receipt approximate HEAP disability certification. Detailed VA, railroad, FECA and disability-based Medicaid certification pathways are not separately identified." + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.ny.otda.heap.payment + person = spm_unit.members + age = person("age", period) + disabled = ( + person("is_permanently_and_totally_disabled", period) + | (person("ssi", period) > 0) + | (person("social_security_disability", period) > 0) + ) + vulnerable = (age < p.young_child_age) | (age >= p.elderly_age) | disabled + return spm_unit.any( + vulnerable & person("is_citizen_or_legal_immigrant", period) + ) From 0c02245bef02d7071e112a3f3b0624416570ea22 Mon Sep 17 00:00:00 2001 From: Ziming Date: Thu, 1 Oct 2026 16:24:40 -0400 Subject: [PATCH 2/7] Make New York heating test inputs explicit and count retirement distributions --- .../gov/states/ny/otda/heap/income/sources/unearned.yaml | 1 + .../heap/eligibility/ny_heap_categorically_eligible.yaml | 2 ++ .../ny/otda/heap/eligibility/ny_heap_income_limit.yaml | 2 ++ .../otda/heap/income/ny_heap_countable_earned_income.yaml | 1 + .../ny/otda/heap/income/ny_heap_countable_income.yaml | 8 +++++--- .../gov/states/ny/otda/heap/income/ny_heap_fpg.yaml | 1 + .../policy/baseline/gov/states/ny/otda/heap/ny_heap.yaml | 1 + .../gov/states/ny/otda/heap/payment/ny_heap_tier_one.yaml | 2 ++ 8 files changed, 15 insertions(+), 3 deletions(-) diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/unearned.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/unearned.yaml index f48ef932f9b..d7e4014fae7 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/unearned.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/unearned.yaml @@ -8,6 +8,7 @@ values: - disability_benefits - veterans_benefits - pension_income + - retirement_distributions - alimony_income - child_support_received - dividend_income diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.yaml index 4be6215b273..cda3b2b46c8 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.yaml @@ -1,6 +1,7 @@ - name: SNAP and federally funded TANF each confer categorical eligibility period: 2026 input: + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] state_code: [NY, NY, NY] snap: [1, 0, 0] ny_tanf: [0, 1, 0] @@ -10,6 +11,7 @@ - name: SSI own-household federal code approximates Code A period: 2026 input: + heating_type: NATURAL_GAS state_code: NY snap: 0 ny_tanf: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_income_limit.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_income_limit.yaml index b8ee2df31ef..bdbce41ae29 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_income_limit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_income_limit.yaml @@ -1,6 +1,7 @@ - name: All published size one through thirteen income limits period: 2026 input: + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] state_code: [NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY] ny_heap_household_size: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13] output: @@ -8,6 +9,7 @@ - name: Size fourteen follows the plan poverty rate rather than the inconsistent incremental row period: 2026 input: + heating_type: NATURAL_GAS state_code: NY ny_heap_household_size: 14 output: diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_earned_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_earned_income.yaml index 4c0175c4792..ed649f18c33 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_earned_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_earned_income.yaml @@ -1,6 +1,7 @@ - name: Dependent children and full-time dependent students have exempt earnings period: 2026 input: + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] state_code: [NY, NY, NY, NY] age: [17, 18, 30, 30] is_tax_unit_dependent: [true, true, false, false] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_income.yaml index 7af373c0aed..e8dded9971a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_income.yaml @@ -1,14 +1,16 @@ -# 12,001 earnings + 11,400 net SSA/railroad + 4,080 other income = 27,481. -# floor(27,481 / 12) * 12 = 27,480. +# 12,001 earnings + 11,400 net SSA/railroad + 4,440 other income = 27,841. +# floor(27,841 / 12) * 12 = 27,840. - name: Deduct paid Medicare Part B premiums and round monthly household income down period: 2026 input: + heating_type: NATURAL_GAS state_code: NY ny_heap_countable_earned_income: 12001 social_security: 12000 medicare_part_b_premium: 1200 railroad_benefits: 600 pension_income: 240 + retirement_distributions: 360 interest_income: 120 dividend_income: 240 rental_income: 360 @@ -22,4 +24,4 @@ estate_income: 120 ssi: 0 output: - ny_heap_countable_income: 27480 + ny_heap_countable_income: 27840 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_fpg.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_fpg.yaml index 3a7755959f0..b507506a2ee 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_fpg.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_fpg.yaml @@ -1,6 +1,7 @@ - name: Backfilled estimates still use the guideline one year before the requested period period: 2022 input: + heating_type: NATURAL_GAS state_code: NY ny_heap_household_size: 2 output: diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/ny_heap.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/ny_heap.yaml index 28c2a7e33ff..5869ff49554 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/ny_heap.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/ny_heap.yaml @@ -94,6 +94,7 @@ - name: Published group and ineligible residence categories period: 2026 input: + heating_type: [NATURAL_GAS, NATURAL_GAS] state_code: [NY, NY] ny_heap_countable_income: [0, 0] ny_heap_categorically_eligible: [false, false] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/payment/ny_heap_tier_one.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/payment/ny_heap_tier_one.yaml index 5580397a2d2..a2aeeddae78 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/payment/ny_heap_tier_one.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/payment/ny_heap_tier_one.yaml @@ -1,6 +1,7 @@ - name: All published Tier I limits are inclusive period: 2026 input: + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] state_code: [NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY] ny_heap_household_size: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13] ny_heap_countable_income: [20340, 27492, 34644, 41784, 48936, 56088, 63240, 70392, 77544, 84684, 91836, 98988, 106140, 20352, 27504, 34656, 41796, 48948, 56100, 63252, 70404, 77556, 84696, 91848, 99000, 106152] @@ -10,6 +11,7 @@ - name: Categorical eligibility confers Tier I despite high income period: 2026 input: + heating_type: NATURAL_GAS state_code: NY ny_heap_countable_income: 200000 ny_heap_categorically_eligible: true From 1bfe348ff26bb3a3c13f3629ba59752321703fe1 Mon Sep 17 00:00:00 2001 From: Ziming Date: Fri, 2 Oct 2026 12:14:52 -0400 Subject: [PATCH 3/7] Reuse shared disability criteria for New York HEAP --- .../gov/states/ny/otda/heap/ny_heap.yaml | 2 +- .../otda/heap/payment/ny_heap_vulnerable.yaml | 25 +++++++++++++++++++ .../eligibility/ny_heap_household_size.py | 2 +- .../otda/heap/payment/ny_heap_vulnerable.py | 8 ++---- 4 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/payment/ny_heap_vulnerable.yaml diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/ny_heap.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/ny_heap.yaml index 5869ff49554..7992de87b6e 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/ny_heap.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/ny_heap.yaml @@ -18,7 +18,7 @@ has_heating_expense: [true, true, true, true, true, true] age: [5, 6, 59, 60, 40, 40] heating_type: [FUEL_OIL, FUEL_OIL, FUEL_OIL, FUEL_OIL, FUEL_OIL, FUEL_OIL] - is_permanently_and_totally_disabled: [false, false, false, false, false, true] + social_security_disability: [0, 0, 0, 0, 0, 1200] output: ny_heap: [996, 961, 961, 996, 900, 935] - name: Heat included in rent by tier and housing assistance diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/payment/ny_heap_vulnerable.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/payment/ny_heap_vulnerable.yaml new file mode 100644 index 00000000000..96298408715 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/payment/ny_heap_vulnerable.yaml @@ -0,0 +1,25 @@ +- name: Existing qualifying benefit pathways establish HEAP disability + period: 2026 + input: + state_code: [NY, NY, NY, NY, NY, NY, NY] + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] + age: [40, 40, 40, 40, 40, 40, 40] + receives_ssi: [true, false, false, false, false, false, false] + ssi: [0, 1200, 0, 0, 0, 0, 0] + social_security_disability: [0, 0, 1200, 0, 0, 0, 0] + is_permanently_disabled_veteran: [false, false, false, true, false, false, false] + is_surviving_spouse_of_disabled_veteran: [false, false, false, false, true, false, false] + is_surviving_child_of_disabled_veteran: [false, false, false, false, false, true, false] + is_permanently_and_totally_disabled: [false, false, false, false, false, false, true] + output: + ny_heap_vulnerable: [true, true, true, true, true, true, false] +- name: A nonqualified disabled member does not establish vulnerability + period: 2026 + input: + state_code: NY + heating_type: NATURAL_GAS + age: 40 + is_citizen_or_legal_immigrant: false + ssi: 1200 + output: + ny_heap_vulnerable: false diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.py b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.py index 4eb1fc21278..26dc2e6150f 100644 --- a/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.py +++ b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.py @@ -8,6 +8,6 @@ class ny_heap_household_size(Variable): label = "New York HEAP qualified household size" defined_for = StateCode.NY reference = ("https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=34,37,44",) - documentation = "SPM members who are citizens or federally qualified noncitizens. Their excluded members' income still counts in full. Foster members, SSI Code C, roomers, employees and fleeing felons cannot be separately identified. The existing immigration enum does not separately identify every additional federally protected status." + documentation = "SPM members who are citizens or federally qualified noncitizens. Nonqualified members' income still counts in full. Additional membership exclusions remain unimplemented, including foster members and SSI Code C recipients (existing inputs identify these), and roomers, employees and fleeing felons. The existing immigration enum does not separately identify every additional federally protected status." adds = ["is_citizen_or_legal_immigrant"] diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/payment/ny_heap_vulnerable.py b/policyengine_us/variables/gov/states/ny/otda/heap/payment/ny_heap_vulnerable.py index 723ec364f30..7d29b6ba668 100644 --- a/policyengine_us/variables/gov/states/ny/otda/heap/payment/ny_heap_vulnerable.py +++ b/policyengine_us/variables/gov/states/ny/otda/heap/payment/ny_heap_vulnerable.py @@ -8,17 +8,13 @@ class ny_heap_vulnerable(Variable): label = "New York HEAP qualified vulnerable member" defined_for = StateCode.NY reference = ("https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=44,45,49",) - documentation = "Permanent-disability input and SSI/SSDI receipt approximate HEAP disability certification. Detailed VA, railroad, FECA and disability-based Medicaid certification pathways are not separately identified." + documentation = "The manual adopts the SNAP benefit-receipt disability criteria. Reuses the existing USDA calculation, including its qualifying veteran and survivor flags. A disability flag alone does not establish receipt or certification. The existing USDA flags approximate detailed VA certification; railroad, FECA and disability-based Medicaid pathways remain incomplete." def formula(spm_unit, period, parameters): p = parameters(period).gov.states.ny.otda.heap.payment person = spm_unit.members age = person("age", period) - disabled = ( - person("is_permanently_and_totally_disabled", period) - | (person("ssi", period) > 0) - | (person("social_security_disability", period) > 0) - ) + disabled = person("is_usda_disabled", period) vulnerable = (age < p.young_child_age) | (age >= p.elderly_age) | disabled return spm_unit.any( vulnerable & person("is_citizen_or_legal_immigrant", period) From ca443e2704bd1f2bb9bb4bc2a010eb8111f7a4b3 Mon Sep 17 00:00:00 2001 From: Ziming Date: Fri, 2 Oct 2026 12:56:43 -0400 Subject: [PATCH 4/7] Fix New York HEAP household exclusions and income receipt --- .../ny/otda/heap/eligibility/smi_rate.yaml | 4 +- .../ny/otda/heap/income/sources/earned.yaml | 4 +- .../ny/otda/heap/income/sources/unearned.yaml | 9 ++-- .../ny/otda/heap/payment/elderly_age.yaml | 4 +- .../otda/heap/payment/tier_one_fpg_rate.yaml | 4 +- .../heap/payment/tier_one_supplement.yaml | 4 +- .../heap/payment/vulnerable_supplement.yaml | 4 +- .../ny/otda/heap/payment/young_child_age.yaml | 4 +- policyengine_us/programs.yaml | 2 +- .../ny_heap_categorically_eligible.yaml | 34 +++++++++++- .../eligibility/ny_heap_household_size.yaml | 53 +++++++++++++++++++ .../heap/income/ny_heap_countable_income.yaml | 23 ++++++-- .../gov/states/ny/otda/heap/integration.yaml | 4 +- .../ny_heap_categorically_eligible.py | 14 +++-- .../otda/heap/eligibility/ny_heap_eligible.py | 3 +- .../eligibility/ny_heap_household_size.py | 11 ++-- .../eligibility/ny_heap_is_excluded_person.py | 22 ++++++++ .../income/ny_heap_countable_earned_income.py | 3 +- .../heap/income/ny_heap_countable_income.py | 27 ++++++---- .../states/ny/otda/heap/income/ny_heap_fpg.py | 3 +- .../gov/states/ny/otda/heap/ny_heap.py | 6 ++- .../ny/otda/heap/ny_heap_dwelling_type.py | 3 +- .../otda/heap/payment/ny_heap_vulnerable.py | 7 ++- 23 files changed, 200 insertions(+), 52 deletions(-) create mode 100644 policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.yaml create mode 100644 policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_is_excluded_person.py diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/smi_rate.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/smi_rate.yaml index ff9f6e1668b..884c86b2b79 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/smi_rate.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/smi_rate.yaml @@ -6,5 +6,5 @@ metadata: period: year label: New York HEAP SMI rate reference: - - title: FY2026 LIHEAP State Plan, regular heating assistance - href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9,10 + - title: FY2026 LIHEAP State Plan, regular heating assistance, PDF pages 9, 10 + href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9 diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/earned.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/earned.yaml index b094c6906fe..4ddda3eb7f8 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/earned.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/earned.yaml @@ -11,5 +11,5 @@ metadata: period: year label: New York HEAP earned sources reference: - - title: HEAP Manual, Chapter 8 D.12 and D.14 - href: https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=37,38,42,43 + - title: HEAP Manual, Chapter 8 D.12 and D.14, PDF pages 37, 38, 42, 43 + href: https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=37 diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/unearned.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/unearned.yaml index d7e4014fae7..94eba6b9c50 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/unearned.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/unearned.yaml @@ -1,6 +1,9 @@ description: New York counts these available unearned income sources under the Home Energy Assistance Program. values: '2025-10-01': + # Medicare deductions from Social Security and railroad benefits are deferred. + - social_security + - railroad_benefits - ssi - unemployment_compensation - strike_benefits @@ -11,7 +14,7 @@ values: - retirement_distributions - alimony_income - child_support_received - - dividend_income + - ordinary_dividend_income - interest_income - rental_income - estate_income @@ -20,5 +23,5 @@ metadata: period: year label: New York HEAP unearned sources reference: - - title: HEAP Manual, Chapter 8 D.12-D.14 - href: https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=37,38,39,40,41,42,43 + - title: HEAP Manual, Chapter 8 D.12-D.14, PDF pages 37, 38, 39, 40, 41, 42, 43 + href: https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=37 diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/elderly_age.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/elderly_age.yaml index e88ebc911d6..6e4d7df7c77 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/elderly_age.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/elderly_age.yaml @@ -6,5 +6,5 @@ metadata: period: year label: New York HEAP elderly age reference: - - title: FY2026 LIHEAP State Plan, regular heating assistance - href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9,10 + - title: FY2026 LIHEAP State Plan, regular heating assistance, PDF pages 9, 10 + href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9 diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_fpg_rate.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_fpg_rate.yaml index 41ba0a53ed0..f91c82853d4 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_fpg_rate.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_fpg_rate.yaml @@ -6,5 +6,5 @@ metadata: period: year label: New York HEAP Tier I poverty rate reference: - - title: FY2026 LIHEAP State Plan, regular heating assistance - href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9,10 + - title: FY2026 LIHEAP State Plan, regular heating assistance, PDF pages 9, 10 + href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9 diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_supplement.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_supplement.yaml index 632e1fb23d1..4549d5f4db6 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_supplement.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_supplement.yaml @@ -6,5 +6,5 @@ metadata: period: year label: New York HEAP Tier I supplement reference: - - title: FY2026 LIHEAP State Plan, regular heating assistance - href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9,10 + - title: FY2026 LIHEAP State Plan, regular heating assistance, PDF pages 9, 10 + href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9 diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/vulnerable_supplement.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/vulnerable_supplement.yaml index c668685b468..a2c5850e63b 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/vulnerable_supplement.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/vulnerable_supplement.yaml @@ -6,5 +6,5 @@ metadata: period: year label: New York HEAP vulnerable supplement reference: - - title: FY2026 LIHEAP State Plan, regular heating assistance - href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9,10 + - title: FY2026 LIHEAP State Plan, regular heating assistance, PDF pages 9, 10 + href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9 diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/young_child_age.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/young_child_age.yaml index 730361573c1..bca38a3b9b6 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/young_child_age.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/young_child_age.yaml @@ -6,5 +6,5 @@ metadata: period: year label: New York HEAP young child age reference: - - title: FY2026 LIHEAP State Plan, regular heating assistance - href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9,10 + - title: FY2026 LIHEAP State Plan, regular heating assistance, PDF pages 9, 10 + href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9 diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 305406125fe..3ec7e10a713 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -811,7 +811,7 @@ programs: variable: ny_heap parameter_prefix: gov.states.ny.otda.heap verified_years: "2026" - notes: Regular heating with categorical eligibility, qualified-member size, full excluded-member income, heating and rent-included payments and Tier I/vulnerability supplements. Annual income and existing net business inputs approximate HEAP accounting; SSI state codes, detailed member exclusions and special income/certification rules remain unsupported. Sizes 14+ follow the stated guideline rule despite an inconsistent published increment. Earlier years are unverified backfilled estimates. + notes: Regular heating eligibility, income, household exclusions and fuel/housing supplements. SSI state codes and some receipt, residence and certification rules remain partial; earlier years are unverified estimates. - state: OR status: in_progress name: LIHEAP diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.yaml index cda3b2b46c8..04c635bd2d7 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.yaml @@ -4,7 +4,7 @@ heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] state_code: [NY, NY, NY] snap: [1, 0, 0] - ny_tanf: [0, 1, 0] + tanf: [0, 1, 0] ssi: [0, 0, 0] output: ny_heap_categorically_eligible: [true, true, false] @@ -14,9 +14,39 @@ heating_type: NATURAL_GAS state_code: NY snap: 0 - ny_tanf: 0 + tanf: 0 ssi: 1200 ssi_federal_living_arrangement: 2026-01: OWN_HOUSEHOLD output: ny_heap_categorically_eligible: true + +- name: Unclaimed state cash entitlement does not confer categorical eligibility + period: 2026 + input: + state_code: [NY, NY] + heating_type: [NATURAL_GAS, NATURAL_GAS] + age: [40, 40] + ny_tanf: [1200, 1200] + takes_up_tanf_if_eligible: [false, true] + snap: [0, 0] + ssi: [0, 0] + output: + ny_heap_categorically_eligible: [false, true] + +- name: An excluded foster member or nonqualified SSI recipient does not confer categorical eligibility + period: 2026 + input: + state_code: [NY, NY] + heating_type: [NATURAL_GAS, NATURAL_GAS] + age: [40, 40] + ssi: [1200, 1200] + snap: [0, 0] + tanf: [0, 0] + is_citizen_or_legal_immigrant: [true, false] + is_in_foster_care: + 2026-01: [true, false] + ssi_federal_living_arrangement: + 2026-01: [OWN_HOUSEHOLD, OWN_HOUSEHOLD] + output: + ny_heap_categorically_eligible: [false, false] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.yaml new file mode 100644 index 00000000000..60167a8ed84 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.yaml @@ -0,0 +1,53 @@ +# HEAP Manual Chapter 8 D.4, PDF page 36, excludes foster members and +# federal Code C SSI recipients. D.4(7) still counts nonqualified members' income. +- name: Foster members and federal Code C SSI recipients do not affect the household award + period: 2026 + input: + people: + adult: + age: 40 + employment_income: 24000 + ssi: 0 + foster_child: + age: 3 + is_in_foster_care: + 2026-01: true + social_security: 6000 + ssi: 0 + ssi_child: + age: 10 + ssi: 6000 + ssi_federal_living_arrangement: + 2026-01: CHILD_IN_PARENTAL_HOUSEHOLD + spm_units: + unit: + members: [adult, foster_child, ssi_child] + heating_type: NATURAL_GAS + has_heating_expense: true + tanf: 0 + snap: 0 + households: + home: + members: [adult, foster_child, ssi_child] + state_code: NY + output: + ny_heap_household_size: 1 + ny_heap_countable_income: 24000 + ny_heap_vulnerable: false + ny_heap_categorically_eligible: false + ny_heap_tier_one: false + ny_heap: 400 + +- name: Federal Code C without SSI receipt does not exclude a child + period: 2026 + input: + state_code: NY + heating_type: NATURAL_GAS + age: 10 + ssi: 0 + receives_ssi: false + ssi_federal_living_arrangement: + 2026-01: CHILD_IN_PARENTAL_HOUSEHOLD + output: + ny_heap_household_size: 1 + ny_heap_vulnerable: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_income.yaml index e8dded9971a..95235527f7a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_income.yaml @@ -1,6 +1,6 @@ -# 12,001 earnings + 11,400 net SSA/railroad + 4,440 other income = 27,841. -# floor(27,841 / 12) * 12 = 27,840. -- name: Deduct paid Medicare Part B premiums and round monthly household income down +# 12,001 earnings + 12,600 SSA/railroad + 4,440 other income = 29,041. +# floor(29,041 / 12) * 12 = 29,040. +- name: Medicare deductions remain deferred and monthly household income rounds down period: 2026 input: heating_type: NATURAL_GAS @@ -12,7 +12,7 @@ pension_income: 240 retirement_distributions: 360 interest_income: 120 - dividend_income: 240 + ordinary_dividend_income: 240 rental_income: 360 alimony_income: 480 child_support_received: 600 @@ -23,5 +23,18 @@ veterans_benefits: 480 estate_income: 120 ssi: 0 + tanf: 0 output: - ny_heap_countable_income: 27840 + ny_heap_countable_income: 29040 + +- name: State cash entitlement counts only when TANF is taken up + period: 2026 + input: + state_code: [NY, NY] + heating_type: [NATURAL_GAS, NATURAL_GAS] + age: [40, 40] + ny_tanf: [1200, 1200] + takes_up_tanf_if_eligible: [false, true] + ssi: [0, 0] + output: + ny_heap_countable_income: [0, 1200] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/integration.yaml index 171c5c484f7..4a5e3f28d09 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/integration.yaml @@ -16,7 +16,7 @@ - child heating_type: NATURAL_GAS gas_expense: 100 - ny_tanf: 0 + tanf: 0 snap: 0 households: home: @@ -41,7 +41,7 @@ employment_income: 0 ssi: 0 snap: 0 - ny_tanf: 0 + tanf: 0 has_heating_expense: true heating_type: NATURAL_GAS output: diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.py b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.py index 44536340e5a..0a0f6d7e82f 100644 --- a/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.py +++ b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.py @@ -9,13 +9,19 @@ class ny_heap_categorically_eligible(Variable): defined_for = StateCode.NY reference = ( "https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=5", - "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=37,110", + # PDF pages 37, 110. + "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=37", ) - documentation = "Receipt of SNAP, federally funded NY TANF or SSI in a federal own-household arrangement. State SSI Code A and recurring receipt on the application date are approximated; expedited/emergency-only benefits and roomer-only awards cannot be distinguished. Safety Net Assistance is not treated as federally funded TANF." + documentation = "Receipt of SNAP, TANF or SSI in a federal own-household arrangement. State SSI Code A and recurring receipt on the application date are approximated; expedited/emergency-only benefits and roomer-only awards cannot be distinguished. Chapter 8 D.10 also qualifies Safety Net Assistance, but no NY-specific receipt input identifies it." def formula(spm_unit, period, parameters): person = spm_unit.members arrangement = person("ssi_federal_living_arrangement", period.first_month) code_a = arrangement == arrangement.possible_values.OWN_HOUSEHOLD - qualifying_ssi = spm_unit.any((person("ssi", period) > 0) & code_a) - return (add(spm_unit, period, ["snap", "ny_tanf"]) > 0) | qualifying_ssi + qualifying_ssi = spm_unit.any( + (person("ssi", period) > 0) + & code_a + & person("is_citizen_or_legal_immigrant", period) + & ~person("ny_heap_is_excluded_person", period) + ) + return (add(spm_unit, period, ["snap", "tanf"]) > 0) | qualifying_ssi diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_eligible.py b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_eligible.py index affce5a2238..8e399128c43 100644 --- a/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_eligible.py +++ b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_eligible.py @@ -8,7 +8,8 @@ class ny_heap_eligible(Variable): label = "New York HEAP regular heating eligibility" defined_for = StateCode.NY reference = ( - "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=34,37,44,45,46,47,48", + # PDF pages 34, 37, 44, 45, 46, 47, 48. + "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=34", ) documentation = "Regular heating has no resource test. SPM units approximate energy-sharing households. Application timing, SSN documentation, duplicate payments and the full set of excluded member categories are not modeled." diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.py b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.py index 26dc2e6150f..393a1fa0377 100644 --- a/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.py +++ b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.py @@ -7,7 +7,12 @@ class ny_heap_household_size(Variable): definition_period = YEAR label = "New York HEAP qualified household size" defined_for = StateCode.NY - reference = ("https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=34,37,44",) - documentation = "SPM members who are citizens or federally qualified noncitizens. Nonqualified members' income still counts in full. Additional membership exclusions remain unimplemented, including foster members and SSI Code C recipients (existing inputs identify these), and roomers, employees and fleeing felons. The existing immigration enum does not separately identify every additional federally protected status." + # Chapter 8 D.4 and D.9, PDF pages 34, 36, 37, 44. + reference = ("https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=34",) + documentation = "SPM members who are citizens or federally qualified noncitizens. Nonqualified members' income still counts in full. Foster members and federal Code C SSI recipients are excluded using existing inputs. Roomers, employees and fleeing felons remain unsupported. The existing immigration enum does not separately identify every additional federally protected status." - adds = ["is_citizen_or_legal_immigrant"] + def formula(spm_unit, period, parameters): + person = spm_unit.members + included = ~person("ny_heap_is_excluded_person", period) + qualified = person("is_citizen_or_legal_immigrant", period) + return spm_unit.sum(included & qualified) diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_is_excluded_person.py b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_is_excluded_person.py new file mode 100644 index 00000000000..1bb39ecad6e --- /dev/null +++ b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_is_excluded_person.py @@ -0,0 +1,22 @@ +from policyengine_us.model_api import * + + +class ny_heap_is_excluded_person(Variable): + value_type = bool + entity = Person + definition_period = YEAR + label = "Person excluded from the New York HEAP household" + defined_for = StateCode.NY + # Chapter 8 D.4(a)(3), (5), PDF page 36. This is the federal Code C, + # distinct from the state Code A required for categorical SSI eligibility. + reference = "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=36" + + def formula(person, period, parameters): + # January living arrangements approximate the application-month status. + foster = person("is_in_foster_care", period.first_month) + arrangement = person("ssi_federal_living_arrangement", period.first_month) + code_c = arrangement == arrangement.possible_values.CHILD_IN_PARENTAL_HOUSEHOLD + receives_ssi = (person("ssi", period) > 0) | ( + add(person, period, ["receives_ssi"]) > 0 + ) + return foster | (code_c & receives_ssi) diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_earned_income.py b/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_earned_income.py index d8c640346b0..ea2f6d4af96 100644 --- a/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_earned_income.py +++ b/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_earned_income.py @@ -8,7 +8,8 @@ class ny_heap_countable_earned_income(Variable): label = "New York HEAP countable earnings" unit = USD defined_for = StateCode.NY - reference = ("https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=37,39,42,43",) + # PDF pages 37, 39, 42, 43. + reference = ("https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=37",) documentation = "Existing net business income is used without further deductions. HEAP-specific depreciation add-backs, separate accounting periods and wage bonus exclusions are unsupported." def formula(person, period, parameters): diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_income.py b/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_income.py index 18b579c5e3a..413c1da1cda 100644 --- a/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_income.py +++ b/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_income.py @@ -9,20 +9,27 @@ class ny_heap_countable_income(Variable): unit = USD defined_for = StateCode.NY reference = ( - "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=37,38,39,40,41,42,43,44", + # PDF pages 36, 37, 38, 39, 40, 41, 42, 43, 44. + "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=37", ) - documentation = "Annual inputs approximate application-month income; final monthly income is rounded down and annualized. Income of nonqualified members counts in full. Medicare Part D premiums, royalties, regular gifts, aid-and-attendance exclusions and irregular-income exclusions have no dedicated inputs. Rental income is assumed nonnegative. No new self-employment or work-expense deduction is applied." + documentation = "Annual inputs approximate application-month income; final monthly income is rounded down and annualized. Income of nonqualified members counts in full. Medicare-premium deductions remain deferred. Royalties, regular gifts, aid-and-attendance exclusions and irregular-income exclusions cannot be isolated reliably. Rental income is assumed nonnegative. No new self-employment or work-expense deduction is applied." def formula(spm_unit, period, parameters): p = parameters(period).gov.states.ny.otda.heap.income person = spm_unit.members - retirement = add(person, period, ["social_security", "railroad_benefits"]) - net_retirement = spm_unit.sum( - max_(retirement - person("medicare_part_b_premium", period), 0) - ) - income = ( - add(spm_unit, period, ["ny_heap_countable_earned_income"]) - + add(spm_unit, period, p.sources.unearned) - + net_retirement + # Chapter 8 D.4 excludes foster members and federal Code C SSI + # recipients. Nonqualified members are excluded only from household + # size; their income still counts in full under D.4(a)(7). + included = ~person("ny_heap_is_excluded_person", period) + income = spm_unit.sum( + ( + person("ny_heap_countable_earned_income", period) + + add(person, period, p.sources.unearned) + ) + * included ) + # The annual TANF aggregate applies take-up; ny_tanf alone is entitlement. + income = income + spm_unit("tanf", period) + # D.12 and D.13 permit actual Medicare B/D deductions. They remain + # deferred: neither modeled nor reported premiums are deducted here. return np.floor(max_(income, 0) / MONTHS_IN_YEAR) * MONTHS_IN_YEAR diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_fpg.py b/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_fpg.py index c909d07e03b..c123ceab3f5 100644 --- a/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_fpg.py +++ b/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_fpg.py @@ -10,7 +10,8 @@ class ny_heap_fpg(Variable): unit = USD defined_for = StateCode.NY reference = ( - "https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9,10", + # PDF pages 9, 10. + "https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9", "https://liheapch.acf.gov/docs/2026/benefits-matricies/NY_BenefitMatrix_2026.docx", ) diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/ny_heap.py b/policyengine_us/variables/gov/states/ny/otda/heap/ny_heap.py index 8fafd15853c..aef1d119d74 100644 --- a/policyengine_us/variables/gov/states/ny/otda/heap/ny_heap.py +++ b/policyengine_us/variables/gov/states/ny/otda/heap/ny_heap.py @@ -10,8 +10,10 @@ class ny_heap(Variable): defined_for = "ny_heap_eligible" reference = ( "https://liheapch.acf.gov/docs/2026/benefits-matricies/NY_BenefitMatrix_2026.docx", - "https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=5,9,10", - "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=45,46,47,48,49,84,87,88", + # PDF pages 5, 9, 10. + "https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=5", + # PDF pages 45, 46, 47, 48, 49, 84, 87, 88. + "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=45", ) documentation = "Annual regular heating benefit and ordinary Tier I/vulnerability supplements, verified for FY2026. Earlier years are unverified backfilled estimates. Housing assistance approximates subsidized rent (market-rent voucher exceptions are unsupported). Unknown direct-heating fuel returns zero; solar falls within other fuels. No bill cap. Crisis, cooling and equipment components are excluded. Mid-year moves and previously advanced nominal payments are not separately tracked." diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/ny_heap_dwelling_type.py b/policyengine_us/variables/gov/states/ny/otda/heap/ny_heap_dwelling_type.py index cbc3b3ffd26..5384f8b1c69 100644 --- a/policyengine_us/variables/gov/states/ny/otda/heap/ny_heap_dwelling_type.py +++ b/policyengine_us/variables/gov/states/ny/otda/heap/ny_heap_dwelling_type.py @@ -16,5 +16,6 @@ class ny_heap_dwelling_type(Variable): entity = SPMUnit definition_period = YEAR label = "New York HEAP dwelling arrangement" - reference = "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=45,46,47" + # PDF pages 45, 46, 47. + reference = "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=45" documentation = "Eligible group residences are the treatment, enriched housing, supervised/supportive living and other settings listed in Chapter 8 F.3 (group living facilities must have at most 16 residents). Ineligible residences include private roomers/boarders, hotels, motels, vehicles, dormitories and congregate care under F.4. Ordinary subsidized housing uses the existing housing-assistance and heat-in-rent inputs." diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/payment/ny_heap_vulnerable.py b/policyengine_us/variables/gov/states/ny/otda/heap/payment/ny_heap_vulnerable.py index 7d29b6ba668..a769e659bed 100644 --- a/policyengine_us/variables/gov/states/ny/otda/heap/payment/ny_heap_vulnerable.py +++ b/policyengine_us/variables/gov/states/ny/otda/heap/payment/ny_heap_vulnerable.py @@ -7,7 +7,8 @@ class ny_heap_vulnerable(Variable): definition_period = YEAR label = "New York HEAP qualified vulnerable member" defined_for = StateCode.NY - reference = ("https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=44,45,49",) + # PDF pages 44, 45, 49. + reference = ("https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=44",) documentation = "The manual adopts the SNAP benefit-receipt disability criteria. Reuses the existing USDA calculation, including its qualifying veteran and survivor flags. A disability flag alone does not establish receipt or certification. The existing USDA flags approximate detailed VA certification; railroad, FECA and disability-based Medicaid pathways remain incomplete." def formula(spm_unit, period, parameters): @@ -17,5 +18,7 @@ def formula(spm_unit, period, parameters): disabled = person("is_usda_disabled", period) vulnerable = (age < p.young_child_age) | (age >= p.elderly_age) | disabled return spm_unit.any( - vulnerable & person("is_citizen_or_legal_immigrant", period) + vulnerable + & person("is_citizen_or_legal_immigrant", period) + & ~person("ny_heap_is_excluded_person", period) ) From ab43f9474cc3e2941b99325c27c504f913d14172 Mon Sep 17 00:00:00 2001 From: Ziming Date: Sat, 3 Oct 2026 01:52:26 -0400 Subject: [PATCH 5/7] Fix issues from review: income sources, TANF, page anchors, registry order, tests Count survivor_benefits, farm_rent_income and miscellaneous_income under HEAP Manual D.12(a)(13), (14), (27) and (38); stop adding TANF to countable income (state plan item 1.9 leaves it unchecked; TANF receipt still confers categorical eligibility); point the Tier I links at plan page 10; name the Medicare premium variables that are not wired in; place New York between IL and TX in programs.yaml. Rewrite the tests to the shared conventions with fuel bills, derivation comments and end-to-end coverage of categorical, over-income, heat-in-rent and public-housing paths. Co-Authored-By: Claude Fable 5.1 --- .../ny/otda/heap/income/sources/unearned.yaml | 12 +- .../otda/heap/payment/tier_one_fpg_rate.yaml | 4 +- .../heap/payment/tier_one_supplement.yaml | 4 +- policyengine_us/programs.yaml | 18 +- .../ny_heap_categorically_eligible.yaml | 128 ++++++-- .../eligibility/ny_heap_household_size.yaml | 53 ++- .../eligibility/ny_heap_income_limit.yaml | 25 +- .../ny_heap_countable_earned_income.yaml | 70 +++- .../heap/income/ny_heap_countable_income.yaml | 103 +++++- .../ny/otda/heap/income/ny_heap_fpg.yaml | 11 +- .../gov/states/ny/otda/heap/integration.yaml | 309 ++++++++++++++++-- .../gov/states/ny/otda/heap/ny_heap.yaml | 244 ++++++++++++-- .../otda/heap/payment/ny_heap_tier_one.yaml | 24 +- .../otda/heap/payment/ny_heap_vulnerable.yaml | 22 +- .../heap/income/ny_heap_countable_income.py | 11 +- 15 files changed, 880 insertions(+), 158 deletions(-) diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/unearned.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/unearned.yaml index 94eba6b9c50..e9c312b4a8b 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/unearned.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/unearned.yaml @@ -1,7 +1,9 @@ description: New York counts these available unearned income sources under the Home Energy Assistance Program. values: '2025-10-01': - # Medicare deductions from Social Security and railroad benefits are deferred. + # D.12(a)(6) and (8) count Social Security and railroad benefits after + # Medicare Part B and D premiums. medicare_part_b_premium and + # medicare_part_b_premiums_reported are not wired in yet, so both count gross. - social_security - railroad_benefits - ssi @@ -10,6 +12,10 @@ values: - workers_compensation - disability_benefits - veterans_benefits + # Survivor benefits other than Social Security survivor benefits: employer + # pension survivor annuities under D.12(a)(13) pensions and (14) payments from + # insurance and annuities; veterans' survivor benefits under D.12(a)(12). + - survivor_benefits - pension_income - retirement_distributions - alimony_income @@ -17,7 +23,11 @@ values: - ordinary_dividend_income - interest_income - rental_income + # D.12(a)(27): income from rents. + - farm_rent_income - estate_income + # D.12(a)(38): all other income not specifically excluded under D.13. + - miscellaneous_income metadata: unit: list period: year diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_fpg_rate.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_fpg_rate.yaml index f91c82853d4..102c028dd41 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_fpg_rate.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_fpg_rate.yaml @@ -6,5 +6,5 @@ metadata: period: year label: New York HEAP Tier I poverty rate reference: - - title: FY2026 LIHEAP State Plan, regular heating assistance, PDF pages 9, 10 - href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9 + - title: FY2026 LIHEAP State Plan, regular heating assistance, PDF page 10 + href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=10 diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_supplement.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_supplement.yaml index 4549d5f4db6..7a26f23578c 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_supplement.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_supplement.yaml @@ -6,5 +6,5 @@ metadata: period: year label: New York HEAP Tier I supplement reference: - - title: FY2026 LIHEAP State Plan, regular heating assistance, PDF pages 9, 10 - href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9 + - title: FY2026 LIHEAP State Plan, regular heating assistance, PDF page 10 + href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=10 diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 3ec7e10a713..8a7c2881d0a 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -802,16 +802,8 @@ programs: category: Benefits agency: HHS status: partial - coverage: OR, DC, Riverside County, MA, IL, TX, NY + coverage: OR, DC, Riverside County, MA, IL, NY, TX state_implementations: - - state: NY - status: partial - name: New York HEAP - full_name: New York Home Energy Assistance Program - variable: ny_heap - parameter_prefix: gov.states.ny.otda.heap - verified_years: "2026" - notes: Regular heating eligibility, income, household exclusions and fuel/housing supplements. SSI state codes and some receipt, residence and certification rules remain partial; earlier years are unverified estimates. - state: OR status: in_progress name: LIHEAP @@ -831,6 +823,14 @@ programs: name: Illinois LIHEAP full_name: Illinois Low Income Home Energy Assistance Program variable: il_liheap + - state: NY + status: partial + name: New York HEAP + full_name: New York Home Energy Assistance Program + variable: ny_heap + parameter_prefix: gov.states.ny.otda.heap + verified_years: "2026" + notes: Regular heating eligibility, income, household exclusions and fuel/housing supplements. SSI state codes and some receipt, residence and certification rules remain partial; earlier years are unverified estimates. - state: TX status: partial name: Texas CEAP diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.yaml index 04c635bd2d7..c4642f156fe 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.yaml @@ -1,46 +1,77 @@ -- name: SNAP and federally funded TANF each confer categorical eligibility +# HEAP Manual, Chapter 8 D.10 and D.11 (PDF page 37): a household is +# categorically income eligible when a household member is an active recipient +# of recurring TA, SNAP or Code A SSI; D.4(a) (page 36) lists who is not a +# household member. https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=37 +# FY2026 State Plan item 1.4a (page 5): one member's receipt qualifies the +# whole household. +# https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=5 + +- name: Case 1, SNAP receipt confers categorical eligibility and no benefit receipt does not. period: 2026 input: - heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] - state_code: [NY, NY, NY] - snap: [1, 0, 0] - tanf: [0, 1, 0] - ssi: [0, 0, 0] + heating_type: [NATURAL_GAS, NATURAL_GAS] + state_code: [NY, NY] + # $200 a month of SNAP for the first single adult; none for the second. + snap: [2_400, 0] + tanf: [0, 0] + ssi: [0, 0] output: - ny_heap_categorically_eligible: [true, true, false] -- name: SSI own-household federal code approximates Code A + # D.10(c) (page 37): the SNAP recipient's household qualifies; the other + # household is income tested under D.9 (page 37). + ny_heap_categorically_eligible: [true, false] + +- name: Case 2, an SSI recipient in the federal own-household arrangement approximates Code A. period: 2026 input: heating_type: NATURAL_GAS state_code: NY snap: 0 tanf: 0 - ssi: 1200 + ssi: 1_200 ssi_federal_living_arrangement: 2026-01: OWN_HOUSEHOLD output: + # State Plan 1.4a (page 5) and D.10(c) (page 37): Code A SSI receipt + # qualifies. The model approximates Code A with the federal own-household + # arrangement because the state living arrangement code is not an input. ny_heap_categorically_eligible: true -- name: Unclaimed state cash entitlement does not confer categorical eligibility +- name: Case 3, a TANF entitlement that is not taken up does not confer categorical eligibility. period: 2026 input: - state_code: [NY, NY] - heating_type: [NATURAL_GAS, NATURAL_GAS] - age: [40, 40] - ny_tanf: [1200, 1200] - takes_up_tanf_if_eligible: [false, true] - snap: [0, 0] - ssi: [0, 0] + people: + person1: + age: 28 + ssi: 0 + person2: + age: 4 + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + # Two-person New York City grant with no other income: $252 basic + # allowance + $22.50 home energy + $17 supplemental home energy + $283 + # shelter = $574.50 a month (18 NYCRR 352.1 to 352.3). + ny_tanf: 6_894 + takes_up_tanf_if_eligible: false + snap: 0 + households: + household: + members: [person1, person2] + state_code: NY output: - ny_heap_categorically_eligible: [false, true] + # D.10(c) and D.11(a) (page 37) require active receipt of recurring TA; an + # unclaimed entitlement is not receipt. + ny_heap_categorically_eligible: false -- name: An excluded foster member or nonqualified SSI recipient does not confer categorical eligibility +- name: Case 4, an excluded foster member or a nonqualified SSI recipient does not confer categorical eligibility. period: 2026 input: state_code: [NY, NY] heating_type: [NATURAL_GAS, NATURAL_GAS] age: [40, 40] - ssi: [1200, 1200] + ssi: [1_200, 1_200] snap: [0, 0] tanf: [0, 0] is_citizen_or_legal_immigrant: [true, false] @@ -49,4 +80,61 @@ ssi_federal_living_arrangement: 2026-01: [OWN_HOUSEHOLD, OWN_HOUSEHOLD] output: + # D.4(a)(3) and (7) (page 36): a foster member and a nonqualified + # noncitizen are not household members, and D.11(b) (page 37) requires the + # benefit recipient to be a HEAP household member. ny_heap_categorically_eligible: [false, false] + +- name: Case 5, TANF receipt confers categorical eligibility. + period: 2026 + input: + people: + person1: + age: 28 + ssi: 0 + person2: + age: 4 + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + # Two-person New York City grant with no other income: $252 basic + # allowance + $22.50 home energy + $17 supplemental home energy + $283 + # shelter = $574.50 a month (18 NYCRR 352.1 to 352.3). + tanf: 6_894 + snap: 0 + households: + household: + members: [person1, person2] + state_code: NY + output: + # D.10(c) (page 37) and State Plan 1.4a (page 5): receipt of federally + # funded Public Assistance by one member qualifies the household. + ny_heap_categorically_eligible: true + +- name: Case 6, a TANF entitlement that is taken up confers categorical eligibility. + period: 2026 + input: + people: + person1: + age: 28 + ssi: 0 + person2: + age: 4 + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + # Same two-person New York City grant as Case 3, taken up. + ny_tanf: 6_894 + takes_up_tanf_if_eligible: true + snap: 0 + households: + household: + members: [person1, person2] + state_code: NY + output: + # D.10(c) (page 37): the family is an active recipient of recurring TA. + ny_heap_categorically_eligible: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.yaml index 60167a8ed84..339ecab2d50 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.yaml @@ -1,44 +1,62 @@ -# HEAP Manual Chapter 8 D.4, PDF page 36, excludes foster members and -# federal Code C SSI recipients. D.4(7) still counts nonqualified members' income. -- name: Foster members and federal Code C SSI recipients do not affect the household award +# HEAP Manual, Chapter 8 D.4(a) (PDF page 36): foster members and federal +# living arrangement Code C SSI recipients are not household members; D.4(a)(7) +# still counts nonqualified members' income. +# https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=36 + +- name: Case 1, foster members and federal Code C SSI recipients do not affect the household award. period: 2026 input: people: - adult: + person1: age: 40 - employment_income: 24000 + # Full-time wages of $2,000 a month. + employment_income: 24_000 ssi: 0 - foster_child: + person2: age: 3 is_in_foster_care: 2026-01: true - social_security: 6000 + social_security: 6_000 ssi: 0 - ssi_child: + person3: age: 10 - ssi: 6000 + ssi: 6_000 ssi_federal_living_arrangement: 2026-01: CHILD_IN_PARENTAL_HOUSEHOLD spm_units: - unit: - members: [adult, foster_child, ssi_child] + spm_unit: + members: [person1, person2, person3] heating_type: NATURAL_GAS - has_heating_expense: true + gas_expense: 1_500 tanf: 0 snap: 0 households: - home: - members: [adult, foster_child, ssi_child] + household: + members: [person1, person2, person3] state_code: NY output: + # Only the adult counts: D.4(a)(3) excludes the foster child, and the model + # maps D.4(a)(5) to the federal child-in-parental-household arrangement + # with SSI receipt. ny_heap_household_size: 1 - ny_heap_countable_income: 24000 + # The excluded members' income leaves with them: 24,000 of wages, which is + # 2,000 a month. + ny_heap_countable_income: 24_000 + # Chapter 9 B.3(b) (page 49): the only members under six or receiving SSI + # are excluded, and the adult is 40. ny_heap_vulnerable: false + # No SNAP or TA, and the SSI recipient is not a household member (D.11(b), + # page 37). ny_heap_categorically_eligible: false + # 2,000 a month exceeds the one-person Tier I maximum of 1,695 and is + # within the 3,473 limit (2025-2026 HEAP Monthly Income Eligibility + # Guidelines). ny_heap_tier_one: false + # Utility base $400 with no add-ons (2025-2026 HEAP Direct Heating Regular + # Base Benefit Levels). ny_heap: 400 -- name: Federal Code C without SSI receipt does not exclude a child +- name: Case 2, the federal Code C arrangement without SSI receipt does not exclude a child. period: 2026 input: state_code: NY @@ -49,5 +67,8 @@ ssi_federal_living_arrangement: 2026-01: CHILD_IN_PARENTAL_HOUSEHOLD output: + # D.4(a)(5) (page 36) names SSI recipients, so a child in that arrangement + # who receives no SSI is counted. ny_heap_household_size: 1 + # Chapter 9 B.3(b) (page 49): aged 10, so not under six, and no SSI receipt. ny_heap_vulnerable: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_income_limit.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_income_limit.yaml index bdbce41ae29..17c17579f57 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_income_limit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_income_limit.yaml @@ -1,16 +1,33 @@ -- name: All published size one through thirteen income limits +# 2025-2026 HEAP Monthly Income Eligibility Guidelines (Clearinghouse benefit +# matrix), Tier II maximum column, annualized (x 12). FY2026 State Plan section +# 2.1 (page 9) sets 60% of state median income for sizes 1 to 13 and 150% of +# the HHS poverty guideline for size 14; HEAP Manual Chapter 8 D.9(c) (page +# 37) uses the higher of the two. +# https://liheapch.acf.gov/docs/2026/benefits-matricies/NY_BenefitMatrix_2026.docx +# https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9 +# https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=37 + +- name: Case 1, all published size one through thirteen income limits. period: 2026 input: heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] state_code: [NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY] ny_heap_household_size: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13] output: - ny_heap_income_limit: [41676, 54504, 67332, 80160, 92988, 105816, 108216, 110616, 113028, 115428, 117840, 120240, 122652] -- name: Size fourteen follows the plan poverty rate rather than the inconsistent incremental row + # Published monthly Tier II maximums 3,473; 4,542; 5,611; 6,680; 7,749; + # 8,818; 9,018; 9,218; 9,419; 9,619; 9,820; 10,020; 10,221, each x 12. + ny_heap_income_limit: [41_676, 54_504, 67_332, 80_160, 92_988, 105_816, 108_216, 110_616, 113_028, 115_428, 117_840, 120_240, 122_652] + +- name: Case 2, size fourteen follows the plan poverty rate rather than the matrix increment. period: 2026 input: heating_type: NATURAL_GAS state_code: NY ny_heap_household_size: 14 output: - ny_heap_income_limit: 130716 + # State Plan 2.1 row 14 (page 9): 150% of the HHS poverty guideline. The + # 2025 guideline for 14 is 15,650 + 13 x 5,500 = 87,150; + # floor(1.5 x 87,150 / 12) = 10,893 a month, 130,716 a year. The matrix's + # 14+ row adds 687 to the size-13 maximum instead (10,908); the plan rule is + # encoded. + ny_heap_income_limit: 130_716 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_earned_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_earned_income.yaml index ed649f18c33..1e82efa3530 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_earned_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_earned_income.yaml @@ -1,15 +1,61 @@ -- name: Dependent children and full-time dependent students have exempt earnings +# HEAP Manual, Chapter 8 D.12(a)(1) and (5) (PDF page 37) count gross wages +# and net profit from self-employment; D.13(a)(4) and (5) (page 39) exclude +# the earned income of full-time dependent students aged 18 and older and of +# dependent minors under 18. +# https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=37 + +- name: Case 1, dependent minors and full-time dependent students have exempt earnings. period: 2026 input: - heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] - state_code: [NY, NY, NY, NY] - age: [17, 18, 30, 30] - is_tax_unit_dependent: [true, true, false, false] - is_full_time_student: [false, true, true, false] - employment_income: [12000, 12000, 12000, 12000] - self_employment_income: [1000, 1000, 1000, -1000] - sstb_self_employment_income: [500, 500, 500, 500] - farm_operations_income: [600, 600, 600, 600] - partnership_s_corp_income: [700, 700, 700, 700] + people: + person1: + age: 17 + is_tax_unit_dependent: true + is_full_time_student: false + employment_income: 12_000 + self_employment_income: 1_000 + sstb_self_employment_income: 500 + farm_operations_income: 600 + partnership_s_corp_income: 700 + person2: + age: 18 + is_tax_unit_dependent: true + is_full_time_student: true + employment_income: 12_000 + self_employment_income: 1_000 + sstb_self_employment_income: 500 + farm_operations_income: 600 + partnership_s_corp_income: 700 + person3: + age: 45 + is_tax_unit_dependent: false + is_full_time_student: true + employment_income: 12_000 + self_employment_income: 1_000 + sstb_self_employment_income: 500 + farm_operations_income: 600 + partnership_s_corp_income: 700 + person4: + age: 47 + is_tax_unit_dependent: false + is_full_time_student: false + employment_income: 12_000 + self_employment_income: -1_000 + sstb_self_employment_income: 500 + farm_operations_income: 600 + partnership_s_corp_income: 700 + spm_units: + spm_unit: + members: [person1, person2, person3, person4] + heating_type: NATURAL_GAS + households: + household: + members: [person1, person2, person3, person4] + state_code: NY output: - ny_heap_countable_earned_income: [0, 0, 14800, 13800] + # D.13(a)(5): the 17-year-old dependent's earnings are excluded; D.13(a)(4): + # so are the 18-year-old full-time dependent student's. The adult student + # is not a dependent, so 12,000 + 1,000 + 500 + 600 + 700 = 14,800 counts. + # A self-employment loss is not net profit under D.12(a)(5), so the last + # adult counts 12,000 + 500 + 600 + 700 = 13,800. + ny_heap_countable_earned_income: [0, 0, 14_800, 13_800] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_income.yaml index 95235527f7a..9615f27dfc2 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_income.yaml @@ -1,13 +1,21 @@ -# 12,001 earnings + 12,600 SSA/railroad + 4,440 other income = 29,041. -# floor(29,041 / 12) * 12 = 29,040. -- name: Medicare deductions remain deferred and monthly household income rounds down +# HEAP Manual, Chapter 8 D.12 to D.14 (PDF pages 37 to 43): countable and +# excluded income; D.14(c) (page 43) rounds final calculations down to the +# nearest dollar. https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=37 +# FY2026 State Plan item 1.9 (page 6) lists the counted income forms and leaves +# the TANF box unchecked. +# https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=6 + +- name: Case 1, Social Security counts gross and monthly household income rounds down. period: 2026 input: heating_type: NATURAL_GAS state_code: NY - ny_heap_countable_earned_income: 12001 - social_security: 12000 - medicare_part_b_premium: 1200 + ny_heap_countable_earned_income: 12_001 + social_security: 12_000 + # D.12(a)(6) (page 38) counts Social Security after the Medicare premium; + # medicare_part_b_premium is not wired in yet, so the premium reported here + # must not reduce the total. + medicare_part_b_premium: 1_200 railroad_benefits: 600 pension_income: 240 retirement_distributions: 360 @@ -23,18 +31,81 @@ veterans_benefits: 480 estate_income: 120 ssi: 0 - tanf: 0 output: - ny_heap_countable_income: 29040 + # 12,001 earnings + 12,600 Social Security and railroad benefits (D.12(a)(6) + # and (8), page 38) + 4,440 from the other counted sources in D.12(a)(10) to + # (28) (pages 38 to 39) = 29,041. D.14(c) (page 43): floor(29,041 / 12) = + # 2,420 a month, annualized 29,040. + ny_heap_countable_income: 29_040 + +- name: Case 2, TANF cash assistance is not counted as income. + period: 2026 + input: + people: + person1: + age: 28 + ssi: 0 + person2: + age: 4 + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + # Two-person New York City grant with no other income: $252 basic + # allowance + $22.50 home energy + $17 supplemental home energy + $283 + # shelter = $574.50 a month (18 NYCRR 352.1 to 352.3). + tanf: 6_894 + snap: 0 + households: + household: + members: [person1, person2] + state_code: NY + output: + # State Plan item 1.9 (page 6) leaves the TANF box unchecked, and Chapter 8 + # D.11(c)(1) (page 37) counts a TA grant only for a minor child budgeted as + # a roomer, so the grant adds nothing to the household's countable income. + ny_heap_countable_income: 0 + # Chapter 8 D.10(c) (page 37): the recurring TA recipient household is + # categorically income eligible instead. + ny_heap_categorically_eligible: true -- name: State cash entitlement counts only when TANF is taken up +- name: Case 3, survivor benefits, farm rent and miscellaneous income are counted. period: 2026 input: - state_code: [NY, NY] - heating_type: [NATURAL_GAS, NATURAL_GAS] - age: [40, 40] - ny_tanf: [1200, 1200] - takes_up_tanf_if_eligible: [false, true] - ssi: [0, 0] + people: + person1: + age: 61 + # Part-time wages of $1,000 a month. + employment_income: 12_000 + # An $800 monthly survivor annuity from a late spouse's employer + # pension, $500 of farm cropland rent and $100 of other regular income. + survivor_benefits: 9_600 + farm_rent_income: 6_000 + miscellaneous_income: 1_200 + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: FUEL_OIL + # About 700 gallons of heating oil at $4.00 a gallon. + fuel_oil_expense: 2_800 + snap: 0 + tanf: 0 + households: + household: + members: [person1] + state_code: NY output: - ny_heap_countable_income: [0, 1200] + # D.12(a)(1) wages (page 37); D.12(a)(13) pensions and (14) payments from + # insurance and annuities for the employer pension survivor annuity (page + # 38); (27) income from rents and (38) all other income not excluded (pages + # 38 to 39): 12,000 + 9,600 + 6,000 + 1,200 = 28,800, which is 2,400 a month. + ny_heap_countable_income: 28_800 + # 2,400 a month exceeds the one-person Tier I maximum of 1,695 in the + # 2025-2026 HEAP Monthly Income Eligibility Guidelines; the wages alone + # would have been Tier I. + ny_heap_tier_one: false + # Oil base $900 + $35 for a member aged 60 or older (2025-2026 HEAP Direct + # Heating Regular Base Benefit Levels, vulnerable add-on column) = $935. + ny_heap: 935 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_fpg.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_fpg.yaml index b507506a2ee..4ac77d09d12 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_fpg.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_fpg.yaml @@ -1,10 +1,15 @@ -- name: Backfilled estimates still use the guideline one year before the requested period +# The 2025-2026 HEAP Monthly Income Eligibility Guidelines (Clearinghouse +# benefit matrix) reproduce from the poverty guideline of the year before the +# program year, so the model lags the guideline by one year. +# https://liheapch.acf.gov/docs/2026/benefits-matricies/NY_BenefitMatrix_2026.docx + +- name: Case 1, backfilled estimates use the guideline one year before the requested period. period: 2022 input: heating_type: NATURAL_GAS state_code: NY ny_heap_household_size: 2 output: - # 2021 guideline: 12,880 + 4,540. This checks lag mechanics, not verified - # historical New York policy coverage. + # 2021 HHS poverty guideline for two: 12,880 + 4,540 = 17,420. This checks + # the lag mechanics, not verified historical New York policy coverage. ny_heap_fpg: 17_420 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/integration.yaml index 4a5e3f28d09..d89f0c3b7e0 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/integration.yaml @@ -1,78 +1,323 @@ -- name: Excluded adult income counts in full while the citizen child determines size and vulnerability +# 2025-2026 HEAP benefit levels and monthly income guidelines (Clearinghouse +# benefit matrix): Utility $400; Oil, Kerosene, and Propane $900; Wood, +# Pellets, Coal, other fuels $635; +$35 vulnerable member; +$61 Tier I; heat +# included $50 (Tier I), $45 (Tier II), $21 (Heat and Eat). Monthly Tier I +# maximums 1,695 / 2,291 / 2,887 and Tier II maximums 3,473 / 4,542 / 5,611 +# for one to three people. +# https://liheapch.acf.gov/docs/2026/benefits-matricies/NY_BenefitMatrix_2026.docx +# FY2026 State Plan: categorical eligibility (1.4a, page 5), income limits +# (2.1, page 9), add-ons and the 130% Tier I line (pages 9 to 10). +# https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=5 +# HEAP Manual, Chapter 8 (PDF pages 34 to 47) and Chapter 9 B.3 (page 49). +# https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=34 +# The 2025 poverty guideline (15,650 + 5,500 per additional person) drives the +# Tier I line; snap, tanf and ssi are reported receipts so that each case rests +# on the HEAP rules alone. + +- name: Case 1, a nonqualified adult's income counts in full while the citizen child determines size and vulnerability. period: 2026 input: people: - parent: + person1: age: 40 - employment_income: 24000 + # Full-time wages of $2,000 a month. + employment_income: 24_000 immigration_status: UNDOCUMENTED - child: + person2: age: 5 immigration_status: CITIZEN spm_units: - unit: - members: - - parent - - child + spm_unit: + members: [person1, person2] heating_type: NATURAL_GAS - gas_expense: 100 + gas_expense: 1_500 tanf: 0 snap: 0 households: - home: - members: - - parent - - child + household: + members: [person1, person2] state_code: NY output: + # Chapter 8 E.9(b) (page 45): size counts citizens and qualified + # noncitizens only. ny_heap_household_size: 1 - ny_heap_countable_income: 24000 - ny_heap_income_limit: 41676 + # D.4(a)(7) (page 36) and E.4 (page 44): the nonqualified member's income + # is counted: 24,000. + ny_heap_countable_income: 24_000 + # One-person Tier II maximum 3,473 x 12. + ny_heap_income_limit: 41_676 + # 2,000 a month exceeds the one-person Tier I maximum of 1,695. ny_heap_tier_one: false + # Chapter 9 B.3(b)(1) (page 49): the citizen child is under six. ny_heap_vulnerable: true ny_heap_eligible: true + # Utility 400 + 35 = 435. ny_heap: 435 -- name: No qualified household members means no benefit + +- name: Case 2, no qualified household member means no benefit. period: 2026 input: state_code: NY immigration_status: UNDOCUMENTED age: 40 - employment_income: 0 + # Wages of $2,500 a month, within the one-person limit, so only the member + # rule denies the benefit. + employment_income: 30_000 ssi: 0 snap: 0 tanf: 0 - has_heating_expense: true heating_type: NATURAL_GAS + gas_expense: 1_500 output: ny_heap_household_size: 0 + # Chapter 8 E.8(a) (page 45): no benefit is issued when all household + # members are nonqualified. ny_heap_eligible: false ny_heap: 0 -- name: Only a nonqualified elderly member does not confer the vulnerable supplement + +- name: Case 3, only a nonqualified elderly member does not confer the vulnerable supplement. period: 2026 input: people: - adult: + person1: age: 40 - elder: + # Full-time wages of $2,000 a month. + employment_income: 24_000 + ssi: 0 + person2: age: 70 immigration_status: UNDOCUMENTED + ssi: 0 spm_units: - unit: - members: - - adult - - elder + spm_unit: + members: [person1, person2] heating_type: NATURAL_GAS - has_heating_expense: true - ny_heap_countable_income: 24000 - ny_heap_categorically_eligible: false + gas_expense: 1_500 + snap: 0 + tanf: 0 households: - home: - members: - - adult - - elder + household: + members: [person1, person2] state_code: NY output: + # E.9(b) (page 45): only the citizen counts. ny_heap_household_size: 1 + ny_heap_countable_income: 24_000 + ny_heap_categorically_eligible: false + # 2,000 a month exceeds the one-person Tier I maximum of 1,695. + ny_heap_tier_one: false + # E.9(c) (page 45): "If the only vulnerable individual is a non-qualified + # alien, the add-on for vulnerability is not included". ny_heap_vulnerable: false + ny_heap_eligible: true + # Utility base $400. ny_heap: 400 + +- name: Case 4, a SNAP recipient's household above the income limit is categorically eligible and paid the Tier I supplement. + period: 2026 + input: + people: + person1: + age: 44 + # Full-time wages of $4,800 a month. + employment_income: 57_600 + ssi: 0 + person2: + age: 39 + # Part-time wages of $1,200 a month. + employment_income: 14_400 + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_600 + # The brothers buy and prepare food separately; the second receives + # about $75 a month of SNAP on his own case (reported receipt). + snap: 900 + tanf: 0 + households: + household: + members: [person1, person2] + state_code: NY + output: + ny_heap_household_size: 2 + ny_heap_countable_earned_income: [57_600, 14_400] + # 57,600 + 14,400 = 72,000, which is 6,000 a month. + ny_heap_countable_income: 72_000 + # 2025 guideline for two: 15,650 + 5,500. + ny_heap_fpg: 21_150 + # Two-person Tier II maximum 4,542 x 12; 6,000 a month exceeds it. + ny_heap_income_limit: 54_504 + # Chapter 8 D.10(c) (page 37) and State Plan 1.4a (page 5): one member's + # SNAP receipt qualifies the household. + ny_heap_categorically_eligible: true + # State Plan page 10: the $61 add-on also applies to CE households. + ny_heap_tier_one: true + # Aged 44 and 39, with no child or qualifying disability. + ny_heap_vulnerable: false + ny_heap_eligible: true + # Utility 400 + 61 = 461. + ny_heap: 461 + +- name: Case 5, a household above the income limit without categorical eligibility is denied. + period: 2026 + input: + people: + person1: + age: 38 + # Full-time wages of $4,000 a month. + employment_income: 48_000 + ssi: 0 + person2: + age: 36 + # Full-time wages of $2,500 a month. + employment_income: 30_000 + ssi: 0 + person3: + age: 7 + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: NATURAL_GAS + gas_expense: 1_600 + snap: 0 + tanf: 0 + households: + household: + members: [person1, person2, person3] + state_code: NY + output: + ny_heap_household_size: 3 + # 48,000 + 30,000 = 78,000, which is 6,500 a month. + ny_heap_countable_income: 78_000 + # 2025 guideline for three: 15,650 + 2 x 5,500. + ny_heap_fpg: 26_650 + # Three-person Tier II maximum 5,611 x 12. + ny_heap_income_limit: 67_332 + ny_heap_categorically_eligible: false + # 6,500 a month exceeds the three-person Tier I maximum of 2,887. + ny_heap_tier_one: false + ny_heap_eligible: false + # Chapter 8 D.9(d) (page 37): denied for excess income. + ny_heap: 0 + +- name: Case 6, an older oil heater in Tier I receives both supplements. + period: 2026 + input: + people: + person1: + age: 63 + # Early Social Security retirement of $1,400 a month. + social_security_retirement: 16_800 + ssi: 0 + spm_units: + spm_unit: + members: [person1] + heating_type: FUEL_OIL + # About 750 gallons of heating oil at $4.00 a gallon. + fuel_oil_expense: 3_000 + snap: 0 + tanf: 0 + households: + household: + members: [person1] + state_code: NY + output: + ny_heap_household_size: 1 + # Chapter 8 D.12(a)(6) (page 38): Social Security, 16,800 (1,400 a month; + # no Medicare premium before age 65). + ny_heap_countable_income: 16_800 + # 2025 guideline for one. + ny_heap_fpg: 15_650 + # One-person Tier II maximum 3,473 x 12. + ny_heap_income_limit: 41_676 + ny_heap_categorically_eligible: false + # 1,400 a month is within the one-person Tier I maximum of 1,695 + # (floor(1.3 x 15,650 / 12)). + ny_heap_tier_one: true + # Chapter 9 B.3(b)(2) (page 49): aged 60 or older. + ny_heap_vulnerable: true + ny_heap_eligible: true + # Oil, Kerosene, and Propane with both add-ons: 900 + 61 + 35 = 996, the + # plan's maximum benefit (page 10). + ny_heap: 996 + +- name: Case 7, a renter with heat included in rent receives the Tier I heat-included amount. + period: 2026 + input: + people: + person1: + age: 31 + # $17.00 an hour (the 2026 New York City minimum wage) for 30 hours a + # week: 2,210 a month. + employment_income: 26_520 + ssi: 0 + person2: + age: 8 + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2] + # The building's boiler burns natural gas and the landlord pays for it. + heating_type: NATURAL_GAS + heat_expense_included_in_rent: true + snap: 0 + tanf: 0 + households: + household: + members: [person1, person2] + state_code: NY + output: + ny_heap_household_size: 2 + ny_heap_countable_income: 26_520 + # Two-person Tier II maximum 4,542 x 12. + ny_heap_income_limit: 54_504 + ny_heap_categorically_eligible: false + # 2,210 a month is within the two-person Tier I maximum of 2,291. + ny_heap_tier_one: true + # No bill of its own: heat is paid indirectly through rent. + has_heating_expense: false + # Chapter 8 F.1 (page 45): undesignated payments for heat in rent are an + # eligible living arrangement. + ny_heap_eligible: true + # Heat Included Regular Benefit Levels, Tier I: $50. + ny_heap: 50 + +- name: Case 8, a public housing tenant with heat included receives the nominal payment. + period: 2026 + input: + people: + person1: + age: 58 + # Wages of $1,800 a month. + employment_income: 21_600 + ssi: 0 + spm_units: + spm_unit: + members: [person1] + # The tenant does not know the building's fuel; heat is in the rent. + heating_type: UNSPECIFIED + heat_expense_included_in_rent: true + snap: 0 + tanf: 0 + households: + household: + members: [person1] + state_code: NY + is_in_public_housing: true + output: + ny_heap_household_size: 1 + ny_heap_countable_income: 21_600 + ny_heap_categorically_eligible: false + # 1,800 a month exceeds the one-person Tier I maximum of 1,695 and is + # within the 3,473 limit. + ny_heap_tier_one: false + # Aged 58, so not yet 60 (Chapter 9 B.3(b)(2), page 49). + ny_heap_vulnerable: false + ny_heap_eligible: true + # Government subsidized housing with heat included in the rent: $21 + # (Chapter 8 F.2(c) and F.3(a), pages 45 to 46; Heat and Eat Benefit in the + # matrix) instead of the Tier II heat-included $45. + ny_heap: 21 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/ny_heap.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/ny_heap.yaml index 7992de87b6e..e7b2caefe4a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/ny_heap.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/ny_heap.yaml @@ -1,105 +1,293 @@ -- name: Every fuel with Tier I supplement +# 2025-2026 HEAP Direct Heating Regular Base Benefit Levels and Heat Included +# Regular Benefit Levels (Clearinghouse benefit matrix): Utility $400; Oil, +# Kerosene, and Propane $900; Wood, Pellets, Coal, other fuels $635; +$35 for a +# vulnerable member; +$61 for Tier I; heat included $50 (Tier I), $45 (Tier II) +# and $21 (Heat and Eat). Tier I is income at or below 130% of the poverty +# guideline or categorical eligibility (FY2026 State Plan, page 10). +# https://liheapch.acf.gov/docs/2026/benefits-matricies/NY_BenefitMatrix_2026.docx +# https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=10 +# Living arrangements: HEAP Manual, Chapter 8 F (PDF pages 45 to 47). +# https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=45 + +- name: Case 1, every fuel with the Tier I supplement. period: 2026 input: state_code: [NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY] ny_heap_countable_income: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] ny_heap_categorically_eligible: [false, false, false, false, false, false, false, false, false, false, false] - has_heating_expense: [true, true, true, true, true, true, true, true, true, true, true] age: [40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40] heating_type: [ELECTRICITY, NATURAL_GAS, FUEL_OIL, KEROSENE, PROPANE, WOOD, COAL, OTHER, SOLAR, UNSPECIFIED, NONE] + # Each household sends its primary fuel's annual bill; an unspecified fuel + # and no heating have no fuel bill to send. + pre_subsidy_electricity_expense: [2_400, 0, 0, 0, 0, 0, 0, 0, 900, 0, 0] + gas_expense: [0, 1_500, 0, 0, 0, 0, 0, 0, 0, 0, 0] + fuel_oil_expense: [0, 0, 2_800, 2_800, 0, 0, 0, 0, 0, 0, 0] + bottled_gas_expense: [0, 0, 0, 0, 2_600, 0, 0, 0, 0, 0, 0] + wood_expense: [0, 0, 0, 0, 0, 1_200, 0, 0, 0, 0, 0] + coal_expense: [0, 0, 0, 0, 0, 0, 1_200, 0, 0, 0, 0] + other_heating_fuel_expense: [0, 0, 0, 0, 0, 0, 0, 1_300, 0, 0, 0] output: + # Zero income is Tier I. Tier I add-on column: Utility 400 + 61 = 461; + # Oil, Kerosene, and Propane 900 + 61 = 961; Wood, Pellets, Coal, other + # fuels 635 + 61 = 696 (the model places solar in the other-fuels row). + # Without a fuel bill the last two households have no responsibility for + # heating costs (Chapter 8 F.4(g), page 46). ny_heap: [461, 461, 961, 961, 961, 696, 696, 696, 696, 0, 0] -- name: Age vulnerability boundaries, Tier I and Tier II + +- name: Case 2, age vulnerability boundary at sixty, Tier I and Tier II. period: 2026 input: - state_code: [NY, NY, NY, NY, NY, NY] - ny_heap_countable_income: [0, 0, 0, 0, 24000, 24000] - ny_heap_categorically_eligible: [false, false, false, false, false, false] - has_heating_expense: [true, true, true, true, true, true] - age: [5, 6, 59, 60, 40, 40] - heating_type: [FUEL_OIL, FUEL_OIL, FUEL_OIL, FUEL_OIL, FUEL_OIL, FUEL_OIL] - social_security_disability: [0, 0, 0, 0, 0, 1200] + state_code: [NY, NY, NY, NY] + ny_heap_countable_income: [0, 0, 24_000, 24_000] + ny_heap_categorically_eligible: [false, false, false, false] + age: [59, 60, 40, 40] + heating_type: [FUEL_OIL, FUEL_OIL, FUEL_OIL, FUEL_OIL] + # About 700 gallons of heating oil at $4.00 a gallon. + fuel_oil_expense: [2_800, 2_800, 2_800, 2_800] + social_security_disability: [0, 0, 0, 1_200] output: - ny_heap: [996, 961, 961, 996, 900, 935] -- name: Heat included in rent by tier and housing assistance + # Chapter 9 B.3(b)(2) (page 49): aged 60 or older. Zero income is Tier I: + # 900 + 61 = 961 at 59 and 900 + 61 + 35 = 996 at 60. 2,000 a month is + # above the one-person Tier I maximum of 1,695: 900 alone, and 900 + 35 = + # 935 with Social Security disability receipt (B.3(b)(3)). + ny_heap: [961, 996, 900, 935] + +- name: Case 3, heat included in rent by tier and housing assistance. period: 2026 input: state_code: [NY, NY, NY, NY] - ny_heap_countable_income: [0, 24000, 0, 24000] + ny_heap_countable_income: [0, 24_000, 0, 24_000] ny_heap_categorically_eligible: [false, false, false, false] - has_heating_expense: [false, false, false, false] age: [40, 40, 40, 40] heat_expense_included_in_rent: [true, true, true, true] heating_type: [UNSPECIFIED, UNSPECIFIED, UNSPECIFIED, UNSPECIFIED] receives_housing_assistance: [false, false, true, true] output: + # Heat Included Regular Benefit Levels: Tier I $50 and Tier II $45 (2,000 a + # month is above the one-person Tier I maximum of 1,695). Tenants of + # subsidized housing with heat included receive the $21 nominal benefit in + # either tier (Chapter 8 F.2(c) and F.3(a), pages 45 to 46). ny_heap: [50, 45, 21, 21] -- name: Subsidized direct heaters receive ordinary fuel payments + +- name: Case 4, subsidized direct heaters receive ordinary fuel payments. period: 2026 input: state_code: NY ny_heap_countable_income: 0 ny_heap_categorically_eligible: false - has_heating_expense: true age: 40 heating_type: NATURAL_GAS + gas_expense: 1_500 receives_housing_assistance: true output: + # F.3(a) (page 46) limits the $21 to subsidized housing with heat included + # in the rent; a subsidized tenant who pays the gas utility directly + # receives Utility 400 + 61 (Tier I) = 461. ny_heap: 461 -- name: Income eligibility includes the published limit + +- name: Case 5, income eligibility includes the published limit. period: 2026 input: state_code: [NY, NY] - ny_heap_countable_income: [41676, 41688] + # The one-person monthly maximum 3,473 x 12, and one more monthly dollar. + ny_heap_countable_income: [41_676, 41_688] ny_heap_categorically_eligible: [false, false] - has_heating_expense: [true, true] age: [40, 40] heating_type: [ELECTRICITY, ELECTRICITY] + pre_subsidy_electricity_expense: [2_400, 2_400] output: + # Chapter 8 D.9(a) (page 37): income "at or below the maximum" is eligible, + # so 3,473 a month receives the Utility base $400 (Tier II, no add-ons), + # and 3,474 a month is denied for excess income under D.9(d). ny_heap: [400, 0] -- name: Regular benefit is not capped at the current heating bill + +- name: Case 6, the regular benefit is not capped at the current heating bill. period: 2026 input: state_code: NY ny_heap_countable_income: 0 ny_heap_categorically_eligible: false - has_heating_expense: true age: 40 heating_type: FUEL_OIL - fuel_oil_expense: 1 + # One 150-gallon delivery at about $4.00 a gallon. + fuel_oil_expense: 600 output: + # The benefit matrix pays a flat amount by fuel group: Oil 900 + 61 (Tier I) + # = 961 regardless of the bill. ny_heap: 961 -- name: Households with no heat responsibility receive zero + +- name: Case 7, households with no heat responsibility receive zero. period: 2026 input: state_code: NY ny_heap_countable_income: 0 ny_heap_categorically_eligible: false - has_heating_expense: false age: 40 heating_type: NATURAL_GAS + # Gas heat, but the household pays no gas bill and heat is not in its rent. + gas_expense: 0 output: + # Chapter 8 F.4(g) (page 46): households with no responsibility for heating + # costs that make no undesignated payments for heat in rent are ineligible. ny_heap: 0 -- name: Other states receive no New York payment + +- name: Case 8, other states receive no New York payment. period: 2026 input: state_code: NJ ny_heap_countable_income: 0 ny_heap_categorically_eligible: false - has_heating_expense: true age: 40 heating_type: NATURAL_GAS + gas_expense: 1_500 output: + # Chapter 8 C.1 (page 34): the applicant must be a New York State resident. ny_heap: 0 -- name: Published group and ineligible residence categories + +- name: Case 9, published group and ineligible residence categories. period: 2026 input: heating_type: [NATURAL_GAS, NATURAL_GAS] state_code: [NY, NY] ny_heap_countable_income: [0, 0] ny_heap_categorically_eligible: [false, false] - has_heating_expense: [false, false] age: [40, 40] ny_heap_dwelling_type: [ELIGIBLE_GROUP_RESIDENCE, INELIGIBLE_RESIDENCE] output: + # Chapter 8 F.3 (pages 45 to 46): the listed group residences receive a + # Regular benefit of $21; F.4 (pages 46 to 47): the listed arrangements are + # not eligible even when all other criteria are met. ny_heap: [21, 0] + +- name: Case 10, a child under six makes the household vulnerable. + period: 2026 + input: + people: + person1: + age: 30 + person2: + age: 5 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: FUEL_OIL + # About 700 gallons of heating oil at $4.00 a gallon. + fuel_oil_expense: 2_800 + # Parent's wages of $2,000 a month. + ny_heap_countable_income: 24_000 + ny_heap_categorically_eligible: false + households: + household: + members: [person1, person2] + state_code: NY + output: + # 2,000 a month is within the two-person Tier I maximum of 2,291, and + # Chapter 9 B.3(b)(1) (page 49) makes a child under six vulnerable: + # 900 + 61 + 35 = 996. + ny_heap: 996 + +- name: Case 11, a child aged six is not a vulnerable member. + period: 2026 + input: + people: + person1: + age: 30 + person2: + age: 6 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: FUEL_OIL + # About 700 gallons of heating oil at $4.00 a gallon. + fuel_oil_expense: 2_800 + # Parent's wages of $2,000 a month. + ny_heap_countable_income: 24_000 + ny_heap_categorically_eligible: false + households: + household: + members: [person1, person2] + state_code: NY + output: + # Tier I (2,000 a month is within 2,291 for two) with no vulnerable + # member, since B.3(b)(1) (page 49) requires "under age six": 900 + 61 = 961. + ny_heap: 961 + +- name: Case 12, an unspecified fuel pays nothing even when a heating cost is reported directly. + period: 2026 + input: + state_code: NY + ny_heap_countable_income: 0 + ny_heap_categorically_eligible: false + has_heating_expense: true + age: 40 + heating_type: UNSPECIFIED + output: + # The benefit matrix pays by fuel group; a household whose fuel is unknown + # cannot be placed in a row, so no direct heating payment is made. + ny_heap: 0 + +- name: Case 13, a SNAP recipient's household above the income limit is paid with the Tier I supplement. + period: 2026 + input: + people: + person1: + age: 46 + # Full-time wages of $5,200 a month. + employment_income: 62_400 + ssi: 0 + person2: + age: 63 + # Early Social Security retirement of $1,200 a month. + social_security_retirement: 14_400 + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2] + heating_type: WOOD + # About four and a half cords of firewood at $310 a cord. + wood_expense: 1_400 + # The parent buys and prepares food separately and receives the $24 + # minimum monthly SNAP allotment on her own case (reported receipt). + snap: 288 + tanf: 0 + households: + household: + members: [person1, person2] + state_code: NY + output: + ny_heap_household_size: 2 + # 62,400 wages + 14,400 Social Security (Chapter 8 D.12(a)(1) and (6), + # pages 37 to 38) = 76,800, which is 6,400 a month. + ny_heap_countable_income: 76_800 + # Two-person Tier II maximum 4,542 x 12 (2025-2026 HEAP Monthly Income + # Eligibility Guidelines); 6,400 a month exceeds it. + ny_heap_income_limit: 54_504 + # D.10(c) (page 37) and State Plan 1.4a (page 5): one member's SNAP receipt + # qualifies the household. + ny_heap_categorically_eligible: true + ny_heap_eligible: true + # State Plan page 10: the $61 add-on also applies to CE households. + ny_heap_tier_one: true + # Aged 63: Chapter 9 B.3(b)(2) (page 49). + ny_heap_vulnerable: true + # Wood, Pellets, Coal, other fuels with both add-ons: 635 + 61 + 35 = 731. + ny_heap: 731 + +- name: Case 14, public housing tenants receive the nominal payment only when heat is included in rent. + period: 2026 + input: + state_code: [NY, NY] + # Wages of $1,500 a month. + ny_heap_countable_income: [18_000, 18_000] + ny_heap_categorically_eligible: [false, false] + age: [40, 40] + is_in_public_housing: [true, true] + heat_expense_included_in_rent: [true, false] + heating_type: [UNSPECIFIED, NATURAL_GAS] + gas_expense: [0, 1_500] + output: + # 1,500 a month is Tier I (one-person maximum 1,695). Government subsidized + # housing with heat included in the rent receives $21 (Chapter 8 F.3(a), + # page 46) instead of the Tier I heat-included $50; a public housing tenant + # who pays the gas utility directly receives Utility 400 + 61 = 461. + ny_heap: [21, 461] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/payment/ny_heap_tier_one.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/payment/ny_heap_tier_one.yaml index a2aeeddae78..bacecda7844 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/payment/ny_heap_tier_one.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/payment/ny_heap_tier_one.yaml @@ -1,19 +1,35 @@ -- name: All published Tier I limits are inclusive +# 2025-2026 HEAP Monthly Income Eligibility Guidelines (Clearinghouse benefit +# matrix), Tier I column: 0 to the listed maximum, which is 130% of the 2025 +# poverty guideline rounded down to the month (FY2026 State Plan, page 10: +# "at or below 130% of the Federal Poverty Level"). +# https://liheapch.acf.gov/docs/2026/benefits-matricies/NY_BenefitMatrix_2026.docx +# https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=10 + +- name: Case 1, all published Tier I maximums are inclusive. period: 2026 input: heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] state_code: [NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY] ny_heap_household_size: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13] - ny_heap_countable_income: [20340, 27492, 34644, 41784, 48936, 56088, 63240, 70392, 77544, 84684, 91836, 98988, 106140, 20352, 27504, 34656, 41796, 48948, 56100, 63252, 70404, 77556, 84696, 91848, 99000, 106152] + # Published monthly Tier I maximums 1,695; 2,291; 2,887; 3,482; 4,078; + # 4,674; 5,270; 5,866; 6,462; 7,057; 7,653; 8,249; 8,845 (x 12), then one + # more monthly dollar for each size (x 12). + ny_heap_countable_income: [20_340, 27_492, 34_644, 41_784, 48_936, 56_088, 63_240, 70_392, 77_544, 84_684, 91_836, 98_988, 106_140, 20_352, 27_504, 34_656, 41_796, 48_948, 56_100, 63_252, 70_404, 77_556, 84_696, 91_848, 99_000, 106_152] ny_heap_categorically_eligible: [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false] output: + # The matrix's Tier I column ends at the maximum, so the maximum itself is + # Tier I and one more dollar a month is Tier II. ny_heap_tier_one: [true, true, true, true, true, true, true, true, true, true, true, true, true, false, false, false, false, false, false, false, false, false, false, false, false, false] -- name: Categorical eligibility confers Tier I despite high income + +- name: Case 2, categorical eligibility confers Tier I above the Tier I maximum. period: 2026 input: heating_type: NATURAL_GAS state_code: NY - ny_heap_countable_income: 200000 + # $2,500 a month, above the one-person Tier I maximum of 1,695. + ny_heap_countable_income: 30_000 ny_heap_categorically_eligible: true output: + # State Plan page 10: the Tier I add-on "is also applied to Regular heater + # benefits received by CE households". ny_heap_tier_one: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/payment/ny_heap_vulnerable.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/payment/ny_heap_vulnerable.yaml index 96298408715..11fa4e96e45 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/payment/ny_heap_vulnerable.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/payment/ny_heap_vulnerable.yaml @@ -1,25 +1,37 @@ -- name: Existing qualifying benefit pathways establish HEAP disability +# HEAP Manual, Chapter 9 B.3(b) (PDF page 49): vulnerable members are children +# under six, people aged 60 or older, and people receiving SSI or Social +# Security disability payments or meeting the listed VA conditions; Chapter 8 +# E.9(c) (page 45) ignores a nonqualified member's vulnerability. +# https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=49 + +- name: Case 1, existing qualifying benefit pathways establish HEAP disability. period: 2026 input: state_code: [NY, NY, NY, NY, NY, NY, NY] heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] age: [40, 40, 40, 40, 40, 40, 40] receives_ssi: [true, false, false, false, false, false, false] - ssi: [0, 1200, 0, 0, 0, 0, 0] - social_security_disability: [0, 0, 1200, 0, 0, 0, 0] + ssi: [0, 1_200, 0, 0, 0, 0, 0] + social_security_disability: [0, 0, 1_200, 0, 0, 0, 0] is_permanently_disabled_veteran: [false, false, false, true, false, false, false] is_surviving_spouse_of_disabled_veteran: [false, false, false, false, true, false, false] is_surviving_child_of_disabled_veteran: [false, false, false, false, false, true, false] is_permanently_and_totally_disabled: [false, false, false, false, false, false, true] output: + # B.3(b)(3): reported SSI, $100 a month of SSI, $100 a month of Social + # Security disability and the three VA conditions each establish + # disability; a disability flag without benefit receipt does not. ny_heap_vulnerable: [true, true, true, true, true, true, false] -- name: A nonqualified disabled member does not establish vulnerability + +- name: Case 2, a nonqualified disabled member does not establish vulnerability. period: 2026 input: state_code: NY heating_type: NATURAL_GAS age: 40 is_citizen_or_legal_immigrant: false - ssi: 1200 + ssi: 1_200 output: + # E.9(c) (page 45): "If the only vulnerable individual is a non-qualified + # alien, the add-on for vulnerability is not included". ny_heap_vulnerable: false diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_income.py b/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_income.py index 413c1da1cda..9c1e6291a44 100644 --- a/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_income.py +++ b/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_income.py @@ -28,8 +28,11 @@ def formula(spm_unit, period, parameters): ) * included ) - # The annual TANF aggregate applies take-up; ny_tanf alone is entitlement. - income = income + spm_unit("tanf", period) - # D.12 and D.13 permit actual Medicare B/D deductions. They remain - # deferred: neither modeled nor reported premiums are deducted here. + # TANF is not counted: FY2026 State Plan item 1.9 (page 6) leaves the + # TANF box unchecked, and Chapter 8 D.11(c)(1) (page 37) counts a TA + # grant only for a minor child budgeted as a roomer, which has no input. + # D.12(a)(6) (page 38) counts Social Security after Medicare Part B and + # D premiums. medicare_part_b_premium and + # medicare_part_b_premiums_reported are not wired in yet, so Social + # Security counts gross. return np.floor(max_(income, 0) / MONTHS_IN_YEAR) * MONTHS_IN_YEAR From 824c7941f457c5702453a8204b4f94534b686b0d Mon Sep 17 00:00:00 2001 From: Ziming Date: Mon, 5 Oct 2026 22:57:31 -0400 Subject: [PATCH 6/7] Tidy New York HEAP parameters and tests Write parameter date keys unquoted, wrap each variable's documentation string across lines, and split the household-size sweeps between files so each size is asserted once: odd sizes in the income-limit test, even sizes in the Tier I maximum test. Co-Authored-By: Claude Fable 5.1 --- .../ny/otda/heap/eligibility/fpg_rate.yaml | 2 +- .../otda/heap/eligibility/fpg_year_lag.yaml | 2 +- .../ny/otda/heap/eligibility/smi_rate.yaml | 2 +- .../otda/heap/income/dependent_child_age.yaml | 2 +- .../ny/otda/heap/income/sources/earned.yaml | 2 +- .../ny/otda/heap/income/sources/unearned.yaml | 2 +- .../gov/states/ny/otda/heap/payment/base.yaml | 22 +++++++++---------- .../ny/otda/heap/payment/elderly_age.yaml | 2 +- .../heap/payment/heat_included/nominal.yaml | 2 +- .../heap/payment/heat_included/tier_one.yaml | 2 +- .../heap/payment/heat_included/tier_two.yaml | 2 +- .../otda/heap/payment/tier_one_fpg_rate.yaml | 2 +- .../heap/payment/tier_one_supplement.yaml | 2 +- .../heap/payment/vulnerable_supplement.yaml | 2 +- .../ny/otda/heap/payment/young_child_age.yaml | 2 +- .../eligibility/ny_heap_income_limit.yaml | 16 ++++++++------ .../otda/heap/payment/ny_heap_tier_one.yaml | 22 ++++++++++--------- .../ny_heap_categorically_eligible.py | 8 ++++++- .../otda/heap/eligibility/ny_heap_eligible.py | 6 ++++- .../eligibility/ny_heap_household_size.py | 8 ++++++- .../income/ny_heap_countable_earned_income.py | 6 ++++- .../heap/income/ny_heap_countable_income.py | 9 +++++++- .../gov/states/ny/otda/heap/ny_heap.py | 10 ++++++++- .../ny/otda/heap/ny_heap_dwelling_type.py | 9 +++++++- .../otda/heap/payment/ny_heap_vulnerable.py | 8 ++++++- 25 files changed, 102 insertions(+), 50 deletions(-) diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/fpg_rate.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/fpg_rate.yaml index a99ef3c1dfb..a73eb423e48 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/fpg_rate.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/fpg_rate.yaml @@ -1,6 +1,6 @@ description: New York uses this poverty guideline rate when it exceeds the state median income limit under the Home Energy Assistance Program. values: - '2025-10-01': 1.5 + 2025-10-01: 1.5 metadata: unit: /1 period: year diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/fpg_year_lag.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/fpg_year_lag.yaml index 12736ca4f01..aac7fff5659 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/fpg_year_lag.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/fpg_year_lag.yaml @@ -1,6 +1,6 @@ description: New York uses the poverty guidelines from this many years before the model year under the Home Energy Assistance Program. values: - '2025-10-01': 1 + 2025-10-01: 1 metadata: unit: year period: year diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/smi_rate.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/smi_rate.yaml index 884c86b2b79..d1bd1be8e7f 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/smi_rate.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/smi_rate.yaml @@ -1,6 +1,6 @@ description: New York limits noncategorical income using this share of state median income under the Home Energy Assistance Program. values: - '2025-10-01': 0.6 + 2025-10-01: 0.6 metadata: unit: /1 period: year diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/income/dependent_child_age.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/income/dependent_child_age.yaml index 1410e01ef81..d42d4c80024 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/income/dependent_child_age.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/income/dependent_child_age.yaml @@ -1,6 +1,6 @@ description: New York excludes earnings of dependent children below this age under the Home Energy Assistance Program. values: - '2025-10-01': 18 + 2025-10-01: 18 metadata: unit: year period: year diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/earned.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/earned.yaml index 4ddda3eb7f8..96e80ec44ce 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/earned.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/earned.yaml @@ -1,6 +1,6 @@ description: New York uses these reported earnings inputs without an additional business expense deduction under the Home Energy Assistance Program. values: - '2025-10-01': + 2025-10-01: - employment_income - self_employment_income - sstb_self_employment_income diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/unearned.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/unearned.yaml index e9c312b4a8b..01b9c640387 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/unearned.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/unearned.yaml @@ -1,6 +1,6 @@ description: New York counts these available unearned income sources under the Home Energy Assistance Program. values: - '2025-10-01': + 2025-10-01: # D.12(a)(6) and (8) count Social Security and railroad benefits after # Medicare Part B and D premiums. medicare_part_b_premium and # medicare_part_b_premiums_reported are not wired in yet, so both count gross. diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/base.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/base.yaml index bd8cfb7612b..0306670f385 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/base.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/base.yaml @@ -1,26 +1,26 @@ description: New York provides this base annual direct heating payment under the Home Energy Assistance Program, by primary heating fuel. ELECTRICITY: - '2025-10-01': 400 + 2025-10-01: 400 NATURAL_GAS: - '2025-10-01': 400 + 2025-10-01: 400 FUEL_OIL: - '2025-10-01': 900 + 2025-10-01: 900 KEROSENE: - '2025-10-01': 900 + 2025-10-01: 900 PROPANE: - '2025-10-01': 900 + 2025-10-01: 900 WOOD: - '2025-10-01': 635 + 2025-10-01: 635 COAL: - '2025-10-01': 635 + 2025-10-01: 635 OTHER: - '2025-10-01': 635 + 2025-10-01: 635 SOLAR: - '2025-10-01': 635 + 2025-10-01: 635 UNSPECIFIED: - '2025-10-01': 0 + 2025-10-01: 0 NONE: - '2025-10-01': 0 + 2025-10-01: 0 metadata: unit: currency-USD period: year diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/elderly_age.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/elderly_age.yaml index 6e4d7df7c77..996e47c5be6 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/elderly_age.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/elderly_age.yaml @@ -1,6 +1,6 @@ description: New York treats people at or above this age as vulnerable under the Home Energy Assistance Program. values: - '2025-10-01': 60 + 2025-10-01: 60 metadata: unit: year period: year diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/nominal.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/nominal.yaml index 1916375278d..bab7163516b 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/nominal.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/nominal.yaml @@ -1,6 +1,6 @@ description: New York pays this regular amount to specified group residences and subsidized households with heat included under the Home Energy Assistance Program. values: - '2025-10-01': 21 + 2025-10-01: 21 metadata: unit: currency-USD period: year diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/tier_one.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/tier_one.yaml index 5831b538e9e..cefbd588a02 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/tier_one.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/tier_one.yaml @@ -1,6 +1,6 @@ description: New York pays this regular amount to Tier I households with heat included in unsubsidized rent under the Home Energy Assistance Program. values: - '2025-10-01': 50 + 2025-10-01: 50 metadata: unit: currency-USD period: year diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/tier_two.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/tier_two.yaml index 57942aa5bac..c8833a2187a 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/tier_two.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/tier_two.yaml @@ -1,6 +1,6 @@ description: New York pays this regular amount to Tier II households with heat included in unsubsidized rent under the Home Energy Assistance Program. values: - '2025-10-01': 45 + 2025-10-01: 45 metadata: unit: currency-USD period: year diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_fpg_rate.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_fpg_rate.yaml index 102c028dd41..a2d7bedb681 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_fpg_rate.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_fpg_rate.yaml @@ -1,6 +1,6 @@ description: New York assigns noncategorical households to Tier I at or below this share of the poverty guideline under the Home Energy Assistance Program. values: - '2025-10-01': 1.3 + 2025-10-01: 1.3 metadata: unit: /1 period: year diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_supplement.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_supplement.yaml index 7a26f23578c..d67cca1bf68 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_supplement.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_supplement.yaml @@ -1,6 +1,6 @@ description: New York adds this amount to direct heating payments for Tier I households under the Home Energy Assistance Program. values: - '2025-10-01': 61 + 2025-10-01: 61 metadata: unit: currency-USD period: year diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/vulnerable_supplement.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/vulnerable_supplement.yaml index a2c5850e63b..111f62d74fa 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/vulnerable_supplement.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/vulnerable_supplement.yaml @@ -1,6 +1,6 @@ description: New York adds this amount once per direct heating household with a qualified vulnerable member under the Home Energy Assistance Program. values: - '2025-10-01': 35 + 2025-10-01: 35 metadata: unit: currency-USD period: year diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/young_child_age.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/young_child_age.yaml index bca38a3b9b6..5b0a8dfd686 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/young_child_age.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/young_child_age.yaml @@ -1,6 +1,6 @@ description: New York treats children below this age as vulnerable under the Home Energy Assistance Program. values: - '2025-10-01': 6 + 2025-10-01: 6 metadata: unit: year period: year diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_income_limit.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_income_limit.yaml index 17c17579f57..53c80093771 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_income_limit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_income_limit.yaml @@ -7,16 +7,18 @@ # https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9 # https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=37 -- name: Case 1, all published size one through thirteen income limits. +- name: Case 1, odd household sizes one through thirteen published income limits. period: 2026 input: - heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] - state_code: [NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY] - ny_heap_household_size: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13] + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] + state_code: [NY, NY, NY, NY, NY, NY, NY] + ny_heap_household_size: [1, 3, 5, 7, 9, 11, 13] output: - # Published monthly Tier II maximums 3,473; 4,542; 5,611; 6,680; 7,749; - # 8,818; 9,018; 9,218; 9,419; 9,619; 9,820; 10,020; 10,221, each x 12. - ny_heap_income_limit: [41_676, 54_504, 67_332, 80_160, 92_988, 105_816, 108_216, 110_616, 113_028, 115_428, 117_840, 120_240, 122_652] + # Odd sizes 1, 3, 5, 7, 9, 11 and 13; payment/ny_heap_tier_one.yaml covers + # the even sizes of the same matrix. Published monthly Tier II maximums + # 3,473; 5,611; 7,749; 9,018; 9,419; 9,820; 10,221 (benefit matrix Tier II + # column; State Plan 2.1, page 9), each x 12. + ny_heap_income_limit: [41_676, 67_332, 92_988, 108_216, 113_028, 117_840, 122_652] - name: Case 2, size fourteen follows the plan poverty rate rather than the matrix increment. period: 2026 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/payment/ny_heap_tier_one.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/payment/ny_heap_tier_one.yaml index bacecda7844..6d04ba088a0 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/payment/ny_heap_tier_one.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/payment/ny_heap_tier_one.yaml @@ -5,21 +5,23 @@ # https://liheapch.acf.gov/docs/2026/benefits-matricies/NY_BenefitMatrix_2026.docx # https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=10 -- name: Case 1, all published Tier I maximums are inclusive. +- name: Case 1, even household size published Tier I maximums are inclusive. period: 2026 input: - heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] - state_code: [NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY] - ny_heap_household_size: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13] - # Published monthly Tier I maximums 1,695; 2,291; 2,887; 3,482; 4,078; - # 4,674; 5,270; 5,866; 6,462; 7,057; 7,653; 8,249; 8,845 (x 12), then one - # more monthly dollar for each size (x 12). - ny_heap_countable_income: [20_340, 27_492, 34_644, 41_784, 48_936, 56_088, 63_240, 70_392, 77_544, 84_684, 91_836, 98_988, 106_140, 20_352, 27_504, 34_656, 41_796, 48_948, 56_100, 63_252, 70_404, 77_556, 84_696, 91_848, 99_000, 106_152] - ny_heap_categorically_eligible: [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false] + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] + state_code: [NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY] + ny_heap_household_size: [2, 4, 6, 8, 10, 12, 2, 4, 6, 8, 10, 12] + # Even sizes 2, 4, 6, 8, 10 and 12; eligibility/ny_heap_income_limit.yaml + # covers the odd sizes of the same matrix. Published monthly Tier I + # maximums 2,291; 3,482; 4,674; 5,866; 7,057; 8,249 (benefit matrix Tier I + # column; State Plan, page 10) x 12, then one more monthly dollar for each + # size (x 12). + ny_heap_countable_income: [27_492, 41_784, 56_088, 70_392, 84_684, 98_988, 27_504, 41_796, 56_100, 70_404, 84_696, 99_000] + ny_heap_categorically_eligible: [false, false, false, false, false, false, false, false, false, false, false, false] output: # The matrix's Tier I column ends at the maximum, so the maximum itself is # Tier I and one more dollar a month is Tier II. - ny_heap_tier_one: [true, true, true, true, true, true, true, true, true, true, true, true, true, false, false, false, false, false, false, false, false, false, false, false, false, false] + ny_heap_tier_one: [true, true, true, true, true, true, false, false, false, false, false, false] - name: Case 2, categorical eligibility confers Tier I above the Tier I maximum. period: 2026 diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.py b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.py index 0a0f6d7e82f..29b6bbf999b 100644 --- a/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.py +++ b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.py @@ -12,7 +12,13 @@ class ny_heap_categorically_eligible(Variable): # PDF pages 37, 110. "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=37", ) - documentation = "Receipt of SNAP, TANF or SSI in a federal own-household arrangement. State SSI Code A and recurring receipt on the application date are approximated; expedited/emergency-only benefits and roomer-only awards cannot be distinguished. Chapter 8 D.10 also qualifies Safety Net Assistance, but no NY-specific receipt input identifies it." + documentation = ( + "Receipt of SNAP, TANF or SSI in a federal own-household arrangement. State " + "SSI Code A and recurring receipt on the application date are approximated; " + "expedited/emergency-only benefits and roomer-only awards cannot be " + "distinguished. Chapter 8 D.10 also qualifies Safety Net Assistance, but no " + "NY-specific receipt input identifies it." + ) def formula(spm_unit, period, parameters): person = spm_unit.members diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_eligible.py b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_eligible.py index 8e399128c43..0e6b816e07f 100644 --- a/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_eligible.py +++ b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_eligible.py @@ -11,7 +11,11 @@ class ny_heap_eligible(Variable): # PDF pages 34, 37, 44, 45, 46, 47, 48. "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=34", ) - documentation = "Regular heating has no resource test. SPM units approximate energy-sharing households. Application timing, SSN documentation, duplicate payments and the full set of excluded member categories are not modeled." + documentation = ( + "Regular heating has no resource test. SPM units approximate energy-sharing " + "households. Application timing, SSN documentation, duplicate payments and the " + "full set of excluded member categories are not modeled." + ) def formula(spm_unit, period, parameters): dwelling = spm_unit("ny_heap_dwelling_type", period) diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.py b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.py index 393a1fa0377..35c538a8150 100644 --- a/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.py +++ b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.py @@ -9,7 +9,13 @@ class ny_heap_household_size(Variable): defined_for = StateCode.NY # Chapter 8 D.4 and D.9, PDF pages 34, 36, 37, 44. reference = ("https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=34",) - documentation = "SPM members who are citizens or federally qualified noncitizens. Nonqualified members' income still counts in full. Foster members and federal Code C SSI recipients are excluded using existing inputs. Roomers, employees and fleeing felons remain unsupported. The existing immigration enum does not separately identify every additional federally protected status." + documentation = ( + "SPM members who are citizens or federally qualified noncitizens. Nonqualified " + "members' income still counts in full. Foster members and federal Code C SSI " + "recipients are excluded using existing inputs. Roomers, employees and fleeing " + "felons remain unsupported. The existing immigration enum does not separately " + "identify every additional federally protected status." + ) def formula(spm_unit, period, parameters): person = spm_unit.members diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_earned_income.py b/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_earned_income.py index ea2f6d4af96..1cc0e56e19e 100644 --- a/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_earned_income.py +++ b/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_earned_income.py @@ -10,7 +10,11 @@ class ny_heap_countable_earned_income(Variable): defined_for = StateCode.NY # PDF pages 37, 39, 42, 43. reference = ("https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=37",) - documentation = "Existing net business income is used without further deductions. HEAP-specific depreciation add-backs, separate accounting periods and wage bonus exclusions are unsupported." + documentation = ( + "Existing net business income is used without further deductions. " + "HEAP-specific depreciation add-backs, separate accounting periods and wage " + "bonus exclusions are unsupported." + ) def formula(person, period, parameters): p = parameters(period).gov.states.ny.otda.heap.income diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_income.py b/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_income.py index 9c1e6291a44..3b5c9f2588f 100644 --- a/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_income.py +++ b/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_income.py @@ -12,7 +12,14 @@ class ny_heap_countable_income(Variable): # PDF pages 36, 37, 38, 39, 40, 41, 42, 43, 44. "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=37", ) - documentation = "Annual inputs approximate application-month income; final monthly income is rounded down and annualized. Income of nonqualified members counts in full. Medicare-premium deductions remain deferred. Royalties, regular gifts, aid-and-attendance exclusions and irregular-income exclusions cannot be isolated reliably. Rental income is assumed nonnegative. No new self-employment or work-expense deduction is applied." + documentation = ( + "Annual inputs approximate application-month income; final monthly income is " + "rounded down and annualized. Income of nonqualified members counts in full. " + "Medicare-premium deductions remain deferred. Royalties, regular gifts, " + "aid-and-attendance exclusions and irregular-income exclusions cannot be " + "isolated reliably. Rental income is assumed nonnegative. No new " + "self-employment or work-expense deduction is applied." + ) def formula(spm_unit, period, parameters): p = parameters(period).gov.states.ny.otda.heap.income diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/ny_heap.py b/policyengine_us/variables/gov/states/ny/otda/heap/ny_heap.py index aef1d119d74..c9a743d615f 100644 --- a/policyengine_us/variables/gov/states/ny/otda/heap/ny_heap.py +++ b/policyengine_us/variables/gov/states/ny/otda/heap/ny_heap.py @@ -15,7 +15,15 @@ class ny_heap(Variable): # PDF pages 45, 46, 47, 48, 49, 84, 87, 88. "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=45", ) - documentation = "Annual regular heating benefit and ordinary Tier I/vulnerability supplements, verified for FY2026. Earlier years are unverified backfilled estimates. Housing assistance approximates subsidized rent (market-rent voucher exceptions are unsupported). Unknown direct-heating fuel returns zero; solar falls within other fuels. No bill cap. Crisis, cooling and equipment components are excluded. Mid-year moves and previously advanced nominal payments are not separately tracked." + documentation = ( + "Annual regular heating benefit and ordinary Tier I/vulnerability supplements, " + "verified for FY2026. Earlier years are unverified backfilled estimates. " + "Housing assistance approximates subsidized rent (market-rent voucher " + "exceptions are unsupported). Unknown direct-heating fuel returns zero; solar " + "falls within other fuels. No bill cap. Crisis, cooling and equipment " + "components are excluded. Mid-year moves and previously advanced nominal " + "payments are not separately tracked." + ) def formula(spm_unit, period, parameters): p = parameters(period).gov.states.ny.otda.heap.payment diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/ny_heap_dwelling_type.py b/policyengine_us/variables/gov/states/ny/otda/heap/ny_heap_dwelling_type.py index 5384f8b1c69..e7a85385f2e 100644 --- a/policyengine_us/variables/gov/states/ny/otda/heap/ny_heap_dwelling_type.py +++ b/policyengine_us/variables/gov/states/ny/otda/heap/ny_heap_dwelling_type.py @@ -18,4 +18,11 @@ class ny_heap_dwelling_type(Variable): label = "New York HEAP dwelling arrangement" # PDF pages 45, 46, 47. reference = "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=45" - documentation = "Eligible group residences are the treatment, enriched housing, supervised/supportive living and other settings listed in Chapter 8 F.3 (group living facilities must have at most 16 residents). Ineligible residences include private roomers/boarders, hotels, motels, vehicles, dormitories and congregate care under F.4. Ordinary subsidized housing uses the existing housing-assistance and heat-in-rent inputs." + documentation = ( + "Eligible group residences are the treatment, enriched housing, " + "supervised/supportive living and other settings listed in Chapter 8 F.3 " + "(group living facilities must have at most 16 residents). Ineligible " + "residences include private roomers/boarders, hotels, motels, vehicles, " + "dormitories and congregate care under F.4. Ordinary subsidized housing uses " + "the existing housing-assistance and heat-in-rent inputs." + ) diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/payment/ny_heap_vulnerable.py b/policyengine_us/variables/gov/states/ny/otda/heap/payment/ny_heap_vulnerable.py index a769e659bed..065da22f704 100644 --- a/policyengine_us/variables/gov/states/ny/otda/heap/payment/ny_heap_vulnerable.py +++ b/policyengine_us/variables/gov/states/ny/otda/heap/payment/ny_heap_vulnerable.py @@ -9,7 +9,13 @@ class ny_heap_vulnerable(Variable): defined_for = StateCode.NY # PDF pages 44, 45, 49. reference = ("https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=44",) - documentation = "The manual adopts the SNAP benefit-receipt disability criteria. Reuses the existing USDA calculation, including its qualifying veteran and survivor flags. A disability flag alone does not establish receipt or certification. The existing USDA flags approximate detailed VA certification; railroad, FECA and disability-based Medicaid pathways remain incomplete." + documentation = ( + "The manual adopts the SNAP benefit-receipt disability criteria. Reuses the " + "existing USDA calculation, including its qualifying veteran and survivor " + "flags. A disability flag alone does not establish receipt or certification. " + "The existing USDA flags approximate detailed VA certification; railroad, FECA " + "and disability-based Medicaid pathways remain incomplete." + ) def formula(spm_unit, period, parameters): p = parameters(period).gov.states.ny.otda.heap.payment From 0478fcd90a1413b90708f173538eee440e4dbdc9 Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 6 Oct 2026 17:07:04 -0400 Subject: [PATCH 7/7] Address review round 1 on New York HEAP - Require Code A SSI recipients to live alone (one individual or one couple) for categorical eligibility and Tier I. - Count Social Security and railroad benefits net of the Medicare Part B premium; floor every unearned source. - Date the Tier I supplement ($41 FY2023-FY2024, $61 FY2025 on) and the base amounts from the FY2024 matrix; verified_years "2024-2026". - Add cases for the living-alone arrangements, FY2024 and FY2025 limits and Tier I lines, a rental loss, an ineligible residence with a bill, heat in rent at 65 and a reported Code C child. - Re-template descriptions, cite 18 NYCRR 393.4 and Attachment 4, and fix the reference anchors. Co-Authored-By: Claude Fable 5.1 --- changelog.d/ny-heap.added.md | 2 +- .../ny/otda/heap/eligibility/fpg_rate.yaml | 2 +- .../otda/heap/eligibility/fpg_year_lag.yaml | 5 + .../ny/otda/heap/eligibility/smi_rate.yaml | 2 +- .../otda/heap/income/dependent_child_age.yaml | 2 +- .../ny/otda/heap/income/sources/earned.yaml | 5 +- .../ny/otda/heap/income/sources/unearned.yaml | 16 +-- .../gov/states/ny/otda/heap/payment/base.yaml | 33 +++--- .../ny/otda/heap/payment/elderly_age.yaml | 4 +- .../heap/payment/heat_included/nominal.yaml | 14 ++- .../heap/payment/heat_included/tier_one.yaml | 10 +- .../heap/payment/heat_included/tier_two.yaml | 10 +- .../otda/heap/payment/tier_one_fpg_rate.yaml | 2 +- .../heap/payment/tier_one_supplement.yaml | 11 +- .../heap/payment/vulnerable_supplement.yaml | 12 ++- .../ny/otda/heap/payment/young_child_age.yaml | 4 +- policyengine_us/programs.yaml | 4 +- .../ny_heap_categorically_eligible.yaml | 100 +++++++++++++++++- .../eligibility/ny_heap_household_size.yaml | 31 ++++++ .../eligibility/ny_heap_income_limit.yaml | 29 ++++- .../heap/income/ny_heap_countable_income.yaml | 63 +++++++++-- .../gov/states/ny/otda/heap/ny_heap.yaml | 41 ++++++- .../otda/heap/payment/ny_heap_tier_one.yaml | 47 +++++++- .../ny_heap_categorically_eligible.py | 31 ++++-- .../otda/heap/eligibility/ny_heap_eligible.py | 1 + .../eligibility/ny_heap_household_size.py | 7 +- .../heap/eligibility/ny_heap_income_limit.py | 6 +- .../eligibility/ny_heap_is_excluded_person.py | 13 ++- .../heap/income/ny_heap_countable_income.py | 37 ++++--- .../gov/states/ny/otda/heap/ny_heap.py | 18 ++-- .../ny/otda/heap/ny_heap_dwelling_type.py | 7 +- .../otda/heap/payment/ny_heap_vulnerable.py | 4 +- 32 files changed, 469 insertions(+), 104 deletions(-) diff --git a/changelog.d/ny-heap.added.md b/changelog.d/ny-heap.added.md index b008ee71556..d4032b14018 100644 --- a/changelog.d/ny-heap.added.md +++ b/changelog.d/ny-heap.added.md @@ -1 +1 @@ -Add New York HEAP regular heating assistance for FY2026. +Add New York HEAP regular heating assistance for FY2024 to FY2026. diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/fpg_rate.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/fpg_rate.yaml index a73eb423e48..35f8aa2b130 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/fpg_rate.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/fpg_rate.yaml @@ -6,7 +6,7 @@ metadata: period: year label: New York HEAP poverty guideline rate reference: - - title: HEAP Manual, Chapter 8 D.9(c) + - title: HEAP Manual (edition dated 7/1/2021), Chapter 8 D.9(c) href: https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=37 - title: FY2026 LIHEAP State Plan, section 2.1 href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9 diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/fpg_year_lag.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/fpg_year_lag.yaml index aac7fff5659..64a5139b710 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/fpg_year_lag.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/fpg_year_lag.yaml @@ -1,4 +1,7 @@ description: New York uses the poverty guidelines from this many years before the model year under the Home Energy Assistance Program. +# Model period Y uses the FFY Y matrix, whose Tier I lines are 130% of the +# guidelines published in January of Y-1: floor(1.3 x 15,650 / 12) = 1,695 for +# one person in FY2026. The FY2025 and FY2024 lines reproduce the same way. values: 2025-10-01: 1 metadata: @@ -8,3 +11,5 @@ metadata: reference: - title: 2025-2026 HEAP monthly income guidelines href: https://liheapch.acf.gov/docs/2026/benefits-matricies/NY_BenefitMatrix_2026.docx + - title: 2025 HHS poverty guidelines, 90 FR 5733, January 17, 2025 + href: https://www.govinfo.gov/content/pkg/FR-2025-01-17/pdf/2025-01377.pdf#page=1 diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/smi_rate.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/smi_rate.yaml index d1bd1be8e7f..41339fbb069 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/smi_rate.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/eligibility/smi_rate.yaml @@ -6,5 +6,5 @@ metadata: period: year label: New York HEAP SMI rate reference: - - title: FY2026 LIHEAP State Plan, regular heating assistance, PDF pages 9, 10 + - title: FY2026 LIHEAP State Plan, regular heating assistance, PDF page 9 href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9 diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/income/dependent_child_age.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/income/dependent_child_age.yaml index d42d4c80024..fe662a3a74e 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/income/dependent_child_age.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/income/dependent_child_age.yaml @@ -6,5 +6,5 @@ metadata: period: year label: New York HEAP dependent child age reference: - - title: HEAP Manual, Chapter 8 D.13(4)-(5) + - title: HEAP Manual (edition dated 7/1/2021), Chapter 8 D.13(a)(4)-(5) href: https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=39 diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/earned.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/earned.yaml index 96e80ec44ce..afb25cdb697 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/earned.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/earned.yaml @@ -1,4 +1,5 @@ -description: New York uses these reported earnings inputs without an additional business expense deduction under the Home Energy Assistance Program. +description: New York uses these sources as earned income under the Home Energy Assistance Program. +# Existing net business inputs are used without a further expense deduction. values: 2025-10-01: - employment_income @@ -11,5 +12,5 @@ metadata: period: year label: New York HEAP earned sources reference: - - title: HEAP Manual, Chapter 8 D.12 and D.14, PDF pages 37, 38, 42, 43 + - title: HEAP Manual (edition dated 7/1/2021), Chapter 8 D.12 and D.14, PDF pages 37, 38, 42, 43 href: https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=37 diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/unearned.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/unearned.yaml index 01b9c640387..95dc3e60e45 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/unearned.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/income/sources/unearned.yaml @@ -1,11 +1,8 @@ -description: New York counts these available unearned income sources under the Home Energy Assistance Program. +description: New York uses these sources as unearned income under the Home Energy Assistance Program. values: 2025-10-01: - # D.12(a)(6) and (8) count Social Security and railroad benefits after - # Medicare Part B and D premiums. medicare_part_b_premium and - # medicare_part_b_premiums_reported are not wired in yet, so both count gross. - - social_security - - railroad_benefits + # Social Security and railroad benefits are counted in the formula, net of the + # Medicare Part B premium under D.12(a)(6) and (8). - ssi - unemployment_compensation - strike_benefits @@ -26,12 +23,15 @@ values: # D.12(a)(27): income from rents. - farm_rent_income - estate_income - # D.12(a)(38): all other income not specifically excluded under D.13. + # D.12(a)(38): all other income not specifically excluded under D.13. Plan + # section 1.9 (page 7) leaves the "Other" box unchecked; the manual controls. - miscellaneous_income metadata: unit: list period: year label: New York HEAP unearned sources reference: - - title: HEAP Manual, Chapter 8 D.12-D.14, PDF pages 37, 38, 39, 40, 41, 42, 43 + - title: HEAP Manual (edition dated 7/1/2021), Chapter 8 D.12-D.14, PDF pages 37, 38, 39, 40, 41, 42, 43 href: https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=37 + - title: FY2026 LIHEAP State Plan, section 1.9 countable income, PDF pages 6, 7 + href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=6 diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/base.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/base.yaml index 0306670f385..b62fba7222b 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/base.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/base.yaml @@ -1,26 +1,29 @@ -description: New York provides this base annual direct heating payment under the Home Energy Assistance Program, by primary heating fuel. +description: New York provides this base annual direct heating payment by primary heating fuel under the Home Energy Assistance Program. ELECTRICITY: - 2025-10-01: 400 + 2023-10-01: 400 NATURAL_GAS: - 2025-10-01: 400 + 2023-10-01: 400 FUEL_OIL: - 2025-10-01: 900 + 2023-10-01: 900 KEROSENE: - 2025-10-01: 900 + 2023-10-01: 900 PROPANE: - 2025-10-01: 900 + 2023-10-01: 900 WOOD: - 2025-10-01: 635 + 2023-10-01: 635 COAL: - 2025-10-01: 635 + 2023-10-01: 635 +# The matrix's "other fuels" row covers fuels outside the named utility and +# delivered-fuel groups; the model places solar there. OTHER: - 2025-10-01: 635 + 2023-10-01: 635 SOLAR: - 2025-10-01: 635 + 2023-10-01: 635 +# No matrix row: an unknown fuel pays nothing. See #9754 for the default fuel. UNSPECIFIED: - 2025-10-01: 0 + 2023-10-01: 0 NONE: - 2025-10-01: 0 + 2023-10-01: 0 metadata: unit: currency-USD period: year @@ -28,5 +31,9 @@ metadata: breakdown: - heating_type reference: - - title: 2025-2026 HEAP regular benefit levels, other fuels include fuels outside the named utility and delivered-fuel groups + - title: 2023-2024 HEAP Direct Heating Regular Base Benefit Levels + href: https://liheapch.acf.gov/docs/2024/benefits-matricies/NY_BenefitMatrix_2024.pdf#page=1 + - title: 2024-2025 HEAP Direct Heating Regular Base Benefit Levels + href: https://liheapch.acf.gov/docs/2025/benefits-matricies/NY_BenefitMatrix_2025.docx + - title: 2025-2026 HEAP Direct Heating Regular Base Benefit Levels href: https://liheapch.acf.gov/docs/2026/benefits-matricies/NY_BenefitMatrix_2026.docx diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/elderly_age.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/elderly_age.yaml index 996e47c5be6..cbf49be6dcd 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/elderly_age.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/elderly_age.yaml @@ -1,4 +1,4 @@ -description: New York treats people at or above this age as vulnerable under the Home Energy Assistance Program. +description: New York sets this age at or above which a member is vulnerable under the Home Energy Assistance Program. values: 2025-10-01: 60 metadata: @@ -6,5 +6,5 @@ metadata: period: year label: New York HEAP elderly age reference: - - title: FY2026 LIHEAP State Plan, regular heating assistance, PDF pages 9, 10 + - title: FY2026 LIHEAP State Plan, regular heating assistance, PDF page 9 href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9 diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/nominal.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/nominal.yaml index bab7163516b..c99e2bc48f0 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/nominal.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/nominal.yaml @@ -1,10 +1,18 @@ -description: New York pays this regular amount to specified group residences and subsidized households with heat included under the Home Energy Assistance Program. +description: New York provides this regular payment to specified group residences and subsidized households with heat included in rent under the Home Energy Assistance Program. values: - 2025-10-01: 21 + 2023-10-01: 21 metadata: unit: currency-USD period: year label: New York HEAP nominal regular payment reference: - - title: 2025-2026 HEAP regular benefit levels + - title: 2023-2024 HEAP Heat Included Regular Benefit Levels + href: https://liheapch.acf.gov/docs/2024/benefits-matricies/NY_BenefitMatrix_2024.pdf#page=1 + - title: 2024-2025 HEAP Heat Included Regular Benefit Levels + href: https://liheapch.acf.gov/docs/2025/benefits-matricies/NY_BenefitMatrix_2025.docx + - title: 2025-2026 HEAP Heat Included Regular Benefit Levels href: https://liheapch.acf.gov/docs/2026/benefits-matricies/NY_BenefitMatrix_2026.docx + - title: HEAP Manual (edition dated 7/1/2021), Chapter 8 F.2(c) and F.3, PDF page 45 + href: https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=45 + - title: FY2026 LIHEAP State Plan, section 1.7b nominal payment, PDF page 6 + href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=6 diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/tier_one.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/tier_one.yaml index cefbd588a02..8c343d9d9fd 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/tier_one.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/tier_one.yaml @@ -1,10 +1,14 @@ -description: New York pays this regular amount to Tier I households with heat included in unsubsidized rent under the Home Energy Assistance Program. +description: New York provides this regular payment to Tier I households with heat included in unsubsidized rent under the Home Energy Assistance Program. values: - 2025-10-01: 50 + 2023-10-01: 50 metadata: unit: currency-USD period: year label: New York HEAP Tier I heat-included payment reference: - - title: 2025-2026 HEAP regular benefit levels + - title: 2023-2024 HEAP Heat Included Regular Benefit Levels + href: https://liheapch.acf.gov/docs/2024/benefits-matricies/NY_BenefitMatrix_2024.pdf#page=1 + - title: 2024-2025 HEAP Heat Included Regular Benefit Levels + href: https://liheapch.acf.gov/docs/2025/benefits-matricies/NY_BenefitMatrix_2025.docx + - title: 2025-2026 HEAP Heat Included Regular Benefit Levels href: https://liheapch.acf.gov/docs/2026/benefits-matricies/NY_BenefitMatrix_2026.docx diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/tier_two.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/tier_two.yaml index c8833a2187a..2e1122dc6ae 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/tier_two.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/heat_included/tier_two.yaml @@ -1,10 +1,14 @@ -description: New York pays this regular amount to Tier II households with heat included in unsubsidized rent under the Home Energy Assistance Program. +description: New York provides this regular payment to Tier II households with heat included in unsubsidized rent under the Home Energy Assistance Program. values: - 2025-10-01: 45 + 2023-10-01: 45 metadata: unit: currency-USD period: year label: New York HEAP Tier II heat-included payment reference: - - title: 2025-2026 HEAP regular benefit levels + - title: 2023-2024 HEAP Heat Included Regular Benefit Levels + href: https://liheapch.acf.gov/docs/2024/benefits-matricies/NY_BenefitMatrix_2024.pdf#page=1 + - title: 2024-2025 HEAP Heat Included Regular Benefit Levels + href: https://liheapch.acf.gov/docs/2025/benefits-matricies/NY_BenefitMatrix_2025.docx + - title: 2025-2026 HEAP Heat Included Regular Benefit Levels href: https://liheapch.acf.gov/docs/2026/benefits-matricies/NY_BenefitMatrix_2026.docx diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_fpg_rate.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_fpg_rate.yaml index a2d7bedb681..6b4ce3c3113 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_fpg_rate.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_fpg_rate.yaml @@ -1,4 +1,4 @@ -description: New York assigns noncategorical households to Tier I at or below this share of the poverty guideline under the Home Energy Assistance Program. +description: New York limits Tier I to noncategorical households at or below this share of the poverty guideline under the Home Energy Assistance Program. values: 2025-10-01: 1.3 metadata: diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_supplement.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_supplement.yaml index d67cca1bf68..7bb1eed0e9e 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_supplement.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/tier_one_supplement.yaml @@ -1,10 +1,17 @@ -description: New York adds this amount to direct heating payments for Tier I households under the Home Energy Assistance Program. +description: New York provides this Tier I supplement to direct heating payments under the Home Energy Assistance Program. values: - 2025-10-01: 61 + 2022-10-01: 41 + 2024-10-01: 61 metadata: unit: currency-USD period: year label: New York HEAP Tier I supplement reference: + - title: 2022-2023 HEAP Direct Heating Regular Base Benefit Levels, Tier I add-on + href: https://liheapch.acf.gov/docs/2023/benefits-matricies/NY_BenefitMatrix_2023.pdf#page=1 + - title: 2023-2024 HEAP Direct Heating Regular Base Benefit Levels, Tier I add-on + href: https://liheapch.acf.gov/docs/2024/benefits-matricies/NY_BenefitMatrix_2024.pdf#page=1 + - title: 2024-2025 HEAP Direct Heating Regular Base Benefit Levels, Tier I add-on + href: https://liheapch.acf.gov/docs/2025/benefits-matricies/NY_BenefitMatrix_2025.docx - title: FY2026 LIHEAP State Plan, regular heating assistance, PDF page 10 href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=10 diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/vulnerable_supplement.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/vulnerable_supplement.yaml index 111f62d74fa..f688a9f1acf 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/vulnerable_supplement.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/vulnerable_supplement.yaml @@ -1,10 +1,16 @@ -description: New York adds this amount once per direct heating household with a qualified vulnerable member under the Home Energy Assistance Program. +description: New York provides this supplement once per direct heating household with a qualified vulnerable member under the Home Energy Assistance Program. values: - 2025-10-01: 35 + 2023-10-01: 35 metadata: unit: currency-USD period: year label: New York HEAP vulnerable supplement reference: - - title: FY2026 LIHEAP State Plan, regular heating assistance, PDF pages 9, 10 + - title: 2023-2024 HEAP Direct Heating Regular Base Benefit Levels, vulnerable add-on + href: https://liheapch.acf.gov/docs/2024/benefits-matricies/NY_BenefitMatrix_2024.pdf#page=1 + - title: 2024-2025 HEAP Direct Heating Regular Base Benefit Levels, vulnerable add-on + href: https://liheapch.acf.gov/docs/2025/benefits-matricies/NY_BenefitMatrix_2025.docx + - title: 2025-2026 HEAP Direct Heating Regular Base Benefit Levels, vulnerable add-on + href: https://liheapch.acf.gov/docs/2026/benefits-matricies/NY_BenefitMatrix_2026.docx + - title: FY2026 LIHEAP State Plan, regular heating assistance, PDF page 9 href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9 diff --git a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/young_child_age.yaml b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/young_child_age.yaml index 5b0a8dfd686..9a8ec14793c 100644 --- a/policyengine_us/parameters/gov/states/ny/otda/heap/payment/young_child_age.yaml +++ b/policyengine_us/parameters/gov/states/ny/otda/heap/payment/young_child_age.yaml @@ -1,4 +1,4 @@ -description: New York treats children below this age as vulnerable under the Home Energy Assistance Program. +description: New York sets this age below which a child is vulnerable under the Home Energy Assistance Program. values: 2025-10-01: 6 metadata: @@ -6,5 +6,5 @@ metadata: period: year label: New York HEAP young child age reference: - - title: FY2026 LIHEAP State Plan, regular heating assistance, PDF pages 9, 10 + - title: FY2026 LIHEAP State Plan, regular heating assistance, PDF page 9 href: https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9 diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 6b9cdc368d1..d2f7b752d69 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -877,8 +877,8 @@ programs: full_name: New York Home Energy Assistance Program variable: ny_heap parameter_prefix: gov.states.ny.otda.heap - verified_years: "2026" - notes: Regular heating eligibility, income, household exclusions and fuel/housing supplements. SSI state codes and some receipt, residence and certification rules remain partial; earlier years are unverified estimates. + verified_years: "2024-2026" + notes: FY2024-FY2026 regular heating only, with the fuel base amounts, the Tier I and vulnerable supplements and the heat-included levels. Crisis, cooling, HERR and clean-and-tune assistance are out of scope. - state: TX status: partial name: Texas CEAP diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.yaml index c4642f156fe..45c3ab4359c 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.yaml @@ -5,6 +5,9 @@ # FY2026 State Plan item 1.4a (page 5): one member's receipt qualifies the # whole household. # https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=5 +# 18 NYCRR 393.4(c)(1)(iii): SSI "as a couple or individual living alone (SSI +# Living Arrangement Code A)". +# https://www.law.cornell.edu/regulations/new-york/18-NYCRR-393.4 - name: Case 1, SNAP receipt confers categorical eligibility and no benefit receipt does not. period: 2026 @@ -20,7 +23,7 @@ # household is income tested under D.9 (page 37). ny_heap_categorically_eligible: [true, false] -- name: Case 2, an SSI recipient in the federal own-household arrangement approximates Code A. +- name: Case 2, a single SSI recipient in the federal own-household arrangement is Code A. period: 2026 input: heating_type: NATURAL_GAS @@ -32,8 +35,9 @@ 2026-01: OWN_HOUSEHOLD output: # State Plan 1.4a (page 5) and D.10(c) (page 37): Code A SSI receipt - # qualifies. The model approximates Code A with the federal own-household - # arrangement because the state living arrangement code is not an input. + # qualifies. 18 NYCRR 393.4(c)(1)(iii) and the glossary (page 110) define + # Code A as an individual or couple living alone in the federal + # own-household arrangement; a recipient living alone satisfies both. ny_heap_categorically_eligible: true - name: Case 3, a TANF entitlement that is not taken up does not confer categorical eligibility. @@ -138,3 +142,93 @@ output: # D.10(c) (page 37): the family is an active recipient of recurring TA. ny_heap_categorically_eligible: true + +- name: Case 7, an SSI recipient living with her daughter's family is not Code A, so the household is income tested. + period: 2026 + input: + people: + person1: + age: 72 + # Federal SSI of $994 a month. + ssi: 11_928 + ssi_federal_living_arrangement: + 2026-01: OWN_HOUSEHOLD + person2: + age: 45 + # Full-time wages of $4,000 a month. + employment_income: 48_000 + ssi: 0 + person3: + age: 47 + # Full-time wages of $3,000 a month. + employment_income: 36_000 + ssi: 0 + spm_units: + spm_unit: + members: [person1, person2, person3] + heating_type: NATURAL_GAS + gas_expense: 1_500 + snap: 0 + tanf: 0 + households: + household: + members: [person1, person2, person3] + state_code: NY + output: + # 393.4(c)(1)(iii) and the glossary (page 110): Code A means living alone, + # and this recipient lives with two other adults. + ny_heap_categorically_eligible: false + # 48,000 + 36,000 wages (D.12(a)(1), page 37) + 11,928 SSI (D.12(a)(7), + # page 38) = 95,928, which is 7,994 a month. + ny_heap_countable_income: 95_928 + # Three-person Tier II maximum 5,611 x 12 (2025-2026 HEAP Monthly Income + # Eligibility Guidelines); 7,994 a month exceeds it. + ny_heap_income_limit: 67_332 + ny_heap_eligible: false + ny_heap: 0 + +- name: Case 8, an SSI couple living alone is Code A. + period: 2026 + input: + people: + person1: + age: 68 + # The couple's federal SSI rate of $1,491 a month, split between them. + ssi: 8_946 + ssi_federal_living_arrangement: + 2026-01: OWN_HOUSEHOLD + person2: + age: 66 + ssi: 8_946 + ssi_federal_living_arrangement: + 2026-01: OWN_HOUSEHOLD + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_500 + snap: 0 + tanf: 0 + households: + household: + members: [person1, person2] + state_code: NY + output: + # 393.4(c)(1)(iii): SSI "as a couple ... living alone" qualifies. + ny_heap_categorically_eligible: true + +- name: Case 9, an SSI recipient living alone in another person's household is not in the federal Code A arrangement. + period: 2026 + input: + heating_type: NATURAL_GAS + state_code: NY + snap: 0 + tanf: 0 + ssi: 1_200 + ssi_federal_living_arrangement: + 2026-01: ANOTHER_PERSONS_HOUSEHOLD + output: + # The glossary (page 110) requires Code A for both state and federal SSI; + # a recipient in the federal another-person's-household arrangement is + # not federal Code A. + ny_heap_categorically_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.yaml index 339ecab2d50..078b7e018c9 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.yaml @@ -72,3 +72,34 @@ ny_heap_household_size: 1 # Chapter 9 B.3(b) (page 49): aged 10, so not under six, and no SSI receipt. ny_heap_vulnerable: false + +- name: Case 3, reported SSI receipt in the federal Code C arrangement excludes a child. + period: 2026 + input: + people: + person1: + age: 40 + # Full-time wages of $2,000 a month. + employment_income: 24_000 + ssi: 0 + person2: + age: 10 + ssi: 0 + receives_ssi: true + ssi_federal_living_arrangement: + 2026-01: CHILD_IN_PARENTAL_HOUSEHOLD + spm_units: + spm_unit: + members: [person1, person2] + heating_type: NATURAL_GAS + gas_expense: 1_500 + tanf: 0 + snap: 0 + households: + household: + members: [person1, person2] + state_code: NY + output: + # D.4(a)(5) (page 36): the child is an SSI recipient by reported receipt, + # so only the adult counts. + ny_heap_household_size: 1 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_income_limit.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_income_limit.yaml index 53c80093771..d874a870cc5 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_income_limit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/eligibility/ny_heap_income_limit.yaml @@ -14,8 +14,9 @@ state_code: [NY, NY, NY, NY, NY, NY, NY] ny_heap_household_size: [1, 3, 5, 7, 9, 11, 13] output: - # Odd sizes 1, 3, 5, 7, 9, 11 and 13; payment/ny_heap_tier_one.yaml covers - # the even sizes of the same matrix. Published monthly Tier II maximums + # Odd sizes 1, 3, 5, 7, 9, 11 and 13 (the even Tier II maximums are not + # asserted; payment/ny_heap_tier_one.yaml tests the Tier I column at the + # even sizes). Published monthly Tier II maximums # 3,473; 5,611; 7,749; 9,018; 9,419; 9,820; 10,221 (benefit matrix Tier II # column; State Plan 2.1, page 9), each x 12. ny_heap_income_limit: [41_676, 67_332, 92_988, 108_216, 113_028, 117_840, 122_652] @@ -33,3 +34,27 @@ # 14+ row adds 687 to the size-13 maximum instead (10,908); the plan rule is # encoded. ny_heap_income_limit: 130_716 + +# 2024-2025 and 2023-2024 HEAP Monthly Income Eligibility Guidelines +# (Clearinghouse benefit matrices), Tier II maximum column, annualized. +# https://liheapch.acf.gov/docs/2025/benefits-matricies/NY_BenefitMatrix_2025.docx +# https://liheapch.acf.gov/docs/2024/benefits-matricies/NY_BenefitMatrix_2024.pdf#page=1 +- name: Case 3, FY2025 published income limits at sizes one, four and thirteen. + period: 2025 + input: + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] + state_code: [NY, NY, NY] + ny_heap_household_size: [1, 4, 13] + output: + # 2024-2025 Tier II maximums 3,322; 6,390; 9,952, each x 12. + ny_heap_income_limit: [39_864, 76_680, 119_424] + +- name: Case 4, FY2024 published income limits at sizes one, four and thirteen. + period: 2024 + input: + heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] + state_code: [NY, NY, NY] + ny_heap_household_size: [1, 4, 13] + output: + # 2023-2024 Tier II maximums 3,035; 5,838; 9,532, each x 12. + ny_heap_income_limit: [36_420, 70_056, 114_384] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_income.yaml index 9615f27dfc2..1b492a2850f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/income/ny_heap_countable_income.yaml @@ -5,16 +5,15 @@ # the TANF box unchecked. # https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=6 -- name: Case 1, Social Security counts gross and monthly household income rounds down. +- name: Case 1, Social Security counts net of the Medicare Part B premium and monthly household income rounds down. period: 2026 input: heating_type: NATURAL_GAS state_code: NY ny_heap_countable_earned_income: 12_001 social_security: 12_000 - # D.12(a)(6) (page 38) counts Social Security after the Medicare premium; - # medicare_part_b_premium is not wired in yet, so the premium reported here - # must not reduce the total. + # D.12(a)(6) (page 38) counts Social Security after the Medicare premium, + # so the $100 a month of Part B premium reduces the total. medicare_part_b_premium: 1_200 railroad_benefits: 600 pension_income: 240 @@ -32,11 +31,11 @@ estate_income: 120 ssi: 0 output: - # 12,001 earnings + 12,600 Social Security and railroad benefits (D.12(a)(6) - # and (8), page 38) + 4,440 from the other counted sources in D.12(a)(10) to - # (28) (pages 38 to 39) = 29,041. D.14(c) (page 43): floor(29,041 / 12) = - # 2,420 a month, annualized 29,040. - ny_heap_countable_income: 29_040 + # 12,001 earnings + 12,600 Social Security and railroad benefits less the + # 1,200 Part B premium (D.12(a)(6) and (8), page 38) + 4,440 from the other + # counted sources in D.12(a)(10) to (28) (pages 38 to 39) = 27,841. D.14(c) + # (page 43): floor(27,841 / 12) = 2,320 a month, annualized 27,840. + ny_heap_countable_income: 27_840 - name: Case 2, TANF cash assistance is not counted as income. period: 2026 @@ -109,3 +108,49 @@ # Oil base $900 + $35 for a member aged 60 or older (2025-2026 HEAP Direct # Heating Regular Base Benefit Levels, vulnerable add-on column) = $935. ny_heap: 935 + +- name: Case 4, a rental loss does not offset wages. + period: 2026 + input: + state_code: NY + heating_type: NATURAL_GAS + gas_expense: 1_500 + age: 40 + # Full-time wages of $3,600 a month. + ny_heap_countable_earned_income: 43_200 + rental_income: -6_000 + ssi: 0 + snap: 0 + tanf: 0 + output: + # D.14(b)(7) (page 41) converts negative income to zero and lists net loss + # as a deduction that is not allowed, so the loss counts as 0 and the + # wages count in full: 3,600 a month. + ny_heap_countable_income: 43_200 + # 3,600 a month exceeds the one-person Tier II maximum of 3,473. + ny_heap: 0 + +- name: Case 5, the Medicare Part B premium brings a retiree under the income limit. + period: 2026 + input: + state_code: NY + heating_type: NATURAL_GAS + gas_expense: 1_500 + age: 70 + # Social Security retirement of $3,600 a month. + social_security: 43_200 + # The standard 2026 Part B premium of $202.90 a month, not covered by a + # Medicare Savings Program at this income. + medicare_part_b_premium: 2_434.8 + ssi: 0 + snap: 0 + tanf: 0 + output: + # D.14(b)(4) (page 41): the benefit after the Part B premium is counted, + # 43,200 - 2,434.80 = 40,765.20; D.14(c): floor(40,765.20 / 12) = 3,397 a + # month, annualized 40,764. + ny_heap_countable_income: 40_764 + # 3,397 a month is within the one-person Tier II maximum of 3,473 and + # above the Tier I maximum of 1,695: Utility 400 + 35 for a member aged 60 + # or older (Chapter 9 B.3(b)(2), page 49) = 435. + ny_heap: 435 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/ny_heap.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/ny_heap.yaml index e7b2caefe4a..283a0d82302 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/ny_heap.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/ny_heap.yaml @@ -153,6 +153,9 @@ ny_heap_categorically_eligible: [false, false] age: [40, 40] ny_heap_dwelling_type: [ELIGIBLE_GROUP_RESIDENCE, INELIGIBLE_RESIDENCE] + # The group residence needs no bill of its own; the ineligible residence + # pays a gas bill and is still denied. + gas_expense: [0, 1_500] output: # Chapter 8 F.3 (pages 45 to 46): the listed group residences receive a # Regular benefit of $21; F.4 (pages 46 to 47): the listed arrangements are @@ -223,7 +226,8 @@ heating_type: UNSPECIFIED output: # The benefit matrix pays by fuel group; a household whose fuel is unknown - # cannot be placed in a row, so no direct heating payment is made. + # cannot be placed in a row, so no direct heating payment is made. This is + # a coverage gap: the UNSPECIFIED default is tracked in issue 9754. ny_heap: 0 - name: Case 13, a SNAP recipient's household above the income limit is paid with the Tier I supplement. @@ -291,3 +295,38 @@ # page 46) instead of the Tier I heat-included $50; a public housing tenant # who pays the gas utility directly receives Utility 400 + 61 = 461. ny_heap: [21, 461] + +- name: Case 15, a vulnerable member adds nothing to the heat-included payments. + period: 2026 + input: + state_code: [NY, NY] + ny_heap_countable_income: [0, 24_000] + ny_heap_categorically_eligible: [false, false] + age: [65, 65] + heat_expense_included_in_rent: [true, true] + heating_type: [UNSPECIFIED, UNSPECIFIED] + output: + # State Plan page 9: the $35 is added "to their base Regular heater + # benefit", so a 65-year-old with heat included in rent receives the Heat + # Included levels alone: Tier I $50 and Tier II $45 (2,000 a month is + # above the one-person Tier I maximum of 1,695). + ny_heap: [50, 45] + +# 2023-2024 HEAP Direct Heating Regular Base Benefit Levels (Clearinghouse +# benefit matrix): the same base amounts with a $41 Tier I add-on. +# https://liheapch.acf.gov/docs/2024/benefits-matricies/NY_BenefitMatrix_2024.pdf#page=1 +- name: Case 16, the FY2024 Tier I add-on was $41. + period: 2024 + input: + state_code: [NY, NY] + ny_heap_countable_income: [0, 0] + ny_heap_categorically_eligible: [false, false] + age: [40, 40] + heating_type: [NATURAL_GAS, FUEL_OIL] + gas_expense: [1_500, 0] + # About 700 gallons of heating oil at $4.00 a gallon. + fuel_oil_expense: [0, 2_800] + output: + # 2023-2024 matrix, Tier I add-on column: Utility 400 + 41 = 441; Oil, + # Kerosene, and Propane 900 + 41 = 941. + ny_heap: [441, 941] diff --git a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/payment/ny_heap_tier_one.yaml b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/payment/ny_heap_tier_one.yaml index 6d04ba088a0..715601aff23 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/payment/ny_heap_tier_one.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ny/otda/heap/payment/ny_heap_tier_one.yaml @@ -11,8 +11,9 @@ heating_type: [NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS, NATURAL_GAS] state_code: [NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY, NY] ny_heap_household_size: [2, 4, 6, 8, 10, 12, 2, 4, 6, 8, 10, 12] - # Even sizes 2, 4, 6, 8, 10 and 12; eligibility/ny_heap_income_limit.yaml - # covers the odd sizes of the same matrix. Published monthly Tier I + # Even sizes 2, 4, 6, 8, 10 and 12 (the odd Tier I maximums are not + # asserted; eligibility/ny_heap_income_limit.yaml tests the Tier II column + # at the odd sizes). Published monthly Tier I # maximums 2,291; 3,482; 4,674; 5,866; 7,057; 8,249 (benefit matrix Tier I # column; State Plan, page 10) x 12, then one more monthly dollar for each # size (x 12). @@ -35,3 +36,45 @@ # State Plan page 10: the Tier I add-on "is also applied to Regular heater # benefits received by CE households". ny_heap_tier_one: true + +- name: Case 3, size fourteen follows the poverty rate rather than the matrix increment. + period: 2026 + input: + heating_type: [NATURAL_GAS, NATURAL_GAS] + state_code: [NY, NY] + ny_heap_household_size: [14, 14] + # 130% of the 2025 guideline for 14 (87,150): floor(1.3 x 87,150 / 12) = + # 9,441 a month, then one more monthly dollar. The matrix's 14+ row adds + # 595 to the size-13 maximum instead (9,440). + ny_heap_countable_income: [113_292, 113_304] + ny_heap_categorically_eligible: [false, false] + output: + ny_heap_tier_one: [true, false] + +# 2024-2025 and 2023-2024 HEAP Monthly Income Eligibility Guidelines +# (Clearinghouse benefit matrices), Tier I column. +# https://liheapch.acf.gov/docs/2025/benefits-matricies/NY_BenefitMatrix_2025.docx +# https://liheapch.acf.gov/docs/2024/benefits-matricies/NY_BenefitMatrix_2024.pdf#page=1 +- name: Case 4, the FY2025 and FY2024 one-person Tier I maximums are inclusive. + period: 2025 + input: + heating_type: [NATURAL_GAS, NATURAL_GAS] + state_code: [NY, NY] + ny_heap_household_size: [1, 1] + # 2024-2025 Tier I maximum 1,631 x 12, then one more monthly dollar. + ny_heap_countable_income: [19_572, 19_584] + ny_heap_categorically_eligible: [false, false] + output: + ny_heap_tier_one: [true, false] + +- name: Case 5, the FY2024 one-person Tier I maximum is inclusive. + period: 2024 + input: + heating_type: [NATURAL_GAS, NATURAL_GAS] + state_code: [NY, NY] + ny_heap_household_size: [1, 1] + # 2023-2024 Tier I maximum 1,579 x 12, then one more monthly dollar. + ny_heap_countable_income: [18_948, 18_960] + ny_heap_categorically_eligible: [false, false] + output: + ny_heap_tier_one: [true, false] diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.py b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.py index 29b6bbf999b..d6f8df579e0 100644 --- a/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.py +++ b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_categorically_eligible.py @@ -8,25 +8,38 @@ class ny_heap_categorically_eligible(Variable): label = "New York HEAP categorical income eligibility" defined_for = StateCode.NY reference = ( + "https://www.law.cornell.edu/regulations/new-york/18-NYCRR-393.4", "https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=5", # PDF pages 37, 110. "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=37", ) documentation = ( - "Receipt of SNAP, TANF or SSI in a federal own-household arrangement. State " - "SSI Code A and recurring receipt on the application date are approximated; " - "expedited/emergency-only benefits and roomer-only awards cannot be " - "distinguished. Chapter 8 D.10 also qualifies Safety Net Assistance, but no " - "NY-specific receipt input identifies it." + "Receipt of SNAP, TANF or Code A SSI. 18 NYCRR 393.4(c)(1)(iii) and the " + "manual glossary define Code A as an SSI individual or couple living alone " + "in the federal own-household arrangement, so the SPM unit may hold no one " + "but the recipient or the recipient couple, foster members aside. Recurring " + "receipt on the application date is approximated; expedited/emergency-only " + "benefits and roomer-only awards cannot be distinguished. Chapter 8 D.10 " + "also qualifies Safety Net Assistance, but no NY-specific receipt input " + "identifies it." ) def formula(spm_unit, period, parameters): person = spm_unit.members arrangement = person("ssi_federal_living_arrangement", period.first_month) - code_a = arrangement == arrangement.possible_values.OWN_HOUSEHOLD - qualifying_ssi = spm_unit.any( - (person("ssi", period) > 0) - & code_a + own_household = arrangement == arrangement.possible_values.OWN_HOUSEHOLD + receives_ssi = (person("ssi", period) > 0) | ( + add(person, period, ["receives_ssi"]) > 0 + ) + foster = person("is_in_foster_care", period.first_month) + head_or_spouse = person("is_tax_unit_head_or_spouse", period) + # State Code A is "living alone": one eligible individual or one + # eligible couple (14-ADM-07), so every non-foster member must be an + # SSI recipient who is the head or spouse. + living_alone = spm_unit.all(foster | (receives_ssi & head_or_spouse)) + qualifying_ssi = living_alone & spm_unit.any( + receives_ssi + & own_household & person("is_citizen_or_legal_immigrant", period) & ~person("ny_heap_is_excluded_person", period) ) diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_eligible.py b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_eligible.py index 0e6b816e07f..37165445659 100644 --- a/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_eligible.py +++ b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_eligible.py @@ -10,6 +10,7 @@ class ny_heap_eligible(Variable): reference = ( # PDF pages 34, 37, 44, 45, 46, 47, 48. "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=34", + "https://www.law.cornell.edu/regulations/new-york/18-NYCRR-393.4", ) documentation = ( "Regular heating has no resource test. SPM units approximate energy-sharing " diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.py b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.py index 35c538a8150..9b58f0ac2d9 100644 --- a/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.py +++ b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_household_size.py @@ -7,8 +7,11 @@ class ny_heap_household_size(Variable): definition_period = YEAR label = "New York HEAP qualified household size" defined_for = StateCode.NY - # Chapter 8 D.4 and D.9, PDF pages 34, 36, 37, 44. - reference = ("https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=34",) + # Chapter 8 D.4 and D.9, PDF pages 34, 36, 37, 44; 18 NYCRR 393.4(b). + reference = ( + "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=34", + "https://www.law.cornell.edu/regulations/new-york/18-NYCRR-393.4", + ) documentation = ( "SPM members who are citizens or federally qualified noncitizens. Nonqualified " "members' income still counts in full. Foster members and federal Code C SSI " diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_income_limit.py b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_income_limit.py index e7f71d4756f..edd5d385e6b 100644 --- a/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_income_limit.py +++ b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_income_limit.py @@ -13,6 +13,7 @@ class ny_heap_income_limit(Variable): "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=37", "https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9", "https://liheapch.acf.gov/docs/2026/benefits-matricies/NY_BenefitMatrix_2026.docx", + "https://acf.gov/sites/default/files/documents/ocs/COMM_LIHEAP_IM2025-02_SMIStateTable_Att4.pdf#page=2", ) def formula(spm_unit, period, parameters): @@ -20,8 +21,9 @@ def formula(spm_unit, period, parameters): size = max_(spm_unit("ny_heap_household_size", period), 1) state = spm_unit.household("state_code_str", period) base = parameters(period).gov.hhs.smi.amount[state] - # Inferred ordering matching every published size 1-13 limit: - # floor the four-person 60% amount before the federal size adjustment. + # ACF IM 2025-02 Attachment 4 floors the four-person 60% amount before + # the federal size adjustment; this reproduces every published size + # 1-13 limit. adjusted_smi = smi(size, state, period, parameters) / base monthly_smi = np.floor( adjusted_smi * np.floor(base * p.smi_rate) / MONTHS_IN_YEAR diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_is_excluded_person.py b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_is_excluded_person.py index 1bb39ecad6e..dafa8d590a6 100644 --- a/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_is_excluded_person.py +++ b/policyengine_us/variables/gov/states/ny/otda/heap/eligibility/ny_heap_is_excluded_person.py @@ -9,7 +9,18 @@ class ny_heap_is_excluded_person(Variable): defined_for = StateCode.NY # Chapter 8 D.4(a)(3), (5), PDF page 36. This is the federal Code C, # distinct from the state Code A required for categorical SSI eligibility. - reference = "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=36" + reference = ( + "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=36", + "https://www.law.cornell.edu/regulations/new-york/18-NYCRR-393.4", + ) + documentation = ( + "Follows the manual literally: Chapter 8 D.4(a)(5) says federal living " + "arrangement Code C SSI recipients are not household members. 18 NYCRR " + "393.4(b) presumes relatives to be members and exempts only foster care and " + "unrelated roomers or boarders, and D.3(a)(2) (page 35) allows no exception " + "for children under 21, so the regulation would count an SSI child's size and " + "vulnerability. The conflict is recorded in rulespec-us issue 1480." + ) def formula(person, period, parameters): # January living arrangements approximate the application-month status. diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_income.py b/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_income.py index 3b5c9f2588f..507f6191782 100644 --- a/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_income.py +++ b/policyengine_us/variables/gov/states/ny/otda/heap/income/ny_heap_countable_income.py @@ -15,10 +15,12 @@ class ny_heap_countable_income(Variable): documentation = ( "Annual inputs approximate application-month income; final monthly income is " "rounded down and annualized. Income of nonqualified members counts in full. " - "Medicare-premium deductions remain deferred. Royalties, regular gifts, " - "aid-and-attendance exclusions and irregular-income exclusions cannot be " - "isolated reliably. Rental income is assumed nonnegative. No new " - "self-employment or work-expense deduction is applied." + "Social Security and railroad benefits are counted net of the computed " + "Medicare Part B premium; Part D has no variable and stays gross. Each " + "unearned source is floored at zero, so a rental or estate loss does not " + "offset other income. Royalties, regular gifts, aid-and-attendance " + "exclusions and irregular-income exclusions cannot be isolated reliably. No " + "new self-employment or work-expense deduction is applied." ) def formula(spm_unit, period, parameters): @@ -28,18 +30,27 @@ def formula(spm_unit, period, parameters): # recipients. Nonqualified members are excluded only from household # size; their income still counts in full under D.4(a)(7). included = ~person("ny_heap_is_excluded_person", period) + # D.14(b)(7) (page 41) converts negative income to zero and lists net + # loss as a deduction that is not allowed, so each source is floored + # like the earned sources: a loss in one source cannot offset another. + unearned = 0 + for source in p.sources.unearned: + unearned = unearned + max_(person(source, period), 0) + # D.12(a)(6) and (8) (page 38) count Social Security and railroad + # benefits after the Medicare Part B and D premiums. The computed + # medicare_part_b_premium is the out-of-pocket Part B premium, zero when + # a Medicare Savings Program pays it; no Part D variable exists. + benefits = max_( + person("social_security", period) + + person("railroad_benefits", period) + - person("medicare_part_b_premium", period), + 0, + ) income = spm_unit.sum( - ( - person("ny_heap_countable_earned_income", period) - + add(person, period, p.sources.unearned) - ) + (person("ny_heap_countable_earned_income", period) + unearned + benefits) * included ) # TANF is not counted: FY2026 State Plan item 1.9 (page 6) leaves the # TANF box unchecked, and Chapter 8 D.11(c)(1) (page 37) counts a TA # grant only for a minor child budgeted as a roomer, which has no input. - # D.12(a)(6) (page 38) counts Social Security after Medicare Part B and - # D premiums. medicare_part_b_premium and - # medicare_part_b_premiums_reported are not wired in yet, so Social - # Security counts gross. - return np.floor(max_(income, 0) / MONTHS_IN_YEAR) * MONTHS_IN_YEAR + return np.floor(income / MONTHS_IN_YEAR) * MONTHS_IN_YEAR diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/ny_heap.py b/policyengine_us/variables/gov/states/ny/otda/heap/ny_heap.py index c9a743d615f..3e35315bd04 100644 --- a/policyengine_us/variables/gov/states/ny/otda/heap/ny_heap.py +++ b/policyengine_us/variables/gov/states/ny/otda/heap/ny_heap.py @@ -10,25 +10,27 @@ class ny_heap(Variable): defined_for = "ny_heap_eligible" reference = ( "https://liheapch.acf.gov/docs/2026/benefits-matricies/NY_BenefitMatrix_2026.docx", - # PDF pages 5, 9, 10. - "https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=5", + # PDF pages 9, 10. + "https://liheapch.acf.gov/docs/2026/state-plans/NY_Plan_2026.pdf#page=9", # PDF pages 45, 46, 47, 48, 49, 84, 87, 88. "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=45", ) documentation = ( "Annual regular heating benefit and ordinary Tier I/vulnerability supplements, " - "verified for FY2026. Earlier years are unverified backfilled estimates. " - "Housing assistance approximates subsidized rent (market-rent voucher " - "exceptions are unsupported). Unknown direct-heating fuel returns zero; solar " - "falls within other fuels. No bill cap. Crisis, cooling and equipment " - "components are excluded. Mid-year moves and previously advanced nominal " - "payments are not separately tracked." + "verified for FY2024 to FY2026; only the Tier I supplement changed across " + "those years. Earlier years are unverified backfilled estimates. Housing " + "assistance approximates subsidized rent (market-rent voucher exceptions are " + "unsupported). An unknown direct-heating fuel returns zero as a coverage gap, " + "tracked in issue 9754; solar falls within other fuels. No bill cap. Crisis, " + "cooling and equipment components are excluded. Mid-year moves and previously " + "advanced nominal payments are not separately tracked." ) def formula(spm_unit, period, parameters): p = parameters(period).gov.states.ny.otda.heap.payment tier_one = spm_unit("ny_heap_tier_one", period) fuel = spm_unit("heating_type", period) + # UNSPECIFIED and NONE have a zero base; see #9754 for the default fuel. base = p.base[fuel] direct = where( base > 0, diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/ny_heap_dwelling_type.py b/policyengine_us/variables/gov/states/ny/otda/heap/ny_heap_dwelling_type.py index e7a85385f2e..92413d71b9a 100644 --- a/policyengine_us/variables/gov/states/ny/otda/heap/ny_heap_dwelling_type.py +++ b/policyengine_us/variables/gov/states/ny/otda/heap/ny_heap_dwelling_type.py @@ -16,8 +16,11 @@ class ny_heap_dwelling_type(Variable): entity = SPMUnit definition_period = YEAR label = "New York HEAP dwelling arrangement" - # PDF pages 45, 46, 47. - reference = "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=45" + # PDF pages 45, 46, 47; 18 NYCRR 393.4(c)(3)-(4). + reference = ( + "https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=45", + "https://www.law.cornell.edu/regulations/new-york/18-NYCRR-393.4", + ) documentation = ( "Eligible group residences are the treatment, enriched housing, " "supervised/supportive living and other settings listed in Chapter 8 F.3 " diff --git a/policyengine_us/variables/gov/states/ny/otda/heap/payment/ny_heap_vulnerable.py b/policyengine_us/variables/gov/states/ny/otda/heap/payment/ny_heap_vulnerable.py index 065da22f704..a5b96e8218a 100644 --- a/policyengine_us/variables/gov/states/ny/otda/heap/payment/ny_heap_vulnerable.py +++ b/policyengine_us/variables/gov/states/ny/otda/heap/payment/ny_heap_vulnerable.py @@ -7,8 +7,8 @@ class ny_heap_vulnerable(Variable): definition_period = YEAR label = "New York HEAP qualified vulnerable member" defined_for = StateCode.NY - # PDF pages 44, 45, 49. - reference = ("https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=44",) + # Chapter 9 B.3(b), PDF page 49; qualified members, pages 44, 45. + reference = ("https://otda.ny.gov/programs/heap/HEAP-manual.pdf#page=49",) documentation = ( "The manual adopts the SNAP benefit-receipt disability criteria. Reuses the " "existing USDA calculation, including its qualifying veteran and survivor "