Skip to content

Count the gross section 911 exclusion toward the filing threshold - #10022

Merged
MaxGhenis merged 2 commits into
mainfrom
section-911-filing-gross-exclusion
Oct 12, 2026
Merged

MaxGhenis merged 2 commits into
mainfrom
section-911-filing-gross-exclusion

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Follows #9976. This fixes three findings from that PR's r5 review: P3-i (the filing threshold), P3-g (one leaf list) and P3-j (a docstring).

What changes

1. The filing threshold counts Form 2555 line 43 (P3-i)

Before. tax_unit_is_required_to_file added foreign_earned_income_exclusion to gross income. That variable is line 2c of the Foreign Earned Income Tax Worksheet:

  • Line 2c is net of the Form 2555 line 44 deductions allocable to the excluded income.
  • It is also net of worksheet line 2b, the disallowed deductions.
  • It includes the line 50 housing deduction, which is a deduction, not an exclusion.

The law. The filing requirement needs gross income without the exclusion:

  • 26 U.S.C. 6012(c): for this section, "gross income shall be computed ... without regard to the exclusion provided for in section 911".
  • The 2025 Form 1040 instructions, Chart A, count income from outside the United States "even if you can exclude part or all of it". They also say not to reduce income by losses.
  • Form 2555 line 43 is that amount: the line 36 housing exclusion plus the line 42 foreign earned income exclusion. It comes before line 44.
  • Section 911(c)(4)(A) treats the line 50 housing amount as "a deduction allowable in computing adjusted gross income". The income behind that deduction is already in gross income.

After. A new helper, section_911_gross_exclusion in tools/section_911.py, sets the add-back:

  • When any Form 2555 leaf input applies, it is foreign_earned_income_exclusion_gross (line 43).
  • Otherwise it keeps the legacy foreign_earned_income_exclusion fallback.
  • Both amounts are floored at zero.

The Massachusetts add-back already used exactly this rule. Its formula now calls the same helper, so the two cannot drift apart.

The review's failure case, a single filer in 2025:

Input Amount
employment_income (after the exclusion) $10,000
foreign_housing_exclusion (line 36) $10,000
foreign_earned_income_exclusion_allocable_deductions (line 44) $10,000
Add-back Gross income Over $15,750?
Before (line 2c) $0 $10,000 no, not required to file
After (line 43) $10,000 $20,000 yes, required to file

2. One leaf list and one input check (P3-g)

Three formulas each wrote out the five leaf names and Core's input check by hand:

  • foreign_earned_income_exclusion;
  • section_911_excluded_income, which imported the tuple but repeated the check;
  • ma_foreign_earned_income_exclusion_addback.

They now call has_section_911_leaf_inputs(tax_unit, period), which iterates SECTION_911_LEAF_INPUTS. The filing requirement reaches it through section_911_gross_exclusion.

The only call to Core's private _get_exportable_input_periods is now inside that helper. The review's P3-b item (formulas relying on private Core helpers) is still open, but this call now lives in one place.

3. The docstring of elects_section_911_exclusion (P3-j)

The docstring now says the bar on a filer who claims only the housing deduction rests on the IRS instructions, not the statute, and it cites both:

  • The statute. §24(d)(3) applies to a taxpayer who "elects to exclude any amount from gross income under section 911". §911(c)(4)(A) makes the housing amount a deduction.
  • The instructions. Schedule 8812 Part II-A says "If you file Form 2555, you cannot claim the additional child tax credit". Credit Limit Worksheet B applies only if "You are not filing Form 2555". Pub. 596 Rule 5 denies the EITC to anyone who files Form 2555 "to deduct or exclude a foreign housing amount".
  • The EITC statute. The docstring also quotes §32(c)(1)(C), which turns on whether an individual "claims the benefits of section 911".

Behavior changes

These are intended:

  • Callers who supply leaves, where line 43 differs from line 2c. Filing gross income now uses line 43. Holding the other lines fixed:
    • A positive line 44 or worksheet line 2b makes it higher than before.
    • A positive line 50 makes it lower than before.
    • When both are present, the larger effect wins. Example: wages of $2,000, a gross exclusion of $10,000, line 44 of $1,000 and line 50 of $5,000. Filing gross income falls from $16,000 to $12,000, and the unit is no longer required to file.
  • Legacy callers with a negative foreign_earned_income_exclusion. That amount no longer lowers filing gross income, because an exclusion cannot be negative and the worksheet floors line 2c at zero. Main already floors the same amount in the main rates worksheet, the AMT, the NIIT and the Massachusetts add-back.

