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
|
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
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, coal and kerosene pay the full cell to the household.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.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
72 YAML cases in 9 files cover:
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 71 cases atee24e71ea5passed 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.make formatchangelog.d/axiom: TheAxiomFoundation/rulespec-us#1539 queued
🤖 Generated with Claude Code