Repository navigation
Give separate filers the $3,750 Schedule R initial amount - #10578
Conversation
section_22_income's select checked one qualified individual before the separate-return condition, so a married individual filing separately got $5,000 instead of the $3,750 that 22(c)(2)(A)(iii) sets. Check the separate return first. Also allow the credit on a separate return only when the spouses lived apart all year (22(e)(1)), using the existing cohabitating_spouses input. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dit limit A Hypothesis test runs batches of mixed tax units and checks that a separate filer's section 22 amount never exceeds $3,750 and that the credit never exceeds the Schedule R credit limit or tax before credits. Restoring main's select order makes the first property fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Ready for hub review Head: What the item asked, and how I resolved it
Tests (one process per file)
Mutants (not committed; code restored after each)
CI: not run, because the PR is a draft. Judgment calls
Proposed follow-ups
Generated by Claude Code |
|
Picking this up for the US hub. I'll post a hand-off comment here when it's ready. Generated by Claude Code |
Michigan's subtraction reads section_22_income, the Schedule R line 19 amount. A separate filer who lived with the spouse can check neither box 8 nor box 9 and has no line 19 amount, so the bar belongs on the amount itself, not on the credit. The potential credit is again rate x section_22_income; federal results are unchanged. Also: explain why the separate condition tests n >= 1 (dependents can qualify), point the phase-out references at 22(d), fix the Schedule R line in the YAML header, add YAML cases for a qualifying dependent, a cohabiting joint return and a Michigan separate filer, and replace the credit-limit property, which could not fail, with a differential test against main's formula and the statute's closed form. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6GnTUicNF8teuGcift
|
Ready for hub review Head: What the item asked for, and where
Values (TY2024, wages only, age 67, separate, itemizing, $9,000 wages)
The reviewer's numbers for Nebraska and Vermont match. Two more separate-filer changes against main, checked at this head: separate filer plus a dependent aged 80, 5,000 → 3,750; separate filer aged 60, not retired on disability, $2,000 of disability payments, 2,000 → 0. Readers of
|
|
Ready for hub review Review of #10578, round 2The head is still
Federal results match the previous head. Run 41471141121 at this head finished with all 34 jobs successful. I found no P1 or P2 problems. Below are four P3s (two documentation overclaims, one test gap and one stray branch) and three follow-ups that behave the same on main. Sources. I couldn't confirm any legal source from this VM. law.cornell.edu, uscode.house.gov, govinfo.gov, irs.gov, legislature.mi.gov and michigan.gov all returned a proxy 403 or DNS failure, through both curl and the fetch tool. So every statement below about what 26 USC 22, Schedule R or MCL 206.30 says is a paraphrase I haven't verified. The hub should check those texts before landing (see "What I couldn't check"). FindingsP3-1. The test and its docstring call the separate-filer reference "the statute's closed form", but it reuses the model's count of qualified individuals, dependents included
Docstring line of P3-2. The body's "2,000 → 0" row is stated more broadly than the code delivers, and no YAML case pins it
P3-3. A second branch carries the head commit
P3-4. A stale line comment in Michigan's subtraction list (not in the diff)
The brief's questionsThe placement and the Michigan case.
Readers at head. I grepped
Every one of these changes for some separate filers compared with main. None changes compared with the previous head except Michigan's subtraction. Reader values I reran (TY2024, separate, 67, itemizing, $9,000 wages):
These match the hand-off. Counting qualified individuals. Separate filers count a dependent toward the initial amount at head, and so do single, head of household and joint filers. Witnesses, all identical at main and head:
The 2,000 → 0 row.
Tests.
Mutants. Each mutant ran in its own process on a scratch worktree, which I restored and confirmed clean (
All five are killed. M4 and M5 are killed only by the fixed The Michigan interest and dividend follow-up.
Docs and body.
Can baseline outputs move? Yes.
Scope and overlap.
CI.
Checked and soundThe defect at main. A separate filer aged 67 with $3,000 of wages gets 5,000 at main and 3,750 at head. Living together gives 5,000 at main and 0 at head. The rule by hand, before I read the code: 3,750 initial; the cap is 1 × 5,000 for an elderly non-dependent; minus nontaxable pensions and social security; minus half of AGI over 5,000; 0 if they lived together. The head code applies this in every year (the parameters run from 2010), for separate returns only, at TaxUnit level. Other statuses are unchanged, as the main-reference run above shows. Test files, one per process at head:
Citations. What I couldn't check
Follow-ups (same on main)
Generated by Claude Code |
Only the Python test's fixed batch killed the mutant that drops the n >= 1 guard, so a YAML case now does too. The separate-filer reference also counts qualifying members as the model does, dependents included, so it can't be "the statute's closed form" on that point; the docstrings now say so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Hub impact pair, progress The base side of the impact pair for this PR has finished; the head side runs next.
Generated by Claude Code |
|
Ready for hub review
The sums use the simulation weights, and the up and down columns add the weights of the rows that rose or fell. I rounded each figure to the dollar on its own, so Change can differ by $1 from Head sum minus Base sum. Wall time and memory:
Which variables move. Two variables move:
Why they differ. The diff changes one formula,
For every other filing status the head formula returns the base value, and for a separate filer the head amount is never above the base amount. No other formula or parameter value changes, so every other output can move only through By the dataset's inputs, all 25 moved units fall under the first case.
Dropped variables. None. All 14 exist on both sides on the same entity (12 on Commands. I ran these from an empty work directory. curl -sI https://huggingface.co/api/datasets/policyengine/populace-us | head -1
git clone https://github.com/PolicyEngine/policyengine-us.git pe && cd pe
git fetch origin pull/10578/head:pr-10578 pull/10561/head:pr-10561
HEAD_SHA=$(git rev-parse pr-10578); BASE_SHA=$(git merge-base origin/main $HEAD_SHA); echo $HEAD_SHA $BASE_SHA
git worktree add --detach ../head $HEAD_SHA
git worktree add --detach ../base $BASE_SHA
git show pr-10561:policyengine_us/tools/calculate_chunked.py > ../calculate_chunked.py
git diff $BASE_SHA $HEAD_SHA -- pyproject.toml uv.lock # empty, so one environment serves both sides
cd ../head && pip install uv 2>/dev/null; uv sync # CPython 3.14.6, policyengine-core 3.33.2
apt-get install -y -q time || (apt-get update -q && apt-get install -y -q time) # the VM had no /usr/bin/time
# Download the dataset once, through the model's own resolver.
PYTHONPATH=$PWD /usr/bin/time -v .venv/bin/python -c "from policyengine_us.system import _resolve_dataset_path, DEFAULT_DATASET; p = _resolve_dataset_path(DEFAULT_DATASET); print(p)" > ../download.log 2> ../download.err
sha256sum <the printed file> # before the runs, and again between the two sides
cd ../pe; mkdir -p ../out
H=$PWD/../calculate_chunked.py; PY=$PWD/../head/.venv/bin/python
export OMP_NUM_THREADS=4 OPENBLAS_NUM_THREADS=4 MKL_NUM_THREADS=4
PYTHONPATH=$PWD/../base /usr/bin/time -v $PY ../pair_side.py $H 8 2026 ../out/base > ../out/base.log 2> ../out/base.err
PYTHONPATH=$PWD/../head /usr/bin/time -v $PY ../pair_side.py $H 8 2026 ../out/head > ../out/head.log 2> ../out/head.err
$PY ../compare.py ../out/base ../out/head ../out/compare.jsonI ran the two sides one at a time. A small wrapper script ran each side's lines above and recorded the side's PID, start and end times and exit status. I detached the wrapper with Other checks, outside the pair itself:
compare.json{
"household_net_income": {
"entity": "household",
"base_sum": 17161528033453.125,
"head_sum": 17161528033453.125,
"change": 0.0,
"records_changed": 0,
"weighted_changed": 0.0,
"weighted_up": 0.0,
"weighted_down": 0.0,
"largest_abs": 0.0,
"rows": []
},
"household_benefits": {
"entity": "household",
"base_sum": 2059893797415.6846,
"head_sum": 2059893797415.6846,
"change": 0.0,
"records_changed": 0,
"weighted_changed": 0.0,
"weighted_up": 0.0,
"weighted_down": 0.0,
"largest_abs": 0.0,
"rows": []
},
"section_22_income": {
"entity": "tax_unit",
"base_sum": 14646812532.989056,
"head_sum": 14568116751.890999,
"change": -78695781.0980568,
"records_changed": 25,
"weighted_changed": 62956.63145736675,
"weighted_up": 0.0,
"weighted_down": 62956.63145736675,
"largest_abs": 1250.0,
"rows": [
{
"id": 1221609,
"base": 5000.0,
"head": 3750.0
},
{
"id": 1218439,
"base": 5000.0,
"head": 3750.0
},
{
"id": 1215840,
"base": 5000.0,
"head": 3750.0
},
{
"id": 1212417,
"base": 3298.20703125,
"head": 2048.20703125
},
{
"id": 1168291,
"base": 3328.9921875,
"head": 2078.9921875
}
]
},
"elderly_disabled_credit_potential": {
"entity": "tax_unit",
"base_sum": 2197021900.5709934,
"head_sum": 2185217533.2551312,
"change": -11804367.315862179,
"records_changed": 25,
"weighted_changed": 62956.63145736675,
"weighted_up": 0.0,
"weighted_down": 62956.63145736675,
"largest_abs": 187.50003051757812,
"rows": [
{
"id": 1158929,
"base": 643.23095703125,
"head": 455.7309265136719
},
{
"id": 1037137,
"base": 405.4091491699219,
"head": 217.9091339111328
},
{
"id": 1215840,
"base": 750.0,
"head": 562.5
},
{
"id": 1221609,
"base": 750.0,
"head": 562.5
},
{
"id": 1212417,
"base": 494.7310791015625,
"head": 307.2310791015625
}
]
},
"elderly_disabled_credit": {
"entity": "tax_unit",
"base_sum": 0.0,
"head_sum": 0.0,
"change": 0.0,
"records_changed": 0,
"weighted_changed": 0.0,
"weighted_up": 0.0,
"weighted_down": 0.0,
"largest_abs": 0.0,
"rows": []
},
"mi_subtractions": {
"entity": "tax_unit",
"base_sum": 104227298654.38034,
"head_sum": 104227298654.38034,
"change": 0.0,
"records_changed": 0,
"weighted_changed": 0.0,
"weighted_up": 0.0,
"weighted_down": 0.0,
"largest_abs": 0.0,
"rows": []
},
"mi_income_tax": {
"entity": "tax_unit",
"base_sum": 13368314227.229733,
"head_sum": 13368314227.229733,
"change": 0.0,
"records_changed": 0,
"weighted_changed": 0.0,
"weighted_up": 0.0,
"weighted_down": 0.0,
"largest_abs": 0.0,
"rows": []
},
"mi_interest_dividends_capital_gains_deduction": {
"entity": "tax_unit",
"base_sum": 29083842.520729866,
"head_sum": 29083842.520729866,
"change": 0.0,
"records_changed": 0,
"weighted_changed": 0.0,
"weighted_up": 0.0,
"weighted_down": 0.0,
"largest_abs": 0.0,
"rows": []
},
"ne_elderly_disabled_credit": {
"entity": "tax_unit",
"base_sum": 0.0,
"head_sum": 0.0,
"change": 0.0,
"records_changed": 0,
"weighted_changed": 0.0,
"weighted_up": 0.0,
"weighted_down": 0.0,
"largest_abs": 0.0,
"rows": []
},
"ne_income_tax": {
"entity": "tax_unit",
"base_sum": 2976276185.1876755,
"head_sum": 2976276185.1876755,
"change": 0.0,
"records_changed": 0,
"weighted_changed": 0.0,
"weighted_up": 0.0,
"weighted_down": 0.0,
"largest_abs": 0.0,
"rows": []
},
"vt_elderly_or_disabled_credit": {
"entity": "tax_unit",
"base_sum": 0.0,
"head_sum": 0.0,
"change": 0.0,
"records_changed": 0,
"weighted_changed": 0.0,
"weighted_up": 0.0,
"weighted_down": 0.0,
"largest_abs": 0.0,
"rows": []
},
"vt_income_tax": {
"entity": "tax_unit",
"base_sum": 1450474522.7552946,
"head_sum": 1450474522.7552946,
"change": 0.0,
"records_changed": 0,
"weighted_changed": 0.0,
"weighted_up": 0.0,
"weighted_down": 0.0,
"largest_abs": 0.0,
"rows": []
},
"income_tax": {
"entity": "tax_unit",
"base_sum": 2595105852713.508,
"head_sum": 2595105852713.508,
"change": 0.0,
"records_changed": 0,
"weighted_changed": 0.0,
"weighted_up": 0.0,
"weighted_down": 0.0,
"largest_abs": 0.0,
"rows": []
},
"state_income_tax": {
"entity": "tax_unit",
"base_sum": 578044599346.724,
"head_sum": 578044599346.724,
"change": 0.0,
"records_changed": 0,
"weighted_changed": 0.0,
"weighted_up": 0.0,
"weighted_down": 0.0,
"largest_abs": 0.0,
"rows": []
}
}Generated by Claude Code |
|
US + core hub merge audit, us#10578 at
|
Defect
section_22_incomepicked the Schedule R initial amount withselecttakes the first true condition, so a married individual filing separately with one qualified individual got $5,000, and one with two qualified members (the filer plus a qualifying dependent) got $7,500 before the under-65 cap. 26 U.S.C. 22(c)(2)(A) gives "$3,750 in the case of a married individual filing a separate return".The model also never applied 22(e)(1): a married individual on a separate return gets the credit only if the spouses lived apart for the whole year.
Fix
Both changes are in
section_22_income.py;elderly_disabled_credit_potentialis againrate × section_22_income.separate & (n >= 1), comes first in the select, so it wins. It testsn >= 1rather thann == 1becausequalifies_for_elderly_or_disabled_creditcounts every member who is 65 or older or retired on total disability, dependents included, so a separate filer with a qualifying dependent hasn = 2.section_22_incomeis 0 for a separate filer withcohabitating_spousestrue. The bar sits insection_22_income, not on the credit, because Michigan's subtraction takes the Schedule R line 19 amount, and a separate filer who lived with the spouse has none: Schedule R gives a separate filer only boxes 8 and 9, and both require that "you lived apart from your spouse for all of" the year. MCL 206.30(1)(f)(v) deducts "the section 22 amount eligible for the … credit", and the 2024 MI-1040 instructions point to "U.S. Form 1040 Schedule R, line 19".cohabitating_spousesis the inputtaxable_social_security_tier_1andira_219g_deductible_limitalready use for the "lived apart" tests of 86 and 219(g); it defaults to false, so a separate filer counts as living apart unless the input says otherwise.phase_out.yamlcited "§ 22(c)(3)(D)"; 22(c)(3) has only (A) and (B), and the thresholds and one-half rate are in 22(d). Both references now point at 22(d). Values are unchanged.Federal results are the same as the earlier head of this PR: the credit was already zero for a separate filer who lived with the spouse.
Other parts of section 22 checked for a separate filer
num_elderly × $5,000 + disability income of members under 65, with dependents left out ofnum_elderly. A separate filer aged 65 or more gets a $5,000 cap, above $3,750; one under 65 is capped at their disability income.Changes for separate filers, compared with main (TY2024, Texas, wages only)
section_22_incomeThe last row: on main the select fell through to the SEPARATE branch even with no qualifying member, so the unit got $3,750 capped at its $2,000 of disability pay. It is now 0. A separate filer whose only qualifying member is a dependent still gets an initial amount, as on main; see the first follow-up.
Tests
section_22_income.yaml(10 cases, TY2024, Texas):section_22_incomemi_subtractions.yaml, new case: Michigan separate filer, 67, $3,000 wages, lived with spouse.section_22_income0; tier three standard deduction 20,000 − 5,600 personal exemption = 14,400;mi_subtractions14,400 (main 19,400; the earlier head of this PR 18,150).test_elderly_disabled_credit_invariants.py: a Hypothesis differential test over batches of eight mixed units (single, joint, separate, head of household; with or without a dependent who can qualify by age; living together or apart), plus one fixed batch that always runs:section_22_incomeequals the formula on main, frozen as a reference function (I ran it against main's model locally and it matched);Mutants, each in its own process, code restored after:
>= 1→== 1; the 22(e)(1) bar removed; the bar applied to every status (separate &dropped). Each fails at least one YAML case and the Python test.Effect on other programs
Readers of
section_22_incomeandelderly_disabled_creditoutside the credit itself:gov/states/mi/tax/income/subtractions.yaml) listssection_22_income. A separate filer's subtraction falls from $5,000 to $3,750 when the spouses lived apart, and to 0 when they lived together (TY2024, $3,000 wages: 19,400 on main → 14,400).mi_interest_dividends_capital_gains_deduction) subtractselderly_disabled_creditfrom the deduction's maximum. A lower federal credit raises the remaining maximum for a separate filer who qualifies for that deduction.ne_elderly_disabled_creditismin(elderly_disabled_credit, income_tax_before_credits). TY2024 separate filer, 67, $9,000 wages, itemizing: 450 on main → 262.50 when the spouses lived apart, 0 when they lived together.vt_elderly_or_disabled_creditis 24% of the federal credit. Same unit: 108 on main → 63 apart, 0 together.elderly_disabled_creditsits innon_refundable.yamland in thepreceding_creditslists of the CTC limit, the education credits, the retirement saving credit, the residential clean energy, energy efficient home improvement and clean vehicle credits. A smaller elderly credit leaves more liability for those later credits. Two contributed reforms (congress/afa/afa_other_dependent_credit.py,crfb/non_refundable_ss_credit.py) list it the same way.Follow-ups (same on main)
section_22_income. 22(c)(2)(A) sets the initial amount by the filer and, on a joint return, the spouse, whilequalifies_for_elderly_or_disabled_creditcounts every member. So a dependent can raise the amount (a single 64-year-old retired on disability, with $10,000 of disability pay and a dependent aged 80, gets 6,250 where the statute gives 3,750), zero it (a single 67-year-old with dependents aged 80 and 85 gets 0 where it gives 5,000), or create one for a filer who doesn't qualify.mi_interest_dividends_capital_gains_deductionsubtracts the 15% credit.impact: we estimate that no tax or benefit moves in 2026. Section 22 income falls $78.7 million on 25 records of the default dataset (62,957 weighted tax units), and their potential elderly and disabled credit falls $11.8 million. All 25 file separately, and each one's initial amount falls from $5,000 to $3,750. No record gets the credit itself on either side, since no record with a potential credit has income tax left for it to offset. Federal and state income tax, benefits and household net income don't move.
axiom: TheAxiomFoundation/rulespec-us#1982 queued
🤖 Generated with Claude Code