Skip to content

Add South Dakota LIEAP regular heating assistance - #9974

Open
hua7450 wants to merge 13 commits into
mainfrom
sd-liheap
Open

hua7450 wants to merge 13 commits into
mainfrom
sd-liheap

Conversation

@hua7450

@hua7450 hua7450 commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

Adds a partial South Dakota LIHEAP regular heating model, which the state runs as the Low Income Energy Assistance Program (LIEAP), using FY2024 through FY2026 schedules. Mixed-status households are eligible: only citizens and eligible noncitizens count toward household size. The model currently counts ineligible members' income in full, overstating it because the operative citizenship proration worksheet is unavailable. The model counts the household's income, tests it against the published limits or all-member SNAP receipt, assigns the county to one of four heating regions, and reads the benefit matrix by region, fuel and income tier. Vendor fuels pay up to the household's unpaid heating charges; wood, coal and kerosene pay the full amount to the household; heat included in rent pays up to 30% of seven months of tenant rent. In FY2026, a 70-year-old in Custer County who heats with natural gas and receives $32,000 a year of Social Security has $29,565.20 of countable income after the $2,434.80 Medicare Part B premium. That income is within the $31,300 one-person limit and above the $19,564 first-tier threshold, so the household receives the $792 region 1, tier 2 natural gas amount against $2,000 of unpaid gas charges. Counting gross Social Security would put the household over the limit, and it would receive $0.

Fixes #9965

No complete benefit year is verified: the published FY2024 and FY2026 benefit amounts are tested, while the citizenship income worksheet and the FY2026 "Mid" thresholds remain source gaps.

Regulatory authority

Eligibility

Requirement Source How modeled
South Dakota residence — defined_for = StateCode.SD
Household size and citizenship Manual pages 9 and 18; FY2026 plan section 17.3, page 34 Manual page 9: "Mixed households are eligible for assistance. Only citizens and eligible aliens are counted as actual HH members." sd_liheap_household_size counts members who meet is_citizen_or_legal_immigrant, with no SNAP-specific waiting periods or bars; the income limit and first-tier threshold use that size. A household needs at least one counted member.
Income limit ARSD 67:15:01:10; FY2026 plan section 2.1, page 9; FY2024 matrix pages 1–5; DSS income-limit pages The annual limit is four times the three-month limit. The rule takes the smaller of 200% of the prior year's poverty guideline and the larger of 150% of that guideline and 60% of state median income from the shared liheap_smi_limit helper (#10019), which floors the four-person amount and the size-scaled amount to the dollar, then floors the three-month amount to the dollar. It reproduces every printed FY2024 limit (sizes 1–15) and FY2026 limit (sizes 1–10); the rule is a reconciled pattern, not an express instruction. FY2025 uses the printed table for sizes 1–10. Larger households follow the rule. The limit is inclusive: $29,160 for one person in FY2024, $30,120 in FY2025 and $31,300 in FY2026.
Categorical income eligibility ARSD 67:15:01:01(1) and 67:15:01:10; FY2026 plan section 1.4a, page 5 The income test passes when every member receives SNAP: a positive January SNAP benefit and a January snap_unit_size equal to the number of SPM unit members, eligible or not. A benefit for only some members does not qualify.
Heating responsibility ARSD 67:15:01:09; manual page 32 A direct heating obligation (has_heating_expense) qualifies. Heat included in rent qualifies with positive tenant rent and no separate bill; zero tenant rent does not. heating_type NONE is ineligible.

Countable income

Item Source How modeled
Earned income ARSD 67:15:01:18(1) and 67:15:01:18.01; FY2026 plan section 1.9, page 6 Counts wages and net self-employment, SSTB, farm operations and partnership/S-corporation income, each floored at zero, with no further business deduction. Excludes the earnings of members under 18, or under 19 while in secondary school.
Unearned income FY2026 plan section 1.9, pages 6–7; ARSD 67:15:01:01(4) and 67:15:01:18(17); manual pages 25, 27–28; energy assistance application, page 5 Counts SSI, pensions, military retirement pay, railroad retirement, survivor benefits, unemployment (total_unemployment_compensation: the reported amount, or modeled state unemployment insurance when none is reported), workers' compensation, veterans benefits, child support received, alimony, rental and farm rental income, strike benefits, general assistance, estate and trust income (estate_income, the manual's "Trusts"), gambling winnings and regular cash help from family or friends (financial_assistance) for members of every age. Floors each source at zero, so a rental, farm-rent or trust loss offsets nothing. Excludes interest, dividends and capital gains.
Social Security ARSD 67:15:01:18(12); manual page 28; FY2026 plan section 1.9, page 6 ("Excluding MediCare deduction") Counts each person's Social Security minus medicare_part_b_premium, floored at zero. The model counts railroad retirement gross.
TANF Manual page 28 Counts the take-up-gated tanf aggregate, which includes sd_tanf.
Ineligible members' income Manual pages 17 and 26 Counted in full. The manual counts only a portion through its Citizenship Income Calculator, whose proration formula is unpublished and unclear: its one example turns $3,000 of eligible members' income and $4,000 of an ineligible member's income into $6,200, matching neither full counting ($7,000) nor SNAP pro rata ($6,000). Counting it in full overstates income for mixed-status households.
Child support paid ARSD 67:15:01:18(20); manual page 25 Subtracts child_support_expense and floors countable income at zero.

Benefit

Each cell lists tier 1 / tier 2 for FY2024 through FY2026.

Schedule Fuels mapped to it Region 1 Region 2 Region 3 Region 4
Natural gas Natural gas $880 / $792 $938 / $844 $1,007 / $907 $1,058 / $953
Electricity Electricity $1,331 / $1,198 $1,410 / $1,270 $1,516 / $1,369 $1,626 / $1,461
Propane Propane $2,499 / $2,249 $2,658 / $2,394 $2,834 / $2,552 $3,066 / $2,762
Fuel oil Fuel oil, kerosene $2,858 / $2,573 $3,018 / $2,715 $3,236 / $2,910 $3,437 / $3,093
Coal and wood Coal, wood $742 / $668 $770 / $694 $812 / $730 $854 / $768
Heat in rent (maximum) Any fuel except NONE $800 $800 $850 $850
  • Tier 1 applies when countable income is at or below the first-tier threshold ("Mid"): 125% of the prior year's guideline, with the three-month amount rounded half up to the dollar, times four. That rule reproduces every printed FY2022 and FY2024 threshold. The FY2026 matrix names Mid without printing it, so the model carries the rule forward: $19,564 for one person in FY2026.
  • Natural gas, electricity, propane and fuel oil pay the smaller of the cell and heating_expense floored at zero. Wood, coal and kerosene pay the full cell to the household.
  • Heat in rent pays the smaller of the regional maximum and 30% of monthly tenant rent for seven months, October through April; $100 a month of rent yields $210.
  • ARSD 67:15:01:35 assigns all 66 counties to the four regions; former Shannon County maps to Oglala Lakota County. An unknown or out-of-state county pays $0. With no county input, the shared county default assigns Aurora County (region 2), so callers should send county.
  • The matrices print the same cells for every household size; size enters only through the limit and the first-tier threshold.
  • The FY2022 and FY2023 matrices print lower cells for three fuels, regions 1–4, tier 1 / tier 2: electricity $1,210 / $1,089, $1,281 / $1,154, $1,378 / $1,244, $1,478 / $1,328; fuel oil $1,905 / $1,715, $2,012 / $1,810, $2,157 / $1,940, $2,291 / $2,062; propane $1,666 / $1,499, $1,772 / $1,596, $1,889 / $1,701, $2,044 / $1,841. The FY2022, FY2023, FY2024 and FY2026 matrices print the same coal and wood, natural gas and heat-in-rent amounts.
  • The PR adds no new inputs.

Years covered

Model year Program year Income limit One-person limit First-tier threshold Direct-payment cells
2024 FY2024, from October 1, 2023 Rule, 2023 guideline $29,160 $18,224 FY2024 matrix
2025 FY2025, from October 1, 2024 Printed table for sizes 1–10; rule above $30,120 $18,824 FY2024 matrix
2026 FY2026, from October 1, 2025 Rule, 2025 guideline $31,300 $19,564 FY2026 matrix

A calendar-year period reads the parameters in force on January 1. Formulas are undated. The programs.yaml entry, South Dakota LIEAP, has status partial and no verified_years; the table lists the schedules the model represents, and no year is verified.

  • FY2025 limits. The FY2025 plan, section 2.1 (page 8), lists 60% of state median income for sizes 1–9 and 150% of the guideline for size 10. The published 2024-2025 limits for sizes 1–3 instead equal 200% of the 2024 guideline, sizes 4 and 5 exceed that cap, and sizes 5–7 round differently. No single rule reproduces all ten, so income/limits/table/amount.yaml prints them and table/in_effect.yaml applies them from October 1, 2024 to September 30, 2025. For four people, the table gives $15,858 a quarter, or $63,432 a year; the rule would give $62,400.
  • FY2026 limits for sizes 4–6. The FY2026 plan, section 2.1 (page 9), sets 60% of state median income for sizes 4–9; applied to the $111,183 FY2026 median, that rate gives three-month limits of $16,677, $19,345 and $22,014. DSS publishes $16,075, $18,825 and $21,575, which equal 200% of the 2025 guideline. The model follows DSS. The plan's size 1–3 rates (54.14%, 55.95% and 57.07% of state median income) equal 200% of the 2025 guideline.
  • Direct-payment cells. payment/direct.yaml carries the FY2022 and FY2023 cells from October 1, 2021 and the FY2024 cells, which the FY2026 matrix repeats, from October 1, 2023. No FY2025 matrix is published; the FY2025 plan's $668 minimum and $3,437 maximum (section 2.6, page 9) match the FY2024 cells.
  • Earlier and later years. Region lists, income source lists and heat-in-rent amounts carry one October 2025 date and backfill to earlier years. Model years 2022 and 2023 use the FY2022 and FY2023 cells, but the income rule does not reproduce every printed FY2022 and FY2023 limit, which used different rounding, so those years stay unverified. The FY2021 plan, section 2.6, prints a $334 minimum benefit against $668 in every later plan, so years before 2022 are unverified backfilled estimates. The FY2027 draft plan (status "Saved") keeps the $668–$3,437 range (section 2.6, page 10) and lists 60% of state median income for sizes 4–10 (section 2.1, page 9); its size 1–3 rates (52.02%, 53.94% and 55.13%) match 200% of the 2026 guideline to within 0.01 point. The model carries the rule forward, so in model year 2027 sizes 4–6 follow the 200% cap ($66,000, $77,360 and $88,720), where the draft's 60% of the $118,003 median gives $70,800, $82,128 and $93,456. FY2027 is not claimed; sizes 4–6 need another check once DSS posts its 2026-2027 limits.

Not modeled and source conflicts

