From f851a051b28e8d3dd9cc85d87fefc1d6bb0b924a Mon Sep 17 00:00:00 2001 From: Ziming Date: Sat, 3 Oct 2026 19:32:27 -0400 Subject: [PATCH 01/14] Add New Hampshire FAP supported calculation components --- changelog.d/nh-liheap-wave2b.added.md | 1 + .../gov/states/nh/doe/fap/fpg_year_lag.yaml | 13 + .../nh/doe/fap/income/business_sources.yaml | 16 + .../doe/fap/income/investment_disregard.yaml | 14 + .../states/nh/doe/fap/income/ssi_min_age.yaml | 11 + .../nh/doe/fap/income/unearned_sources.yaml | 39 + .../nh/doe/fap/payment/delivered_benefit.yaml | 63 + .../fap/payment/heating_cost_threshold.yaml | 17 + .../fap/payment/income_band_upper_bound.yaml | 20 + .../doe/fap/payment/utility_multiplier.yaml | 12 + .../gov/states/nh/doe/fap/smi_rate.yaml | 11 + .../nh/doe/fap/nh_fap_base_benefit.yaml | 2176 +++++++++++++++++ .../nh/doe/fap/nh_fap_countable_income.yaml | 780 ++++++ .../states/nh/doe/fap/nh_fap_eligible.yaml | 912 +++++++ .../nh/doe/fap/nh_fap_household_size.yaml | 279 +++ .../nh/doe/fap/nh_fap_income_limit.yaml | 361 +++ .../states/nh/doe/fap/nh_fap_base_benefit.py | 64 + .../nh/doe/fap/nh_fap_countable_income.py | 60 + .../gov/states/nh/doe/fap/nh_fap_eligible.py | 31 + .../nh/doe/fap/nh_fap_household_size.py | 16 + .../states/nh/doe/fap/nh_fap_income_limit.py | 30 + 21 files changed, 4926 insertions(+) create mode 100644 changelog.d/nh-liheap-wave2b.added.md create mode 100644 policyengine_us/parameters/gov/states/nh/doe/fap/fpg_year_lag.yaml create mode 100644 policyengine_us/parameters/gov/states/nh/doe/fap/income/business_sources.yaml create mode 100644 policyengine_us/parameters/gov/states/nh/doe/fap/income/investment_disregard.yaml create mode 100644 policyengine_us/parameters/gov/states/nh/doe/fap/income/ssi_min_age.yaml create mode 100644 policyengine_us/parameters/gov/states/nh/doe/fap/income/unearned_sources.yaml create mode 100644 policyengine_us/parameters/gov/states/nh/doe/fap/payment/delivered_benefit.yaml create mode 100644 policyengine_us/parameters/gov/states/nh/doe/fap/payment/heating_cost_threshold.yaml create mode 100644 policyengine_us/parameters/gov/states/nh/doe/fap/payment/income_band_upper_bound.yaml create mode 100644 policyengine_us/parameters/gov/states/nh/doe/fap/payment/utility_multiplier.yaml create mode 100644 policyengine_us/parameters/gov/states/nh/doe/fap/smi_rate.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_base_benefit.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_countable_income.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_household_size.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_income_limit.yaml create mode 100644 policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_base_benefit.py create mode 100644 policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py create mode 100644 policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_eligible.py create mode 100644 policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_household_size.py create mode 100644 policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_income_limit.py diff --git a/changelog.d/nh-liheap-wave2b.added.md b/changelog.d/nh-liheap-wave2b.added.md new file mode 100644 index 00000000000..e2c26be4485 --- /dev/null +++ b/changelog.d/nh-liheap-wave2b.added.md @@ -0,0 +1 @@ +Add New Hampshire FAP eligibility, countable income, and actual-cost benefit schedule components for PY2027; the final heat-in-rent adjustment remains pending. diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/fpg_year_lag.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/fpg_year_lag.yaml new file mode 100644 index 00000000000..cbd3d3aedd7 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/fpg_year_lag.yaml @@ -0,0 +1,13 @@ +description: New Hampshire uses federal poverty guidelines from this many years before the modeled heating season under the Fuel Assistance Program. +# The annual model uses the PY2027 starting schedule with 2026 guidelines. +# The manual's within-season February updates are not separately modeled. +values: + 2026-10-01: 1 + +metadata: + unit: year + period: year + label: New Hampshire FAP federal poverty guideline year lag + reference: + - title: PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Income Guidelines and Federal Poverty Guideline Updates + href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=10, 17, 74 diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/income/business_sources.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/income/business_sources.yaml new file mode 100644 index 00000000000..e2c59074ecd --- /dev/null +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/income/business_sources.yaml @@ -0,0 +1,16 @@ +description: New Hampshire includes these reported business income sources under the Fuel Assistance Program, excluding each source's losses. +values: + 2026-10-01: + - self_employment_income + - sstb_self_employment_income + - farm_operations_income + - partnership_income + - s_corp_income + +metadata: + unit: list + period: year + label: New Hampshire FAP business income sources + reference: + - title: PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Self Employment + href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=25, 26, 27, 28 diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/income/investment_disregard.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/income/investment_disregard.yaml new file mode 100644 index 00000000000..5d9430a6f02 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/income/investment_disregard.yaml @@ -0,0 +1,14 @@ +description: New Hampshire excludes this annual amount of each household's interest and dividend income under the Fuel Assistance Program. +# Page 16 lists this exclusion separately for interest and dividends. Its +# application to each household source follows that specific list; page 17's +# combined "and/or" summary is ambiguous and lacks agency clarification. +values: + 2026-10-01: 50 + +metadata: + unit: currency-USD + period: year + label: New Hampshire FAP annual disregard for each investment income source + reference: + - title: PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Non-countable Income and Allowable Expenses + href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=15, 16, 17 diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/income/ssi_min_age.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/income/ssi_min_age.yaml new file mode 100644 index 00000000000..4662f4e19b7 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/income/ssi_min_age.yaml @@ -0,0 +1,11 @@ +description: New Hampshire excludes Supplemental Security Income for people below this age under the Fuel Assistance Program. +values: + 2026-10-01: 18 + +metadata: + unit: year + period: year + label: New Hampshire FAP minimum age for countable SSI + reference: + - title: PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Non-countable Income + href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=15, 16 diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/income/unearned_sources.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/income/unearned_sources.yaml new file mode 100644 index 00000000000..99adc5f9ba3 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/income/unearned_sources.yaml @@ -0,0 +1,39 @@ +description: New Hampshire includes these unearned income sources under the Fuel Assistance Program before its source-specific SSI and investment income exclusions. +values: + 2026-10-01: + - social_security + - pension_income + - retirement_distributions + - railroad_benefits + - survivor_benefits + - veterans_benefits + - unemployment_compensation + - workers_compensation + - disability_benefits + - general_assistance + - strike_benefits + - financial_assistance + - alimony_income + - rental_income + - farm_rent_income + - estate_income + - gambling_winnings + +# Broad receipts approximate the source's regular payments. Inputs do not +# identify VA Aid and Attendance, educational VA benefits, special-needs +# trusts, or irregular injury/insurance payments. Financial assistance lacks +# the required six-month regularity flag. General assistance approximates +# non-FANF state welfare; FANF is included separately once per SPM unit. +# Rental inputs are treated as net and nonnegative without reconstructing +# business expenses. Pension income includes annuities and insurance income. +# Gambling winnings approximate the taxable gambling and lottery category. +# Military pay is part of wages and is not added again. Missing taxable grants, +# adoption subsidies, royalties and tribal receipts are not imputed; balances, +# capital gains and regular child support received are not counted as income. +metadata: + unit: list + period: year + label: New Hampshire FAP unearned income sources + reference: + - title: PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Countable Income, Non-countable Income, and Rental Property Income + href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=15, 16, 17, 23, 29 diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/payment/delivered_benefit.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/payment/delivered_benefit.yaml new file mode 100644 index 00000000000..cbee26ef772 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/payment/delivered_benefit.yaml @@ -0,0 +1,63 @@ +description: New Hampshire provides this annual delivered-fuel scheduled benefit by heating cost band and income band under the Fuel Assistance Program. +DD: + A: + 2026-10-01: 750 + B: + 2026-10-01: 650 + C: + 2026-10-01: 550 + D: + 2026-10-01: 500 + E: + 2026-10-01: 440 + F: + 2026-10-01: 400 +CC: + A: + 2026-10-01: 1_460 + B: + 2026-10-01: 1_270 + C: + 2026-10-01: 1_150 + D: + 2026-10-01: 950 + E: + 2026-10-01: 730 + F: + 2026-10-01: 525 +BB: + A: + 2026-10-01: 2_350 + B: + 2026-10-01: 2_040 + C: + 2026-10-01: 1_720 + D: + 2026-10-01: 1_410 + E: + 2026-10-01: 1_100 + F: + 2026-10-01: 800 +AA: + A: + 2026-10-01: 2_500 + B: + 2026-10-01: 2_150 + C: + 2026-10-01: 1_825 + D: + 2026-10-01: 1_500 + E: + 2026-10-01: 1_200 + F: + 2026-10-01: 825 + +# The operative PY2027 matrix supersedes inconsistent illustrative amounts +# in the manual's older examples. No actual-heating-cost cap is applied. +metadata: + unit: currency-USD + period: year + label: New Hampshire FAP delivered-fuel scheduled benefit + reference: + - title: PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Fuel Assistance Program Benefits + href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=78 diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/payment/heating_cost_threshold.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/payment/heating_cost_threshold.yaml new file mode 100644 index 00000000000..f2c6dfb144d --- /dev/null +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/payment/heating_cost_threshold.yaml @@ -0,0 +1,17 @@ +description: New Hampshire uses these inclusive lower annual heating cost thresholds for benefit bands above the lowest band under the Fuel Assistance Program. +CC: + 2026-10-01: 1_000 +BB: + 2026-10-01: 1_750 +AA: + 2026-10-01: 3_500 + +# Page 78 utility labels overlap at the endpoints; the non-overlapping +# delivered schedule and benefit-determination worksheet establish the tiers. +metadata: + unit: currency-USD + period: year + label: New Hampshire FAP heating cost band thresholds + reference: + - title: PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Benefit Determination and Benefits + href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=76, 78 diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/payment/income_band_upper_bound.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/payment/income_band_upper_bound.yaml new file mode 100644 index 00000000000..dbdd4c2e5e5 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/payment/income_band_upper_bound.yaml @@ -0,0 +1,20 @@ +description: New Hampshire assigns these inclusive upper federal poverty guideline shares to the first five income bands under the Fuel Assistance Program. +A: + 2026-10-01: 0.75 +B: + 2026-10-01: 1 +C: + 2026-10-01: 1.25 +D: + 2026-10-01: 1.5 +E: + 2026-10-01: 1.75 + +# Band F covers income above band E through the separate 60% SMI limit. +metadata: + unit: /1 + period: year + label: New Hampshire FAP income band upper bounds + reference: + - title: PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Income Guidelines + href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=17, 74 diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/payment/utility_multiplier.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/payment/utility_multiplier.yaml new file mode 100644 index 00000000000..7d6d5600cb1 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/payment/utility_multiplier.yaml @@ -0,0 +1,12 @@ +description: New Hampshire multiplies the delivered-fuel scheduled benefit by this share for electric and natural gas heat under the Fuel Assistance Program. +# Whole-dollar half-up rounding reproduces all 24 published utility cells. +values: + 2026-10-01: 0.75 + +metadata: + unit: /1 + period: year + label: New Hampshire FAP utility benefit multiplier + reference: + - title: PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Benefit Determination and Utility Benefits + href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=76, 77, 78 diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/smi_rate.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/smi_rate.yaml new file mode 100644 index 00000000000..926cb63390d --- /dev/null +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/smi_rate.yaml @@ -0,0 +1,11 @@ +description: New Hampshire limits annual income to this share of state median income under the Fuel Assistance Program. +values: + 2026-10-01: 0.6 + +metadata: + unit: /1 + period: year + label: New Hampshire FAP state median income limit rate + reference: + - title: PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Income Guidelines + href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=17, 74 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_base_benefit.yaml b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_base_benefit.yaml new file mode 100644 index 00000000000..f64d4fb7ce0 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_base_benefit.yaml @@ -0,0 +1,2176 @@ +# Source: PY27 NH Fuel Assistance Program Procedures Manual. +# https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf +# Physical PDF pages: 10, 17, 18, 35, 36, 74, 76, 77, 78. +# Period 2027 represents the researched 2026-2027 schedule with 2026 FPG. +# Inputs are annual; SSI and FANF totals are explicitly supplied to isolate receipts. +# This is the actual-cost base schedule, before unresolved heat-in-rent adjustment. +# No final nh_fap award, room-based proxy, or unsupported fuel amount is asserted. + +# Manual p78, delivered DD/A cell. Actual annual primary-fuel bill selects the row. +- name: "Case 1, delivered matrix cost tier DD and income band A." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 500 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 10_000 + nh_fap_base_benefit: 750 + +# Manual p78, utility DD/A cell, including published whole-dollar rounding. +- name: "Case 2, utility matrix cost tier DD and income band A." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "NATURAL_GAS" + heat_expense_included_in_rent: false + receives_housing_assistance: false + gas_expense: 500 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 10_000 + nh_fap_base_benefit: 563 + +# Manual p78, delivered DD/B cell. Actual annual primary-fuel bill selects the row. +- name: "Case 3, delivered matrix cost tier DD and income band B." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 14_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 500 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 14_000 + nh_fap_base_benefit: 650 + +# Manual p78, utility DD/B cell, including published whole-dollar rounding. +- name: "Case 4, utility matrix cost tier DD and income band B." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 14_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "ELECTRICITY" + heat_expense_included_in_rent: false + receives_housing_assistance: false + pre_subsidy_electricity_expense: 500 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 14_000 + nh_fap_base_benefit: 488 + +# Manual p78, delivered DD/C cell. Actual annual primary-fuel bill selects the row. +- name: "Case 5, delivered matrix cost tier DD and income band C." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 18_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 500 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 18_000 + nh_fap_base_benefit: 550 + +# Manual p78, utility DD/C cell, including published whole-dollar rounding. +- name: "Case 6, utility matrix cost tier DD and income band C." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 18_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "NATURAL_GAS" + heat_expense_included_in_rent: false + receives_housing_assistance: false + gas_expense: 500 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 18_000 + nh_fap_base_benefit: 413 + +# Manual p78, delivered DD/D cell. Actual annual primary-fuel bill selects the row. +- name: "Case 7, delivered matrix cost tier DD and income band D." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 22_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 500 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 22_000 + nh_fap_base_benefit: 500 + +# Manual p78, utility DD/D cell, including published whole-dollar rounding. +- name: "Case 8, utility matrix cost tier DD and income band D." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 22_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "ELECTRICITY" + heat_expense_included_in_rent: false + receives_housing_assistance: false + pre_subsidy_electricity_expense: 500 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 22_000 + nh_fap_base_benefit: 375 + +# Manual p78, delivered DD/E cell. Actual annual primary-fuel bill selects the row. +- name: "Case 9, delivered matrix cost tier DD and income band E." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 26_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 500 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 26_000 + nh_fap_base_benefit: 440 + +# Manual p78, utility DD/E cell, including published whole-dollar rounding. +- name: "Case 10, utility matrix cost tier DD and income band E." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 26_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "NATURAL_GAS" + heat_expense_included_in_rent: false + receives_housing_assistance: false + gas_expense: 500 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 26_000 + nh_fap_base_benefit: 330 + +# Manual p78, delivered DD/F cell. Actual annual primary-fuel bill selects the row. +- name: "Case 11, delivered matrix cost tier DD and income band F." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 40_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 500 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 40_000 + nh_fap_base_benefit: 400 + +# Manual p78, utility DD/F cell, including published whole-dollar rounding. +- name: "Case 12, utility matrix cost tier DD and income band F." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 40_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "ELECTRICITY" + heat_expense_included_in_rent: false + receives_housing_assistance: false + pre_subsidy_electricity_expense: 500 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 40_000 + nh_fap_base_benefit: 300 + +# Manual p78, delivered CC/A cell. Actual annual primary-fuel bill selects the row. +- name: "Case 13, delivered matrix cost tier CC and income band A." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 1_200 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 10_000 + nh_fap_base_benefit: 1_460 + +# Manual p78, utility CC/A cell, including published whole-dollar rounding. +- name: "Case 14, utility matrix cost tier CC and income band A." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "ELECTRICITY" + heat_expense_included_in_rent: false + receives_housing_assistance: false + pre_subsidy_electricity_expense: 1_200 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 10_000 + nh_fap_base_benefit: 1_095 + +# Manual p78, delivered CC/B cell. Actual annual primary-fuel bill selects the row. +- name: "Case 15, delivered matrix cost tier CC and income band B." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 14_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 1_200 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 14_000 + nh_fap_base_benefit: 1_270 + +# Manual p78, utility CC/B cell, including published whole-dollar rounding. +- name: "Case 16, utility matrix cost tier CC and income band B." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 14_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "NATURAL_GAS" + heat_expense_included_in_rent: false + receives_housing_assistance: false + gas_expense: 1_200 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 14_000 + nh_fap_base_benefit: 953 + +# Manual p78, delivered CC/C cell. Actual annual primary-fuel bill selects the row. +- name: "Case 17, delivered matrix cost tier CC and income band C." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 18_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 1_200 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 18_000 + nh_fap_base_benefit: 1_150 + +# Manual p78, utility CC/C cell, including published whole-dollar rounding. +- name: "Case 18, utility matrix cost tier CC and income band C." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 18_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "ELECTRICITY" + heat_expense_included_in_rent: false + receives_housing_assistance: false + pre_subsidy_electricity_expense: 1_200 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 18_000 + nh_fap_base_benefit: 863 + +# Manual p78, delivered CC/D cell. Actual annual primary-fuel bill selects the row. +- name: "Case 19, delivered matrix cost tier CC and income band D." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 22_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 1_200 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 22_000 + nh_fap_base_benefit: 950 + +# Manual p78, utility CC/D cell, including published whole-dollar rounding. +- name: "Case 20, utility matrix cost tier CC and income band D." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 22_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "NATURAL_GAS" + heat_expense_included_in_rent: false + receives_housing_assistance: false + gas_expense: 1_200 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 22_000 + nh_fap_base_benefit: 713 + +# Manual p78, delivered CC/E cell. Actual annual primary-fuel bill selects the row. +- name: "Case 21, delivered matrix cost tier CC and income band E." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 26_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 1_200 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 26_000 + nh_fap_base_benefit: 730 + +# Manual p78, utility CC/E cell, including published whole-dollar rounding. +- name: "Case 22, utility matrix cost tier CC and income band E." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 26_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "ELECTRICITY" + heat_expense_included_in_rent: false + receives_housing_assistance: false + pre_subsidy_electricity_expense: 1_200 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 26_000 + nh_fap_base_benefit: 548 + +# Manual p78, delivered CC/F cell. Actual annual primary-fuel bill selects the row. +- name: "Case 23, delivered matrix cost tier CC and income band F." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 40_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 1_200 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 40_000 + nh_fap_base_benefit: 525 + +# Manual p78, utility CC/F cell, including published whole-dollar rounding. +- name: "Case 24, utility matrix cost tier CC and income band F." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 40_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "NATURAL_GAS" + heat_expense_included_in_rent: false + receives_housing_assistance: false + gas_expense: 1_200 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 40_000 + nh_fap_base_benefit: 394 + +# Manual p78, delivered BB/A cell. Actual annual primary-fuel bill selects the row. +- name: "Case 25, delivered matrix cost tier BB and income band A." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 2_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 10_000 + nh_fap_base_benefit: 2_350 + +# Manual p78, utility BB/A cell, including published whole-dollar rounding. +- name: "Case 26, utility matrix cost tier BB and income band A." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "NATURAL_GAS" + heat_expense_included_in_rent: false + receives_housing_assistance: false + gas_expense: 2_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 10_000 + nh_fap_base_benefit: 1_763 + +# Manual p78, delivered BB/B cell. Actual annual primary-fuel bill selects the row. +- name: "Case 27, delivered matrix cost tier BB and income band B." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 14_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 2_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 14_000 + nh_fap_base_benefit: 2_040 + +# Manual p78, utility BB/B cell, including published whole-dollar rounding. +- name: "Case 28, utility matrix cost tier BB and income band B." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 14_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "ELECTRICITY" + heat_expense_included_in_rent: false + receives_housing_assistance: false + pre_subsidy_electricity_expense: 2_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 14_000 + nh_fap_base_benefit: 1_530 + +# Manual p78, delivered BB/C cell. Actual annual primary-fuel bill selects the row. +- name: "Case 29, delivered matrix cost tier BB and income band C." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 18_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 2_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 18_000 + nh_fap_base_benefit: 1_720 + +# Manual p78, utility BB/C cell, including published whole-dollar rounding. +- name: "Case 30, utility matrix cost tier BB and income band C." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 18_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "NATURAL_GAS" + heat_expense_included_in_rent: false + receives_housing_assistance: false + gas_expense: 2_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 18_000 + nh_fap_base_benefit: 1_290 + +# Manual p78, delivered BB/D cell. Actual annual primary-fuel bill selects the row. +- name: "Case 31, delivered matrix cost tier BB and income band D." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 22_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 2_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 22_000 + nh_fap_base_benefit: 1_410 + +# Manual p78, utility BB/D cell, including published whole-dollar rounding. +- name: "Case 32, utility matrix cost tier BB and income band D." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 22_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "ELECTRICITY" + heat_expense_included_in_rent: false + receives_housing_assistance: false + pre_subsidy_electricity_expense: 2_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 22_000 + nh_fap_base_benefit: 1_058 + +# Manual p78, delivered BB/E cell. Actual annual primary-fuel bill selects the row. +- name: "Case 33, delivered matrix cost tier BB and income band E." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 26_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 2_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 26_000 + nh_fap_base_benefit: 1_100 + +# Manual p78, utility BB/E cell, including published whole-dollar rounding. +- name: "Case 34, utility matrix cost tier BB and income band E." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 26_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "NATURAL_GAS" + heat_expense_included_in_rent: false + receives_housing_assistance: false + gas_expense: 2_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 26_000 + nh_fap_base_benefit: 825 + +# Manual p78, delivered BB/F cell. Actual annual primary-fuel bill selects the row. +- name: "Case 35, delivered matrix cost tier BB and income band F." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 40_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 2_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 40_000 + nh_fap_base_benefit: 800 + +# Manual p78, utility BB/F cell, including published whole-dollar rounding. +- name: "Case 36, utility matrix cost tier BB and income band F." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 40_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "ELECTRICITY" + heat_expense_included_in_rent: false + receives_housing_assistance: false + pre_subsidy_electricity_expense: 2_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 40_000 + nh_fap_base_benefit: 600 + +# Manual p78, delivered AA/A cell. Actual annual primary-fuel bill selects the row. +- name: "Case 37, delivered matrix cost tier AA and income band A." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 10_000 + nh_fap_base_benefit: 2_500 + +# Manual p78, utility AA/A cell, including published whole-dollar rounding. +- name: "Case 38, utility matrix cost tier AA and income band A." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "ELECTRICITY" + heat_expense_included_in_rent: false + receives_housing_assistance: false + pre_subsidy_electricity_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 10_000 + nh_fap_base_benefit: 1_875 + +# Manual p78, delivered AA/B cell. Actual annual primary-fuel bill selects the row. +- name: "Case 39, delivered matrix cost tier AA and income band B." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 14_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 14_000 + nh_fap_base_benefit: 2_150 + +# Manual p78, utility AA/B cell, including published whole-dollar rounding. +- name: "Case 40, utility matrix cost tier AA and income band B." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 14_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "NATURAL_GAS" + heat_expense_included_in_rent: false + receives_housing_assistance: false + gas_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 14_000 + nh_fap_base_benefit: 1_613 + +# Manual p78, delivered AA/C cell. Actual annual primary-fuel bill selects the row. +- name: "Case 41, delivered matrix cost tier AA and income band C." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 18_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 18_000 + nh_fap_base_benefit: 1_825 + +# Manual p78, utility AA/C cell, including published whole-dollar rounding. +- name: "Case 42, utility matrix cost tier AA and income band C." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 18_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "ELECTRICITY" + heat_expense_included_in_rent: false + receives_housing_assistance: false + pre_subsidy_electricity_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 18_000 + nh_fap_base_benefit: 1_369 + +# Manual p78, delivered AA/D cell. Actual annual primary-fuel bill selects the row. +- name: "Case 43, delivered matrix cost tier AA and income band D." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 22_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 22_000 + nh_fap_base_benefit: 1_500 + +# Manual p78, utility AA/D cell, including published whole-dollar rounding. +- name: "Case 44, utility matrix cost tier AA and income band D." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 22_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "NATURAL_GAS" + heat_expense_included_in_rent: false + receives_housing_assistance: false + gas_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 22_000 + nh_fap_base_benefit: 1_125 + +# Manual p78, delivered AA/E cell. Actual annual primary-fuel bill selects the row. +- name: "Case 45, delivered matrix cost tier AA and income band E." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 26_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 26_000 + nh_fap_base_benefit: 1_200 + +# Manual p78, utility AA/E cell, including published whole-dollar rounding. +- name: "Case 46, utility matrix cost tier AA and income band E." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 26_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "ELECTRICITY" + heat_expense_included_in_rent: false + receives_housing_assistance: false + pre_subsidy_electricity_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 26_000 + nh_fap_base_benefit: 900 + +# Manual p78, delivered AA/F cell. Actual annual primary-fuel bill selects the row. +- name: "Case 47, delivered matrix cost tier AA and income band F." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 40_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 40_000 + nh_fap_base_benefit: 825 + +# Manual p78, utility AA/F cell, including published whole-dollar rounding. +- name: "Case 48, utility matrix cost tier AA and income band F." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 40_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "NATURAL_GAS" + heat_expense_included_in_rent: false + receives_housing_assistance: false + gas_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 40_000 + nh_fap_base_benefit: 619 + +# Manual pp17 and74; one-person 2026 FPG of 15,960 for the initial PY2027 snapshot. +- name: "Case 49, income exactly 11970 remains in its inclusive FPG band." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 11_970 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 2_500 + +# Manual pp17,74 and78; strict upper-band transition. +- name: "Case 50, income one cent above 11970 enters the next FPG band." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 11_970.01 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 2_150 + +# Manual pp17 and74; one-person 2026 FPG of 15,960 for the initial PY2027 snapshot. +- name: "Case 51, income exactly 15960 remains in its inclusive FPG band." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 15_960 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 2_150 + +# Manual pp17,74 and78; strict upper-band transition. +- name: "Case 52, income one cent above 15960 enters the next FPG band." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 15_960.01 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 1_825 + +# Manual pp17 and74; one-person 2026 FPG of 15,960 for the initial PY2027 snapshot. +- name: "Case 53, income exactly 19950 remains in its inclusive FPG band." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 19_950 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 1_825 + +# Manual pp17,74 and78; strict upper-band transition. +- name: "Case 54, income one cent above 19950 enters the next FPG band." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 19_950.01 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 1_500 + +# Manual pp17 and74; one-person 2026 FPG of 15,960 for the initial PY2027 snapshot. +- name: "Case 55, income exactly 23940 remains in its inclusive FPG band." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 23_940 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 1_500 + +# Manual pp17,74 and78; strict upper-band transition. +- name: "Case 56, income one cent above 23940 enters the next FPG band." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 23_940.01 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 1_200 + +# Manual pp17 and74; one-person 2026 FPG of 15,960 for the initial PY2027 snapshot. +- name: "Case 57, income exactly 27930 remains in its inclusive FPG band." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 27_930 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 1_200 + +# Manual pp17,74 and78; strict upper-band transition. +- name: "Case 58, income one cent above 27930 enters the next FPG band." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 27_930.01 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 825 + +# Manual p77 exact ECOS ranges control the overlapping printed utility labels on p78. +- name: "Case 59, delivered annual cost 999.99 selects the current cost tier." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 999.99 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 750 + +# Manual pp77-78 exact cost ranges and utility schedule. +- name: "Case 60, utility annual cost 999.99 selects the current cost tier." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "NATURAL_GAS" + heat_expense_included_in_rent: false + receives_housing_assistance: false + gas_expense: 999.99 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 563 + +# Manual p77 exact ECOS ranges control the overlapping printed utility labels on p78. +- name: "Case 61, delivered annual cost 1000 selects the current cost tier." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 1_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 1_460 + +# Manual pp77-78 exact cost ranges and utility schedule. +- name: "Case 62, utility annual cost 1000 selects the current cost tier." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "NATURAL_GAS" + heat_expense_included_in_rent: false + receives_housing_assistance: false + gas_expense: 1_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 1_095 + +# Manual p77 exact ECOS ranges control the overlapping printed utility labels on p78. +- name: "Case 63, delivered annual cost 1749.99 selects the current cost tier." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 1_749.99 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 1_460 + +# Manual pp77-78 exact cost ranges and utility schedule. +- name: "Case 64, utility annual cost 1749.99 selects the current cost tier." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "NATURAL_GAS" + heat_expense_included_in_rent: false + receives_housing_assistance: false + gas_expense: 1_749.99 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 1_095 + +# Manual p77 exact ECOS ranges control the overlapping printed utility labels on p78. +- name: "Case 65, delivered annual cost 1750 selects the current cost tier." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 1_750 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 2_350 + +# Manual pp77-78 exact cost ranges and utility schedule. +- name: "Case 66, utility annual cost 1750 selects the current cost tier." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "NATURAL_GAS" + heat_expense_included_in_rent: false + receives_housing_assistance: false + gas_expense: 1_750 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 1_763 + +# Manual p77 exact ECOS ranges control the overlapping printed utility labels on p78. +- name: "Case 67, delivered annual cost 3499.99 selects the current cost tier." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 3_499.99 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 2_350 + +# Manual pp77-78 exact cost ranges and utility schedule. +- name: "Case 68, utility annual cost 3499.99 selects the current cost tier." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "NATURAL_GAS" + heat_expense_included_in_rent: false + receives_housing_assistance: false + gas_expense: 3_499.99 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 1_763 + +# Manual p77 exact ECOS ranges control the overlapping printed utility labels on p78. +- name: "Case 69, delivered annual cost 3500 selects the current cost tier." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 3_500 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 2_500 + +# Manual pp77-78 exact cost ranges and utility schedule. +- name: "Case 70, utility annual cost 3500 selects the current cost tier." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "NATURAL_GAS" + heat_expense_included_in_rent: false + receives_housing_assistance: false + gas_expense: 3_500 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 1_875 + +# Manual p77 multiplier 1.000 and p78 BB/F delivered benefit. +- name: "Case 71, kerosene uses the full delivered-fuel schedule." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 40_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "KEROSENE" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 2_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 800 + +# Manual p77 multiplier 1.000 and p78 BB/F delivered benefit. +- name: "Case 72, propane uses the full delivered-fuel schedule." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 40_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "PROPANE" + heat_expense_included_in_rent: false + receives_housing_assistance: false + bottled_gas_expense: 2_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 800 + +# Manual p77 multiplier 1.000 and p78 BB/F delivered benefit. +- name: "Case 73, wood uses the full delivered-fuel schedule." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 40_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "WOOD" + heat_expense_included_in_rent: false + receives_housing_assistance: false + wood_expense: 2_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 800 + +# Manual p77 multiplier 1.000 and p78 BB/F delivered benefit. +- name: "Case 74, coal uses the full delivered-fuel schedule." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 40_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "COAL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + coal_expense: 2_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 800 + +# Manual p78 DD/A amount is750; no actual-cost cap is stated. +- name: "Case 75, actual-cost schedule is not capped at a small positive bill." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 100 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 750 + +# Manual p10 provides application priority; pp77-78 contain no age supplement. +- name: "Case 76, an older adult receives the same regular schedule." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 60 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 14_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 2_150 + +# Manual p10 provides application priority; pp77-78 contain no disability supplement. +- name: "Case 77, disability does not add an unsourced regular supplement." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + is_disabled: true + employment_income: 14_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 2_150 + +# Manual p74: size 2 D band ends at 32,460; p78 AA/D is 1500. +- name: "Case 78, two qualified members use the published two-person FPG." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 15_000 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 15_000 + spm_units: + spm_unit1: + members: ["person1", "person2"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2"] + state_code: "NH" + output: + nh_fap_household_size: 2 + nh_fap_countable_income: 30_000 + nh_fap_base_benefit: 1_500 + +# Manual p36 includes ineligible-member income but excludes their size; size 1 band F applies without income proration. +- name: "Case 79, mixed status counts both incomes but only the qualified person." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 15_000 + person2: + age: 40 + immigration_status: "UNDOCUMENTED" + ssi: 0 + employment_income: 15_000 + spm_units: + spm_unit1: + members: ["person1", "person2"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2"] + state_code: "NH" + output: + nh_fap_household_size: 1 + nh_fap_countable_income: 30_000 + nh_fap_base_benefit: 825 + +# Manual p74 size 1 ceiling 48,713; p78 AA/F is 825. +- name: "Case 80, the inclusive income ceiling retains the base schedule." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 48_713 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 825 + +# Manual p74 size 1 maximum 48,713. +- name: "Case 81, income above the ceiling removes the scheduled benefit." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 48_714 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_base_benefit: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_countable_income.yaml new file mode 100644 index 00000000000..5d69c2a2836 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_countable_income.yaml @@ -0,0 +1,780 @@ +# Source: PY27 NH Fuel Assistance Program Procedures Manual. +# https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf +# Physical PDF pages: 13, 14, 15, 16, 17, 23, 25, 26, 29, 34, 36. +# Period 2027 represents the researched 2026-2027 schedule with 2026 FPG. +# Inputs are annual; SSI and FANF totals are explicitly supplied to isolate receipts. +# Investment treatment follows the specific separate exclusions on pp15-16; +# p17's combined wording remains an explicitly documented interpretation gap. + +# Manual p15 counts salaries and wages before deductions; p17 does not list a general earned-income disregard. +- name: "Case 1, gross annual wages receive no general earnings disregard." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 12_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 12_000 + +# Manual pp16 and23 specifically exclude regular child support. +- name: "Case 2, regular child support received is excluded." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 11_900 + child_support_received: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 11_900 + +# Manual pp17 and23 allow regular support paid during the income period. +- name: "Case 3, documented child support paid is deducted." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 12_100 + child_support_expense: 200 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 11_900 + +# Contract household income floor; sourced support deduction exceeds otherwise countable receipts. +- name: "Case 4, support expenses cannot produce negative household income." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 100 + child_support_expense: 200 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 0 + +# Manual p16 excludes SSI for children under 18; annual SSI input represents total receipts. +- name: "Case 5, SSI for a child just below age eighteen is excluded." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + person2: + age: 17 + immigration_status: "CITIZEN" + ssi: 6_000 + is_disabled: true + spm_units: + spm_unit1: + members: ["person1", "person2"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2"] + state_code: "NH" + output: + nh_fap_countable_income: 10_000 + +# Manual pp15-16 adult SSI is countable; exclusion ends at 18. +- name: "Case 6, SSI at age eighteen is countable." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + person2: + age: 18 + immigration_status: "CITIZEN" + ssi: 6_000 + is_disabled: true + spm_units: + spm_unit1: + members: ["person1", "person2"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2"] + state_code: "NH" + output: + nh_fap_countable_income: 16_000 + +# Manual p16 excludes earned income from full-time high-school students, without a stated age 18 cutoff. +- name: "Case 7, full-time high-school wages and net business income are excluded." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + person2: + age: 18 + immigration_status: "CITIZEN" + ssi: 0 + is_full_time_student: true + is_in_secondary_school: true + employment_income: 3_000 + self_employment_income: 2_000 + spm_units: + spm_unit1: + members: ["person1", "person2"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2"] + state_code: "NH" + output: + nh_fap_countable_income: 10_000 + +# Manual p16 requires full-time high-school attendance for the earnings exclusion. +- name: "Case 8, part-time high-school earnings remain countable." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + person2: + age: 18 + immigration_status: "CITIZEN" + ssi: 0 + is_full_time_student: false + is_in_secondary_school: true + employment_income: 3_000 + spm_units: + spm_unit1: + members: ["person1", "person2"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2"] + state_code: "NH" + output: + nh_fap_countable_income: 13_000 + +# Manual pp16 and34 distinguish high-school earnings from a resident college student's countable income. +- name: "Case 9, full-time college earnings remain countable." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + person2: + age: 19 + immigration_status: "CITIZEN" + ssi: 0 + is_full_time_student: true + is_in_secondary_school: false + employment_income: 3_000 + spm_units: + spm_unit1: + members: ["person1", "person2"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2"] + state_code: "NH" + output: + nh_fap_countable_income: 13_000 + +# Manual p16 exempts specified full-time high-school earnings, not every minor's earnings. +- name: "Case 10, a minor without reported high-school attendance has countable earnings." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + person2: + age: 17 + immigration_status: "CITIZEN" + ssi: 0 + is_full_time_student: false + is_in_secondary_school: false + employment_income: 3_000 + spm_units: + spm_unit1: + members: ["person1", "person2"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2"] + state_code: "NH" + output: + nh_fap_countable_income: 13_000 + +# Manual p15 counts Social Security; p16 high-school exclusion is for earned income. +- name: "Case 11, high-school exclusion does not exempt unearned Social Security." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + person2: + age: 18 + immigration_status: "CITIZEN" + ssi: 0 + is_full_time_student: true + is_in_secondary_school: true + employment_income: 3_000 + social_security: 1_200 + spm_units: + spm_unit1: + members: ["person1", "person2"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2"] + state_code: "NH" + output: + nh_fap_countable_income: 11_200 + +# Manual p36 requires income of ineligible noncitizens in household income. +- name: "Case 12, mixed status includes all income without proration." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 15_000 + person2: + age: 40 + immigration_status: "UNDOCUMENTED" + ssi: 0 + employment_income: 15_000 + spm_units: + spm_unit1: + members: ["person1", "person2"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2"] + state_code: "NH" + output: + nh_fap_countable_income: 30_000 + +# Manual p26 example counts profitable business 800 and loss business 0; reported net inputs used without reconstructing deductions. +- name: "Case 13, each reported business source loss is floored separately." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + self_employment_income: 800 + sstb_self_employment_income: -600 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 10_800 + +# Manual p26 prohibits subtracting negative self-employment income from other household income. +- name: "Case 14, one member's business loss does not offset another member's income." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + self_employment_income: 800 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + self_employment_income: -600 + spm_units: + spm_unit1: + members: ["person1", "person2"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2"] + state_code: "NH" + output: + nh_fap_countable_income: 800 + +# Manual p15 net rent and pp25-29 business treatment, with user-directed nonnegative rental streams. +- name: "Case 15, negative rental income does not offset a separate positive rental stream." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + rental_income: -300 + farm_rent_income: 600 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 10_600 + +# Manual p25 requires Schedule F farms and partnership/corporation returns; reported net sources are separate. +- name: "Case 16, reported farm and partnership business profits count once." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + farm_operations_income: 800 + partnership_income: 600 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 1_400 + +# Manual pp15-16 separate interest and dividend exclusions; p17 wording ambiguity is retained in source documentation. +- name: "Case 17, the first fifty dollars of each investment source is excluded." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + interest_income: 50 + ordinary_dividend_income: 50 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 0 + +# Manual pp15-16: (51-50)+(51-50)=2 under the documented separate-source interpretation. +- name: "Case 18, one dollar above each investment disregard is countable." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + interest_income: 51 + ordinary_dividend_income: 51 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 2 + +# Contract interpretation of household income: (80-50)+(60-50)=40, not a separate 50 allowance per person. +- name: "Case 19, investment disregards apply once per household source." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + interest_income: 40 + ordinary_dividend_income: 20 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + interest_income: 40 + ordinary_dividend_income: 40 + spm_units: + spm_unit1: + members: ["person1", "person2"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2"] + state_code: "NH" + output: + nh_fap_countable_income: 40 + +# Manual pp15-16 counts dividends above 50; canonical ordinary_dividend_income includes both dividend components. +- name: "Case 20, qualified and nonqualified dividends feed the canonical ordinary aggregate." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + qualified_dividend_income: 100 + non_qualified_dividend_income: 100 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 150 + +# Manual p15; annual SSI receipts count once. Medicare reconstruction is expressly deferred. +- name: "Case 21, gross Social Security and adult SSI are both countable." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 6_000 + social_security: 12_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 18_000 + +# Manual p15 lists pensions, retirement withdrawals, unemployment, workers compensation, alimony and regular estates/trust receipts. +- name: "Case 22, retirement receipts and other regular listed cash income count." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + pension_income: 2_400 + retirement_distributions: 1_200 + unemployment_compensation: 1_200 + workers_compensation: 600 + alimony_income: 600 + estate_income: 600 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 6_600 + +# Manual p15 counts welfare, regular outside support, nonexempt veterans benefits and strike pay; fixtures represent regular otherwise countable receipts. +- name: "Case 23, reported regular public and private support income counts." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + general_assistance: 600 + financial_assistance: 1_200 + veterans_benefits: 1_200 + strike_benefits: 600 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 3_600 + +# Manual p15 counts FANF, SSI and Social Security: 1000+2400+4800+3600; FANF is a household annual total. +- name: "Case 24, state FANF receipts count once for the household." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 1_000 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 2_400 + person3: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + social_security: 4_800 + spm_units: + spm_unit1: + members: ["person1", "person2", "person3"] + nh_fanf: 3_600 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2", "person3"] + state_code: "NH" + output: + nh_fap_countable_income: 11_800 + +# Manual p16 excludes capital gains. +- name: "Case 25, capital gains are excluded." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + short_term_capital_gains: 4_000 + long_term_capital_gains: 6_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 10_000 + +# Manual p17 does not allow generic medical or child-care expenses. Medicare-only reconstruction is deferred. +- name: "Case 26, no automatic medical or child-care deduction is applied." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 12_000 + other_medical_expenses: 1_200 + health_insurance_premiums: 2_400 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + childcare_expenses: 3_600 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 12_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml new file mode 100644 index 00000000000..e87d3305001 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml @@ -0,0 +1,912 @@ +# Source: PY27 NH Fuel Assistance Program Procedures Manual. +# https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf +# Physical PDF pages: 12, 17, 21, 35, 36, 74. +# Period 2027 represents the researched 2026-2027 schedule with 2026 FPG. +# Inputs are annual; SSI and FANF totals are explicitly supplied to isolate receipts. +# Heat-in-rent cases assert eligibility only, not an unresolved final award. + +# Manual pp12,17 and74; maximum is inclusive. +- name: "Case 1, income at the 1-person annual ceiling is eligible." + period: 2027 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 48_713 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_eligible: true + +# Manual pp12,17 and74; above published maximum. +- name: "Case 2, income one cent above the 1-person annual ceiling is ineligible." + period: 2027 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 48_713.01 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_eligible: false + +# Manual pp12,17 and74; maximum is inclusive. +- name: "Case 3, income at the 2-person annual ceiling is eligible." + period: 2027 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 63_702 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + spm_units: + spm_unit1: + members: ["person1", "person2"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2"] + state_code: "NH" + output: + nh_fap_eligible: true + +# Manual pp12,17 and74; above published maximum. +- name: "Case 4, income one cent above the 2-person annual ceiling is ineligible." + period: 2027 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 63_702.01 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + spm_units: + spm_unit1: + members: ["person1", "person2"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2"] + state_code: "NH" + output: + nh_fap_eligible: false + +# Manual pp12,17 and74; maximum is inclusive. +- name: "Case 5, income at the 3-person annual ceiling is eligible." + period: 2027 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 78_691 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person3: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + spm_units: + spm_unit1: + members: ["person1", "person2", "person3"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2", "person3"] + state_code: "NH" + output: + nh_fap_eligible: true + +# Manual pp12,17 and74; above published maximum. +- name: "Case 6, income one cent above the 3-person annual ceiling is ineligible." + period: 2027 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 78_691.01 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person3: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + spm_units: + spm_unit1: + members: ["person1", "person2", "person3"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2", "person3"] + state_code: "NH" + output: + nh_fap_eligible: false + +# Manual pp12,17 and74; maximum is inclusive. +- name: "Case 7, income at the 4-person annual ceiling is eligible." + period: 2027 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 93_680 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person3: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person4: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + spm_units: + spm_unit1: + members: ["person1", "person2", "person3", "person4"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2", "person3", "person4"] + state_code: "NH" + output: + nh_fap_eligible: true + +# Manual pp12,17 and74; above published maximum. +- name: "Case 8, income one cent above the 4-person annual ceiling is ineligible." + period: 2027 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 93_680.01 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person3: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person4: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + spm_units: + spm_unit1: + members: ["person1", "person2", "person3", "person4"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2", "person3", "person4"] + state_code: "NH" + output: + nh_fap_eligible: false + +# Manual pp12,17 and74; maximum is inclusive. +- name: "Case 9, income at the 5-person annual ceiling is eligible." + period: 2027 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 108_668 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person3: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person4: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person5: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + spm_units: + spm_unit1: + members: ["person1", "person2", "person3", "person4", "person5"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2", "person3", "person4", "person5"] + state_code: "NH" + output: + nh_fap_eligible: true + +# Manual pp12,17 and74; above published maximum. +- name: "Case 10, income one cent above the 5-person annual ceiling is ineligible." + period: 2027 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 108_668.01 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person3: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person4: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person5: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + spm_units: + spm_unit1: + members: ["person1", "person2", "person3", "person4", "person5"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2", "person3", "person4", "person5"] + state_code: "NH" + output: + nh_fap_eligible: false + +# Manual pp12,17 and74; maximum is inclusive. +- name: "Case 11, income at the 6-person annual ceiling is eligible." + period: 2027 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 123_657 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person3: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person4: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person5: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person6: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + spm_units: + spm_unit1: + members: ["person1", "person2", "person3", "person4", "person5", "person6"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2", "person3", "person4", "person5", "person6"] + state_code: "NH" + output: + nh_fap_eligible: true + +# Manual pp12,17 and74; above published maximum. +- name: "Case 12, income one cent above the 6-person annual ceiling is ineligible." + period: 2027 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 123_657.01 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person3: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person4: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person5: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person6: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + spm_units: + spm_unit1: + members: ["person1", "person2", "person3", "person4", "person5", "person6"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2", "person3", "person4", "person5", "person6"] + state_code: "NH" + output: + nh_fap_eligible: false + +# Manual pp12,17 and74; maximum is inclusive. +- name: "Case 13, income at the 7-person annual ceiling is eligible." + period: 2027 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 126_468 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person3: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person4: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person5: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person6: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person7: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + spm_units: + spm_unit1: + members: ["person1", "person2", "person3", "person4", "person5", "person6", "person7"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2", "person3", "person4", "person5", "person6", "person7"] + state_code: "NH" + output: + nh_fap_eligible: true + +# Manual pp12,17 and74; above published maximum. +- name: "Case 14, income one cent above the 7-person annual ceiling is ineligible." + period: 2027 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 126_468.01 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person3: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person4: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person5: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person6: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person7: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + spm_units: + spm_unit1: + members: ["person1", "person2", "person3", "person4", "person5", "person6", "person7"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2", "person3", "person4", "person5", "person6", "person7"] + state_code: "NH" + output: + nh_fap_eligible: false + +# Manual pp12,17 and74; maximum is inclusive. +- name: "Case 15, income at the 8-person annual ceiling is eligible." + period: 2027 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 129_278 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person3: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person4: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person5: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person6: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person7: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person8: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + spm_units: + spm_unit1: + members: ["person1", "person2", "person3", "person4", "person5", "person6", "person7", "person8"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2", "person3", "person4", "person5", "person6", "person7", "person8"] + state_code: "NH" + output: + nh_fap_eligible: true + +# Manual pp12,17 and74; above published maximum. +- name: "Case 16, income one cent above the 8-person annual ceiling is ineligible." + period: 2027 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 129_278.01 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person3: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person4: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person5: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person6: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person7: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person8: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + spm_units: + spm_unit1: + members: ["person1", "person2", "person3", "person4", "person5", "person6", "person7", "person8"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2", "person3", "person4", "person5", "person6", "person7", "person8"] + state_code: "NH" + output: + nh_fap_eligible: false + +# Manual p12 primary New Hampshire residence required. +- name: "Case 17, a residence outside New Hampshire is ineligible." + period: 2027 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "MA" + output: + nh_fap_eligible: false + +# Manual pp35-36 require a qualified recipient. +- name: "Case 18, no qualified household members are ineligible." + period: 2027 + input: + people: + person1: + age: 40 + immigration_status: "UNDOCUMENTED" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_eligible: false + +# Contract represents no heating exposure through heating_type NONE; manual p12 requires energy responsibility. +- name: "Case 19, no heating type is ineligible despite an unrelated fuel bill." + period: 2027 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "NONE" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_eligible: false + +# Manual p12 requires responsibility for energy costs; proxy room inputs are unavailable. +- name: "Case 20, zero direct cost without heat in rent is ineligible." + period: 2027 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 0 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_eligible: false + +# Manual p12 has no positive-dollar expense minimum. +- name: "Case 21, a positive direct heating cost satisfies energy responsibility." + period: 2027 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 0.01 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_eligible: true + +# Manual pp12 and21. Eligibility only: the unresolved heat-in-rent award adjustment is not asserted. +- name: "Case 22, nonsubsidized heat in rent can qualify without a separate bill." + period: 2027 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: true + receives_housing_assistance: false + fuel_oil_expense: 0 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_eligible: true + +# Manual p21 excludes subsidized housing with heat included in rent. +- name: "Case 23, subsidized housing with heat included in rent is ineligible." + period: 2027 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: true + receives_housing_assistance: true + fuel_oil_expense: 0 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_eligible: false + +# Manual p21 permits subsidized tenants otherwise eligible when they pay their own heat. +- name: "Case 24, subsidized housing with separately paid heat can qualify." + period: 2027 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: true + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_eligible: true + +# Manual p35 all qualified noncitizens regardless of entry date. +- name: "Case 25, a new lawful permanent resident has no five-year bar." + period: 2027 + input: + people: + person1: + age: 40 + immigration_status: "LEGAL_PERMANENT_RESIDENT" + ssi: 0 + years_since_us_entry: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_eligible: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_household_size.yaml b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_household_size.yaml new file mode 100644 index 00000000000..7b77d9ff8cc --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_household_size.yaml @@ -0,0 +1,279 @@ +# Source: PY27 NH Fuel Assistance Program Procedures Manual. +# https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf +# Physical PDF pages: 12, 35, 36. +# Period 2027 represents the researched 2026-2027 schedule with 2026 FPG. +# Inputs are annual; SSI and FANF totals are explicitly supplied to isolate receipts. + +# Manual pp35-36 includes the reported qualified status regardless of entry date. +- name: "Case 1, citizen is a qualified household member." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + years_since_us_entry: 0 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_household_size: 1 + +# Manual pp35-36 includes the reported qualified status regardless of entry date. +- name: "Case 2, legal_permanent_resident is a qualified household member." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "LEGAL_PERMANENT_RESIDENT" + ssi: 0 + years_since_us_entry: 0 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_household_size: 1 + +# Manual pp35-36 includes the reported qualified status regardless of entry date. +- name: "Case 3, refugee is a qualified household member." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "REFUGEE" + ssi: 0 + years_since_us_entry: 0 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_household_size: 1 + +# Manual pp35-36 includes the reported qualified status regardless of entry date. +- name: "Case 4, asylee is a qualified household member." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "ASYLEE" + ssi: 0 + years_since_us_entry: 0 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_household_size: 1 + +# Manual pp35-36 includes the reported qualified status regardless of entry date. +- name: "Case 5, paroled_one_year is a qualified household member." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "PAROLED_ONE_YEAR" + ssi: 0 + years_since_us_entry: 0 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_household_size: 1 + +# Manual pp35-36 includes the reported qualified status regardless of entry date. +- name: "Case 6, deportation_withheld is a qualified household member." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "DEPORTATION_WITHHELD" + ssi: 0 + years_since_us_entry: 0 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_household_size: 1 + +# Manual pp35-36 includes the reported qualified status regardless of entry date. +- name: "Case 7, conditional_entrant is a qualified household member." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CONDITIONAL_ENTRANT" + ssi: 0 + years_since_us_entry: 0 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_household_size: 1 + +# Manual pp35-36 includes the reported qualified status regardless of entry date. +- name: "Case 8, cuban_haitian_entrant is a qualified household member." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CUBAN_HAITIAN_ENTRANT" + ssi: 0 + years_since_us_entry: 0 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_household_size: 1 + +# Manual pp35-36 and existing qualified-status mapping; income is tested separately. +- name: "Case 9, mixed status excludes ineligible members only from household size." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + person2: + age: 40 + immigration_status: "UNDOCUMENTED" + ssi: 0 + person3: + age: 40 + immigration_status: "DACA" + ssi: 0 + person4: + age: 40 + immigration_status: "TPS" + ssi: 0 + person5: + age: 40 + immigration_status: "REFUGEE" + ssi: 0 + spm_units: + spm_unit1: + members: ["person1", "person2", "person3", "person4", "person5"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2", "person3", "person4", "person5"] + state_code: "NH" + output: + nh_fap_household_size: 2 + +# Manual p36 does not count an ineligible noncitizen for eligibility size. +- name: "Case 10, an entirely ineligible-status household has size zero." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "UNDOCUMENTED" + ssi: 0 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_household_size: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_income_limit.yaml b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_income_limit.yaml new file mode 100644 index 00000000000..f1f17864b03 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_income_limit.yaml @@ -0,0 +1,361 @@ +# Source: PY27 NH Fuel Assistance Program Procedures Manual. +# https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf +# Physical PDF pages: 17, 74. +# Period 2027 represents the researched 2026-2027 schedule with 2026 FPG. +# Inputs are annual; SSI and FANF totals are explicitly supplied to isolate receipts. + +# Manual p74 literal published annual maximum; the separate reconciliation documents inferred rounding. +- name: "Case 1, published annual ceiling for 1 qualified household member." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 48_713 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_household_size: 1 + nh_fap_income_limit: 48_713 + +# Manual p74 literal published annual maximum; the separate reconciliation documents inferred rounding. +- name: "Case 2, published annual ceiling for 2 qualified household members." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 63_702 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + spm_units: + spm_unit1: + members: ["person1", "person2"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2"] + state_code: "NH" + output: + nh_fap_household_size: 2 + nh_fap_income_limit: 63_702 + +# Manual p74 literal published annual maximum; the separate reconciliation documents inferred rounding. +- name: "Case 3, published annual ceiling for 3 qualified household members." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 78_691 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person3: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + spm_units: + spm_unit1: + members: ["person1", "person2", "person3"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2", "person3"] + state_code: "NH" + output: + nh_fap_household_size: 3 + nh_fap_income_limit: 78_691 + +# Manual p74 literal published annual maximum; the separate reconciliation documents inferred rounding. +- name: "Case 4, published annual ceiling for 4 qualified household members." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 93_680 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person3: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person4: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + spm_units: + spm_unit1: + members: ["person1", "person2", "person3", "person4"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2", "person3", "person4"] + state_code: "NH" + output: + nh_fap_household_size: 4 + nh_fap_income_limit: 93_680 + +# Manual p74 literal published annual maximum; the separate reconciliation documents inferred rounding. +- name: "Case 5, published annual ceiling for 5 qualified household members." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 108_668 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person3: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person4: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person5: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + spm_units: + spm_unit1: + members: ["person1", "person2", "person3", "person4", "person5"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2", "person3", "person4", "person5"] + state_code: "NH" + output: + nh_fap_household_size: 5 + nh_fap_income_limit: 108_668 + +# Manual p74 literal published annual maximum; the separate reconciliation documents inferred rounding. +- name: "Case 6, published annual ceiling for 6 qualified household members." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 123_657 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person3: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person4: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person5: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person6: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + spm_units: + spm_unit1: + members: ["person1", "person2", "person3", "person4", "person5", "person6"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2", "person3", "person4", "person5", "person6"] + state_code: "NH" + output: + nh_fap_household_size: 6 + nh_fap_income_limit: 123_657 + +# Manual p74 literal published annual maximum; the separate reconciliation documents inferred rounding. +- name: "Case 7, published annual ceiling for 7 qualified household members." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 126_468 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person3: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person4: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person5: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person6: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person7: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + spm_units: + spm_unit1: + members: ["person1", "person2", "person3", "person4", "person5", "person6", "person7"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2", "person3", "person4", "person5", "person6", "person7"] + state_code: "NH" + output: + nh_fap_household_size: 7 + nh_fap_income_limit: 126_468 + +# Manual p74 literal published annual maximum; the separate reconciliation documents inferred rounding. +- name: "Case 8, published annual ceiling for 8 qualified household members." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 129_278 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person3: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person4: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person5: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person6: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person7: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + person8: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 0 + spm_units: + spm_unit1: + members: ["person1", "person2", "person3", "person4", "person5", "person6", "person7", "person8"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1", "person2", "person3", "person4", "person5", "person6", "person7", "person8"] + state_code: "NH" + output: + nh_fap_household_size: 8 + nh_fap_income_limit: 129_278 diff --git a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_base_benefit.py b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_base_benefit.py new file mode 100644 index 00000000000..65dd01b1923 --- /dev/null +++ b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_base_benefit.py @@ -0,0 +1,64 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg + + +class nh_fap_base_benefit(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "New Hampshire FAP actual-cost scheduled benefit" + documentation = "Actual-cost scheduled intermediate before any heat-in-rent adjustment, not the final FAP award. Verified against the PY2027 schedule; earlier results are unverified historical estimates. Zero can mean that actual cost or fuel classification is unsupported, rather than legal ineligibility." + reference = "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=10, 17, 74, 76, 77, 78" + defined_for = "nh_fap_eligible" + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.nh.doe.fap + size = spm_unit("nh_fap_household_size", period) + state_group = spm_unit.household("state_group_str", period) + # Use the guideline at the start of the modeled heating season. + # Within-season February guideline changes are not separately modeled. + poverty_guideline = fpg( + size, state_group, period, parameters, year_lag=p.fpg_year_lag + ) + income = spm_unit("nh_fap_countable_income", period) + bounds = p.payment.income_band_upper_bound + income_band = select( + [ + income <= poverty_guideline * bounds.A, + income <= poverty_guideline * bounds.B, + income <= poverty_guideline * bounds.C, + income <= poverty_guideline * bounds.D, + income <= poverty_guideline * bounds.E, + ], + ["A", "B", "C", "D", "E"], + default="F", + ) + expense = spm_unit("heating_expense", period) + thresholds = p.payment.heating_cost_threshold + cost_band = select( + [expense < thresholds.CC, expense < thresholds.BB, expense < thresholds.AA], + ["DD", "CC", "BB"], + default="AA", + ) + delivered_benefit = p.payment.delivered_benefit[cost_band][income_band] + heating_type = spm_unit("heating_type", period) + fuel = heating_type.possible_values + utility = (heating_type == fuel.ELECTRICITY) | ( + heating_type == fuel.NATURAL_GAS + ) + delivered = ( + (heating_type == fuel.FUEL_OIL) + | (heating_type == fuel.PROPANE) + | (heating_type == fuel.KEROSENE) + | (heating_type == fuel.WOOD) + | (heating_type == fuel.COAL) + ) + multiplier = where(utility, p.payment.utility_multiplier, 1) + # Half-up whole dollars reproduce every utility matrix cell. + benefit = np.floor(delivered_benefit * multiplier + 0.5) + # No heat-in-rent adjustment is inferred. Missing heated-room data + # prevents the proxy route. Unknown, solar and other fuels have no + # supported payment classification; zero marks an unsupported amount. + # The schedule is not capped at the household's actual heating cost. + return where((expense > 0) & (utility | delivered), benefit, 0) diff --git a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py new file mode 100644 index 00000000000..a4d6c5503e0 --- /dev/null +++ b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py @@ -0,0 +1,60 @@ +from policyengine_us.model_api import * + + +class nh_fap_countable_income(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "New Hampshire FAP countable income" + reference = "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=13, 14, 15, 16, 17, 23, 25, 26, 27, 28, 29, 34, 35, 36" + defined_for = StateCode.NH + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.nh.doe.fap.income + person = spm_unit.members + # Annual inputs approximate application-period income annualization. + # Separate business losses cannot offset positive sources. Existing + # net inputs lack depreciation, amortization and home-business addbacks; + # partnership/S-corporation inputs also lack FAP expense detail. + business_income = sum( + max_(person(source, period), 0) for source in p.business_sources + ) + earned_income = max_(person("employment_income", period), 0) + business_income + # Secondary school also includes equivalent training in the model; + # this conjunction approximates the full-time high-school exclusion. + high_school_student = person("is_full_time_student", period) & person( + "is_in_secondary_school", period + ) + countable_earnings = spm_unit.sum(where(high_school_student, 0, earned_income)) + unearned_income = spm_unit.sum( + sum(max_(person(source, period), 0) for source in p.unearned_sources) + ) + adult_ssi = spm_unit.sum( + where( + person("age", period) >= p.ssi_min_age, + person("ssi", period), + 0, + ) + ) + # Page 16 separately excludes the first $50 of each source; page 17's + # "and/or" summary is ambiguous. Apply each exclusion once per household + # source, not per person. This interpretation lacks agency confirmation. + interest = add(spm_unit, period, ["interest_income"]) + dividends = add(spm_unit, period, ["ordinary_dividend_income"]) + investment_income = max_(interest - p.investment_disregard, 0) + max_( + dividends - p.investment_disregard, 0 + ) + # FANF is an SPM-unit benefit and is counted once. Gross Social Security + # and Railroad Retirement remain counted: Medicare B/D reconstruction + # is deferred. Support-paid regularity and mileage reimbursements are + # not separately observed; only reported support is deducted. + income = ( + countable_earnings + + unearned_income + + adult_ssi + + investment_income + + spm_unit("nh_fanf", period) + ) + support_paid = add(spm_unit, period, ["child_support_expense"]) + return max_(income - support_paid, 0) diff --git a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_eligible.py b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_eligible.py new file mode 100644 index 00000000000..1597a6ecbb2 --- /dev/null +++ b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_eligible.py @@ -0,0 +1,31 @@ +from policyengine_us.model_api import * + + +class nh_fap_eligible(Variable): + value_type = bool + entity = SPMUnit + definition_period = YEAR + label = "Eligible for New Hampshire FAP" + reference = "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=10, 11, 12, 17, 21, 34, 35, 36, 57, 74" + defined_for = StateCode.NH + + def formula(spm_unit, period, parameters): + size = spm_unit("nh_fap_household_size", period) + income = spm_unit("nh_fap_countable_income", period) + limit = spm_unit("nh_fap_income_limit", period) + heat_in_rent = spm_unit("heat_expense_included_in_rent", period) + subsidized = spm_unit("receives_housing_assistance", period) + heating_expense = spm_unit("heating_expense", period) + heating_type = spm_unit("heating_type", period) + fuel = heating_type.possible_values + # Subsidized heat-in-rent is excluded. Nonsubsidized heat-in-rent can + # qualify even when its actual cost or payment adjustment is unknown. + housing_eligible = ~(heat_in_rent & subsidized) + heating_eligible = (heating_expense > 0) | heat_in_rent + return ( + (size > 0) + & (income <= limit) + & housing_eligible + & heating_eligible + & (heating_type != fuel.NONE) + ) diff --git a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_household_size.py b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_household_size.py new file mode 100644 index 00000000000..1d17d88eb48 --- /dev/null +++ b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_household_size.py @@ -0,0 +1,16 @@ +from policyengine_us.model_api import * + + +class nh_fap_household_size(Variable): + value_type = int + entity = SPMUnit + definition_period = YEAR + label = "New Hampshire FAP household size" + documentation = "Number of citizens and qualified immigrants in the SPM unit. All members' otherwise countable income remains included." + reference = "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=11, 12, 34, 35, 36" + defined_for = StateCode.NH + + # SPM composition approximates the shared residence and economic unit. + # Custody, foster-child, live-in-care-provider and finer immigration rules + # are not separately observed by the existing household/status inputs. + adds = ["is_citizen_or_legal_immigrant"] diff --git a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_income_limit.py b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_income_limit.py new file mode 100644 index 00000000000..56d914a4e6d --- /dev/null +++ b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_income_limit.py @@ -0,0 +1,30 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.hhs_smi import smi + + +class nh_fap_income_limit(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "New Hampshire FAP income limit" + reference = "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=17, 74" + defined_for = StateCode.NH + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.nh.doe.fap + size = spm_unit("nh_fap_household_size", period) + state = spm_unit.household("state_code_str", period) + four_person_smi = parameters(period).gov.hhs.smi.amount[state] + household_smi = smi(size, state, period, parameters) + # Numerical inference matching all eight PY2027 published amounts: + # floor the four-person limit, apply the federal factor, then floor. + # The manual does not prescribe that intermediate rounding order. + four_person_limit = np.floor(four_person_smi * p.smi_rate) + # Federal factors are integer percentages; recover them to avoid a + # floating-point value just below an exact whole-dollar boundary. + factor_percent = np.rint(household_smi / four_person_smi * 100) + limit = np.floor(four_person_limit * factor_percent / 100) + # Federal size extensions are retained; NH rounding above eight is + # unverified. A household with no qualified member is ineligible. + return where(size > 0, limit, 0) From c688d608ca67d83ba322418f44050b83a024c625 Mon Sep 17 00:00:00 2001 From: Ziming Date: Sat, 3 Oct 2026 21:20:40 -0400 Subject: [PATCH 02/14] Deduct reported Medicare premiums from New Hampshire FAP income --- .../nh-liheap-reported-medicare.fixed.md | 1 + .../nh/doe/fap/nh_fap_countable_income.yaml | 162 ++++++++++++++++++ .../states/nh/doe/fap/nh_fap_eligible.yaml | 44 +++++ .../nh/doe/fap/nh_fap_countable_income.py | 14 +- 4 files changed, 216 insertions(+), 5 deletions(-) create mode 100644 changelog.d/nh-liheap-reported-medicare.fixed.md diff --git a/changelog.d/nh-liheap-reported-medicare.fixed.md b/changelog.d/nh-liheap-reported-medicare.fixed.md new file mode 100644 index 00000000000..d170b94dfa1 --- /dev/null +++ b/changelog.d/nh-liheap-reported-medicare.fixed.md @@ -0,0 +1 @@ +Deduct reported Medicare Part B premiums from New Hampshire FAP countable income. diff --git a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_countable_income.yaml index 5d69c2a2836..596b73ad9e7 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_countable_income.yaml @@ -778,3 +778,165 @@ state_code: "NH" output: nh_fap_countable_income: 12_000 + +# Manual pp14-17 allows reported Medicare deductions, separately from support paid. +- name: "Case 27, reported Part B reduces Social Security income." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + immigration_status: "CITIZEN" + ssi: 0 + social_security: 24_000 + medicare_part_b_premiums_reported: 2_400 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 21_600 + +# Manual pp14-17 allows reported Medicare deductions, separately from support paid. +- name: "Case 28, reported Part B reduces Railroad Retirement income." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + immigration_status: "CITIZEN" + ssi: 0 + railroad_benefits: 18_000 + medicare_part_b_premiums_reported: 2_400 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 15_600 + +# Manual pp14-17 allows reported Medicare deductions, separately from support paid. +- name: "Case 29, reported support and Part B deductions both apply." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + immigration_status: "CITIZEN" + ssi: 0 + social_security: 24_000 + medicare_part_b_premiums_reported: 2_400 + child_support_expense: 2_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 19_600 + +# Manual pp14-17 allows reported Medicare deductions, separately from support paid. +- name: "Case 30, combined allowed deductions cannot produce negative income." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + immigration_status: "CITIZEN" + ssi: 0 + social_security: 24_000 + medicare_part_b_premiums_reported: 2_400 + child_support_expense: 23_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 0 + +# Manual pp14-17 allows reported Medicare deductions, separately from support paid. +- name: "Case 31, Medicare age does not create an unreported premium deduction." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + immigration_status: "CITIZEN" + ssi: 0 + social_security: 24_000 + medicare_part_b_premiums_reported: 0 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 24_000 + +# Manual pp14-17 deducts each reported allowable premium within its own household. +- name: "Case 32, reported premiums aggregate within each SPM unit." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + immigration_status: "CITIZEN" + ssi: 0 + social_security: 24_000 + medicare_part_b_premiums_reported: 2_400 + person2: + age: 68 + immigration_status: "CITIZEN" + ssi: 0 + railroad_benefits: 18_000 + medicare_part_b_premiums_reported: 1_200 + person3: + age: 70 + immigration_status: "CITIZEN" + ssi: 0 + social_security: 12_000 + medicare_part_b_premiums_reported: 0 + spm_units: + spm_unit1: + members: ["person1", "person2"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + spm_unit2: + members: ["person3"] + nh_fanf: 0 + heating_type: "NATURAL_GAS" + households: + household1: + members: ["person1", "person2", "person3"] + state_code: "NH" + output: + nh_fap_countable_income: [38_400, 12_000] diff --git a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml index e87d3305001..d017c82ef43 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml @@ -910,3 +910,47 @@ state_code: "NH" output: nh_fap_eligible: true + +# Manual pp17,74: 50,000 - 2,000 = 48,000 is below the 48,713 ceiling; +# the identical gross amount without a reported premium remains above it. +- name: "Case 26, reported Medicare deductions affect income eligibility." + period: 2027 + input: + people: + person1: + age: 70 + immigration_status: "CITIZEN" + ssi: 0 + social_security: 50_000 + medicare_part_b_premiums_reported: 2_000 + person2: + age: 70 + immigration_status: "CITIZEN" + ssi: 0 + social_security: 50_000 + medicare_part_b_premiums_reported: 0 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + fuel_oil_expense: 4_000 + heat_expense_included_in_rent: false + receives_housing_assistance: false + spm_unit2: + members: ["person2"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + fuel_oil_expense: 4_000 + heat_expense_included_in_rent: false + receives_housing_assistance: false + households: + household1: + members: ["person1"] + state_code: "NH" + household2: + members: ["person2"] + state_code: "NH" + output: + nh_fap_countable_income: [48_000, 50_000] + nh_fap_eligible: [true, false] diff --git a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py index a4d6c5503e0..a13b317ae48 100644 --- a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py +++ b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py @@ -45,10 +45,7 @@ def formula(spm_unit, period, parameters): investment_income = max_(interest - p.investment_disregard, 0) + max_( dividends - p.investment_disregard, 0 ) - # FANF is an SPM-unit benefit and is counted once. Gross Social Security - # and Railroad Retirement remain counted: Medicare B/D reconstruction - # is deferred. Support-paid regularity and mileage reimbursements are - # not separately observed; only reported support is deducted. + # FANF is an SPM-unit benefit and is counted once. income = ( countable_earnings + unearned_income @@ -56,5 +53,12 @@ def formula(spm_unit, period, parameters): + investment_income + spm_unit("nh_fanf", period) ) + # Support-paid regularity and mileage reimbursements are not observed. support_paid = add(spm_unit, period, ["child_support_expense"]) - return max_(income - support_paid, 0) + # Reported Part B represents the allowable benefit-withheld deduction; + # withholding is not separately observed. Part D and automatic premium + # reconstruction remain unmodeled. + reported_part_b = spm_unit.sum( + max_(person("medicare_part_b_premiums_reported", period), 0) + ) + return max_(income - support_paid - reported_part_b, 0) From d270946543409e0cfda9b1c992a3ca61f333d03f Mon Sep 17 00:00:00 2001 From: Ziming Date: Sun, 4 Oct 2026 19:57:42 -0400 Subject: [PATCH 03/14] Estimate New Hampshire FAP heating cost from existing bedrooms --- changelog.d/nh-liheap-room-proxy.added.md | 1 + .../fap/heating_cost/additional_rooms.yaml | 13 + .../doe/fap/heating_cost/dwelling_factor.yaml | 17 + .../fap/heating_cost/fuel_cost_per_mbtu.yaml | 26 + .../doe/fap/heating_cost/fuel_efficiency.yaml | 27 + .../fap/heating_cost/heating_degree_days.yaml | 34 + .../nh/doe/fap/nh_fap_base_benefit.yaml | 135 +++- .../states/nh/doe/fap/nh_fap_eligible.yaml | 158 ++++- .../nh/doe/fap/nh_fap_heating_cost.yaml | 643 ++++++++++++++++++ .../states/nh/doe/fap/nh_fap_base_benefit.py | 12 +- .../states/nh/doe/fap/nh_fap_dwelling_type.py | 19 + .../gov/states/nh/doe/fap/nh_fap_eligible.py | 6 +- .../states/nh/doe/fap/nh_fap_heating_cost.py | 51 ++ 13 files changed, 1129 insertions(+), 13 deletions(-) create mode 100644 changelog.d/nh-liheap-room-proxy.added.md create mode 100644 policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/additional_rooms.yaml create mode 100644 policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/dwelling_factor.yaml create mode 100644 policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/fuel_cost_per_mbtu.yaml create mode 100644 policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/fuel_efficiency.yaml create mode 100644 policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/heating_degree_days.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_heating_cost.yaml create mode 100644 policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_dwelling_type.py create mode 100644 policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_heating_cost.py diff --git a/changelog.d/nh-liheap-room-proxy.added.md b/changelog.d/nh-liheap-room-proxy.added.md new file mode 100644 index 00000000000..2f8e698bdc1 --- /dev/null +++ b/changelog.d/nh-liheap-room-proxy.added.md @@ -0,0 +1 @@ +Add a documented bedroom-based heating-cost estimate for New Hampshire FAP and compare it with actual heating cost. diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/additional_rooms.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/additional_rooms.yaml new file mode 100644 index 00000000000..56f99f4b605 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/additional_rooms.yaml @@ -0,0 +1,13 @@ +description: PolicyEngine approximates the heated room count under the New Hampshire Fuel Assistance Program by adding this number to a positive reported bedroom count. +# Modeling assumption: one kitchen, living room and bathroom. The manual +# requires actual program rooms and does not prescribe this approximation. +values: + 2026-10-01: 3 + +metadata: + unit: /1 + period: year + label: New Hampshire FAP additional rooms modeling assumption + reference: + - title: PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Calculating the Number of Rooms and Application + href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=17, 18, 71 diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/dwelling_factor.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/dwelling_factor.yaml new file mode 100644 index 00000000000..2899599f68c --- /dev/null +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/dwelling_factor.yaml @@ -0,0 +1,17 @@ +description: New Hampshire uses these dwelling energy factors to estimate heating cost under the Fuel Assistance Program. +SINGLE_FAMILY_OR_MODULAR: + 2026-10-01: 2_000 +MOBILE_HOME: + 2026-10-01: 1_800 +APARTMENT_CONDO_OR_DUPLEX: + 2026-10-01: 1_400 + +metadata: + unit: /1 + period: year + label: New Hampshire FAP dwelling energy factor + breakdown: + - nh_fap_dwelling_type + reference: + - title: PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Benefit Determination + href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=76, 77 diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/fuel_cost_per_mbtu.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/fuel_cost_per_mbtu.yaml new file mode 100644 index 00000000000..a5f7621580d --- /dev/null +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/fuel_cost_per_mbtu.yaml @@ -0,0 +1,26 @@ +description: New Hampshire uses these fuel costs per million British thermal units to estimate heating cost under the Fuel Assistance Program. +KEROSENE: + 2026-10-01: 35.185 +FUEL_OIL: + 2026-10-01: 28.881 +NATURAL_GAS: + 2026-10-01: 15 +COAL: + 2026-10-01: 14.286 +WOOD: + 2026-10-01: 21.65 +ELECTRICITY: + 2026-10-01: 64.706 +PROPANE: + 2026-10-01: 42.497 +# Unsupported fuel contributes no estimate, not a legal zero cost. +UNAVAILABLE: + 2026-10-01: 0 + +metadata: + unit: currency-USD + period: year + label: New Hampshire FAP fuel cost per million British thermal units + reference: + - title: PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Benefit Determination + href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=77 diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/fuel_efficiency.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/fuel_efficiency.yaml new file mode 100644 index 00000000000..f595d46c797 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/fuel_efficiency.yaml @@ -0,0 +1,27 @@ +description: New Hampshire uses these fuel efficiency rates to estimate heating cost under the Fuel Assistance Program. +KEROSENE: + 2026-10-01: 0.78 +FUEL_OIL: + 2026-10-01: 0.78 +NATURAL_GAS: + 2026-10-01: 0.82 +COAL: + 2026-10-01: 0.75 +# Existing WOOD represents cordwood; pellets and bio-bricks are unobserved. +WOOD: + 2026-10-01: 0.63 +ELECTRICITY: + 2026-10-01: 1 +PROPANE: + 2026-10-01: 0.78 +# A safe denominator for the zero-cost computational fallback. +UNAVAILABLE: + 2026-10-01: 1 + +metadata: + unit: /1 + period: year + label: New Hampshire FAP fuel efficiency rate + reference: + - title: PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Benefit Determination + href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=77 diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/heating_degree_days.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/heating_degree_days.yaml new file mode 100644 index 00000000000..86690891a50 --- /dev/null +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/heating_degree_days.yaml @@ -0,0 +1,34 @@ +description: New Hampshire uses these agency heating degree days by county to estimate heating cost under the Fuel Assistance Program. +BELKNAP_COUNTY_NH: + 2026-10-01: 6_610 +MERRIMACK_COUNTY_NH: + 2026-10-01: 6_610 +HILLSBOROUGH_COUNTY_NH: + 2026-10-01: 5_993 +ROCKINGHAM_COUNTY_NH: + 2026-10-01: 5_993 +STRAFFORD_COUNTY_NH: + 2026-10-01: 6_061 +CHESHIRE_COUNTY_NH: + 2026-10-01: 6_588 +SULLIVAN_COUNTY_NH: + 2026-10-01: 6_588 +CARROLL_COUNTY_NH: + 2026-10-01: 7_672 +COOS_COUNTY_NH: + 2026-10-01: 7_672 +GRAFTON_COUNTY_NH: + 2026-10-01: 7_672 +# Unsupported geography contributes no estimate, not a legal zero cost. +UNAVAILABLE: + 2026-10-01: 0 + +metadata: + unit: /1 + period: year + label: New Hampshire FAP agency heating degree days by county + reference: + - title: PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Benefit Determination + href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=77 + - title: Community Action Partnership of New Hampshire, Agency County Service Areas + href: https://www.capnh.org/home-old diff --git a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_base_benefit.yaml b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_base_benefit.yaml index f64d4fb7ce0..2c1afd4598c 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_base_benefit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_base_benefit.yaml @@ -3,8 +3,8 @@ # Physical PDF pages: 10, 17, 18, 35, 36, 74, 76, 77, 78. # Period 2027 represents the researched 2026-2027 schedule with 2026 FPG. # Inputs are annual; SSI and FANF totals are explicitly supplied to isolate receipts. -# This is the actual-cost base schedule, before unresolved heat-in-rent adjustment. -# No final nh_fap award, room-based proxy, or unsupported fuel amount is asserted. +# This is the base schedule using actual or estimated costs, before unresolved heat-in-rent adjustment. +# No final nh_fap award or unsupported fuel amount is asserted. # Manual p78, delivered DD/A cell. Actual annual primary-fuel bill selects the row. - name: "Case 1, delivered matrix cost tier DD and income band A." @@ -2174,3 +2174,134 @@ state_code: "NH" output: nh_fap_base_benefit: 0 + +# Manual p76: assume four bedrooms + three rooms = seven; cost 3976.988 selects AA, income 45120 selects F, yielding 825 before any heat-in-rent adjustment. +- name: "Case 82, the published two-person oil example is reachable through bedrooms." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 45_120 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + spm_units: + spm_unit1: + members: ["person1", "person2"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + fuel_oil_expense: 0 + nh_fap_dwelling_type: "SINGLE_FAMILY_OR_MODULAR" + heat_expense_included_in_rent: false + receives_housing_assistance: false + households: + household1: + members: ["person1", "person2"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 4 + output: + nh_fap_heating_cost: 3_976.987754 + nh_fap_countable_income: 45_120 + nh_fap_base_benefit: 825 + +# Manual pp18,77-78: two bedrooms imply five rooms, estimated cost 2840.706 selects BB/A 2350; actual 4000 selects AA/A 2500. The 500 bill does not cap the benefit. +- name: "Case 83, the larger cost basis determines the delivered benefit." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + heating_type: "FUEL_OIL" + nh_fanf: 0 + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 500 + nh_fap_dwelling_type: "SINGLE_FAMILY_OR_MODULAR" + spm_unit2: + members: ["person2"] + heating_type: "FUEL_OIL" + nh_fanf: 0 + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + nh_fap_dwelling_type: "SINGLE_FAMILY_OR_MODULAR" + households: + household1: + members: ["person1"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 2 + household2: + members: ["person2"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 2 + output: + nh_fap_heating_cost: [2_840.705538, 4_000] + nh_fap_base_benefit: [2_350, 2_500] + +# Manual pp77-78: five-room Coos gas cost 1403.415 selects CC/A 1460 * .75 =1095; actual 4000 selects AA/A 2500 * .75 =1875. +- name: "Case 84, utility reduction follows the selected proxy or actual cost band." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + heating_type: "NATURAL_GAS" + nh_fanf: 0 + heat_expense_included_in_rent: false + receives_housing_assistance: false + gas_expense: 0 + nh_fap_dwelling_type: "SINGLE_FAMILY_OR_MODULAR" + spm_unit2: + members: ["person2"] + heating_type: "NATURAL_GAS" + nh_fanf: 0 + heat_expense_included_in_rent: false + receives_housing_assistance: false + gas_expense: 4_000 + nh_fap_dwelling_type: "SINGLE_FAMILY_OR_MODULAR" + households: + household1: + members: ["person1"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 2 + household2: + members: ["person2"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 2 + output: + nh_fap_heating_cost: [1_403.414634, 4_000] + nh_fap_base_benefit: [1_095, 1_875] diff --git a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml index d017c82ef43..f9540a3ef27 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml @@ -1,6 +1,6 @@ # Source: PY27 NH Fuel Assistance Program Procedures Manual. # https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf -# Physical PDF pages: 12, 17, 21, 35, 36, 74. +# Physical PDF pages: 12, 17, 18, 21, 35, 36, 74. # Period 2027 represents the researched 2026-2027 schedule with 2026 FPG. # Inputs are annual; SSI and FANF totals are explicitly supplied to isolate receipts. # Heat-in-rent cases assert eligibility only, not an unresolved final award. @@ -760,7 +760,7 @@ output: nh_fap_eligible: false -# Manual p12 requires responsibility for energy costs; proxy room inputs are unavailable. +# Manual p12 requires responsibility for energy costs; absent bedrooms leave no proxy estimate. - name: "Case 20, zero direct cost without heat in rent is ineligible." period: 2027 input: @@ -954,3 +954,157 @@ output: nh_fap_countable_income: [48_000, 50_000] nh_fap_eligible: [true, false] + +# Manual pp12,18,21,35: estimate may replace an absent actual bill; retain income, qualified-member, subsidized heat-in-rent, fuel and state requirements. +- name: "Case 27, a room proxy establishes heating costs while other gates remain." + period: 2027 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + person3: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 60_000 + person4: + age: 40 + immigration_status: "UNDOCUMENTED" + ssi: 0 + employment_income: 10_000 + person5: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + person6: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + person7: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + person8: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + person9: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + heating_type: "FUEL_OIL" + nh_fanf: 0 + heat_expense_included_in_rent: false + receives_housing_assistance: false + spm_unit2: + members: ["person2"] + heating_type: "FUEL_OIL" + nh_fanf: 0 + heat_expense_included_in_rent: false + receives_housing_assistance: false + spm_unit3: + members: ["person3"] + heating_type: "FUEL_OIL" + nh_fanf: 0 + heat_expense_included_in_rent: false + receives_housing_assistance: false + spm_unit4: + members: ["person4"] + heating_type: "FUEL_OIL" + nh_fanf: 0 + heat_expense_included_in_rent: false + receives_housing_assistance: false + spm_unit5: + members: ["person5"] + heating_type: "FUEL_OIL" + nh_fanf: 0 + heat_expense_included_in_rent: true + receives_housing_assistance: true + spm_unit6: + members: ["person6"] + heating_type: "NONE" + nh_fanf: 0 + heat_expense_included_in_rent: false + receives_housing_assistance: false + spm_unit7: + members: ["person7"] + heating_type: "FUEL_OIL" + nh_fanf: 0 + heat_expense_included_in_rent: false + receives_housing_assistance: false + spm_unit8: + members: ["person8"] + heating_type: "FUEL_OIL" + nh_fanf: 0 + heat_expense_included_in_rent: false + receives_housing_assistance: false + spm_unit9: + members: ["person9"] + heating_type: "FUEL_OIL" + nh_fanf: 0 + heat_expense_included_in_rent: false + receives_housing_assistance: true + households: + household1: + members: ["person1"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 4 + household2: + members: ["person2"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 0 + household3: + members: ["person3"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 4 + household4: + members: ["person4"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 4 + household5: + members: ["person5"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 4 + household6: + members: ["person6"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 4 + household7: + members: ["person7"] + state_code: "NH" + county_str: "UNKNOWN" + bedrooms: 4 + household8: + members: ["person8"] + state_code: "MA" + county_str: "COOS_COUNTY_NH" + bedrooms: 4 + household9: + members: ["person9"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 4 + output: + nh_fap_eligible: [true, false, false, false, false, false, false, false, true] diff --git a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_heating_cost.yaml b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_heating_cost.yaml new file mode 100644 index 00000000000..b5abb4b4440 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_heating_cost.yaml @@ -0,0 +1,643 @@ +# Source: PY27 NH Fuel Assistance Program Procedures Manual, physical pages 17, 18, 76, 77. +# https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf +# Agency service counties: https://www.capnh.org/home-old +# Bedrooms plus three is a user-approved modeling assumption, not a statutory room definition. +# Inputs and estimates are annual; actual bills and the estimated cost are not added together. + +# Manual p76: 2000 * 7 * 7672 / 1,000,000 * 28.881 / .78 = 3976.987754. Four bedrooms + three assumed rooms is a model approximation. +- name: "Case 1, four bedrooms reproduce the manual seven-room oil example." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + spm_units: + spm_unit1: + members: ["person1"] + heating_type: "FUEL_OIL" + nh_fap_dwelling_type: "SINGLE_FAMILY_OR_MODULAR" + fuel_oil_expense: 0 + households: + household1: + members: ["person1"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 4 + output: + nh_fap_heating_cost: 3_976.987754 + +# Manual p77: single-family factor 2000 * five assumed rooms * 7672 HDD; apply each fuel price / efficiency. WOOD is cordwood; electricity has no extra 70% reduction. +- name: "Case 2, each observed fuel uses its published heating-cost conversion." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + person3: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + person4: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + person5: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + person6: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + person7: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + spm_units: + spm_unit1: + members: ["person1"] + heating_type: "KEROSENE" + nh_fap_dwelling_type: "SINGLE_FAMILY_OR_MODULAR" + spm_unit2: + members: ["person2"] + heating_type: "FUEL_OIL" + nh_fap_dwelling_type: "SINGLE_FAMILY_OR_MODULAR" + spm_unit3: + members: ["person3"] + heating_type: "NATURAL_GAS" + nh_fap_dwelling_type: "SINGLE_FAMILY_OR_MODULAR" + spm_unit4: + members: ["person4"] + heating_type: "COAL" + nh_fap_dwelling_type: "SINGLE_FAMILY_OR_MODULAR" + spm_unit5: + members: ["person5"] + heating_type: "WOOD" + nh_fap_dwelling_type: "SINGLE_FAMILY_OR_MODULAR" + spm_unit6: + members: ["person6"] + heating_type: "ELECTRICITY" + nh_fap_dwelling_type: "SINGLE_FAMILY_OR_MODULAR" + spm_unit7: + members: ["person7"] + heating_type: "PROPANE" + nh_fap_dwelling_type: "SINGLE_FAMILY_OR_MODULAR" + households: + household1: + members: ["person1"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 2 + household2: + members: ["person2"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 2 + household3: + members: ["person3"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 2 + household4: + members: ["person4"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 2 + household5: + members: ["person5"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 2 + household6: + members: ["person6"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 2 + household7: + members: ["person7"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 2 + output: + nh_fap_heating_cost: [3_460.760513, 2_840.705538, 1_403.414634, 1_461.36256, 2_636.488889, 4_964.24432, 4_179.961333] + +# Manual p77 agency HDD and CAPNH county directory; factor 2000 * five assumed rooms * HDD / 1,000,000 * 28.881 / .78. +- name: "Case 3, all ten counties use the agency heating degree days." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + person3: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + person4: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + person5: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + person6: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + person7: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + person8: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + person9: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + person10: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + spm_units: + spm_unit1: + members: ["person1"] + heating_type: "FUEL_OIL" + nh_fap_dwelling_type: "SINGLE_FAMILY_OR_MODULAR" + spm_unit2: + members: ["person2"] + heating_type: "FUEL_OIL" + nh_fap_dwelling_type: "SINGLE_FAMILY_OR_MODULAR" + spm_unit3: + members: ["person3"] + heating_type: "FUEL_OIL" + nh_fap_dwelling_type: "SINGLE_FAMILY_OR_MODULAR" + spm_unit4: + members: ["person4"] + heating_type: "FUEL_OIL" + nh_fap_dwelling_type: "SINGLE_FAMILY_OR_MODULAR" + spm_unit5: + members: ["person5"] + heating_type: "FUEL_OIL" + nh_fap_dwelling_type: "SINGLE_FAMILY_OR_MODULAR" + spm_unit6: + members: ["person6"] + heating_type: "FUEL_OIL" + nh_fap_dwelling_type: "SINGLE_FAMILY_OR_MODULAR" + spm_unit7: + members: ["person7"] + heating_type: "FUEL_OIL" + nh_fap_dwelling_type: "SINGLE_FAMILY_OR_MODULAR" + spm_unit8: + members: ["person8"] + heating_type: "FUEL_OIL" + nh_fap_dwelling_type: "SINGLE_FAMILY_OR_MODULAR" + spm_unit9: + members: ["person9"] + heating_type: "FUEL_OIL" + nh_fap_dwelling_type: "SINGLE_FAMILY_OR_MODULAR" + spm_unit10: + members: ["person10"] + heating_type: "FUEL_OIL" + nh_fap_dwelling_type: "SINGLE_FAMILY_OR_MODULAR" + households: + household1: + members: ["person1"] + state_code: "NH" + county_str: "BELKNAP_COUNTY_NH" + bedrooms: 2 + household2: + members: ["person2"] + state_code: "NH" + county_str: "MERRIMACK_COUNTY_NH" + bedrooms: 2 + household3: + members: ["person3"] + state_code: "NH" + county_str: "HILLSBOROUGH_COUNTY_NH" + bedrooms: 2 + household4: + members: ["person4"] + state_code: "NH" + county_str: "ROCKINGHAM_COUNTY_NH" + bedrooms: 2 + household5: + members: ["person5"] + state_code: "NH" + county_str: "STRAFFORD_COUNTY_NH" + bedrooms: 2 + household6: + members: ["person6"] + state_code: "NH" + county_str: "CHESHIRE_COUNTY_NH" + bedrooms: 2 + household7: + members: ["person7"] + state_code: "NH" + county_str: "SULLIVAN_COUNTY_NH" + bedrooms: 2 + household8: + members: ["person8"] + state_code: "NH" + county_str: "CARROLL_COUNTY_NH" + bedrooms: 2 + household9: + members: ["person9"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 2 + household10: + members: ["person10"] + state_code: "NH" + county_str: "GRAFTON_COUNTY_NH" + bedrooms: 2 + output: + nh_fap_heating_cost: [2_447.479615, 2_447.479615, 2_219.0235, 2_219.0235, 2_244.201808, 2_439.333692, 2_439.333692, 2_840.705538, 2_840.705538, 2_840.705538] + +# Manual p77: dwelling factors 2000, 1800 and 1400 with Strafford HDD 6061. +- name: "Case 4, dwelling factors change a five-room oil estimate." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + person3: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + spm_units: + spm_unit1: + members: ["person1"] + heating_type: "FUEL_OIL" + nh_fap_dwelling_type: "SINGLE_FAMILY_OR_MODULAR" + spm_unit2: + members: ["person2"] + heating_type: "FUEL_OIL" + nh_fap_dwelling_type: "MOBILE_HOME" + spm_unit3: + members: ["person3"] + heating_type: "FUEL_OIL" + nh_fap_dwelling_type: "APARTMENT_CONDO_OR_DUPLEX" + households: + household1: + members: ["person1"] + state_code: "NH" + county_str: "STRAFFORD_COUNTY_NH" + bedrooms: 2 + household2: + members: ["person2"] + state_code: "NH" + county_str: "STRAFFORD_COUNTY_NH" + bedrooms: 2 + household3: + members: ["person3"] + state_code: "NH" + county_str: "STRAFFORD_COUNTY_NH" + bedrooms: 2 + output: + nh_fap_heating_cost: [2_244.201808, 2_019.781627, 1_570.941265] + +# Approved model assumption: no room estimate for nonpositive bedrooms, so actual bills are retained. +- name: "Case 5, zero or negative bedrooms preserve the actual bill fallback." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + person3: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + spm_units: + spm_unit1: + members: ["person1"] + heating_type: "FUEL_OIL" + fuel_oil_expense: 0 + spm_unit2: + members: ["person2"] + heating_type: "FUEL_OIL" + fuel_oil_expense: 650 + spm_unit3: + members: ["person3"] + heating_type: "FUEL_OIL" + fuel_oil_expense: 750 + households: + household1: + members: ["person1"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 0 + household2: + members: ["person2"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 0 + household3: + members: ["person3"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: -1 + output: + nh_fap_heating_cost: [0, 650, 750] + +# Manual p18: compare actual annual cost and design heat load for best benefit; 500 is below the seven-room estimate. +- name: "Case 6, the estimate is retained when the actual bill is lower." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + spm_units: + spm_unit1: + members: ["person1"] + heating_type: "FUEL_OIL" + fuel_oil_expense: 500 + households: + household1: + members: ["person1"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 4 + output: + nh_fap_heating_cost: 3_976.987754 + +# Manual p18 allows documented actual cost for high usage; 5000 exceeds 3976.987754. +- name: "Case 7, a larger annual bill replaces the estimate." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + spm_units: + spm_unit1: + members: ["person1"] + heating_type: "FUEL_OIL" + fuel_oil_expense: 5_000 + households: + household1: + members: ["person1"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 4 + output: + nh_fap_heating_cost: 5_000 + +# No proxy for OTHER, SOLAR, UNSPECIFIED or NONE. Canonical OTHER and grid-tied SOLAR bills still permit actual-cost fallback. +- name: "Case 8, unsupported proxy fuels retain available canonical actual bills." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + person3: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + person4: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + spm_units: + spm_unit1: + members: ["person1"] + heating_type: "OTHER" + other_heating_fuel_expense: 250 + spm_unit2: + members: ["person2"] + heating_type: "SOLAR" + pre_subsidy_electricity_expense: 650 + spm_unit3: + members: ["person3"] + heating_type: "UNSPECIFIED" + fuel_oil_expense: 4_000 + spm_unit4: + members: ["person4"] + heating_type: "NONE" + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 4 + household2: + members: ["person2"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 4 + household3: + members: ["person3"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 4 + household4: + members: ["person4"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 4 + output: + nh_fap_heating_cost: [250, 650, 0, 0] + +# Only the ten NH counties map to published agency HDD; actual-cost fallback remains available. +- name: "Case 9, unknown and outside counties do not generate an estimate." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + spm_units: + spm_unit1: + members: ["person1"] + heating_type: "FUEL_OIL" + spm_unit2: + members: ["person2"] + heating_type: "FUEL_OIL" + fuel_oil_expense: 650 + households: + household1: + members: ["person1"] + state_code: "NH" + county_str: "UNKNOWN" + bedrooms: 4 + household2: + members: ["person2"] + state_code: "NH" + county_str: "SUFFOLK_COUNTY_MA" + bedrooms: 4 + output: + nh_fap_heating_cost: [0, 650] + +# State scope applies even when a supplied county string and bill would otherwise give a positive cost. +- name: "Case 10, the heating-cost component is limited to New Hampshire." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + spm_units: + spm_unit1: + members: ["person1"] + heating_type: "FUEL_OIL" + fuel_oil_expense: 5_000 + households: + household1: + members: ["person1"] + state_code: "MA" + county_str: "COOS_COUNTY_NH" + bedrooms: 4 + output: + nh_fap_heating_cost: 0 + +# The adopted contract floors actual bills at zero and selects the better heating-cost basis. +- name: "Case 11, negative reported bills cannot reduce a positive estimate." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + spm_units: + spm_unit1: + members: ["person1"] + heating_type: "FUEL_OIL" + fuel_oil_expense: -1_000 + households: + household1: + members: ["person1"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 4 + output: + nh_fap_heating_cost: 3_976.987754 + +# With no room estimate and no positive annual cost, the component is zero. +- name: "Case 12, negative reported bills without bedrooms have zero modeled cost." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + spm_units: + spm_unit1: + members: ["person1"] + heating_type: "FUEL_OIL" + fuel_oil_expense: -1_000 + households: + household1: + members: ["person1"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 0 + output: + nh_fap_heating_cost: 0 + +# The existing bedrooms zero default disables the approximate room path. +- name: "Case 13, omitted bedrooms preserve existing actual-cost cases." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + spm_units: + spm_unit1: + members: ["person1"] + heating_type: "FUEL_OIL" + fuel_oil_expense: 500 + households: + household1: + members: ["person1"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + output: + nh_fap_heating_cost: 500 + +# Approved whole-household approximation: both units use five rooms; no inferred private-room allocation. Factor 1400 with Coos HDD 7672. +- name: "Case 14, SPM units reuse household bedrooms with their own fuel." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + person2: + age: 40 + spm_units: + spm_unit1: + members: ["person1"] + heating_type: "WOOD" + nh_fap_dwelling_type: "APARTMENT_CONDO_OR_DUPLEX" + spm_unit2: + members: ["person2"] + heating_type: "NATURAL_GAS" + nh_fap_dwelling_type: "APARTMENT_CONDO_OR_DUPLEX" + households: + household1: + members: ["person1", "person2"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + bedrooms: 2 + output: + nh_fap_heating_cost: [1_845.542222, 982.390244] diff --git a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_base_benefit.py b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_base_benefit.py index 65dd01b1923..4c252a6a624 100644 --- a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_base_benefit.py +++ b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_base_benefit.py @@ -7,8 +7,8 @@ class nh_fap_base_benefit(Variable): entity = SPMUnit definition_period = YEAR unit = USD - label = "New Hampshire FAP actual-cost scheduled benefit" - documentation = "Actual-cost scheduled intermediate before any heat-in-rent adjustment, not the final FAP award. Verified against the PY2027 schedule; earlier results are unverified historical estimates. Zero can mean that actual cost or fuel classification is unsupported, rather than legal ineligibility." + label = "New Hampshire FAP scheduled benefit before heat-in-rent adjustments" + documentation = "Scheduled intermediate using actual or bedroom-proxy heating cost before any heat-in-rent adjustment, not the final FAP award. The room proxy is a modeling approximation. Verified against the PY2027 schedule; earlier results are unverified historical estimates. Zero can mean that heating cost or fuel classification is unsupported, rather than legal ineligibility." reference = "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=10, 17, 74, 76, 77, 78" defined_for = "nh_fap_eligible" @@ -34,7 +34,7 @@ def formula(spm_unit, period, parameters): ["A", "B", "C", "D", "E"], default="F", ) - expense = spm_unit("heating_expense", period) + expense = spm_unit("nh_fap_heating_cost", period) thresholds = p.payment.heating_cost_threshold cost_band = select( [expense < thresholds.CC, expense < thresholds.BB, expense < thresholds.AA], @@ -57,8 +57,8 @@ def formula(spm_unit, period, parameters): multiplier = where(utility, p.payment.utility_multiplier, 1) # Half-up whole dollars reproduce every utility matrix cell. benefit = np.floor(delivered_benefit * multiplier + 0.5) - # No heat-in-rent adjustment is inferred. Missing heated-room data - # prevents the proxy route. Unknown, solar and other fuels have no - # supported payment classification; zero marks an unsupported amount. + # No heat-in-rent adjustment is inferred. Unknown, solar and other + # fuels have no supported payment classification; zero marks an + # unsupported amount. # The schedule is not capped at the household's actual heating cost. return where((expense > 0) & (utility | delivered), benefit, 0) diff --git a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_dwelling_type.py b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_dwelling_type.py new file mode 100644 index 00000000000..002e76b77c8 --- /dev/null +++ b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_dwelling_type.py @@ -0,0 +1,19 @@ +from policyengine_us.model_api import * + + +class NHFAPDwellingType(Enum): + MOBILE_HOME = "Mobile home" + APARTMENT_CONDO_OR_DUPLEX = "Apartment, condominium or duplex" + SINGLE_FAMILY_OR_MODULAR = "Single family or modular home" + + +class nh_fap_dwelling_type(Variable): + value_type = Enum + possible_values = NHFAPDwellingType + default_value = NHFAPDwellingType.SINGLE_FAMILY_OR_MODULAR + entity = SPMUnit + definition_period = YEAR + label = "New Hampshire FAP dwelling type" + reference = "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=77" + # The single-family/modular default is a modeling assumption. Private + # rented rooms require room-use details absent from the bedroom proxy. diff --git a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_eligible.py b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_eligible.py index 1597a6ecbb2..c9e017ab2d9 100644 --- a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_eligible.py +++ b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_eligible.py @@ -6,7 +6,7 @@ class nh_fap_eligible(Variable): entity = SPMUnit definition_period = YEAR label = "Eligible for New Hampshire FAP" - reference = "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=10, 11, 12, 17, 21, 34, 35, 36, 57, 74" + reference = "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=10, 11, 12, 17, 18, 21, 34, 35, 36, 57, 74, 76, 77" defined_for = StateCode.NH def formula(spm_unit, period, parameters): @@ -15,13 +15,13 @@ def formula(spm_unit, period, parameters): limit = spm_unit("nh_fap_income_limit", period) heat_in_rent = spm_unit("heat_expense_included_in_rent", period) subsidized = spm_unit("receives_housing_assistance", period) - heating_expense = spm_unit("heating_expense", period) + heating_cost = spm_unit("nh_fap_heating_cost", period) heating_type = spm_unit("heating_type", period) fuel = heating_type.possible_values # Subsidized heat-in-rent is excluded. Nonsubsidized heat-in-rent can # qualify even when its actual cost or payment adjustment is unknown. housing_eligible = ~(heat_in_rent & subsidized) - heating_eligible = (heating_expense > 0) | heat_in_rent + heating_eligible = (heating_cost > 0) | heat_in_rent return ( (size > 0) & (income <= limit) diff --git a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_heating_cost.py b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_heating_cost.py new file mode 100644 index 00000000000..5d9a166776b --- /dev/null +++ b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_heating_cost.py @@ -0,0 +1,51 @@ +from policyengine_us.model_api import * + + +class nh_fap_heating_cost(Variable): + value_type = float + entity = SPMUnit + definition_period = YEAR + unit = USD + label = "New Hampshire FAP annual heating cost using actual or estimated cost" + reference = "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=17, 18, 76, 77" + defined_for = StateCode.NH + + def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.nh.doe.fap.heating_cost + bedrooms = spm_unit.household("bedrooms", period) + # Bedrooms plus a kitchen, living room and bathroom is an approved + # modeling approximation, not the program's measured room count. + # Zero bedrooms cannot distinguish a studio from missing information. + rooms = where(bedrooms > 0, bedrooms + p.additional_rooms, 0) + # Whole-household bedrooms approximate the SPM unit's dwelling; + # private, shared and business-only room adjustments are unobserved. + dwelling = spm_unit("nh_fap_dwelling_type", period) + county = spm_unit.household("county_str", period) + # Preserve the existing county input's geographic fallback. Explicit + # unknown/non-NH counties cannot establish an estimated heating cost. + county_key = where( + np.isin(county, list(p.heating_degree_days._children)), + county, + "UNAVAILABLE", + ) + fuel = spm_unit("heating_type", period).decode_to_str() + fuel_key = where( + np.isin(fuel, list(p.fuel_cost_per_mbtu._children)), + fuel, + "UNAVAILABLE", + ) + # WOOD approximates cordwood; pellets, bio-bricks and winter blends + # cannot be distinguished with the existing heating type input. + # The million divisor converts the heat-load quantity to MBTU. + estimated_cost = ( + p.dwelling_factor[dwelling] + * rooms + * p.heating_degree_days[county_key] + / 1_000_000 + * p.fuel_cost_per_mbtu[fuel_key] + / p.fuel_efficiency[fuel_key] + ) + actual_cost = max_(spm_unit("heating_expense", period), 0) + # The schedule is monotonic in cost, so the higher cost yields the + # better of the actual-cost and design-heat-load determinations. + return max_(actual_cost, estimated_cost) From 9f8630910d7a2c181bff2b6703603e150b63c08b Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 6 Oct 2026 13:22:52 -0400 Subject: [PATCH 04/14] Use bedrooms alone for New Hampshire FAP heating estimate --- changelog.d/nh-liheap-room-proxy.added.md | 2 +- .../fap/heating_cost/additional_rooms.yaml | 13 -------- .../nh/doe/fap/nh_fap_base_benefit.yaml | 20 ++++++------ .../nh/doe/fap/nh_fap_heating_cost.yaml | 32 +++++++++---------- .../states/nh/doe/fap/nh_fap_heating_cost.py | 9 +++--- 5 files changed, 32 insertions(+), 44 deletions(-) delete mode 100644 policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/additional_rooms.yaml diff --git a/changelog.d/nh-liheap-room-proxy.added.md b/changelog.d/nh-liheap-room-proxy.added.md index 2f8e698bdc1..ffec522867a 100644 --- a/changelog.d/nh-liheap-room-proxy.added.md +++ b/changelog.d/nh-liheap-room-proxy.added.md @@ -1 +1 @@ -Add a documented bedroom-based heating-cost estimate for New Hampshire FAP and compare it with actual heating cost. +Add a documented heating-cost estimate using bedrooms alone for New Hampshire FAP and compare it with actual heating cost. diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/additional_rooms.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/additional_rooms.yaml deleted file mode 100644 index 56f99f4b605..00000000000 --- a/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/additional_rooms.yaml +++ /dev/null @@ -1,13 +0,0 @@ -description: PolicyEngine approximates the heated room count under the New Hampshire Fuel Assistance Program by adding this number to a positive reported bedroom count. -# Modeling assumption: one kitchen, living room and bathroom. The manual -# requires actual program rooms and does not prescribe this approximation. -values: - 2026-10-01: 3 - -metadata: - unit: /1 - period: year - label: New Hampshire FAP additional rooms modeling assumption - reference: - - title: PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Calculating the Number of Rooms and Application - href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=17, 18, 71 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_base_benefit.yaml b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_base_benefit.yaml index 2c1afd4598c..3bf40752a29 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_base_benefit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_base_benefit.yaml @@ -2175,8 +2175,8 @@ output: nh_fap_base_benefit: 0 -# Manual p76: assume four bedrooms + three rooms = seven; cost 3976.988 selects AA, income 45120 selects F, yielding 825 before any heat-in-rent adjustment. -- name: "Case 82, the published two-person oil example is reachable through bedrooms." +# Manual pp76-78 formula and matrix with four bedrooms alone: cost 2272.564 selects BB, income 45120 selects F, yielding 800 before any heat-in-rent adjustment. +- name: "Case 82, bedrooms alone determine the two-person oil benefit cost band." period: 2027 absolute_error_margin: 0.01 input: @@ -2206,11 +2206,11 @@ county_str: "COOS_COUNTY_NH" bedrooms: 4 output: - nh_fap_heating_cost: 3_976.987754 + nh_fap_heating_cost: 2_272.564431 nh_fap_countable_income: 45_120 - nh_fap_base_benefit: 825 + nh_fap_base_benefit: 800 -# Manual pp18,77-78: two bedrooms imply five rooms, estimated cost 2840.706 selects BB/A 2350; actual 4000 selects AA/A 2500. The 500 bill does not cap the benefit. +# Manual pp18,77-78: two bedrooms alone give estimated cost 1136.282, selecting CC/A 1460; actual 4000 selects AA/A 2500. The 500 bill does not cap the benefit. - name: "Case 83, the larger cost basis determines the delivered benefit." period: 2027 absolute_error_margin: 0.01 @@ -2255,10 +2255,10 @@ county_str: "COOS_COUNTY_NH" bedrooms: 2 output: - nh_fap_heating_cost: [2_840.705538, 4_000] - nh_fap_base_benefit: [2_350, 2_500] + nh_fap_heating_cost: [1_136.282215, 4_000] + nh_fap_base_benefit: [1_460, 2_500] -# Manual pp77-78: five-room Coos gas cost 1403.415 selects CC/A 1460 * .75 =1095; actual 4000 selects AA/A 2500 * .75 =1875. +# Manual pp77-78: two-bedroom Coos gas cost 561.366 selects DD/A 750 * .75 =562.50, rounded to 563; actual 4000 selects AA/A 2500 * .75 =1875. - name: "Case 84, utility reduction follows the selected proxy or actual cost band." period: 2027 absolute_error_margin: 0.01 @@ -2303,5 +2303,5 @@ county_str: "COOS_COUNTY_NH" bedrooms: 2 output: - nh_fap_heating_cost: [1_403.414634, 4_000] - nh_fap_base_benefit: [1_095, 1_875] + nh_fap_heating_cost: [561.365854, 4_000] + nh_fap_base_benefit: [563, 1_875] diff --git a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_heating_cost.yaml b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_heating_cost.yaml index b5abb4b4440..95d181d67d0 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_heating_cost.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_heating_cost.yaml @@ -1,11 +1,11 @@ # Source: PY27 NH Fuel Assistance Program Procedures Manual, physical pages 17, 18, 76, 77. # https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf # Agency service counties: https://www.capnh.org/home-old -# Bedrooms plus three is a user-approved modeling assumption, not a statutory room definition. +# Using bedrooms alone is a user-approved modeling approximation, not the statutory room definition. # Inputs and estimates are annual; actual bills and the estimated cost are not added together. -# Manual p76: 2000 * 7 * 7672 / 1,000,000 * 28.881 / .78 = 3976.987754. Four bedrooms + three assumed rooms is a model approximation. -- name: "Case 1, four bedrooms reproduce the manual seven-room oil example." +# Manual pp76-77 formula with the approved bedroom-only proxy: 2000 * 4 * 7672 / 1,000,000 * 28.881 / .78 = 2272.564431. +- name: "Case 1, four bedrooms produce a four-room oil estimate without added rooms." period: 2027 absolute_error_margin: 0.01 input: @@ -27,9 +27,9 @@ county_str: "COOS_COUNTY_NH" bedrooms: 4 output: - nh_fap_heating_cost: 3_976.987754 + nh_fap_heating_cost: 2_272.564431 -# Manual p77: single-family factor 2000 * five assumed rooms * 7672 HDD; apply each fuel price / efficiency. WOOD is cordwood; electricity has no extra 70% reduction. +# Manual p77: single-family factor 2000 * two bedrooms * 7672 HDD; apply each fuel price / efficiency. WOOD is cordwood; electricity has no extra 70% reduction. - name: "Case 2, each observed fuel uses its published heating-cost conversion." period: 2027 absolute_error_margin: 0.01 @@ -129,9 +129,9 @@ county_str: "COOS_COUNTY_NH" bedrooms: 2 output: - nh_fap_heating_cost: [3_460.760513, 2_840.705538, 1_403.414634, 1_461.36256, 2_636.488889, 4_964.24432, 4_179.961333] + nh_fap_heating_cost: [1_384.304205, 1_136.282215, 561.365854, 584.545024, 1_054.595556, 1_985.697728, 1_671.984533] -# Manual p77 agency HDD and CAPNH county directory; factor 2000 * five assumed rooms * HDD / 1,000,000 * 28.881 / .78. +# Manual p77 agency HDD and CAPNH county directory; factor 2000 * two bedrooms * HDD / 1,000,000 * 28.881 / .78. - name: "Case 3, all ten counties use the agency heating degree days." period: 2027 absolute_error_margin: 0.01 @@ -270,10 +270,10 @@ county_str: "GRAFTON_COUNTY_NH" bedrooms: 2 output: - nh_fap_heating_cost: [2_447.479615, 2_447.479615, 2_219.0235, 2_219.0235, 2_244.201808, 2_439.333692, 2_439.333692, 2_840.705538, 2_840.705538, 2_840.705538] + nh_fap_heating_cost: [978.991846, 978.991846, 887.6094, 887.6094, 897.680723, 975.733477, 975.733477, 1_136.282215, 1_136.282215, 1_136.282215] # Manual p77: dwelling factors 2000, 1800 and 1400 with Strafford HDD 6061. -- name: "Case 4, dwelling factors change a five-room oil estimate." +- name: "Case 4, dwelling factors change a two-bedroom oil estimate." period: 2027 absolute_error_margin: 0.01 input: @@ -320,7 +320,7 @@ county_str: "STRAFFORD_COUNTY_NH" bedrooms: 2 output: - nh_fap_heating_cost: [2_244.201808, 2_019.781627, 1_570.941265] + nh_fap_heating_cost: [897.680723, 807.912651, 628.376506] # Approved model assumption: no room estimate for nonpositive bedrooms, so actual bills are retained. - name: "Case 5, zero or negative bedrooms preserve the actual bill fallback." @@ -372,7 +372,7 @@ output: nh_fap_heating_cost: [0, 650, 750] -# Manual p18: compare actual annual cost and design heat load for best benefit; 500 is below the seven-room estimate. +# Manual p18: compare actual annual cost and design heat load for best benefit; 500 is below the four-bedroom estimate. - name: "Case 6, the estimate is retained when the actual bill is lower." period: 2027 absolute_error_margin: 0.01 @@ -394,9 +394,9 @@ county_str: "COOS_COUNTY_NH" bedrooms: 4 output: - nh_fap_heating_cost: 3_976.987754 + nh_fap_heating_cost: 2_272.564431 -# Manual p18 allows documented actual cost for high usage; 5000 exceeds 3976.987754. +# Manual p18 allows documented actual cost for high usage; 5000 exceeds 2272.564431. - name: "Case 7, a larger annual bill replaces the estimate." period: 2027 absolute_error_margin: 0.01 @@ -565,7 +565,7 @@ county_str: "COOS_COUNTY_NH" bedrooms: 4 output: - nh_fap_heating_cost: 3_976.987754 + nh_fap_heating_cost: 2_272.564431 # With no room estimate and no positive annual cost, the component is zero. - name: "Case 12, negative reported bills without bedrooms have zero modeled cost." @@ -614,7 +614,7 @@ output: nh_fap_heating_cost: 500 -# Approved whole-household approximation: both units use five rooms; no inferred private-room allocation. Factor 1400 with Coos HDD 7672. +# Approved whole-household approximation: both units use two bedrooms; no inferred private-room allocation. Factor 1400 with Coos HDD 7672. - name: "Case 14, SPM units reuse household bedrooms with their own fuel." period: 2027 absolute_error_margin: 0.01 @@ -640,4 +640,4 @@ county_str: "COOS_COUNTY_NH" bedrooms: 2 output: - nh_fap_heating_cost: [1_845.542222, 982.390244] + nh_fap_heating_cost: [738.216889, 392.956098] diff --git a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_heating_cost.py b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_heating_cost.py index 5d9a166776b..8026278ebde 100644 --- a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_heating_cost.py +++ b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_heating_cost.py @@ -7,16 +7,17 @@ class nh_fap_heating_cost(Variable): definition_period = YEAR unit = USD label = "New Hampshire FAP annual heating cost using actual or estimated cost" - reference = "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=17, 18, 76, 77" + # PY2027 manual, pages 17, 18, 76, 77. + reference = "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=17" defined_for = StateCode.NH def formula(spm_unit, period, parameters): p = parameters(period).gov.states.nh.doe.fap.heating_cost bedrooms = spm_unit.household("bedrooms", period) - # Bedrooms plus a kitchen, living room and bathroom is an approved - # modeling approximation, not the program's measured room count. + # Use bedrooms alone as an approved modeling approximation, + # not the program's measured room count. No other rooms are inferred. # Zero bedrooms cannot distinguish a studio from missing information. - rooms = where(bedrooms > 0, bedrooms + p.additional_rooms, 0) + rooms = max_(bedrooms, 0) # Whole-household bedrooms approximate the SPM unit's dwelling; # private, shared and business-only room adjustments are unobserved. dwelling = spm_unit("nh_fap_dwelling_type", period) From 66fb81e8f719d5654ce3ff10db0767d55f99e2c5 Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 6 Oct 2026 13:57:06 -0400 Subject: [PATCH 05/14] Defer ambiguous New Hampshire FAP investment income deduction --- .../nh-liheap-investment-disregard.changed.md | 1 + .../doe/fap/income/investment_disregard.yaml | 19 +++++++----- .../nh/doe/fap/nh_fap_countable_income.yaml | 22 +++++++------- .../states/nh/doe/fap/nh_fap_eligible.yaml | 30 +++++++++++++++++++ .../nh/doe/fap/nh_fap_countable_income.py | 6 ++-- 5 files changed, 56 insertions(+), 22 deletions(-) create mode 100644 changelog.d/nh-liheap-investment-disregard.changed.md diff --git a/changelog.d/nh-liheap-investment-disregard.changed.md b/changelog.d/nh-liheap-investment-disregard.changed.md new file mode 100644 index 00000000000..609fba342f6 --- /dev/null +++ b/changelog.d/nh-liheap-investment-disregard.changed.md @@ -0,0 +1 @@ +Defer the ambiguous New Hampshire FAP investment-income deduction and count interest and dividends without that deduction pending clarification. diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/income/investment_disregard.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/income/investment_disregard.yaml index 5d9430a6f02..1a0f66bf09d 100644 --- a/policyengine_us/parameters/gov/states/nh/doe/fap/income/investment_disregard.yaml +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/income/investment_disregard.yaml @@ -1,14 +1,17 @@ -description: New Hampshire excludes this annual amount of each household's interest and dividend income under the Fuel Assistance Program. -# Page 16 lists this exclusion separately for interest and dividends. Its -# application to each household source follows that specific list; page 17's -# combined "and/or" summary is ambiguous and lacks agency clarification. +description: PolicyEngine applies this modeled annual investment-income disregard per source when calculating countable income under the New Hampshire Fuel Assistance Program. +# Modeling omission: skip this deduction for now, as requested by the user. +# The PY2027 manual lists the first $50 of interest and dividends separately +# on pages 15-16, but page 17 combines them as interest "and/or" dividends. +# It does not specify whether the allowance applies per person or household. +# Zero deliberately disables the deduction; it is not the published policy +# amount. Revisit both the amount and its application when clarified. values: - 2026-10-01: 50 + 2026-10-01: 0 metadata: unit: currency-USD period: year - label: New Hampshire FAP annual disregard for each investment income source + label: New Hampshire FAP modeled annual investment income disregard reference: - - title: PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Non-countable Income and Allowable Expenses - href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=15, 16, 17 + - title: "PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Countable and Non-countable Income and Allowable Expenses #page 15,16,17" + href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=15 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_countable_income.yaml index 596b73ad9e7..a08850a32c4 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_countable_income.yaml @@ -487,8 +487,8 @@ output: nh_fap_countable_income: 1_400 -# Manual pp15-16 separate interest and dividend exclusions; p17 wording ambiguity is retained in source documentation. -- name: "Case 17, the first fifty dollars of each investment source is excluded." +# User-approved omission: the ambiguous investment deduction is disabled; 50 + 50 = 100. +- name: "Case 17, interest and dividends are counted without the deferred deduction." period: 2027 absolute_error_margin: 0.01 input: @@ -512,10 +512,10 @@ members: ["person1"] state_code: "NH" output: - nh_fap_countable_income: 0 + nh_fap_countable_income: 100 -# Manual pp15-16: (51-50)+(51-50)=2 under the documented separate-source interpretation. -- name: "Case 18, one dollar above each investment disregard is countable." +# Skipping the ambiguous deduction counts the full 51 + 51 = 102. +- name: "Case 18, investment receipts above fifty dollars remain fully countable." period: 2027 absolute_error_margin: 0.01 input: @@ -539,10 +539,10 @@ members: ["person1"] state_code: "NH" output: - nh_fap_countable_income: 2 + nh_fap_countable_income: 102 -# Contract interpretation of household income: (80-50)+(60-50)=40, not a separate 50 allowance per person. -- name: "Case 19, investment disregards apply once per household source." +# With no investment deduction, both household members contribute their full receipts: 40 + 20 + 40 + 40 = 140. +- name: "Case 19, investment receipts are counted for every household member without deductions." period: 2027 absolute_error_margin: 0.01 input: @@ -572,9 +572,9 @@ members: ["person1", "person2"] state_code: "NH" output: - nh_fap_countable_income: 40 + nh_fap_countable_income: 140 -# Manual pp15-16 counts dividends above 50; canonical ordinary_dividend_income includes both dividend components. +# The canonical ordinary_dividend_income includes both dividend components; the deferred deduction is not applied. - name: "Case 20, qualified and nonqualified dividends feed the canonical ordinary aggregate." period: 2027 absolute_error_margin: 0.01 @@ -599,7 +599,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_countable_income: 150 + nh_fap_countable_income: 200 # Manual p15; annual SSI receipts count once. Medicare reconstruction is expressly deferred. - name: "Case 21, gross Social Security and adult SSI are both countable." diff --git a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml index f9540a3ef27..300f4c5ff50 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml @@ -1108,3 +1108,33 @@ bedrooms: 4 output: nh_fap_eligible: [true, false, false, false, false, false, false, false, true] + +# The user-approved omission counts all $25 of interest; 48,713 + 25 = 48,738 +# exceeds the one-person income limit. Previously the investment disregard +# excluded this interest and left the household eligible at the limit. +- name: "Case 28, interest below fifty dollars can put income above the limit when the deduction is deferred." + period: 2027 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 48_713 + interest_income: 25 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 48_738 + nh_fap_eligible: false + nh_fap_base_benefit: 0 diff --git a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py index a13b317ae48..1db14851cdb 100644 --- a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py +++ b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py @@ -37,9 +37,9 @@ def formula(spm_unit, period, parameters): 0, ) ) - # Page 16 separately excludes the first $50 of each source; page 17's - # "and/or" summary is ambiguous. Apply each exclusion once per household - # source, not per person. This interpretation lacks agency confirmation. + # The investment disregard is set to zero while its source and + # person/household application remain unresolved. Count these receipts + # without that deduction; see the parameter's modeling-omission note. interest = add(spm_unit, period, ["interest_income"]) dividends = add(spm_unit, period, ["ordinary_dividend_income"]) investment_income = max_(interest - p.investment_disregard, 0) + max_( From b0763dbada6882cfecf1474f48ba38f227626b03 Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 6 Oct 2026 14:03:50 -0400 Subject: [PATCH 06/14] Apply one New Hampshire FAP investment exclusion per SPM unit --- .../nh-liheap-investment-disregard.changed.md | 2 +- .../doe/fap/income/investment_disregard.yaml | 13 ++-- .../nh/doe/fap/nh_fap_countable_income.yaml | 67 ++++++++++++++++--- .../states/nh/doe/fap/nh_fap_eligible.yaml | 9 ++- .../nh/doe/fap/nh_fap_countable_income.py | 13 ++-- 5 files changed, 73 insertions(+), 31 deletions(-) diff --git a/changelog.d/nh-liheap-investment-disregard.changed.md b/changelog.d/nh-liheap-investment-disregard.changed.md index 609fba342f6..2089855698e 100644 --- a/changelog.d/nh-liheap-investment-disregard.changed.md +++ b/changelog.d/nh-liheap-investment-disregard.changed.md @@ -1 +1 @@ -Defer the ambiguous New Hampshire FAP investment-income deduction and count interest and dividends without that deduction pending clarification. +Apply one $50 annual New Hampshire FAP investment-income exclusion per SPM unit to combined interest and dividends, documenting the modeled basis. diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/income/investment_disregard.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/income/investment_disregard.yaml index 1a0f66bf09d..c7d18b0bc11 100644 --- a/policyengine_us/parameters/gov/states/nh/doe/fap/income/investment_disregard.yaml +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/income/investment_disregard.yaml @@ -1,17 +1,18 @@ -description: PolicyEngine applies this modeled annual investment-income disregard per source when calculating countable income under the New Hampshire Fuel Assistance Program. -# Modeling omission: skip this deduction for now, as requested by the user. +description: PolicyEngine applies this annual exclusion once to each SPM unit's combined interest and dividend income under the New Hampshire Fuel Assistance Program. +# Modeling convention: apply one $50 annual exclusion per SPM unit to +# combined interest and dividends, as requested by the user. # The PY2027 manual lists the first $50 of interest and dividends separately # on pages 15-16, but page 17 combines them as interest "and/or" dividends. # It does not specify whether the allowance applies per person or household. -# Zero deliberately disables the deduction; it is not the published policy -# amount. Revisit both the amount and its application when clarified. +# The combined-source, SPM-unit basis is an explicit modeling assumption +# pending agency clarification. values: - 2026-10-01: 0 + 2026-10-01: 50 metadata: unit: currency-USD period: year - label: New Hampshire FAP modeled annual investment income disregard + label: New Hampshire FAP annual investment income exclusion per SPM unit reference: - title: "PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Countable and Non-countable Income and Allowable Expenses #page 15,16,17" href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=15 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_countable_income.yaml index a08850a32c4..5daffd814f3 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_countable_income.yaml @@ -487,8 +487,8 @@ output: nh_fap_countable_income: 1_400 -# User-approved omission: the ambiguous investment deduction is disabled; 50 + 50 = 100. -- name: "Case 17, interest and dividends are counted without the deferred deduction." +# User-approved convention: one $50 annual exclusion per SPM unit; 50 + 50 - 50 = 50. +- name: "Case 17, interest and dividends share one fifty-dollar exclusion." period: 2027 absolute_error_margin: 0.01 input: @@ -512,10 +512,10 @@ members: ["person1"] state_code: "NH" output: - nh_fap_countable_income: 100 + nh_fap_countable_income: 50 -# Skipping the ambiguous deduction counts the full 51 + 51 = 102. -- name: "Case 18, investment receipts above fifty dollars remain fully countable." +# Apply the $50 exclusion once to combined interest and dividends: 51 + 51 - 50 = 52. +- name: "Case 18, combined investment receipts above the shared exclusion are countable." period: 2027 absolute_error_margin: 0.01 input: @@ -539,10 +539,10 @@ members: ["person1"] state_code: "NH" output: - nh_fap_countable_income: 102 + nh_fap_countable_income: 52 -# With no investment deduction, both household members contribute their full receipts: 40 + 20 + 40 + 40 = 140. -- name: "Case 19, investment receipts are counted for every household member without deductions." +# Both members share one SPM-unit exclusion: 40 + 20 + 40 + 40 - 50 = 90. +- name: "Case 19, members of one SPM unit share one investment exclusion." period: 2027 absolute_error_margin: 0.01 input: @@ -572,9 +572,9 @@ members: ["person1", "person2"] state_code: "NH" output: - nh_fap_countable_income: 140 + nh_fap_countable_income: 90 -# The canonical ordinary_dividend_income includes both dividend components; the deferred deduction is not applied. +# The canonical ordinary_dividend_income includes both dividend components; 100 + 100 - 50 = 150. - name: "Case 20, qualified and nonqualified dividends feed the canonical ordinary aggregate." period: 2027 absolute_error_margin: 0.01 @@ -599,7 +599,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_countable_income: 200 + nh_fap_countable_income: 150 # Manual p15; annual SSI receipts count once. Medicare reconstruction is expressly deferred. - name: "Case 21, gross Social Security and adult SSI are both countable." @@ -940,3 +940,48 @@ state_code: "NH" output: nh_fap_countable_income: [38_400, 12_000] + +# Each SPM unit in the same household gets its own $50 exclusion. +# Unit 1: 40 + 40 - 50 = 30. Unit 2: 25 + 25 - 50 = 0. +# Unit 3: investment income of 10 + 20 is fully excluded; the unused $20 +# of the allowance does not offset $10,000 of earnings. +- name: "Case 33, each SPM unit receives its own exclusion limited to investment income." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + interest_income: 40 + ordinary_dividend_income: 40 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + interest_income: 25 + ordinary_dividend_income: 25 + person3: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + interest_income: 10 + ordinary_dividend_income: 20 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + spm_unit2: + members: ["person2"] + nh_fanf: 0 + spm_unit3: + members: ["person3"] + nh_fanf: 0 + households: + household1: + members: ["person1", "person2", "person3"] + state_code: "NH" + output: + nh_fap_countable_income: [30, 0, 10_000] diff --git a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml index 300f4c5ff50..9256f52c47f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml @@ -1109,10 +1109,9 @@ output: nh_fap_eligible: [true, false, false, false, false, false, false, false, true] -# The user-approved omission counts all $25 of interest; 48,713 + 25 = 48,738 -# exceeds the one-person income limit. Previously the investment disregard -# excluded this interest and left the household eligible at the limit. -- name: "Case 28, interest below fifty dollars can put income above the limit when the deduction is deferred." +# The shared $50 investment exclusion leaves $25 of $75 interest countable; +# 48,713 + 25 = 48,738 exceeds the one-person income limit. +- name: "Case 28, interest remaining after the SPM-unit exclusion can put income above the limit." period: 2027 input: people: @@ -1121,7 +1120,7 @@ immigration_status: "CITIZEN" ssi: 0 employment_income: 48_713 - interest_income: 25 + interest_income: 75 spm_units: spm_unit1: members: ["person1"] diff --git a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py index 1db14851cdb..f96eec19935 100644 --- a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py +++ b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py @@ -37,14 +37,11 @@ def formula(spm_unit, period, parameters): 0, ) ) - # The investment disregard is set to zero while its source and - # person/household application remain unresolved. Count these receipts - # without that deduction; see the parameter's modeling-omission note. - interest = add(spm_unit, period, ["interest_income"]) - dividends = add(spm_unit, period, ["ordinary_dividend_income"]) - investment_income = max_(interest - p.investment_disregard, 0) + max_( - dividends - p.investment_disregard, 0 - ) + # Apply one exclusion to combined investment income per SPM unit; + # see the parameter's modeling-convention note. + interest = max_(add(spm_unit, period, ["interest_income"]), 0) + dividends = max_(add(spm_unit, period, ["ordinary_dividend_income"]), 0) + investment_income = max_(interest + dividends - p.investment_disregard, 0) # FANF is an SPM-unit benefit and is counted once. income = ( countable_earnings From b6ca47745f1dd58dab365dbdd4012baec2ef774a Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 6 Oct 2026 15:16:47 -0400 Subject: [PATCH 07/14] Expose New Hampshire FAP award under approved heat-in-rent assumption --- changelog.d/nh-liheap-wave2b.added.md | 2 +- policyengine_us/programs.yaml | 10 +- .../{nh_fap_base_benefit.yaml => nh_fap.yaml} | 359 +++++++++++++----- .../states/nh/doe/fap/nh_fap_eligible.yaml | 2 +- .../fap/{nh_fap_base_benefit.py => nh_fap.py} | 30 +- .../gov/states/nh/doe/fap/nh_fap_eligible.py | 5 +- 6 files changed, 309 insertions(+), 99 deletions(-) rename policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/{nh_fap_base_benefit.yaml => nh_fap.yaml} (89%) rename policyengine_us/variables/gov/states/nh/doe/fap/{nh_fap_base_benefit.py => nh_fap.py} (61%) diff --git a/changelog.d/nh-liheap-wave2b.added.md b/changelog.d/nh-liheap-wave2b.added.md index e2c26be4485..16686f03b94 100644 --- a/changelog.d/nh-liheap-wave2b.added.md +++ b/changelog.d/nh-liheap-wave2b.added.md @@ -1 +1 @@ -Add New Hampshire FAP eligibility, countable income, and actual-cost benefit schedule components for PY2027; the final heat-in-rent adjustment remains pending. +Add New Hampshire FAP eligibility, countable income, and regular heating awards for PY2027, with a documented assumption that unsubsidized heat-in-rent receives the ordinary fuel schedule without an additional reduction. diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 756dc2c4988..3ee4f4682a7 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -802,7 +802,7 @@ programs: category: Benefits agency: HHS status: partial - coverage: OR, DC, Riverside County, MA, IL, KS, MS, TX + coverage: OR, DC, Riverside County, MA, IL, KS, MS, NH, TX state_implementations: - state: OR status: in_progress @@ -839,6 +839,14 @@ programs: parameter_prefix: gov.states.ms.mdhs.liheap verified_years: "2026" notes: FY2026 regular assistance for heating fuel and electric bills, using the published matrix as printed. Crisis assistance and energy equipment are out of scope. + - state: NH + status: partial + name: New Hampshire FAP + full_name: New Hampshire Fuel Assistance Program + variable: nh_fap + parameter_prefix: gov.states.nh.doe.fap + verified_years: "2027" + notes: PY2027 regular heating only. Bedrooms alone approximate room count, and one annual $50 exclusion applies to combined interest and dividends per SPM unit. Unsubsidized heat-in-rent uses the ordinary fuel schedule without an additional reduction under an approved modeling assumption; the state's reduction is unverified and awards may be overstated. Subsidized heat-in-rent is ineligible. Unsupported fuel or heating-cost cases return zero. Earlier years are unverified historical estimates; crisis, cooling, weatherization and equipment assistance are out of scope. - state: TX status: partial name: Texas CEAP diff --git a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_base_benefit.yaml b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap.yaml similarity index 89% rename from policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_base_benefit.yaml rename to policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap.yaml index 3bf40752a29..afc0d2cb028 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_base_benefit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap.yaml @@ -3,8 +3,9 @@ # Physical PDF pages: 10, 17, 18, 35, 36, 74, 76, 77, 78. # Period 2027 represents the researched 2026-2027 schedule with 2026 FPG. # Inputs are annual; SSI and FANF totals are explicitly supplied to isolate receipts. -# This is the base schedule using actual or estimated costs, before unresolved heat-in-rent adjustment. -# No final nh_fap award or unsupported fuel amount is asserted. +# Final modeled awards use actual or estimated costs and the ordinary fuel schedule. +# No additional heat-in-rent reduction is applied under the approved modeling assumption. +# This assumption is not agency-confirmed; unsupported fuel amounts remain zero. # Manual p78, delivered DD/A cell. Actual annual primary-fuel bill selects the row. - name: "Case 1, delivered matrix cost tier DD and income band A." @@ -31,7 +32,7 @@ state_code: "NH" output: nh_fap_countable_income: 10_000 - nh_fap_base_benefit: 750 + nh_fap: 750 # Manual p78, utility DD/A cell, including published whole-dollar rounding. - name: "Case 2, utility matrix cost tier DD and income band A." @@ -58,7 +59,7 @@ state_code: "NH" output: nh_fap_countable_income: 10_000 - nh_fap_base_benefit: 563 + nh_fap: 563 # Manual p78, delivered DD/B cell. Actual annual primary-fuel bill selects the row. - name: "Case 3, delivered matrix cost tier DD and income band B." @@ -85,7 +86,7 @@ state_code: "NH" output: nh_fap_countable_income: 14_000 - nh_fap_base_benefit: 650 + nh_fap: 650 # Manual p78, utility DD/B cell, including published whole-dollar rounding. - name: "Case 4, utility matrix cost tier DD and income band B." @@ -112,7 +113,7 @@ state_code: "NH" output: nh_fap_countable_income: 14_000 - nh_fap_base_benefit: 488 + nh_fap: 488 # Manual p78, delivered DD/C cell. Actual annual primary-fuel bill selects the row. - name: "Case 5, delivered matrix cost tier DD and income band C." @@ -139,7 +140,7 @@ state_code: "NH" output: nh_fap_countable_income: 18_000 - nh_fap_base_benefit: 550 + nh_fap: 550 # Manual p78, utility DD/C cell, including published whole-dollar rounding. - name: "Case 6, utility matrix cost tier DD and income band C." @@ -166,7 +167,7 @@ state_code: "NH" output: nh_fap_countable_income: 18_000 - nh_fap_base_benefit: 413 + nh_fap: 413 # Manual p78, delivered DD/D cell. Actual annual primary-fuel bill selects the row. - name: "Case 7, delivered matrix cost tier DD and income band D." @@ -193,7 +194,7 @@ state_code: "NH" output: nh_fap_countable_income: 22_000 - nh_fap_base_benefit: 500 + nh_fap: 500 # Manual p78, utility DD/D cell, including published whole-dollar rounding. - name: "Case 8, utility matrix cost tier DD and income band D." @@ -220,7 +221,7 @@ state_code: "NH" output: nh_fap_countable_income: 22_000 - nh_fap_base_benefit: 375 + nh_fap: 375 # Manual p78, delivered DD/E cell. Actual annual primary-fuel bill selects the row. - name: "Case 9, delivered matrix cost tier DD and income band E." @@ -247,7 +248,7 @@ state_code: "NH" output: nh_fap_countable_income: 26_000 - nh_fap_base_benefit: 440 + nh_fap: 440 # Manual p78, utility DD/E cell, including published whole-dollar rounding. - name: "Case 10, utility matrix cost tier DD and income band E." @@ -274,7 +275,7 @@ state_code: "NH" output: nh_fap_countable_income: 26_000 - nh_fap_base_benefit: 330 + nh_fap: 330 # Manual p78, delivered DD/F cell. Actual annual primary-fuel bill selects the row. - name: "Case 11, delivered matrix cost tier DD and income band F." @@ -301,7 +302,7 @@ state_code: "NH" output: nh_fap_countable_income: 40_000 - nh_fap_base_benefit: 400 + nh_fap: 400 # Manual p78, utility DD/F cell, including published whole-dollar rounding. - name: "Case 12, utility matrix cost tier DD and income band F." @@ -328,7 +329,7 @@ state_code: "NH" output: nh_fap_countable_income: 40_000 - nh_fap_base_benefit: 300 + nh_fap: 300 # Manual p78, delivered CC/A cell. Actual annual primary-fuel bill selects the row. - name: "Case 13, delivered matrix cost tier CC and income band A." @@ -355,7 +356,7 @@ state_code: "NH" output: nh_fap_countable_income: 10_000 - nh_fap_base_benefit: 1_460 + nh_fap: 1_460 # Manual p78, utility CC/A cell, including published whole-dollar rounding. - name: "Case 14, utility matrix cost tier CC and income band A." @@ -382,7 +383,7 @@ state_code: "NH" output: nh_fap_countable_income: 10_000 - nh_fap_base_benefit: 1_095 + nh_fap: 1_095 # Manual p78, delivered CC/B cell. Actual annual primary-fuel bill selects the row. - name: "Case 15, delivered matrix cost tier CC and income band B." @@ -409,7 +410,7 @@ state_code: "NH" output: nh_fap_countable_income: 14_000 - nh_fap_base_benefit: 1_270 + nh_fap: 1_270 # Manual p78, utility CC/B cell, including published whole-dollar rounding. - name: "Case 16, utility matrix cost tier CC and income band B." @@ -436,7 +437,7 @@ state_code: "NH" output: nh_fap_countable_income: 14_000 - nh_fap_base_benefit: 953 + nh_fap: 953 # Manual p78, delivered CC/C cell. Actual annual primary-fuel bill selects the row. - name: "Case 17, delivered matrix cost tier CC and income band C." @@ -463,7 +464,7 @@ state_code: "NH" output: nh_fap_countable_income: 18_000 - nh_fap_base_benefit: 1_150 + nh_fap: 1_150 # Manual p78, utility CC/C cell, including published whole-dollar rounding. - name: "Case 18, utility matrix cost tier CC and income band C." @@ -490,7 +491,7 @@ state_code: "NH" output: nh_fap_countable_income: 18_000 - nh_fap_base_benefit: 863 + nh_fap: 863 # Manual p78, delivered CC/D cell. Actual annual primary-fuel bill selects the row. - name: "Case 19, delivered matrix cost tier CC and income band D." @@ -517,7 +518,7 @@ state_code: "NH" output: nh_fap_countable_income: 22_000 - nh_fap_base_benefit: 950 + nh_fap: 950 # Manual p78, utility CC/D cell, including published whole-dollar rounding. - name: "Case 20, utility matrix cost tier CC and income band D." @@ -544,7 +545,7 @@ state_code: "NH" output: nh_fap_countable_income: 22_000 - nh_fap_base_benefit: 713 + nh_fap: 713 # Manual p78, delivered CC/E cell. Actual annual primary-fuel bill selects the row. - name: "Case 21, delivered matrix cost tier CC and income band E." @@ -571,7 +572,7 @@ state_code: "NH" output: nh_fap_countable_income: 26_000 - nh_fap_base_benefit: 730 + nh_fap: 730 # Manual p78, utility CC/E cell, including published whole-dollar rounding. - name: "Case 22, utility matrix cost tier CC and income band E." @@ -598,7 +599,7 @@ state_code: "NH" output: nh_fap_countable_income: 26_000 - nh_fap_base_benefit: 548 + nh_fap: 548 # Manual p78, delivered CC/F cell. Actual annual primary-fuel bill selects the row. - name: "Case 23, delivered matrix cost tier CC and income band F." @@ -625,7 +626,7 @@ state_code: "NH" output: nh_fap_countable_income: 40_000 - nh_fap_base_benefit: 525 + nh_fap: 525 # Manual p78, utility CC/F cell, including published whole-dollar rounding. - name: "Case 24, utility matrix cost tier CC and income band F." @@ -652,7 +653,7 @@ state_code: "NH" output: nh_fap_countable_income: 40_000 - nh_fap_base_benefit: 394 + nh_fap: 394 # Manual p78, delivered BB/A cell. Actual annual primary-fuel bill selects the row. - name: "Case 25, delivered matrix cost tier BB and income band A." @@ -679,7 +680,7 @@ state_code: "NH" output: nh_fap_countable_income: 10_000 - nh_fap_base_benefit: 2_350 + nh_fap: 2_350 # Manual p78, utility BB/A cell, including published whole-dollar rounding. - name: "Case 26, utility matrix cost tier BB and income band A." @@ -706,7 +707,7 @@ state_code: "NH" output: nh_fap_countable_income: 10_000 - nh_fap_base_benefit: 1_763 + nh_fap: 1_763 # Manual p78, delivered BB/B cell. Actual annual primary-fuel bill selects the row. - name: "Case 27, delivered matrix cost tier BB and income band B." @@ -733,7 +734,7 @@ state_code: "NH" output: nh_fap_countable_income: 14_000 - nh_fap_base_benefit: 2_040 + nh_fap: 2_040 # Manual p78, utility BB/B cell, including published whole-dollar rounding. - name: "Case 28, utility matrix cost tier BB and income band B." @@ -760,7 +761,7 @@ state_code: "NH" output: nh_fap_countable_income: 14_000 - nh_fap_base_benefit: 1_530 + nh_fap: 1_530 # Manual p78, delivered BB/C cell. Actual annual primary-fuel bill selects the row. - name: "Case 29, delivered matrix cost tier BB and income band C." @@ -787,7 +788,7 @@ state_code: "NH" output: nh_fap_countable_income: 18_000 - nh_fap_base_benefit: 1_720 + nh_fap: 1_720 # Manual p78, utility BB/C cell, including published whole-dollar rounding. - name: "Case 30, utility matrix cost tier BB and income band C." @@ -814,7 +815,7 @@ state_code: "NH" output: nh_fap_countable_income: 18_000 - nh_fap_base_benefit: 1_290 + nh_fap: 1_290 # Manual p78, delivered BB/D cell. Actual annual primary-fuel bill selects the row. - name: "Case 31, delivered matrix cost tier BB and income band D." @@ -841,7 +842,7 @@ state_code: "NH" output: nh_fap_countable_income: 22_000 - nh_fap_base_benefit: 1_410 + nh_fap: 1_410 # Manual p78, utility BB/D cell, including published whole-dollar rounding. - name: "Case 32, utility matrix cost tier BB and income band D." @@ -868,7 +869,7 @@ state_code: "NH" output: nh_fap_countable_income: 22_000 - nh_fap_base_benefit: 1_058 + nh_fap: 1_058 # Manual p78, delivered BB/E cell. Actual annual primary-fuel bill selects the row. - name: "Case 33, delivered matrix cost tier BB and income band E." @@ -895,7 +896,7 @@ state_code: "NH" output: nh_fap_countable_income: 26_000 - nh_fap_base_benefit: 1_100 + nh_fap: 1_100 # Manual p78, utility BB/E cell, including published whole-dollar rounding. - name: "Case 34, utility matrix cost tier BB and income band E." @@ -922,7 +923,7 @@ state_code: "NH" output: nh_fap_countable_income: 26_000 - nh_fap_base_benefit: 825 + nh_fap: 825 # Manual p78, delivered BB/F cell. Actual annual primary-fuel bill selects the row. - name: "Case 35, delivered matrix cost tier BB and income band F." @@ -949,7 +950,7 @@ state_code: "NH" output: nh_fap_countable_income: 40_000 - nh_fap_base_benefit: 800 + nh_fap: 800 # Manual p78, utility BB/F cell, including published whole-dollar rounding. - name: "Case 36, utility matrix cost tier BB and income band F." @@ -976,7 +977,7 @@ state_code: "NH" output: nh_fap_countable_income: 40_000 - nh_fap_base_benefit: 600 + nh_fap: 600 # Manual p78, delivered AA/A cell. Actual annual primary-fuel bill selects the row. - name: "Case 37, delivered matrix cost tier AA and income band A." @@ -1003,7 +1004,7 @@ state_code: "NH" output: nh_fap_countable_income: 10_000 - nh_fap_base_benefit: 2_500 + nh_fap: 2_500 # Manual p78, utility AA/A cell, including published whole-dollar rounding. - name: "Case 38, utility matrix cost tier AA and income band A." @@ -1030,7 +1031,7 @@ state_code: "NH" output: nh_fap_countable_income: 10_000 - nh_fap_base_benefit: 1_875 + nh_fap: 1_875 # Manual p78, delivered AA/B cell. Actual annual primary-fuel bill selects the row. - name: "Case 39, delivered matrix cost tier AA and income band B." @@ -1057,7 +1058,7 @@ state_code: "NH" output: nh_fap_countable_income: 14_000 - nh_fap_base_benefit: 2_150 + nh_fap: 2_150 # Manual p78, utility AA/B cell, including published whole-dollar rounding. - name: "Case 40, utility matrix cost tier AA and income band B." @@ -1084,7 +1085,7 @@ state_code: "NH" output: nh_fap_countable_income: 14_000 - nh_fap_base_benefit: 1_613 + nh_fap: 1_613 # Manual p78, delivered AA/C cell. Actual annual primary-fuel bill selects the row. - name: "Case 41, delivered matrix cost tier AA and income band C." @@ -1111,7 +1112,7 @@ state_code: "NH" output: nh_fap_countable_income: 18_000 - nh_fap_base_benefit: 1_825 + nh_fap: 1_825 # Manual p78, utility AA/C cell, including published whole-dollar rounding. - name: "Case 42, utility matrix cost tier AA and income band C." @@ -1138,7 +1139,7 @@ state_code: "NH" output: nh_fap_countable_income: 18_000 - nh_fap_base_benefit: 1_369 + nh_fap: 1_369 # Manual p78, delivered AA/D cell. Actual annual primary-fuel bill selects the row. - name: "Case 43, delivered matrix cost tier AA and income band D." @@ -1165,7 +1166,7 @@ state_code: "NH" output: nh_fap_countable_income: 22_000 - nh_fap_base_benefit: 1_500 + nh_fap: 1_500 # Manual p78, utility AA/D cell, including published whole-dollar rounding. - name: "Case 44, utility matrix cost tier AA and income band D." @@ -1192,7 +1193,7 @@ state_code: "NH" output: nh_fap_countable_income: 22_000 - nh_fap_base_benefit: 1_125 + nh_fap: 1_125 # Manual p78, delivered AA/E cell. Actual annual primary-fuel bill selects the row. - name: "Case 45, delivered matrix cost tier AA and income band E." @@ -1219,7 +1220,7 @@ state_code: "NH" output: nh_fap_countable_income: 26_000 - nh_fap_base_benefit: 1_200 + nh_fap: 1_200 # Manual p78, utility AA/E cell, including published whole-dollar rounding. - name: "Case 46, utility matrix cost tier AA and income band E." @@ -1246,7 +1247,7 @@ state_code: "NH" output: nh_fap_countable_income: 26_000 - nh_fap_base_benefit: 900 + nh_fap: 900 # Manual p78, delivered AA/F cell. Actual annual primary-fuel bill selects the row. - name: "Case 47, delivered matrix cost tier AA and income band F." @@ -1273,7 +1274,7 @@ state_code: "NH" output: nh_fap_countable_income: 40_000 - nh_fap_base_benefit: 825 + nh_fap: 825 # Manual p78, utility AA/F cell, including published whole-dollar rounding. - name: "Case 48, utility matrix cost tier AA and income band F." @@ -1300,7 +1301,7 @@ state_code: "NH" output: nh_fap_countable_income: 40_000 - nh_fap_base_benefit: 619 + nh_fap: 619 # Manual pp17 and74; one-person 2026 FPG of 15,960 for the initial PY2027 snapshot. - name: "Case 49, income exactly 11970 remains in its inclusive FPG band." @@ -1326,7 +1327,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 2_500 + nh_fap: 2_500 # Manual pp17,74 and78; strict upper-band transition. - name: "Case 50, income one cent above 11970 enters the next FPG band." @@ -1352,7 +1353,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 2_150 + nh_fap: 2_150 # Manual pp17 and74; one-person 2026 FPG of 15,960 for the initial PY2027 snapshot. - name: "Case 51, income exactly 15960 remains in its inclusive FPG band." @@ -1378,7 +1379,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 2_150 + nh_fap: 2_150 # Manual pp17,74 and78; strict upper-band transition. - name: "Case 52, income one cent above 15960 enters the next FPG band." @@ -1404,7 +1405,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 1_825 + nh_fap: 1_825 # Manual pp17 and74; one-person 2026 FPG of 15,960 for the initial PY2027 snapshot. - name: "Case 53, income exactly 19950 remains in its inclusive FPG band." @@ -1430,7 +1431,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 1_825 + nh_fap: 1_825 # Manual pp17,74 and78; strict upper-band transition. - name: "Case 54, income one cent above 19950 enters the next FPG band." @@ -1456,7 +1457,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 1_500 + nh_fap: 1_500 # Manual pp17 and74; one-person 2026 FPG of 15,960 for the initial PY2027 snapshot. - name: "Case 55, income exactly 23940 remains in its inclusive FPG band." @@ -1482,7 +1483,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 1_500 + nh_fap: 1_500 # Manual pp17,74 and78; strict upper-band transition. - name: "Case 56, income one cent above 23940 enters the next FPG band." @@ -1508,7 +1509,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 1_200 + nh_fap: 1_200 # Manual pp17 and74; one-person 2026 FPG of 15,960 for the initial PY2027 snapshot. - name: "Case 57, income exactly 27930 remains in its inclusive FPG band." @@ -1534,7 +1535,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 1_200 + nh_fap: 1_200 # Manual pp17,74 and78; strict upper-band transition. - name: "Case 58, income one cent above 27930 enters the next FPG band." @@ -1560,7 +1561,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 825 + nh_fap: 825 # Manual p77 exact ECOS ranges control the overlapping printed utility labels on p78. - name: "Case 59, delivered annual cost 999.99 selects the current cost tier." @@ -1586,7 +1587,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 750 + nh_fap: 750 # Manual pp77-78 exact cost ranges and utility schedule. - name: "Case 60, utility annual cost 999.99 selects the current cost tier." @@ -1612,7 +1613,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 563 + nh_fap: 563 # Manual p77 exact ECOS ranges control the overlapping printed utility labels on p78. - name: "Case 61, delivered annual cost 1000 selects the current cost tier." @@ -1638,7 +1639,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 1_460 + nh_fap: 1_460 # Manual pp77-78 exact cost ranges and utility schedule. - name: "Case 62, utility annual cost 1000 selects the current cost tier." @@ -1664,7 +1665,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 1_095 + nh_fap: 1_095 # Manual p77 exact ECOS ranges control the overlapping printed utility labels on p78. - name: "Case 63, delivered annual cost 1749.99 selects the current cost tier." @@ -1690,7 +1691,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 1_460 + nh_fap: 1_460 # Manual pp77-78 exact cost ranges and utility schedule. - name: "Case 64, utility annual cost 1749.99 selects the current cost tier." @@ -1716,7 +1717,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 1_095 + nh_fap: 1_095 # Manual p77 exact ECOS ranges control the overlapping printed utility labels on p78. - name: "Case 65, delivered annual cost 1750 selects the current cost tier." @@ -1742,7 +1743,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 2_350 + nh_fap: 2_350 # Manual pp77-78 exact cost ranges and utility schedule. - name: "Case 66, utility annual cost 1750 selects the current cost tier." @@ -1768,7 +1769,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 1_763 + nh_fap: 1_763 # Manual p77 exact ECOS ranges control the overlapping printed utility labels on p78. - name: "Case 67, delivered annual cost 3499.99 selects the current cost tier." @@ -1794,7 +1795,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 2_350 + nh_fap: 2_350 # Manual pp77-78 exact cost ranges and utility schedule. - name: "Case 68, utility annual cost 3499.99 selects the current cost tier." @@ -1820,7 +1821,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 1_763 + nh_fap: 1_763 # Manual p77 exact ECOS ranges control the overlapping printed utility labels on p78. - name: "Case 69, delivered annual cost 3500 selects the current cost tier." @@ -1846,7 +1847,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 2_500 + nh_fap: 2_500 # Manual pp77-78 exact cost ranges and utility schedule. - name: "Case 70, utility annual cost 3500 selects the current cost tier." @@ -1872,7 +1873,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 1_875 + nh_fap: 1_875 # Manual p77 multiplier 1.000 and p78 BB/F delivered benefit. - name: "Case 71, kerosene uses the full delivered-fuel schedule." @@ -1898,7 +1899,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 800 + nh_fap: 800 # Manual p77 multiplier 1.000 and p78 BB/F delivered benefit. - name: "Case 72, propane uses the full delivered-fuel schedule." @@ -1924,7 +1925,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 800 + nh_fap: 800 # Manual p77 multiplier 1.000 and p78 BB/F delivered benefit. - name: "Case 73, wood uses the full delivered-fuel schedule." @@ -1950,7 +1951,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 800 + nh_fap: 800 # Manual p77 multiplier 1.000 and p78 BB/F delivered benefit. - name: "Case 74, coal uses the full delivered-fuel schedule." @@ -1976,7 +1977,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 800 + nh_fap: 800 # Manual p78 DD/A amount is750; no actual-cost cap is stated. - name: "Case 75, actual-cost schedule is not capped at a small positive bill." @@ -2002,7 +2003,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 750 + nh_fap: 750 # Manual p10 provides application priority; pp77-78 contain no age supplement. - name: "Case 76, an older adult receives the same regular schedule." @@ -2028,7 +2029,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 2_150 + nh_fap: 2_150 # Manual p10 provides application priority; pp77-78 contain no disability supplement. - name: "Case 77, disability does not add an unsourced regular supplement." @@ -2055,7 +2056,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 2_150 + nh_fap: 2_150 # Manual p74: size 2 D band ends at 32,460; p78 AA/D is 1500. - name: "Case 78, two qualified members use the published two-person FPG." @@ -2088,7 +2089,7 @@ output: nh_fap_household_size: 2 nh_fap_countable_income: 30_000 - nh_fap_base_benefit: 1_500 + nh_fap: 1_500 # Manual p36 includes ineligible-member income but excludes their size; size 1 band F applies without income proration. - name: "Case 79, mixed status counts both incomes but only the qualified person." @@ -2121,7 +2122,7 @@ output: nh_fap_household_size: 1 nh_fap_countable_income: 30_000 - nh_fap_base_benefit: 825 + nh_fap: 825 # Manual p74 size 1 ceiling 48,713; p78 AA/F is 825. - name: "Case 80, the inclusive income ceiling retains the base schedule." @@ -2147,7 +2148,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 825 + nh_fap: 825 # Manual p74 size 1 maximum 48,713. - name: "Case 81, income above the ceiling removes the scheduled benefit." @@ -2173,9 +2174,9 @@ members: ["person1"] state_code: "NH" output: - nh_fap_base_benefit: 0 + nh_fap: 0 -# Manual pp76-78 formula and matrix with four bedrooms alone: cost 2272.564 selects BB, income 45120 selects F, yielding 800 before any heat-in-rent adjustment. +# Manual pp76-78 formula and matrix with four bedrooms alone: cost 2272.564 selects BB, income 45120 selects F, yielding a modeled award of 800. - name: "Case 82, bedrooms alone determine the two-person oil benefit cost band." period: 2027 absolute_error_margin: 0.01 @@ -2208,7 +2209,7 @@ output: nh_fap_heating_cost: 2_272.564431 nh_fap_countable_income: 45_120 - nh_fap_base_benefit: 800 + nh_fap: 800 # Manual pp18,77-78: two bedrooms alone give estimated cost 1136.282, selecting CC/A 1460; actual 4000 selects AA/A 2500. The 500 bill does not cap the benefit. - name: "Case 83, the larger cost basis determines the delivered benefit." @@ -2256,7 +2257,7 @@ bedrooms: 2 output: nh_fap_heating_cost: [1_136.282215, 4_000] - nh_fap_base_benefit: [1_460, 2_500] + nh_fap: [1_460, 2_500] # Manual pp77-78: two-bedroom Coos gas cost 561.366 selects DD/A 750 * .75 =562.50, rounded to 563; actual 4000 selects AA/A 2500 * .75 =1875. - name: "Case 84, utility reduction follows the selected proxy or actual cost band." @@ -2304,4 +2305,188 @@ bedrooms: 2 output: nh_fap_heating_cost: [561.365854, 4_000] - nh_fap_base_benefit: [563, 1_875] + nh_fap: [563, 1_875] + +# Approved heat-in-rent assumption; this is not an agency-confirmed rent reduction. +# Manual pp76-78: default single-family factor 2000, Belknap HDD 6610, +# 3 bedrooms used as rooms, electricity 64.706/MBTU at efficiency 1. +# Estimated cost = 2000 * 3 * 6610 / 1_000_000 * 64.706 = 2566.23996. +# Income band A and cost band BB: 2350 * 0.75, rounded half up = 1763. +- name: Case 85, unsubsidized heat-in-rent with three bedrooms and electricity receives the modeled final award. + period: 2027 + absolute_error_margin: 0.01 + input: + people: + parent1: + age: 40 + immigration_status: CITIZEN + employment_income: 10_000 + ssi: 0 + parent2: + age: 38 + immigration_status: CITIZEN + ssi: 0 + child: + age: 8 + immigration_status: CITIZEN + ssi: 0 + spm_units: + family: + members: [parent1, parent2, child] + nh_fanf: 0 + heating_expense: 0 + heating_type: ELECTRICITY + heat_expense_included_in_rent: true + receives_housing_assistance: false + households: + home: + members: [parent1, parent2, child] + state_code: NH + bedrooms: 3 + output: + nh_fap_countable_income: 10_000 + nh_fap_heating_cost: 2_566.23996 + nh_fap_eligible: true + nh_fap: 1_763 + +# Manual p78 AA/A: delivered 2500; electricity and gas 2500 * 0.75 = 1875. +# Approved assumption applies that fuel schedule once to direct-pay and heat-in-rent. +# Manual p21 excludes subsidized heat-in-rent, not subsidized direct-pay households. +- name: Case 86, heat-in-rent retains each fuel schedule while subsidized heat-in-rent is excluded. + period: 2027 + absolute_error_margin: 0.01 + input: + people: + electric_direct: + age: 40 + immigration_status: CITIZEN + employment_income: 10_000 + ssi: 0 + electric_rent: + age: 40 + immigration_status: CITIZEN + employment_income: 10_000 + ssi: 0 + gas_direct: + age: 40 + immigration_status: CITIZEN + employment_income: 10_000 + ssi: 0 + gas_rent: + age: 40 + immigration_status: CITIZEN + employment_income: 10_000 + ssi: 0 + oil_direct: + age: 40 + immigration_status: CITIZEN + employment_income: 10_000 + ssi: 0 + oil_rent: + age: 40 + immigration_status: CITIZEN + employment_income: 10_000 + ssi: 0 + subsidized_rent: + age: 40 + immigration_status: CITIZEN + employment_income: 10_000 + ssi: 0 + subsidized_direct: + age: 40 + immigration_status: CITIZEN + employment_income: 10_000 + ssi: 0 + spm_units: + electric_direct: + members: [electric_direct] + nh_fanf: 0 + heating_expense: 4_000 + heating_type: ELECTRICITY + heat_expense_included_in_rent: false + receives_housing_assistance: false + electric_rent: + members: [electric_rent] + nh_fanf: 0 + heating_expense: 4_000 + heating_type: ELECTRICITY + heat_expense_included_in_rent: true + receives_housing_assistance: false + gas_direct: + members: [gas_direct] + nh_fanf: 0 + heating_expense: 4_000 + heating_type: NATURAL_GAS + heat_expense_included_in_rent: false + receives_housing_assistance: false + gas_rent: + members: [gas_rent] + nh_fanf: 0 + heating_expense: 4_000 + heating_type: NATURAL_GAS + heat_expense_included_in_rent: true + receives_housing_assistance: false + oil_direct: + members: [oil_direct] + nh_fanf: 0 + heating_expense: 4_000 + heating_type: FUEL_OIL + heat_expense_included_in_rent: false + receives_housing_assistance: false + oil_rent: + members: [oil_rent] + nh_fanf: 0 + heating_expense: 4_000 + heating_type: FUEL_OIL + heat_expense_included_in_rent: true + receives_housing_assistance: false + subsidized_rent: + members: [subsidized_rent] + nh_fanf: 0 + heating_expense: 4_000 + heating_type: ELECTRICITY + heat_expense_included_in_rent: true + receives_housing_assistance: true + subsidized_direct: + members: [subsidized_direct] + nh_fanf: 0 + heating_expense: 4_000 + heating_type: ELECTRICITY + heat_expense_included_in_rent: false + receives_housing_assistance: true + households: + electric_direct: + members: [electric_direct] + state_code: NH + bedrooms: 3 + electric_rent: + members: [electric_rent] + state_code: NH + bedrooms: 3 + gas_direct: + members: [gas_direct] + state_code: NH + bedrooms: 3 + gas_rent: + members: [gas_rent] + state_code: NH + bedrooms: 3 + oil_direct: + members: [oil_direct] + state_code: NH + bedrooms: 3 + oil_rent: + members: [oil_rent] + state_code: NH + bedrooms: 3 + subsidized_rent: + members: [subsidized_rent] + state_code: NH + bedrooms: 3 + subsidized_direct: + members: [subsidized_direct] + state_code: NH + bedrooms: 3 + output: + nh_fap_eligible: [true, true, true, true, true, true, false, true] + nh_fap: [1_875, 1_875, 1_875, 1_875, 2_500, 2_500, 0, 1_875] diff --git a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml index 9256f52c47f..d6cb6be8c46 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml @@ -1136,4 +1136,4 @@ output: nh_fap_countable_income: 48_738 nh_fap_eligible: false - nh_fap_base_benefit: 0 + nh_fap: 0 diff --git a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_base_benefit.py b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap.py similarity index 61% rename from policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_base_benefit.py rename to policyengine_us/variables/gov/states/nh/doe/fap/nh_fap.py index 4c252a6a624..f0093553e4a 100644 --- a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_base_benefit.py +++ b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap.py @@ -2,14 +2,28 @@ from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg -class nh_fap_base_benefit(Variable): +class nh_fap(Variable): value_type = float entity = SPMUnit definition_period = YEAR unit = USD - label = "New Hampshire FAP scheduled benefit before heat-in-rent adjustments" - documentation = "Scheduled intermediate using actual or bedroom-proxy heating cost before any heat-in-rent adjustment, not the final FAP award. The room proxy is a modeling approximation. Verified against the PY2027 schedule; earlier results are unverified historical estimates. Zero can mean that heating cost or fuel classification is unsupported, rather than legal ineligibility." - reference = "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=10, 17, 74, 76, 77, 78" + label = "New Hampshire Fuel Assistance Program benefit" + documentation = ( + "Modeled regular heating award using actual or bedroom-proxy heating cost. " + "For unsubsidized heat included in rent, the model uses the ordinary fuel " + "schedule without an additional reduction. This approved modeling assumption " + "may overestimate awards: the state plan describes reduced heat-in-rent " + "benefits but does not specify the calculation. Bedrooms alone approximate " + "the program's room count. Verified against the PY2027 schedule; earlier " + "results are unverified historical estimates. Zero can mean unsupported " + "heating cost or fuel classification rather than legal ineligibility." + ) + reference = ( + # PY2027 manual, pages 10, 17-21, 57, 74, 76-78. + "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=78", + # FY2026 plan, section 2.3: heat-in-rent reduction remains unquantified. + "https://liheapch.acf.gov/docs/2026/state-plans/NH_Plan_2026.pdf#page=8", + ) defined_for = "nh_fap_eligible" def formula(spm_unit, period, parameters): @@ -57,8 +71,10 @@ def formula(spm_unit, period, parameters): multiplier = where(utility, p.payment.utility_multiplier, 1) # Half-up whole dollars reproduce every utility matrix cell. benefit = np.floor(delivered_benefit * multiplier + 0.5) - # No heat-in-rent adjustment is inferred. Unknown, solar and other - # fuels have no supported payment classification; zero marks an - # unsupported amount. + # Approved approximation: unsubsidized heat-in-rent receives this + # scheduled amount, including the utility multiplier exactly once. + # The state's heat-in-rent reduction remains unverified. + # Unknown, solar and other fuels have no supported payment + # classification; zero marks an unsupported amount. # The schedule is not capped at the household's actual heating cost. return where((expense > 0) & (utility | delivered), benefit, 0) diff --git a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_eligible.py b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_eligible.py index c9e017ab2d9..43268540399 100644 --- a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_eligible.py +++ b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_eligible.py @@ -6,7 +6,8 @@ class nh_fap_eligible(Variable): entity = SPMUnit definition_period = YEAR label = "Eligible for New Hampshire FAP" - reference = "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=10, 11, 12, 17, 18, 21, 34, 35, 36, 57, 74, 76, 77" + # PY2027 manual, pages 10-12, 17-18, 21, 34-36, 57, 74, 76-77. + reference = "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=21" defined_for = StateCode.NH def formula(spm_unit, period, parameters): @@ -19,7 +20,7 @@ def formula(spm_unit, period, parameters): heating_type = spm_unit("heating_type", period) fuel = heating_type.possible_values # Subsidized heat-in-rent is excluded. Nonsubsidized heat-in-rent can - # qualify even when its actual cost or payment adjustment is unknown. + # qualify without a separately reported heating bill. housing_eligible = ~(heat_in_rent & subsidized) heating_eligible = (heating_cost > 0) | heat_in_rent return ( From cfc48b84ccd0172e94a8beff2c6a84ad43a53f9d Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 20 Oct 2026 20:13:02 -0400 Subject: [PATCH 08/14] Normalize New Hampshire FAP reference page formatting --- .../parameters/gov/states/nh/doe/fap/fpg_year_lag.yaml | 4 ++-- .../gov/states/nh/doe/fap/heating_cost/dwelling_factor.yaml | 4 ++-- .../gov/states/nh/doe/fap/income/business_sources.yaml | 4 ++-- .../gov/states/nh/doe/fap/income/investment_disregard.yaml | 2 +- .../parameters/gov/states/nh/doe/fap/income/ssi_min_age.yaml | 4 ++-- .../gov/states/nh/doe/fap/income/unearned_sources.yaml | 4 ++-- .../gov/states/nh/doe/fap/payment/heating_cost_threshold.yaml | 4 ++-- .../states/nh/doe/fap/payment/income_band_upper_bound.yaml | 4 ++-- .../gov/states/nh/doe/fap/payment/utility_multiplier.yaml | 4 ++-- .../parameters/gov/states/nh/doe/fap/smi_rate.yaml | 4 ++-- .../gov/states/nh/doe/fap/nh_fap_countable_income.py | 3 ++- .../variables/gov/states/nh/doe/fap/nh_fap_household_size.py | 3 ++- .../variables/gov/states/nh/doe/fap/nh_fap_income_limit.py | 3 ++- 13 files changed, 25 insertions(+), 22 deletions(-) diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/fpg_year_lag.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/fpg_year_lag.yaml index cbd3d3aedd7..82dbc588604 100644 --- a/policyengine_us/parameters/gov/states/nh/doe/fap/fpg_year_lag.yaml +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/fpg_year_lag.yaml @@ -9,5 +9,5 @@ metadata: period: year label: New Hampshire FAP federal poverty guideline year lag reference: - - title: PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Income Guidelines and Federal Poverty Guideline Updates - href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=10, 17, 74 + - title: "PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Income Guidelines and Federal Poverty Guideline Updates #page 10,17,74" + href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=10 diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/dwelling_factor.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/dwelling_factor.yaml index 2899599f68c..0a063515fde 100644 --- a/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/dwelling_factor.yaml +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/dwelling_factor.yaml @@ -13,5 +13,5 @@ metadata: breakdown: - nh_fap_dwelling_type reference: - - title: PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Benefit Determination - href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=76, 77 + - title: "PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Benefit Determination #page 76-77" + href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=76 diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/income/business_sources.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/income/business_sources.yaml index e2c59074ecd..07e3289fa2c 100644 --- a/policyengine_us/parameters/gov/states/nh/doe/fap/income/business_sources.yaml +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/income/business_sources.yaml @@ -12,5 +12,5 @@ metadata: period: year label: New Hampshire FAP business income sources reference: - - title: PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Self Employment - href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=25, 26, 27, 28 + - title: "PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Self Employment #page 25-28" + href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=25 diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/income/investment_disregard.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/income/investment_disregard.yaml index c7d18b0bc11..d3eed690985 100644 --- a/policyengine_us/parameters/gov/states/nh/doe/fap/income/investment_disregard.yaml +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/income/investment_disregard.yaml @@ -14,5 +14,5 @@ metadata: period: year label: New Hampshire FAP annual investment income exclusion per SPM unit reference: - - title: "PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Countable and Non-countable Income and Allowable Expenses #page 15,16,17" + - title: "PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Countable and Non-countable Income and Allowable Expenses #page 15-17" href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=15 diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/income/ssi_min_age.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/income/ssi_min_age.yaml index 4662f4e19b7..55441f99478 100644 --- a/policyengine_us/parameters/gov/states/nh/doe/fap/income/ssi_min_age.yaml +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/income/ssi_min_age.yaml @@ -7,5 +7,5 @@ metadata: period: year label: New Hampshire FAP minimum age for countable SSI reference: - - title: PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Non-countable Income - href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=15, 16 + - title: "PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Non-countable Income #page 15-16" + href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=15 diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/income/unearned_sources.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/income/unearned_sources.yaml index 99adc5f9ba3..f8774114db7 100644 --- a/policyengine_us/parameters/gov/states/nh/doe/fap/income/unearned_sources.yaml +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/income/unearned_sources.yaml @@ -35,5 +35,5 @@ metadata: period: year label: New Hampshire FAP unearned income sources reference: - - title: PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Countable Income, Non-countable Income, and Rental Property Income - href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=15, 16, 17, 23, 29 + - title: "PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Countable Income, Non-countable Income, and Rental Property Income #page 15-17,23,29" + href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=15 diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/payment/heating_cost_threshold.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/payment/heating_cost_threshold.yaml index f2c6dfb144d..a5d9f10c01f 100644 --- a/policyengine_us/parameters/gov/states/nh/doe/fap/payment/heating_cost_threshold.yaml +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/payment/heating_cost_threshold.yaml @@ -13,5 +13,5 @@ metadata: period: year label: New Hampshire FAP heating cost band thresholds reference: - - title: PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Benefit Determination and Benefits - href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=76, 78 + - title: "PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Benefit Determination and Benefits #page 76,78" + href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=76 diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/payment/income_band_upper_bound.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/payment/income_band_upper_bound.yaml index dbdd4c2e5e5..63939fc27ae 100644 --- a/policyengine_us/parameters/gov/states/nh/doe/fap/payment/income_band_upper_bound.yaml +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/payment/income_band_upper_bound.yaml @@ -16,5 +16,5 @@ metadata: period: year label: New Hampshire FAP income band upper bounds reference: - - title: PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Income Guidelines - href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=17, 74 + - title: "PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Income Guidelines #page 17,74" + href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=17 diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/payment/utility_multiplier.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/payment/utility_multiplier.yaml index 7d6d5600cb1..e3474117b8e 100644 --- a/policyengine_us/parameters/gov/states/nh/doe/fap/payment/utility_multiplier.yaml +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/payment/utility_multiplier.yaml @@ -8,5 +8,5 @@ metadata: period: year label: New Hampshire FAP utility benefit multiplier reference: - - title: PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Benefit Determination and Utility Benefits - href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=76, 77, 78 + - title: "PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Benefit Determination and Utility Benefits #page 76-78" + href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=76 diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/smi_rate.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/smi_rate.yaml index 926cb63390d..8ec5067f335 100644 --- a/policyengine_us/parameters/gov/states/nh/doe/fap/smi_rate.yaml +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/smi_rate.yaml @@ -7,5 +7,5 @@ metadata: period: year label: New Hampshire FAP state median income limit rate reference: - - title: PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Income Guidelines - href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=17, 74 + - title: "PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Income Guidelines #page 17,74" + href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=17 diff --git a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py index f96eec19935..5f38655b833 100644 --- a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py +++ b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py @@ -7,7 +7,8 @@ class nh_fap_countable_income(Variable): definition_period = YEAR unit = USD label = "New Hampshire FAP countable income" - reference = "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=13, 14, 15, 16, 17, 23, 25, 26, 27, 28, 29, 34, 35, 36" + # PDF pages 13-17,23,25-29,34-36. + reference = "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=13" defined_for = StateCode.NH def formula(spm_unit, period, parameters): diff --git a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_household_size.py b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_household_size.py index 1d17d88eb48..661f9d01af9 100644 --- a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_household_size.py +++ b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_household_size.py @@ -7,7 +7,8 @@ class nh_fap_household_size(Variable): definition_period = YEAR label = "New Hampshire FAP household size" documentation = "Number of citizens and qualified immigrants in the SPM unit. All members' otherwise countable income remains included." - reference = "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=11, 12, 34, 35, 36" + # PDF pages 11-12,34-36. + reference = "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=11" defined_for = StateCode.NH # SPM composition approximates the shared residence and economic unit. diff --git a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_income_limit.py b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_income_limit.py index 56d914a4e6d..808dcf1525b 100644 --- a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_income_limit.py +++ b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_income_limit.py @@ -8,7 +8,8 @@ class nh_fap_income_limit(Variable): definition_period = YEAR unit = USD label = "New Hampshire FAP income limit" - reference = "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=17, 74" + # PDF pages 17,74. + reference = "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=17" defined_for = StateCode.NH def formula(spm_unit, period, parameters): From e9eff3be95f64beeb339ef41d21eafd9ed6bbf1b Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 20 Oct 2026 20:44:54 -0400 Subject: [PATCH 09/14] Fix New Hampshire FAP income and eligibility --- changelog.d/nh-liheap-review-fixes.fixed.md | 1 + .../nh/doe/fap/income/unearned_sources.yaml | 3 +- .../gov/states/nh/doe/fap/nh_fap.yaml | 387 ++++++++++++++++++ .../variables/gov/states/nh/doe/fap/nh_fap.py | 4 +- .../nh/doe/fap/nh_fap_countable_income.py | 4 +- .../gov/states/nh/doe/fap/nh_fap_eligible.py | 17 +- .../states/nh/doe/fap/nh_fap_heating_cost.py | 2 +- 7 files changed, 409 insertions(+), 9 deletions(-) create mode 100644 changelog.d/nh-liheap-review-fixes.fixed.md diff --git a/changelog.d/nh-liheap-review-fixes.fixed.md b/changelog.d/nh-liheap-review-fixes.fixed.md new file mode 100644 index 00000000000..cbdafd0e252 --- /dev/null +++ b/changelog.d/nh-liheap-review-fixes.fixed.md @@ -0,0 +1 @@ +Fix New Hampshire FAP income sources, TANF receipt treatment and the federal poverty-floor eligibility protection. diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/income/unearned_sources.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/income/unearned_sources.yaml index f8774114db7..1cf2e885a3e 100644 --- a/policyengine_us/parameters/gov/states/nh/doe/fap/income/unearned_sources.yaml +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/income/unearned_sources.yaml @@ -3,11 +3,12 @@ values: 2026-10-01: - social_security - pension_income + - military_retirement_pay - retirement_distributions - railroad_benefits - survivor_benefits - veterans_benefits - - unemployment_compensation + - total_unemployment_compensation - workers_compensation - disability_benefits - general_assistance diff --git a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap.yaml b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap.yaml index afc0d2cb028..736b35251b4 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap.yaml @@ -2490,3 +2490,390 @@ output: nh_fap_eligible: [true, true, true, true, true, true, false, true] nh_fap: [1_875, 1_875, 1_875, 1_875, 2_500, 2_500, 0, 1_875] + +# Manual page 15 counts ordinary DoD retirement separately from pension/VA income. +# Primary pay of 50,000 exceeds the one-person 48,713 ceiling. The 10,000 control +# remains in income band A; a 500 electric bill uses DD/A: 750 * 0.75 -> 563. +- name: "Case 87, ordinary military retirement pay affects eligibility and retains eligible retirees." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + is_veteran: true + years_in_military: 20 + military_retirement_pay: 50_000 + pension_income: 0 + veterans_benefits: 0 + person2: + age: 40 + immigration_status: CITIZEN + is_veteran: true + years_in_military: 20 + military_retirement_pay: 10_000 + pension_income: 0 + veterans_benefits: 0 + tax_units: + tax_unit1: + members: [person1] + tax_unit2: + members: [person2] + spm_units: + spm_unit1: + members: [person1] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 500 + spm_unit2: + members: [person2] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 500 + households: + household1: + members: [person1] + state_code: NH + bedrooms: 0 + household2: + members: [person2] + state_code: NH + bedrooms: 0 + output: + nh_fap_countable_income: [50_000, 10_000] + nh_fap_income_limit: [48_713, 48_713] + nh_fap_eligible: [false, true] + nh_fap: [0, 563] + +# Manual pages 13,15-16 count received welfare and adult SSI. The first family +# does not take up TANF, so only SSI of 9,600 counts. The second family reports +# actual TANF receipts of 3,600, counted once: 9,600 + 3,600 = 13,200. +# Both are below the two-person A ceiling of 16,230, receiving AA/A = 2,500. +# No counterfactual FANF entitlement is supplied or asserted as a legal award. +- name: "Case 88, actual TANF receipt counts once and nonreceipt does not reduce the heating award." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: CITIZEN + is_disabled: true + ssi: 9_600 + person2: + age: 10 + immigration_status: CITIZEN + person3: + age: 40 + immigration_status: CITIZEN + is_disabled: true + ssi: 9_600 + person4: + age: 10 + immigration_status: CITIZEN + tax_units: + tax_unit1: + members: [person1, person2] + tax_unit2: + members: [person3, person4] + spm_units: + spm_unit1: + members: [person1, person2] + takes_up_tanf_if_eligible: false + heating_type: FUEL_OIL + fuel_oil_expense: 4_000 + spm_unit2: + members: [person3, person4] + tanf: 3_600 + heating_type: FUEL_OIL + fuel_oil_expense: 4_000 + households: + household1: + members: [person1, person2] + state_code: NH + bedrooms: 0 + household2: + members: [person3, person4] + state_code: NH + bedrooms: 0 + output: + tanf: [0, 3_600] + nh_fap_countable_income: [9_600, 13_200] + nh_fap_eligible: [true, true] + nh_fap: [2_500, 2_500] + +# 42 USC 8624(b)(2)(B) protects income strictly below 110% FPG. PY2027 uses +# 2026 guidelines: 15,960 + 27 * 5,680 = 169,320; the floor is 186,252. +# Each economic household has 28 adult relatives with separate tax units. +# Income 186,000 and a cent below the floor qualify despite SMI of 185,486; +# equality and a cent above do not. Protected income uses band C and a 1,000 +# electric bill uses cost band CC: 1,150 * 0.75 = 862.50 -> 863. +- name: "Case 89, the federal floor protects large households strictly below the season-start threshold." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: {age: 40, immigration_status: CITIZEN, employment_income: 186_000} + person2: {age: 40, immigration_status: CITIZEN} + person3: {age: 40, immigration_status: CITIZEN} + person4: {age: 40, immigration_status: CITIZEN} + person5: {age: 40, immigration_status: CITIZEN} + person6: {age: 40, immigration_status: CITIZEN} + person7: {age: 40, immigration_status: CITIZEN} + person8: {age: 40, immigration_status: CITIZEN} + person9: {age: 40, immigration_status: CITIZEN} + person10: {age: 40, immigration_status: CITIZEN} + person11: {age: 40, immigration_status: CITIZEN} + person12: {age: 40, immigration_status: CITIZEN} + person13: {age: 40, immigration_status: CITIZEN} + person14: {age: 40, immigration_status: CITIZEN} + person15: {age: 40, immigration_status: CITIZEN} + person16: {age: 40, immigration_status: CITIZEN} + person17: {age: 40, immigration_status: CITIZEN} + person18: {age: 40, immigration_status: CITIZEN} + person19: {age: 40, immigration_status: CITIZEN} + person20: {age: 40, immigration_status: CITIZEN} + person21: {age: 40, immigration_status: CITIZEN} + person22: {age: 40, immigration_status: CITIZEN} + person23: {age: 40, immigration_status: CITIZEN} + person24: {age: 40, immigration_status: CITIZEN} + person25: {age: 40, immigration_status: CITIZEN} + person26: {age: 40, immigration_status: CITIZEN} + person27: {age: 40, immigration_status: CITIZEN} + person28: {age: 40, immigration_status: CITIZEN} + person29: {age: 40, immigration_status: CITIZEN, employment_income: 186_251.99} + person30: {age: 40, immigration_status: CITIZEN} + person31: {age: 40, immigration_status: CITIZEN} + person32: {age: 40, immigration_status: CITIZEN} + person33: {age: 40, immigration_status: CITIZEN} + person34: {age: 40, immigration_status: CITIZEN} + person35: {age: 40, immigration_status: CITIZEN} + person36: {age: 40, immigration_status: CITIZEN} + person37: {age: 40, immigration_status: CITIZEN} + person38: {age: 40, immigration_status: CITIZEN} + person39: {age: 40, immigration_status: CITIZEN} + person40: {age: 40, immigration_status: CITIZEN} + person41: {age: 40, immigration_status: CITIZEN} + person42: {age: 40, immigration_status: CITIZEN} + person43: {age: 40, immigration_status: CITIZEN} + person44: {age: 40, immigration_status: CITIZEN} + person45: {age: 40, immigration_status: CITIZEN} + person46: {age: 40, immigration_status: CITIZEN} + person47: {age: 40, immigration_status: CITIZEN} + person48: {age: 40, immigration_status: CITIZEN} + person49: {age: 40, immigration_status: CITIZEN} + person50: {age: 40, immigration_status: CITIZEN} + person51: {age: 40, immigration_status: CITIZEN} + person52: {age: 40, immigration_status: CITIZEN} + person53: {age: 40, immigration_status: CITIZEN} + person54: {age: 40, immigration_status: CITIZEN} + person55: {age: 40, immigration_status: CITIZEN} + person56: {age: 40, immigration_status: CITIZEN} + person57: {age: 40, immigration_status: CITIZEN, employment_income: 186_252} + person58: {age: 40, immigration_status: CITIZEN} + person59: {age: 40, immigration_status: CITIZEN} + person60: {age: 40, immigration_status: CITIZEN} + person61: {age: 40, immigration_status: CITIZEN} + person62: {age: 40, immigration_status: CITIZEN} + person63: {age: 40, immigration_status: CITIZEN} + person64: {age: 40, immigration_status: CITIZEN} + person65: {age: 40, immigration_status: CITIZEN} + person66: {age: 40, immigration_status: CITIZEN} + person67: {age: 40, immigration_status: CITIZEN} + person68: {age: 40, immigration_status: CITIZEN} + person69: {age: 40, immigration_status: CITIZEN} + person70: {age: 40, immigration_status: CITIZEN} + person71: {age: 40, immigration_status: CITIZEN} + person72: {age: 40, immigration_status: CITIZEN} + person73: {age: 40, immigration_status: CITIZEN} + person74: {age: 40, immigration_status: CITIZEN} + person75: {age: 40, immigration_status: CITIZEN} + person76: {age: 40, immigration_status: CITIZEN} + person77: {age: 40, immigration_status: CITIZEN} + person78: {age: 40, immigration_status: CITIZEN} + person79: {age: 40, immigration_status: CITIZEN} + person80: {age: 40, immigration_status: CITIZEN} + person81: {age: 40, immigration_status: CITIZEN} + person82: {age: 40, immigration_status: CITIZEN} + person83: {age: 40, immigration_status: CITIZEN} + person84: {age: 40, immigration_status: CITIZEN} + person85: {age: 40, immigration_status: CITIZEN, employment_income: 186_252.01} + person86: {age: 40, immigration_status: CITIZEN} + person87: {age: 40, immigration_status: CITIZEN} + person88: {age: 40, immigration_status: CITIZEN} + person89: {age: 40, immigration_status: CITIZEN} + person90: {age: 40, immigration_status: CITIZEN} + person91: {age: 40, immigration_status: CITIZEN} + person92: {age: 40, immigration_status: CITIZEN} + person93: {age: 40, immigration_status: CITIZEN} + person94: {age: 40, immigration_status: CITIZEN} + person95: {age: 40, immigration_status: CITIZEN} + person96: {age: 40, immigration_status: CITIZEN} + person97: {age: 40, immigration_status: CITIZEN} + person98: {age: 40, immigration_status: CITIZEN} + person99: {age: 40, immigration_status: CITIZEN} + person100: {age: 40, immigration_status: CITIZEN} + person101: {age: 40, immigration_status: CITIZEN} + person102: {age: 40, immigration_status: CITIZEN} + person103: {age: 40, immigration_status: CITIZEN} + person104: {age: 40, immigration_status: CITIZEN} + person105: {age: 40, immigration_status: CITIZEN} + person106: {age: 40, immigration_status: CITIZEN} + person107: {age: 40, immigration_status: CITIZEN} + person108: {age: 40, immigration_status: CITIZEN} + person109: {age: 40, immigration_status: CITIZEN} + person110: {age: 40, immigration_status: CITIZEN} + person111: {age: 40, immigration_status: CITIZEN} + person112: {age: 40, immigration_status: CITIZEN} + tax_units: + tax_unit1: {members: [person1]} + tax_unit2: {members: [person2]} + tax_unit3: {members: [person3]} + tax_unit4: {members: [person4]} + tax_unit5: {members: [person5]} + tax_unit6: {members: [person6]} + tax_unit7: {members: [person7]} + tax_unit8: {members: [person8]} + tax_unit9: {members: [person9]} + tax_unit10: {members: [person10]} + tax_unit11: {members: [person11]} + tax_unit12: {members: [person12]} + tax_unit13: {members: [person13]} + tax_unit14: {members: [person14]} + tax_unit15: {members: [person15]} + tax_unit16: {members: [person16]} + tax_unit17: {members: [person17]} + tax_unit18: {members: [person18]} + tax_unit19: {members: [person19]} + tax_unit20: {members: [person20]} + tax_unit21: {members: [person21]} + tax_unit22: {members: [person22]} + tax_unit23: {members: [person23]} + tax_unit24: {members: [person24]} + tax_unit25: {members: [person25]} + tax_unit26: {members: [person26]} + tax_unit27: {members: [person27]} + tax_unit28: {members: [person28]} + tax_unit29: {members: [person29]} + tax_unit30: {members: [person30]} + tax_unit31: {members: [person31]} + tax_unit32: {members: [person32]} + tax_unit33: {members: [person33]} + tax_unit34: {members: [person34]} + tax_unit35: {members: [person35]} + tax_unit36: {members: [person36]} + tax_unit37: {members: [person37]} + tax_unit38: {members: [person38]} + tax_unit39: {members: [person39]} + tax_unit40: {members: [person40]} + tax_unit41: {members: [person41]} + tax_unit42: {members: [person42]} + tax_unit43: {members: [person43]} + tax_unit44: {members: [person44]} + tax_unit45: {members: [person45]} + tax_unit46: {members: [person46]} + tax_unit47: {members: [person47]} + tax_unit48: {members: [person48]} + tax_unit49: {members: [person49]} + tax_unit50: {members: [person50]} + tax_unit51: {members: [person51]} + tax_unit52: {members: [person52]} + tax_unit53: {members: [person53]} + tax_unit54: {members: [person54]} + tax_unit55: {members: [person55]} + tax_unit56: {members: [person56]} + tax_unit57: {members: [person57]} + tax_unit58: {members: [person58]} + tax_unit59: {members: [person59]} + tax_unit60: {members: [person60]} + tax_unit61: {members: [person61]} + tax_unit62: {members: [person62]} + tax_unit63: {members: [person63]} + tax_unit64: {members: [person64]} + tax_unit65: {members: [person65]} + tax_unit66: {members: [person66]} + tax_unit67: {members: [person67]} + tax_unit68: {members: [person68]} + tax_unit69: {members: [person69]} + tax_unit70: {members: [person70]} + tax_unit71: {members: [person71]} + tax_unit72: {members: [person72]} + tax_unit73: {members: [person73]} + tax_unit74: {members: [person74]} + tax_unit75: {members: [person75]} + tax_unit76: {members: [person76]} + tax_unit77: {members: [person77]} + tax_unit78: {members: [person78]} + tax_unit79: {members: [person79]} + tax_unit80: {members: [person80]} + tax_unit81: {members: [person81]} + tax_unit82: {members: [person82]} + tax_unit83: {members: [person83]} + tax_unit84: {members: [person84]} + tax_unit85: {members: [person85]} + tax_unit86: {members: [person86]} + tax_unit87: {members: [person87]} + tax_unit88: {members: [person88]} + tax_unit89: {members: [person89]} + tax_unit90: {members: [person90]} + tax_unit91: {members: [person91]} + tax_unit92: {members: [person92]} + tax_unit93: {members: [person93]} + tax_unit94: {members: [person94]} + tax_unit95: {members: [person95]} + tax_unit96: {members: [person96]} + tax_unit97: {members: [person97]} + tax_unit98: {members: [person98]} + tax_unit99: {members: [person99]} + tax_unit100: {members: [person100]} + tax_unit101: {members: [person101]} + tax_unit102: {members: [person102]} + tax_unit103: {members: [person103]} + tax_unit104: {members: [person104]} + tax_unit105: {members: [person105]} + tax_unit106: {members: [person106]} + tax_unit107: {members: [person107]} + tax_unit108: {members: [person108]} + tax_unit109: {members: [person109]} + tax_unit110: {members: [person110]} + tax_unit111: {members: [person111]} + tax_unit112: {members: [person112]} + spm_units: + spm_unit1: + members: [person1, person2, person3, person4, person5, person6, person7, person8, person9, person10, person11, person12, person13, person14, person15, person16, person17, person18, person19, person20, person21, person22, person23, person24, person25, person26, person27, person28] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + spm_unit2: + members: [person29, person30, person31, person32, person33, person34, person35, person36, person37, person38, person39, person40, person41, person42, person43, person44, person45, person46, person47, person48, person49, person50, person51, person52, person53, person54, person55, person56] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + spm_unit3: + members: [person57, person58, person59, person60, person61, person62, person63, person64, person65, person66, person67, person68, person69, person70, person71, person72, person73, person74, person75, person76, person77, person78, person79, person80, person81, person82, person83, person84] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + spm_unit4: + members: [person85, person86, person87, person88, person89, person90, person91, person92, person93, person94, person95, person96, person97, person98, person99, person100, person101, person102, person103, person104, person105, person106, person107, person108, person109, person110, person111, person112] + heating_type: ELECTRICITY + pre_subsidy_electricity_expense: 1_000 + households: + household1: + members: [person1, person2, person3, person4, person5, person6, person7, person8, person9, person10, person11, person12, person13, person14, person15, person16, person17, person18, person19, person20, person21, person22, person23, person24, person25, person26, person27, person28] + state_code: NH + bedrooms: 0 + household2: + members: [person29, person30, person31, person32, person33, person34, person35, person36, person37, person38, person39, person40, person41, person42, person43, person44, person45, person46, person47, person48, person49, person50, person51, person52, person53, person54, person55, person56] + state_code: NH + bedrooms: 0 + household3: + members: [person57, person58, person59, person60, person61, person62, person63, person64, person65, person66, person67, person68, person69, person70, person71, person72, person73, person74, person75, person76, person77, person78, person79, person80, person81, person82, person83, person84] + state_code: NH + bedrooms: 0 + household4: + members: [person85, person86, person87, person88, person89, person90, person91, person92, person93, person94, person95, person96, person97, person98, person99, person100, person101, person102, person103, person104, person105, person106, person107, person108, person109, person110, person111, person112] + state_code: NH + bedrooms: 0 + output: + nh_fap_household_size: [28, 28, 28, 28] + nh_fap_countable_income: [186_000, 186_251.99, 186_252, 186_252.01] + nh_fap_income_limit: [185_486, 185_486, 185_486, 185_486] + nh_fap_eligible: [true, true, false, false] + nh_fap: [863, 863, 0, 0] diff --git a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap.py b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap.py index f0093553e4a..88619690252 100644 --- a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap.py +++ b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap.py @@ -19,8 +19,8 @@ class nh_fap(Variable): "heating cost or fuel classification rather than legal ineligibility." ) reference = ( - # PY2027 manual, pages 10, 17-21, 57, 74, 76-78. - "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=78", + # PDF pages 10,17-21,57,74,76-78. + "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=10", # FY2026 plan, section 2.3: heat-in-rent reduction remains unquantified. "https://liheapch.acf.gov/docs/2026/state-plans/NH_Plan_2026.pdf#page=8", ) diff --git a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py index 5f38655b833..2973674d500 100644 --- a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py +++ b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py @@ -43,13 +43,13 @@ def formula(spm_unit, period, parameters): interest = max_(add(spm_unit, period, ["interest_income"]), 0) dividends = max_(add(spm_unit, period, ["ordinary_dividend_income"]), 0) investment_income = max_(interest + dividends - p.investment_disregard, 0) - # FANF is an SPM-unit benefit and is counted once. + # Count received FANF once per SPM unit, respecting TANF take-up. income = ( countable_earnings + unearned_income + adult_ssi + investment_income - + spm_unit("nh_fanf", period) + + spm_unit("tanf", period) ) # Support-paid regularity and mileage reimbursements are not observed. support_paid = add(spm_unit, period, ["child_support_expense"]) diff --git a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_eligible.py b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_eligible.py index 43268540399..4264f1d0402 100644 --- a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_eligible.py +++ b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_eligible.py @@ -1,4 +1,5 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg class nh_fap_eligible(Variable): @@ -6,14 +7,24 @@ class nh_fap_eligible(Variable): entity = SPMUnit definition_period = YEAR label = "Eligible for New Hampshire FAP" - # PY2027 manual, pages 10-12, 17-18, 21, 34-36, 57, 74, 76-77. - reference = "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=21" + reference = ( + # PDF pages 10-12,17-18,21,34-36,57,74,76-77. + "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=10", + "https://www.law.cornell.edu/uscode/text/42/8624#b_2_B", + ) defined_for = StateCode.NH def formula(spm_unit, period, parameters): + p = parameters(period).gov.states.nh.doe.fap size = spm_unit("nh_fap_household_size", period) income = spm_unit("nh_fap_countable_income", period) limit = spm_unit("nh_fap_income_limit", period) + state_group = spm_unit.household("state_group_str", period) + guideline = fpg(size, state_group, period, parameters, year_lag=p.fpg_year_lag) + floor_rate = parameters(period).gov.hhs.liheap.fpg_floor + # Federal law protects income strictly below this floor when SMI is lower. + federal_floor = np.round(guideline * floor_rate, 2) + income_eligible = (income <= limit) | (income < federal_floor) heat_in_rent = spm_unit("heat_expense_included_in_rent", period) subsidized = spm_unit("receives_housing_assistance", period) heating_cost = spm_unit("nh_fap_heating_cost", period) @@ -25,7 +36,7 @@ def formula(spm_unit, period, parameters): heating_eligible = (heating_cost > 0) | heat_in_rent return ( (size > 0) - & (income <= limit) + & income_eligible & housing_eligible & heating_eligible & (heating_type != fuel.NONE) diff --git a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_heating_cost.py b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_heating_cost.py index 8026278ebde..ea8d7da5b07 100644 --- a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_heating_cost.py +++ b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_heating_cost.py @@ -7,7 +7,7 @@ class nh_fap_heating_cost(Variable): definition_period = YEAR unit = USD label = "New Hampshire FAP annual heating cost using actual or estimated cost" - # PY2027 manual, pages 17, 18, 76, 77. + # PDF pages 17-18,76-77. reference = "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=17" defined_for = StateCode.NH From 7bcd7da083af2bcf5d4a98e7c4de6b953bfaaaaa Mon Sep 17 00:00:00 2001 From: Ziming Date: Wed, 21 Oct 2026 01:15:46 -0400 Subject: [PATCH 10/14] Consolidate New Hampshire LIHEAP changelog and registry note One added changelog fragment for the new program and a one- or two-sentence programs.yaml note; limitations stay in code comments and the PR body. Co-Authored-By: Claude Opus 5.5 --- changelog.d/nh-liheap-investment-disregard.changed.md | 1 - changelog.d/nh-liheap-reported-medicare.fixed.md | 1 - changelog.d/nh-liheap-review-fixes.fixed.md | 1 - changelog.d/nh-liheap-room-proxy.added.md | 1 - changelog.d/nh-liheap-wave2b.added.md | 2 +- policyengine_us/programs.yaml | 2 +- 6 files changed, 2 insertions(+), 6 deletions(-) delete mode 100644 changelog.d/nh-liheap-investment-disregard.changed.md delete mode 100644 changelog.d/nh-liheap-reported-medicare.fixed.md delete mode 100644 changelog.d/nh-liheap-review-fixes.fixed.md delete mode 100644 changelog.d/nh-liheap-room-proxy.added.md diff --git a/changelog.d/nh-liheap-investment-disregard.changed.md b/changelog.d/nh-liheap-investment-disregard.changed.md deleted file mode 100644 index 2089855698e..00000000000 --- a/changelog.d/nh-liheap-investment-disregard.changed.md +++ /dev/null @@ -1 +0,0 @@ -Apply one $50 annual New Hampshire FAP investment-income exclusion per SPM unit to combined interest and dividends, documenting the modeled basis. diff --git a/changelog.d/nh-liheap-reported-medicare.fixed.md b/changelog.d/nh-liheap-reported-medicare.fixed.md deleted file mode 100644 index d170b94dfa1..00000000000 --- a/changelog.d/nh-liheap-reported-medicare.fixed.md +++ /dev/null @@ -1 +0,0 @@ -Deduct reported Medicare Part B premiums from New Hampshire FAP countable income. diff --git a/changelog.d/nh-liheap-review-fixes.fixed.md b/changelog.d/nh-liheap-review-fixes.fixed.md deleted file mode 100644 index cbdafd0e252..00000000000 --- a/changelog.d/nh-liheap-review-fixes.fixed.md +++ /dev/null @@ -1 +0,0 @@ -Fix New Hampshire FAP income sources, TANF receipt treatment and the federal poverty-floor eligibility protection. diff --git a/changelog.d/nh-liheap-room-proxy.added.md b/changelog.d/nh-liheap-room-proxy.added.md deleted file mode 100644 index ffec522867a..00000000000 --- a/changelog.d/nh-liheap-room-proxy.added.md +++ /dev/null @@ -1 +0,0 @@ -Add a documented heating-cost estimate using bedrooms alone for New Hampshire FAP and compare it with actual heating cost. diff --git a/changelog.d/nh-liheap-wave2b.added.md b/changelog.d/nh-liheap-wave2b.added.md index 16686f03b94..2bb2cb3cede 100644 --- a/changelog.d/nh-liheap-wave2b.added.md +++ b/changelog.d/nh-liheap-wave2b.added.md @@ -1 +1 @@ -Add New Hampshire FAP eligibility, countable income, and regular heating awards for PY2027, with a documented assumption that unsubsidized heat-in-rent receives the ordinary fuel schedule without an additional reduction. +Add New Hampshire Fuel Assistance Program (nh_fap): PY2027 regular heating benefit from the Attachment D matrix with the 60% state median income test and a bedroom-based heating-cost estimate. diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 256d692ec62..1e78e0b4ad3 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -967,7 +967,7 @@ programs: variable: nh_fap parameter_prefix: gov.states.nh.doe.fap verified_years: "2027" - notes: PY2027 regular heating only. Bedrooms alone approximate room count, and one annual $50 exclusion applies to combined interest and dividends per SPM unit. Unsubsidized heat-in-rent uses the ordinary fuel schedule without an additional reduction under an approved modeling assumption; the state's reduction is unverified and awards may be overstated. Subsidized heat-in-rent is ineligible. Unsupported fuel or heating-cost cases return zero. Earlier years are unverified historical estimates; crisis, cooling, weatherization and equipment assistance are out of scope. + notes: PY2027 regular heating, estimating heating cost with bedrooms as the room count; unsubsidized heat in rent receives the ordinary schedule, which may overstate awards. Crisis, cooling, weatherization and equipment assistance are out of scope. # --- SSA programs --- - id: ssi From 3aceb16f276dca7d6b34550ad672cc886c83d63a Mon Sep 17 00:00:00 2001 From: Ziming Date: Fri, 23 Oct 2026 22:46:01 -0400 Subject: [PATCH 11/14] Address review findings for New Hampshire LIHEAP Co-Authored-By: Claude Opus 5.5 --- .../nh/doe/fap/income/unearned_sources.yaml | 2 +- .../doe/fap/payment/benefit_multiplier.yaml | 38 +++ .../fap/payment/heating_cost_threshold.yaml | 4 +- .../doe/fap/payment/utility_multiplier.yaml | 12 - .../gov/states/nh/doe/fap/nh_fap.yaml | 237 +++++------------- .../nh/doe/fap/nh_fap_household_size.yaml | 160 +----------- .../variables/gov/states/nh/doe/fap/nh_fap.py | 19 +- .../states/nh/doe/fap/nh_fap_income_limit.py | 17 +- source_conflicts/candidates.yaml | 35 +++ 9 files changed, 143 insertions(+), 381 deletions(-) create mode 100644 policyengine_us/parameters/gov/states/nh/doe/fap/payment/benefit_multiplier.yaml delete mode 100644 policyengine_us/parameters/gov/states/nh/doe/fap/payment/utility_multiplier.yaml diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/income/unearned_sources.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/income/unearned_sources.yaml index 1cf2e885a3e..d4ea151cfb6 100644 --- a/policyengine_us/parameters/gov/states/nh/doe/fap/income/unearned_sources.yaml +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/income/unearned_sources.yaml @@ -3,7 +3,7 @@ values: 2026-10-01: - social_security - pension_income - - military_retirement_pay + - military_retirement_income - retirement_distributions - railroad_benefits - survivor_benefits diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/payment/benefit_multiplier.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/payment/benefit_multiplier.yaml new file mode 100644 index 00000000000..afce1f3993f --- /dev/null +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/payment/benefit_multiplier.yaml @@ -0,0 +1,38 @@ +description: New Hampshire provides this share of the delivered-fuel scheduled benefit under the Fuel Assistance Program. +# Whole-dollar half-up rounding reproduces all 24 published utility cells. +# Unsupported fuels have no published multiplier; zero marks an unsupported +# amount, not a legal zero benefit. +UNSPECIFIED: + 2026-10-01: 0 +ELECTRICITY: + 2026-10-01: 0.75 +NATURAL_GAS: + 2026-10-01: 0.75 +FUEL_OIL: + 2026-10-01: 1 +KEROSENE: + 2026-10-01: 1 +PROPANE: + 2026-10-01: 1 +# Existing WOOD also represents pellets, bio-bricks and winter blends, which +# share the 1.000 multiplier. +WOOD: + 2026-10-01: 1 +COAL: + 2026-10-01: 1 +SOLAR: + 2026-10-01: 0 +OTHER: + 2026-10-01: 0 +NONE: + 2026-10-01: 0 + +metadata: + unit: /1 + period: year + label: New Hampshire FAP benefit multiplier + breakdown: + - heating_type + reference: + - title: PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Benefit Determination, Fuel Cost, Efficiency, and Benefit Multiplier + href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=77 diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/payment/heating_cost_threshold.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/payment/heating_cost_threshold.yaml index a5d9f10c01f..67d06f05cd8 100644 --- a/policyengine_us/parameters/gov/states/nh/doe/fap/payment/heating_cost_threshold.yaml +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/payment/heating_cost_threshold.yaml @@ -7,11 +7,11 @@ AA: 2026-10-01: 3_500 # Page 78 utility labels overlap at the endpoints; the non-overlapping -# delivered schedule and benefit-determination worksheet establish the tiers. +# page 77 Heating Cost Tiers table and delivered schedule establish the tiers. metadata: unit: currency-USD period: year label: New Hampshire FAP heating cost band thresholds reference: - - title: "PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Benefit Determination and Benefits #page 76,78" + - title: "PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Benefit Determination, Heating Cost Tiers and Benefits #page 76-78" href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=76 diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/payment/utility_multiplier.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/payment/utility_multiplier.yaml deleted file mode 100644 index e3474117b8e..00000000000 --- a/policyengine_us/parameters/gov/states/nh/doe/fap/payment/utility_multiplier.yaml +++ /dev/null @@ -1,12 +0,0 @@ -description: New Hampshire multiplies the delivered-fuel scheduled benefit by this share for electric and natural gas heat under the Fuel Assistance Program. -# Whole-dollar half-up rounding reproduces all 24 published utility cells. -values: - 2026-10-01: 0.75 - -metadata: - unit: /1 - period: year - label: New Hampshire FAP utility benefit multiplier - reference: - - title: "PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Benefit Determination and Utility Benefits #page 76-78" - href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=76 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap.yaml b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap.yaml index 736b35251b4..6b01c863b83 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap.yaml @@ -1563,34 +1563,8 @@ output: nh_fap: 825 -# Manual p77 exact ECOS ranges control the overlapping printed utility labels on p78. -- name: "Case 59, delivered annual cost 999.99 selects the current cost tier." - period: 2027 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 40 - immigration_status: "CITIZEN" - ssi: 0 - employment_income: 10_000 - spm_units: - spm_unit1: - members: ["person1"] - nh_fanf: 0 - heating_type: "FUEL_OIL" - heat_expense_included_in_rent: false - receives_housing_assistance: false - fuel_oil_expense: 999.99 - households: - household1: - members: ["person1"] - state_code: "NH" - output: - nh_fap: 750 - # Manual pp77-78 exact cost ranges and utility schedule. -- name: "Case 60, utility annual cost 999.99 selects the current cost tier." +- name: "Case 59, utility annual cost 999.99 selects the current cost tier." period: 2027 absolute_error_margin: 0.01 input: @@ -1615,34 +1589,8 @@ output: nh_fap: 563 -# Manual p77 exact ECOS ranges control the overlapping printed utility labels on p78. -- name: "Case 61, delivered annual cost 1000 selects the current cost tier." - period: 2027 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 40 - immigration_status: "CITIZEN" - ssi: 0 - employment_income: 10_000 - spm_units: - spm_unit1: - members: ["person1"] - nh_fanf: 0 - heating_type: "FUEL_OIL" - heat_expense_included_in_rent: false - receives_housing_assistance: false - fuel_oil_expense: 1_000 - households: - household1: - members: ["person1"] - state_code: "NH" - output: - nh_fap: 1_460 - # Manual pp77-78 exact cost ranges and utility schedule. -- name: "Case 62, utility annual cost 1000 selects the current cost tier." +- name: "Case 60, utility annual cost 1000 selects the current cost tier." period: 2027 absolute_error_margin: 0.01 input: @@ -1667,34 +1615,8 @@ output: nh_fap: 1_095 -# Manual p77 exact ECOS ranges control the overlapping printed utility labels on p78. -- name: "Case 63, delivered annual cost 1749.99 selects the current cost tier." - period: 2027 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 40 - immigration_status: "CITIZEN" - ssi: 0 - employment_income: 10_000 - spm_units: - spm_unit1: - members: ["person1"] - nh_fanf: 0 - heating_type: "FUEL_OIL" - heat_expense_included_in_rent: false - receives_housing_assistance: false - fuel_oil_expense: 1_749.99 - households: - household1: - members: ["person1"] - state_code: "NH" - output: - nh_fap: 1_460 - # Manual pp77-78 exact cost ranges and utility schedule. -- name: "Case 64, utility annual cost 1749.99 selects the current cost tier." +- name: "Case 61, utility annual cost 1749.99 selects the current cost tier." period: 2027 absolute_error_margin: 0.01 input: @@ -1719,34 +1641,8 @@ output: nh_fap: 1_095 -# Manual p77 exact ECOS ranges control the overlapping printed utility labels on p78. -- name: "Case 65, delivered annual cost 1750 selects the current cost tier." - period: 2027 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 40 - immigration_status: "CITIZEN" - ssi: 0 - employment_income: 10_000 - spm_units: - spm_unit1: - members: ["person1"] - nh_fanf: 0 - heating_type: "FUEL_OIL" - heat_expense_included_in_rent: false - receives_housing_assistance: false - fuel_oil_expense: 1_750 - households: - household1: - members: ["person1"] - state_code: "NH" - output: - nh_fap: 2_350 - # Manual pp77-78 exact cost ranges and utility schedule. -- name: "Case 66, utility annual cost 1750 selects the current cost tier." +- name: "Case 62, utility annual cost 1750 selects the current cost tier." period: 2027 absolute_error_margin: 0.01 input: @@ -1771,34 +1667,8 @@ output: nh_fap: 1_763 -# Manual p77 exact ECOS ranges control the overlapping printed utility labels on p78. -- name: "Case 67, delivered annual cost 3499.99 selects the current cost tier." - period: 2027 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 40 - immigration_status: "CITIZEN" - ssi: 0 - employment_income: 10_000 - spm_units: - spm_unit1: - members: ["person1"] - nh_fanf: 0 - heating_type: "FUEL_OIL" - heat_expense_included_in_rent: false - receives_housing_assistance: false - fuel_oil_expense: 3_499.99 - households: - household1: - members: ["person1"] - state_code: "NH" - output: - nh_fap: 2_350 - # Manual pp77-78 exact cost ranges and utility schedule. -- name: "Case 68, utility annual cost 3499.99 selects the current cost tier." +- name: "Case 63, utility annual cost 3499.99 selects the current cost tier." period: 2027 absolute_error_margin: 0.01 input: @@ -1823,34 +1693,8 @@ output: nh_fap: 1_763 -# Manual p77 exact ECOS ranges control the overlapping printed utility labels on p78. -- name: "Case 69, delivered annual cost 3500 selects the current cost tier." - period: 2027 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 40 - immigration_status: "CITIZEN" - ssi: 0 - employment_income: 10_000 - spm_units: - spm_unit1: - members: ["person1"] - nh_fanf: 0 - heating_type: "FUEL_OIL" - heat_expense_included_in_rent: false - receives_housing_assistance: false - fuel_oil_expense: 3_500 - households: - household1: - members: ["person1"] - state_code: "NH" - output: - nh_fap: 2_500 - # Manual pp77-78 exact cost ranges and utility schedule. -- name: "Case 70, utility annual cost 3500 selects the current cost tier." +- name: "Case 64, utility annual cost 3500 selects the current cost tier." period: 2027 absolute_error_margin: 0.01 input: @@ -1876,7 +1720,7 @@ nh_fap: 1_875 # Manual p77 multiplier 1.000 and p78 BB/F delivered benefit. -- name: "Case 71, kerosene uses the full delivered-fuel schedule." +- name: "Case 65, kerosene uses the full delivered-fuel schedule." period: 2027 absolute_error_margin: 0.01 input: @@ -1902,7 +1746,7 @@ nh_fap: 800 # Manual p77 multiplier 1.000 and p78 BB/F delivered benefit. -- name: "Case 72, propane uses the full delivered-fuel schedule." +- name: "Case 66, propane uses the full delivered-fuel schedule." period: 2027 absolute_error_margin: 0.01 input: @@ -1928,7 +1772,7 @@ nh_fap: 800 # Manual p77 multiplier 1.000 and p78 BB/F delivered benefit. -- name: "Case 73, wood uses the full delivered-fuel schedule." +- name: "Case 67, wood uses the full delivered-fuel schedule." period: 2027 absolute_error_margin: 0.01 input: @@ -1954,7 +1798,7 @@ nh_fap: 800 # Manual p77 multiplier 1.000 and p78 BB/F delivered benefit. -- name: "Case 74, coal uses the full delivered-fuel schedule." +- name: "Case 68, coal uses the full delivered-fuel schedule." period: 2027 absolute_error_margin: 0.01 input: @@ -1980,7 +1824,7 @@ nh_fap: 800 # Manual p78 DD/A amount is750; no actual-cost cap is stated. -- name: "Case 75, actual-cost schedule is not capped at a small positive bill." +- name: "Case 69, actual-cost schedule is not capped at a small positive bill." period: 2027 absolute_error_margin: 0.01 input: @@ -2006,7 +1850,7 @@ nh_fap: 750 # Manual p10 provides application priority; pp77-78 contain no age supplement. -- name: "Case 76, an older adult receives the same regular schedule." +- name: "Case 70, an older adult receives the same regular schedule." period: 2027 absolute_error_margin: 0.01 input: @@ -2032,7 +1876,7 @@ nh_fap: 2_150 # Manual p10 provides application priority; pp77-78 contain no disability supplement. -- name: "Case 77, disability does not add an unsourced regular supplement." +- name: "Case 71, disability does not add an unsourced regular supplement." period: 2027 absolute_error_margin: 0.01 input: @@ -2059,7 +1903,7 @@ nh_fap: 2_150 # Manual p74: size 2 D band ends at 32,460; p78 AA/D is 1500. -- name: "Case 78, two qualified members use the published two-person FPG." +- name: "Case 72, two qualified members use the published two-person FPG." period: 2027 absolute_error_margin: 0.01 input: @@ -2092,7 +1936,7 @@ nh_fap: 1_500 # Manual p36 includes ineligible-member income but excludes their size; size 1 band F applies without income proration. -- name: "Case 79, mixed status counts both incomes but only the qualified person." +- name: "Case 73, mixed status counts both incomes but only the qualified person." period: 2027 absolute_error_margin: 0.01 input: @@ -2125,7 +1969,7 @@ nh_fap: 825 # Manual p74 size 1 ceiling 48,713; p78 AA/F is 825. -- name: "Case 80, the inclusive income ceiling retains the base schedule." +- name: "Case 74, the inclusive income ceiling retains the base schedule." period: 2027 absolute_error_margin: 0.01 input: @@ -2151,7 +1995,7 @@ nh_fap: 825 # Manual p74 size 1 maximum 48,713. -- name: "Case 81, income above the ceiling removes the scheduled benefit." +- name: "Case 75, income above the ceiling removes the scheduled benefit." period: 2027 absolute_error_margin: 0.01 input: @@ -2177,7 +2021,7 @@ nh_fap: 0 # Manual pp76-78 formula and matrix with four bedrooms alone: cost 2272.564 selects BB, income 45120 selects F, yielding a modeled award of 800. -- name: "Case 82, bedrooms alone determine the two-person oil benefit cost band." +- name: "Case 76, bedrooms alone determine the two-person oil benefit cost band." period: 2027 absolute_error_margin: 0.01 input: @@ -2212,7 +2056,7 @@ nh_fap: 800 # Manual pp18,77-78: two bedrooms alone give estimated cost 1136.282, selecting CC/A 1460; actual 4000 selects AA/A 2500. The 500 bill does not cap the benefit. -- name: "Case 83, the larger cost basis determines the delivered benefit." +- name: "Case 77, the larger cost basis determines the delivered benefit." period: 2027 absolute_error_margin: 0.01 input: @@ -2260,7 +2104,7 @@ nh_fap: [1_460, 2_500] # Manual pp77-78: two-bedroom Coos gas cost 561.366 selects DD/A 750 * .75 =562.50, rounded to 563; actual 4000 selects AA/A 2500 * .75 =1875. -- name: "Case 84, utility reduction follows the selected proxy or actual cost band." +- name: "Case 78, utility reduction follows the selected proxy or actual cost band." period: 2027 absolute_error_margin: 0.01 input: @@ -2312,7 +2156,7 @@ # 3 bedrooms used as rooms, electricity 64.706/MBTU at efficiency 1. # Estimated cost = 2000 * 3 * 6610 / 1_000_000 * 64.706 = 2566.23996. # Income band A and cost band BB: 2350 * 0.75, rounded half up = 1763. -- name: Case 85, unsubsidized heat-in-rent with three bedrooms and electricity receives the modeled final award. +- name: Case 79, unsubsidized heat-in-rent with three bedrooms and electricity receives the modeled final award. period: 2027 absolute_error_margin: 0.01 input: @@ -2352,7 +2196,7 @@ # Manual p78 AA/A: delivered 2500; electricity and gas 2500 * 0.75 = 1875. # Approved assumption applies that fuel schedule once to direct-pay and heat-in-rent. # Manual p21 excludes subsidized heat-in-rent, not subsidized direct-pay households. -- name: Case 86, heat-in-rent retains each fuel schedule while subsidized heat-in-rent is excluded. +- name: Case 80, heat-in-rent retains each fuel schedule while subsidized heat-in-rent is excluded. period: 2027 absolute_error_margin: 0.01 input: @@ -2494,7 +2338,7 @@ # Manual page 15 counts ordinary DoD retirement separately from pension/VA income. # Primary pay of 50,000 exceeds the one-person 48,713 ceiling. The 10,000 control # remains in income band A; a 500 electric bill uses DD/A: 750 * 0.75 -> 563. -- name: "Case 87, ordinary military retirement pay affects eligibility and retains eligible retirees." +- name: "Case 81, ordinary military retirement pay affects eligibility and retains eligible retirees." period: 2027 absolute_error_margin: 0.01 input: @@ -2549,7 +2393,7 @@ # actual TANF receipts of 3,600, counted once: 9,600 + 3,600 = 13,200. # Both are below the two-person A ceiling of 16,230, receiving AA/A = 2,500. # No counterfactual FANF entitlement is supplied or asserted as a legal award. -- name: "Case 88, actual TANF receipt counts once and nonreceipt does not reduce the heating award." +- name: "Case 82, actual TANF receipt counts once and nonreceipt does not reduce the heating award." period: 2027 absolute_error_margin: 0.01 input: @@ -2607,7 +2451,7 @@ # Income 186,000 and a cent below the floor qualify despite SMI of 185,486; # equality and a cent above do not. Protected income uses band C and a 1,000 # electric bill uses cost band CC: 1,150 * 0.75 = 862.50 -> 863. -- name: "Case 89, the federal floor protects large households strictly below the season-start threshold." +- name: "Case 83, the federal floor protects large households strictly below the season-start threshold." period: 2027 absolute_error_margin: 0.01 input: @@ -2877,3 +2721,34 @@ nh_fap_income_limit: [185_486, 185_486, 185_486, 185_486] nh_fap_eligible: [true, true, false, false] nh_fap: [863, 863, 0, 0] + +# Manual page 15 counts annuity payments and military retirement pay. A uniformed +# services Survivor Benefit Plan annuity is reported in military_retirement_pay_survivors. +# Income 20,000 is above 125% of the 2026 one-person FPG (15,960 * 1.25 = 19,950) +# and within 150% (23,940), so band D. No bill: Coos single-family proxy with four +# bedrooms, 2,000 * 4 * 7,672 / 1,000,000 * 28.881 / 0.78 = 2,272.56, cost band BB. +# Fuel oil multiplier 1: BB/D = 1,410. +- name: Case 84, a military Survivor Benefit Plan annuity counts as unearned income. + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + immigration_status: CITIZEN + military_retirement_pay_survivors: 20_000 + spm_units: + spm_unit1: + members: [person1] + heating_type: FUEL_OIL + households: + household1: + members: [person1] + state_code: NH + county_str: COOS_COUNTY_NH + bedrooms: 4 + output: + nh_fap_countable_income: 20_000 + nh_fap_heating_cost: 2_272.564431 + nh_fap_eligible: true + nh_fap: 1_410 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_household_size.yaml b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_household_size.yaml index 7b77d9ff8cc..ac071903d07 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_household_size.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_household_size.yaml @@ -56,164 +56,8 @@ output: nh_fap_household_size: 1 -# Manual pp35-36 includes the reported qualified status regardless of entry date. -- name: "Case 3, refugee is a qualified household member." - period: 2027 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 40 - immigration_status: "REFUGEE" - ssi: 0 - years_since_us_entry: 0 - spm_units: - spm_unit1: - members: ["person1"] - nh_fanf: 0 - heating_type: "FUEL_OIL" - heat_expense_included_in_rent: false - receives_housing_assistance: false - fuel_oil_expense: 4_000 - households: - household1: - members: ["person1"] - state_code: "NH" - output: - nh_fap_household_size: 1 - -# Manual pp35-36 includes the reported qualified status regardless of entry date. -- name: "Case 4, asylee is a qualified household member." - period: 2027 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 40 - immigration_status: "ASYLEE" - ssi: 0 - years_since_us_entry: 0 - spm_units: - spm_unit1: - members: ["person1"] - nh_fanf: 0 - heating_type: "FUEL_OIL" - heat_expense_included_in_rent: false - receives_housing_assistance: false - fuel_oil_expense: 4_000 - households: - household1: - members: ["person1"] - state_code: "NH" - output: - nh_fap_household_size: 1 - -# Manual pp35-36 includes the reported qualified status regardless of entry date. -- name: "Case 5, paroled_one_year is a qualified household member." - period: 2027 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 40 - immigration_status: "PAROLED_ONE_YEAR" - ssi: 0 - years_since_us_entry: 0 - spm_units: - spm_unit1: - members: ["person1"] - nh_fanf: 0 - heating_type: "FUEL_OIL" - heat_expense_included_in_rent: false - receives_housing_assistance: false - fuel_oil_expense: 4_000 - households: - household1: - members: ["person1"] - state_code: "NH" - output: - nh_fap_household_size: 1 - -# Manual pp35-36 includes the reported qualified status regardless of entry date. -- name: "Case 6, deportation_withheld is a qualified household member." - period: 2027 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 40 - immigration_status: "DEPORTATION_WITHHELD" - ssi: 0 - years_since_us_entry: 0 - spm_units: - spm_unit1: - members: ["person1"] - nh_fanf: 0 - heating_type: "FUEL_OIL" - heat_expense_included_in_rent: false - receives_housing_assistance: false - fuel_oil_expense: 4_000 - households: - household1: - members: ["person1"] - state_code: "NH" - output: - nh_fap_household_size: 1 - -# Manual pp35-36 includes the reported qualified status regardless of entry date. -- name: "Case 7, conditional_entrant is a qualified household member." - period: 2027 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 40 - immigration_status: "CONDITIONAL_ENTRANT" - ssi: 0 - years_since_us_entry: 0 - spm_units: - spm_unit1: - members: ["person1"] - nh_fanf: 0 - heating_type: "FUEL_OIL" - heat_expense_included_in_rent: false - receives_housing_assistance: false - fuel_oil_expense: 4_000 - households: - household1: - members: ["person1"] - state_code: "NH" - output: - nh_fap_household_size: 1 - -# Manual pp35-36 includes the reported qualified status regardless of entry date. -- name: "Case 8, cuban_haitian_entrant is a qualified household member." - period: 2027 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 40 - immigration_status: "CUBAN_HAITIAN_ENTRANT" - ssi: 0 - years_since_us_entry: 0 - spm_units: - spm_unit1: - members: ["person1"] - nh_fanf: 0 - heating_type: "FUEL_OIL" - heat_expense_included_in_rent: false - receives_housing_assistance: false - fuel_oil_expense: 4_000 - households: - household1: - members: ["person1"] - state_code: "NH" - output: - nh_fap_household_size: 1 - # Manual pp35-36 and existing qualified-status mapping; income is tested separately. -- name: "Case 9, mixed status excludes ineligible members only from household size." +- name: "Case 3, mixed status excludes ineligible members only from household size." period: 2027 absolute_error_margin: 0.01 input: @@ -254,7 +98,7 @@ nh_fap_household_size: 2 # Manual p36 does not count an ineligible noncitizen for eligibility size. -- name: "Case 10, an entirely ineligible-status household has size zero." +- name: "Case 4, an entirely ineligible-status household has size zero." period: 2027 absolute_error_margin: 0.01 input: diff --git a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap.py b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap.py index 88619690252..eab60650c14 100644 --- a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap.py +++ b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap.py @@ -57,24 +57,13 @@ def formula(spm_unit, period, parameters): ) delivered_benefit = p.payment.delivered_benefit[cost_band][income_band] heating_type = spm_unit("heating_type", period) - fuel = heating_type.possible_values - utility = (heating_type == fuel.ELECTRICITY) | ( - heating_type == fuel.NATURAL_GAS - ) - delivered = ( - (heating_type == fuel.FUEL_OIL) - | (heating_type == fuel.PROPANE) - | (heating_type == fuel.KEROSENE) - | (heating_type == fuel.WOOD) - | (heating_type == fuel.COAL) - ) - multiplier = where(utility, p.payment.utility_multiplier, 1) + multiplier = p.payment.benefit_multiplier[heating_type] # Half-up whole dollars reproduce every utility matrix cell. benefit = np.floor(delivered_benefit * multiplier + 0.5) # Approved approximation: unsubsidized heat-in-rent receives this - # scheduled amount, including the utility multiplier exactly once. + # scheduled amount, including the fuel multiplier exactly once. # The state's heat-in-rent reduction remains unverified. # Unknown, solar and other fuels have no supported payment - # classification; zero marks an unsupported amount. + # classification; their zero multiplier marks an unsupported amount. # The schedule is not capped at the household's actual heating cost. - return where((expense > 0) & (utility | delivered), benefit, 0) + return where(expense > 0, benefit, 0) diff --git a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_income_limit.py b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_income_limit.py index 808dcf1525b..61fcdbc1958 100644 --- a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_income_limit.py +++ b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_income_limit.py @@ -1,5 +1,5 @@ from policyengine_us.model_api import * -from policyengine_us.variables.gov.hhs.hhs_smi import smi +from policyengine_us.variables.gov.hhs.hhs_smi import liheap_smi_limit class nh_fap_income_limit(Variable): @@ -16,16 +16,9 @@ def formula(spm_unit, period, parameters): p = parameters(period).gov.states.nh.doe.fap size = spm_unit("nh_fap_household_size", period) state = spm_unit.household("state_code_str", period) - four_person_smi = parameters(period).gov.hhs.smi.amount[state] - household_smi = smi(size, state, period, parameters) - # Numerical inference matching all eight PY2027 published amounts: - # floor the four-person limit, apply the federal factor, then floor. - # The manual does not prescribe that intermediate rounding order. - four_person_limit = np.floor(four_person_smi * p.smi_rate) - # Federal factors are integer percentages; recover them to avoid a - # floating-point value just below an exact whole-dollar boundary. - factor_percent = np.rint(household_smi / four_person_smi * 100) - limit = np.floor(four_person_limit * factor_percent / 100) + # The HHS floor order reproduces all eight PY2027 published amounts. # Federal size extensions are retained; NH rounding above eight is - # unverified. A household with no qualified member is ineligible. + # unverified. + limit = liheap_smi_limit(size, state, p.smi_rate, period, parameters) + # A household with no qualified member is ineligible. return where(size > 0, limit, 0) diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index 7d8c41658c6..23f4aec5172 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -3732,6 +3732,41 @@ candidates: prs: [] state: unreviewed note: Verify whether the still-listed ruling applies during these years despite its obsolete tax base. +- id: us-nh:fap-income-floor-vs-smi-limit + program: liheap + years: + from: 2027 + to: 2027 + type: program + note: New Hampshire Fuel Assistance Program year 2026-2027 (PY2027) + question: Can New Hampshire's 60% SMI limit exclude a large household whose income is below the federal 110% + poverty-guideline floor? + sources: + - kind: statute + citation: 42 U.S.C. 8624(b)(2)(B) + url: https://www.law.cornell.edu/uscode/text/42/8624 + - kind: plan + citation: FY2026 New Hampshire LIHEAP State Plan section 2.1, 60% SMI for all household sizes + url: https://liheapch.acf.gov/docs/2026/state-plans/NH_Plan_2026.pdf#page=8 + - kind: manual + citation: PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Income Guidelines, 60% SMI printed for + sizes 1-8 + url: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=74 + readings: + statute: Income below 110% of the poverty guideline cannot by itself exclude the household. + other: The 60% SMI limit applies at every household size; with the federal SMI size adjustments it falls below + that floor starting at household size 28. + example: A 28-person household with $186,000 of countable income and a $2,000 fuel-oil heating cost exceeds the + $185,486 limit but is below the $186,252 floor and receives the $1,720 BB/C award in the PR. + pe_us: + follows: statute + code: [] + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10343 + state: unreviewed + note: 'The open PR applies the federal floor; verify the extended limits, since the manual prints 60% SMI amounts + only through eight persons. Code not yet on main: nh_fap_eligible, nh_fap_income_limit, + gov.states.nh.doe.fap.smi_rate.' - id: us-nj:claim-of-right-same-category-offset program: income-tax years: From 8dc671f86ec096e37ca1b742061012c00a8f39d3 Mon Sep 17 00:00:00 2001 From: Ziming Date: Fri, 23 Oct 2026 22:46:59 -0400 Subject: [PATCH 12/14] Sum New Hampshire LIHEAP income without built-in sum Co-Authored-By: Claude Opus 5.5 --- .../states/nh/doe/fap/nh_fap_countable_income.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py index 2973674d500..8fb0acf738b 100644 --- a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py +++ b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py @@ -18,9 +18,9 @@ def formula(spm_unit, period, parameters): # Separate business losses cannot offset positive sources. Existing # net inputs lack depreciation, amortization and home-business addbacks; # partnership/S-corporation inputs also lack FAP expense detail. - business_income = sum( - max_(person(source, period), 0) for source in p.business_sources - ) + business_income = 0 + for source in p.business_sources: + business_income = business_income + max_(person(source, period), 0) earned_income = max_(person("employment_income", period), 0) + business_income # Secondary school also includes equivalent training in the model; # this conjunction approximates the full-time high-school exclusion. @@ -28,9 +28,10 @@ def formula(spm_unit, period, parameters): "is_in_secondary_school", period ) countable_earnings = spm_unit.sum(where(high_school_student, 0, earned_income)) - unearned_income = spm_unit.sum( - sum(max_(person(source, period), 0) for source in p.unearned_sources) - ) + unearned_person = 0 + for source in p.unearned_sources: + unearned_person = unearned_person + max_(person(source, period), 0) + unearned_income = spm_unit.sum(unearned_person) adult_ssi = spm_unit.sum( where( person("age", period) >= p.ssi_min_age, From a7d76c39d81ef3a7a44906480eb79f3fb74e2e74 Mon Sep 17 00:00:00 2001 From: Ziming Date: Fri, 23 Oct 2026 23:21:28 -0400 Subject: [PATCH 13/14] Count New Hampshire state supplement and public housing in FAP Co-Authored-By: Claude Opus 5.5 --- .../nh/doe/fap/income/unearned_sources.yaml | 6 ++- .../gov/states/nh/doe/fap/nh_fap.yaml | 4 ++ .../nh/doe/fap/nh_fap_countable_income.yaml | 37 ++++++++++++++++++- .../states/nh/doe/fap/nh_fap_eligible.yaml | 29 +++++++++++++++ .../nh/doe/fap/nh_fap_countable_income.py | 3 +- .../gov/states/nh/doe/fap/nh_fap_eligible.py | 4 +- 6 files changed, 77 insertions(+), 6 deletions(-) diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/income/unearned_sources.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/income/unearned_sources.yaml index d4ea151cfb6..189588d54d2 100644 --- a/policyengine_us/parameters/gov/states/nh/doe/fap/income/unearned_sources.yaml +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/income/unearned_sources.yaml @@ -11,6 +11,7 @@ values: - total_unemployment_compensation - workers_compensation - disability_benefits + - nh_ssp - general_assistance - strike_benefits - financial_assistance @@ -23,8 +24,9 @@ values: # Broad receipts approximate the source's regular payments. Inputs do not # identify VA Aid and Attendance, educational VA benefits, special-needs # trusts, or irregular injury/insurance payments. Financial assistance lacks -# the required six-month regularity flag. General assistance approximates -# non-FANF state welfare; FANF is included separately once per SPM unit. +# the required six-month regularity flag. nh_ssp holds the APTD, ANB and OAA +# state welfare payments; general assistance approximates other non-FANF state +# welfare. FANF is included separately once per SPM unit. # Rental inputs are treated as net and nonnegative without reconstructing # business expenses. Pension income includes annuities and insurance income. # Gambling winnings approximate the taxable gambling and lottery category. diff --git a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap.yaml b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap.yaml index 6b01c863b83..dbf0f70529b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap.yaml @@ -1885,6 +1885,7 @@ age: 40 immigration_status: "CITIZEN" ssi: 0 + nh_ssp: 0 is_disabled: true employment_income: 14_000 spm_units: @@ -2403,6 +2404,7 @@ immigration_status: CITIZEN is_disabled: true ssi: 9_600 + nh_ssp: 0 person2: age: 10 immigration_status: CITIZEN @@ -2411,6 +2413,7 @@ immigration_status: CITIZEN is_disabled: true ssi: 9_600 + nh_ssp: 0 person4: age: 10 immigration_status: CITIZEN @@ -2735,6 +2738,7 @@ people: person1: age: 70 + nh_ssp: 0 immigration_status: CITIZEN military_retirement_pay_survivors: 20_000 spm_units: diff --git a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_countable_income.yaml index 5daffd814f3..a81339bdc0f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_countable_income.yaml @@ -3,8 +3,9 @@ # Physical PDF pages: 13, 14, 15, 16, 17, 23, 25, 26, 29, 34, 36. # Period 2027 represents the researched 2026-2027 schedule with 2026 FPG. # Inputs are annual; SSI and FANF totals are explicitly supplied to isolate receipts. -# Investment treatment follows the specific separate exclusions on pp15-16; -# p17's combined wording remains an explicitly documented interpretation gap. +# Investment income receives one combined $50 exclusion per SPM unit (p17's +# interest "and/or" dividends wording), a modeling convention; pp15-16 list +# the two exclusions separately. # Manual p15 counts salaries and wages before deductions; p17 does not list a general earned-income disregard. - name: "Case 1, gross annual wages receive no general earnings disregard." @@ -159,6 +160,7 @@ age: 18 immigration_status: "CITIZEN" ssi: 6_000 + nh_ssp: 0 is_disabled: true spm_units: spm_unit1: @@ -789,6 +791,7 @@ age: 70 immigration_status: "CITIZEN" ssi: 0 + nh_ssp: 0 social_security: 24_000 medicare_part_b_premiums_reported: 2_400 spm_units: @@ -813,6 +816,7 @@ age: 70 immigration_status: "CITIZEN" ssi: 0 + nh_ssp: 0 railroad_benefits: 18_000 medicare_part_b_premiums_reported: 2_400 spm_units: @@ -837,6 +841,7 @@ age: 70 immigration_status: "CITIZEN" ssi: 0 + nh_ssp: 0 social_security: 24_000 medicare_part_b_premiums_reported: 2_400 child_support_expense: 2_000 @@ -862,6 +867,7 @@ age: 70 immigration_status: "CITIZEN" ssi: 0 + nh_ssp: 0 social_security: 24_000 medicare_part_b_premiums_reported: 2_400 child_support_expense: 23_000 @@ -887,6 +893,7 @@ age: 70 immigration_status: "CITIZEN" ssi: 0 + nh_ssp: 0 social_security: 24_000 medicare_part_b_premiums_reported: 0 spm_units: @@ -911,18 +918,21 @@ age: 70 immigration_status: "CITIZEN" ssi: 0 + nh_ssp: 0 social_security: 24_000 medicare_part_b_premiums_reported: 2_400 person2: age: 68 immigration_status: "CITIZEN" ssi: 0 + nh_ssp: 0 railroad_benefits: 18_000 medicare_part_b_premiums_reported: 1_200 person3: age: 70 immigration_status: "CITIZEN" ssi: 0 + nh_ssp: 0 social_security: 12_000 medicare_part_b_premiums_reported: 0 spm_units: @@ -985,3 +995,26 @@ state_code: "NH" output: nh_fap_countable_income: [30, 0, 10_000] + +# Manual p15 counts state welfare payments (APTD, ANB, FANF, OAA, RCA); the +# state supplement pays APTD, ANB and OAA. $100 a month x 12 = $1,200. +- name: "Case 34, state supplement payments count as unearned income." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + immigration_status: "CITIZEN" + ssi: 0 + nh_ssp: 1_200 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 1_200 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml index d6cb6be8c46..789997187b0 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml @@ -921,12 +921,14 @@ age: 70 immigration_status: "CITIZEN" ssi: 0 + nh_ssp: 0 social_security: 50_000 medicare_part_b_premiums_reported: 2_000 person2: age: 70 immigration_status: "CITIZEN" ssi: 0 + nh_ssp: 0 social_security: 50_000 medicare_part_b_premiums_reported: 0 spm_units: @@ -1137,3 +1139,30 @@ nh_fap_countable_income: 48_738 nh_fap_eligible: false nh_fap: 0 + +# Manual p21 excludes subsidized tenants whose heat is included in rent; +# public housing is subsidized housing. +- name: "Case 29, public housing with heat included in rent is ineligible." + period: 2027 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: true + receives_housing_assistance: false + fuel_oil_expense: 0 + households: + household1: + members: ["person1"] + state_code: "NH" + is_in_public_housing: true + output: + nh_fap_eligible: false diff --git a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py index 8fb0acf738b..91e8c49be2f 100644 --- a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py +++ b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py @@ -30,7 +30,8 @@ def formula(spm_unit, period, parameters): countable_earnings = spm_unit.sum(where(high_school_student, 0, earned_income)) unearned_person = 0 for source in p.unearned_sources: - unearned_person = unearned_person + max_(person(source, period), 0) + # add() also sums monthly sources such as nh_ssp over the year. + unearned_person = unearned_person + max_(add(person, period, [source]), 0) unearned_income = spm_unit.sum(unearned_person) adult_ssi = spm_unit.sum( where( diff --git a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_eligible.py b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_eligible.py index 4264f1d0402..83454f4a868 100644 --- a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_eligible.py +++ b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_eligible.py @@ -26,7 +26,9 @@ def formula(spm_unit, period, parameters): federal_floor = np.round(guideline * floor_rate, 2) income_eligible = (income <= limit) | (income < federal_floor) heat_in_rent = spm_unit("heat_expense_included_in_rent", period) - subsidized = spm_unit("receives_housing_assistance", period) + subsidized = spm_unit( + "receives_housing_assistance", period + ) | spm_unit.household("is_in_public_housing", period) heating_cost = spm_unit("nh_fap_heating_cost", period) heating_type = spm_unit("heating_type", period) fuel = heating_type.possible_values From 6e975b160513a02409041a896779a6435af932d4 Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 27 Oct 2026 15:47:48 -0400 Subject: [PATCH 14/14] Address review of New Hampshire LIHEAP Co-Authored-By: Claude Opus 5.5 --- .../fap/heating_cost/fuel_cost_per_mbtu.yaml | 4 + .../doe/fap/heating_cost/fuel_efficiency.yaml | 4 + .../fap/heating_cost/heating_degree_days.yaml | 2 + .../nh/doe/fap/income/business_sources.yaml | 4 +- .../doe/fap/income/investment_disregard.yaml | 15 +- .../nh/doe/fap/income/unearned_sources.yaml | 19 +- .../doe/fap/payment/benefit_multiplier.yaml | 13 +- .../gov/states/nh/doe/fap/smi_rate.yaml | 2 + policyengine_us/programs.yaml | 2 +- .../gov/states/nh/doe/fap/nh_fap.yaml | 157 ++++++++++++++- .../nh/doe/fap/nh_fap_countable_income.yaml | 187 +++++++++++++++--- .../states/nh/doe/fap/nh_fap_eligible.yaml | 64 ++++-- .../nh/doe/fap/nh_fap_heating_cost.yaml | 63 ++++-- .../variables/gov/states/nh/doe/fap/nh_fap.py | 37 ++-- .../nh/doe/fap/nh_fap_countable_income.py | 34 +++- .../states/nh/doe/fap/nh_fap_heating_cost.py | 20 +- source_conflicts/candidates.yaml | 10 +- 17 files changed, 513 insertions(+), 124 deletions(-) diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/fuel_cost_per_mbtu.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/fuel_cost_per_mbtu.yaml index a5f7621580d..3e390f72fe8 100644 --- a/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/fuel_cost_per_mbtu.yaml +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/fuel_cost_per_mbtu.yaml @@ -13,6 +13,10 @@ ELECTRICITY: 2026-10-01: 64.706 PROPANE: 2026-10-01: 42.497 +# SOLAR takes the electricity row because heating_expense reads a solar +# home's bill as its grid electricity bill. +SOLAR: + 2026-10-01: 64.706 # Unsupported fuel contributes no estimate, not a legal zero cost. UNAVAILABLE: 2026-10-01: 0 diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/fuel_efficiency.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/fuel_efficiency.yaml index f595d46c797..69bf0e0deae 100644 --- a/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/fuel_efficiency.yaml +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/fuel_efficiency.yaml @@ -14,6 +14,10 @@ ELECTRICITY: 2026-10-01: 1 PROPANE: 2026-10-01: 0.78 +# SOLAR takes the electricity row because heating_expense reads a solar +# home's bill as its grid electricity bill. +SOLAR: + 2026-10-01: 1 # A safe denominator for the zero-cost computational fallback. UNAVAILABLE: 2026-10-01: 1 diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/heating_degree_days.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/heating_degree_days.yaml index 86690891a50..70dc97688ca 100644 --- a/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/heating_degree_days.yaml +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/heating_cost/heating_degree_days.yaml @@ -32,3 +32,5 @@ metadata: href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=77 - title: Community Action Partnership of New Hampshire, Agency County Service Areas href: https://www.capnh.org/home-old + - title: Community Action Partnership of New Hampshire, Agency County Service Areas (Wayback Machine capture, June 13, 2026) + href: https://web.archive.org/web/20260613220254/https://www.capnh.org/home-old diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/income/business_sources.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/income/business_sources.yaml index 07e3289fa2c..e491b5037f6 100644 --- a/policyengine_us/parameters/gov/states/nh/doe/fap/income/business_sources.yaml +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/income/business_sources.yaml @@ -1,4 +1,6 @@ -description: New Hampshire includes these reported business income sources under the Fuel Assistance Program, excluding each source's losses. +description: New Hampshire counts these business income sources under the Fuel Assistance Program. +# Each source's losses count as zero (page 26), so a loss cannot offset +# another source. values: 2026-10-01: - self_employment_income diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/income/investment_disregard.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/income/investment_disregard.yaml index d3eed690985..867c230a094 100644 --- a/policyengine_us/parameters/gov/states/nh/doe/fap/income/investment_disregard.yaml +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/income/investment_disregard.yaml @@ -1,11 +1,10 @@ -description: PolicyEngine applies this annual exclusion once to each SPM unit's combined interest and dividend income under the New Hampshire Fuel Assistance Program. -# Modeling convention: apply one $50 annual exclusion per SPM unit to -# combined interest and dividends, as requested by the user. -# The PY2027 manual lists the first $50 of interest and dividends separately -# on pages 15-16, but page 17 combines them as interest "and/or" dividends. -# It does not specify whether the allowance applies per person or household. -# The combined-source, SPM-unit basis is an explicit modeling assumption -# pending agency clarification. +description: New Hampshire excludes this amount of annual interest and dividend income under the Fuel Assistance Program. +# One $50 exclusion applies to combined interest and dividends per SPM unit, +# following page 17's allowable expense "$50 of earned interest and/or +# dividends annually". Page 15 counts "Interest over $50 a year" and +# "Dividends over $50 a year", and page 16 excludes "the first $50" of each, +# which would allow up to $100. None of these pages says whether the amount +# applies per person or per household. values: 2026-10-01: 50 diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/income/unearned_sources.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/income/unearned_sources.yaml index 189588d54d2..937869e9ca5 100644 --- a/policyengine_us/parameters/gov/states/nh/doe/fap/income/unearned_sources.yaml +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/income/unearned_sources.yaml @@ -1,4 +1,6 @@ -description: New Hampshire includes these unearned income sources under the Fuel Assistance Program before its source-specific SSI and investment income exclusions. +description: New Hampshire counts these unearned income sources under the Fuel Assistance Program. +# nh_fap_countable_income adds adult SSI, FANF and interest and dividends +# above the $50 exclusion to these sources. values: 2026-10-01: - social_security @@ -12,7 +14,6 @@ values: - workers_compensation - disability_benefits - nh_ssp - - general_assistance - strike_benefits - financial_assistance - alimony_income @@ -25,14 +26,16 @@ values: # identify VA Aid and Attendance, educational VA benefits, special-needs # trusts, or irregular injury/insurance payments. Financial assistance lacks # the required six-month regularity flag. nh_ssp holds the APTD, ANB and OAA -# state welfare payments; general assistance approximates other non-FANF state -# welfare. FANF is included separately once per SPM unit. +# state welfare payments, FANF is included separately once per SPM unit, and +# RCA has no input. Page 15's state welfare payments are APTD, ANB, FANF, OAA +# and RCA, and plan item 1.9 leaves General Assistance benefits unchecked, so +# municipal general assistance (RSA 165) is not counted. # Rental inputs are treated as net and nonnegative without reconstructing # business expenses. Pension income includes annuities and insurance income. # Gambling winnings approximate the taxable gambling and lottery category. -# Military pay is part of wages and is not added again. Missing taxable grants, -# adoption subsidies, royalties and tribal receipts are not imputed; balances, -# capital gains and regular child support received are not counted as income. +# Missing taxable grants, adoption subsidies, royalties and tribal receipts +# are not imputed; balances, capital gains and regular child support received +# are not counted as income. metadata: unit: list period: year @@ -40,3 +43,5 @@ metadata: reference: - title: "PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Countable Income, Non-countable Income, and Rental Property Income #page 15-17,23,29" href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=15 + - title: New Hampshire FY2026 LIHEAP State Plan, Section 1.9 + href: https://liheapch.acf.gov/docs/2026/state-plans/NH_Plan_2026.pdf#page=6 diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/payment/benefit_multiplier.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/payment/benefit_multiplier.yaml index afce1f3993f..9c183ec9e99 100644 --- a/policyengine_us/parameters/gov/states/nh/doe/fap/payment/benefit_multiplier.yaml +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/payment/benefit_multiplier.yaml @@ -1,7 +1,9 @@ description: New Hampshire provides this share of the delivered-fuel scheduled benefit under the Fuel Assistance Program. # Whole-dollar half-up rounding reproduces all 24 published utility cells. -# Unsupported fuels have no published multiplier; zero marks an unsupported -# amount, not a legal zero benefit. +# SOLAR takes the electricity row because heating_expense reads a solar +# home's bill as its grid electricity bill. OTHER has no row on page 77 and +# no fuel on the application, and UNSPECIFIED names no fuel; zero marks an +# unsupported amount, not a legal zero benefit. UNSPECIFIED: 2026-10-01: 0 ELECTRICITY: @@ -14,14 +16,15 @@ KEROSENE: 2026-10-01: 1 PROPANE: 2026-10-01: 1 -# Existing WOOD also represents pellets, bio-bricks and winter blends, which -# share the 1.000 multiplier. +# WOOD also represents pellets and bio-bricks, which share the 1.000 +# multiplier. Winter blend is a heating-oil blend entered as FUEL_OIL or +# KEROSENE, which share the same multiplier. WOOD: 2026-10-01: 1 COAL: 2026-10-01: 1 SOLAR: - 2026-10-01: 0 + 2026-10-01: 0.75 OTHER: 2026-10-01: 0 NONE: diff --git a/policyengine_us/parameters/gov/states/nh/doe/fap/smi_rate.yaml b/policyengine_us/parameters/gov/states/nh/doe/fap/smi_rate.yaml index 8ec5067f335..219b33151ba 100644 --- a/policyengine_us/parameters/gov/states/nh/doe/fap/smi_rate.yaml +++ b/policyengine_us/parameters/gov/states/nh/doe/fap/smi_rate.yaml @@ -9,3 +9,5 @@ metadata: reference: - title: "PY2027 New Hampshire Fuel Assistance Program Procedures Manual, Income Guidelines #page 17,74" href: https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=17 + - title: New Hampshire FY2026 LIHEAP State Plan, Section 2.1 + href: https://liheapch.acf.gov/docs/2026/state-plans/NH_Plan_2026.pdf#page=8 diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 83246a794ee..408f50c8dc4 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -1161,7 +1161,7 @@ programs: variable: nh_fap parameter_prefix: gov.states.nh.doe.fap verified_years: "2027" - notes: PY2027 regular heating, estimating heating cost with bedrooms as the room count; unsubsidized heat in rent receives the ordinary schedule, which may overstate awards. Crisis, cooling, weatherization and equipment assistance are out of scope. + notes: PY2027 regular heating, estimating heating cost with bedrooms (at least one room) as the room count; unsubsidized heat in rent receives the ordinary schedule, which prints no heat-in-rent reduction. Crisis, cooling, weatherization and equipment assistance are out of scope. # --- SSA programs --- - id: ssi diff --git a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap.yaml b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap.yaml index dbf0f70529b..abde032c84d 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap.yaml @@ -3,9 +3,9 @@ # Physical PDF pages: 10, 17, 18, 35, 36, 74, 76, 77, 78. # Period 2027 represents the researched 2026-2027 schedule with 2026 FPG. # Inputs are annual; SSI and FANF totals are explicitly supplied to isolate receipts. -# Final modeled awards use actual or estimated costs and the ordinary fuel schedule. -# No additional heat-in-rent reduction is applied under the approved modeling assumption. -# This assumption is not agency-confirmed; unsupported fuel amounts remain zero. +# Final modeled awards use the larger of the actual and estimated costs and the +# ordinary fuel schedule. Unsubsidized heat in rent gets no further reduction because +# the PY2027 schedule (page 78) prints none; unsupported fuel amounts remain zero. # Manual p78, delivered DD/A cell. Actual annual primary-fuel bill selects the row. - name: "Case 1, delivered matrix cost tier DD and income band A." @@ -1823,8 +1823,10 @@ output: nh_fap: 800 -# Manual p78 DD/A amount is750; no actual-cost cap is stated. -- name: "Case 69, actual-cost schedule is not capped at a small positive bill." +# Manual p78 DD/A amount is 750; no actual-cost cap is stated. The one-room Belknap +# estimate, 2,000 * 1 * 6,610 / 1,000,000 * 28.881 / 0.78 = 489.50, exceeds the +# 100 bill and also falls in tier DD. +- name: "Case 69, the scheduled award is not capped at a small positive bill." period: 2027 absolute_error_margin: 0.01 input: @@ -2152,7 +2154,7 @@ nh_fap_heating_cost: [561.365854, 4_000] nh_fap: [563, 1_875] -# Approved heat-in-rent assumption; this is not an agency-confirmed rent reduction. +# The PY2027 schedule prints no heat-in-rent reduction, so the ordinary schedule applies. # Manual pp76-78: default single-family factor 2000, Belknap HDD 6610, # 3 bedrooms used as rooms, electricity 64.706/MBTU at efficiency 1. # Estimated cost = 2000 * 3 * 6610 / 1_000_000 * 64.706 = 2566.23996. @@ -2195,7 +2197,7 @@ nh_fap: 1_763 # Manual p78 AA/A: delivered 2500; electricity and gas 2500 * 0.75 = 1875. -# Approved assumption applies that fuel schedule once to direct-pay and heat-in-rent. +# That fuel schedule applies once to direct-pay and heat-in-rent households. # Manual p21 excludes subsidized heat-in-rent, not subsidized direct-pay households. - name: Case 80, heat-in-rent retains each fuel schedule while subsidized heat-in-rent is excluded. period: 2027 @@ -2756,3 +2758,144 @@ nh_fap_heating_cost: 2_272.564431 nh_fap_eligible: true nh_fap: 1_410 + +# Manual pp12,18,21,77-78: unsubsidized heat in rent with no bill and no bedrooms. +# Unit 1: one room in Belknap County, +# 2,000 * 1 * 6,610 / 1,000,000 * 28.881 / 0.78 = 489.50, tier DD; 10,000 is within +# 75% of 15,960 (11,970), band A; DD/A = 750. +# Unit 2: an unknown county gives no estimate, so the cost is 0 and no award is paid +# although the household is eligible. +- name: "Case 85, heat in rent with no bill uses the one-room estimate, and no estimate pays nothing." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + person2: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: true + receives_housing_assistance: false + spm_unit2: + members: ["person2"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: true + receives_housing_assistance: false + households: + household1: + members: ["person1"] + state_code: "NH" + county_str: "BELKNAP_COUNTY_NH" + household2: + members: ["person2"] + state_code: "NH" + county_str: "UNKNOWN" + output: + nh_fap_heating_cost: [489.495923, 0] + nh_fap_eligible: [true, true] + nh_fap: [750, 0] + +# Manual pp15,17: Social Security of 28,000 is 2,333.33 a month, or 2,313.33 after +# the $20 exclusion, above the MSP limit (135% of the 2027 one-person guideline, +# about 1,838), so the enrollee pays 202.90 * 12 = 2,434.80. +# 28,000 - 2,434.80 = 25,565.20, 160.2% of 15,960: above 150% (23,940) and within +# 175% (27,930), band E. The 2,000 oil bill is in tier BB; BB/E = 1,100. +- name: "Case 86, the Part B deduction moves a Social Security recipient into a higher-paying band." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + immigration_status: "CITIZEN" + ssi: 0 + nh_ssp: 0 + social_security: 28_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 2_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + medicare_part_b_premium: 2_434.8 + nh_fap_countable_income: 25_565.2 + nh_fap: 1_100 + +# Manual p15 and plan item 1.9: municipal general assistance is not counted. +# 19,500 is 122.2% of 15,960: above 100% and within 125% (19,950), band C. +# The 2,000 oil bill is in tier BB; BB/C = 1,720. +- name: "Case 87, general assistance does not raise the income band." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 19_500 + general_assistance: 1_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 2_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 19_500 + nh_fap: 1_720 + +# A solar home's bill is its grid electricity bill, so SOLAR uses the electricity +# row and utility multiplier. The 1,200 bill exceeds the one-room Belknap estimate +# 2,000 * 1 * 6,610 / 1,000,000 * 64.706 = 855.41 and is in tier CC; 10,000 is +# band A; CC/A = 1,460 * 0.75 = 1,095. +- name: "Case 88, solar heat with an electricity bill receives the utility schedule." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "SOLAR" + heat_expense_included_in_rent: false + receives_housing_assistance: false + pre_subsidy_electricity_expense: 1_200 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_heating_cost: 1_200 + nh_fap: 1_095 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_countable_income.yaml index a81339bdc0f..aab8c32eef7 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_countable_income.yaml @@ -434,7 +434,8 @@ output: nh_fap_countable_income: 800 -# Manual p15 net rent and pp25-29 business treatment, with user-directed nonnegative rental streams. +# Manual p15 net rent and pp25-29 business treatment; each rental stream is floored +# at zero, so a loss on one stream cannot offset another. - name: "Case 15, negative rental income does not offset a separate positive rental stream." period: 2027 absolute_error_margin: 0.01 @@ -489,7 +490,8 @@ output: nh_fap_countable_income: 1_400 -# User-approved convention: one $50 annual exclusion per SPM unit; 50 + 50 - 50 = 50. +# Page 17's "$50 of earned interest and/or dividends" gives one $50 annual exclusion +# per SPM unit; 50 + 50 - 50 = 50. - name: "Case 17, interest and dividends share one fifty-dollar exclusion." period: 2027 absolute_error_margin: 0.01 @@ -603,7 +605,7 @@ output: nh_fap_countable_income: 150 -# Manual p15; annual SSI receipts count once. Medicare reconstruction is expressly deferred. +# Manual p15; annual SSI receipts count once. At age 40 there is no Medicare Part B premium. - name: "Case 21, gross Social Security and adult SSI are both countable." period: 2027 absolute_error_margin: 0.01 @@ -660,8 +662,11 @@ output: nh_fap_countable_income: 6_600 -# Manual p15 counts welfare, regular outside support, nonexempt veterans benefits and strike pay; fixtures represent regular otherwise countable receipts. -- name: "Case 23, reported regular public and private support income counts." +# Manual p15 counts state welfare payments (APTD, ANB, FANF, OAA, RCA), regular +# outside support, nonexempt veterans benefits and strike pay. Plan item 1.9 leaves +# General Assistance benefits unchecked, so the 600 of municipal general assistance +# (RSA 165) is excluded: 1,200 + 1,200 + 600 = 3,000. +- name: "Case 23, municipal general assistance is excluded while regular support, veterans and strike income count." period: 2027 absolute_error_margin: 0.01 input: @@ -687,7 +692,7 @@ members: ["person1"] state_code: "NH" output: - nh_fap_countable_income: 3_600 + nh_fap_countable_income: 3_000 # Manual p15 counts FANF, SSI and Social Security: 1000+2400+4800+3600; FANF is a household annual total. - name: "Case 24, state FANF receipts count once for the household." @@ -752,7 +757,8 @@ output: nh_fap_countable_income: 10_000 -# Manual p17 does not allow generic medical or child-care expenses. Medicare-only reconstruction is deferred. +# Manual p17 does not allow generic medical or child-care expenses. At age 40 there is +# no Medicare Part B premium to deduct. - name: "Case 26, no automatic medical or child-care deduction is applied." period: 2027 absolute_error_margin: 0.01 @@ -781,8 +787,11 @@ output: nh_fap_countable_income: 12_000 -# Manual pp14-17 allows reported Medicare deductions, separately from support paid. -- name: "Case 27, reported Part B reduces Social Security income." +# Manual p17 deducts Medicare premiums withheld from Social Security. Social +# Security of 12,000 is 1,000 a month, or 980 after the $20 exclusion, within the +# Medicare Savings Program limit (135% of the 2027 one-person guideline, about +# 1,838 a month), so MSP pays the premium and nothing is withheld or deducted. +- name: "Case 27, no Part B deduction applies when the Medicare Savings Program pays the premium." period: 2027 absolute_error_margin: 0.01 input: @@ -792,8 +801,7 @@ immigration_status: "CITIZEN" ssi: 0 nh_ssp: 0 - social_security: 24_000 - medicare_part_b_premiums_reported: 2_400 + social_security: 12_000 spm_units: spm_unit1: members: ["person1"] @@ -804,10 +812,14 @@ members: ["person1"] state_code: "NH" output: - nh_fap_countable_income: 21_600 + medicare_part_b_premium: 0 + nh_fap_countable_income: 12_000 -# Manual pp14-17 allows reported Medicare deductions, separately from support paid. -- name: "Case 28, reported Part B reduces Railroad Retirement income." +# Manual pp15,17 deduct the Medicare premium withheld from railroad retirement. +# railroad_benefits is not an SSI unearned source in the model, so MSP status +# turns on assets: 20,000 exceeds the 9,950 MSP resource limit and the enrollee +# pays the 2027 premium of 202.90 * 12 = 2,434.80. 18,000 - 2,434.80 = 15,565.20. +- name: "Case 28, the Part B premium withheld from railroad retirement is deducted." period: 2027 absolute_error_margin: 0.01 input: @@ -818,7 +830,7 @@ ssi: 0 nh_ssp: 0 railroad_benefits: 18_000 - medicare_part_b_premiums_reported: 2_400 + bank_account_assets: 20_000 spm_units: spm_unit1: members: ["person1"] @@ -829,10 +841,13 @@ members: ["person1"] state_code: "NH" output: - nh_fap_countable_income: 15_600 + medicare_part_b_premium: 2_434.8 + nh_fap_countable_income: 15_565.2 -# Manual pp14-17 allows reported Medicare deductions, separately from support paid. -- name: "Case 29, reported support and Part B deductions both apply." +# Manual pp14-17 allow support paid and the withheld Medicare premium separately. +# Social Security of 2,000 a month is above the MSP limit (about 1,838 a month), +# so the premium is 2,434.80: 24,000 - 2,000 - 2,434.80 = 19,565.20. +- name: "Case 29, support paid and the Part B premium are both deducted." period: 2027 absolute_error_margin: 0.01 input: @@ -843,7 +858,6 @@ ssi: 0 nh_ssp: 0 social_security: 24_000 - medicare_part_b_premiums_reported: 2_400 child_support_expense: 2_000 spm_units: spm_unit1: @@ -855,9 +869,10 @@ members: ["person1"] state_code: "NH" output: - nh_fap_countable_income: 19_600 + medicare_part_b_premium: 2_434.8 + nh_fap_countable_income: 19_565.2 -# Manual pp14-17 allows reported Medicare deductions, separately from support paid. +# Manual pp14-17 deductions; 24,000 - 23,000 - 2,434.80 is negative and floors at 0. - name: "Case 30, combined allowed deductions cannot produce negative income." period: 2027 absolute_error_margin: 0.01 @@ -869,7 +884,6 @@ ssi: 0 nh_ssp: 0 social_security: 24_000 - medicare_part_b_premiums_reported: 2_400 child_support_expense: 23_000 spm_units: spm_unit1: @@ -881,10 +895,14 @@ members: ["person1"] state_code: "NH" output: + medicare_part_b_premium: 2_434.8 nh_fap_countable_income: 0 -# Manual pp14-17 allows reported Medicare deductions, separately from support paid. -- name: "Case 31, Medicare age does not create an unreported premium deduction." +# Manual p17 and plan item 1.9 count Social Security net of the Medicare deduction. +# An enrollee aged 70 needs no reported amount: 1,980 a month after the $20 +# exclusion is above the MSP limit (about 1,838), so the premium is +# 202.90 * 12 = 2,434.80 and 24,000 - 2,434.80 = 21,565.20. +- name: "Case 31, a Medicare enrollee's Part B premium is deducted from Social Security." period: 2027 absolute_error_margin: 0.01 input: @@ -895,7 +913,6 @@ ssi: 0 nh_ssp: 0 social_security: 24_000 - medicare_part_b_premiums_reported: 0 spm_units: spm_unit1: members: ["person1"] @@ -906,10 +923,16 @@ members: ["person1"] state_code: "NH" output: - nh_fap_countable_income: 24_000 + medicare_part_b_premium: 2_434.8 + nh_fap_countable_income: 21_565.2 -# Manual pp14-17 deducts each reported allowable premium within its own household. -- name: "Case 32, reported premiums aggregate within each SPM unit." +# Manual pp14-17; each member's withheld premium is deducted within its own SPM +# unit. Separate tax and marital units keep each person's MSP test individual. +# Person1 (Social Security above the MSP limit) and person2 (assets of 20,000 above +# the 9,950 limit) each pay 2,434.80: 24,000 + 18,000 - 2 * 2,434.80 = 37,130.40. +# Person3's 980 a month after the $20 exclusion is within the MSP limit, so MSP +# pays the premium and unit 2 keeps 12,000. +- name: "Case 32, each SPM unit deducts its members' Part B premiums, and none that MSP pays." period: 2027 absolute_error_margin: 0.01 input: @@ -920,21 +943,33 @@ ssi: 0 nh_ssp: 0 social_security: 24_000 - medicare_part_b_premiums_reported: 2_400 person2: age: 68 immigration_status: "CITIZEN" ssi: 0 nh_ssp: 0 railroad_benefits: 18_000 - medicare_part_b_premiums_reported: 1_200 + bank_account_assets: 20_000 person3: age: 70 immigration_status: "CITIZEN" ssi: 0 nh_ssp: 0 social_security: 12_000 - medicare_part_b_premiums_reported: 0 + tax_units: + tax_unit1: + members: ["person1"] + tax_unit2: + members: ["person2"] + tax_unit3: + members: ["person3"] + marital_units: + marital_unit1: + members: ["person1"] + marital_unit2: + members: ["person2"] + marital_unit3: + members: ["person3"] spm_units: spm_unit1: members: ["person1", "person2"] @@ -949,7 +984,8 @@ members: ["person1", "person2", "person3"] state_code: "NH" output: - nh_fap_countable_income: [38_400, 12_000] + medicare_part_b_premium: [2_434.8, 2_434.8, 0] + nh_fap_countable_income: [37_130.4, 12_000] # Each SPM unit in the same household gets its own $50 exclusion. # Unit 1: 40 + 40 - 50 = 30. Unit 2: 25 + 25 - 50 = 0. @@ -1018,3 +1054,90 @@ state_code: "NH" output: nh_fap_countable_income: 1_200 + +# Manual pp15,25-29 count business income, survivor and disability benefits, +# gambling and lottery winnings and net rent. Distinct positive amounts for an +# adult who is not a high-school student make each source visible: +# 3,200 + 1,600 + 800 + 400 + 200 + 100 = 6,300. +- name: "Case 35, S corporation, SSTB, rental, survivor, disability and gambling income each count." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + s_corp_income: 3_200 + sstb_self_employment_income: 1_600 + rental_income: 800 + survivor_benefits: 400 + disability_benefits: 200 + gambling_winnings: 100 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + heat_expense_included_in_rent: false + receives_housing_assistance: false + fuel_oil_expense: 4_000 + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_countable_income: 6_300 + +# Manual p17 deducts Medicare premiums withheld from Social Security or railroad +# retirement, so the deduction cannot exceed those benefits. Pension income of +# 30,000 keeps both enrollees above the MSP limit, so each pays 2,434.80. +# Unit 1: 30,000 + 1,200 - min(2,434.80, 1,200) = 30,000. +# Unit 2: no Social Security or railroad retirement, so 30,000 - 0 = 30,000. +- name: "Case 36, the Part B deduction is capped at the Social Security and railroad benefits." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + immigration_status: "CITIZEN" + ssi: 0 + nh_ssp: 0 + social_security: 1_200 + pension_income: 30_000 + person2: + age: 70 + immigration_status: "CITIZEN" + ssi: 0 + nh_ssp: 0 + pension_income: 30_000 + tax_units: + tax_unit1: + members: ["person1"] + tax_unit2: + members: ["person2"] + marital_units: + marital_unit1: + members: ["person1"] + marital_unit2: + members: ["person2"] + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + spm_unit2: + members: ["person2"] + nh_fanf: 0 + heating_type: "FUEL_OIL" + households: + household1: + members: ["person1"] + state_code: "NH" + household2: + members: ["person2"] + state_code: "NH" + output: + medicare_part_b_premium: [2_434.8, 2_434.8] + nh_fap_countable_income: [30_000, 30_000] diff --git a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml index 789997187b0..0a6b296b81d 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_eligible.yaml @@ -3,7 +3,7 @@ # Physical PDF pages: 12, 17, 18, 21, 35, 36, 74. # Period 2027 represents the researched 2026-2027 schedule with 2026 FPG. # Inputs are annual; SSI and FANF totals are explicitly supplied to isolate receipts. -# Heat-in-rent cases assert eligibility only, not an unresolved final award. +# Heat-in-rent awards are asserted in nh_fap.yaml. # Manual pp12,17 and74; maximum is inclusive. - name: "Case 1, income at the 1-person annual ceiling is eligible." @@ -760,8 +760,10 @@ output: nh_fap_eligible: false -# Manual p12 requires responsibility for energy costs; absent bedrooms leave no proxy estimate. -- name: "Case 20, zero direct cost without heat in rent is ineligible." +# Manual pp12,18: with no bill, the estimate gives the heating cost. With no +# bedrooms the dwelling counts as one room, and with no county input the Belknap +# fallback applies: 2,000 * 1 * 6,610 / 1,000,000 * 28.881 / 0.78 = 489.50 > 0. +- name: "Case 20, a household with no bill and no bedrooms has a one-room heating cost." period: 2027 input: people: @@ -783,9 +785,10 @@ members: ["person1"] state_code: "NH" output: - nh_fap_eligible: false + nh_fap_eligible: true -# Manual p12 has no positive-dollar expense minimum. +# Manual p12 has no positive-dollar expense minimum. An unknown county gives no +# estimate, so the one-cent bill alone establishes the heating cost. - name: "Case 21, a positive direct heating cost satisfies energy responsibility." period: 2027 input: @@ -807,10 +810,11 @@ household1: members: ["person1"] state_code: "NH" + county_str: "UNKNOWN" output: nh_fap_eligible: true -# Manual pp12 and21. Eligibility only: the unresolved heat-in-rent award adjustment is not asserted. +# Manual pp12 and21. Unsubsidized heat in rent establishes energy responsibility. - name: "Case 22, nonsubsidized heat in rent can qualify without a separate bill." period: 2027 input: @@ -911,10 +915,13 @@ output: nh_fap_eligible: true -# Manual pp17,74: 50,000 - 2,000 = 48,000 is below the 48,713 ceiling; -# the identical gross amount without a reported premium remains above it. -- name: "Case 26, reported Medicare deductions affect income eligibility." +# Manual pp17,74: the 2027 Part B premium of 202.90 * 12 = 2,434.80 withheld from +# Social Security is deducted for the enrollee: 50,000 - 2,434.80 = 47,565.20, within +# the 48,713 ceiling. Social Security of 50,000 is far above the MSP limit. The +# person who is not enrolled in Medicare pays no premium and stays above it. +- name: "Case 26, the withheld Part B premium brings an enrollee within the income ceiling." period: 2027 + absolute_error_margin: 0.01 input: people: person1: @@ -923,14 +930,14 @@ ssi: 0 nh_ssp: 0 social_security: 50_000 - medicare_part_b_premiums_reported: 2_000 + medicare_enrolled: false person2: age: 70 immigration_status: "CITIZEN" ssi: 0 nh_ssp: 0 social_security: 50_000 - medicare_part_b_premiums_reported: 0 + medicare_enrolled: true spm_units: spm_unit1: members: ["person1"] @@ -954,10 +961,13 @@ members: ["person2"] state_code: "NH" output: - nh_fap_countable_income: [48_000, 50_000] - nh_fap_eligible: [true, false] + medicare_part_b_premium: [0, 2_434.8] + nh_fap_countable_income: [50_000, 47_565.2] + nh_fap_eligible: [false, true] # Manual pp12,18,21,35: estimate may replace an absent actual bill; retain income, qualified-member, subsidized heat-in-rent, fuel and state requirements. +# Unit 2 has 0 bedrooms and counts as one room: 2,000 * 1 * 7,672 / 1,000,000 * 28.881 / 0.78 = 568.14. +# Unit 7's unknown county gives no estimate. - name: "Case 27, a room proxy establishes heating costs while other gates remain." period: 2027 input: @@ -1109,7 +1119,7 @@ county_str: "COOS_COUNTY_NH" bedrooms: 4 output: - nh_fap_eligible: [true, false, false, false, false, false, false, false, true] + nh_fap_eligible: [true, true, false, false, false, false, false, false, true] # The shared $50 investment exclusion leaves $25 of $75 interest countable; # 48,713 + 25 = 48,738 exceeds the one-person income limit. @@ -1166,3 +1176,29 @@ is_in_public_housing: true output: nh_fap_eligible: false + +# Manual p12 requires responsibility for heating costs. Unsubsidized heat in rent +# passes the cost and housing tests, but heating type NONE means the home has no +# heating, so the household is ineligible. +- name: "Case 30, heat in rent with no heating type is ineligible." + period: 2027 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + employment_income: 10_000 + spm_units: + spm_unit1: + members: ["person1"] + nh_fanf: 0 + heating_type: "NONE" + heat_expense_included_in_rent: true + receives_housing_assistance: false + households: + household1: + members: ["person1"] + state_code: "NH" + output: + nh_fap_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_heating_cost.yaml b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_heating_cost.yaml index 95d181d67d0..63e372ec840 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_heating_cost.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nh/doe/fap/nh_fap_heating_cost.yaml @@ -1,10 +1,12 @@ # Source: PY27 NH Fuel Assistance Program Procedures Manual, physical pages 17, 18, 76, 77. # https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf # Agency service counties: https://www.capnh.org/home-old -# Using bedrooms alone is a user-approved modeling approximation, not the statutory room definition. +# No room-count input exists, so bedrooms stand in for the manual's room count, with at +# least one room for a dwelling. Page 17 also counts kitchens and living rooms, so this +# understates rooms. # Inputs and estimates are annual; actual bills and the estimated cost are not added together. -# Manual pp76-77 formula with the approved bedroom-only proxy: 2000 * 4 * 7672 / 1,000,000 * 28.881 / .78 = 2272.564431. +# Manual pp76-77 formula with bedrooms as the room count: 2000 * 4 * 7672 / 1,000,000 * 28.881 / .78 = 2272.564431. - name: "Case 1, four bedrooms produce a four-room oil estimate without added rooms." period: 2027 absolute_error_margin: 0.01 @@ -322,8 +324,9 @@ output: nh_fap_heating_cost: [897.680723, 807.912651, 628.376506] -# Approved model assumption: no room estimate for nonpositive bedrooms, so actual bills are retained. -- name: "Case 5, zero or negative bedrooms preserve the actual bill fallback." +# A dwelling has at least one room, so zero or negative bedrooms give the one-room +# estimate 2000 * 1 * 7672 / 1,000,000 * 28.881 / .78 = 568.141108; larger bills replace it. +- name: "Case 5, zero or negative bedrooms count as one room below larger actual bills." period: 2027 absolute_error_margin: 0.01 input: @@ -370,7 +373,7 @@ county_str: "COOS_COUNTY_NH" bedrooms: -1 output: - nh_fap_heating_cost: [0, 650, 750] + nh_fap_heating_cost: [568.141108, 650, 750] # Manual p18: compare actual annual cost and design heat load for best benefit; 500 is below the four-bedroom estimate. - name: "Case 6, the estimate is retained when the actual bill is lower." @@ -420,8 +423,11 @@ output: nh_fap_heating_cost: 5_000 -# No proxy for OTHER, SOLAR, UNSPECIFIED or NONE. Canonical OTHER and grid-tied SOLAR bills still permit actual-cost fallback. -- name: "Case 8, unsupported proxy fuels retain available canonical actual bills." +# Page 77 has no OTHER row, and UNSPECIFIED and NONE name no fuel, so they give no +# estimate; the canonical OTHER bill is retained. SOLAR uses the electricity row, +# as heating_expense reads a solar home's grid electricity bill: +# 2000 * 4 * 7672 / 1,000,000 * 64.706 / 1 = 3971.395456, above the 650 bill. +- name: "Case 8, OTHER, UNSPECIFIED and NONE give no estimate while SOLAR uses the electricity estimate." period: 2027 absolute_error_margin: 0.01 input: @@ -481,7 +487,7 @@ county_str: "COOS_COUNTY_NH" bedrooms: 4 output: - nh_fap_heating_cost: [250, 650, 0, 0] + nh_fap_heating_cost: [250, 3_971.395456, 0, 0] # Only the ten NH counties map to published agency HDD; actual-cost fallback remains available. - name: "Case 9, unknown and outside counties do not generate an estimate." @@ -567,8 +573,9 @@ output: nh_fap_heating_cost: 2_272.564431 -# With no room estimate and no positive annual cost, the component is zero. -- name: "Case 12, negative reported bills without bedrooms have zero modeled cost." +# A negative bill floors at zero, and 0 bedrooms count as one room: +# 2000 * 1 * 7672 / 1,000,000 * 28.881 / .78 = 568.141108. +- name: "Case 12, a negative bill without bedrooms leaves the one-room estimate." period: 2027 absolute_error_margin: 0.01 input: @@ -589,10 +596,11 @@ county_str: "COOS_COUNTY_NH" bedrooms: 0 output: - nh_fap_heating_cost: 0 + nh_fap_heating_cost: 568.141108 -# The existing bedrooms zero default disables the approximate room path. -- name: "Case 13, omitted bedrooms preserve existing actual-cost cases." +# Omitted bedrooms default to 0, which counts as one room; the one-room estimate +# 568.141108 exceeds the 500 bill. +- name: "Case 13, omitted bedrooms give the one-room estimate above a smaller bill." period: 2027 absolute_error_margin: 0.01 input: @@ -612,9 +620,9 @@ state_code: "NH" county_str: "COOS_COUNTY_NH" output: - nh_fap_heating_cost: 500 + nh_fap_heating_cost: 568.141108 -# Approved whole-household approximation: both units use two bedrooms; no inferred private-room allocation. Factor 1400 with Coos HDD 7672. +# Whole-household approximation: both units use two bedrooms; no inferred private-room allocation. Factor 1400 with Coos HDD 7672. - name: "Case 14, SPM units reuse household bedrooms with their own fuel." period: 2027 absolute_error_margin: 0.01 @@ -641,3 +649,28 @@ bedrooms: 2 output: nh_fap_heating_cost: [738.216889, 392.956098] + +# A homeless household has no dwelling to heat, so 0 bedrooms give no room and no +# estimate; the bill is also 0. +- name: "Case 15, a homeless household without bedrooms gets no one-room estimate." + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + immigration_status: "CITIZEN" + ssi: 0 + spm_units: + spm_unit1: + members: ["person1"] + heating_type: "FUEL_OIL" + fuel_oil_expense: 0 + households: + household1: + members: ["person1"] + state_code: "NH" + county_str: "COOS_COUNTY_NH" + is_homeless: true + output: + nh_fap_heating_cost: 0 diff --git a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap.py b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap.py index eab60650c14..2984eb3b25f 100644 --- a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap.py +++ b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap.py @@ -9,20 +9,29 @@ class nh_fap(Variable): unit = USD label = "New Hampshire Fuel Assistance Program benefit" documentation = ( - "Modeled regular heating award using actual or bedroom-proxy heating cost. " - "For unsubsidized heat included in rent, the model uses the ordinary fuel " - "schedule without an additional reduction. This approved modeling assumption " - "may overestimate awards: the state plan describes reduced heat-in-rent " - "benefits but does not specify the calculation. Bedrooms alone approximate " - "the program's room count. Verified against the PY2027 schedule; earlier " - "results are unverified historical estimates. Zero can mean unsupported " - "heating cost or fuel classification rather than legal ineligibility." + "Regular heating award from the PY2027 schedule, using the larger of the " + "actual heating cost and the heating-cost estimate, with bedrooms (at " + "least one room for a dwelling) standing in for the room count. " + "Unsubsidized heat included in rent receives the ordinary schedule with " + "no further reduction. The PY2025 manual printed separate heat-in-rent " + "matrices (page 74), but the PY26 benefit matrix and the PY2027 schedule " + "(page 78) print none. On page 9, the FFY2027 draft state plan replaces " + "the FY2026 plan's 'eligible for a reduced benefit' with 'eligible for a " + "benefit', although its item 2.3 still describes reduced heat-in-rent " + "benefits. Verified against the PY2027 schedule; earlier results are " + "unverified historical estimates. Zero can mean an unsupported heating " + "cost or fuel classification rather than legal ineligibility." ) reference = ( # PDF pages 10,17-21,57,74,76-78. "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=10", - # FY2026 plan, section 2.3: heat-in-rent reduction remains unquantified. + # PDF pages 8-9: item 2.3 and the heat-in-rent benefit statement. "https://liheapch.acf.gov/docs/2026/state-plans/NH_Plan_2026.pdf#page=8", + # PY2025 manual, Wayback Machine capture: heat-in-rent matrices. + "https://web.archive.org/web/20250523173906/https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=74", + "https://liheapch.acf.gov/docs/2026/benefits-matricies/NH_BenefitMatrix_2026.pdf#page=1", + # FFY2027 draft plan, PDF pages 8-9. + "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/low-income-home-energy-assistance-detailed-model-plan.pdf#page=8", ) defined_for = "nh_fap_eligible" @@ -60,10 +69,10 @@ def formula(spm_unit, period, parameters): multiplier = p.payment.benefit_multiplier[heating_type] # Half-up whole dollars reproduce every utility matrix cell. benefit = np.floor(delivered_benefit * multiplier + 0.5) - # Approved approximation: unsubsidized heat-in-rent receives this - # scheduled amount, including the fuel multiplier exactly once. - # The state's heat-in-rent reduction remains unverified. - # Unknown, solar and other fuels have no supported payment - # classification; their zero multiplier marks an unsupported amount. + # Unsubsidized heat in rent receives this scheduled amount, with the + # fuel multiplier applied once, because the PY2027 schedule prints no + # heat-in-rent reduction (see the documentation). + # Unspecified and other fuels have no page 77 row; their zero + # multiplier marks an unsupported amount. # The schedule is not capped at the household's actual heating cost. return where(expense > 0, benefit, 0) diff --git a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py index 91e8c49be2f..7abe3728406 100644 --- a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py +++ b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_countable_income.py @@ -7,8 +7,13 @@ class nh_fap_countable_income(Variable): definition_period = YEAR unit = USD label = "New Hampshire FAP countable income" - # PDF pages 13-17,23,25-29,34-36. - reference = "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=13" + reference = ( + # PDF pages 13-17,23,25-29,34-36. + "https://www.energy.nh.gov/sites/g/files/ehbemt551/files/inline-documents/sonh/fuel-assistance-program-procedures-manual.pdf#page=13", + # Plan item 1.9: Social Security "Excluding MediCare deduction"; + # General Assistance benefits unchecked. + "https://liheapch.acf.gov/docs/2026/state-plans/NH_Plan_2026.pdf#page=6", + ) defined_for = StateCode.NH def formula(spm_unit, period, parameters): @@ -21,6 +26,9 @@ def formula(spm_unit, period, parameters): business_income = 0 for source in p.business_sources: business_income = business_income + max_(person(source, period), 0) + # Military basic pay is part of employment_income, the convention under + # which Virginia and Indiana subtract military_basic_pay from AGI, so it + # is not added again. earned_income = max_(person("employment_income", period), 0) + business_income # Secondary school also includes equivalent training in the model; # this conjunction approximates the full-time high-school exclusion. @@ -40,8 +48,8 @@ def formula(spm_unit, period, parameters): 0, ) ) - # Apply one exclusion to combined investment income per SPM unit; - # see the parameter's modeling-convention note. + # Apply one exclusion to combined interest and dividends per SPM unit; + # the parameter comment gives the source basis. interest = max_(add(spm_unit, period, ["interest_income"]), 0) dividends = max_(add(spm_unit, period, ["ordinary_dividend_income"]), 0) investment_income = max_(interest + dividends - p.investment_disregard, 0) @@ -55,10 +63,16 @@ def formula(spm_unit, period, parameters): ) # Support-paid regularity and mileage reimbursements are not observed. support_paid = add(spm_unit, period, ["child_support_expense"]) - # Reported Part B represents the allowable benefit-withheld deduction; - # withholding is not separately observed. Part D and automatic premium - # reconstruction remain unmodeled. - reported_part_b = spm_unit.sum( - max_(person("medicare_part_b_premiums_reported", period), 0) + # Page 17 deducts "Medicare deductions from Social Security or Railroad + # Retirement benefits (Part B and Part D)". Each member's out-of-pocket + # Part B premium (zero when not enrolled or when a Medicare Savings + # Program pays it) is deducted up to the Social Security and railroad + # retirement benefits it is withheld from. No Part D premium variable + # exists, so Part D is not deducted. + withholding_benefits = max_(person("social_security", period), 0) + max_( + person("railroad_benefits", period), 0 ) - return max_(income - support_paid - reported_part_b, 0) + medicare_deduction = spm_unit.sum( + min_(person("medicare_part_b_premium", period), withholding_benefits) + ) + return max_(income - support_paid - medicare_deduction, 0) diff --git a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_heating_cost.py b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_heating_cost.py index ea8d7da5b07..60a91f7ad67 100644 --- a/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_heating_cost.py +++ b/policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_heating_cost.py @@ -14,10 +14,15 @@ class nh_fap_heating_cost(Variable): def formula(spm_unit, period, parameters): p = parameters(period).gov.states.nh.doe.fap.heating_cost bedrooms = spm_unit.household("bedrooms", period) - # Use bedrooms alone as an approved modeling approximation, - # not the program's measured room count. No other rooms are inferred. - # Zero bedrooms cannot distinguish a studio from missing information. - rooms = max_(bedrooms, 0) + # No room-count input exists, so bedrooms stand in for the room count. + # Page 17 counts every room except hallways, unfinished basements, + # garages, attics and closets, and the application form also counts + # kitchen/dining and living/family rooms, so bedrooms understate rooms. + # A dwelling has at least one room, so a studio or an unreported + # bedroom count gets the one-room estimate. A homeless household has + # no dwelling to heat and keeps its reported count. + has_dwelling = ~spm_unit.household("is_homeless", period) + rooms = max_(bedrooms, where(has_dwelling, 1, 0)) # Whole-household bedrooms approximate the SPM unit's dwelling; # private, shared and business-only room adjustments are unobserved. dwelling = spm_unit("nh_fap_dwelling_type", period) @@ -35,8 +40,11 @@ def formula(spm_unit, period, parameters): fuel, "UNAVAILABLE", ) - # WOOD approximates cordwood; pellets, bio-bricks and winter blends - # cannot be distinguished with the existing heating type input. + # WOOD approximates cordwood; pellets and bio-bricks cannot be + # distinguished with the existing heating type input. Winter blend, a + # heating-oil blend, is entered as FUEL_OIL or KEROSENE, so its own + # page 77 cost per MBTU is not used. An unsupported county or fuel + # gives no estimate, whatever the room count. # The million divisor converts the heat-load quantity to MBTU. estimated_cost = ( p.dwelling_factor[dwelling] diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index aee71c6eab0..cceadef0441 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -3968,13 +3968,15 @@ candidates: $185,486 limit but is below the $186,252 floor and receives the $1,720 BB/C award in the PR. pe_us: follows: statute - code: [] + code: + - policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_eligible.py + - policyengine_us/variables/gov/states/nh/doe/fap/nh_fap_income_limit.py + - policyengine_us/parameters/gov/states/nh/doe/fap/smi_rate.yaml prs: - https://github.com/PolicyEngine/policyengine-us/pull/10343 state: unreviewed - note: 'The open PR applies the federal floor; verify the extended limits, since the manual prints 60% SMI amounts - only through eight persons. Code not yet on main: nh_fap_eligible, nh_fap_income_limit, - gov.states.nh.doe.fap.smi_rate.' + note: The open PR applies the federal floor; verify the extended limits, since the manual prints 60% SMI amounts + only through eight persons. - id: us-nh:ssp-permanent-resident-forty-quarters program: ssi-state-supplement years: