Skip to content

Add North Carolina LIEAP regular heating assistance - #9726

Open
hua7450 wants to merge 9 commits into
PolicyEngine:mainfrom
hua7450:nc-lieap
Open

hua7450 wants to merge 9 commits into
PolicyEngine:mainfrom
hua7450:nc-lieap

Conversation

@hua7450

@hua7450 hua7450 commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

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.

  • Applies regular 130% and special-population 150% limits for age 60+ or qualifying disability with DAAS services to existing federal poverty guidelines. Eligibility and lower payment bands are independently rounded from unrounded guideline amounts; the calculations reproduce published FY2026 tables, including large households. The year denotes the heating season ending in that year.
  • Reuses existing income and expense inputs and incorporated SNAP income-source parameters. EP-300.09 A takes countable income types from the FNS manual, whose sections 350.01 D.1, 300.02 and 315.09 exclude a K-12 student's earnings and Title IV work-study pay; the state plan's section 1.9 checklist marks child earnings and work-study income as countable without those exceptions, and the model follows the manual. The shared SNAP earner variable does not test head-of-unit status and excludes all earnings of a federal work-study participant. Includes work-related deductions, the $85 monthly medical allowance per specified person, child support and care deductions, and excluded-member income proration with the separate unprorated gross test. Counts existing net self-employment (ordinary and SSTB) and farm operations income without a second business deduction; the earned sources are a list parameter, and rental income is counted there because Section 300.09 B.3 gives it the work deduction. The work deduction is capped at earnings, a model choice that keeps the $10 minimum deduction from offsetting unearned income. The plan's section 1.8 reports gross income while Section 300.09 B nets out deductions; the model follows the manual.
  • Implements $300/$400/$500 payments by household size and income, the fixed $300 coal/wood payment for every income-eligible household, and heating vulnerability including the public-housing excess-payment exception. The FY2025–2026 resource waiver ends on its published date; later resource tests count net bank balances, nonbank cash and retained lump sums, including ineligible members’ resources. Checks the FY2027 amendment's income thresholds. No federal benefit aggregate is changed.

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_costs records 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 reuse has_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, plus nc_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_disabled records 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_type must 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

@codecov

codecov Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Codecov Report

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

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     
Flag Coverage Δ
unittests 100.00% <100.00%> (ø)

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

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

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

hua7450 and others added 3 commits September 30, 2026 19:59
…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>
@DTrim99

DTrim99 commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator

Program Review

PR #9726: North Carolina LIEAP regular heating assistance (hua7450). Reviewed head: 895d89f452.

Scope: 33 files: 15 parameters, 11 variables (7 formulas and 4 inputs), 5 test files (39 cases), a changelog fragment and programs.yaml. The PR covers regular heating only; the Crisis Intervention Program, cooling and weatherization are excluded. The year denotes the heating season ending in that year, so FY2026 is period 2026. Parameters start 2025-10-01, and earlier years are backfilled.

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 UNSPECIFIED receives $0, and that includes the heat-in-rent public-housing case. The other findings cover a contested coal/wood income cap, a microsimulation data gap, reference anchors, test gaps and PR hygiene.

Path abbreviations:

  • params/ = policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/
  • vars/ = policyengine_us/variables/gov/states/nc/ncdhhs/lieap/
  • tests/ = policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/

Line numbers refer to the files at head 895d89f452. "EP-300 (May 2026)" is the PR's main citation. "EP-300 (Oct 2026)" is the current manual.

Source Documents

