Repository navigation
Conversation
- 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 Report✅ All modified and coverable lines are covered by tests. 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
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
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>
PR Review: Add South Dakota LIEAP regular heating assistancePR #9974: Add South Dakota LIEAP regular heating assistance (author: hua7450, head 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:
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), Overall: the policy values are right where the sources print them.
There are no critical findings. The four should-address items:
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:
Line numbers refer to head Source Documents
Critical (Must Fix)None. Should AddressS1. Merge blocker: after merging main, the unearned list reads the reported-only
|
| 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-28pays 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-37floors 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_incomeandfarm_rent_incomebefore 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-01cells 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: truewithsd_liheap: 0, and so is an explicitcounty: UNKNOWN(region 0).T/sd_liheap.yamlCases 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_expensetreats 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 #9754note 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-19says so in a comment; merged New Jersey and Oregon state their first-county fallback in the variable'sdocumentation, 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 fourT/eligibility/sd_liheap_household_size.yamlcases 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:3and 16;T/payment/sd_liheap_region.yaml:3and 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:3cites "sources/sd_county_regions.json", which is not in the repo. T/income/sd_liheap_countable_income.yaml:1andT/payment/sd_liheap_region.yaml:1open with "supported income region and heat-in-rent components only", copied across files.- The isolated limit, threshold and base-payment tests set
heating_typethat 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-01entries 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-44and 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-gatedtanf(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.SDon eight variables andsd_liheapgated bysd_liheap_eligible; vectorized formulas; all 19 parameters read; the sharedfpg()andliheap_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.yamlCases 1-4, 7-10, 12 and 13;T/payment/sd_liheap_base_payment.yamlCases 1-4, 6 and 7 (all 40 FY2026 cells and nine FY2023 cells);T/eligibility/sd_liheap_income_limit.yamlCases 1, 2, 3 and 5 (all 41 limits);T/payment/sd_liheap_income_threshold.yamlCases 1 and 2;T/income/sd_liheap_countable_income.yamlCases 1-11, 13, 14, 16 and 22;T/payment/sd_liheap_heat_in_rent_payment.yamlCases 1-5 and 7;T/eligibility/sd_liheap_eligible.yamlCases 1, 4 and 7;T/payment/sd_liheap_region.yamlCase 1.
- Changelog:
changelog.d/sd-liheap.added.mdis top level and typedadded(its wording is in G7).
Checklist results (LIHEAP wave 2a)
- 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.
- 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). - 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).
- 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).
- 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.
- Parameter descriptions: twelve off-template, five with trailing sentences (S3).
- References: one missing anchor, two incomplete page lists and uncited plan rates (S4).
- Wiring: pass. The
programs.yamlentry 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_forgating is on every variable. No take-up beyond TANF's. - Axiom: valid
queuedline. rulespec-us#1539 is open, labelledpe-parity, dispatch-ready and current toee24e71ea5. It needs updates for S1-S2 (see below). - 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).
- 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.
- 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).
- Mutation check: pass. All 16 mutations fail at least one case (see Verified correct).
- 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_premiumsplays no role.medicare_part_b_premium, which the formula reads, is unchanged on main. - State-gated index defaults: pass.
sd_liheap_region(int, default 0 outside South Dakota) is clipped to 1-4 before it indexespayment/directandheat_in_rent/amount, the tier key is always 1 or 2, andsd_liheap_household_sizefeeds 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
- 11 commits ahead of
mainand 82 behind. The merge base is1d699fec2d(Share one HHS SMI helper for LIHEAP income limits #10019); main is at8ae01aa3cb. git merge-tree --write-tree PolicyEngine/main ee24e71ea5exits 0 with no textual conflicts.programs.yamlmerges tocoverage: DC, Riverside County, MA, IL, IN, KS, KY, MD, MS, NC, NE, NJ, NY, OR, SD, TX.- Semantic conflict: main's Integrate modeled state unemployment insurance into income flows #9558 guard fails on the merged tree (S1). The PR's 71 cases pass there.
- None of the files defining the inputs the South Dakota variables read changed on main since the merge base. Document disjoint health premium inputs and correct consumer treatment #10046 and Uprate premium inputs by premium series, not CMS out-of-pocket spending #10085 touched
medicare_part_b_premiums_reported(documentation and uprating), whichmedicare_part_b_premiumdoes not read. - The wave 2a siblings (Add Alabama LIHEAP regular heating assistance #9967-Add Ohio HEAP regular heating assistance #9975) touch the same LIHEAP coverage line, which is a branch note, not a finding.
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(plusenhancement), 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").
- proposed
- It describes head
ee24e71ea5correctly, 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
- S1: rebase onto main and switch
P/income/sources/unearned.yaml:14tototal_unemployment_compensation. - S2: count
financial_assistanceand update Case 8, or document the plan-box reading with DSS confirmation. - S3: align the twelve descriptions and move the trailing sentences into comments.
- S4: fix the eligibility anchor and the two page lists, and cite plan item 2.1 for the rates.
- 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
left a comment
There was a problem hiding this comment.
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.
- 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>
|
Thanks for the review. Fixes pushed in cd74fd5. The branch also merges upstream/main (PRs 9558 and 10046). PR 10046's
|
PavelMakarchuk
left a comment
There was a problem hiding this comment.
Program Review
IDs: C = critical, S = should address, G = suggestion. S1-S4 and G1-G8 keep the prior review's numbers; C1 and G9-G17 are new.
Source Documents
- PDF: FY2026 South Dakota LIHEAP State Plan (49 pages); FY2025 plan (47 pages); draft FY2027 plan (48 pages)
- PDF: LIEAP Policy and Procedures Manual, 2018 copy, chapters Rev. 8/2012 (60 pages)
- PDF: Application EA-297, 08/25 (6 pages); FY2026 Benefit Matrix (1 page)
- Rule: ARSD 67:15:01 (:01, :18, :40, :41.02, :43.01, read 2026-10-19)
- Web: DSS energy assistance page. The Wayback capture
20260101052007cited in 5 places could not be fetched from this network (intercepted TLS). The live page shows the same 2025-2026 table, so the capture is unverifiable here, not wrong. - Year: FY2024-FY2026 (model years 2024-2026); FY2027 not claimed
- Scope: Incremental — changes since the prior review, plus unresolved prior findings
- Reviewed head SHA: cd74fd5
- Mode: incremental from ee24e71
Paths: P/, V/, T/ = gov/states/sd/dss/liheap/ under policyengine_us/parameters, policyengine_us/variables and policyengine_us/tests/policy/baseline. Pages are PDF file pages.
Branch Status
13 commits ahead of main and 42 behind; merges cleanly. Informational only: the staleness did not affect any finding.
Critical (Must Fix)
C1. Kerosene is paid the full award, but the state plan pays only coal, wood and heat in rent to the household
- Defect:
V/sd_liheap.py:29-33putsKEROSENEincash_payment, so line 39 pays the full Fuel Oil cell whatever the unpaid charges. Other vendor fuels getmin(cell, heating_expense). The comment at:23-28says "Neither bars a check for kerosene, so the manual is followed."T/sd_liheap.yamlCase 2 pins $2,858 on a $1 bill. - Evidence:
- Plan item 9.1 asks "Do you make payments directly to home energy suppliers?" Heating: Yes. Exceptions: Yes, and it lists them: "If household uses coal or wood as it's primary heat source or a household's heat is included in rent, a payment is issued directly to the household." Same text in FY2026 (p. 22), FY2025 (p. 21), FY2024 (p. 21) and the FY2027 draft (p. 22).
- ARSD 67:15:01:40 names only "wood or coal" for direct payment. ARSD 67:15:01:43.01 makes payment "to the energy supplier or landlord" the default.
- EA-297 lists "Fuel Oil/Kerosene" with a supplier fill ticket and pays only "Heat in Rent, Wood, or Coal" to the household (p. 4). The DSS page: "the payment is made directly to the energy supplier."
- Only the manual pays kerosene "the entire benefit amount" by check (p. 31).
- The PR prices kerosene from the Fuel Oil row because EA-297 groups "Fuel Oil/Kerosene" (
V/payment/sd_liheap_base_payment.py:31), but does not follow that grouping for the payment method.
- History: round 1 raised this as G1, a suggestion, on EA-297 p. 4, ARSD :40 and the DSS page, and called the reading ambiguous. The author declined because "neither ARSD 67:15:01:40 nor the application bars a check" and documented the conflict, a fair answer to that evidence. Round 1 did not cite plan item 9.1, the state's own list of exceptions to supplier payment for every claimed year. With the supplier default in ARSD :43.01, it answers the author's reason.
- Impact (reproduced at head; one adult, no income, Custer County): kerosene gets $2,858 on a $1 bill (vendor rule: $1) and on a $1,500 bill (fuel oil: $1,500), in 2024, 2025 and 2026. Microsimulation impact is about $0 (no heating inputs, #9754).
- Severity: critical, because it passed code-path and 600-DPI visual verification, changes awards in every claimed year and rests on evidence round 1 did not have (as in #9973); round-1-accepted items on unchanged evidence are not escalated (as in #9972).
- Fix:
- Remove
| (heating_type == fuels.KEROSENE)fromcash_payment. Kerosene then takesmin_(base_payment, expense), as fuel oil does. - Rewrite
V/sd_liheap.py:23-28to cite plan 9.1, ARSD :40 and :43.01 and EA-297 p. 4, and say manual p. 31 is superseded for kerosene. Add plan 9.1 (#page=22) and ARSD 67:15:01:40 toreference. T/sd_liheap.yamlCase 2: KEROSENE2858becomes1; rename the case. Case 8 (2023) stays $1,905 (min($1,905, $3,000)); fix only its comment at:168and the header at:2-3.- Update the PR body's kerosene row and rulespec-us#1539 interpretation 4.
- Remove
Should Address
None. Round-1 S1-S4 are resolved.
Suggestions
- G9. Case 23 (
T/income/sd_liheap_countable_income.yaml:696-700) and Case 22 (:666-669) cite DLR with no URL; link SDCL 61-6-2 and the DLR fact sheet. - G10. Case 23 passes with the raw or total UI input; add
unemployment_compensation: 0andmodeled_state_unemployment_compensation: 6_400(total $27,200, raw $20,800, hand-computed). - G11.
T/payment/sd_liheap_base_payment.yaml:67-70calls the NONE zero a coverage gap, but NONE is ineligible (ARSD 67:15:01:09); the name still says "component placeholders". - G12.
V/eligibility/sd_liheap_income_limit.py:42: $22,014 should be $22,013 under the model's flooring. Note infpg_upper_rate.yamlthat plan p. 9 prints 60% SMI for sizes 4-6. - G13.
P/payment/direct.yaml:8-9: use the full FY2021 plan URL (/sites/default/files/webfiles/docs/SD_Plan_2021.pdf#page=9). - G14. Optional: cite 42 USC 8624(b)(2)(B) on
fpg_lower_rate.yamland the archived DSS table onfpg_year_lag.yaml. - G15. Optional end-to-end pins:
sd_liheap: [938, 938, 938, 0]inT/eligibility/sd_liheap_eligible.yamlCase 3 (hand-computed); a $27,000 wages + $6,000financial_assistancecase giving $0. - G16. Consider a
T/integration.yaml, as the merged state LIHEAP folders have. - G17. Style: drop
absolute_error_marginfrom boolean-only cases; use20_000consistently.
Prior Findings
| ID | Finding | Status |
|---|---|---|
| S1 | Raw UI input fails #9558 guard | RESOLVED; Case 23 added |
| S2 | financial_assistance excluded |
RESOLVED; Case 8 = $14,400 |
| S3 | Off-template descriptions | RESOLVED |
| S4 | Anchors, page lists, plan 2.1, live DSS link | RESOLVED |
| G1 | Kerosene paid in full | STILL OPEN: declined by author; superseded by C1 |
| G2 | Self-employment and rental netting | RESOLVED (documented) |
| G3 | Ineligible members' income in full | RESOLVED (documented) |
| G4 | Backfill and FY2027 | RESOLVED (documented) |
| G5 | SOLAR, OTHER, UNSPECIFIED at $0 | STILL OPEN (partial): documented coverage gap, acceptable |
| G6 | Base-payment size gate | RESOLVED |
| G7 | Changelog, body, registry | RESOLVED |
| G8 | Hygiene | STILL OPEN (partial): T/income/sd_liheap_countable_income.yaml:369-370; isolated-test heating_type declined by author |
PDF Audit Summary
| Category | Count |
|---|---|
| Documents audited | 4 PDFs (162 pages at 300 DPI), DSS page, 4 external PDFs |
| Changed values and claims matched | 36 |
| Mismatches queued | 11 |
| Confirmed (code path and 600-DPI check) | 1 (C1) |
| Investigated and cleared | 10 |
| References checked | 44: 38 correct, 0 wrong, 6 unverifiable |
Investigated and cleared ("R1" = round 1 accepted the same evidence):
- FY2026 sizes 4-6 follow the DSS table over plan 2.1 (R1).
financial_assistancecounted over plan 1.9's box, per ARSD :18 (R1's S2 fix).- Ineligible members' income counted in full; no published formula (R1, G3).
- Manual's "all three months" gift test; annual input cannot carry it (R1).
gambling_winningscounted; ARSD :18 has no exclusion (R1).- OTHER, SOLAR, UNSPECIFIED at $0; no source prices them (G5).
- Categorical tier from SNAP income × 3; ARSD :20 uses one measure (R1).
- FY2027 sizes 4-6; unclaimed draft year (R1, G4).
- Heat in rent for condo fees and hotels (manual p. 30); no input; optional "Not modeled" row.
- Pre-FY2022 cells vs the FY2021 $334 minimum; unclaimed (R1, G4).
Validation Summary
| Check | Result |
|---|---|
| Regulatory accuracy | 1 critical (C1); incremental fixes correct |
| References | Changed anchors correct; Wayback unverifiable here |
| Code patterns | No hard-coded policy values; descriptions on template |
| Test coverage | 72 cases pass; Cases 8 and 23 hand-checked; all branches covered |
| CI | 29/29 pass at cd74fd52d6 |
| Main merge | #9558 guard passes; #10046 not applicable |
| Federal 110% floor | Never binds (lowest limit about 150% of the lagged guideline) |
| Changelog | Present, top level, added |
Review Severity: REQUEST_CHANGES
One confirmed value mismatch (C1) changes kerosene awards in every claimed year.
Next Steps
To auto-fix issues: run the fix-pr workflow for this PR.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for the review. Fixes pushed in f6a8bbf. The suite now has 78 cases in 10 files. The new and changed expected values are hand-computed, and CI validates them.
Thanks for checking the ten cleared items. The PR body adds your optional "Not modeled" row for heat included in condominium fees or hotel charges (manual page 30). The Wayback capture |
PavelMakarchuk
left a comment
There was a problem hiding this comment.
Program Review
IDs: C = critical, S = should address, G = suggestion. C1, S1-S4 and G1-G17 keep the prior review's numbers; G18-G21 are new and optional.
Source Documents
- PDF: FY2026 South Dakota LIHEAP State Plan (49 pages); FY2025 plan (47 pages); FY2024 plan (47 pages, page 21 fetched and rendered this round); FY2021 plan (49 pages, page 9)
- PDF: LIEAP Policy and Procedures Manual, 2018 copy (60 pages)
- PDF: Application EA-297, 08/25 (6 pages); FY2026 Benefit Matrix (1 page)
- PDF: DLR weekly workers' compensation rates (2 pages); DLR reemployment benefits fact sheet
- Rule and statute: ARSD 67:15:01 (:02, :06, :09, :18, :35, :39, :40, :41, :41.02, :43, :43.01; read 2026-10-20); SDCL 61-6-2; SDCL 62-4-3; 42 U.S.C. 8624(b)(2)(B)
- Web: DSS energy assistance page, live and the cited Wayback capture 20260101052007. The capture now loads (HTTP 200 in the cited form and the
id_form, memento 2026-01-01 05:20:07 GMT) and shows the 2025-2026 table, so the prior "unverifiable" note is closed. CMS 2026 Part B fact sheet ($202.90). SSA 2026 COLA fact sheet ($2,071 after 2.8%; the live page returns 403 to scripts, so it was read from a 2025-10-30 Wayback copy). DLR minimum wage page ($11.85 from 2026-01-01). - Year: FY2024-FY2026 (model years 2024-2026); FY2027 not claimed
- Scope: Incremental — author commit
f6a8bbf432and theupstream/mainmergef88aa84a78, plus unresolved prior findings - Reviewed head SHA: f88aa84
- Mode: incremental from cd74fd5
Paths: P/, V/, T/ = gov/states/sd/dss/liheap/ under policyengine_us/parameters, policyengine_us/variables and policyengine_us/tests/policy/baseline. Pages are PDF file pages.
Branch Status
15 commits ahead of main and 17 behind; merges cleanly. Informational only. The merge of main at 1661d715bd changed no SD file, and no shared input that SD reads (heating_expense, has_heating_expense, total_unemployment_compensation, FPG, SMI, Part B).
Prior Findings
| ID | Finding | Status |
|---|---|---|
| C1 | Kerosene paid the full award by check | RESOLVED. V/sd_liheap.py:36 is WOOD or COAL only; kerosene takes min_(base_payment, expense) at :42. Comment :26-35 and reference :13-15 cite plan 9.1, ARSD :40 and :43.01. T/sd_liheap.yaml:45 = [742, 742, 1]; Case 8 stays $1,905 (:170-174); T/integration.yaml:129 = $900. Putting KEROSENE back fails exactly these 2 cases. |
| G1 | Kerosene paid in full (round-1 form) | RESOLVED (closed by C1) |
| G5 | SOLAR, OTHER, UNSPECIFIED at $0 | STILL OPEN (accepted): documented gap at V/payment/sd_liheap_base_payment.py:44-47, #9754; NONE correctly removed from the list |
| G8 | Hygiene | STILL OPEN (accepted, partial): Case 12 comment fixed (T/income/sd_liheap_countable_income.yaml:369-371); heating_type in isolated limit and threshold tests kept by the author, inert |
| G9 | Cases 22 and 23 cite DLR with no URL | RESOLVED: SDCL 62-4-3 and DLR rate table p. 2 ($554-$1,108 for 7-1-25 to 6-30-26); SDCL 61-6-2 and DLR fact sheet p. 2 |
| G10 | Case 23 cannot tell raw UI from total UI | RESOLVED: :721-722 reported 0, modeled 6,400; :735-736 assert 6,400 and 27,200. The raw-reader mutation gives 20,800 and fails. |
| G11 | NONE described as a coverage gap | RESOLVED (T/payment/sd_liheap_base_payment.yaml:67-71; ARSD :06, :09) |
| G12 | $22,014 comment; sizes 4-6 note | RESOLVED: $22,013 recomputed (V/eligibility/sd_liheap_income_limit.py:41-43); note at P/income/limits/fpg_upper_rate.yaml:7-9 matches plan p. 9 |
| G13 | Short FY2021 plan URL | RESOLVED (P/payment/direct.yaml:9-12; p. 9 shows the $334 minimum) |
| G14 | 42 U.S.C. and archived DSS references | RESOLVED (P/income/limits/fpg_lower_rate.yaml:20-21, anchor b_2_B exists; P/income/fpg_year_lag.yaml:13-14, capture verified) |
| G15 | End-to-end pins | RESOLVED: T/eligibility/sd_liheap_eligible.yaml:55-58 and T/sd_liheap.yaml:346-365. Each fails its mutation (drop has_qualified_member; drop financial_assistance, which also fails countable-income Case 8). |
| G16 | Integration file | RESOLVED: T/integration.yaml, 5 FY2026 households, 11 outputs each; all 55 values hand-checked |
| G17 | Style | RESOLVED: no boolean-only case keeps a margin; every 4+ digit value in the 10 SD test files uses underscores |
| S1-S4, G2-G4, G6, G7 | Round-1 items | RESOLVED in round 2; no regression |
Critical (Must Fix)
None.
Should Address
None.
Suggestions
All four are optional and do not block approval.
- G18. Pin heat in rent over the wood, coal and kerosene payment rules.
V/sd_liheap.py:46-50checks heat in rent before the wood/coal branch at:36and:42. That order is correct: plan 9.1 lists coal/wood and heat in rent as separate exceptions (FY2026 p. 22), and ARSD 67:15:01:41.02 prices heat in rent from rent, not fuel (manual p. 30). But no case combines heat in rent with WOOD, COAL or KEROSENE, so a refactor that testscash_paymentfirst passes all 78 cases (mutation run). Append this toT/sd_liheap.yamlafter:365. It passes at head; under the mutation it gives[742, 742, 800]and fails. Custer is region 1: min($800, $6,000 / 12 x 7 x 0.30 = $1,050) = $800.- name: Case 15, heat in rent takes precedence over the wood, coal and kerosene payment rules. period: 2026 absolute_error_margin: 0.01 input: state_code: [SD, SD, SD] age: [40, 40, 40] immigration_status: [CITIZEN, CITIZEN, CITIZEN] heating_type: [WOOD, COAL, KEROSENE] heating_expense: [0, 0, 0] employment_income: [0, 0, 0] ssi: [0, 0, 0] tanf: [0, 0, 0] snap: 2026-01: [0, 0, 0] heat_expense_included_in_rent: [true, true, true] rent: [6_000, 6_000, 6_000] county_str: [CUSTER_COUNTY_SD, CUSTER_COUNTY_SD, CUSTER_COUNTY_SD] output: sd_liheap_eligible: [true, true, true] sd_liheap: [800, 800, 800]
- G19. Optional: wood with no purchased-fuel bill.
V/sd_liheap.py:42pays wood and coal the full cell whateverheating_expenseis, and eligibility rests onhas_heating_expense. Every wood and coal case has a positive bill, so a mutation that requiresexpense > 0for the cash branch survives. If $742 is the intended award when the user setshas_heating_expense: truewith a $0 bill (that variable's documentation names free gathered wood), add a two-column Case 16: WOOD, Custer,heating_expense: [0, 0],has_heating_expense: [true, false], expectingsd_liheap_eligible: [true, false]andsd_liheap: [742, 0](passes at head). Check this against the proof of heating liability in ARSD 67:15:01:09 and manual p. 31 first. - G20. Style. Add a blank line before the new comment blocks at
T/sd_liheap.yaml:25andT/eligibility/sd_liheap_eligible.yaml:38; each starts right after the previous case's output, so it reads as part of that case. Optionally add#page=1to the one-page matrix URL atV/sd_liheap.py:12andT/integration.yaml:4(also the unchangedV/payment/sd_liheap_heat_in_rent_payment.py:13andP/payment/heat_in_rent/amount.yaml:19), asV/payment/sd_liheap_base_payment.py:12does. - G21. Comments.
T/integration.yaml:82and:113-114give the $202.90 Part B premium without a source; add the CMS 2026 fact sheet. InT/income/sd_liheap_countable_income.yaml:707-709, say that South Dakota UI is not modeled (modeled_state_unemployment_compensationlists only AL, NJ, NY, OK, PA and UT), so the input stands in for reported UI to test the fallback (manual p. 28: "All UI payments received are countable").
PDF Audit Summary
| Category | Count |
|---|---|
| Documents audited | No new primary source. Round-2 300-DPI renders reused (4 PDFs, 162 pages), plus the FY2026 matrix and FY2021 plan p. 9. New this round: FY2024 plan p. 21 (300 DPI), DLR rate table (2 pages), DLR fact sheet p. 2, and the web sources above (DSS, Wayback, CMS, SSA, DLR wage page, ARSD, SDCL, U.S.C.). |
| Changed page anchors and page claims | 23 checked (7 #page= hrefs, 16 page claims): 23 correct |
| Hand-computed expected values | 55 integration outputs, T/sd_liheap.yaml Cases 2, 8, 14 and eligible Case 3: all match |
| Value mismatches queued | 0 |
| Verification items | 14 (5 external, 9 code-path): 5/5 external verified, 9/9 code-path claims confirmed |
| Investigated and cleared | 7 (below) |
| References checked | 18 added or changed: 18 correct, 0 wrong, 0 unverifiable. The 5 unchanged hrefs to the same Wayback capture are now verified too. |
Investigated and cleared:
- Wayback
20260101052007(7 places): loads in both forms and shows the 2025-2026 LIEAP table ($7,825 ... $24,431) and "the payment is made directly to the energy supplier". The weatherization table on the same page differs for sizes 7-10; the PR uses the LIEAP table. - FY2024 plan item 9.1 on p. 21 (cited at
V/sd_liheap.py:26): Heating Yes, exceptions Yes, same coal/wood/heat-in-rent text. - SSA $2,071 average and 2.8% COLA (
T/integration.yaml:79-81): match the archived fact sheet. - CMS $202.90 Part B premium: matches the fact sheet and
gov.hhs.medicare.part_b.base_premium. - Case 23 and main's
total_unemployment_compensation(reported amount, else modeled): the right "received" concept for manual p. 28; also what the #9558 guard requires. - Case 22 spans two DLR rate years ($554-$1,108 and $576-$1,152); $600 a week is inside both.
- Heat in rent: plan 9.1 pays the household, ARSD :41 the landlord; the amount is the same, so the model is unaffected.
Validation Summary
| Check | Result |
|---|---|
| Regulatory accuracy | C1 fix follows plan 9.1, ARSD :40 and :43.01 and EA-297 p. 4 in every claimed year; wood, coal and heat in rent unchanged |
| References | 18 added or changed, all correct; Wayback capture verified |
| Code patterns | One formula line changed (V/sd_liheap.py:36); no hard-coded values; other variable edits are comments |
| Test coverage | 78 cases; C1 revert fails 2; G10 and G15 pins each fail their mutation; heat-in-rent order unpinned (G18) |
| CI | 29/29 pass at f88aa84a78 |
| Local tests | 78/78 pass at f88aa84a78 (SD folder) |
| Main merge | No SD file changed; no regression |
| Federal 110% floor (#10279) | Not wired for SD; never binds (lowest limit about 150% of the lagged guideline) |
| Changelog | Present, top level, added |
Review Severity: APPROVE
No critical or should-address item is open. C1 is fixed and covered by tests, and the round-2 suggestions G9-G17 are done. G5 and G8 stay open as accepted. G18-G21 are optional.
Next Steps
Nothing blocks merge. To apply the optional suggestions, run the fix-pr workflow for this PR.
PR Review (round 2): Add South Dakota LIEAP regular heating assistancePR #9974 (author: hua7450). This round reviews head
Neither merge had a hand-resolved conflict. Path prefixes, as in round 1:
Line numbers refer to SummaryZiming fixed all four round-1 should items:
Of the eight suggestions:
I accept the reasons for each. Pavel's C1 fixes my round-1 G1 with evidence I did not have: plan item 9.1, which I confirmed on FY2026 page 22. All of his G9–G17 are done. His G18–G21 are open and optional, and I agree with them. There are no new critical or should findings. Two new suggestions come from
Batch checks:
I checked every new or changed expectation by hand: 7 new cases, 2 cases with changed values, 1 case with added award outputs and the cases whose unread inputs were removed. CI is 29/29 green at the head. The branch is 15 commits ahead of Prior findings status
Pavel's review of
Pavel's review of this head (
New findingsCritical (must fix)None. Should addressNone. Suggestions1. After #10182, count military SBP survivor annuities through
2. After #10169, count South Dakota's State Supplemental Payment (
Verified
Checklist results (LIHEAP wave 2a)
Axiom line assessmentValid:
CI status29 pass, 0 pending, 0 failed (run 39324256958 on head
Branch status
Next stepsNothing blocks merge. Optionally:
0 new critical and 0 new should findings, plus 2 new suggestions. Open at this head: 0 critical, 0 should and 6 suggestions (Pavel's G18–G21 and Suggestions 1–2). Verdict: APPROVE |
# Conflicts: # policyengine_us/programs.yaml
…seline (#10334) The guard (#10287) took its KNOWN_UNMARKED baseline at 0f21a63. FL LIHEAP (#9969) and SD LIHEAP (#9974) merged before the guard did, each with comments the ratchet reads as unmarked source conflicts, so test_conflict_sounding_comments_do_not_grow fails on main from ce9377e. These five files were on main when the guard landed, so they belong in the baseline, one comment each. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…10349) * Point the FL and SD LIHEAP conflict candidates at their code on main #10287 wrote these candidates while #9969 (FL) and #9974 (SD) were open, so each said its code was not on main. Both have merged. Each candidate now lists the files that decide it, and its note says which source that code follows, as read on main. One note changes substance: SD's merged code caps kerosene at unpaid charges like fuel oil, following the plan and rules, not the 2018 manual's full payment by check. The SD 2025 table candidate gets the archived DSS page as its source. Two FL conflicts recorded in the merged code had no candidate: the FY2026 plan's payment estimate against the October 2025 matrix, and manual part 900.01D against parts 1100.03D and 1200.01A and contract G(7)d on the income limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add the FL floor and minors' income candidates; reframe the SD limits From DTrim99's review and a Sol review of #10349. - Two more conflicts the merged FL code records had no candidate. The first is the federal 110% poverty floor against the 60% SMI limit from household size 15 (statute against guidance; PE-US follows the statute). The second is minors' income: the current application and portal FAQ against manual part 1200.01D. Every value is checked against the merged code and its tests: the floor case's $98,320 limit and $101,915 floor are fl_liheap.yaml's Case 27. - The SD income-limit entries credited ARSD 67:15:01:10 with a limit formula it doesn't contain; it sets only the three-month income period. FY2025 is now the plan's 60% of SMI against the published table's 200% of the 2024 guideline for sizes 1-3: $32,984 against $30,120 for one person, as table/amount.yaml records. FY2026 is DSS's 200% of the 2025 guideline against the plan's 60% of SMI. Its $22,013 is the model's flooring, and $22,014 is unrounded. - Anchors and sources: - FY2026 plan item 2.1 is on page 8, not 4. - Contract G(8)c is on page 12, not 9. - The null URLs are filled from the merged code's references. - The FL manual-sections entry gets the plans, the matrix and the manual's "current matrix" page. - Kerosene gets plan item 9.1 and ARSD 67:15:01:43.01. - The FL size-13 and SD 2026 entries list the remaining deciding files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Cite the FY2025 FL LIHEAP matrix; list the floor candidate's lag and band files Review nits from the round-2 review of #10349: - us-fl:liheap-income-limit-manual-sections now cites the FY2025 payment matrix the code follows. - us-fl:liheap-income-floor-vs-smi-limit lists fpg_year_lag.yaml and fl_liheap.py, which its numbers rest on, and says the band 4 base is the model's estimate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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. Natural gas, electricity, propane, fuel oil and kerosene awards go to the supplier, up to the household's unpaid heating charges; wood and coal 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
defined_for = StateCode.SDsd_liheap_household_sizecounts members who meetis_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.liheap_smi_limithelper (#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.snap_unit_sizeequal to the number of SPM unit members, eligible or not. A benefit for only some members does not qualify.has_heating_expense) qualifies. Heat included in rent qualifies with positive tenant rent and no separate bill; zero tenant rent does not.heating_typeNONE is ineligible.Countable income
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.medicare_part_b_premium, floored at zero. The model counts railroad retirement gross.tanfaggregate, which includessd_tanf.child_support_expenseand floors countable income at zero.Benefit
Each cell lists tier 1 / tier 2 for FY2024 through FY2026.
heating_expensefloored at zero. Wood and coal pay the full cell to the household (plan item 9.1; ARSD 67:15:01:40).county.Years covered
A calendar-year period reads the parameters in force on January 1. Formulas are undated. The
programs.yamlentry, South Dakota LIEAP, has statuspartialand noverified_years; the table lists the schedules the model represents, and no year is verified.income/limits/table/amount.yamlprints them andtable/in_effect.yamlapplies 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.payment/direct.yamlcarries 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.Not modeled and source conflicts
heating_expenseis 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.rentwithheat_expense_included_in_rent, and the heat-in-rent payment then applies the 30% share.Where sources differ or are silent, the model reads them as follows:
gambling_winnings.financial_assistanceand reads an annual amount as regular help.Microsimulation: the microsimulation datasets carry no heating inputs, so population totals are about $0 until #9754 lands. Callers must send
heating_typewith the matching fuel bill.Test plan
78 YAML cases in 10 files cover:
integration.yaml, four of which carry a fuel bill throughheating_expense: gas in Minnehaha County ($1,007), kerosene in Brown County paid to the supplier and capped at $900 of fill tickets, wood in Harding County paid in full ($854 on a $500 bill) and propane in Lincoln County with a teenager's excluded earnings ($1,800); the fifth has heat in rent in Pennington County ($800);Every case specifies
heating_typeexcept the foursd_liheap_household_sizecases; that variable does not read it. Expected values come from hand arithmetic on the sources. The 72 cases atcd74fd52d6passed CI; the new and changed cases 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.make formatchangelog.d/axiom: TheAxiomFoundation/rulespec-us#1539 queued
🤖 Generated with Claude Code