Item Source Model treatment
Proration of ineligible members' income Manual page 17 Not modeled; the income is counted in full (see Countable income).
Medicare premiums beyond Part B on Social Security Manual pages 25 and 28 The manual subtracts Part A and Part D premiums from pensions and Part B and Part D premiums from VA benefits and Social Security. The model counts pensions and VA benefits gross and has no Part D premium input.
Jury duty pay and long-term care insurance Manual page 27 Not counted (no input).
Overpayment recoveries withheld by other programs and expense reimbursements ARSD 67:15:01:18(4) and (21) Not separated from the counted sources.
Self-employment income from the latest tax return ARSD 67:15:01:10 and 67:15:01:18.01 Uses existing net business inputs.
Income in the three months before application ARSD 67:15:01:10 Annual inputs approximate it.
Seasonal unpaid charges FY2026 plan section 1.1, page 4; manual page 30 heating_expense is treated as unpaid charges from gas and electric meter reads between October 1 and May 15, or propane and fuel oil fills between July 1 and April 30. The model does not observe charge dates or balances.
Heat-in-rent months Manual pages 31–32 Assumes the same tenant rent in all seven months and a signed landlord agreement; rent changes and months already paid are not tracked.
Tribal service areas, institutional residents, duplicate awards and verification ARSD 67:15:01:08.01 and 67:15:01:39 The model assumes an independent, noninstitutional household that the state serves, with verification complete and no earlier award.
OTHER (including corn or pellet stoves), SOLAR and unspecified direct fuels Manual page 31; FY2026 matrix No matrix price; pay $0 as a coverage gap (#9754 for unspecified fuel).
Crisis, cooling, weatherization and furnace repair FY2026 plan section 1.1, page 4 Out of scope.

Where sources differ or are silent, the model reads them as follows:

Topic Other source Source followed and model
Gambling winnings FY2026 plan section 1.9 (page 6) leaves "One-time lump-sum payments, such as rebates/credits, winnings from lotteries" unchecked Manual page 27 counts lottery or gaming winnings in full; the model counts gambling_winnings.
Cash gifts FY2026 plan section 1.9 (page 6) leaves "Cash gifts" unchecked ARSD 67:15:01:18(17) excludes only "Cash gifts for recurring occasions such as birthdays and Christmas"; manual page 25 counts "Cash Gifts (Monthly)" listed for all three months, and the application (page 5) lists "Money from Family or Friends" as gross income. The model counts financial_assistance and reads an annual amount as regular help.
Kerosene payment method ARSD 67:15:01:40 names only wood and coal for direct household payments. The application (page 4) lists "Fuel Oil/Kerosene" with a supplier fill ticket and says "The payment for Heat in Rent, Wood, or Coal will be either direct deposited or put on an EPC card for you." The DSS page says "the payment is made directly to the energy supplier." Manual page 31 pays wood, coal and kerosene households the entire benefit by check, and neither the rule nor the application bars a check for kerosene; the model pays kerosene in full at the fuel oil amount.
Categorical eligibility Manual page 12 also covers members whose income SNAP considered, Medicare Savings Program households and SSI recipients ARSD 67:15:01:01(1) and FY2026 plan section 1.4a (page 5) require every application member to receive SNAP; the model follows them.
Categorical benefit level Manual page 13: "The reported income counted by the SNAP case in the month of LIEAP application will be multiplied by 3 to determine the benefit level." The model sets the tier from its own countable income.
Self-employment and rental losses ARSD 67:15:01:18.01 counts "any net profit or loss" and treats a self-employment loss as zero, without saying whether a member's businesses or rental income are netted first The model floors each source at zero, so a loss in one source does not offset a gain in another.

Microsimulation: the microsimulation datasets carry no heating inputs, so population totals are about $0 until #9754 lands. Callers must send heating_type with the matching fuel bill.

Test plan

72 YAML cases in 9 files cover:

  • all 40 FY2026 direct-payment cells, nine FY2023 cells, kerosene on the fuel oil schedule, and coal and wood on one schedule;
  • FY2024 limits and first-tier thresholds for sizes 1–15, the printed FY2025 limits for sizes 1–10 plus the size-11 rule, FY2026 limits for sizes 1–15, and FY2026 thresholds;
  • inclusive limits and thresholds at the dollar and one cent above, including the FY2025 four-person limit of $63,432 and quarter rounding against the printed annual column;
  • all 66 county assignments including former Shannon County, and unknown or foreign counties;
  • SNAP categorical eligibility for all members and for only some, permanent residents and refugees, an undocumented member and another state;
  • household size counting only citizens and eligible noncitizens (sizes 3, 2 and 0), a mixed-status household with an undocumented adult that is eligible at size 1 ($880), and one whose undocumented member's $9,000 of wages counts in full (size 2, $27,000, tier 2, $792);
  • heating responsibility through positive rent with no bill; heat-in-rent maximums, the $210 cap on $100 a month of rent, rent summed across members, a subsidized positive share, a zero share, unspecified fuel accepted and NONE rejected;
  • the earnings exclusions at ages 17, 18 and 19 by school status, business loss floors, unearned income at every age, workers' compensation, excluded interest, dividends and capital gains, counted regular cash help from family ($12,000 of wages plus $2,400 of help counts $14,400), unemployment insurance alongside wages ($20,800 plus $6,400 counts $27,200), SSI and TANF, child support paid and its floor, railroad retirement, Social Security net of the Part B premium and a premium larger than the benefit, military retirement pay, trust income and a trust loss, rental and farm-rent losses, gambling winnings, and TANF with and without take-up;
  • final awards for vendor caps, full cash payments for wood, coal and kerosene, a tier change, negative bills, unpriced fuels, the FY2025 four-person household, 2023 and 2024 matrix cells, a retiree with the Part B premium, military retirement pay above the limit and a retiree's rental loss.

Every case specifies heating_type except the four sd_liheap_household_size cases; that variable does not read it. Expected values come from hand arithmetic on the sources. The 71 cases at ee24e71ea5 passed locally; the changes since then were not run locally, and CI validates them. Cases for citizenship income and FY2026 Mid thresholds exercise the documented assumptions, not independently verified policy rules.

  • YAML tests whose expected values come from an external source (statute, official form or worksheet, official calculator)
  • make format
  • Changelog fragment in changelog.d/

axiom: TheAxiomFoundation/rulespec-us#1539 queued

🤖 Generated with Claude Code

hua7450 and others added 5 commits October 3, 2026 19:49
- Encode the published FY2025 three-month income limits (sizes 1-10) as a
  dated table, since no single rule reproduces them, while FY2024 and
  FY2026 keep the rule-based limits.
- Date the direct-payment matrix from October 2021 (FY2022/FY2023 cells)
  and October 2023 (FY2024-FY2026 cells).
- Count Social Security net of the Medicare Part B premium (ARSD
  67:15:01:18(12)), military retirement pay, trust income (manual p25)
  and gambling winnings (manual p27, followed over the plan); floor each
  unearned source at zero.
- Make mixed-status households eligible: only citizens and eligible aliens
  count toward household size (manual p9, p18), and ineligible members'
  income counts in full because the manual's citizenship income
  calculator does not publish its proration formula.
- Count TANF through the take-up-gated tanf variable instead of sd_tanf.
- Put SD on the LIHEAP coverage line; verified_years 2024-2026; short note.
- Add and update tests for each change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	policyengine_us/programs.yaml
@codecov

codecov Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (909176a) to head (cd74fd5).
⚠️ Report is 495 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #9974   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            4         9    +5     
  Lines           76       168   +92     
  Branches         2         3    +1     
=========================================
+ Hits            76       168   +92     
Flag Coverage Δ
unittests 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

hua7450 and others added 6 commits October 7, 2026 22:56
Add workers_compensation to the unearned sources (EA-297 application page 5; ARSD 67:15:01:01(4)) and document the FY2026 plan-versus-DSS income-limit conflict for sizes 4-6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	policyengine_us/programs.yaml
One reference entry per source; single-page citations keep the page only in the href; multi-page titles end with a quoted #page list and the href opens the first cited page; variable references use '# PDF pages ...' comments above multi-page hrefs (CLAUDE.md, PolicyEngine/policyengine-us PR 10009). Reference lines only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Calls liheap_smi_limit from PR 10019 instead of repeating the HHS
floor-then-floor calculation. Limits are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@DTrim99

DTrim99 commented Oct 15, 2026

Copy link
Copy Markdown
Collaborator

PR Review: Add South Dakota LIEAP regular heating assistance

PR #9974: Add South Dakota LIEAP regular heating assistance (author: hua7450, head ee24e71ea5)

Scope: South Dakota's Low Income Energy Assistance Program (LIEAP), the state's LIHEAP regular heating benefit, run by the DSS Office of Energy Assistance, with the FY2024-FY2026 schedules. The PR covers:

  • household size, which counts citizens and qualified noncitizens (mixed-status households are eligible), and countable income: gross earnings of members 18 and over (19 for high school students), each member's unearned income with every source floored at zero, Social Security net of the Medicare Part B premium, take-up-gated TANF, less child support paid;
  • the income limit: an inferred rule, the lesser of 200% of the prior year's poverty guideline and the greater of 150% of it and 60% of state median income (SMI), floored to the quarter, plus the printed FY2025 table for sizes 1-10; and categorical income eligibility when every member receives SNAP;
  • the benefit matrix: five fuel rows by four county regions by two income tiers, with the tier break ("Mid") at 125% of the prior year's guideline;
  • vendor payments up to the household's heating bill, the full award paid to wood, coal and kerosene households, and heat-in-rent payments of 30% of monthly rent for seven months, up to $800 or $850.

Crisis (ECIP), weatherization, furnace repair and tribal service areas are out of scope. The PR changes 39 files: 19 parameters, 9 variables, 9 test files (71 cases), programs.yaml and a changelog fragment. The direct-payment cells are dated 2021-10-01 and 2023-10-01; the limit and tier rule parameters 2024-01-01; the FY2025 table 2024-10-01, switched on by table/in_effect for that year only; everything else 2025-10-01. The tests use periods 2023-2026, and the registry lists South Dakota as partial with no verified_years.

Overall: the policy values are right where the sources print them.

  • All 40 FY2026 direct-payment cells and the four heat-in-rent amounts match the FY2026 matrix, and the FY2024 matrix prints the same cells for every household size. The 24 lower FY2022/FY2023 cells match those matrices.
  • The inferred limit rule reproduces all 15 printed FY2024 limits and all 10 published FY2026 limits. The printed FY2025 table matches figures that each reproduce from HHS's FFY2025 60% SMI amounts and the 2024 guideline. The Mid rule reproduces all 15 printed FY2024 three-month thresholds, and sizes 1-10 for FY2022 and FY2023.
  • All 66 county assignments match ARSD 67:15:01:35 and the manual's region map.
  • The income rules follow ARSD 67:15:01:18 and the manual's income chart. Social Security is counted net of the Medicare premium, which ARSD 67:15:01:18(12) requires and plan item 1.9 confirms ("Excluding MediCare deduction" checked).
  • Every one of the 21 listed income sources is nonzero in at least one case, and each of 16 mutations of the eligibility, income, limit, tier and payment logic breaks at least one case. This is the first PR in the batch with no untested logic.

There are no critical findings. The four should-address items:

  • S1: a merge blocker from main's Integrate modeled state unemployment insurance into income flows #9558. The unearned list reads the raw unemployment_compensation, so main's code-health guard fails on the merged tree. South Dakota has no modeled unemployment insurance yet, so no value changes today.
  • S2: regular cash help from family or friends (financial_assistance) is excluded. ARSD 67:15:01:18 excludes only gifts for recurring occasions, and the manual and the current application count regular help; only plan item 1.9's unchecked "Cash gifts" box supports the PR. Reproduced: $792 at head, $0 with the help counted.
  • S3: twelve parameter descriptions fall outside the house template, and five of them continue with sentences after the program name.
  • S4: the eligibility variable links the 49-page FY2026 plan without a page, two variables list incomplete pages, and the rate parameters do not cite plan item 2.1, which prints the rates.

All 71 cases pass locally at the head and on the merged tree. The model outputs quoted below were reproduced by running the head code, or the merged tree for S1. Values that a proposed fix or another reading would produce are hand computations and are labelled as such. The mutation results come from mutating the code and rerunning the PR's tests.

Path prefixes used below:

  • P/ = policyengine_us/parameters/gov/states/sd/dss/liheap/
  • V/ = policyengine_us/variables/gov/states/sd/dss/liheap/
  • T/ = policyengine_us/tests/policy/baseline/gov/states/sd/dss/liheap/
  • M = LIEAP Policy and Procedures Manual (60 pp, the Clearinghouse's 2018 copy; PDF pages cited)

Line numbers refer to head ee24e71ea5.

Source Documents

# Document Link Use
1 FY2026 South Dakota LIHEAP State Plan (49 pp, report period 10/01/2025-09/30/2026, "Submission Accepted by CO") SD_Plan_2026.pdf 1.1 dates and claim windows (p4); 1.4 Yes (SNAP Yes for heating; TANF and SSI No), 1.4a, 1.5 (p5); 1.8 Gross Income, 1.9 (p6-7); 2.1 thresholds, 2.3 (p9); 2.5, 2.6 $668 / $3,437 (p10); 17.3 (p34). The text layer lacks the checkbox states, so the rendered pages were read
2 FY2025 plan (47 pp, Revision #1) SD_Plan_2025.pdf 1.1 dates (p4); 2.1 60% SMI for sizes 1-9 and 150% of the guideline for size 10 (p8); 2.6 $668 / $3,437 (p9)
3 FY2021-FY2024 plans (accepted) FY2021, FY2022, FY2023, FY2024 2.6 ranges (p9): $334 / $2,291 (FY2021); $668 / $2,291 (FY2022, FY2023); $668 / $3,437 (FY2024)
4 Draft FY2027 plan (48 pp, report period 10/01/2026-09/30/2027, status "Saved") DSS hearing copy 2.1 sizes 1-3 at 52.02%, 53.94% and 55.13% of SMI, sizes 4-10 at 60% (p9); 2.6 $668 / $3,437 (p10). No IIJA text
5 FY2026 Benefit Matrix (1 p) SD_BenefitMatrix_2026.pdf "Own and Rent Cases" (five fuel rows, Reg 1-4, "Income Tier 1 0-Mid" and "Income Tier 2 Mid-Max") and "Heat in Rent Cases"
6 FY2024, FY2023 and FY2022 Benefit Matrices (5 pp each) FY2024, FY2023, FY2022 Cells and "Maximum Income" (Annual, 3-Month), both tiers, sizes 1-15
7 M (60 pp; chapter pages marked "Rev. 8/2012") M Eligibility (p2); household (p3); citizenship (p7-9); time frame and categorical eligibility (p12-13); ineligible members (p17-18); income chart (p24-28); payment (p30-31); heat in rent (p30-32); region map (p52)
8 Application EA-297 (08/25, 6 pp) EA-297 Claim windows and "already been paid" (p2); heating-source categories and the fuels paid to the household (p4); gross income list (p5)
9 DSS Energy and Weatherization Assistance page (read 2026-10-14) dss.sd.gov "2025-2026 Heating Season Income Limits" ($7,825 ... $24,431); "If eligible, the payment is made directly to the energy supplier." No 2026-2027 table yet. The January 14, 2025 capture the PR cites for 2024-2025 could not be fetched (the Wayback Machine blocks automated requests), so its ten values were checked against item 10 and the 2024 guideline
10 HHS LIHEAP IM 2024-02 and IM 2025-02, Attachment 4 FFY2025, FFY2026 South Dakota four-person SMI $105,723 and $111,183 and the 60% amounts for sizes 1-6 (p3) and 7-12 (p6)
11 ARSD chapter 67:15:01 67:15:01 :01(1) categorical eligibility and (4) gross income; :09; :10; :11; :18 exclusions; :18.01; :20; :35 regions; :39 tribal areas; :40 wood and coal; :41-:41.02 heat in rent; :43.01 payment limits

Critical (Must Fix)

None.

Should Address

S1. Merge blocker: after merging main, the unearned list reads the reported-only unemployment_compensation, so main's guard fails

  • Where: P/income/sources/unearned.yaml:14 (- unemployment_compensation).
  • What changed on main: Integrate modeled state unemployment insurance into income flows #9558 (merged 2026-10-13, after this PR's last push on 2026-10-11) added total_unemployment_compensation, which uses the reported amount when present and otherwise the modeled state unemployment insurance programs. It moved every income-source list under parameters/ to the total, including the merged LIHEAP lists (for example nj/dca/liheap/income/sources/unearned.yaml:4), and added policyengine_us/tests/code_health/test_unemployment_compensation_readers.py, which fails on any raw list entry.
  • Reproduced on the git merge-tree result of main 8ae01aa3cb and this head: test_parameter_source_lists_do_not_read_raw_unemployment_compensation fails with one violation, parameters/gov/states/sd/dss/liheap/income/sources/unearned.yaml:14. The file's other three tests pass, and the PR's 71 cases still pass on that tree.
  • Source: plan item 1.9, p. 6 checks "Unemployment insurance"; M p28: "All UI payments received are countable."; EA-297 p5 lists "*Unemployment" as gross income. None separates reported from modeled amounts.
  • Impact: main models unemployment insurance for AL, NJ, NY, OK, PA and UT only (gov.states.household.modeled_state_unemployment_compensation), so for South Dakota households the total equals the reported amount and no value changes today. The guard still blocks the merge, and the raw entry would silently drop South Dakota UI once it is modeled.
  • Why should, not critical: the head is correct against its base and CI is green. git merge-tree shows a clean textual merge, but this has to change before merge (as Add Alabama LIHEAP regular heating assistance #9967 S1, Add Rhode Island LIHEAP regular heating assistance #9972 S1, Add Georgia LIHEAP regular heating assistance #9968 S1 and Add Ohio HEAP regular heating assistance #9975 S1).
  • Fix: replace the entry with total_unemployment_compensation. T/income/sd_liheap_countable_income.yaml Case 7 reports $1,200 of unemployment compensation, so it keeps covering the entry. Update rulespec-us#1539's required-outputs list, which names unemployment_compensation.

S2. Regular cash help from family is excluded, although ARSD 67:15:01:18 excludes only occasion gifts and the manual and application count it

  • Where: P/income/sources/unearned.yaml leaves out financial_assistance ("Cash financial assistance from friends or relatives outside the household"), and T/income/sd_liheap_countable_income.yaml Case 8 pins its exclusion (line 239: "Current planpp6-7 excludes ... gifts").
  • Source:
    • ARSD 67:15:01:01(4) defines gross income as "earned and unearned income before deductions", and ARSD 67:15:01:18 lists the exclusions. For gifts it excludes only "(17) Cash gifts for recurring occasions such as birthdays and Christmas".
    • M p25 counts "Cash Gifts (Monthly)": "If applicant lists gifts for all three months on the application, that will count toward the households income" (Y). One-time gifts are N.
    • EA-297 p5 lists "*Money from Family or Friends" among the gross income to report.
    • The PR's reading rests on plan item 1.9 (p. 6, rendered), which leaves "Cash gifts" unchecked.
  • Impact (reproduced at head): one adult with $27,000 of wages and $6,000 of financial_assistance, Custer County gas, counts $27,000, is eligible and gets $792. Counting the $6,000 gives $33,000, above the $31,300 limit, so $0 (reproduced with $33,000 of wages).
  • The PR follows the manual over plan 1.9 for gambling winnings, but the plan over the rule and the manual here. Its body lists "Recurring monthly cash gifts" as "Not counted" under "Not modeled", although the input exists and Case 8 asserts the exclusion.
  • Why should, not critical: plan item 1.9, an accepted state submission, supports the exclusion, so this is the plan-versus-rule class of Add Oklahoma LIHEAP regular heating assistance #9970 S2.
  • Fix: add financial_assistance to the unearned list (an annual input of regular help is not an occasion gift) and change Case 8's expectation to $14,400 ($12,000 of wages plus $2,400 of help), with a conflict-table row. Or, if DSS confirms it follows the plan box, keep the exclusion and correct the "not modeled" wording and the test comment.

S3. Twelve parameter descriptions fall outside the house template, five with sentences after the program name

All 19 descriptions use South Dakota's own program name, "Low Income Energy Assistance Program", which is fine (Ohio's review accepted "Home Energy Assistance Program", as merged New York HEAP uses). The template's verbs are limits, provides, sets, excludes, deducts and uses, and nothing may follow the program name (#9745 S6). The explanatory sentences can move into YAML comments, as the PR already does elsewhere.

File:line Problem Suggested
P/income/fpg_year_lag.yaml:1 "...for the Low Income Energy Assistance Program. The date records the FY2024 schedule in the annual model, not policy commencement; earlier backfilled years are unverified." ("for", trailing sentence) "South Dakota uses poverty guidelines from this many years before the heating year under the Low Income Energy Assistance Program."
P/income/limits/fpg_lower_rate.yaml:1 "...under the Low Income Energy Assistance Program. This numerical rule reproduces the FY2024 schedule..." (trailing sentence) "South Dakota uses this poverty guideline rate as the lower bound of its income limit under the Low Income Energy Assistance Program."
P/income/limits/fpg_upper_rate.yaml:1 "caps the income limit at this federal poverty guideline rate ... This numerical rule..." (verb, trailing sentence) "South Dakota limits household income to at most this share of the poverty guideline under the Low Income Energy Assistance Program."
P/income/limits/smi_rate.yaml:1 "applies this rate to four-person state median income ... Flooring before size adjustment ... The date is an FY2024 annual-model observation..." (verb, two trailing sentences) "South Dakota limits household income to this share of state median income, within the poverty guideline bounds, under the Low Income Energy Assistance Program."
P/payment/income_threshold_rate.yaml:1 "...under the Low Income Energy Assistance Program. Rounding its three-month equivalent half up reproduces ... The date is the latest explicit FY2024..." (two trailing sentences) "South Dakota uses this poverty guideline rate as the first income tier limit under the Low Income Energy Assistance Program."
P/income/sources/earned.yaml:1 "counts these earned income sources subject to its age and school exclusions" "South Dakota uses these sources as earned income under the Low Income Energy Assistance Program."
P/income/sources/unearned.yaml:1 "counts these unearned income sources for members of all ages" "South Dakota uses these sources as unearned income for members of all ages under the ..."
P/payment/heat_in_rent/months.yaml:1 "covers this many months of tenant-paid heat in rent, from October through April" "South Dakota provides heat-in-rent payments for this many months, October through April, under the ..."
P/payment/regions/region_1.yaml:1 to region_4.yaml:1 (4 files) "assigns these counties to heating region N" "South Dakota uses these counties as heating region N under the ..."

The other seven fit: earned_income_min_age and secondary_school_age_limit (excludes), table/amount and heat_in_rent/rent_share (limits), table/in_effect (uses), direct and heat_in_rent/amount (provides). Consistency: #9967 S4, #9972 S4, #9968 S3, #9975 S5 and #9745 S6.

S4. References: the 49-page plan linked without a page, two incomplete page lists, and the rate parameters do not cite the plan item that prints the rates

File:line Problem Fix
V/eligibility/sd_liheap_eligible.py:13-14 The FY2026 plan link has no anchor, so it opens the cover page. The comment names sections 1.4a and 17.3, which are on p. 5 and p. 34 #page=5, with # PDF pages 5, 34 above it
V/income/sd_liheap_countable_income.py:14 #page=6 for item 1.9, which runs onto p. 7, where the counted "Veterans Administration (VA) benefits" box and the unchecked insurance and child-earnings boxes sit. There is no # PDF pages line # PDF pages 6-7
V/payment/sd_liheap_heat_in_rent_payment.py:14-15 # PDF pages 31-32 leaves out M p30, which states the payment the formula caps: "a LIEAP payment of up to 30% of the monthly rent is paid to the client for unpaid rental obligations until the benefit is exhausted or the heating season ends" #page=30, # PDF pages 30-32
P/income/limits/smi_rate.yaml:8-12, fpg_lower_rate.yaml:8-12, fpg_upper_rate.yaml:8-12 Each cites the FY2024 matrix's dollar limits and the live DSS page. Neither prints a rate. Plan item 2.1 does: "State Median Income ... 60.00%" for sizes 4-9 and "HHS Poverty Guidelines 150.00%" for size 10 (FY2026, p. 9; FY2025, p. 8), and its size 1-3 entries (54.14%, 55.95%, 57.07% of SMI) equal 200% of the 2025 guideline ($31,300, $42,300, $53,300). The DSS page is overwritten each season, so the "FY2026" reference will stop showing FY2026 figures once DSS posts 2026-2027 Add the two plan references (p9 and p8), note the 54.14% equivalence in fpg_upper_rate.yaml, and cite an archived copy of the 2025-2026 DSS table

Consistency: #9972 S5 and #9975 S6 rated missing anchors and incomplete page lists SHOULD.

Suggestions

G1. Kerosene is paid the full award, but the current application pays only heat-in-rent, wood and coal awards to the household

  • V/sd_liheap.py:24-28 pays KEROSENE the full Fuel Oil cell whatever the bill, citing M p31: "LIEAP eligible households whose primary heat source is wood, coal, kerosene and other alternative heat sources, such as corn or pellet stoves, will receive a check directly to them for the entire benefit amount."
  • The current application (EA-297 p4, 08/25) lists "Fuel Oil/Kerosene - Include a fill ticket, monthly statement or a note from your supplier" and says "The payment for Heat in Rent, Wood, or Coal will be either direct deposited or put on an EPC card for you." ARSD 67:15:01:40 names wood and coal for direct payment without barring other fuels, and the DSS page says "the payment is made directly to the energy supplier".
  • Reproduced at head: with $1,500 of unpaid charges and no income in Custer County, fuel oil gets $1,500 and kerosene $2,858.
  • Severity: a suggestion, because the rule does not address kerosene, the DSS manual (p31) expressly pays it the entire benefit by check, and the PR documents the conflict in its conflict table, so the reading is genuinely ambiguous rather than contrary to the rule.
  • The current application points to the vendor cap, as for fuel oil. Either switch to it, or cite the application's wording beside the manual in the variable comment.

G2. Self-employment and rental results may net before the zero floor

  • ARSD 67:15:01:18.01: "the department shall include depreciation and any net profit or loss" and "shall consider that the income earned through self-employment is zero if the self-employment income calculations result in a loss". M p25 computes rental income on "the self-employment rental sheet", from the tax return (ARSD 67:15:01:10).
  • V/income/sd_liheap_countable_income.py:31-37 floors each source separately, so a business gain and a rental loss count the gain in full. Reproduced at head: $15,000 of wages, $6,000 of self-employment income and a $6,000 rental loss count $21,000, above the $19,564 Mid, so tier 2 and $792 for gas. Netting the two gives $15,000, tier 1 and $880 (hand-computed; reproduced with $15,000 of wages alone). Wages are never offset under either reading. Main's Offset SSTB and non-SSTB Schedule C results in federal AGI #9995 now offsets SSTB and non-SSTB Schedule C results in federal AGI.
  • Severity: a suggestion, because ARSD 67:15:01:18.01 does not say whether a member's businesses, or rental income (its own row on M p25, from its own sheet), are netted before the zero floor, and the PR documents its per-source reading in the code comment and the PR body; Add Ohio HEAP regular heating assistance #9975 G4 rated the same point a suggestion.
  • The per-source reading is conservative. Either net a member's self_employment_income, sstb_self_employment_income, farm_operations_income, partnership_s_corp_income, rental_income and farm_rent_income before one floor, or add the reading to the PR body's conflict table (as Add Ohio HEAP regular heating assistance #9975 G4).

G3. Ineligible members' income counted in full (documented)

  • The PR's description of M p17 is accurate: the "Citizenship Income Calculator" example (4 members, 3 eligible, $3,000 and $4,000, adjusted income $6,200) states no formula, and p26 ("using the following formula") gives none either.
  • Reproduced at head: a citizen adult with $30,000 of wages, an undocumented adult with $24,000 and two citizen children (Custer County, gas) have size 3 and count $54,000, above the $53,300 limit, so they get $0. Counting the undocumented adult's wages pro rata (3 of 4 members, $18,000) gives $48,000, eligible at tier 2 for $792; the manual example's ratio ($3,200 of $4,000) gives $49,200, also $792 (both hand-computed).
  • Full counting is $800 above the manual's example, and SNAP-style pro rata ($6,000) is $200 below it. Keeping the conservative reading is defensible; if it stays, the registry note already names the gap. Consider the pro rata reading or ask DSS for the worksheet formula.

G4. Backfilled years and FY2027

  • FY2022 and FY2023 (model years 2022 and 2023, unclaimed): the cells match, and the Mid rule reproduces every printed three-month Mid for sizes 1-10. The limit rule differs from the printed three-month limits by $1 a quarter ($4 a year) for sizes 5-7, 9 and 10 in FY2022 and sizes 5-9 in FY2023, where those years rounded instead of flooring (FY2023 size 7: 73,938 / 4 = 18,484.50, printed 18,485; reproduced period 2023: 73,936). The PR documents this.
  • FY2021 and earlier: the 2021-10-01 cells carry back, but the FY2021 plan, p. 9 prints "Minimum Benefit $334", against $668 in every later plan, so the FY2021 schedule differed at least at the low end. Unclaimed and documented as "unverified backfilled estimates"; record the $334 in the parameter comment or gate pre-FY2022 payments.
  • FY2027: the draft plan (status "Saved") keeps $668 / $3,437 (p10). Its item 2.1 (p9) prints 52.02%, 53.94% and 55.13% of SMI for sizes 1-3, which equal 200% of the 2026 guideline, and 60% of SMI for sizes 4-10. Reproduced at head, period 2027: limits $31,920, $43,280, $54,640, $66,000, $77,360, $88,720, $95,580, $97,704, $99,828, $101,952. Sizes 1-3 and 7-10 match the draft (size 10 switches to 60% of SMI, as the rule does). Sizes 4-6 follow the 200% cap, while the draft's 60% of SMI ($118,003 four-person median) gives $70,800, $82,128 and $93,456 (hand-computed, quarter-floored). That is the same plan-versus-DSS split as FY2026, where DSS published the 200% figures, so revisit when DSS posts the 2026-2027 table (the page still shows 2025-2026 on 2026-10-14). 2027 is not claimed.

G5. Fuels and locations that are eligible but paid $0

  • Reproduced at head (period 2026, no income, Custer County): SOLAR with an electric bill and OTHER with an other-fuel bill are sd_liheap_eligible: true with sd_liheap: 0, and so is an explicit county: UNKNOWN (region 0). T/sd_liheap.yaml Cases 5 and 6 pin these zeros.
  • M p31 pays "other alternative heat sources, such as corn or pellet stoves" "the entire benefit amount", and heating_expense treats SOLAR homes as grid-tied with electric heat, so the Coal & Wood and Electric rows are natural columns. Either pay those, or make the eligibility flag match the $0. Add a # See #9754 note for UNSPECIFIED (Add New Jersey LIHEAP regular heating assistance #9745 G1, Add Alabama LIHEAP regular heating assistance #9967 G2, Add Georgia LIHEAP regular heating assistance #9968 G2, Add Ohio HEAP regular heating assistance #9975 G3).
  • With no county input, the shared formula assigns Aurora County, so the household is priced as region 2 (reproduced: one adult, no income, gas bill: $938). V/payment/sd_liheap_region.py:18-19 says so in a comment; merged New Jersey and Oregon state their first-county fallback in the variable's documentation, which would help callers here.

G6. The base payment's size gate reads spm_unit_size

V/payment/sd_liheap_base_payment.py:49-50 returns 0 when spm_unit_size is 0, which no real SPM unit is, while the limit and tier read sd_liheap_household_size. Only T/payment/sd_liheap_base_payment.yaml Case 5's seventh unit, which sets spm_unit_size: 0 directly, reaches it. Eligibility already requires a counted member, so read sd_liheap_household_size or drop the gate.

G7. PR body, changelog and issue housekeeping

  • The changelog says "regular heating estimates for supported wholly qualified households", but the PR now models mixed-status households (Case 13 pays one).
  • The body says "this PR remains a draft"; the PR is open and ready for review.
  • "Every case specifies heating_type": the four T/eligibility/sd_liheap_household_size.yaml cases do not (harmless, since that variable does not read it).
  • Add conflict-table rows for cash gifts (S2) and for M p13, which sets a categorically eligible household's benefit level from "The reported income counted by the SNAP case ... multiplied by 3"; the model uses its own countable income (rulespec-us#1539 interpretation 8 records it).
  • The registry entry is named "South Dakota LIHEAP" ("South Dakota Low Income Home Energy Assistance Program"), while the labels and descriptions say LIEAP. Merged New York uses its program's own name ("New York HEAP"), which would fit here.
  • rulespec-us#1539 still calls the PR "open as a draft" and says "The tests were not run locally; CI validates them". All 71 pass locally at this head.

G8. Test and comment hygiene

  • About 25 comment lines lost the spaces around their numbers, for example:
    • T/eligibility/sd_liheap_income_limit.yaml:3 ("labels2025-2026season and prints quarterly7825,10575,..."), lines 15 and 28;
    • T/income/sd_liheap_countable_income.yaml:3 ("approximation:12000+600+600+1200+600=15000"), lines 33, 62, 97, 132, 167, 239, 301 and 328;
    • T/payment/sd_liheap_base_payment.yaml:3 ("FY2026matrix physicalp1:5fuelgroups x2income tiers x4regions=40literalcells"), lines 26, 39 and 54;
    • T/payment/sd_liheap_heat_in_rent_payment.yaml:3, 18, 45, 84, 127 and 184; T/payment/sd_liheap_income_threshold.yaml:3 and 16; T/payment/sd_liheap_region.yaml:3 and 41.
  • Comments refer to internal decisions: "approved net-input approximation" (T/income/sd_liheap_countable_income.yaml:3), "the approved eligible-unpaid-expense assumption" (T/sd_liheap.yaml:2), "Approved input assumption" (V/sd_liheap.py:29) and "the approved assumption" (V/payment/sd_liheap_base_payment.py:45), as in Add Rhode Island LIHEAP regular heating assistance #9972 G7. T/payment/sd_liheap_region.yaml:3 cites "sources/sd_county_regions.json", which is not in the repo.
  • T/income/sd_liheap_countable_income.yaml:1 and T/payment/sd_liheap_region.yaml:1 open with "supported income region and heat-in-rent components only", copied across files.
  • The isolated limit, threshold and base-payment tests set heating_type that those variables never read.

Verified correct

  • Matrix (FY2026, rendered):
    • Tier 1 / tier 2 by region 1-4: Coal & Wood 742 / 770 / 812 / 854 and 668 / 694 / 730 / 768; Electric 1331 / 1410 / 1516 / 1626 and 1198 / 1270 / 1369 / 1461; Fuel Oil 2858 / 3018 / 3236 / 3437 and 2573 / 2715 / 2910 / 3093; Natural Gas 880 / 938 / 1007 / 1058 and 792 / 844 / 907 / 953; Propane 2499 / 2658 / 2834 / 3066 and 2249 / 2394 / 2552 / 2762. Heat in rent 800 / 800 / 850 / 850 in every row and tier.
    • The FY2024 matrix prints the same cells for sizes 1-15. The FY2023 and FY2022 matrices print Electric 1210 / 1281 / 1378 / 1478 and 1089 / 1154 / 1244 / 1328, Fuel Oil 1905 / 2012 / 2157 / 2291 and 1715 / 1810 / 1940 / 2062, Propane 1666 / 1772 / 1889 / 2044 and 1499 / 1596 / 1701 / 1841, and the same other rows, as the 2021-10-01 entries hold.
    • Plan 2.6 matches the cells: $668-$2,291 in FY2022-FY2023 and $668-$3,437 in FY2024-FY2026 and the FY2027 draft. No FY2025 matrix is published; carrying the FY2024 cells fits FY2025's identical range.
    • Coal and wood share a row; kerosene is priced from Fuel Oil (EA-297 p4 "Fuel Oil/Kerosene"); household size does not change the cells.
  • Limits:
    • FY2026: all 10 DSS figures reproduce. Sizes 1-6 are 200% of the 2025 guideline ($7,825 ... $21,575 a quarter); sizes 7-9 are HHS's FFY2026 60% SMI amounts ($90,057, $92,058, $94,059), floored to $22,514, $23,014 and $23,514 a quarter; size 10 is 150% ($97,725, so $24,431). The rule's sizes 11-15 continue at 150% (105,972 ... 138,972).
    • FY2024: all 15 printed Max values (29,160 ... 129,808) reproduce with the FFY2024 median ($96,351).
    • FY2025: the printed table holds. Sizes 1-3 are 200% of the 2024 guideline ($7,530, $10,220, $12,910); sizes 4-9 are HHS's FFY2025 60% SMI amounts ($63,433, $73,582, $83,731, $85,634, $87,537, $89,440) divided by 4 and rounded half up; size 10 is 150% ($23,805). Size 11 follows the rule (103,288). The table exists because sizes 4 and 5 exceed the 200% cap and the quarters rounded differently, as the PR says.
    • Federal bounds (42 USC 8624(b)(2)(B)): the rule's inner term is the federal maximum, the greater of 150% of poverty and 60% of SMI, and the 200% cap only lowers it, so every rule-based limit lies between 150% of the guideline and that maximum. The 110%-of-poverty floor cannot bind at any size or year (the lowest printed figure, FY2025 size 10, is 150%).
    • The FY2026 plan-versus-DSS split for sizes 4-6 is documented in V/eligibility/sd_liheap_income_limit.py:38-44 and the PR body, and following DSS's published table is reasonable. Limits are inclusive ("may not exceed").
  • Mid: 125% of the prior year's guideline, rounded half up to the quarter: all 15 FY2024 three-month values (4556 ... 27044, so 18,224 rather than the annual column's 18,225), FY2023 and FY2022 sizes 1-10, and $19,564 for one person in FY2026 (inferred and documented; the FY2026 matrix names "0-Mid" without a number).
  • Timing: FY N runs October 1 of N-1 to September 30 of N (plan 1.1, p4), so period N reads FY N on January 1, with the prior year's guideline and the FFY N median (dated October 1).
  • Income (ARSD 67:15:01:18; M p24-28; plan 1.8-1.9 rendered):
    • Gross income ("earned and unearned income before deductions", ARSD 67:15:01:01(4)); three months approximated by annual income (ARSD 67:15:01:10).
    • Earnings of members under 18, or under 19 in high school, are excluded (:18(1)); their unearned income counts (M p26: "SSI or Social Security are considered the income of the child").
    • Counted, each with a basis: wages and self-employment (1.9; :18.01), SSI, pensions including military retirement, railroad retirement (plan 1.5, p5), non-Social Security survivor benefits, unemployment, workers' compensation (EA-297 p5; not excluded by :18), VA benefits, child support received, alimony, rental and farm rent (M p25), strike pay and general assistance (1.9), trusts as estate_income (M p25) and gambling winnings (M p27, documented against 1.9).
    • Excluded: interest and dividends (1.9 unchecked; M p25 "interest/dividend income from a bank or credit union account" N; M p28 "Stocks and bonds are NOT countable"), capital gains (:18(14)), SNAP.
    • Social Security net of medicare_part_b_premium, floored at zero (:18(12); M p28; 1.9 "Excluding MediCare deduction" checked). TANF through the take-up-gated tanf (M p28). Child support paid deducted (:18(20)) with a final floor.
  • Household: size counts is_citizen_or_legal_immigrant (M p9: "Mixed households are eligible for assistance. Only citizens and eligible aliens are counted as actual HH members"; p18 codes I and Q); the limit and Mid use that size; a unit with no counted member is ineligible.
  • Eligibility: South Dakota residence; categorical income eligibility only when every member receives SNAP (ARSD 67:15:01:01(1), :10; plan 1.4 and 1.4a, p5); responsibility through a vendor bill or heat paid in rent (ARSD 67:15:01:09; M p2); heat in rent with a $0 tenant payment ineligible (M p32); NONE ineligible; no asset test (plan 2.3).
  • Payment: vendor awards go to unpaid charges "until the benefit is exhausted" (M p30; plan 1.1; ARSD 67:15:01:43.01), so min(cell, bill); wood and coal paid directly (ARSD 67:15:01:40); heat in rent "up to 30 percent of the household's monthly rent" (ARSD 67:15:01:41.02) for October-April (M p31), with a positive subsidized tenant share allowed (M p32; plan 2.3).
  • Regions: the four ARSD 67:15:01:35 columns (19, 19, 15 and 13 counties), with Shannon as Oglala Lakota, match the region files and M p52's map.
  • Code patterns: no hard-coded values; defined_for = StateCode.SD on eight variables and sd_liheap gated by sd_liheap_eligible; vectorized formulas; all 19 parameters read; the shared fpg() and liheap_smi_limit() helpers reused; mixed-state rows return 0.
  • Mutation check: each of these, applied alone, fails at least one PR case: dropping the SNAP receipt or SNAP unit-size conjunct, the size gate, the NONE check, either heating-responsibility branch, kerosene's cash route, the expense floor, the base-payment gate, the minor-earnings exclusion, the Social Security premium netting (control), the limit's size mask, the Mid's half-up rounding, the heat-in-rent region and NONE gates, or the FY2025 table's size gate.
  • Tests:
    • 71 of 71 pass locally at head (policyengine-core 3.32.29) and on the merged tree. Periods are plain.
    • Hand-checked against the matrices, plans, DSS tables, HHS tables, ARSD and M:
      • T/sd_liheap.yaml Cases 1-4, 7-10, 12 and 13;
      • T/payment/sd_liheap_base_payment.yaml Cases 1-4, 6 and 7 (all 40 FY2026 cells and nine FY2023 cells);
      • T/eligibility/sd_liheap_income_limit.yaml Cases 1, 2, 3 and 5 (all 41 limits);
      • T/payment/sd_liheap_income_threshold.yaml Cases 1 and 2;
      • T/income/sd_liheap_countable_income.yaml Cases 1-11, 13, 14, 16 and 22;
      • T/payment/sd_liheap_heat_in_rent_payment.yaml Cases 1-5 and 7; T/eligibility/sd_liheap_eligible.yaml Cases 1, 4 and 7; T/payment/sd_liheap_region.yaml Case 1.
  • Changelog: changelog.d/sd-liheap.added.md is top level and typed added (its wording is in G7).

Checklist results (LIHEAP wave 2a)

  1. State plan and manual first: pass. I read the accepted FY2026 plan (rendered checkboxes) and FY2025 plan, item 2.6 of the FY2021-FY2024 plans, the FY2027 draft, the FY2022-FY2026 matrices, M, EA-297, the DSS page, ARSD 67:15:01 and the HHS SMI tables, and verified every encoded cell, limit, threshold, region and branch.
  2. Income definition: pass at head on gross income, the minor-earnings exclusion, Social Security net of Medicare (ARSD 67:15:01:18(12); plan 1.9), per-source loss floors and annual income for the three-month test. Excluding regular family help contradicts ARSD 67:15:01:18 (S2); Schedule-level netting is a reading point (G2). After rebase the list must read total_unemployment_compensation (S1).
  3. Backdating: FY2024-FY2026 reproduce. FY2022-FY2023 cells and Mid reproduce, with $4-a-year limit differences for five sizes (unclaimed, documented). FY2021 and earlier carry FY2022 cells although the FY2021 minimum was $334 (G4). The FY2027 draft plan is published, with no FY2027 DSS table or matrix yet (G4).
  4. Household and arrangement rules: pass on subsidized housing (positive tenant share), heat in rent, renters, categorical eligibility (the rule followed over the manual, documented), no priority, and the $668-$3,437 range. Eligible-but-$0 fuels and locations (G5); kerosene (G1).
  5. Tests: 71 cases, more than 8 hand-checked. FY2024 limits and Mid for sizes 1-15, FY2025 and FY2026 limits for sizes 1-10 (and 11-15 by rule), the limit and tier edges at the dollar and one cent, and plain periods are covered.
  6. Parameter descriptions: twelve off-template, five with trailing sentences (S3).
  7. References: one missing anchor, two incomplete page lists and uncited plan rates (S4).
  8. Wiring: pass. The programs.yaml entry and coverage line are well formed. No aggregate wiring, which matches OR, NY, NJ and MD on main (no LIHEAP program is in the household benefit aggregates). defined_for gating is on every variable. No take-up beyond TANF's.
  9. Axiom: valid queued line. rulespec-us#1539 is open, labelled pe-parity, dispatch-ready and current to ee24e71ea5. It needs updates for S1-S2 (see below).
  10. Branch and CI: all checks are green at head. 11 ahead and 82 behind main. No textual conflicts, but main's UI guard fails on the merged tree (S1).
  11. Integrate modeled state unemployment insurance into income flows #9558: S1. No modeled South Dakota UI exists, so the effect today is the guard failure only.
  12. IIJA and FY2027: no IIJA supplement; none of the FY2021-FY2026 plans or the FY2027 draft mentions one, and the matrices print one amount per cell. The FY2027 draft keeps $668-$3,437 and changes item 2.1 (G4).
  13. Mutation check: pass. All 16 mutations fail at least one case (see Verified correct).
  14. Health premiums after Document disjoint health premium inputs and correct consumer treatment #10046: not applicable. South Dakota deducts only Medicare premiums from benefit income; wages count gross ("REPORT GROSS (amount before deductions) INCOME", EA-297 p5; ARSD 67:15:01:01(4)), so pre_tax_health_insurance_premiums plays no role. medicare_part_b_premium, which the formula reads, is unchanged on main.
  15. State-gated index defaults: pass. sd_liheap_region (int, default 0 outside South Dakota) is clipped to 1-4 before it indexes payment/direct and heat_in_rent/amount, the tier key is always 1 or 2, and sd_liheap_household_size feeds only bracket and helper calls that accept 0. Mixed-state rows (ND and CA beside SD) run in one simulation in five of the PR's test files without error.

CI status

28 of 28 checks pass at ee24e71ea5 (gh pr checks 9974, run of 2026-10-12). The three "skipping" entries belong to a superseded run on the same SHA.

Branch status

Axiom line

axiom: TheAxiomFoundation/rulespec-us#1539 queued: valid format, and the issue meets the bar.

  • rulespec-us#1539 is open and labelled pe-parity (plus enhancement), with no comments, and was last updated 2026-10-12.
  • It is dispatch-ready under the Add AMT parameters for 2020-2023 #1467 standard. It has:
    • proposed us-sd/ module paths with corpus status;
    • verbatim ARSD, plan, matrix, manual, application and DSS text;
    • required outputs for all 9 variables;
    • 14 companion tests derived from the DSS tables, matrices, ARSD, plan, manual and EA-297 ("none is taken from policyengine-us output").
  • It describes head ee24e71ea5 correctly, including the plan-versus-DSS limit conflict (interpretation 1), the inferred Mid (2), kerosene (4), the unpriced fuels (5), the mixed-status gap (7) and the categorical-eligibility conflict (8).
  • Updates needed when the fixes land:
    • the UI input in the required outputs (S1);
    • the cash-help decision for interpretation 9 and the countable-income definition (S2);
    • the FY2027 draft's item 2.1 for interpretation 1 and the FY2021 $334 minimum for interpretation 13 (G4);
    • any change from G1-G3;
    • the stale "open as a draft" and "The tests were not run locally".

Next steps

  1. S1: rebase onto main and switch P/income/sources/unearned.yaml:14 to total_unemployment_compensation.
  2. S2: count financial_assistance and update Case 8, or document the plan-box reading with DSS confirmation.
  3. S3: align the twelve descriptions and move the trailing sentences into comments.
  4. S4: fix the eligibility anchor and the two page lists, and cite plan item 2.1 for the rates.
  5. Optionally address G1-G8, then refresh the PR body, the changelog and rulespec-us#1539 and re-request review.

0 critical and 4 should-address findings, plus 8 suggestions.

Verdict: REQUEST_CHANGES

@DTrim99 DTrim99 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes for 4 should items; details in the full report above. Verified: all 40 FY2026 benefit cells, the heat-in-rent amounts, the limit rule against every published limit, all 66 county regions and the ARSD income rules. All 71 tests pass and CI is green. (1) Merge blocker from main's #9558: unearned.yaml:14 reads the raw unemployment_compensation, so the guard fails on the merged tree. (2) Regular cash help from family (financial_assistance) is excluded. ARSD 67:15:01:18 excludes only gifts for recurring occasions, and the manual (p25) and current application count regular help; only plan item 1.9's unchecked box supports the PR. Example: $792 at head, $0 with the help counted. (3) Twelve parameter descriptions are off the house template. (4) References: the FY2026 plan link lacks #page=5, two page lists are incomplete, and the rate parameters don't cite plan item 2.1.

hua7450 and others added 2 commits October 15, 2026 15:13
- S1: Unearned list reads the raw unemployment_compensation (main's #9558 guard fails)
- S2: Regular cash help from family (financial_assistance) is excluded
- S3: Twelve parameter descriptions are off the house template (five have trailing sentences)
- S4: References: plan link without a page, incomplete page lists, rate parameters missing plan item 2.1

Also applies the in-scope suggestions. Tests not run locally; CI validates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@hua7450

hua7450 commented Oct 15, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the review. Fixes pushed in cd74fd5.

The branch also merges upstream/main (PRs 9558 and 10046). PR 10046's pre_tax_health_insurance_premiums does not apply, because South Dakota counts wages gross. No limit falls below 149% of the guideline, so the shared 110% floor gov.hhs.liheap.fpg_floor (42 U.S.C. 8624(b)(2)(B)) never binds here, and this PR does not add it. The suite now has 72 cases; the new and changed expected values are hand-computed, and CI validates them.

  • S1: income/sources/unearned.yaml now lists total_unemployment_compensation, so no raw entry is left for main's guard. South Dakota has no modeled unemployment insurance, so no value changes. New income Case 23 counts $20,800 of wages and $6,400 of benefits as $27,200 ($400 a week, 1/26 of a $10,400 high quarter under the DLR rule, for 16 weeks).
  • S2: done as you proposed. financial_assistance is in the unearned list, with a comment that follows ARSD 67:15:01:18(17), manual page 25 and EA-297 page 5 over plan item 1.9's unchecked box. Income Case 8 now expects $14,400 ($12,000 of wages plus $2,400 of help). The PR body adds a cash-gifts conflict row and drops cash gifts from "Not modeled".
  • S3: the twelve descriptions use your suggested text, and the trailing sentences are now YAML comments. All 19 descriptions are one sentence ending in "under the Low Income Energy Assistance Program."
  • S4: sd_liheap_eligible opens the FY2026 plan at page 5 with # PDF pages 5, 34, sd_liheap_countable_income adds # PDF pages 6-7, and sd_liheap_heat_in_rent_payment opens manual page 30 with # PDF pages 30-32. smi_rate and fpg_lower_rate cite plan item 2.1 (FY2026 page 9, FY2025 page 8). fpg_upper_rate cites FY2026 page 9 only, since FY2025 prints 60% of SMI for sizes 1-3, and its comment shows that 54.14%, 55.95% and 57.07% equal 200% of the 2025 guideline. The live DSS page is replaced in all five places by the January 1, 2026 Wayback capture of the 2025-2026 table.
  • G1: kerosene keeps the manual's full payment. Manual page 31 is the only source that names kerosene in a payment rule, and neither ARSD 67:15:01:40 nor the application bars a check. The sd_liheap comment now quotes EA-297 page 4 beside the manual and the rule, and the PR body's kerosene row adds the application and DSS page wording.
  • G2: documented in the PR body's conflict table. The per-source floors stay, since this batch treats rental income as non-negative and makes no self-employment netting changes.
  • G3: kept full counting. The Citizenship Income Calculator formula is unpublished, and its one example matches neither full counting nor pro rata; the code comment, PR body and registry note record the gap.
  • G4: partly done. History is out of scope for this PR, so FY2021-FY2023 are not gated or re-encoded; payment/direct.yaml now notes the FY2021 plan's $334 minimum. For FY2027, a sd_liheap_income_limit comment cites the draft's item 2.1 and flags sizes 4-6 for a recheck when DSS posts its 2026-2027 limits. The PR body gives the model year 2027 values: sizes 4-6 follow the 200% cap ($66,000, $77,360, $88,720) where the draft's 60% gives $70,800, $82,128 and $93,456.
  • G5: partly done. Fuels without a matrix row stay eligible at $0 as a documented coverage gap: the FY2026 matrix prices five fuel rows, no South Dakota source maps SOLAR or OTHER to one, and manual page 31 sets only the payment method for corn and pellet stoves. The base-payment comment now points to Default heating_type to electricity and remove the UNSPECIFIED-gated state heating adapters #9754 for UNSPECIFIED. sd_liheap_region gained a documentation string, as in New Jersey, stating that a household with no county input takes Aurora County (region 2).
  • G6: done. sd_liheap_base_payment drops the spm_unit_size gate, because sd_liheap_eligible already requires a counted member. Base-payment Case 5 loses its size-0 column, and Cases 1-4 and 7 lose the unused spm_unit_size inputs.
  • G7: done. The changelog now names mixed-status households. The registry entry is South Dakota LIEAP (South Dakota Low Income Energy Assistance Program) with a two-sentence note. The PR body drops the draft sentence and says every case except the four household-size cases sends heating_type. It also adds conflict rows for cash gifts and for manual page 13's SNAP income times three.
  • G8: partly done. The listed comments are rewritten with spaced, hand-checked arithmetic, the "approved" wording and the sources/sd_county_regions.json reference are gone, and the copied file headers are replaced. heating_type stays in the isolated limit, threshold and base-payment tests: sd_liheap_base_payment reads it, and the batch sends it in those fixtures so none depends on the default.
  • LIHEAP wave 2a: encode South Dakota LIEAP regular heating assistance TheAxiomFoundation/rulespec-us#1539 is refreshed to this head.

@hua7450
hua7450 requested a review from DTrim99 October 15, 2026 21:08

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement South Dakota LIEAP regular heating assistance

2 participants