Document Link Pages used
EP-300 Energy Programs Manual, Change 2-2026, May 1 2026 (PR's main citation; 22 pp) EP-300-5.1.2026.pdf p1-2 (300.02 DAAS), p9 (300.08), p10-15 (300.09 limits, deductions, tables), p15-17 (300.10), p17-18 (300.11), p18-20 (300.12)
EP-300, Change 04-2026, Oct 1 2026 (current manual; same text as EP-CN-04-2026a4.pdf; 22 pp) EP-300_10.1.2026.pdf p14-15 (FY2027 limits), p18 (300.11, 300.12 A), p19-21 (bands; coal/wood row p20)
EP-300, Change 3-2026, Oct 1 2026 (PR body's FY2027 citation, since superseded) EP-CN-03-2026a1.pdf p14-15, p17, p19-20
FY2026 heating benefit matrix (3 pp) NC_BenefitMatrix_Heat-Cool_2026.pdf p1-3 (130% and 150% limits and bands)
FY2026 LIHEAP state plan (49 pp) NC_Plan_2026.pdf p5 (1.4, 1.8), p5-6 (1.9), p9-10 (2.1, 2.3, special population, payments)
FY2025 LIHEAP state plan (47 pp) NC_Plan_2025.pdf p5, p8, p9
EP-150 household; EP-175 citizenship and alien status eps150.pdf; eps175.pdf EP-150 p1; EP-175 p1 and p4-6 (175.05)
FNS 350, FNS 300, FNS 315 (income rules incorporated by 300.09) fns-350; fns-300; fns-315 FNS 350 p2; FNS 300 p5, p21, p26; FNS 315 p8
Change notices: CN-05-2025 (FY2026 FPL), FNSEP-CN-06-2025 (resource waiver), CN-03-2026 (FY2027 FPL), CN-04-2026 EP-CN-05-2025.pdf; FNSEP-CN-06-2025.pdf; EP-CN-03-2026.pdf; EP-CN-04-2026.pdf p1 each (CN-04-2026 p1-2)
Earlier EP-300 editions: Change 5-2025 (Oct 1 2025) and Change 6-2025 (Nov 1 2025) EP-CN-05-2025a1.pdf; FNSEP-CN-06-2025a1.pdf p13-14 (FY2026 limits), p17 (300.11 without and with the waiver)
FNS 225 and FNS 227 (non-citizen rules) fss225-1.pdf; FNS-227 FNS 225 p1; FNS 227 p1-4, p38-40
  • Year: FY2026 (period 2026) is verified. The FY2027 thresholds (period 2027) were checked against EP-300 (Oct 2026).
  • Scope: PR changes only.
  • Not available: the FY2027 NC state plan and matrix return 404 because they have not been posted.

Branch Status

PR 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 heating_type = UNSPECIFIED receives $0, including the heat-in-rent public-housing case

  • Code: vars/nc_lieap.py:35-40. The comment says "NONE/UNSPECIFIED do not establish a heating arrangement", and the where returns 0 for both values.

  • Tests that lock this in:

    • tests/integration.yaml:60-105 (Case 4) asserts nc_lieap_eligible: true and nc_lieap: 0.
    • tests/nc_lieap.yaml:66-75 asserts that the UNSPECIFIED column, with nc_lieap_eligible forced true, pays 0.
  • Source: EP-300 (May 2026), 300.08 item 4, p.9:

    "A household living in public housing where utilities for heat are billed separately from the rent or where utilities for heat are included in the rent, and the household has paid an excess in utilities for heat in the last 12 months at the current address, is fully vulnerable."

    EP-300 (May 2026), 300.12 A, p.18:

    "Benefits are based on the household's size and income at the time of application. … Households that heat with coal and/or wood will receive a benefit of $300 regardless of household size"

    The only fuel-dependent payment rule is the coal/wood flat amount. The manual does not make an unknown fuel pay $0.

  • Repo input contract: the heating_type docstring (policyengine_us/variables/household/expense/utilities/heating_type.py) says UNSPECIFIED "is also the right value when heat is included in rent and the fuel is unknown, since the separate heat_expense_included_in_rent input carries that fact." Both that path and a directly set has_heating_expense produce an eligible household with UNSPECIFIED fuel.

  • Code-path trace (Case 4, period 2026):

    1. heat_expense_included_in_rent, is_in_public_housing and nc_lieap_has_paid_excess_heating_costs are all true, so vars/nc_lieap_eligible.py:37 sets vulnerable = True.
    2. Income is $1,248 a month, at or below the $2,887 limit, so the household is eligible.
    3. nc_lieap passes defined_for. Line 37 then matches fuel == UNSPECIFIED, so the household receives 0.
    4. Case 5 is the same household with electric heat named, and it receives $400.
  • Effect: household calculations understate the benefit by the full $300-$500 band amount for these households. Current microsimulation totals do not change, because heat_expense_included_in_rent is absent from the microdata (see S2).

  • Fix direction: pay the size-and-income band for UNSPECIFIED, since only COAL/WOOD is fuel-specific. A NONE household without heat in rent already fails vulnerability. Update Case 4 and the UNSPECIFIED column in tests/nc_lieap.yaml.

Should Address

S1. The coal/wood flat award does not apply the manual's express 130% cap for coal/wood households

  • Code: vars/nc_lieap.py:28-39. The comment at :29-33 reads the 300.12 A sentence as restating general income eligibility.

  • Tests:

    • tests/nc_lieap.yaml:52-65: age 60, $1,696 a month, $300.
    • tests/nc_lieap.yaml:77-92: DAAS-disabled coal household at $1,696, $300.
    • tests/nc_lieap.yaml:94-122: age-62 wood household up to $1,956, $300.
  • Source: EP-300 (May 2026), 300.12 A, p.18:

    "Households that heat with coal and/or wood will receive a benefit of $300 regardless of household size; however, the income will still need to be at or below the 130% income limit."

    In the 300.12 B charts (p.20), the coal/wood $300 row sits inside the 130% table, and the 150% table has no coal/wood row. The current edition repeats both the text and the layout (EP-300 (Oct 2026) p.20). A 600-dpi render confirms the row placement.

  • Effect: the outcome flips between $300 and $0 for special-population coal/wood households with income above 130% and at or below 150% FPG. If the literal reading is correct, the PR overstates benefits for that group. The size is not quantified, and it is currently moot in microsimulation (S2).

  • Ask: either apply the literal 130% cap to the coal/wood path, or obtain and cite written NCDHHS confirmation (dss.policy.questions@dhhs.nc.gov). At minimum, record the contested reading in the PR body.

S2. Microsimulation: absent heating inputs drive nc_lieap to $0 (downward bias, full program amount)

  • Code: vars/nc_lieap_eligible.py:33-37; vars/nc_lieap.py:36-40.
  • Mechanism:
    • The Populace engine ABI lock (populace/packages/microcosm-build/src/microcosm/build/us/engine_abi.lock.json, remaining_stage_input_manifest rows 775, 779 and 782) lists heating_type, gas_expense and heat_expense_included_in_rent as declared_absent_engine_input with fallback: policyengine_default_if_absent.
    • Every NC SPM unit therefore has heating_type = UNSPECIFIED. That makes heating_expense 0, has_heating_expense false and heat_expense_included_in_rent false, so vulnerable is false.
    • This is independent of C1: even with C1 fixed, the vulnerability gate fails under these defaults.
    • DC, IL and MA LIHEAP keep deprecated adapters for UNSPECIFIED, and NC has none. The same data gap was reported on the sibling LIHEAP PRs (Add Kansas LIEAP regular heating assistance #9663, Add Nebraska LIHEAP regular heating assistance #9723, Add Kentucky LIHEAP regular heating assistance #9725).
  • Bias direction and size:
    • Heating inputs: downward, by the full program amount. The modeled NC LIEAP total is $0 in any dataset that leaves these inputs absent.
    • nc_lieap_is_daas_disabled defaults to false: downward. Disabled members under 60 are tested at 130% instead of 150% FPG. Size not quantified.
    • nc_lieap_has_paid_excess_heating_costs defaults to false: downward, for heat-in-rent public-housing units. Size not quantified.
    • nc_lieap_nonbank_resources defaults to 0 (from period 2027, when the resource test applies): upward, because cash and lump sums are understated. Size not quantified.
    • nc_lieap_bank_account_exclusions defaults to 0: slightly downward.
  • Valid remedies: (a) populate the inputs in the dataset; (b) document the limitation, for example in the programs.yaml notes, stating that aggregates are near zero until heating inputs exist in the data; (c) accept the bias if it is quantified and small.

S3. 25 of 33 reference URLs use multi-page #page=a,b,… anchors, which PDF viewers do not support

Every cited page holds the claimed content, with offset 0. Viewers honor only a single #page=N. Use the suggested page, or split the URL into separate references.

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:17 and nc_lieap_household_size.py:11.
  • PR body: NC_Plan_2026.pdf#page=5,9,10 is multi-page, and EP-300…#page=1,2,9..20 is 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,37
    • tests/nc_lieap.yaml:3,16,29,41,56,67
    • tests/nc_lieap_eligible.yaml:3,25,47,69,91,104,118,131
    • tests/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 use period: YYYY, and the same files also mix in period: 2026.
    • PyYAML's YAML 1.1 resolver accepts the underscores, so CI passes. A YAML 1.2 parser would read the string "2_026".
  • Fix: use 2026 / 2027 / 2022, and exclude period: 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) and vars/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: 2027 payment-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).
  • 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 included has no stated basis.
  • Test gap: the only disability resource test (tests/nc_lieap_eligible.yaml:207-222) sets is_usda_disabled: [false, false] and relies on DAAS. No case has a member under 60 who is is_usda_disabled but not DAAS-disabled, so removing that branch would fail no test.
  • Fix:
    • Align the included treatment, or document it in a comment.
    • Add a period: 2027 row: age 40, is_usda_disabled: true, nc_lieap_is_daas_disabled: false, with bank assets of $4,500 (eligible) and $4,501 (ineligible).

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).
  • (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_RESIDENT or REFUGEE) counted in nc_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 by share (: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:1 and params/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".
  • Inaccurate description:
    • params/earned_income_deduction_rate.yaml:1 says "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:1 contains "as inferred from the published income limit tables". See G6 for a direct source.
  • Metadata: earned_income_deduction_rate.yaml:4 has period: year, while its companion earned_income_deduction_threshold.yaml:4 has period: 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 only return add(spm_unit, period, ["is_citizen_or_legal_immigrant"]).
  • Fix: replace the formula with adds = ["is_citizen_or_legal_immigrant"], following the precedent at policyengine_us/variables/.../spm_unit_count_children.py:9, and move the six-line comment into documentation.
  • 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 queued rulespec-us issue, labelled pe-parity and 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/a is reserved for changes that are not policy.
  • Fix: open the issue and change the line to axiom: <issue> queued.

Suggestions

  1. G1. resource_test dating: 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.
  2. 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.
  3. 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-60 documents 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.
  4. G4. Keep is_citizen_or_legal_immigrant for 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.
  5. G5. Citation editions.
    • params/payment/higher_income.yaml:18-19 and params/payment/lower_income.yaml:18-19 cite 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.
  6. G6. fpg_year_lag has a direct source. params/fpg_year_lag.yaml:8-10.
    • The change notices state the lag outright: CN-05-2025 "update[s] the Federal Poverty Level for Federal Fiscal year 2026 … effective October 1, 2025" (p.1), and CN-03-2026 does the same for FY2027 (p.1).
    • Cite them, and drop "as inferred" (S8).
  7. G7. Move the earned and unearned income sources into LIEAP-owned parameters.
    • vars/nc_lieap_earned_income.py:30-37 hardcodes five income variables.
    • vars/nc_lieap_gross_income_person.py:17 filters "rental_income" out of gov.usda.snap.income.sources.unearned by 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).
  8. G8. The care deduction is not tied to earnings.
    • vars/nc_lieap_income.py:35,42 deducts 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.
  9. 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.
  10. 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 the nc_lieap documentation. S7(a) covers the test.
  11. 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 :46 sum, by at most $10 a month (p.12).
    • Consider min_(work_deduction, earned).
  12. G12. Merge the two add() calls in the gross test (vars/nc_lieap_eligible.py:24-26) into add(spm_unit, period, ["nc_lieap_gross_income_person", *sources]).
  13. G13. Folder layout.
    • Fifteen parameters sit at the top level, and only payment/ is grouped. Possible structure:
      • income/limit/{regular,special}
      • income/fpg_year_lag
      • income/deductions/{work/{amount,rate,threshold},medical}
      • resources/{limit,special_limit,test}
    • Variables could be grouped the same way, into eligibility/, income/ and resources/.
  14. G14. Naming.
    • nc_lieap_income holds net countable income, so nc_lieap_countable_income would match the state-program convention.
    • params/payment/higher_income.yaml and lower_income.yaml are payment amounts by income band. upper_band / lower_band, or *_amount, would describe them more precisely.
  15. 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.
  16. G16. Revision-history and draft wording.
    • tests/nc_lieap.yaml:95-96 ("the payment formula no longer tests income") and tests/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.
  17. 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.
  18. G18. programs.yaml.
    • NC is appended last on the coverage line (:805) but placed before TX in state_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" while verified_years: "2026" (:832) excludes 2027. Either use "2026-2027" for the verified thresholds or reword the note.
  19. G19. Document the period convention (the year is the heating season ending in that year) in the nc_lieap documentation (vars/nc_lieap.py:10-13). At present it appears only in the fpg_year_lag description and the PR body.
  20. G20. Changelog wording. changelog.d/nc-lieap.added.md:1 uses 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 bands floor(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.
  • 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):

    1. SNAP categorical eligibility (G3).
    2. Automatic reenrollment and prior-year awards.
    3. Tribal administration.
    4. Discretionary supplements.
    5. Institutional status and vendor/account checks.
    6. EP-175 document and PRUCOL categories, qualified-but-ineligible members, and the optional inclusion of foster children.
    7. 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

  1. C1: stop zeroing UNSPECIFIED in vars/nc_lieap.py, so that eligible households receive the size-and-income band. Update integration Case 4 and the UNSPECIFIED column in tests/nc_lieap.yaml.
  2. 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.
  3. 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.
  4. S3: convert the 25 multi-page anchors to single pages (table above), and fix the PR-body anchors.
  5. S4, S8, S9: replace the 2_0xx periods, revise the parameter descriptions, and switch nc_lieap_household_size to adds.
  6. S5-S7: add the FY2027 band-edge rows, the is_usda_disabled resource row, and the four edge cases. Align or document the special-population included difference.
  7. S10: open a dispatch-ready rulespec-us issue labelled pe-parity, and change the Axiom line to queued.
  8. Suggestions: optional. G1 (resource-test dating) and G5 (citation editions) affect the year-coverage notes.
  9. Rebase onto main after the first sibling LIHEAP PR merges, then re-request review.

