Repository navigation
Conversation
…nputs Without Form 2555 leaf inputs, foreign_earned_income_exclusion uprated from the latest earlier period in holder.get_known_periods(), which includes years the simulation had calculated. A 2026 value computed after 2025 was uprated from the float32-rounded 2025 result instead of the 2024 input, so its bits depended on calculation order (2024 input 10,000: 11090.326171875 fresh, 11090.3271484375 after 2025). Mirror Core's uprating (policyengine-core#563, released in 3.32.17): take the latest earlier same-unit period from Holder.get_input_periods for this branch, breaking ties with Core's _latest_input_key. The legacy default, carry-over and held-flat index behaviour are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dependent
From 2027, medicaid_enrolled_for_ssi_state_supplement returned
simulation.get_array("medicaid_enrolled") whenever any value was stored,
calculated or supplied, and otherwise calculated enrollment in a private
branch with the SNAP/TANF community-engagement pass-through forced False.
A calculated medicaid_enrolled keeps the pass-through, so the result
depended on whether medicaid_enrolled had been calculated first. With SNAP
or the pass-through supplied (so the SNAP -> SSP cycle does not compute this
variable on the way), a pass-through adult got False fresh and True after
medicaid_enrolled. Under a reform that removes Indiana's SSI-recipient
Medicaid coverage, a working-age reported SSI recipient in a medical
facility falls into the adult group, and Indiana SAPN changed from $0 fresh
to $22 after medicaid_enrolled.
Read medicaid_enrolled directly only when it is a supplied input this branch
reads (Holder.get_input_periods); otherwise always use the private branch.
The recursion guard is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
foreign_earned_income_exclusion: a Hypothesis property over entered year, target year, amounts and the years calculated first asserts the target is bit-for-bit the same as asked alone and equals the entered amount uprated straight from its year; the 2024 -> 2026-after-2025 repro is an explicit @example. Example tests pin the latest-entered-year source, the held-flat index before 2015, and the default when only an unreadable branch holds an amount (previously a TypeError). medicaid_enrolled_for_ssi_state_supplement: a Hypothesis property over state, which value on the SNAP-SSP-Medicaid cycle is supplied, and the variables calculated first asserts the SSP enrollment, eligibility and benefit match a fresh simulation; the SNAP-supplied, medicaid_enrolled-first repro is an explicit @example. Example tests cover SNAP and TANF pass-through adults and an SSI recipient placed in the adult group, whose SAPN was $22 or $0 depending on order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
marked this pull request as ready for review
October 14, 2026 11:30
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two formulas read a stored value as if it had been entered. A stored value can also be one the simulation calculated, so their results depended on what had been calculated first. A code-reading sweep on
a2f63c1235reported both. Each was reproduced before any change, and only what reproduced is fixed.Invariant
Order independence. For a simulation built from fixed inputs, the value of each changed variable for a period is the same whatever was calculated before it, and in whatever order. For
foreign_earned_income_exclusionwithout Form 2555 leaf inputs, two more things hold:1.
foreign_earned_income_exclusion(no-leaf-input path)Reproduced: float32 drift. The formula uprated from the latest earlier period in
holder.get_known_periods(), which includes years the simulation calculated. So 2026 computed after 2025 was uprated from 2025's float32 result, not from the 2024 input.main(a2f63c1235)The drift persists into later years: 2035 asked after 2025–2034 differed by up to 0.125 on a $1,000,003.30 input. Of six amounts, five differed bitwise for 2026.
Also reproduced: a crash with the same cause. An amount stored only under a branch the simulation cannot read (
holder.set_input(period, value, "sibling")) was picked as the source, read back asNone, and raisedTypeError: unsupported operand type(s) for *: 'NoneType' and 'float'. It now takes the legacy default.Did not reproduce: anything beyond rounding from Form 2555 leaf inputs. These cases matched bitwise before and after:
has_section_911_leaf_inputscounts every leaf input at or before the period, across the whole simulation. So the no-leaf path never runs for a year after a leaf year, and a leaf-path result is never an uprating source. A batch that mixes leaf and legacy coverage across tax units is rejected when it is built (validate_section_911_batch_inputs).Fix. The formula now uprates as Core does since policyengine-core#563 (released in 3.32.17; pe-us requires ≥ 3.32.27). It uprates from the latest earlier same-unit period in
Holder.get_input_periods(branch_name), the periods whose stored value is an input, and breaks ties with Core's_latest_input_key. These behaviours are unchanged:None, so Core's carry-over or default applies;2.
medicaid_enrolled_for_ssi_state_supplementFrom 2027, the formula returned the stored
medicaid_enrolledwhenever one existed, whether supplied or calculated. Otherwise it calculatedmedicaid_enrolledin a private branch, withmedicaid_community_engagement_pass_through_eligibleforced False, to avoid the SNAP → SSP → Medicaid recursion. A calculatedmedicaid_enrolledkeeps that pass-through.What reproduced. Each case below compares the value asked alone with the value after
medicaid_enrolled(and aftersnap), using IN SAPN/RCAP and KS SSPP candidates and pass-through non-candidates.Baseline, SNAP calculated by formula: no order dependence. Calculating
medicaid_enrolledreaches this variable through pass-through → SNAP →in_ssp, beforemedicaid_enrolledis stored, so every order uses the branch.Baseline, SNAP or the pass-through supplied as an input:
medicaid_enrolled.At baseline, SSI recipients in Indiana (section 1634) and Kansas (SSI criteria) are in the
SSI_RECIPIENTcategory, which the community engagement requirement does not reach. RCAP's aged, blind or disabled candidates are exempt by age (SSI's aged threshold is 65; the requirement covers ages 19–64) or by blindness or disability.A candidate differs under a reform. With Indiana's SSI-recipient Medicaid coverage turned off (
gov.hhs.medicaid.eligibility.categories.ssi_recipient.is_covered.IN: false), a 40-year-old institutionalized SSI recipient with no disability finding falls into theADULTgroup. With the pass-through supplied, that person's SAPN depended on order:in_ssp, 2027-01medicaid_enrolledmainThe non-candidate case is the same shape without the reform: an Indiana adult, 40, with $5,000 of earnings and SNAP workfare, and
snapsupplied.medicaid_enrolled_for_ssi_state_supplementgaveFalseasked alone andTrueaftermedicaid_enrolledonmain, andFalsein both orders here.Fix.
medicaid_enrolledis read directly only when it is a supplied input for the period (period in holder.get_input_periods(branch_name)); otherwise the private branch is always used. The recursion guard is unchanged. Every value asked alone is unchanged: in each case the fix only removes the path that read a calculated value. Across these variants, all orders now agree:medicaid_enrolledsupplied;Supplied enrollment is still honoured, including a
set_inputafter construction (test_ssp_preserves_medicaid_inputs_set_after_initialization).Tests
Hypothesis is used for both sites, with
derandomize=True. Each property has its reproducing case as an explicit@hypothesis.example.tests/test_foreign_earned_income_exclusion_leaves.pyTypeError.tests/core/test_ssi_state_supplement_medicaid_dependency.pysnap, pass-through,medicaid_enrolled), and up to four variables calculated first. It asserts the SSP enrollment, eligibility and benefit match a fresh simulation.is_ssi_recipient_for_medicaid: False, which has the same effect as the reform above, without building a reformed system (about 40 s).On
main(the base formulas with the new tests), 6 of the 13 new tests fail: both properties, the branchTypeError, both pass-through examples aftermedicaid_enrolled, and the SSI-recipient example aftermedicaid_enrolled. The order examples pass onmainwhen nothing is calculated first. The latest-input and held-flat examples pass onmain; they pin behaviour the fix must keep.Local runs on this head, one file per process:
medicaid_snap_cycle,in_ssp_sapn_eligible,in_ssp_rcap_eligible,integration,edge_cases), KS SSPP (medicaid_snap_cycle,integration,ks_sspp),snap_unearned_income_state_ssi_supplements,foreign_earned_income_exclusion/integration,section_911_excluded_income, MAforeign_earned_income,refundable_ctc_barred_by_section_911_exclusion,agi_plus_section_911_931_933_exclusions;test_ctc_foreign_earned_income_exclusion,test_ctc_foreign_earned_income_exclusion_property,test_section_911_batch_inputs,test_ma_foreign_earned_income_addback,test_modified_agi_add_backs,test_section_911_tax_stacking,test_form_4952_section_911_interaction,core/test_ald_determinism,contrib/congress/afa/test_afa_section_911,test_niit_section_911_magi,test_capital_gain_rate_group_netting,test_section_1h_regular_tax.Test cost
These numbers are local macOS runs on a shared host under load 60–165, so wall time is noisy and user CPU is the steadier measure.
main, for the grouptest_foreign_earned_income_exclusion_leaves.py(remaining)core/test_ssi_state_supplement_medicaid_dependency.py(heavy-a)a2f63c1235)The added cases build small situations only. The Medicaid file's peak comes from the existing all-states poverty test. No job, matrix entry or group assignment changes.
Impact
Microsimulation impact: pending. The US + core hub runs it.
What to expect, not yet measured:
foreign_earned_income_exclusionis entered for a year before the one simulated and intermediate years were calculated first.snapandmedicaid_enrolledonly as calibration target measures, not as supplied inputs. In the household runs with SNAP calculated by formula, every order already took the branch path.Overlapping PRs. #10170 and #10164 edit this variable's
documentationstring, in a hunk separate from this change. #10161 and #10176 add Colorado enrollment helpers that always branch and never read a storedmedicaid_enrolled.axiom: n/a: calculation-order fix, no change to any rule's value
🤖 Generated with Claude Code