Skip to content

Cast bool-bracket .calc() results to bool before bitwise-NOT#8919

Merged
MaxGhenis merged 1 commit into
PolicyEngine:mainfrom
MaxGhenis:codex/bool-bracket-casts
Jul 6, 2026
Merged

Cast bool-bracket .calc() results to bool before bitwise-NOT#8919
MaxGhenis merged 1 commit into
PolicyEngine:mainfrom
MaxGhenis:codex/bool-bracket-casts

Conversation

@MaxGhenis

Copy link
Copy Markdown
Contributor

Summary

Addresses #8780.

single_amount bracket parameters with amount_unit: bool return a raw int64 array (0/1) from .calc(), not a genuine boolean array (a core-library quirk). Applying ~ (bitwise NOT) to that result performs a bitwise negation — ~1 = -2, ~0 = -1, both truthy in Python/NumPy — instead of a logical NOT. This produces the correct answer only by accident, when the surrounding expression happens to mask the high bits back down via & with a genuine bool term. That accidental correctness broke for real in is_snap_ineligible_student (#8763) once a masking & term was removed — this PR is the broader defensive audit that issue asked for.

This PR addresses the two live instances found; the framework-level root-cause fix (policyengine-core's bracket .calc() returning int64 for bool brackets in the first place) belongs upstream and stays out of scope here, so #8780 stays open per its own framing.

Primary fix

policyengine_us/variables/gov/hhs/medicaid/eligibility/medicaid_community_engagement_pass_through_eligible.py

snap_age_exempt = snap_work.general.age_threshold.exempted.calc(age)
...
snap_pass_through = (
    snap & ~snap_age_exempt & ~snap_non_age_exempt & snap_work_compliant
)

~snap_age_exempt bitwise-negates the raw int64 bracket output. Today's output is correct only because the leading snap & term masks the high bits back down to the genuine logical NOT for real 0/1 inputs — "correct by accident," exactly like the prior is_snap_ineligible_student incident. Fixed by casting to bool at the .calc() call site:

snap_age_exempt = snap_work.general.age_threshold.exempted.calc(age).astype(bool)

Second live instance found during the audit

policyengine_us/variables/gov/aca/ptc/aca_required_contribution_percentage.py

income_eligible = parameters(period).gov.aca.ptc_income_eligibility.calc(magi_frac)
above_fpl_limit = magi_frac > thresholds[-1]
capped_out = above_fpl_limit & ~income_eligible

Same pattern: ~income_eligible bitwise-negates a raw bracket .calc() result, masked into correctness only by the leading above_fpl_limit & term. Fixed identically with .astype(bool).

Full audit of all 27 amount_unit: bool bracket parameters

I enumerated every parameter YAML with amount_unit: bool (grep -rl "amount_unit: bool" policyengine_us/parameters, 27 files) and, for each, traced every .calc() consumer in policyengine_us/variables and policyengine_us/reforms, checking whether the result (or anything derived from it) is ever combined with ~.

# Parameter path Finding
1 gov.aca.ptc_income_eligibility Live bug, fixed (aca_required_contribution_percentage.py, see above). Other two consumers (is_aca_ptc_eligible.py, basic_health_program.py) only combine the result via |/where() — benign.
2 gov.contrib.aca.ptc_700_fpl_cliff.income_eligibility Benign — reforms/aca/aca_ptc_700_fpl_cliff.py combines via &, never negates.
3 gov.contrib.aca.ptc_additional_bracket.income_eligibility Benign — reforms/aca/aca_ptc_additional_bracket.py combines via &, never negates.
4 gov.contrib.aca.ptc_simplified_bracket.income_eligibility Benign — reforms/aca/aca_ptc_simplified_bracket.py combines via &, never negates.
5 gov.contrib.reconciliation.medicaid_work_requirement.age_range No consumers found anywhere in the repo (orphaned reform scaffold).
6 gov.hhs.medicaid.eligibility.work_requirements.age_range Benign — medicaid_work_requirement_eligible.py uses the .calc() result only inside where(work_required_age, ...). A downstream ~ in is_medicaid_ineligible_due_to_work_requirement.py negates the fully-resolved medicaid_work_requirement_eligible Variable (via person(...), which is always properly typed bool), not the raw bracket output — different, safe pattern.
7 gov.irs.ald.alimony_expense.divorce_year_threshold Benign — alimony_expense_ald.py combines via multiplication, never negates.
8 gov.local.ca.la.general_relief.housing_subsidy.age_eligibility No consumers found (orphaned parameter; the actual LA GR age-eligibility check reads a different, scalar parameter).
9 gov.states.ar.dhs.medicaid.work_requirements.age_range Benign — ar_medicaid_work_requirement_subject.py combines via &, never negates. A downstream ~subject in ar_medicaid_work_requirement_eligible.py negates the resolved Variable via person(...), same safe pattern as #6.
10 gov.states.ca.chhs.eligible_regardless_of_immigration_status Benign — is_ca_medicaid_immigration_status_eligible.py combines via |, never negates.
11 gov.states.co.hcpf.chp.child Benign — co_chp_eligible.py combines via | (is_age_eligible = is_pregnant | is_child), never negates.
12 gov.states.de.tax.income.subtractions.exclusions.pension.age_threshold Benign — de_pension_exclusion.py uses the result only inside where(...) (twice), never negates.
13 gov.states.il.hfs.hbwd.eligibility.age Benign — il_hbwd_age_eligible.py returns the .calc() result directly, no negation.
14 gov.states.il.isbe.pfae.eligibility.age_range Benign — il_pfae_age_eligible_child.py returns the .calc() result directly, no negation.
15 gov.states.il.rta.cta.reduced_fare_program.age_threshold.child Benign — il_cta_children_reduced_fare_eligible.py returns the .calc() result directly, no negation.
16 gov.states.ma.dot.mbta.income_eligible_reduced_fares.age_threshold Benign — ma_mbta_income_eligible_reduced_fare_eligible.py returns the .calc() result directly, no negation.
17 gov.states.mi.tax.income.deductions.retirement.expanded.birth_year Benign — mi_expanded_retirement_benefits_deduction_eligible.py returns the .calc() result directly (or False), no negation.
18 gov.states.mi.tax.income.deductions.retirement.tier_three.age_eligibility Benign — mi_retirement_benefits_deduction_tier_three_eligible.py returns the .calc() result directly, no negation.
19 gov.states.mi.tax.income.deductions.standard.tier_three.birth_year Benign — mi_standard_deduction_tier_three_eligible.py returns the .calc() result directly, no negation.
20 gov.states.mi.tax.income.deductions.standard.tier_two.birth_year Benign — mi_standard_deduction_tier_two_eligible.py returns the .calc() result directly, no negation.
21 gov.states.nc.ncdhhs.scca.age.group Benign — nc_scca_age_group.py feeds the result into where()/array indexing, never negates. (Unrelated observation: the YAML's amount_unit: bool tag doesn't match its integer 1/2/3 bracket amounts — a pre-existing metadata mismatch, out of scope here.)
22 gov.states.tx.dart.reduced_fare.age_threshold.child Benign — tx_dart_reduced_fare_age_eligible.py combines via |, never negates.
23 gov.states.ut.tax.income.credits.ctc.child_age_threshold Benign — ut_ctc_potential.py combines via &, never negates.
24 gov.states.wa.dshs.sfa.student_pathway.age_eligible Benign — wa_sfa_student_pathway_eligible.py combines via &, never negates.
25 gov.usda.snap.student.age_threshold Already fixed (pre-existing) — this is the exact parameter fixed in #8763 / commit fa1f721d46, cited in #8780 as the precedent for this audit.
26 gov.usda.snap.work_requirements.abawd.age_threshold.exempted Benign — meets_snap_abawd_work_requirements.py combines via |, never negates.
27 gov.usda.snap.work_requirements.general.age_threshold.exempted Primary fix, this PR (medicaid_community_engagement_pass_through_eligible.py, see above). Its other consumer, meets_snap_general_work_requirements.py, combines via | and is unaffected.

Also confirmed benign per the issue's own framing: gov/states/co/hcpf/chp/co_chp.py's ~is_pregnant * p.copays[element].calc(income_level) — the ~ applies to is_pregnant (a genuine bool Variable), not to the bracket .calc() result, which is combined via multiplication rather than negation. No change needed.

Two broad sanity-check greps across the entire policyengine_us/variables and policyengine_us/reforms trees (same-line ~....calc( and cross-line file-level co-occurrence of .calc( and ~) turned up no additional live instances beyond the two fixed here.

Tests

  • New/modified test file: policyengine_us/tests/policy/baseline/gov/hhs/medicaid/eligibility/medicaid_community_engagement_pass_through_eligible.yaml — added 4 boundary-age regression cases (ages 15, 16, 59, 60) locking the gov.usda.snap.work_requirements.general.age_threshold.exempted bracket boundaries (exempt under 16 or 60+, non-exempt 16–59) that the primary fix touches.
  • No new test needed for the second fix — the existing policyengine_us/tests/policy/baseline/gov/aca/ptc/aca_required_contribution_percentage.yaml suite already locks the 400% FPL boundary (e.g. "2026 ACA contribution percentage is zero above 400 percent FPL" at magi_fraction: 4.01) that exercises the exact capped_out branch this fix touches.

Pass/fail counts (via uv run policyengine-core test <path> -c policyengine_us):

  • medicaid_community_engagement_pass_through_eligible.yaml: 9 passed (5 pre-existing + 4 new)
  • policyengine_us/tests/policy/baseline/gov/hhs/medicaid/ (full tree): 368 passed
  • policyengine_us/tests/policy/baseline/gov/usda/snap/eligibility/ (full tree): 188 passed
  • policyengine_us/tests/policy/baseline/gov/aca/ (full tree): 270 passed
  • policyengine_us/tests/policy/contrib/aca/ (all 3 reform variants): 38 passed
  • policyengine_us/tests/policy/baseline/gov/hhs/basic_health_program/: 31 passed

All green, no regressions — both fixes are behavior-preserving (the buggy code was masked into numeric correctness today; these casts remove the fragility without changing any current output).

ruff format and ruff check pass clean on both modified .py files.

Changelog fragment: changelog.d/bool-bracket-negation-casts.fixed.md.

single_amount bracket parameters with amount_unit: bool return raw int64
(0/1) from .calc(), not a real boolean array. Applying ~ to that result
performs a bitwise negation (~1 = -2, ~0 = -1, both truthy) instead of a
logical NOT, which only produces the correct answer when the surrounding
expression happens to mask the high bits back down via & with a genuine
bool term. That's "correct by accident," and it broke for real in
is_snap_ineligible_student (PolicyEngine#8763) once the masking AND was removed.

Audited every one of the ~27 amount_unit: bool bracket parameters in the
repo for live ~ usage on a raw .calc() result and fixed the two found:

- medicaid_community_engagement_pass_through_eligible.py: snap_age_exempt
  from snap_work.general.age_threshold.exempted.calc(age), negated twice
  in the snap_pass_through AND-chain. Currently correct only because the
  leading `snap &` term masks it.
- aca_required_contribution_percentage.py: income_eligible from
  gov.aca.ptc_income_eligibility.calc(magi_frac), negated in
  capped_out = above_fpl_limit & ~income_eligible. Currently correct only
  because `above_fpl_limit &` masks it.

Every other consumer of the 27 bool-bracket parameters combines the
.calc() result via |, &, where(), comparison, or multiplication rather
than negating it directly, so no other live bug exists. Two parameters
(gov.contrib.reconciliation.medicaid_work_requirement.age_range and
gov.local.ca.la.general_relief.housing_subsidy.age_eligibility) have no
consumers at all.

Added boundary-age regression tests (15/16/59/60) to
medicaid_community_engagement_pass_through_eligible.yaml locking the
general SNAP work-requirement age exemption thresholds this formula
depends on. The existing aca_required_contribution_percentage.yaml
suite already locks the 400% FPL boundary the second fix touches.

Addresses PolicyEngine#8780

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 50.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 64.15%. Comparing base (f9e58e7) to head (d23c644).
⚠️ Report is 127 commits behind head on main.

Files with missing lines Patch % Lines
...ov/aca/ptc/aca_required_contribution_percentage.py 0.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##              main    #8919       +/-   ##
============================================
- Coverage   100.00%   64.15%   -35.85%     
============================================
  Files            3        2        -1     
  Lines           55       53        -2     
============================================
- Hits            55       34       -21     
- Misses           0       19       +19     
Flag Coverage Δ
unittests 64.15% <50.00%> (-35.85%) ⬇️

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.

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Merged over the codecov pair: the reported numbers are self-refuting for this diff — two one-line .astype(bool) casts cannot produce a −35.85% project drop, indicating a partial coverage upload corrupted both contexts. The full test suite is green, and the Medicaid (368) and ACA (270) trees exercising both cast lines pass locally and in CI. Fable review: primary fix plus the second live instance found by the 27-parameter audit (aca_required_contribution_percentage ~income_eligible), boundary tests at the 0/16/60 age brackets, repo otherwise verified clean of the pattern.

@MaxGhenis MaxGhenis merged commit 9c7aa77 into PolicyEngine:main Jul 6, 2026
33 of 35 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.

1 participant