To auto-fix issues: /fix-pr 9726

@DTrim99 DTrim99 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Requesting changes 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.

hua7450 and others added 2 commits October 2, 2026 12:35
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>
@hua7450

hua7450 commented Oct 2, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the review. Fixes are in 586fafeb25, followed by a merge of main (head 419f6ef3de). Nothing was run locally; CI validates.

Critical

  • C1. Fixed for the case the source describes. An unspecified fuel is now paid the size-and-income amount when heat is included in rent, which is the public-housing household in 300.08 item 4 and the value the heating_type documentation prescribes there. Integration Case 4 expects $400. Outside heat in rent an unspecified fuel still pays $0: callers must send heating_type.

Should-address

  • S1. Kept, and recorded in the PR body and the code comment. EP-300.09 (page 10) and plan section 2.1 (page 9) set the limit at 150% for households with a member aged 60 or older or a DAAS-served disabled member. The 300.12 A paragraph says "The income limit is 130%" for every household, so its coal and wood sentence is read as restating income eligibility, not as a fuel-specific cap.
  • S2. Tracked in Default heating_type to electricity and remove the UNSPECIFIED-gated state heating adapters #9754 (default heating_type); it affects API partners and is separate work.
  • S3. Fixed: one page in each link, sections and pages named in the title or in a comment above the URL.
  • S4. All 23 periods fixed.
  • S5. FY2027 band-edge rows added (865/866, 998/999, 2,773/2,774).
  • S6. The is_usda_disabled resource row is added, and a comment explains why the two tests differ. Behaviour unchanged.
  • S7. All four edge cases added.
  • S8. Descriptions rewritten, including the 20% rate. Its period stays year, which is the repo rule for a dimensionless rate.
  • S9. nc_lieap_household_size uses adds.
  • S10. Now axiom: TheAxiomFoundation/rulespec-us#1466 queued.

Suggestions

  • Taken: G4, G5, G6, G11 (deduction capped at earnings, with a test), G12, G15, G16, G18 (verified_years: "2026-2027", alphabetical registry order), G19, G20, and the comments for G1, G2, G8, G9, G10.
  • G7, in part: the earned sources are a list parameter, and the gross-income variable skips any SNAP unearned source that is on that list instead of naming rental_income. The SNAP unearned list is still reused, not copied.
  • Not taken: G13, G14, G17.

@hua7450
hua7450 requested a review from DTrim99 October 2, 2026 16:37
@DTrim99

DTrim99 commented Oct 2, 2026

Copy link
Copy Markdown
Collaborator

Program Review — Round 2

PR #9726: North Carolina LIEAP regular heating assistance (hua7450). Re-reviewed head: 419f6ef3de. That head is fix commit 586fafeb25 ("Address North Carolina LIEAP review") plus a merge of main. Round 1 reviewed 895d89f452.

Scope now: 35 files:

  • 16 parameters, including the new earned_income_sources.yaml;
  • 11 variables: 6 formulas, 1 adds and 4 inputs;
  • 6 test files with 46 cases, including the new nc_lieap_household_size.yaml;
  • the changelog fragment and programs.yaml.

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:

  • params/ = policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/
  • vars/ = policyengine_us/variables/gov/states/nc/ncdhhs/lieap/
  • tests/ = policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/

Line numbers refer to head 419f6ef3de. "EP-300 (May 2026)" is Change 2-2026 (EP-300-5.1.2026.pdf). "EP-300 (Oct 2026)" is Change 04-2026, the current manual (EP-300_10.1.2026.pdf).

Status of Round 1 Findings

ID Finding Verdict Evidence
C1 Eligible UNSPECIFIED-fuel household paid $0, including heat-in-rent public housing FIXED vars/nc_lieap.py:45-52 pays the size-and-income band when heat_expense_included_in_rent is true. tests/integration.yaml:75-122 (Case 4) now expects $400, and tests/nc_lieap.yaml:101-104 adds an UNSPECIFIED + heat-in-rent row at $400. A narrow residual is listed under Suggestions (1).
S1 Coal/wood $300 not capped at 130% ACCEPTED-AS-RATIONALE Round 1's minimum ask, recording the contested reading in the PR body, is met. The PR body paragraph and vars/nc_lieap.py:39-43 give the rationale (see below).
S2 Absent heating inputs give a $0 microsimulation total ACCEPTED (tracked) Tracked in #9754 (open), and the PR body's "Microsimulation" paragraph discloses it. One sentence in the PR body overstates what #9754 alone fixes for NC; see Suggestions (2).
S3 25 multi-page #page=a,b anchors FIXED No multi-page anchor remains in the 16 parameters or 11 variables. Titles or comments now name the page ranges. The PR-body links also use single pages.
S4 period: 2_0xx in 23 places FIXED Zero occurrences in the 6 test files; all periods are 2022, 2026 or 2027.
S5 Half-up rounding untested FIXED tests/nc_lieap.yaml:153-183 (Case 9, period 2027) covers 865/866, 998/999 and 2,773/2,774. All six rows match EP-300 (Oct 2026). Banker's rounding would fail two of the three pairs.
S6 Special-population divergence between income and resource tests; is_usda_disabled branch untested FIXED The comment at vars/nc_lieap_eligible.py:42-55 documents both differences: DAAS versus general disability, and the included asymmetry. tests/nc_lieap_eligible.yaml:294-322 (Case 14) pins the is_usda_disabled resource branch at $4,500/$4,501 and the 130% income limit.
S7 Four untested edge cases FIXED (a) tests/nc_lieap.yaml:185-219; (b) tests/nc_lieap_eligible.yaml:324-352; (c) tests/nc_lieap_household_size.yaml:2-27; (d) tests/nc_lieap_income.yaml:174-222
S8 Description verbs, placeholders, inaccurate rate description, draft wording FIXED All descriptions use allowed verbs, except "counts" in the new list parameter, which 139 state parameter files use. The placeholders are gone, the rate description matches the formula, and "as inferred" is removed. The rate's period: year follows the parameter guidance that a dimensionless rate is period: year.
S9 nc_lieap_household_size should use adds FIXED vars/nc_lieap_household_size.py:34 is adds = ["is_citizen_or_legal_immigrant"], and the explanation moved to documentation (:9-21).
S10 Axiom parity line NOT FIXED The line now reads axiom: TheAxiomFoundation/rulespec-us#1466 queued, and #1466 carries the pe-parity label. The issue is a generic template rather than a dispatch-ready one; details below.

