Skip to content

Add Kentucky LIHEAP regular heating assistance - #9725

Merged
hua7450 merged 12 commits into
PolicyEngine:mainfrom
hua7450:ky-liheap
Oct 5, 2026
Merged

hua7450 merged 12 commits into
PolicyEngine:mainfrom
hua7450:ky-liheap

Conversation

@hua7450

@hua7450 hua7450 commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

Adds Kentucky LIHEAP regular heating assistance for FY2025 and FY2026, using the annual year to represent the heating season ending in that year. A one-person household with no countable income and a gas heating bill receives $134; the published subsidized-housing schedule gives $26.

Year convention: undated formulas follow the model's existing parameter backfilling. Parameter values are dated 2024-10-01 and verified against the FY2025 and FY2026 plans and benefit workbooks; the registry records verified_years: "2025-2026". Earlier years use the earliest schedule with period-specific federal guidelines and the one-year guideline lag, and are unverified historical estimates.

  • Uses 150% of the prior year's poverty guidelines and monthly whole-dollar ceiling rounding. Above eight people the increment is derived from the lagged guideline's additional-person amount at the same rate and rounding, which reproduces the fact sheet's $688 for FY2026; no dollar increment is stored. The regulation's base is 130%; Section 3(1)(b) allows a cabinet-approved increase, and the FY2025 and FY2026 plans set 150%.
  • Reproduces all 384 regular heating cells of the FY2026 workbook and all 384 of the FY2025 workbook with compact point parameters, including the two-person coal exception and the increasing income points for subsidized housing. Subsidized housing is identified by receives_housing_assistance or is_in_public_housing. No new input variables.
  • Counts existing net self-employment (ordinary and SSTB), partnership and S corporation income and farm operations income once without another business-expense deduction, and floors each earned and unearned source at zero; both source lists are parameters. Unearned income includes State Supplementation (ky_ssp), railroad benefits, farm rental income, workers' compensation and employer disability benefits. Excludes the earnings of members under 18. Uses annual tanf, which already includes ky_ktap and applies take-up.
  • Social Security is counted gross for FY2025, where the plan checks "Including MediCare deduction", and net of the Medicare Part B premium the person pays from FY2026, where the plan checks "Excluding MediCare deduction". A dated parameter switches the treatment, and the premium is the existing medicare_part_b_premium, which is zero for a person who is not enrolled and excludes the share a Medicare Savings Program pays, since that share is not withheld from the check. Military retirement pay is counted beside pension income, because the two are separate inputs.
  • Full subsidies may credit an account and are not capped at the current bill.

Household and immigration basis

921 KAR 4:116 Section 2(1)(d) requires "a Social Security number, or a permanent residency card, for each household member"; Section 2(2) treats the application as incomplete until it is received, and Section 8(3) denies it after five working days. A household with a member who has neither is therefore ineligible. The plan's Section 17.2 checklist requires the card from adults and requests it from other members; the regulation's per-member rule is applied. At least one member must also be a citizen or qualified noncitizen, because LIHEAP is a federal public benefit. Section 1(10) defines the household with no immigration carve-out, so every member counts for size and income. 921 KAR 4:116; Kentucky FY2026 plan, Sections 17.2 and 17.3, pages 33 and 34

Source discrepancies

The FY2026 rev benefit income range worksheet has a stale 2024-2025 title but uses 2025 poverty guidelines and matches the FY2026 plan's $15-$250 range; the conditional if no funding FFY26 sheet is not used. The two-person coal cells are preserved as published. Both workbooks' subsidized rows reference the unsubsidized rows in reverse order, so the published subsidized payment rises with income, while 921 KAR 4:116 Section 4(1)(c) gives the highest benefit to the lowest income; the model reproduces the published cells, and which ordering agencies pay is unconfirmed. The fact sheet specifies $688 per additional person, which the derived increment matches; the workbook's own extension for sizes 9-14 differs by up to $3. The FY2025 plan's $134 minimum benefit contradicts its own workbook, whose lowest heating cell is $15.

Other documented gaps

Annual income divided by twelve approximates the month before application; SPM units approximate energy-purchasing households. WIA and work-study exclusions, some lump sums, royalties, jury duty, legal settlements, other insurance payments and refunds cannot be isolated with existing inputs. The FY2026 Medicare premium deduction from Social Security is not modeled. Existing housing-assistance indicators approximate federally assisted housing. Fuels without a published column, including an unspecified fuel, return zero.

Microsimulation: the datasets carry no heating inputs, so population totals are $0 until the default heating type changes. That change affects API partners and is tracked separately in #9754.

Heating only; crisis, cooling, weatherization and equipment assistance are excluded.

Sources:

Validation: 34 Kentucky YAML cases cover the income limit and large-household extension, an FY2025 household, the documentation and citizenship rules, subsidized payment cells, the coal adjustment, age exclusions and the benefit calculation. An independent script matched all 384 heating cells of each workbook against the point formula. CI runs the test suite.

axiom: TheAxiomFoundation/rulespec-us#1465 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 (21682c4).
⚠️ Report is 196 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #9725   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            4         5    +1     
  Lines           76       108   +32     
  Branches         2         4    +2     
=========================================
+ Hits            76       108   +32     
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 5 commits September 30, 2026 19:59
…come limit

- Move the program from ky/chfs to ky/dcbs
- Derive the above-eight income-limit increment from the lagged poverty
  guideline in place of the stored $688
- Drop the hud_utility_allowance proxy from the subsidized-housing test
- Floor each unearned source at zero per source
- Count ky_ssp, farm operations and farm rent income, workers' compensation and
  disability benefits; replace the deprecated dividend_income alias
- Cite the statewide fact sheet PDF; add metadata.period to parameters
- Document the workbook's reversed subsidized rows and the mixed-status gate
  assumption without changing behavior
- Add end-to-end, income, subsidized-cell and large-household tests

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ky LIHEAP

The FY2026 plan's Section 1.9 has no dedicated row for either source, so they are recorded as a documented gap instead of counted income. Update the income test case accordingly.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@hua7450
hua7450 marked this pull request as ready for review October 1, 2026 19:12
@hua7450
hua7450 requested a review from DTrim99 October 1, 2026 19:12
@DTrim99

DTrim99 commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator

Program Review

PR #9725: Add Kentucky LIHEAP regular heating assistance (hua7450). Reviewed head: 88403030b1.

Scope: 25 files: 13 parameters, 5 variables, 5 test files (24 cases), a changelog fragment and programs.yaml. The PR covers the regular heating subsidy only (crisis, cooling and weatherization are excluded). It is verified for FY2026 (period 2026). Every parameter is dated 2025-10-01, so earlier years are backfilled.

Overall: The benefit arithmetic reproduces the published matrices exactly:

  • 384 of 384 FY2026 heating cells match.
  • 384 of 384 FY2025 cells match, using a workbook found during the review.
  • 8 of 8 fact-sheet income limits match.
  • All 53 audited parameter values match their sources.

One eligibility rule contradicts 921 KAR 4:116. The other findings cover gaps in the income definition, a microsimulation default, citation precision and PR hygiene.

Path abbreviations:

  • params/ = policyengine_us/parameters/gov/states/ky/dcbs/liheap/
  • vars/ = policyengine_us/variables/gov/states/ky/dcbs/liheap/
  • tests/ = policyengine_us/tests/policy/baseline/gov/states/ky/dcbs/liheap/

Line numbers refer to the files at head 88403030b1.

Source Documents

Document Link Pages / cells used
FY2026 LIHEAP state plan (10/01/2025-09/30/2026) KY_Plan_2026.pdf p4 (1.1 dates), p5 (1.4, 1.8, 1.9 earned), p6 (1.9 unearned and Medicare), p8 (2.1, 2.3, 2.4), p9 (2.5, 2.6), p33-34 (17.2-17.4)
FY2025-2026 fact sheet V2 (Community Action Kentucky) 2025-2026-LIHEAP-Fact-Sheet-V2.pdf p1 (income limits, "Add $688", documents, benefits)
921 KAR 4:116, engrossed (effective 11-23-2021) document.engrossed.pdf; HTML as cited in the PR p1 (§1(8), §1(10)), p2 (§2, §3), p3 (§4), p5 (§8)
FY2026 benefit workbook KY_BenefitMatrix_Heat-Cool_2026.xlsx "rev benefit income range", "Subsidy points", "Points Description"
FY2025 benefit workbook (found during the review; not cited in the PR) KY_BenefitMatrix_2025.xlsx "subsidy benefits" (all 384 heating cells; E45)
FY2025 LIHEAP state plan (Revision #3) KY_Plan_2025.pdf p6 (Medicare checkbox), p8 (2.1), p9 (2.6)
FY2027 preliminary state plan (LRC committee document, 7/2/2026) FY2027 preliminary plan p4 (dates), p6 (1.9), p8 (2.1), p9 (2.6)
Community Action Kentucky announcement, 10/30/2025 capky.org news Documents and benefit form
CHFS LIHEAP page chfs.ky.gov Documents (its 130% limit and asset test are stale)

Branch Status

Critical (Must Fix)

C1. The mixed-status gate approves households that 921 KAR 4:116 denies

  • Code: vars/ky_liheap_eligible.py:17-19 treats the unit as eligible when any member passes is_citizen_or_legal_immigrant, and it counts every member toward size and income.
    • The comment at :20-28 says Sections 2(1)(d) and 2(2) do not say whether a mixed-status household is denied. It leaves out Section 8(3), which does.
  • Test that locks the behavior in: tests/ky_liheap_eligible.yaml:2-51 (Case 1).
    • Both parents are UNDOCUMENTED (:8, :13), and the two children are citizens.
    • The test expects ky_liheap_eligible: true (:44) and ky_liheap: 217 (:51).
  • Sources:
    • 921 KAR 4:116 §2(1)(d), p2: "A household or authorized representative applying for LIHEAP shall provide to an agency the following: ... (d) A Social Security number, or a permanent residency card, for each household member."
    • §2(2), same page: "An application shall not be considered complete until the required information, as specified in subsection (1) of this section, is received by the agency."
    • §8(3), p5: "Under a subsidy, crisis or cooling component, an applicant shall have five (5) working days from the date of application to provide required information to an agency as specified in Section 2 of this administrative regulation, or the application shall be denied."
    • FY2026 plan §17.2, p33: for "Social Security Card is photocopied and retained", "Required" is checked for "All Adults in Household"; "All Household Members" is only "Requested". This was confirmed on a 600-dpi render.
    • §17.3, p34: "Non-Citizens must provide documentation of immigration status" is checked.
    • Fact sheet, p1: "Proof of Social Security Number or Permanent Residence card (Green Card) for each member of the household." The CAK announcement and the CHFS page say the same.
  • Trace (period 2026):
    • Monthly income of 2,166.67 is at or below the limit of 4,019, and the heating expense of 1,560 is above 0.
    • The citizen children satisfy the any-member gate, so the unit is eligible and receives $217.
    • Under §2(1)(d) with §8(3), or under plan §17.2, the two undocumented adults hold neither an SSN nor a permanent residency card. The application is incomplete and is denied, so the expected benefit is $0.
  • Fix direction:
    • Require each member, or at least each adult per plan §17.2, to hold an SSN or permanent-resident status. Existing person inputs can express this, for example ssn_card_type != NONE or immigration_status == LEGAL_PERMANENT_RESIDENT.
    • The any-qualified-member check can stay as the PRWORA screen. Write it, and any new per-member count, as add(spm_unit, period, [...]) > 0 rather than spm_unit.any(spm_unit.members(...)). The aggregation pattern reference lists the latter as the form to avoid.
    • In Case 1, set ssn_card_type: NONE for the parents and expect false and 0.
    • Cite §8(3) in the comment and the reference.
    • Update the note at programs.yaml:833, which says "mixed-immigration adjustments ... remain incomplete".
  • Microsim bias: upward. Eligibility and benefits are overstated for Kentucky SPM units with an adult who holds neither an SSN nor a permanent residency card. The size has not been quantified.

Should Address

S1. Countable income leaves out three existing variables that the plan counts

  • Code: params/unearned_income_sources.yaml:3-26 and the hard-coded earned sources at vars/ky_liheap_income.py:26-31.
  • Sources:
    • FY2026 plan §1.9, p5 checks "Self - Employment Income" and "Contract Income".
    • §1.9, p6 checks "Retirement / pension benefits".
    • §17.4, p34 lists "Pension statement", "Railroad Retirement records" and "Veterans Administration records" as income verification.
  • The three variables counted nowhere:
    • military_retirement_pay. PolicyEngine keeps it separate from pension_income, and KY CCAP's ccap/income/countable_income/sources.yaml lists it on its own for that reason.
    • railroad_benefits.
    • partnership_s_corp_income. This is business income outside self_employment_income, sstb_self_employment_income and farm_operations_income.
  • Microsim bias: upward. Countable income is understated, so eligibility and income-band points are overstated for military and railroad retirees and pass-through owners. The size has not been quantified.

S2. The Medicare treatment of SSA benefits is fixed in the formula, but the plans change it by year

  • Code: vars/ky_liheap_income.py:35-41 always subtracts medicare_part_b_premiums_reported from social_security.
  • Sources (checkboxes read from the page images):
  • Effect: for period 2025 the premium is deducted, which contradicts FY2025. Apart from this toggle, period 2025 is fully reproduced by the FY2025 workbook (see G1), so this is the only FY2025 rule the backfill gets wrong. FY2026 is unaffected.
  • Fix direction:
    • Move the treatment into a dated boolean parameter, dated on or before the 2025-10-01 values it gates, and read it with where. This holds whichever way the checkbox wording is read, because the FY2025 and FY2026 boxes differ either way.
    • Pair tests/ky_liheap_income.yaml:47 with a test for the other year.
  • Microsim bias (period 2025 only): upward. Countable income for SSA recipients with reported Part B premiums is understated by the premium ($185.00 a month standard in 2025, up to $2,220 a year), which overstates eligibility and benefits.

S3. Microsim default: heating_type is UNSPECIFIED, so the program pays $0

  • Code: vars/ky_liheap.py:22,38,56, params/payment/fuel_points.yaml:2-3 (UNSPECIFIED: 0) and vars/ky_liheap_eligible.py:14-16.
  • How the $0 arises:
    • heating_type defaults to UNSPECIFIED, and the review found nothing in the populace packages that sets it. receives_housing_assistance, by contrast, is populated.
    • With UNSPECIFIED, fuel_points is 0, so ky_liheap returns 0 through the fuel_points > 0 check at :56.
    • heating_expense is also 0, so only units with heat included in rent pass the responsibility test.
  • Household-level inconsistency:
    • The heating_type docstring calls UNSPECIFIED the right value "when heat is included in rent and the fuel is unknown". Such a Kentucky unit comes out ky_liheap_eligible = true with ky_liheap = 0.
    • The fact sheet states that "All eligible households will receive a benefit".
    • Plan §2.6, p9 sets a $15 minimum benefit.
  • Comparable programs: MA, IL and DC LIHEAP each resolve UNSPECIFIED through an adapter (ma_liheap_heating_type.py:30-34, il_liheap_base_payment.py:41-46).
  • Microsim bias: downward, by the full program amount. The modeled Kentucky total is $0 in any dataset that leaves heating_type at UNSPECIFIED.
  • Test hook: lock in whichever behavior is chosen with a test of a heat-in-rent unit whose fuel is UNSPECIFIED.

S4. The multi-page #page= anchors are invalid

  • Where:
    • params/unearned_income_sources.yaml:33 and vars/ky_liheap_income.py:12 both use KY_Plan_2026.pdf#page=5,6,7,34.
    • The PR body uses #page=5,6,7,8,9,33,34.
  • Problem: PDF open parameters take a single page, so viewers open one page at most.
  • Fix: use one reference per page.
    • The unearned list maps to p6 (the §1.9 boxes) and p34 (§17.4).
    • The income variable also needs p5 (§1.8 gross income, wages and self-employment).
    • p7 holds no checked income item.

S5. Four workbook citations name a worksheet that does not show the cited value

  • Where:

    • params/payment/fuel_points.yaml:31
    • params/payment/income_points/unsubsidized.yaml:26
    • params/payment/maximum_household_points.yaml:9
    • params/payment/coal_adjustment.yaml:22

    All four cite "rev benefit income range". That sheet shows only dollar amounts.

  • Where each value appears in the FY2026 workbook:

    • Fuel points 1/2/3/4/5/6: "Points Description" J2:J7.
    • Income points 4/3/2/1: "Points Description" H2:H5.
    • Household-size cap of 5: "Subsidy points" E28 (=5+6+4). Plan §2.6, p9 also corroborates it: its $250 maximum equals ceil(15 x 16.66), while a cap of 6 would allow $267.
    • Two-person coal exception: "Subsidy points" H8:H11 (=2+1+4 and following).
  • The values themselves all match (0 mismatches). Only the sheet named in the title needs to change.

S6. A contradicting regulation is listed as a supporting reference for the reversed subsidized income points

  • Where:
    • params/payment/income_points/subsidized.yaml:34-35 lists "921 KAR 4:116 Section 4(1)(c)" as a reference for values that contradict it.
    • The same file has the description at :1 and the caveat comment at :2-7. The formula is at vars/ky_liheap.py:33-37,52.
  • Sources:
    • KAR §4(1)(c), p3: "A household with the lowest income and highest heating season fuel cost shall receive highest benefits."
    • Plan §2.4, p8: "The highest benefit is provided to the households with the lowest income and highest energy burden based on family size and fuel type."
    • The published workbook reverses this for subsidized housing. The FY2025 workbook reverses it the same way: "subsidy benefits" E45 = ROUNDUP(E6*0.3,0), so the 1-person 0-75% subsidized cell takes 30% of the 1-person 126-150% unsubsidized cell. The matrix has therefore been reversed two years running.
    • Plan §2.6's $15-$250 range is the same under either ordering, so it cannot settle the question.
  • Stakes: up to $15 per subsidized household. For example, a 1-person household heating with electricity at 0-75% gets $41 under the PR and $56 under the regulation's ordering.
  • Fix direction:
    • Retitle the KAR reference to show that it conflicts, for example "... Section 4(1)(c) (lowest income receives highest benefit; conflicts with the workbook ordering)", or keep that citation only in the comment.
    • Add the FY2025 workbook as corroborating evidence of the reversal.
    • Keep the "agency confirmation needed" caveat.

S7. Test periods are written as 2_026 / 2_022

  • Where: 14 occurrences.

    • tests/integration.yaml:3,20,39
    • tests/ky_liheap.yaml:3,15,27,39,51,62
    • tests/ky_liheap_income.yaml:3,34,48
    • tests/ky_liheap_income_eligible.yaml:3,11

    The same files use plain 2026 elsewhere, for example integration.yaml:57 and ky_liheap.yaml:73. A repo-wide search finds period: 2_0XX only in these 4 files.

  • Why it matters: PyYAML (YAML 1.1) reads 2_026 as the integer 2026, so CI passes. YAML 1.2 core-schema tools read it as a string. The underscore separator is the convention for amounts (50_000), not periods.

  • Fix: use period: 2026 and period: 2022.

S8. Parameter descriptions use non-standard verbs or describe the model rather than the rule

The convention is [State] [limits|provides|sets|excludes|deducts|uses] ... under the [Full Program Name] program.

  • Descriptions that describe the model:
    • params/payment/coal_adjustment.yaml:1: "adjusts ... to reproduce its published ... matrix". Suggested: "Kentucky sets this coal heating point adjustment by household size under the Low Income Home Energy Assistance Program."
    • params/payment/income_points/subsidized.yaml:1: "uses these income points to reproduce its published subsidized-housing heating payments". Suggested: "Kentucky provides these income points to households in subsidized housing ...". The caveat can stay in the comment.
    • params/payment/subsidized_housing_rate.yaml:1: "reduces the published subsidized-housing heating payment ...". Suggested: "Kentucky provides this share of the point-based heating payment to households in subsidized housing ...".
    • params/maximum_table_size.yaml:1: "publishes monthly income limits through this household size". Suggested: "Kentucky sets income limits by household size up to this size ...".
    • params/unearned_income_sources.yaml:1: "counts these available person-level unearned income sources". Drop the modeling qualifiers.
  • Non-standard verbs:
    • params/earned_income_age.yaml:1 ("counts").
    • params/payment/fuel_points.yaml:1 and params/payment/income_points/unsubsidized.yaml:1 ("assigns").
    • params/payment/maximum_household_points.yaml:1 ("caps"; use "limits").
    • params/payment/dollars_per_point.yaml:1 ("pays"; use "provides").
  • Already compliant: income_limit.yaml, fpg_year_lag.yaml and payment/income_bands.yaml.

S9. Draft and process wording remains in the code, programs.yaml and the PR body

  • Code: vars/ky_liheap_income.py:44-45 says "Do not add new inputs for this draft." Keep the list of unmodeled items at :42-44 and drop the instruction.
  • Optional: vars/ky_liheap.py:50 says "These source inconsistencies need agency clarification." Restate it as a description of the source conflict rather than an open action item.
  • programs.yaml:833: drop the process phrase "no new inputs are added".
  • PR body: the PR is not a GitHub draft, but the body still says:
    • "Source discrepancies requiring review before this leaves draft"
    • "This draft uses that sheet ..."
    • "the draft reproduces the published cells"
    • "the draft's provisional assumption, not a verified Kentucky rule"
  • Also in the PR body:

S10. The Axiom parity line uses n/a for a new program

  • Current line: axiom: n/a: rulespec-us does not have this program yet; it should be added as a follow-up.
  • Requirement: per CONTRIBUTING.md#axiom-parity, a deferred follow-up is the <rulespec issue> queued case. That requires a dispatch-ready rulespec-us issue labelled pe-parity, containing:
    • the module path and corpus citation;
    • the verbatim law (921 KAR 4:116);
    • the required outputs;
    • companion tests from the same external sources as the YAML tests (the fact-sheet limits and workbook cells).
  • Fix: open the issue and change the line to axiom: <issue> queued.

S11. The coal adjustment's closing edge and its subsidized path are untested

  • Parameter: params/payment/coal_adjustment.yaml:2-14 applies -1 at size 2 and returns to 0 at size 3.
  • Current coverage: coal at sizes 1, 2 and 4-8, all unsubsidized (tests/ky_liheap.yaml:2, :14-25).
  • Missing edge cases (cells from the FY2026 workbook):
    • Size 3 coal, unsubsidized, 0-75%: 3 + 4 + 2 = 9 points, giving $150 ("rev benefit income range" H15). This is the threshold where the adjustment ends. If the threshold were mis-keyed as 4, the cell would pay $134 and no current test would fail.
    • Size 2 coal, subsidized, 0-75%: 2 + 1 + 2 - 1 = 4 points, giving 67 and then ceil(20.1) = $21 (H57). The adjustment also flows through the 30% path (vars/ky_liheap.py:39,52).
  • Fix: add both cells to the tests/ky_liheap.yaml:14 case or to a new case.

Suggestions

G1. FY2025 can be verified, not just backfilled

  • The match: all 384 heating cells on the FY2025 workbook's "subsidy benefits" sheet match the PR's formula.
  • Guideline year: the sheet uses the 2024 guideline (P9 = 1255 = 15,060/12), consistent with fpg_year_lag = 1 for period 2025.
  • Workbook typos: its only deviations are internal typos. T11 = 2827.5 should be 3227.5, S12 = 2850 should be 3250, and the size-6 rows point at the size-2 thresholds.
  • The $134 minimum: the FY2025 plan §2.6, p9 states a "Minimum Benefit $134", which contradicts its own workbook, so it is not evidence against the backfill.
  • Next step once S2 is fixed:
    • Consider dating the values 2024-10-01 with this reference and widening verified_years.
    • Add a verified FY2025 case next to the 2022 backfill case at tests/integration.yaml:38-55.

G2. FY2027 values are extrapolated

  • Timing: period 2027 is the current fiscal year. FY2027 began 10/1/2026, and its heating window runs 11/03-12/14/2026 (FY2027 plan, p4).
  • Benefit range: the FY2027 plan §2.6, p9 states "Minimum Benefit $21 Maximum Benefit $250", while the extrapolated FY2026 schedule's minimum is $15. The FY2027 matrix therefore differs.
  • New income source: FY2027 also checks "Payments from mortgage or Sales Contracts" (p6).
  • Matrix not yet published: no FY2027 matrix is posted (the liheapch 2027 URLs return 404). Noting this in the registry or docs, and updating once the matrix is published, would keep coverage claims accurate.

G3. Add the legal basis to the parameter and variable references

  • params/income_limit.yaml:8-10: the regulation's base is 130% (KAR §3(1), p2). The 150% rests on §3(1)(b), "The cabinet approves an increase to the poverty income guidelines due to funding availability", together with plan §2.1, p8. Add the §3(1)(b) reference next to the plan.
  • vars/ky_liheap.py:15: cites only the workbook. Add KAR §4(1), which :24 cites, and plan §§2.4-2.6, whose $15-$250 range :46-50 relies on.
  • vars/ky_liheap_eligible.py:10: add KAR §3(2) or the fact sheet for the heating-responsibility test (:14-16).
  • vars/ky_liheap_income_eligible.py:11: add plan §2.1 (#page=8) and KAR §3(1) for the "at or below" rule.

G4. Make the 30% ceiling robust to floating point

  • Current state: vars/ky_liheap.py:52 computes exact results in float64 for every reachable amount (1-259 were checked).
  • Risk: if the operands are promoted to float32, ceil(x x 0.3) returns 16 for 50, 31 for 100 and 61 for 200. All three are real cells, published as 15, 30 and 60.
  • Fix: a small rounding guard before np.ceil, or integer arithmetic, would make the formula independent of dtype.

G5. The fuel_points > 0 check conflates a point value with coverage

  • Where: vars/ky_liheap.py:56; params/payment/fuel_points.yaml:2-23.
  • Problem: an unsupported fuel is marked by giving it 0 points. A reform that set WOOD to 0 points would zero the whole benefit instead of removing one point.
  • Fix: a boolean breakdown parameter, or a comment stating that 0 is reserved, would separate the two meanings. This relates to S3.

G6. Earned-income sources are hard-coded, while unearned sources are a parameter

  • Where: vars/ky_liheap_income.py:26-31, compared with params/unearned_income_sources.yaml.
  • Suggestion: an earned_income_sources.yaml looped with max_(..., 0) would make the two consistent. It would also make adding partnership_s_corp_income (S1) a YAML-only change.

G7. income_bands is an inline list parameter

  • Where: params/payment/income_bands.yaml:3 ([0.75, 1, 1.25]). A repo search finds no other date: [n, ...] parameter.
  • Problem: reforms and the app cannot address individual list elements.
  • Fix: three scalar parameters, or a bracket whose thresholds are iterated, would make the band edges reformable.

G8. Labels and names

  • params/fpg_year_lag.yaml:11 reads "Kentucky LIHEAP fpg year lag". Spell out "federal poverty guideline".
  • Optional renames, for self-describing paths:
    • earned_income_age to earned_income_minimum_age
    • maximum_table_size to income_limit_table_maximum_size

G9. Test naming conventions

  • Unnumbered case names:
    • tests/integration.yaml:2,19,38
    • tests/ky_liheap.yaml:2,14,26,38,50,61
    • tests/ky_liheap_income.yaml:2,33,47
    • tests/ky_liheap_income_eligible.yaml:2,10
  • Numbering starts mid-sequence: Case 4 at integration.yaml:56, Case 7 at ky_liheap.yaml:72, Case 3 at ky_liheap_income_eligible.yaml:18.
  • Entity names: adult, child and unit in tests/ky_liheap_income.yaml depart from the person1 / spm_unit convention.
  • Formatting: there is a stray blank line at tests/ky_liheap_income.yaml:134.

G10. Unused hud_utility_allowance inputs

  • No Kentucky variable reads hud_utility_allowance.
  • Remove it at tests/integration.yaml:15,51 and tests/ky_liheap.yaml:11,23,35,47,57,68.
  • Keep tests/integration.yaml:28, which is a deliberate negative case.

G11. A test overrides a formula variable

  • Where: tests/integration.yaml:48 sets heating_expense: 1_000 directly.
  • Fix: use pre_subsidy_electricity_expense: 1_000, as Case 1 does, so the test follows the path production uses.

G12. Coverage additions (non-blocking)

  • (a) Subsidized households of size 6 or more. Workbook "rev benefit income range" E77:K90 hard-codes those rows, and the size cap is tested only on the unsubsidized path.
  • (b) A dedicated ky_liheap_fpg test file. The variable is asserted only through the integration outputs at tests/integration.yaml:54,88,127,190.

G13. programs.yaml ordering

  • Inconsistent order: programs.yaml:805 appends KY after TX on the coverage: line, while the state_implementations list inserts KY before TX (:826).
  • Sibling PRs: the sibling LIHEAP PRs already disagree on order. Agreeing on one rule, for example alphabetical by state in both places, would make each rebase deterministic.

PDF Audit Summary

Parameter Values Source (page / cell) Result
earned_income_age 1 (18) Plan p6: "Earned income of a child under the age of 18" unchecked Match
fpg_year_lag 1 (1) Workbook "rev" R11 = 15,650/12; fact sheet p1 1,957 Match
income_limit 1 (1.5) Plan p8, §2.1 "150.00%" Match (G3: add KAR §3(1)(b))
maximum_table_size 1 (8) Fact sheet p1, sizes 1-8 Match
payment/coal_adjustment 6 (thresholds 0/2/3; amounts 0/-1/0) Workbook "Subsidy points" H3, H8:H11, H13 Match (S5: sheet label)
payment/dollars_per_point 1 (16.66) Workbook "rev" L5:L28, N5 Match
payment/fuel_points 11 (7 mapped; 4 set to 0 by design) Workbook "Points Description" J2:J7 Match (S5: sheet label)
payment/income_bands 3 (0.75/1/1.25) Workbook "rev" Q10:S10, Q28:S28 Match
payment/income_points/subsidized 4 (1/2/3/4) Workbook "rev" E52:E55 formulas (reversed ordering) Matches the workbook; conflicts with KAR §4(1)(c) p3 (S6)
payment/income_points/unsubsidized 4 (4/3/2/1) Workbook "Points Description" H2:H5 Match (S5: sheet label)
payment/maximum_household_points 1 (5) Workbook "Subsidy points" E28; "rev" rows 30-43; plan p9 $250 maximum Match (S5: sheet label)
payment/subsidized_housing_rate 1 (0.3) Workbook "rev" E52 ROUNDUP(E8*0.3,0) Match
unearned_income_sources 18 Plan p6 §1.9 boxes; p34 §17.4 All 18 match; 3 omissions (S1)
  • Total: 53 parameter values audited. No mismatches survived verification.
  • Derived outputs:
    • 384 of 384 FY2026 dollar cells match (8 sizes x 4 bands x 6 fuels x 2 housing types).
    • 384 of 384 FY2025 cells match.
    • 8 of 8 fact-sheet income limits match. Each is ceil(FPG x 1.5 / 12) using the 2025 guideline: 1,957 / 2,644 / 3,332 / 4,019 / 4,707 / 5,394 / 6,082 / 6,769.
  • Checkbox reads: the FY2025, FY2026 and FY2027 p6 checkboxes were confirmed from the page images. The §17.2 boxes on p33 were confirmed on a 600-dpi render.
  • Source discrepancies checked, no finding:
    • Workbook guideline years: the stale "2024-2025" title on the rev sheet does not matter, because Q:T use the 2025 guideline and the 2024-based cells feed no dollar cell.
    • $687 vs $688: the fact sheet's "Add $688" controls (workbook T37 = 7,457 = 6,769 + 688). For sizes 10-14 the workbook is 1 to 3 dollars lower, which the code comments document.
    • Size points capped at 5, not 6: the matrix hard-codes the size-5 values for larger households, and the $250 maximum requires the cap at 5.
    • No asset test and no categorical test: plan §2.3 says there is no asset test and §1.4 answers "No", while the CHFS page's 130% limit and asset limits are stale.
    • Benefits not capped at the bill: KAR §4(1) requires payment "for the full benefit amount", which supports this.
    • Gross income and child earnings: gross income per plan §1.8, with child earnings under 18 excluded, matches all three plan years.
    • Heat in rent: the responsibility rule matches KAR §3(2)(c).
    • Subsidized status: receives_housing_assistance | is_in_public_housing matches KAR §4(1)(e).

Validation Summary

Check Result
Regulatory accuracy 1 critical (C1, mixed-status gate). Income-definition gaps in S1 and S2 (period 2025 only)
Parameter values vs sources 53 of 53 match
Benefit matrix reproduction FY2026: 384 of 384. FY2025: 384 of 384
Income limits 8 of 8 fact-sheet values; "at or below" test matches KAR §3(1)
Reference links 5 of 5 live (plan, workbook, fact sheet, KAR HTML, CAK announcement). The FY2025 workbook proposed in G1 is also live
Reference anchors and labels 2 valid single-page anchors (earned_income_age.yaml:10 p6, income_limit.yaml:10 p8). 2 invalid multi-page anchors in files, plus 1 in the PR body (S4). 4 wrong sheet labels (S5). 1 contradicting reference (S6)
Code patterns Only 0/1 numeric literals; fully vectorized; defined_for and entities correct; no duplicate variables (ky_liheap_fpg uses the shared fpg(..., year_lag=) helper). One aggregation-pattern note is folded into C1
Test coverage 24 cases; 7 hand recomputations match the workbook and fact sheet. Gaps: coal size 3 and subsidized coal (S11); subsidized size 6+ and a dedicated ky_liheap_fpg file (G12)
Test format 14 underscore-formatted periods (S7). Naming (G9). One formula-variable override (G11)
Microsimulation heating_type default gives a $0 KY total, biased downward by the full program amount (S3). C1 and S1 bias upward, size not quantified. S2 biases period 2025 upward by up to $2,220 a year of countable income per affected recipient
CI 37 of 37 pass
Changelog changelog.d/ky-liheap.added.md: valid type, top-level
programs.yaml Format and placement correct. Order inconsistency (G13). Conflicts with sibling PRs expected
Axiom parity n/a line does not meet CONTRIBUTING requirements (S10)
Branch Head unchanged; 6 ahead and 11 behind main; no overlap with main; clean local merge

Review Severity: REQUEST_CHANGES

1 critical and 11 should-address findings (13 suggestions).

Next Steps

  1. C1: require an SSN or permanent residency card for each member (at minimum each adult, per plan §17.2), using existing inputs. Then:
    • Rewrite tests/ky_liheap_eligible.yaml Case 1 to expect ineligible / $0.
    • Cite KAR §8(3).
    • Update the programs.yaml note.
  2. S1, S2: add military_retirement_pay, railroad_benefits and partnership_s_corp_income to countable income. Move the Medicare treatment of SSA benefits into a dated parameter, with tests on either side of the change.
  3. S3: decide how UNSPECIFIED heating_type is handled in microsimulation (the current default zeroes the program) and lock the behavior in with a test.
  4. S4-S6: split the #page= anchors, correct the four workbook sheet labels, and retitle the KAR §4(1)(c) reference. Add the FY2025 workbook as a reference.
  5. S7-S9: replace the 2_026 / 2_022 periods, align the parameter descriptions, and remove draft and process wording from the code, programs.yaml and the PR body.
  6. S10: open a dispatch-ready rulespec-us issue labelled pe-parity and change the Axiom line to queued.
  7. S11: add the size-3 coal and two-person subsidized coal cells.
  8. Suggestions: optional. G1 (verify FY2025 after S2) and G2 (note the FY2027 schedule change) affect the registry's year coverage.
  9. Rebase onto main once a sibling LIHEAP PR merges, then re-request review.

@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 (#9725 (comment)): 1 critical (921 KAR 4:116 §2(1)(d) and §8(3) deny households where a member lacks an SSN or permanent residency card, but the PR approves mixed-status households), 11 should-address items. All 53 parameter values and the 384 FY2026 benefit cells match the sources. Run /fix-pr 9725 or address the list.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
hua7450 and others added 2 commits October 2, 2026 12:35
Require a Social Security number or permanent residency card for every
household member (921 KAR 4:116 Sections 2(1)(d), 2(2) and 8(3)), keeping
the citizen or qualified member requirement. Count partnership and S
corporation income and railroad benefits, move both source lists into
parameters, and count Social Security gross with the Medicare premium
deduction left as a documented gap. Date the parameter values from FY2025
with FY2025 plan and workbook references and record verified years
2025-2026. Name the worksheets and cells that hold each value, mark the
regulation that conflicts with the subsidized ordering, use single-page
references, fix test periods and names, and add coal, documentation and
FY2025 cases.

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 e850fa8bf4, followed by a merge of main (head f3a71d37b8). Nothing was run locally; CI validates.

Critical

  • C1. Fixed as the regulation reads. Every member must hold a Social Security number or lawful permanent resident status (ssn_card_type != NONE or LPR), on top of the existing citizen-or-qualified-member requirement, now written as add(...) > 0. Case 1 expects ineligible. New cases cover a parent with a work-authorized SSN and a citizen child (eligible), a household with SSNs but no qualified member (ineligible), and permanent residents without an SSN card (eligible). The comment cites Sections 2(1)(d), 2(2) and 8(3) and notes the plan's adults-only checklist.

Should-address

  • S1. partnership_s_corp_income and railroad_benefits are now counted. military_retirement_pay is not: your Kansas review treats it as part of taxable pension income, the plan's line is "Retirement / pension benefits", and pension_income covers it. Indiana drops it for the same reason, so the six PRs are consistent.
  • S2. Social Security is counted gross in both years. That matches the FY2025 plan ("Including MediCare deduction"). For FY2026 ("Excluding MediCare deduction") the premium deduction is left as a documented gap: neither medicare_part_b_premium nor medicare_part_b_premiums_reported is wired in yet, and a YAML comment says so. No dated toggle.
  • S3. Callers must send heating_type. The default change is tracked in Default heating_type to electricity and remove the UNSPECIFIED-gated state heating adapters #9754; it affects API partners and is separate work. A new case pins heat in rent with an unspecified fuel as eligible with $0.
  • S4. Fixed: one page in each link, pages named in the title or in a comment above the URL; page 7 dropped.
  • S5. Titles now name the worksheet and cells (Points Description J2-J7 and H2-H5; Subsidy points E28 and H8-H11).
  • S6. The regulation reference is retitled as conflicting with the workbook ordering, and the FY2025 workbook is cited.
  • S7, S8, S9. Periods, descriptions and draft wording fixed, in the code and the PR body.
  • S10. Now axiom: TheAxiomFoundation/rulespec-us#1465 queued.
  • S11. Both coal cells added ($150 and $21).

Suggestions

  • G1: taken. Parameter values are dated 2024-10-01 with FY2025 plan and workbook references, verified_years is "2025-2026", and an FY2025 case is added. I re-ran the 384 FY2025 heating cells against the point formula independently: 0 mismatches.
  • G3, G8 (label), G9, G10, G11, G13: done. G6: the earned sources are now a list parameter.
  • G2, G4, G5, G7, G12 and the optional renames: not taken.

@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 #9725: Add Kentucky LIHEAP regular heating assistance (hua7450). Re-reviewed head: f3a71d37b8. It contains fix commit e850fa8bf4 ("Address Kentucky LIHEAP review"), registry commit 568a9f79e9 and a merge of main. Round 1 reviewed 88403030b1.

Scope at this head: 26 files: 14 parameters (earned_income_sources.yaml is new), 5 variables, 5 test files with 31 cases (round 1 had 24), a changelog fragment and programs.yaml. Every parameter is now dated 2024-10-01 and cites both the FY2025 and the FY2026 sources. verified_years is now "2025-2026".

Path abbreviations (same as round 1):

  • params/ = policyengine_us/parameters/gov/states/ky/dcbs/liheap/
  • vars/ = policyengine_us/variables/gov/states/ky/dcbs/liheap/
  • tests/ = policyengine_us/tests/policy/baseline/gov/states/ky/dcbs/liheap/

Line numbers refer to head f3a71d37b8.

Status of Round-1 Findings

ID Round-1 finding Verdict Evidence
C1 Mixed-status households approved, contrary to 921 KAR 4:116 §2(1)(d) and §8(3) FIXED vars/ky_liheap_eligible.py:39-48 requires every member to hold an SSN (ssn_card_type != NONE) or LPR status. The qualified-member screen is now add(...) > 0 (:27-29). §§2(1)(d), 2(2) and 8(3) are cited (:30-38, :11). Case 1 now expects false/0 (tests/ky_liheap_eligible.yaml:45-50). Cases 2-4 cover the SSN path, the no-qualified-member path and the residency-card path.
S1 Countable income omits military_retirement_pay, railroad_benefits, partnership_s_corp_income FIXED (2 of 3); ACCEPTED-AS-RATIONALE for military retirement pay partnership_s_corp_income is at params/earned_income_sources.yaml:9 and railroad_benefits at params/unearned_income_sources.yaml:18-19. Both are tested (tests/ky_liheap_income.yaml:156-179). The military retirement rationale holds up against repo practice (see the note below this table).
S2 Medicare treatment of SSA benefits fixed in the formula while the plans change it by year NOT FIXED Social Security is now counted gross in every year (params/unearned_income_sources.yaml:6-10). That fixes FY2025 but breaks FY2026 and FY2027 under the PR's own reading of the checkboxes. There is no dated toggle and no tracking issue. See S2 below.
S3 heating_type UNSPECIFIED default gives $0 in microsimulation ACCEPTED-AS-RATIONALE (tracked) #9754 is open. It proposes an ELECTRICITY default, lists the affected adapters and the partner-fixture impact, and states that the new state LIHEAP PRs expect $0 for UNSPECIFIED. The PR body's "Microsimulation" paragraph links it. tests/ky_liheap_eligible.yaml:167-189 pins heat-in-rent with an unspecified fuel as eligible with $0.
S4 Invalid multi-page #page= anchors FIXED No #page=n,m anchors remain in the PR files or the PR body. Each link has one page, and the other pages are named in titles or comments (params/unearned_income_sources.yaml:41-42, vars/ky_liheap_income.py:12-15).
S5 Four workbook citations name the wrong worksheet FIXED The new titles match the FY2026 workbook: "Points Description" J2:J7 = 1-6 and H2:H5 = 4/3/2/1, "Subsidy points" E28 =5+6+4 and H8 =2+1+4 with H9:H11 = 6/5/4.
S6 Contradicting KAR §4(1)(c) listed as support for the reversed subsidized points FIXED The reference is retitled "conflicts with the workbook ordering" (params/payment/income_points/subsidized.yaml:37-38). The FY2025 workbook is cited at :35-36; its E45:E48 are verified as ROUNDUP(E6*0.3) … ROUNDUP(E3*0.3). The caveat is kept (:2-8).
S7 2_026 / 2_022 periods FIXED No period: n_nnn remains in tests/.
S8 Non-standard parameter descriptions FIXED All 14 descriptions now use a standard verb and describe the rule.
S9 Draft and process wording FIXED No "draft", "need agency clarification", "Do not add new inputs" or "no new inputs" remains in the code, programs.yaml or the PR body. The Kansas precedent line and the #9731 history are gone from the body.
S10 Axiom line n/a for a new program NOT FIXED The line is now axiom: TheAxiomFoundation/rulespec-us#1465 queued, and #1465 carries the pe-parity label. The issue is not dispatch-ready, though. See S10 below.
S11 Coal size-3 edge and subsidized coal untested FIXED tests/ky_liheap.yaml:98-116 expects 150 and 21, which match FY2026 "rev benefit income range" H15 = 150 and H57 = 21 (ROUNDUP(H13*0.3,0), with H13 = 67).

S1 note on military_retirement_pay. The repo has two conventions:

  • Treated as part of pension income (the author's reading):
    • Federal gross income counts only taxable_pension_income (parameters/gov/irs/gross_income/sources.yaml).
    • Several state tax codes subtract military_retirement_pay from an AGI that already contains it: CT, IA, MI, ND and RI.
  • Treated as separate income: about 25 benefit-program source lists add it on its own. One is KY CCAP, administered by the same agency, whose policyengine_us/parameters/gov/states/ky/dcbs/ccap/income/countable_income/sources.yaml:17-19 reads "PolicyEngine records military retirement separately from pension_income".
  • Microsimulation: the variable has no formula, and the populace build does not populate it (in microcosm it appears only in a concept-coverage golden file). Omitting it therefore has no microsimulation effect.
  • Documentation: the PR records its choice in a YAML comment (params/unearned_income_sources.yaml:15-16) and in a test (tests/ky_liheap_income.yaml:163-166).

The choice is defensible. Suggestion R4 covers the cross-program difference.

Critical (Must Fix)

None. C1 is fixed as the regulation reads.

Should Address (remaining)

S2. Social Security is counted gross in FY2026 and FY2027, contrary to the plans the PR cites

  • Code:
    • params/unearned_income_sources.yaml:6-10 adds social_security to the plain source list, so vars/ky_liheap_income.py:33-35,42 counts the gross benefit in every period.
    • tests/ky_liheap_income.yaml:48-59 (Case 3, period 2026) asserts [12_000, 1_000]. Its own comment says the FY2026 rule is not modeled.
  • Sources (checkbox states from the page images; round 1 confirmed them visually):
  • What changed since round 1:
    • Round 1's head deducted the premium in every year. Under the reading the PR body adopts (FY2026 means "net of the premium"), only the backfilled FY2025 was wrong.
    • The fix reverses this. FY2025 is now right. Period 2026, a year the PR now labels verified, and period 2027, the current fiscal year, are wrong.
    • Read either way, the FY2025 and FY2026 boxes differ, so one constant treatment cannot match both years.
  • The deferral rationale does not hold: the YAML comment says neither premium variable "is wired in". Both exist and are usable:
    • medicare_part_b_premium (variables/gov/hhs/medicare/eligibility/part_b/medicare_part_b_premium.py) returns the out-of-pocket Part B premium net of Medicare Savings Program coverage. Washington's senior property tax exemption already subtracts it as an income deduction (parameters/gov/states/wa/dor/property_tax_exemption/senior_disabled/income/deductions/sources.yaml:7).
    • medicare_part_b_premiums_reported is the input that round 1's head subtracted. It is listed in microcosm's stored_inputs.py.
    • No issue tracks the gap, so the deferral does not count as tracked.
  • Effect (period 2026, FY2026 workbook cells):
    • Band change: a one-person household heating with natural gas, with $1,000 a month in Social Security and the $202.90 standard 2026 premium withheld, is counted at $1,000. That is the 75.01-100% band, which pays $117 (F6). Its net benefit of $797.10 falls in the 0-75% band, which pays $134 (F5).
    • Eligibility change: with $2,100 a month gross, the household is over the $1,957 limit and gets $0. Its net of $1,897.10 is eligible and pays $84 (F8).
  • Consistency: until this is fixed, verified_years: "2025-2026" (programs.yaml:832) and the documentation string "Verified for FY2025 and FY2026" (vars/ky_liheap.py:10-13) overstate FY2026 verification.
  • Fix direction:
    • Add a dated boolean parameter, for example params/social_security_net_of_medicare_premium.yaml, with 2024-10-01: false and 2025-10-01: true. Cite FY2025 p6 and FY2026 p6. Period 2025 then resolves to the 2024-10-01 value and period 2026 to the 2025-10-01 value.
    • In ky_liheap_income, compute Social Security as max_(social_security - where(p.social_security_net_of_medicare_premium, premium, 0), 0), with the premium taken from medicare_part_b_premium or medicare_part_b_premiums_reported. Remove social_security from the plain list.
    • Pair the tests:
      • Period 2026 with a $2,000 premium should expect [10_000, 0], round 1's original expectation.
      • Period 2025 with the same premium should expect [12_000, 1_000].
    • The alternative is to keep the gap, open a tracking issue, and narrow verified_years and the documentation to FY2025.
  • Microsim bias (periods 2026 onward): downward. Countable income is overstated by up to $2,434.80 a year (12 x $202.90) for each Social Security recipient whose Part B premium is withheld. That understates eligibility and income-band points.

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

  • Line: axiom: TheAxiomFoundation/rulespec-us#1465 queued, the last line of the PR body. The format is correct.
  • Requirement: repo CLAUDE.md "Axiom Parity" and CONTRIBUTING.md#axiom-parity (lines 51-57) state that a queued issue "must be dispatch-ready and labelled pe-parity. It needs the module path and corpus citation, the verbatim law, the required outputs, and companion tests from the same external source as your YAML tests." State modules go under us-<state>/.
  • What #1465 contains: the issue is open, labelled enhancement and pe-parity, and has no comments.
    • PR link: missing. The body says "The PR URL, exact scope, policy periods, and eventual merged commit still need to be linked; they have not been independently verified here."
    • Module path: only "under us-ky/ in the canonical layout"; no module is named.
    • Corpus citation: only the FY2026 state-plan manifest. 921 KAR 4:116, the FY2025 plan and both benefit workbooks are missing.
    • Verbatim law: none.
    • Required outputs: none. Defining them is itself a checklist item ("agree … inputs, and outputs").
    • Companion tests: none.
  • Fix: edit Recent problem with US itemized deduction calculations #1465 to add:
    • the PR Add Kentucky LIHEAP regular heating assistance #9725 link and the FY2025-FY2026 scope;
    • the target module path or paths under us-ky/;
    • corpus citations for 921 KAR 4:116, both plans and both workbooks;
    • the verbatim text of §§1(8), 1(10), 2(1)(d), 2(2), 3(1)-(2), 4(1) and 8(3);
    • the required outputs (income eligibility, overall eligibility, countable income, benefit amount);
    • companion tests built from the external values the YAML tests use: fact-sheet limits 1,957 / 2,644 / … / 6,769 and the $688 increment; workbook cells FY2026 H15 = 150, H57 = 21, E13 = 150, G10 = 184; FY2025 F4 = 117 and T27 = 1,883.

Suggestions

New in this round:

  • R1. FY2025 income-band cell citation. params/payment/income_bands.yaml:11 cites FY2025 "subsidy benefits" Q27-S27. Those cells hold the one-person dollar boundaries (942 / 1,255 / 1,569). The shares 0.75 / 1 / 1.25 are in Q26-S26, which matches the FY2026 citation of the share header (Q10-S10). Either cite Q26-S26 or note that Q27-S27 are the rounded boundaries.
  • R2. The registry note no longer records known gaps. programs.yaml:833 now reads only "FY2025-FY2026 regular heating only … Crisis and cooling assistance are out of scope." The model coverage page reads this field. Consider restating the main limitations briefly:
  • R3. A test overrides an adds variable. tests/ky_liheap_income.yaml:171 sets partnership_s_corp_income, which adds partnership_income and s_corp_income. Using the component inputs follows the production path, as G11 did for heating_expense.
  • R4. Military retirement pay differs across Kentucky programs. A household that enters military retirement only through military_retirement_pay is counted by KY CCAP but not by KY LIHEAP. The current comment at params/unearned_income_sources.yaml:15-16 could say that the model expects military retirement in taxable_public_pension_income, so web-app users know which input to use.
  • R5. Authority for the qualified-member gate. vars/ky_liheap_eligible.py:24-29 gives a rationale ("LIHEAP is a federal public benefit") without a citation. 8 U.S.C. 1611 and HHS's PRWORA interpretation of "federal public benefit" (63 FR 41658, Aug. 4, 1998), which lists LIHEAP, would ground it.

Carried over from round 1, not taken and optional:

  • G2. FY2027 (period 2027, the current fiscal year) is extrapolated from FY2026. The FY2027 plan states a $21 minimum against the extrapolated $15 and adds "Payments from mortgage or Sales Contracts".
  • G4. vars/ky_liheap.py:61 (np.ceil(amount * 0.3)) is exact in float64 but would misround under float32 (50 → 16, 100 → 31, 200 → 61).
  • G7. params/payment/income_bands.yaml:3 is an inline list, so reforms cannot address individual band edges.
  • G12. No test covers subsidized households of size 6 or more, and there is no dedicated ky_liheap_fpg test file.
  • G8. Optional renames: earned_income_minimum_age and income_limit_table_maximum_size.

Suggestions the Author Reports as Done (verified)

ID Verified at head
G1 All values are dated 2024-10-01 and carry FY2025 references, and verified_years is "2025-2026". Every new FY2025 citation matches the FY2025 workbook: "Subsidy points" H8:H11 (7/6/5/4, H8 =2+1+4) and E28 =5+6+4; "points" J2:J7 = 1-6 and H2:H5 = 4/3/2/1; "subsidy benefits" L3 = 16.66, P9 = 1,255 (2024 guideline) and E45:E48 reversed. The new FY2025 case (tests/integration.yaml:208) expects $117 = "subsidy benefits" F4 (one person, 76-100%, natural gas) and a $1,883 limit = T27. The 2025 gross Social Security treatment is correct for FY2025.
G3 References added: KAR §3(1)(b) (params/income_limit.yaml:13-14); plan §§2.4-2.6 and KAR §4(1) (vars/ky_liheap.py:15-22); KAR §3(2) (vars/ky_liheap_eligible.py:19-20); plan §2.1 p8 and KAR §3(1) (vars/ky_liheap_income_eligible.py:11-17).
G5 Listed as not taken, but the comment at vars/ky_liheap.py:62-63 now states that zero points also mark "no schedule". That is the comment option G5 offered.
G6 params/earned_income_sources.yaml is looped with max_(…, 0) (vars/ky_liheap_income.py:29-32).
G8 (label) "Kentucky LIHEAP federal poverty guideline year lag" (params/fpg_year_lag.yaml:11).
G9 All 31 cases are numbered "Case n, …" in sequence. tests/ky_liheap_income.yaml uses person1/person2/spm_unit. The stray blank line is removed.
G10 hud_utility_allowance remains only in the deliberate negative case (tests/integration.yaml:31).
G11 tests/integration.yaml Case 3 uses pre_subsidy_electricity_expense: 1_000.
G13 The coverage: line (programs.yaml:805) and state_implementations both place KY before TX.

Regression Pass over the Delta

  • Income sources by year:
    • The FY2025 and FY2026 §1.9 checkboxes are identical apart from two items:
      • Medicare: S2.
      • Legal settlements: unchecked in FY2025, checked in FY2026. The PR does not model settlements, so this has no effect.
    • Every source in the 2024-10-01 lists is therefore checked in both plans.
    • Partnership/S-corp income does not overlap Schedule C income: it adds partnership_income and s_corp_income, and passive_partnership_s_corp_income is documented as a subset. railroad_benefits does not overlap social_security.
  • Changed expectations checked against the sources:
    • All recomputed by hand: tests/ky_liheap_eligible.yaml Case 2 = $150 (FY2026 E13) and Case 4 = $184 (G10); tests/ky_liheap.yaml Case 8 = 150 / 21 (H15 / H57); tests/integration.yaml Case 7 = $117 (FY2025 F4).
    • Income figures checked: tests/ky_liheap_income.yaml Case 8 (24,000 / 35,800 / 20,800).
    • The only changed expectation that departs from a source is Case 3 in tests/ky_liheap_income.yaml (S2).
  • C1 implementation:
    • ssn_card_type and immigration_status both default to CITIZEN, so existing callers are unaffected.
    • The populace build imputes ssn_card_type (microcosm us_runtime/immigration.py), so the per-member rule also applies in microsimulation.
    • The .members call is justified, because two per-person inputs are combined before spm_unit.all.
  • Code patterns:
    • The only literals are 0/1.
    • The formulas are vectorized.
    • The defined_for declarations and entities are unchanged and correct.
    • The adds-versus-formula usage is correct.
  • No other regressions found.

CI Status

  • Totals: 34 of 37 checks pass, including Lint, the changelog check, Household API Partners, Microsimulation, Quick Feedback, codecov patch/project and states-shard-1.
  • PR tests: all five Kentucky LIHEAP files run in Baseline states-shard-1, which passed. That covers 31 of 31 cases (7 + 8 + 5 + 8 + 3).
  • The 3 failures are runner kills, unrelated to the PR's files. Each job log ends in Process completed with exit code 143. Before that, the logs show repeated "Still running Batch N … since last output" lines. There are no FAILED or AssertionError lines and no failed pytest summary.
Job Last tests before the kill Classification
Baseline (states-shard-2), job 110932110753 DC tax tests, Batch 1 stalled about 15 min Runner kill (exit 143); no PR files in the shard
Baseline (states-shard-3), job 110932110769 OK tax tests, Batch 3 stalled about 7 min; earlier batches 1,505 and 1,901 passed Runner kill (exit 143); no PR files in the shard
Baseline (ssa-usda), job 110932110849 USDA WIC tests; make: *** [...] Terminated Runner kill (exit 143); no PR files in the shard

Review Severity: REQUEST_CHANGES

0 critical, 2 should-address (S2, S10) and 10 suggestions.

  • Fixed: C1 and eight of the eleven round-1 should items (S4-S9, S11, and S1 for two of its three sources).
  • Accepted: S3 is accepted as a tracked deferral (Default heating_type to electricity and remove the UNSPECIFIED-gated state heating adapters #9754), and S1's military retirement point is accepted as rationale.
  • S2 remaining: a dated Medicare toggle with paired tests, or a tracked deferral with verified_years narrowed to FY2025.
  • S10 remaining: rulespec-us#1465 needs the PR link, a module path, corpus citations, the verbatim law, the required outputs and companion tests.

@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 (#9725 (comment)): the critical mixed-status gate is fixed and 9 of 11 should items are resolved. Remaining: (S2) Social Security is now counted gross in every year, but the FY2026 and FY2027 plans exclude the Medicare deduction (medicare_part_b_premium exists and is already used elsewhere); (S10) rulespec-us#1465 is labelled pe-parity but not yet dispatch-ready. The 3 CI failures are runner kills in shards without this PR's files.

hua7450 and others added 2 commits October 2, 2026 22:08
…nd count military retirement pay

The FY2026 plan checks "Excluding MediCare deduction" where the FY2025 plan checks "Including", so a dated parameter switches Social Security from gross to net of the Part B premium the person pays, taken from the existing medicare_part_b_premium variable. military_retirement_pay is a separate input from the pension inputs that pension_income adds, so it is listed beside pension_income.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…into ky-liheap

# Conflicts:
#	policyengine_us/programs.yaml
@hua7450

hua7450 commented Oct 3, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for round 2. Head is now 7bd0a99968: fix commit c753fb4a81 plus a merge of main (which brings in #9758).

S2, Social Security and the Medicare premium. Fixed with your first option. A new dated parameter, social_security_medicare_premium_deduction (2024-10-01: false, 2025-10-01: true, citing FY2025 plan page 6 "Including MediCare deduction" and FY2026 plan page 6 "Excluding MediCare deduction"), switches the treatment. social_security left the unearned list; the formula now counts max_(social_security - medicare_part_b_premium, 0) when the parameter is true, and gross otherwise. medicare_part_b_premium is zero for a person who is not enrolled and excludes the share a Medicare Savings Program pays, which is not withheld from the check. Paired tests: ky_liheap_income.yaml Case 3 (2026: a $12,000 retiree whose premium an MSP covers keeps $12,000; a $23,600 retiree is counted at $21,165.20; a $2,000 benefit with a larger premium is floored at $0) and Case 9 (2025: $22,700 counted gross); integration.yaml Case 8 (2026: the $23,600 retiree is eligible for $84 only because of the deduction) and Case 9 (2025: $22,700 is over the $1,883 limit). verified_years stays "2025-2026".

S10, rulespec-us#1465. Filled in on 2026-10-02 with the PR link, module paths under us-ky/regulations/921-kar/4/116/ and us-ky/policies/, corpus citations (only the FY2026 plan is in a manifest; 921 KAR 4:116, both workbooks, the FY2025 plan and the fact sheet are flagged for ingest), verbatim text of sections 1(5), 1(8), 1(10), 1(12), 1(15), 2(1), 2(2), 3(1), 3(2), 4(1), 5(1)(a), 8(1) and 8(3), the workbook amounts, required outputs, fourteen companion cases with workbook cell references, and the interpretations. Updated today for the S2 change and for military retirement pay.

S1 note and R4, military retirement pay. You are right that pension_income does not include military_retirement_pay; my earlier claim was wrong. military_retirement_pay is now listed beside pension_income, with a comment that the two are separate inputs, and ky_liheap_income.yaml Case 8 enters the military pension there.

R1, R2, R3, R5 and the carry-overs (G2, G4, G7, G12, G8). 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 #9725: Add Kentucky LIHEAP regular heating assistance (hua7450). This round reviews head 7bd0a99968, which adds fix commit c753fb4a81 ("Deduct the Medicare Part B premium from Social Security from FY2026 and count military retirement pay") and a merge of main (merge base 7bc185b0cc, which includes #9663 and #9758). Round 2 reviewed f3a71d37b8.

Path abbreviations (same as earlier rounds):

  • params/ = policyengine_us/parameters/gov/states/ky/dcbs/liheap/
  • vars/ = policyengine_us/variables/gov/states/ky/dcbs/liheap/
  • tests/ = policyengine_us/tests/policy/baseline/gov/states/ky/dcbs/liheap/

Scope at this head: 27 files: 15 parameters (social_security_medicare_premium_deduction.yaml is new), 5 variables, 5 test files with 34 cases (9 / 8 / 5 / 9 / 3; round 2 had 31), a changelog fragment and programs.yaml. Line numbers below refer to head 7bd0a99968.

Status of Round-2 Findings

ID Round-2 finding Verdict Evidence
S2 Social Security counted gross in FY2026 and FY2027, contrary to the plans FIXED
  • New params/social_security_medicare_premium_deduction.yaml: 2024-10-01: false, 2025-10-01: true, citing FY2025 plan p6 and FY2026 plan p6.
  • social_security is removed from params/unearned_income_sources.yaml (comment at lines 6-8). vars/ky_liheap_income.py:42-45 counts max_(social_security, 0), less medicare_part_b_premium (floored at zero) when the parameter is true.
  • Paired tests: tests/ky_liheap_income.yaml Case 3 (2026) and Case 9 (2025); tests/integration.yaml Case 8 (2026, $84) and Case 9 (2025, $0).
  • The verified_years: "2025-2026" entry and the "Verified for FY2025 and FY2026" documentation (vars/ky_liheap.py:11) are now accurate.
S10 rulespec-us#1465 not dispatch-ready FIXED Details under "Axiom issue" below. The issue now has the PR link, module paths, corpus citations, verbatim law, required outputs and 14 companion tests. Minor refresh items are in Suggestion 3.
S1 note / R4 Military retirement pay omitted (accepted as rationale in round 2) FIXED (now counted) params/unearned_income_sources.yaml:13-17 lists military_retirement_pay beside pension_income, with a comment that they are separate inputs. tests/ky_liheap_income.yaml Case 8 now enters the $18,000 military pension through military_retirement_pay. Source support is under "Military retirement pay" below.
S3 heating_type UNSPECIFIED gives $0 in microsimulation ACCEPTED-AS-RATIONALE (tracked) Unchanged. Tracked in #9754 and stated in the PR body.
R1, R2, R3, R5; G2, G4, G7, G8, G12 Optional suggestions Not taken (optional) Carried over under Suggestions.

Verification of the Medicare Fix

Years. The parameter is read as parameters(period), so period 2025 resolves at 2025-01-01 to the 2024-10-01 value (false) and period 2026 resolves at 2026-01-01 to the 2025-10-01 value (true). This matches the plan checkboxes (FY2025 and FY2026 read from the page images; FY2027 from the checkbox extraction):

Plan §1.9, p6, Social Security sub-box Model
FY2025 "Including MediCare deduction" checked gross (period 2025)
FY2026 "Excluding MediCare deduction" checked net of Part B (period 2026)
FY2027 preliminary "Excluding MediCare deduction" checked net (the true value carries forward)

The reading "Excluding" = net is corroborated by Indiana: its FFY2025 and FFY2026 plans check the same "Excluding MediCare deduction" box, and its PY2026 manual (Section 6.1, p55) states that "the net amount of the Social Security check, after the deduction for Medicare Part B premiums" is counted.

Variable. medicare_part_b_premium (Person, YEAR, defined_for = "medicare_enrolled") returns max_(gross_medicare_part_b_premium - msp_part_b_premium_coverage, 0).

  • The gross premium is the standard premium plus the Part B IRMAA. Both are withheld from the check.
  • The MSP coverage is the standard premium for each month in which the enrollee meets the MSP income test (countable income under 135% of the guideline after the $20 exclusion) and the MSP asset test. A premium paid by a Medicare Savings Program is not withheld, so excluding it matches the "net check" reading.
  • Non-enrollees (under 65 without 24 months of SSDI, or not taking up Medicare) get $0.
  • The Kentucky plan names only a "MediCare deduction", so Part D is not at issue here.

Code. The Python if (vars/ky_liheap_income.py:43) tests a scalar, non-indexed boolean parameter. This is vectorization-safe, and it keeps the Medicare chain out of FY2025 and earlier periods. The floor at zero applies to Social Security alone, so a premium larger than the benefit does not reduce other income. That matches a check-level deduction.

Hand checks (2026 standard premium $202.90 × 12 = $2,434.80; 2025 premium $185 × 12 = $2,220; MSP limit 1.35 × $15,960 / 12 = $1,795.50 a month in 2026 and 1.35 × $15,650 / 12 = $1,760.63 in 2025):

Test Inputs Hand calculation Expected Result
tests/ky_liheap_income.yaml Case 3, household 1 (2026) Age 70, Social Security $12,000 MSP countable (12,000 − 240) / 12 = $980 < $1,795.50, so the MSP pays the premium: $0 deducted premium 0; income 12,000 Match
Case 3, household 2 (2026) Age 70, $23,600 $1,946.67 > $1,795.50, so the premium is paid: 23,600 − 2,434.80 2,434.8; 21,165.2 Match
Case 3, household 3 (2026) Age 70, $2,000 + $24,000 private pension $2,146.67 > limit; premium exceeds the benefit: max(2,000 − 2,434.80, 0) + 24,000 2,434.8; 24,000 Match
tests/ky_liheap_income.yaml Case 9 (2025) Age 70, $22,700 $1,871.67 > $1,760.63, so $2,220 is paid but FY2025 does not deduct it 2,220; 22,700 Match
tests/integration.yaml Case 8 (2026) Age 70, $23,600, natural gas Net $21,165.20 = $1,763.77 a month ≤ $1,957 (ceil(15,650 × 1.5 / 12)): eligible. Above $1,631 (ceil(15,650 × 1.25 / 12)): band 126-150%, 1 point. 1 + 1 + 3 = 5; ceil(5 × 16.66) = 84 = FY2026 workbook F8. Gross $1,966.67 would exceed $1,957 $84 Match; the case discriminates
tests/integration.yaml Case 9 (2025) Age 70, $22,700, natural gas $1,891.67 > $1,883 (ceil(15,060 × 1.5 / 12)): ineligible. Net $1,706.67 would qualify $0 Match; the case discriminates
tests/ky_liheap_income.yaml Case 8, household 1 Military retirement $18,000 + railroad $6,000 24,000 24,000 Match

Existing tests touched by the rule. Only one other period-2026 case has a Medicare-age Social Security recipient: tests/integration.yaml Case 6, where person3 (age 70) has $13,200. MSP countable income is $1,080 a month, under the limit, so the premium is $0 and the expected $83,920 still holds. If the standard premium were deducted, income would be $81,485.20 ($6,790.43 a month); the band and the $184 payment would be the same. See Suggestion 2. Integration Case 7 (period 2025) is unaffected.

Microsimulation note (informational). msp_part_b_premium_coverage assumes every income- and asset-eligible enrollee is covered by an MSP. In microsimulation, therefore, the deduction reaches only enrollees above the MSP limits. This is a property of the shared variable, not of this PR.

Military Retirement Pay

  • Sources: §1.9 p6 checks "Retirement / pension benefits" in the FY2025, FY2026 and FY2027 plans (FY2025 and FY2026 read from the page images; FY2027 from the checkbox extraction). 921 KAR 4:116 Section 1(8) counts all earned and unearned income. Counting military retirement is supported in every modeled year.
  • No double counting:
    • pension_income adds only tax_exempt_pension_income and taxable_pension_income.
    • retirement_distributions adds only the taxable and tax-exempt retirement distributions.
    • veterans_benefits is a separate input.
    • None of them contains military_retirement_pay.
  • Consistency: the treatment now matches KY CCAP, which is administered by the same agency.

Axiom Issue (rulespec-us#1465)

Compared against the structure of #1467:

Requirement Status at 2026-10-03 update
pe-parity label Present (with enhancement). The issue is open.
PR link #9725, head 7bd0a99968, scope (regular heating only) and policy periods (FY2025, FY2026).
Module paths us-ky/regulations/921-kar/4/116/section-{1,2,3,4,8}; us-ky/policies/acf/liheap-plan/fy2026/section-{1,2,17} and fy2025/section-1, -2; us-ky/policies/dcbs/liheap-benefit-matrix/fy2026/heating and fy2025/heating; the fact sheet and announcement; the us/ poverty guidelines; us/statutes/8/1611.
Corpus citations The FY2026 plan is in manifest us-ky-liheap-state-plan-fy2026.yaml. 921 KAR 4:116, the FY2025 plan, both workbooks and the fact sheet are flagged "needs ingest", with official URLs and pages.
Verbatim law 921 KAR 4:116 §§1(5), 1(8), 1(10), 1(12), 1(15), 2(1), 2(2), 3(1), 3(2), 4(1), 5(1)(a), 8(1) and 8(3). It also has the plan §1.8-1.9 checkbox states for both years, including the Medicare sub-boxes, §17.4, and the workbook cells.
Required outputs All five variables. The ky_liheap_income definition reflects the FY2025 gross and FY2026 net Social Security rule and lists military retirement pay.
Companion tests 14 cases with hand derivations from the fact sheet, plans, regulation and named workbook cells. The issue states that none is taken from policyengine-us output.

The issue is dispatch-ready. Three small refresh items are in Suggestion 3.

Main-Merge Check (#9672, #9671, #9727)

Programs.yaml Merge Resolution

Diffed against the merge base 7bc185b0cc:

  • The only changes are the coverage: line (..., IL, KS, KY, TX) and the new KY block after KS.
  • The Kansas entry from Add Kansas LIEAP regular heating assistance #9663 is byte-identical to main.
  • The file parses with yaml.safe_load: 103 programs, no duplicate ids. LIHEAP state_implementations order is OR, DC, MA, IL, KS, KY, TX.

Regression Pass over the Delta

  • Comments match the code:
    • params/unearned_income_sources.yaml:6-8 and :13-15.
    • vars/ky_liheap_income.py:36-41, which states the year split and the MSP treatment.
  • Parameter file format: description, unit: bool, period: year, label, and two references with section and page.
  • Tests:
    • New expectations use absolute_error_margin: 0.01 where cents occur.
    • Case names follow the "Case n" sequence.
    • New inputs set the social_security_retirement component rather than overriding the social_security aggregate.
  • Code patterns: no hardcoded policy values were added, and the entities and defined_for are unchanged.
  • PR body: one stale sentence remains (Suggestion 1).
  • No regressions found.

Critical (Must Fix)

None.

Should Address (remaining)

None. S2 and S10 are resolved.

Suggestions

New in this round:

  1. Stale PR-body sentence. "Other documented gaps" still says "The FY2026 Medicare premium deduction from Social Security is not modeled." That contradicts the income bullet above it and the code. Removing the sentence aligns the body (and any squash message) with the head.
  2. Integration Case 6 depends on MSP coverage without saying so (tests/integration.yaml:145-207).
    • The expected ky_liheap_income: 83_920 holds in FY2026 only because person3's $1,080 of monthly countable income is under the MSP limit, so the premium is $0.
    • A comment line would make the dependency visible, for example "the MSP pays person3's Part B premium, so nothing is deducted". So would asserting medicare_part_b_premium: 0 for person3.
    • The same note belongs in companion case 6 of rulespec-us#1465. An encoder that deducts the standard premium without modeling MSP would get $81,485.20 there, with the same $184 payment.
  3. rulespec-us#1465 refresh:
    • "Tests: 31 YAML cases" should read 34.
    • The companion table has no FY2026 Medicare row. integration.yaml Case 8 is described only under "Interpretations and gaps". A row would make the dated switch part of the oracle set: $23,600 Social Security, CMS 2026 premium $202.90 a month, income $21,165.20, payment $84 = workbook F8, and $0 if counted gross.

Carried over from round 2, optional:

  1. R1. params/payment/income_bands.yaml cites FY2025 "subsidy benefits" Q27-S27, which hold the dollar boundaries. The shares are in Q26-S26.
  2. R2. The registry note (programs.yaml) lists no known gaps. Candidates are the $0 microsimulation total pending Default heating_type to electricity and remove the UNSPECIFIED-gated state heating adapters #9754 and the unmodeled lump sums, settlements and jury duty.
  3. R3. tests/ky_liheap_income.yaml Case 8 sets partnership_s_corp_income, an adds variable. The component inputs would follow the production path.
  4. R5. vars/ky_liheap_eligible.py gives the qualified-member rationale without a citation. Candidates are 8 U.S.C. 1611 and 63 FR 41658.
  5. G2. FY2027 (period 2027) is extrapolated from FY2026. The FY2027 plan states a $21 minimum and adds "Payments from mortgage or Sales Contracts".
  6. G4. np.ceil(amount * 0.3) is exact in float64 but would misround in float32.
  7. G7. params/payment/income_bands.yaml is an inline list, so reforms cannot address individual band edges.
  8. G8. Optional renames: earned_income_minimum_age and income_limit_table_maximum_size.
  9. G12. No test covers subsidized households of size 6 or more, and there is no dedicated ky_liheap_fpg test file.

CI and Branch Status

  • Checks: gh pr checks 9725 shows 37 of 37 checks passing on head 7bd0a99968 (run 37088735374, pull_request). They include:
    • Lint and the changelog check;
    • Baseline states shards 1-4, ssa-usda and contrib-hhs;
    • Microsimulation and Household API Partners;
    • Quick Feedback and codecov patch/project.
  • No failures: the runner kills seen in round 2 did not recur.
  • Merge state: CONFLICTING / DIRTY, with review decision CHANGES_REQUESTED from round 2.

Review Severity: APPROVE

0 critical, 0 should-address and 12 suggestions (3 new, 9 carried over).

  • S2 is fixed: a dated parameter, sourced to the FY2025 and FY2026 plan checkboxes, switches Social Security from gross to net of medicare_part_b_premium. Paired tests in both years match hand calculations and workbook cells.
  • S10 is fixed: rulespec-us#1465 is dispatch-ready.
  • Military retirement pay is now counted, as the "Retirement / pension benefits" box in all three plans supports.
  • Before merge: the programs.yaml conflict with main needs to be resolved, and the stale PR-body sentence (Suggestion 1) is a one-line edit.

@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 (#9725 (comment)): the Medicare Part B deduction and military retirement pay now match the state sources, the axiom issue is dispatch-ready, and the programs.yaml merge is correct; 0 critical, 0 should. The income lists are self-contained, so main's #9672 (snap_rental_income) does not cause double counting. #9727's merge reopened a programs.yaml conflict with main to resolve before merging.

…into ky-liheap

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@hua7450
hua7450 merged commit 18cc357 into PolicyEngine:main Oct 5, 2026
37 checks passed
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