Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #9726 +/- ##
==========================================
Coverage 100.00% 100.00%
==========================================
Files 4 11 +7
Lines 76 183 +107
Branches 2 3 +1
==========================================
+ Hits 76 183 +107
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:
|
…ata and tests - Pay the flat coal/wood amount to every income-eligible household, so the special population is paid up to its 150% limit (EP-300 300.09, 300.12) - Cite FNS manual sections 300, 315 and 350 for the student-earner and work-study exclusions and record the state plan checklist difference - Complete parameter metadata: period, subsection reference titles, labels - Add tests isolating the gross test, end-to-end households, the FY2027 resource test and coal/wood boundaries; send heating_type explicitly in every eligibility or payment case Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Program ReviewPR #9726: North Carolina LIEAP regular heating assistance (hua7450). Reviewed head: Scope: 33 files: 15 parameters, 11 variables (7 formulas and 4 inputs), 5 test files (39 cases), a changelog fragment and Overall: All 189 audited values match the sources. They comprise 156 published FY2026 and FY2027 income limits and payment-band edges, plus 33 scalars. The half-up monthly rounding, the work-deduction table, ineligible-alien proration and the vulnerability mapping all reproduce EP-300. One payment rule contradicts EP-300: an eligible household whose fuel is Path abbreviations:
Line numbers refer to the files at head Source Documents
Branch StatusPR branch is 12 commits behind main. Consider rebasing before merging. Review was scoped to the PR's actual changes, and staleness did not affect findings.
Critical (Must Fix)C1. An eligible household with
|
| file:line | Current anchor | Suggested |
|---|---|---|
params/earned_income_deduction.yaml:39 |
#page=12,13 |
12 |
params/earned_income_deduction_rate.yaml:10 |
#page=12,13 |
13 |
params/earned_income_deduction_threshold.yaml:10 |
#page=12,13 |
13 |
params/elderly_age.yaml:10 |
#page=1,10,13 |
10 |
params/fpg_year_lag.yaml:10 |
#page=14,15 |
14 |
params/payment/coal_wood.yaml:10 |
#page=18,20 |
18 |
params/payment/higher_income.yaml:19 |
matrix #page=1,2,3 |
1 (also add EP-300 p19, see G5) |
params/payment/lower_income.yaml:19 |
matrix #page=1,2,3 |
1 (also add EP-300 p19, see G5) |
params/payment/lower_income_share.yaml:10 |
#page=19,20 |
19 |
params/resource_limit.yaml:10 |
#page=17,18 |
17 |
params/resource_test.yaml:11 |
#page=17,18 |
17 |
params/special_resource_limit.yaml:10 |
#page=17,18 |
17 |
vars/nc_lieap.py:15 |
#page=10,18,19,20 |
18 |
vars/nc_lieap_bank_account_exclusions.py:19 |
#page=17,18 |
18 |
vars/nc_lieap_earned_income.py:12 |
#page=10,11,12,13,15,16,17 |
10 (split) |
vars/nc_lieap_earned_income.py:14 |
FNS 300 #page=5,21,26 |
5 (split) |
vars/nc_lieap_eligible.py:10 |
#page=9,15,16,17,18 |
9 (split) |
vars/nc_lieap_gross_income_person.py:11 |
#page=10,11,12,13,15,16,17 |
10 (split) |
vars/nc_lieap_household_size.py:12 |
EP-175 #page=1,4,5,6 |
4 |
vars/nc_lieap_household_size.py:13 |
#page=15,16 |
15 |
vars/nc_lieap_income.py:11 |
#page=10,11,12,13,15,16,17 |
10 (split) |
vars/nc_lieap_income_limit.py:12 |
#page=10,14,15,19,20 |
10 (split) |
vars/nc_lieap_is_daas_disabled.py:17 |
#page=1,2,10 |
1 |
vars/nc_lieap_nonbank_resources.py:18 |
#page=17,18 |
18 |
- Valid as is (8):
income_limit.yaml:10,special_income_limit.yaml:10,medical_deduction.yaml:10,nc_lieap_earned_income.py:13,15,16,nc_lieap_has_paid_excess_heating_costs.py:17andnc_lieap_household_size.py:11. - PR body:
NC_Plan_2026.pdf#page=5,9,10is multi-page, andEP-300…#page=1,2,9..20is a range. Both are invalid. - If references move to EP-300 (Oct 2026): limits stay on p14-15. 300.11 moves to p.18, the bands to p19-21, and the coal/wood row to p20.
S4. Test periods are written as 2_026 / 2_027 / 2_022 (23 occurrences)
- Where:
tests/integration.yaml:3,20,37tests/nc_lieap.yaml:3,16,29,41,56,67tests/nc_lieap_eligible.yaml:3,25,47,69,91,104,118,131tests/nc_lieap_income.yaml:3,16,35,49,65,98
- Why it matters:
- These four files are the only test files in the repo with
period: 2_0xx. About 3,250 others useperiod: YYYY, and the same files also mix inperiod: 2026. - PyYAML's YAML 1.1 resolver accepts the underscores, so CI passes. A YAML 1.2 parser would read the string
"2_026".
- These four files are the only test files in the repo with
- Fix: use
2026/2027/2022, and excludeperiod:from whatever tool inserts digit separators. - Source: not applicable (test format).
S5. No test exercises the half-up rounding, so replacing np.floor(x + 0.5) with np.round would pass all 39 cases
-
Code:
vars/nc_lieap.py:22(band edge) andvars/nc_lieap_eligible.py:16-18(eligibility limit). Both round halves up on purpose. -
Gap:
- FY2026 has no exact-half thresholds.
- FY2027 has 34-35 exact-half band edges. All of them round correctly today, but the FY2027 tests check only the size-1 limits ($1,729 and $1,995, both whole dollars).
- Case 9 incomes ($762.67 and $1,315) sit far from the $865 and $998 band edges.
- Banker's rounding, or float drift just below .5, would go undetected.
-
Fix: add
period: 2027payment-band rows using EP-300 (Oct 2026) p.20 and p.21:Population and size Income at edge One dollar above 130%, size 1 $865 → $400 $866 → $300 150%, size 1 $998 → $400 $999 → $300 150%, size 6 (2,772.5 rounds to $2,773) $2,773 → $500 $2,774 → $400
S6. Special-population membership differs between the income and resource tests, and the divergent branch is untested
- Code:
- The income limit counts a member aged 60+ or DAAS-disabled, and only if that member is an eligible member (
vars/nc_lieap_income_limit.py:19-25,& included). - The resource limit counts any member and adds
is_usda_disabled(vars/nc_lieap_eligible.py:40-44).
- The income limit counts a member aged 60+ or DAAS-disabled, and only if that member is an eligible member (
- Source: the disability difference has textual support. 300.09 says "receiving services through DAAS" (p.10), while 300.11 says "disabled" (p.17). The asymmetry in
includedhas no stated basis. - Test gap: the only disability resource test (
tests/nc_lieap_eligible.yaml:207-222) setsis_usda_disabled: [false, false]and relies on DAAS. No case has a member under 60 who isis_usda_disabledbut not DAAS-disabled, so removing that branch would fail no test. - Fix:
- Align the
includedtreatment, or document it in a comment. - Add a
period: 2027row: age 40,is_usda_disabled: true,nc_lieap_is_daas_disabled: false, with bank assets of $4,500 (eligible) and $4,501 (ineligible).
- Align the
S7. Edge cases not yet tested
- (a) Payments beyond the published band tables: 130% sizes 16+ and 150% sizes 12-26.
- Code:
vars/nc_lieap.py:22-25,vars/nc_lieap_income_limit.py:28-33. - Bands are published only for sizes 1-15 (130%) and 1-11 (150%), p.19-20.
- Pin the extrapolated amounts (pairs with G10).
- Code:
- (b) A household with a $0 heating bill and heat not in rent: expect
nc_lieap_eligible: false(300.08, p.9). Every current $0-bill row is heat-in-rent (tests/nc_lieap_eligible.yaml:154-157,tests/integration.yaml:342-345). - (c) A qualified non-citizen (
LEGAL_PERMANENT_RESIDENTorREFUGEE) counted innc_lieap_household_size(vars/nc_lieap_household_size.py:16-23). This pins the EP-175.05 reading (eps175 p.4) against a later switch to the SNAP variable (see G4). - (d) An excluded member aged 60+ who pays child support or adult care. That member gets no $85 medical deduction (
vars/nc_lieap_income.py:33), and the payments are prorated byshare(:34-35). Neither branch is tested (300.10, p.15).
S8. Parameter descriptions use verbs outside the allowed set, vague placeholders, one inaccurate formula description and draft wording
- Verbs (allowed: limits, provides, sets, excludes, deducts, uses):
params/earned_income_deduction.yaml:1: "allows". Suggested: "deducts this amount from monthly earned income, based on the earnings band, …".params/medical_deduction.yaml:1: "allows". Suggested: "provides this amount as a monthly standard medical deduction …".params/elderly_age.yaml:1: "treats". Suggested: "sets this age as the elderly threshold …".params/payment/coal_wood.yaml:1,params/payment/higher_income.yaml:1andparams/payment/lower_income.yaml:1: "pays". Suggested: "provides this amount …".params/resource_test.yaml:1: "applies a resource test …, except during the expressly waived fiscal year". The values already encode the waiver. Suggested: "uses a resource test … if this is true."
- Placeholders:
- "these monthly work-related deductions" (
earned_income_deduction.yaml:1); - "this monthly earned income" (
earned_income_deduction_threshold.yaml:1); - "this fixed regular heating subsidy" (
coal_wood.yaml:1).
Use "this amount" or "this threshold".
- "these monthly work-related deductions" (
- Inaccurate description:
params/earned_income_deduction_rate.yaml:1says "deducts this share of monthly earnings above the fixed deduction table", which reads as a marginal rate.- The formula (
vars/nc_lieap_income.py:24-25) applies 20% to total monthly earnings once they exceed $400, so $401 a month yields $80.20 (p.13). - Suggested: "deducts this share of total monthly earnings when earnings exceed the deduction threshold …".
- Draft wording:
params/fpg_year_lag.yaml:1contains "as inferred from the published income limit tables". See G6 for a direct source. - Metadata:
earned_income_deduction_rate.yaml:4hasperiod: year, while its companionearned_income_deduction_threshold.yaml:4hasperiod: month.
S9. nc_lieap_household_size is a pure sum and should use adds
- Code:
vars/nc_lieap_household_size.py:16-23. The formula body is onlyreturn add(spm_unit, period, ["is_citizen_or_legal_immigrant"]). - Fix: replace the formula with
adds = ["is_citizen_or_legal_immigrant"], following the precedent atpolicyengine_us/variables/.../spm_unit_count_children.py:9, and move the six-line comment intodocumentation. - Source: EP-300 (May 2026) 300.10, p.15.
S10. The Axiom parity line does not follow CONTRIBUTING.md
- Current line (PR body):
axiom: n/a: rulespec-us does not have this program yet; it should be added as a follow-up. - Rule: CONTRIBUTING.md#axiom-parity covers new programs. A not-yet-encoded program calls for a
queuedrulespec-us issue, labelledpe-parityand dispatch-ready. The issue needs:- the module path (
us-nc/...); - the EP-300 citations and verbatim text;
- the required outputs;
- companion tests that reuse this PR's external expected values.
n/ais reserved for changes that are not policy.
- the module path (
- Fix: open the issue and change the line to
axiom: <issue> queued.
Suggestions
- G1.
resource_testdating: document the source for both ends.params/resource_test.yaml:1-4.- FY2027 start: the parameter flips to true on 2026-07-01, following the literal waiver dates.
- Both Oct 2026 editions still print the expired line "Resources are not countable for LIEAP for FY July 1, 2025 – June 30, 2026" (EP-300 (Oct 2026) p.18).
- No FY2027 plan is posted. Both posted plans say there is no assets test (FY2026 p.9; FY2025 p.8).
- Mark FY2027 as provisional in the description and the PR body, and cite the current edition.
- Backfill: the first value (
2025-07-01: false) backfills "no resource test" to every earlier year. That matches the plans but not the Oct 1 2025 manual, which printed the $3,000/$4,500 test with no waiver (EP-CN-05-2025a1 p.17) until FNSEP-CN-06-2025 added one on Nov 1 2025 (p.1). State which source the backfill follows.
- FY2027 start: the parameter flips to true on 2026-07-01, following the literal waiver dates.
- G2. Document the conflict between the plan and the manual on gross versus net income.
- Plan 1.8 ticks "Gross Income" (FY2026 plan p.5).
- The manual nets out deductions (300.09 B, p.11).
- The PR follows the manual (
vars/nc_lieap_income.py:45-50), but neither the code nor the PR body mentions the conflict.
- G3. SNAP categorical eligibility.
- The FY2026 plan 1.4 marks SNAP = Yes for heating, with 1.4a blank (p.5). The FY2025 plan said No, and the manual is silent.
vars/nc_lieap_eligible.py:58-60documents the decision not to model it. Repeat that as a known limitation in the PR body.- If the plan provision is operative, the omission biases eligibility downward for SNAP households above 130/150% FPG. Size not quantified.
- G4. Keep
is_citizen_or_legal_immigrantfor non-citizens, and cite EP-175.05.vars/nc_lieap_household_size.py:17-22.- Reason not to switch to
is_snap_immigration_status_eligible: EP-175.05 lists permanent residents, refugees, asylees, parolees, conditional entrants and deportation-withheld as "Eligible for Energy Programs", with no 5-year bar (eps175 p.4). - FNS 227.11's "qualified ineligible" category reflects SNAP-only restrictions (p.38). Importing the SNAP variable would therefore exclude LIEAP-eligible LPRs, refugees and asylees.
- Add the citation to the comment. S7(c) covers the matching test.
- Reason not to switch to
- G5. Citation editions.
params/payment/higher_income.yaml:18-19andparams/payment/lower_income.yaml:18-19cite only the matrix. Add the operative chart, EP-300 300.12 B (p.19).- Values effective 2025-10-01 cite the later May 2026 edition. Consider adding the edition that set them (EP-CN-05-2025a1 p.13), whose numbers are identical.
tests/integration.yaml:285("October 2026 reissue, pages 14, 19, 20") and the PR body's FY2027 link point to Change 3-2026 (EP-CN-03-2026a1.pdf), which has been superseded. Name the current edition with its own pages (EP-300 (Oct 2026) p.14; bands p20-21). The values are identical.
- G6.
fpg_year_laghas a direct source.params/fpg_year_lag.yaml:8-10. - G7. Move the earned and unearned income sources into LIEAP-owned parameters.
vars/nc_lieap_earned_income.py:30-37hardcodes five income variables.vars/nc_lieap_gross_income_person.py:17filters"rental_income"out ofgov.usda.snap.income.sources.unearnedby string. If SNAP renames or splits that source, rental income would be counted twice, and any SNAP-only edit to the list would silently change NC LIEAP.- A
gov.states.nc.ncdhhs.lieap.income.sources.{earned,unearned}list resolves both issues (300.09 A, p.10).
- G8. The care deduction is not tied to earnings.
vars/nc_lieap_income.py:35,42deducts care regardless of earnings.- 300.09 B.1 b says "Verify actual childcare costs paid in the base period for each household member with earned income", while B.1 a is unconditional (p.11).
- Document the choice.
- G9. Note the source typos next to the matrix-based tests (
tests/nc_lieap.yaml:28-51), so nobody later "corrects" the code toward a typo:- the manual's 150% size-6 band reads "$0-$2,267" (p.20). The PR uses the matrix's $2,697 (matrix p.3), consistent with the manual's own upper-band start of $2,698;
- the matrix's "$8,1444";
- the matrix's "65 or older" special population.
- G10. Document the extrapolation beyond published sizes.
- Limits are published up to size 26. For larger households the manual says to contact the Economic and Family Services Section (p.15).
- The formula extrapolates both limits and bands (
vars/nc_lieap.py:22,vars/nc_lieap_income_limit.py:28-33). Say so in thenc_lieapdocumentation. S7(a) covers the test.
- G11. The work deduction can exceed earnings.
- In
vars/nc_lieap_income.py:22-29, monthly earnings between $0 and $10 receive the $10 bracket deduction. - The negative net earnings then offset unearned income in the
:46sum, by at most $10 a month (p.12). - Consider
min_(work_deduction, earned).
- In
- G12. Merge the two
add()calls in the gross test (vars/nc_lieap_eligible.py:24-26) intoadd(spm_unit, period, ["nc_lieap_gross_income_person", *sources]). - G13. Folder layout.
- Fifteen parameters sit at the top level, and only
payment/is grouped. Possible structure:income/limit/{regular,special}income/fpg_year_lagincome/deductions/{work/{amount,rate,threshold},medical}resources/{limit,special_limit,test}
- Variables could be grouped the same way, into
eligibility/,income/andresources/.
- Fifteen parameters sit at the top level, and only
- G14. Naming.
nc_lieap_incomeholds net countable income, sonc_lieap_countable_incomewould match the state-program convention.params/payment/higher_income.yamlandlower_income.yamlare payment amounts by income band.upper_band/lower_band, or*_amount, would describe them more precisely.
- G15. Test names.
- Several names use external plan numbering: "Case 4"–"Case 9" (
tests/integration.yaml:60-282), "Case 8" (tests/nc_lieap.yaml:94), "Case 13" (tests/nc_lieap_eligible.yaml:223) and "Case 2" (tests/nc_lieap_income_limit.yaml:14). - "Case 8" appears twice with different content. Use descriptive names only.
- Several names use external plan numbering: "Case 4"–"Case 9" (
- G16. Revision-history and draft wording.
tests/nc_lieap.yaml:95-96("the payment formula no longer tests income") andtests/nc_lieap_income_limit.yaml:15-16("Replaces the former fourth household…") describe earlier iterations.- The PR body uses "draft" six times. Describe the current behavior instead.
- G17. The backfill test asserts outputs for an unverified year.
tests/integration.yaml:35-58(period 2022, limit $16,744, payment $400) pins values the registry calls unverified.- It will fail if real FY2022 rules are added later. The FY2027 cases already exercise the guideline lag.
- G18.
programs.yaml.- NC is appended last on the coverage line (
:805) but placed before TX instate_implementations(:826). Agreeing on one rule for the sibling set (for example, alphabetical in both places) would make each rebase deterministic. notes(:833) mentions "the FY2027 thresholds" whileverified_years: "2026"(:832) excludes 2027. Either use"2026-2027"for the verified thresholds or reword the note.
- NC is appended last on the coverage line (
- G19. Document the period convention (the year is the heating season ending in that year) in the
nc_lieapdocumentation (vars/nc_lieap.py:10-13). At present it appears only in thefpg_year_lagdescription and the PR body. - G20. Changelog wording.
changelog.d/nc-lieap.added.md:1uses internal terms ("reported public-housing excess payments", "DAAS disability pathway"). A plainer alternative: "Add North Carolina Low Income Energy Assistance Program (LIEAP) regular heating benefits."
PDF Audit Summary
| Category | Count |
|---|---|
| Confirmed correct | 189 |
| Mismatches (code-path confirmed + visually verified) | 0 |
| Mismatches rejected (code-path cleared) | 0 |
| Unmodeled items | 7 (all documented in code comments) |
| Pre-existing issues | 0 |
| Source typos (not PR errors) | 5 |
| Group | Source (page) | Values | Mismatches |
|---|---|---|---|
| FY2026 130% limits, sizes 1-26 | EP-300 (May 2026) p.14; matrix p.1 | 26 | 0 |
| FY2026 150% limits, sizes 1-26 | EP-300 (May 2026) p14-15; matrix p.2 | 26 | 0 |
| FY2026 130% bands, sizes 1-15 | EP-300 (May 2026) p.19-20; matrix p1-2 | 15 | 0 |
| FY2026 150% bands, sizes 1-11 | EP-300 (May 2026) p.20; matrix p2-3 | 11 | 0 (manual typo $2,267; PR uses the matrix's $2,697) |
| FY2027 130% limits, sizes 1-26 | EP-300 (Oct 2026) p.14-15 | 26 | 0 |
| FY2027 150% limits, sizes 1-26 | EP-300 (Oct 2026) p.15 | 26 | 0 |
| FY2027 130% bands, sizes 1-15 | EP-300 (Oct 2026) p.20 | 15 | 0 |
| FY2027 150% bands, sizes 1-11 | EP-300 (Oct 2026) p.20-21 (600-dpi re-render; the text layer is misaligned) | 11 | 0 |
Scalars: 130%, 150%, age 60, $85, $3,000, $4,500, work table (6 thresholds and 6 amounts), $400, 20%, $300 coal/wood, payment amounts and thresholds, 0.5 share, lag 1, 2 resource_test dates |
EP-300 (May 2026) p.10, p12-13, p17, p18-20; matrix | 33 | 0 |
| Total | 189 | 0 |
-
Method: an independent recomputation using the PR's exact float operations, followed by 300-dpi image checks of every dense table. The size-6 rows (May 2026 p20, matrix p3) and Oct 2026 p21 were re-rendered at 600 dpi.
- Limits equal
floor(m + 0.5)and bandsfloor(0.5·m + 0.5)on the unrounded monthly FPG × 1.3 or 1.5, with a one-year guideline lag. - The 34 exact-half FY2027 cases (for example 150% size 6: 2,772.5 rounds to $2,773) match the published values.
- Limits equal
-
Hand spot-checks (all match):
tests/nc_lieap_eligible.yaml:10: $1,695 and $5,270.tests/nc_lieap_eligible.yaml:56: $19,144.tests/nc_lieap.yaml:9: $5,019.tests/nc_lieap.yaml:35: $3,041 and $2,697.tests/nc_lieap_eligible.yaml:135: $1,729 and $1,995.tests/integration.yaml:282-297: $865 and $998.- Integration Case 4: $1,248, $2,887 and $1,444.
-
Unmodeled items (documented in code; PR scope):
- SNAP categorical eligibility (G3).
- Automatic reenrollment and prior-year awards.
- Tribal administration.
- Discretionary supplements.
- Institutional status and vendor/account checks.
- EP-175 document and PRUCOL categories, qualified-but-ineligible members, and the optional inclusion of foster children.
- Terminated-income exceptions and room-and-board transfers.
The Crisis Intervention Program, cooling and weatherization are out of scope.
-
Source typos (not PR errors):
- May 2026 p20, 150% size 6: "$0-$2,267".
- Matrix p3: "$8,1444".
- Oct 2026 p20: "$0-$4.864".
- The 150% band header "Represents 76 - 130% of 100%", which should read 76-150%.
- Matrix p2: special population "65 or older" (the manual says 60).
Validation Summary
| Check | Result |
|---|---|
| Regulatory Accuracy | 1 critical (C1, UNSPECIFIED fuel pays $0). 1 contested reading (S1, coal/wood 130% cap). 3 documentation items (G2, G3, G8) |
| Reference Quality | All URLs in code and PR body live (2026-10-01). 8 valid anchors, 25 multi-page (S3). Payments cite only the matrix, and FY2027 cites a superseded edition (G5). fpg_year_lag has a direct source (G6) |
| Code Patterns | No hard-coded values (np.floor(x + 0.5) follows repo precedent). Vectorized. defined_for and entities correct. No duplicate variables. One adds candidate (S9) |
| Formatting (params & vars) | Parameter description verbs, placeholders and one inaccurate description (S8). Mixed period metadata on the work-deduction pair (S8) |
| Test Coverage | 39 cases, and every formula variable is asserted. Arithmetic re-derived for Cases 4-9, Case 13, the work-deduction ladder, proration and every boundary row. Gaps: rounding (S5), is_usda_disabled resource branch (S6), 4 edge cases (S7). 23 underscore periods (S4) |
| PDF Value Audit | 0 mismatches / 189 confirmed |
| Microsimulation | Absent heating inputs give a $0 NC total, biased downward by the full program amount (S2). DAAS and excess-paid defaults bias downward and the nonbank-resource default biases upward (from 2027); sizes not quantified |
| CI Status | Passing (37/37) |
| Changelog | changelog.d/nc-lieap.added.md: valid type, top-level file (wording, G20) |
programs.yaml |
Sibling conflicts expected. Order inconsistency and a verified_years vs notes mismatch (G18) |
| Axiom parity | The n/a line does not meet CONTRIBUTING requirements (S10) |
| Branch | Head unchanged; 6 ahead and 12 behind main; no overlap with main changes |
Review Severity: REQUEST_CHANGES
1 critical and 10 should-address findings (20 suggestions).
Next Steps
- C1: stop zeroing
UNSPECIFIEDinvars/nc_lieap.py, so that eligible households receive the size-and-income band. Update integration Case 4 and theUNSPECIFIEDcolumn intests/nc_lieap.yaml. - S1: apply the 130% cap to the coal/wood award, or cite written NCDHHS confirmation of the 150% reading. At minimum, record the contested reading in the PR body.
- S2: choose a remedy for the absent heating inputs: populate them, document the near-zero aggregate in
programs.yaml, or quantify the bias and accept it. Note the direction of the input defaults. - S3: convert the 25 multi-page anchors to single pages (table above), and fix the PR-body anchors.
- S4, S8, S9: replace the
2_0xxperiods, revise the parameter descriptions, and switchnc_lieap_household_sizetoadds. - S5-S7: add the FY2027 band-edge rows, the
is_usda_disabledresource row, and the four edge cases. Align or document the special-populationincludeddifference. - S10: open a dispatch-ready rulespec-us issue labelled
pe-parity, and change the Axiom line toqueued. - Suggestions: optional. G1 (resource-test dating) and G5 (citation editions) affect the year-coverage notes.
- Rebase onto main after the first sibling LIHEAP PR merges, then re-request review.
To auto-fix issues: /fix-pr 9726
DTrim99
left a comment
There was a problem hiding this comment.
Requesting changes per the program review above (#9726 (comment)): 1 critical (an eligible household with heating_type UNSPECIFIED, including heat-in-rent public housing, receives $0, but EP-300 300.12 bases the benefit on size and income with only coal/wood fuel-specific), plus should-address items listed in the report. Hand-checked values against the EP-300 tables match. Run /fix-pr 9726 or address the list.
Pay the size and income amount for an unspecified fuel when heat is included in rent. Move the earned income sources into a list parameter and stop filtering rental income out of the SNAP list by name. Cap the work deduction at earnings. Use single-page references with sections and pages in the titles, cite the current manual edition for FY2027, fix test periods and names, make household size a plain sum, rewrite parameter descriptions, and add rounding-edge, resource, extrapolation and household cases. Record verified years 2026-2027 and place North Carolina alphabetically in the registry. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Thanks for the review. Fixes are in Critical
Should-address
Suggestions
|
Program Review — Round 2PR #9726: North Carolina LIEAP regular heating assistance (hua7450). Re-reviewed head: Scope now: 35 files:
Result: C1 and eight of the ten should-address items are resolved. The two exceptions:
Every changed value and expectation was rechecked against EP-300. No regressions were found. Path abbreviations:
Line numbers refer to head Status of Round 1 Findings
S1 rationale, weighed.
Suggestions the author reports as taken. All verified at head:
Regression Check on the DeltaEvery changed expectation and every new row was recomputed by hand and checked against the source tables. No mismatches were found. New payment-band tests:
Coal/wood 130% cap: the formula is unchanged; only its comment moved. The expectations in
Changed and new test cases:
Code changes:
Comments and documentation:
Critical (Must Fix)None. Should Address (remaining)S10. The queued rulespec-us issue is not dispatch-ready
Suggestions
CI Status35 of 37 checks pass on The NC tests ran in Full Suite - Baseline (states-shard-4), Batch 2: integration 9,
Neither failure blocks approval on the merits. Both jobs need a re-run before merge. Branch:
Review Severity: REQUEST_CHANGES0 critical, 1 remaining should-address (S10) and 6 suggestions. The code, parameters and tests are ready. The only blocker is the Axiom parity requirement: rulespec-us#1466 needs a module path, the EP-300 corpus citation, verbatim text, required outputs and companion tests from the PR's published-table values. Once that is done, and the two runner-killed shards pass on re-run, nothing in this review blocks approval. |
DTrim99
left a comment
There was a problem hiding this comment.
Round 2 (#9726 (comment)): the critical unknown-fuel issue and 9 of 10 should items are resolved. One item remains: rulespec-us#1466 is labelled pe-parity but not yet dispatch-ready (module path, provision citations, verbatim law, outputs, companion tests from the EP-300 tables), as the CLAUDE.md axiom-parity rule requires for a queued line.
…into nc-lieap # Conflicts: # policyengine_us/programs.yaml
|
Thanks for round 2. Head is now S10, rulespec-us#1466. Filled in on 2026-10-02. The issue now has the PR link, scope and both policy periods (FY2026 and FY2027); module paths under Suggestion 2. Reworded in the PR body: totals stay at $0 until both a heating type and a heating bill are in the microdata. Suggestions 1, 3, 4, 5 and 6. Not taken in this round. Tests were not run locally; CI runs them. |
Program Review — Round 3PR #9726: North Carolina LIEAP regular heating assistance (@hua7450).
Result: 1 critical, 0 should address remaining, 1 suggestion.
Path abbreviations:
Status of round-2 items
rulespec-us#1466 against the Axiom Parity requirements
programs.yaml merge resolution
Delta check
Critical (Must Fix)1. After the next
Should Address (remaining)None. Suggestions
CI
Branch state
Review Severity: REQUEST_CHANGESThere is 1 critical issue, 0 remaining should-address items and 1 optional suggestion.
|
DTrim99
left a comment
There was a problem hiding this comment.
Round 3 (#9726 (comment)): rulespec-us#1466 is now dispatch-ready. New critical item from main: #9672 replaced rental_income with snap_rental_income in SNAP's unearned list, so once this branch takes current main (needed anyway for the #9727 programs.yaml conflict), NC counts positive rental income twice (nc_lieap_income.yaml Case 2: $18,000 to $24,000, denying an eligible household). Current CI predates #9672.
Adds North Carolina regular LIEAP heating assistance using FY2026 and FY2027 rules. Four reported inputs cover public-housing excess heating payments, qualifying disability with DAAS services, nonbank resources, and excluded bank funds; monetary inputs default to zero and booleans to false. A one-person household earning $12,000 annually has $9,600 of countable income after the work deduction and receives $400 for gas heat, even if the current bill is smaller.
Year convention: the year is the heating season ending in that year. Undated formulas follow the model’s existing parameter backfilling; earlier years use the earliest available state schedule with period-specific federal guidelines and the one-year guideline lag, and are unverified historical estimates. The registry records
verified_years: "2026-2027": the FY2027 limits and payment bands are checked against the October 2026 manual.Household and immigration basis
EP-150 defines the energy household, approximated here by the SPM unit. EP-175.01 requires a citizen or eligible alien in the household, and EP-175.05 supplies the program's documentary eligibility categories. The existing qualified-status variable is a partial mapping: PRUCOL/document-specific cases and qualified-but-ineligible members are not fully distinguished.
EP-300.10(A)(2)(b) requires a gross test counting a non-qualified ineligible member's income in full while excluding that member from size; a failed gross test denies the household. After passing, subsection (d) divides excluded income across everyone, and subsection (e) directs: “Multiply the pro rata share amount by the number of eligible household members in the LIEAP budget.” The model applies that proration before the specified deductions. Subsection (a)'s qualified-but-ineligible pathway is distinct and remains a gap in the current status mapping. This is not just an unconditional headcount reduction to income. EP-300, particularly Section 300.10, pages 15–17; other encoded rules on pages 1–2 and 9–20
Housing, resources, and DAAS disability
EP-300.08 accepts the applicant’s statement of heating vulnerability. Public housing with heat included in rent qualifies only when excess heating utility charges were paid in the preceding 12 months at the current address.
nc_lieap_has_paid_excess_heating_costsrecords that combined fact; a positive current bill no longer substitutes for it. Private heat-in-rent arrangements remain ineligible, and Section 8 is treated as private housing. Separately billed households reusehas_heating_expense.EP-300.11 waives resources July 1, 2025 through June 30, 2026. Outside that waiver, the model counts existing bank balances less reported
nc_lieap_bank_account_exclusions, floored at zero for each person, plusnc_lieap_nonbank_resources. The two new monetary inputs cover outstanding withdrawals/funds already counted as income and cash/retained lump sums outside bank accounts, respectively. Their documentation prevents double counting; bank exclusions cannot offset nonbank resources. All household members’ resources count, including ineligible aliens. The ceiling is $3,000, or $4,500 for households with an elderly or disabled member.EP-300.02 and .09 require both qualifying disability-benefit receipt and DAAS services for the special disability income pathway.
nc_lieap_is_daas_disabledrecords this combined eligibility fact and activates the 150% limit and special payment bands. It also qualifies the household for the higher resource ceiling. Generic disability alone does not activate the 150% limit. Coal/wood households receive the flat $300 whenever they are income-eligible, so a special-population coal/wood household is paid up to its 150% limit. EP-300.12 A (page 18) says coal/wood income must be at or below the 130% limit, in a paragraph that describes 130% as the income limit for every household; the model reads that sentence as restating income eligibility, which EP-300.09 (page 10) and plan section 2.1 (page 9) set at 150% for the special population, and not as a separate fuel-specific cap. This reading is recorded here because the sentence, taken literally, says 130%. The DAAS flag does not broaden the separate medical-deduction definition, which has its own disability criteria.Other limitations: SPM units approximate energy households.
heating_typemust be sent: an unspecified fuel is paid $0 with no fallback fuel, except when heat is included in rent, where the payment depends only on size and income and the tenant may not know the fuel. Detailed immigration/document categories, optional foster-child inclusion, childcare payer proration and extra transport costs, institutional status, and vendor-contract/account checks remain incomplete. Annual income approximates application-month budgeting. The plan marks SNAP categorical eligibility but leaves its operational definition blank; the model follows the manual's financial tests and does not infer a blanket SNAP waiver. Automatic reenrollment, tribal administration, discretionary supplements and funding-exhaustion adjustments are not modeled.Source reconciliation: the FY2026 matrix labels the special population age 65, while the plan and EP-300 say 60; the model follows the plan and manual. The manual's six-person special-payment threshold has a typo; the matrix and adjacent band boundary establish $2,697. The May 2026 amendment changes terminated-income handling and processing, not the modeled payment schedule; those timing exceptions remain documented gaps.
Regular heating only. Crisis, cooling, weatherization and equipment assistance are excluded.
Sources:
Microsimulation: the datasets carry no heating type and no fuel bills, and vulnerability needs a heating bill, so population totals are $0 until both are in the microdata. The default heating type is tracked separately in #9754.
Validation: 46 North Carolina YAML cases cover the published income tables through size 26 and their extrapolation beyond it, FY2026 and FY2027 payment-band boundaries including the half-dollar rounding cases, special-population rules, resources and waiver expiry, public-housing excess payments, heat in rent with an unspecified fuel, household proration, qualified non-citizens, the work deduction, and historical backfilling. CI runs the test suite.
axiom: TheAxiomFoundation/rulespec-us#1466 queued