S1 rationale, weighed.

  • For the PR's reading: EP-300 (May 2026) 300.12 A (p.18) opens with "The income limit is 130% of the federal poverty level". That sentence cannot be literal for the special population, because 300.09 (p.10) sets their limit at 150%. Reading the paragraph's later coal/wood sentence ("at or below the 130% income limit") as the same generic restatement is coherent.
  • For the literal reading: in the 300.12 B charts, the coal/wood row appears only in the 130% table. That remains true in the current edition (EP-300 (Oct 2026) p.20, rechecked from the page image).
  • Conclusion: without NCDHHS guidance, neither reading is conclusive. Round 1 accepted documentation as the minimum remedy, and the PR body now records the literal reading and why the model departs from it.

Suggestions the author reports as taken. All verified at head:

  • G2: comment at vars/nc_lieap_income.py:18-20, and the PR body.
  • G4: EP-175.05 cited at vars/nc_lieap_household_size.py:31-33.
  • G5: EP-300 300.12 B p19 added to params/payment/{higher,lower}_income.yaml:18-19. The FY2027 citations name Change 04-2026.
  • G6: the change-notice references at params/fpg_year_lag.yaml:13-16.
  • G8: comment at vars/nc_lieap_income.py:50-52.
  • G9: source-typo note at tests/nc_lieap.yaml:35-44.
  • G10: extrapolation documented at vars/nc_lieap.py:15-18, with a test.
  • G11: cap at vars/nc_lieap_income.py:34-37, with the test at tests/nc_lieap_income.yaml:224-248.
  • G12: single add() at vars/nc_lieap_eligible.py:28.
  • G15: sequential "Case N" names, with no duplicates.
  • G16: revision-history wording removed from the tests, and "draft" removed from the PR body.
  • G18: NC precedes TX in both programs.yaml:805 and :826; verified_years: "2026-2027" at :832; note reworded at :833.
  • G19: period convention documented at vars/nc_lieap.py:11-13.
  • G20: changelog reworded.
  • G1: dating comment at params/resource_test.yaml:2-11.
  • G3: the SNAP categorical limitation is restated in the PR body.
  • G7 (partial): params/earned_income_sources.yaml, plus the list filter at vars/nc_lieap_gross_income_person.py:18-24.
  • Declined: G13, G14 and G17, all optional.

Regression Check on the Delta

Every changed expectation and every new row was recomputed by hand and checked against the source tables. No mismatches were found.

New payment-band tests:

  • FY2027 half-up rounding (tests/nc_lieap.yaml:153-183). Limits use the 2026 guideline of $15,960 plus $5,680 per person:
    • 130%, size 1: $1,729 a month; half is 864.5, published as $0-$865 / $866-$1729.
    • 150%, size 1: $1,995; half is 997.5, published as $0-$998 / $999-$1,995.
    • 150%, size 6: $5,545; half is 2,772.5, published as $0-$2,773 / $2,774-$5545.
    • Sources: EP-300 (Oct 2026) p.20 and p.21. p.21's text layer is misaligned, so the size-6 row was confirmed from a rendered image.
    • The tests expect $400/$300, $400/$300 and $500/$400, which is correct.
  • Extrapolated bands, FY2026 (tests/nc_lieap.yaml:185-219):
    • 130%, size 16: 127,595/12 = $10,633, matching the published limit (EP-300 (May 2026) p.14). The band edge, floor(5,316.46 + 0.5), is $5,316.
    • 150%, size 12: $9,519, matching the published limit (p.15). The band edge is $4,759.
    • Size 27 (unpublished): $17,187, with a band edge of $8,594.
    • The payments of $500 below and $400 above each edge are correct.

Coal/wood 130% cap: the formula is unchanged; only its comment moved. The expectations in tests/nc_lieap.yaml:75-151 are unchanged and consistent with the 150% reading accepted under S1:

  • age 62, wood: $1,956 receives $300 and $1,957 receives $0;
  • age 40, coal: $1,695 receives $300 and $1,696 receives $0.

Changed and new test cases:

  • C1 rows: integration Case 4 recomputes to $1,248 net income against a $2,887 limit and a $1,444 band edge, giving $400. The same household with electricity (Case 5) also receives $400.
  • Case 14, is_usda_disabled (period 2027):
    • income: 15,600 − 1,020 = 14,580, or $1,215 against a $1,729 limit;
    • resources: $4,500 is eligible and $4,501 is not.
    • If the is_usda_disabled resource branch were removed, both households would fail at $3,000.
  • Case 15, no heating bill and heat not in rent:
    • $1,040 net income;
    • the $0-bill household is ineligible;
    • the $900-bill household receives $300, since $1,040 is above the $848 band edge.
  • Case 8, excluded member aged 61: 14,400 − 2,880 − 1,800 − 900 = 8,820. The citations hold: 300.10 B.1 ("Do not allow a medical deduction for an ineligible alien, even if he is a specified person"), A.2.d-e and B.3 are on EP-300 (May 2026) p.16-17.
  • Case 9, deduction cap: 13,380, 13,380 and 13,392, as expected.
  • Household-size case: a permanent resident, a refugee and a citizen count, and an undocumented member does not, giving a size of 3.

Code changes:

  • Behaviour-preserving refactors:
    • vars/nc_lieap_earned_income.py:39-49: the same five sources, with rental income still counted in full and the others multiplied by snap_countable_earner.
    • vars/nc_lieap_gross_income_person.py:24: the same exclusion of rental income.
    • vars/nc_lieap_eligible.py:28: the same sum.
    • vars/nc_lieap_household_size.py:34: the same count.
  • New behaviour: the work-deduction cap at vars/nc_lieap_income.py:37. It only changes results for monthly earnings below $10.
  • Pattern checks: the code is vectorized, and Python if appears only on parameter-list membership. No numeric values are hardcoded.

Comments and documentation:

  • Page citations: each test file's header names the edition its page numbers refer to. Spot checks match: Oct 2026 has the $1,729 limit on p14, $1,995 on p15, 300.11 on p18 and the $85 deduction on p13.
  • Application window: the documentation's "December 2025 through March 2026" matches 300.02 (December 1-31 for the target group, January 1 to March 31 for all households).

Critical (Must Fix)

None.

Should Address (remaining)