Nothing else changes:

  • Legacy callers with a non-negative amount get the same filing result as on main.
  • ma_foreign_earned_income_exclusion_addback, foreign_earned_income_exclusion and section_911_excluded_income keep their formulas; the code only moved into the helpers.

Not changed here: the filing variable's unearned-income test applies to every tax unit, not only to dependents. That is a separate, pre-existing issue and is tracked separately.

Invariants

These hold for every input:

  1. The filing add-back is never negative.
  2. With leaf inputs and no directly entered foreign_earned_income_exclusion_gross, the add-back is max(0, line 36 + line 42). Lines 44 and 50 and worksheet line 2b never change it. A directly entered foreign_earned_income_exclusion_gross replaces the sum, as an entered aggregate does elsewhere: entering 0 beside a $10,000 housing exclusion gives an add-back of 0.
  3. Without leaf inputs, the add-back is max(0, legacy amount). That equals main's add-back whenever the legacy amount is non-negative.
  4. The add-back cancels in the unearned-income test (gross income − earned income − add-back), so it cannot change that test.
  5. Every name in SECTION_911_LEAF_INPUTS, entered alone (even as zero), switches every formula that chooses between leaf and legacy inputs to the leaf calculation.

A Hypothesis property test covers invariants 1, 2 and 4. YAML cases cover 3. A test parametrized over the tuple covers 5. There is no second implementation of this rule to run a differential test against: Massachusetts and the filing requirement now share one.

Tests

Adoption work on 2026-10-11 merged main a844fc1717e8632bc92a9b0d7c9d41dda5359fdc into the PR branch with a plain, conflict-free merge, producing 94f3dd7e42d1cb5f0b582fdea6b0b04f03a4a8b3. All eight original PR files are byte-identical to 6974105681895e2cbafffdfea35b415bea89d53f; the diff against merged main still contains only those files. No additional model or test changes were needed.

make format passed before the merge: 6,771 Python files were already formatted, and Ruff lint passed.

Run by the hub at 94f3dd7e42d1cb5f0b582fdea6b0b04f03a4a8b3 on 2026-10-11, one file per process:

Test file Result
policyengine_us/tests/policy/baseline/gov/irs/tax_unit_is_required_to_file.yaml 8 passed
policyengine_us/tests/test_foreign_earned_income_exclusion_leaves.py 25 passed
test_ma_foreign_earned_income_addback.py, test_section_911_batch_inputs.py Not run locally at this head. CI runs them.

The adoption build could not run tests in its sandbox, so the author's earlier 585 YAML and 51 Python passes predate the merge of main; the rows above are the current-head results.

Partner contract files were not edited. A scan found no section 911 aggregate/leaf inputs or direct references to tax_unit_is_required_to_file in those files; no moving partner expected values were identified. The committed diff against main contains no partner files or lockfile changes.

An independent review (GPT-6.1 Sol) approved this head on 2026-10-11 with no blocking findings; its three notes on this description are applied above.

Hub microsimulation impact (current head)

A real microsimulation on the default dataset, main at the merge base a844fc1717 against this PR's head 94f3dd7e42, for 2026. The runs went one at a time under the shared heavy-job lock. Sums are weighted.

Output 2026 change 2026 records changed (weighted)
tax_unit_is_required_to_file +0 0 (0)
foreign_earned_income_exclusion +0 0 (0)
income_tax +0 0 (0)
state_income_tax +0 0 (0)
household_net_income +0 0 (0)
household_benefits +0 0 (0)

Comparison files are in fixes/10022-impact/ on the hub host.

Axiom

axiom: TheAxiomFoundation/rulespec-us#1484 queued

  • The rule already matches. It adds foreign_earned_income_exclusion_adjustment_for_return_filing_gross_income, which is the section 911 exclusion itself, as an input amount. Axiom has no Form 2555 leaves, so this PR changes only how PolicyEngine derives that amount.
  • No oracle mapping. No axiom-oracles adapter maps that input; I searched main.
  • Still pending. Incorrect calculation of PA taxable income #1484's companion case, a $30,000 exclusion, still waits for the signed encoder.
  • P3-g and P3-j need no Axiom work: one is a refactor and the other is a docstring.

Still open from the #9976 reviews

These are not in this PR:

  • P3-f: a batch of tax units that all use aggregate inputs, but different ones, is not rejected.
  • P3-h: the 2015–16 IRMAA path doesn't see leaf inputs.
  • P3-b: formulas still use private Core helpers. The leaf check is now one call site, and the legacy carry-over in foreign_earned_income_exclusion still imports _uprating_index_value.
  • The LR 84-40 deduction follow-up.

MaxGhenis and others added 2 commits October 9, 2026 07:35
Follow-ups from the r5 review of #9976:
- 26 U.S.C. 6012(c): the filing add-back is Form 2555 line 43
  (foreign_earned_income_exclusion_gross) when leaf inputs apply, with
  the legacy foreign_earned_income_exclusion fallback, floored at zero.
- One Form 2555 leaf check (has_section_911_leaf_inputs) and one line 43
  helper (section_911_gross_exclusion) replace hand-written leaf lists.
- elects_section_911_exclusion docstring: the housing-deduction-only bar
  rests on the IRS instructions, not section 24(d)(3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Context from the session that opened this PR, for the US + core hub, which now owns it. I won't push to this branch.

Head I last tested: 6974105681 (one commit on main 2de3df4c88, #9976's merge). The hub's main merge, 94f3dd7e42, came after that, so none of the numbers below were run at the current head.

Tests at 6974105681. All runs were local: macOS, uv's Python 3.14 free-threaded build. CI has not run, because this is a draft.

  • The new tax_unit_is_required_to_file.yaml has 8 cases. 6 fail on Add excluded foreign earnings to Massachusetts gross income #9976's head 0aabd69 and pass after; the 2 legacy/invariant cases pass on both.
  • All 63 non-partner YAML files that mention the section 911 variables or tax_unit_is_required_to_file: 585 passed.
  • The five section 911 Python test files: 51 passed. The Hypothesis filing test was added after that run and passed on its own.
  • Some of these multi-file runs went ahead before I read ~/reviews/us-hub/test-run-rule.md, so they ran without the heavy lock. They finished cleanly.

Why the per-leaf consumer test is built the way it is. My first version entered the legacy amount for the prior year only, and it missed 4 of 5 mutations. Every downstream fallback (section_911_excluded_income, the Massachusetts add-back, the filing add-back) reads foreign_earned_income_exclusion, and that variable had already switched to the leaf path. So the leaf path and the fallback both gave 0.

  • The current test enters the legacy amount for 2025. Downstream consumers are checked in 2025, where their fallback is the entered amount, and foreign_earned_income_exclusion is checked in 2026, where its fallback is the uprated carry.
  • Mutation check. I added a sixth leaf to the tuple and made one consumer ignore it, for each of five consumers in turn: the federal amount, the MAGI amount, the Massachusetts add-back only, the filing requirement only, and the shared line 43 helper. All 5 failed.
  • The script is ~/reviews/us-hub/reviews-gen/us-s911-followups/mutate.py. Run it from the worktree root as uv run python -I mutate.py <federal|magi|ma_only|filing_only|gross_helper>, then restore with git checkout HEAD -- policyengine_us/ and delete zz_mutation_test_leaf.py.

Review brief. A first-review prompt is ready at ~/reviews/us-hub/reviews-gen/us-s911-followups/prompt-r1.template.md. Fill in __PR__, __HEAD__, __BASE__ and __TITLE__; the body copy is body.md in the same folder. No review has been dispatched.

Impact. Not run at any head of this PR.

  • The body's "expected zero" rests on the hub's Add excluded foreign earnings to Massachusetts gross income #9976 run at 0aabd69 (~/reviews/us-hub/fixes/9976-impact/compare.2025.json and compare.2026.json): foreign_earned_income_exclusion totaled $0, with 0 records changed, in both years.
  • The default dataset supplies no Form 2555 leaves, so with no leaves this PR's add-back reduces to max(0, that amount).

Out of scope, found in passing. tax_unit_is_required_to_file applies its unearned-income test (500 + additional standard deduction) to every tax unit. Probed on 10/09: a single non-dependent adult with $600 of interest comes out required to file. That rule belongs only to people who can be claimed as dependents (§6012(a)(1)(C)/(f)(2)(D), §63(c)(5), and Chart B, where the 2025 threshold is $1,350). I raised it as a separate task card, task_b21a5056, with no PR yet. It is not part of this PR.

Local state. I'm removing my worktree (policyengine-us-worktrees/s911-followups-prep) and its local branches. Everything there was pushed or superseded, so nothing holds this branch.

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

US + core hub merge audit, us#10022 at 94f3dd7e42d1cb5f0b582fdea6b0b04f03a4a8b3 (squash): Count the gross section 911 exclusion toward the filing threshold.

@MaxGhenis
MaxGhenis deleted the section-911-filing-gross-exclusion branch October 12, 2026 14:58
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