S10. The queued rulespec-us issue is not dispatch-ready

  • Where: the PR body's last line, axiom: TheAxiomFoundation/rulespec-us#1466 queued, and rulespec-us#1466.
  • Requirement: the repo guidance (CLAUDE.md "Axiom Parity"; CONTRIBUTING.md#axiom-parity) says a queued issue must be:
    • dispatch-ready and labelled pe-parity;
    • specific, with the module path and corpus citation, the verbatim law and the required outputs;
    • backed by companion tests that reuse the PR's external expected values ("don't copy values computed by policyengine-us").
  • What Colorado Old Age Pension #1466 has:
    • the pe-parity label;
    • links to two corpus manifests, the FY2026 state plan and the benefit-matrix manual;
    • a generic delivery checklist.
  • What it lacks:
    • Module path: the issue says only us-nc/, with no file path. The repo examples are of the form us-nj/statutes/54a:4-7.yaml.
    • Corpus citation for the operative source: EP-300, which supplies most of the encoded rules, is not cited. The issue also says its manifests' "presence on corpus main alone does not establish complete coverage".
    • Verbatim text: none of 300.08, 300.09, 300.10, 300.11 or 300.12 A/B is quoted.
    • Required outputs: for example eligibility, the income limit, countable income and the payment.
    • Companion tests: none. Candidates are the published-table boundary values this PR already uses, such as the FY2026 limits $1,695/$1,956 and bands $848/$978, and the FY2027 $865/$998/$2,773.
    • PR link: the body says the PR URL and scope "still need to be linked; they have not been independently verified here."
  • Comparison: rulespec-us#1422 shows the expected shape, with a module path, corpus citation, verbatim text and rulings.
  • Fix: edit Colorado Old Age Pension #1466 to add the items above. The PR body line can stay as it is.

Suggestions

  1. Residual UNSPECIFIED case: eligible but paid $0.

    • Where: vars/nc_lieap.py:49-51, pinned by the forced-eligibility row at tests/nc_lieap.yaml:101-104 (row 8).
    • Behaviour: when heat is not in rent, an UNSPECIFIED fuel still pays $0. Through the canonical inputs this cannot happen, because heating_expense is 0 for UNSPECIFIED, so has_heating_expense is false and the household is not eligible. It is reachable only when a caller sets has_heating_expense: true directly with no fuel. The output then reads nc_lieap_eligible: true with nc_lieap: 0.
    • Context: this matches the convention Default heating_type to electricity and remove the UNSPECIFIED-gated state heating adapters #9754 records for the new state LIHEAP PRs, and the PR body discloses it.
    • Optional fixes: state the behaviour in the nc_lieap documentation as well, or pay the band in this case too. 300.12 A ties the payment to size and income, and only coal/wood is fuel-specific.
  2. The PR body's microsimulation sentence overstates what Default heating_type to electricity and remove the UNSPECIFIED-gated state heating adapters #9754 delivers for NC.

  3. S1 (optional): written NCDHHS guidance (dss.policy.questions@dhhs.nc.gov) would settle the coal/wood cap. If guidance confirms the literal reading, the fix is confined to vars/nc_lieap.py:52, plus the expectations at tests/nc_lieap.yaml:75-121.

  4. G7 remnant: coupling to the SNAP list.

    • Where: vars/nc_lieap_earned_income.py:40-48.
    • Behaviour: whether the K-12 and work-study earner exclusion applies to a LIEAP earned source depends on whether SNAP lists that source in gov.usda.snap.income.sources.unearned. If a SNAP-side edit moved rental_income off that list, NC rental income would silently become subject to snap_countable_earner.
    • Option: a LIEAP-owned list of the earned sources that are exempt from the earner exclusions, for example [rental_income], would remove the dependency.
  5. G1 remnant: surface the provisional FY2027 resource test.

    • The flag: params/resource_test.yaml:2-6 marks the FY2027 true value as provisional.
    • Where it is missing: vars/nc_lieap.py:11 ("Verified for FY2026 and FY2027"), programs.yaml:832-833 and the PR body make no such distinction.
    • Option: a short clause in one of those places, saying FY2027 thresholds are verified and the resource-test restart follows the waiver's printed end date.
  6. Optional carry-overs (author declined or did in part):

    • G5 remnant: the edition that first set the 2025-10-01 values (EP-CN-05-2025a1 p.13) is still not cited.
    • G13: folder grouping.
    • G14: naming (nc_lieap_countable_income, *_band).
    • G17: tests/integration.yaml:48-67 still asserts unverified 2022 backfill values.

    None of these blocks the PR.

CI Status

35 of 37 checks pass on 419f6ef3de. They include Household API Partners, Lint, the changelog and registry-lock checks, Microsimulation, the other baseline and all contrib shards, the smoke imports for py3.11-3.14, and codecov patch and project.

The NC tests ran in Full Suite - Baseline (states-shard-4), Batch 2: integration 9, nc_lieap 10, nc_lieap_eligible 15, nc_lieap_household_size 1, nc_lieap_income 9 and nc_lieap_income_limit 2. All 46 cases pass.

Failing check Log evidence Classification
Full Suite - Baseline (ssa-usda) (job) make: *** [Makefile:142: test-yaml-no-structural-other-usda] Terminated; "The runner has received a shutdown signal"; exit code 143. No assertion failures. Runner kill. Unrelated to the PR's files.
Full Suite - Baseline (states-shard-3) (job) Exit code 143 after 495 s with no output in Batch 3 (the last file was ok/tax/income/ok_count_exemptions.yaml). No assertion failures. The shard covers DE, FL, GA, HI, MI, MN, MO, NY, OH and OK, not NC. Runner kill. Unrelated to the PR's files.

Neither failure blocks approval on the merits. Both jobs need a re-run before merge.

Branch:

Review Severity: REQUEST_CHANGES

0 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 DTrim99 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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
@hua7450

hua7450 commented Oct 3, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for round 2. Head is now d79103c29f: the previous head plus a merge of main (which brings in #9758). No North Carolina code changed in this push.

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 us-nc/policies/dhhs/energy-programs/ep-300/ for 300.02, 300.08, 300.09, 300.10, 300.11 and 300.12, plus EP-150, EP-175, the FNS sections and the plan, each mapped to its corpus citation path or marked for ingest (EP-300 Change 2-2026 and the FNS manuals are in the corpus; EP-150, EP-175, the change notices with the FY2027 figures and the FY2026 matrix are not); the EP-300 text quoted verbatim, with the FY2026 and FY2027 income limits and payment charts for sizes 1 to 8 transcribed; a required-outputs table; fourteen companion cases derived by hand from the tables (the $848 / $849 / $1,695 / $1,696 and $978 / $979 / $1,956 / $1,957 boundaries, the FY2027 $865 / $866, $998 / $999 and $2,773 / $2,774 pairs, the manual's own ineligible-alien example, and the coal and wood cases); and the interpretations, with the coal and wood 150% reading quoted from both 300.12 A and 300.09.

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.

@hua7450
hua7450 requested a review from DTrim99 October 3, 2026 02:10
@DTrim99

DTrim99 commented Oct 5, 2026

Copy link
Copy Markdown
Collaborator

Program Review — Round 3

PR #9726: North Carolina LIEAP regular heating assistance (@hua7450).

Result: 1 critical, 0 should address remaining, 1 suggestion.

  • Round-2 S10 is fixed. rulespec-us#1466 now has everything CLAUDE.md "Axiom Parity" requires. It also follows the layout of the reference issue, rulespec-us#1467.
  • The programs.yaml merge resolution is correct.
  • New critical item: rental income would be counted twice.
    • Cause: after the merged base, main changed SNAP's unearned-income list (Stop rental losses from offsetting other income in SNAP #9672).
    • When it bites: once the branch takes current main, NC counts positive rental income twice. The new programs.yaml conflict requires that merge.
    • Background: round-2 Suggestion 4 warned about this coupling to the SNAP list.
    • Test impact: the PR's own nc_lieap_income.yaml Case 2 will fail on the next CI run.

Path abbreviations:

  • params/ = policyengine_us/parameters/gov/states/nc/ncdhhs/lieap/
  • vars/ = policyengine_us/variables/gov/states/nc/ncdhhs/lieap/
  • tests/ = policyengine_us/tests/policy/baseline/gov/states/nc/ncdhhs/lieap/

Status of round-2 items

ID Round-2 item Verdict Evidence
S10 The queued rulespec-us issue is not dispatch-ready FIXED The issue body was rewritten (updatedAt 2026-10-02T18:48:23Z). Each requirement is checked in the next table. The PR body still ends with axiom: TheAxiomFoundation/rulespec-us#1466 queued.
G1 An eligible UNSPECIFIED-fuel household is paid $0 when has_heating_expense is set directly Not taken (optional) The author's reply says "Not taken in this round."
G2 The PR body's microsimulation sentence overstated what #9754 delivers FIXED The PR body now reads: "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."
G3 Ask NCDHHS for written guidance on the coal/wood cap Not taken (optional) —
G4 NC's earned-income handling depends on SNAP's unearned list Not taken; the risk has now happened See Critical 1.
G5 Make the provisional FY2027 resource test visible Not taken (optional) —
G6 Optional carry-overs: G5 remnant, G13, G14, G17 Not taken (optional) —
CI The ssa-usda and states-shard-3 shards were killed by the runner Resolved All 37 checks pass on d79103c29f.

rulespec-us#1466 against the Axiom Parity requirements

Requirement Status Evidence in #1466
pe-parity label Met Labelled enhancement and pe-parity. The issue is open and was opened by the PR author.
PR link Met The issue reads "PolicyEngine PR: #9726 (branch nc-lieap)". It also gives:
  • scope: regular heating only; CIP, supplements, weatherization and equipment are out;
  • both policy periods: FY2026 as model period 2026, and FY2027 as 2027, with the resource test;
  • the variables, parameters and test count.
Module path Met The issue proposes:
  • six EP-300 section modules under us-nc/policies/dhhs/energy-programs/ep-300/ (300-02, 300-08, 300-09, 300-10, 300-11, 300-12);
  • an FY2027 expression of 300-09 and 300-12;
  • EP-150 and EP-175 modules;
  • FNS 350, 300, 315 and 225 modules;
  • plan sections 1 and 2;
  • the FY2026 matrix;
  • a us/ poverty-guidelines module.
The layout follows the existing us-nc/policies/dhhs/fns/... modules. Round 2 found only us-nc/.
Provision-level corpus citations Met EP-300 Change 2-2026 maps to us-nc/manual/dss/energy-programs/ep-300 (manifest us-nc-liheap-benefit-matrix-manual.yaml), with page ranges for each section. Round 2's main gap, EP-300 being uncited, is closed. The following are marked "needs ingest", each with single-page official links:
  • EP-150 and EP-175;
  • the change notices;
  • the FY2027 prints (Change 3-2026 and Change 04-2026);
  • the FY2026 matrix;
  • the 2025 poverty notice.
The dss/dhhs path mismatch and the manifest's benefit_matrix_pdf_pages: '19' are disclosed.
Verbatim operative law Met Quoted:
  • EP-300: 300.02 A, 300.07, 300.08 items 1-5, 300.09 with A and B.1-B.5, 300.10 A-B with the manual's examples, 300.11 and 300.12 A-B;
  • EP-150.01, and EP-175.01, .05 and .06 D;
  • FNS 350.01, 300.01-.02 and 315.09;
  • plan items 1.6, 1.8, 1.9, 2.1, 2.3, 2.5 and 2.6;
  • the FY2026 and FY2027 income limits and payment charts for sizes 1-8.
Phrases from 300.08, 300.10, 300.11, 300.12 A, EP-150.01 and EP-175.01 were found in the texts pinned in round 1. The "$0-$2,267" typo was confirmed on EP-300 page 20.
Required outputs Met A seven-row table gives the variable, entity, period and definition for each output: eligible household size, earned income, gross income, income limit, countable income, eligible and payment. A full input list follows.
Companion tests from the external source Met Fourteen cases. The issue says: "Every expected value is read from the EP-300 tables and text quoted above. None is taken from policyengine-us output." The cases include:
  • the FY2026 boundaries $848/$849/$1,695/$1,696 and $978/$979/$1,956/$1,957;
  • the FY2027 pairs $865/$866, $998/$999 and $2,773/$2,774;
  • the manual's ineligible-alien example: $300, then $200, then $40, then $160.
All 14 hand derivations were recomputed and hold, including the monthly-to-annual conversions and the FY2027 limits: 15,960 × 1.3 = 20,748; × 1.5 = 23,940; (15,960 + 5 × 5,680) × 1.5 = 66,540. They match the PR's expectations, e.g. integration.yaml Cases 1, 2, 5, 6, 7 and 8: $14,976 / $400; $26,120 / $300; $22,068, $21,180, $21,180 / $300, $300, $0; $15,600 / $400. The coal/wood case stays at or below 130%, so it does not depend on the contested 150% reading. That reading is set out under "Interpretations and gaps".

programs.yaml merge resolution

  • Diff against main (7bc185b0cc). The head adds only NC to the coverage line, giving coverage: OR, DC, Riverside County, MA, IL, KS, NC, TX, plus the eight-line NC entry.
  • NC entry. As parsed YAML, it is identical to the entry reviewed at 419f6ef3de: status partial, variable nc_lieap, prefix gov.states.nc.ncdhhs.lieap, verified_years: "2026-2027" and the same note.
  • Kansas and everything else. The KS entry from Add Kansas LIEAP regular heating assistance #9663 is identical to main. So is every other state implementation and program; both sides have 103 programs.
  • Ordering. The state list keeps main's order (OR, DC, MA, IL, KS) and puts NC after KS and before TX. The coverage string uses the same order.
  • Validity. The file parses with yaml.safe_load, and a duplicate-key check finds none.

Delta check

Critical (Must Fix)

1. After the next main merge, positive rental income is counted twice. This is a semantic conflict with #9672.

  • Where:
    • vars/nc_lieap_gross_income_person.py filters SNAP's list with sources = [source for source in p.unearned if source not in earned_sources].
    • vars/nc_lieap_earned_income.py uses if source in snap_unearned: to decide whether the earner exclusions apply.
    • params/earned_income_sources.yaml lists rental_income.
  • What changed on main:
  • Effect once merged:
    • Double count. The gross-income filter drops only names that appear on NC's earned list, and snap_rental_income is not one of them. It therefore enters the unearned sum, while rental_income is already counted in nc_lieap_earned_income. Positive rental income is counted twice in:
      • nc_lieap_gross_income_person;
      • nc_lieap_income;
      • the eligibility test;
      • the payment band.
    • Earner exclusion.
      • The change: rental_income in snap_unearned becomes false, so rental income starts being multiplied by snap_countable_earner.
      • Who is affected: a K-12 student, or a federal work-study participant, would lose their rental income from the count.
      • Conflict with the code's intent: the code comment says rental income is "counted in full".
  • Worked example: tests/nc_lieap_income.yaml Case 2, period 2026. One adult aged 40 has $6,000 of self-employment income, $3,000 SSTB, $3,000 farm and $6,000 rental income.
    • Expected under EP-300.09 B.3, and pinned by the test:

      • earned income $18,000;
      • gross income $18,000;
      • countable income 18,000 × 0.8 = $14,400, i.e. $1,200 a month, which is eligible against the $1,695 limit.
    • After merging current main:

      • earned income $18,000;
      • gross income 18,000 + 6,000 = $24,000;
      • countable income 24,000 − 3,600 = $20,400, i.e. $1,700 a month.

      That is above the $1,695 one-person limit (EP-300 Change 2-2026, page 14), so the household becomes ineligible.

  • Why CI is green now: the passing runs tested a merge with 7bc185b0cc, which predates Stop rental losses from offsetting other income in SNAP #9672. Resolving the current conflict means merging newer main, which brings Stop rental losses from offsetting other income in SNAP #9672 in.
  • Fix, one option:
    • Keep the lists on the LIEAP side instead of inferring them from SNAP's list:
      • Gross income: in nc_lieap_gross_income_person, filter with a parameter that lists the SNAP unearned sources LIEAP counts as earned. That list is snap_rental_income, plus snap_farm_rent_income if farm rent moves to the earned list.
      • Earned income: in nc_lieap_earned_income, replace if source in snap_unearned with a parameter that lists the earned sources exempt from the earner exclusions (rental_income).
    • Decide how to treat farm rent.
      • The question: should Form 4835 farm rent (farm_rent_income) join rental_income on the earned list?
      • The rule: 300.09 B.3 applies the work deduction to "all types of earned income including farm, self-employment, and rental income".
      • If nothing changes: after Count farm rental income as SNAP unearned income #9671, farm rent is counted as unearned income, with no work deduction.
    • Keep Case 2's expectations of $18,000, $18,000 and $14,400. They follow EP-300.09 B.3 and should not be re-pinned to the merged output. A test case with a K-12 child who has rental income would pin the second effect.
    • Update Colorado Old Age Pension #1466 if the definitions change. The "Earned income per person" and "Gross income per person" rows should match the fix. The gross row now reads "Earned income above plus the SNAP unearned sources", which would also double count rental income under the new SNAP list.

Should Address (remaining)

None.

Suggestions

  1. Optional carry-overs from round 2 that were not taken.

    • G1: an eligible UNSPECIFIED-fuel household is paid $0 when has_heating_expense is set directly.
    • G3: written NCDHHS guidance on the coal/wood cap.
    • G5: state that the FY2027 resource-test restart is provisional, in vars/nc_lieap.py, programs.yaml or the PR body.
    • G6: the EP-CN-05-2025a1 citation, folder grouping, naming, and the 2022 backfill test.

    None of these blocks the PR.

CI

Branch state

  • GitHub status: mergeable: CONFLICTING, mergeStateStatus: DIRTY.
  • Cause: Mississippi LIHEAP (Add Mississippi LIHEAP regular heating assistance #9727) merged at 02:52 UTC on 2026-10-03, 44 minutes after the author's merge at 02:08 UTC. It adds MS at the same place in programs.yaml.
  • Scope: git merge-tree against current main (dfdf88111f) shows policyengine_us/programs.yaml as the only conflicting path.
  • Expected resolution: use coverage: OR, DC, Riverside County, MA, IL, KS, MS, NC, TX, and put the NC entry after MS and before TX.
  • Linked to Critical 1: that merge also brings in Stop rental losses from offsetting other income in SNAP #9672, so Critical 1 needs to be fixed in the same push or Case 2 fails.
  • Sibling PR: NE LIHEAP (Add Nebraska LIHEAP regular heating assistance #9723) inserts at the same spot, so whichever of the two merges second needs one more resolution of the same kind.

Review Severity: REQUEST_CHANGES

There is 1 critical issue, 0 remaining should-address items and 1 optional suggestion.

@DTrim99 DTrim99 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants