Skip to content

Keep inputs when NY and AL recompute federal credits under pinned rules - #10587

Merged
MaxGhenis merged 4 commits into
mainfrom
credit-recompute-branches-keep-inputs
Nov 3, 2026
Merged

MaxGhenis merged 4 commits into
mainfrom
credit-recompute-branches-keep-inputs

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Defect

Four formulas recompute a federal credit under pinned rules in a branch. Each one then deleted the branch's arrays for every variable whose name matched a substring list:

Formula Branch Years Names deleted
ny_eitc ny_pre_arpa_eitc TY2021 (eitc.decoupled) contains eitc
al_federal_income_tax_deduction al_2020_irc TY2021 contains ctc, cdcc, eitc, residential_clean_energy_credit or dependent_care_assistance_exclusion
ny_ctc_pre_2024 pre_tcja_ctc years with ctc.pre_tcja (2018+) when the post-2024 rules are off: 2018-2024 and 2028+ contains ctc or residential_clean_energy_credit
ny_ctc_pre_2024_eligible pre_tcja_ctc same same

Mechanism. In policyengine-core 3.33.2 (main's lock), Simulation.delete_arrays purges the calling branch, each ancestor and the default branch from the branch's private holder storage. Its docstring says "Deleted inputs stop counting as inputs". So any plain input whose name matched lost its value in the branch, and the branch read the input's default instead. Two plain inputs (no formula, adds or subtracts) match the EITC and Alabama selections: takes_up_eitc (default true) and eitc_passive_income_also_in_earned_income (default 0). No plain input matches the NY CTC selection.

Reproduction on main (household simulations, one parent aged 35, one child aged 3):

Case takes_up_eitc eitc state output on main on this branch
NY 2021, $10,000 wages true 3,400 ny_eitc 1,020 1,020
NY 2021, $10,000 wages false 0 ny_eitc 1,020 0
AL 2021, $41,000 wages (head of household) true 185.50 al_federal_income_tax_deduction 258.87 258.87
AL 2021, $41,000 wages false 0 258.87 380

By the same mechanism, a NY 2021 EITC non-taker in a microsimulation would get the NY EITC (no microsimulation was run here). In Alabama, a 2021 non-taker's Part II is reduced by a 2020-rule EITC they never claimed.

Fix

policyengine_us/tools/period_branch.py gains drop_recomputed_values(branch, name_parts). It keeps the same name selection, any(part in name for part in name_parts) over the branch system's variables, and calls branch.delete_arrays only for the selected variables that have formulas, adds or subtracts. All four formulas now call it with their existing selections; I found no selection that was wrong, and kept them all.

The plain inputs it keeps, by selection:

  • ny_eitc (eitc) and al_federal_income_tax_deduction (ctc, cdcc, eitc, residential_clean_energy_credit, dependent_care_assistance_exclusion): takes_up_eitc and eitc_passive_income_also_in_earned_income.
  • ny_ctc_pre_2024 and ny_ctc_pre_2024_eligible (ctc, residential_clean_energy_credit): none, so these two behave exactly as on main.

The rule: plain inputs carry into the branch, and formula variables don't. A plain input such as takes_up_eitc is a fact the pinned rules don't change. A supplied value of a formula variable in the selection (eitc, ctc, refundable_ctc, cdcc, ctc_qualifying_child, ...) is the current-law amount, and the pinned rules define a different one: ny_eitc recomputes the federal EITC with pre-ARPA parameter values, and ny_ctc_pre_2024 calculates the pre-TCJA CTC. So the branch recomputes every formula variable in its selection, supplied or not, as main did. For a caller who supplies ctc or eitc, nothing changes from main. The parent keeps its supplied values: delete_arrays on the branch leaves the parent simulation's storage alone (a parent supplied ctc 4,000 still reads 4,000 after ny_ctc runs).

Checked at this head against the review's witnesses: 2023 NY, supplied ctc 4,000 gives ny_ctc 330 (as main; the previous head gave 633.60); NY 2021, supplied eitc 500 gives ny_eitc 1,020 (as main; the previous head gave 150).

drop_inherited_values is unchanged; get_override_branch, capital_gains_responses.py and behavioral_response_measurements.py still call it.

Other delete_arrays callers on main

None of them deletes an input, and none is changed here:

  • tools/branched_simulation.py: on exit, deletes from the parent only the variables first stored while the clone was open. Inputs were stored before then, so they are never selected.
  • variables/gov/simulation/capital_gains_responses.py: deletes only variables that are neither inputs (user_set_variables) nor formula-less. Inputs that have formulas go through drop_inherited_values, which keeps their input periods.
  • variables/gov/ssa/revenue/tob_revenue_total.py, tob_revenue_oasdi.py (two branches), employer_ss_tax_income_tax_revenue.py and employer_medicare_tax_income_tax_revenue.py: delete only variables not in branch.input_variables, the variables with stored values when the simulation was built.
  • variables/household/marginal_tax_rate.py, marginal_tax_rate_including_health_benefits.py and marginal_tax_rate_helpers.py: delete only variables not in user_set_variables(sim), plus employment_income, which each branch sets again with the raised earnings on purpose.

(system.py calls delete_input_values on the earnings and capital-gains inputs while it builds the simulation. That is input rewriting, not a branch, so it is outside this audit.)

Tests

Take-up cases (YAML):

  • ny_eitc.yaml: NY 2021, parent with $10,000 wages and a child aged 3. With take-up, the pre-ARPA EITC is 34% × 10,000 = 3,400 (under the 3,618 maximum), and 30% of it is 1,020 (Tax Law 606(d); 2021 IT-215 instructions). The household credit is capped at NY tax before credits, which is 0. Without take-up, ny_eitc is 0.
  • al_federal_income_tax_deduction.yaml: AL 2021, head of household with $41,000 wages and a child aged 3. Federal tax is (41,000 − 18,800) = 22,200 taxable, so 1,420 + 12% × 8,000 = 2,380. Part I is 0, because the refundable CTC is 3,600. Part II line 6 is 2,380 − 2,000 (2020 CTC, all nonrefundable here) = 380. The 2020-rule EITC is 3,584 − 15.98% × (41,000 − 19,330) = 121.13. Deduction: 380 − 121.13 = 258.87 for a taker, and 380 for a non-taker.

Order-dependent cases (YAML calculates outputs in order, and these branches copy the parent's cache, so each lists the current-law federal credit first):

  • ny_eitc.yaml, "2021 NY EITC recomputes the federal credit without ARPA after the ARPA credit": childless filer aged 30 with $9,000 wages. eitc (ARPA) is 15.3% × 9,000 = 1,377, under the 1,502 maximum and below the 11,610 phase-out start. Pre-ARPA (Rev. Proc. 2020-45 amounts in gov.states.ny.tax.income.credits.eitc.pre_arpa: maximum 543, phase-out start 8,880; 2020 phase-out rate 7.65%): 543 − 7.65% × (9,000 − 8,880) = 533.82; 30% = 160.15, less the household credit of 40 (capped at NY tax before credits of 40, both from the model) = 120.15.
  • ny_ctc_pre_2024.yaml, "2023 pre-TCJA credit recomputes the federal CTC after the current-law CTC": parent with $30,000 wages, child aged 3. ctc (current law) is 2,000. Pre-TCJA: $1,000 per child, under the $75,000 head-of-household phase-out threshold; federal tax is (30,000 − 20,800) × 10% = 920, so 920 is nonrefundable and the other 80 refundable (15% × 27,000 = 4,050 exceeds it). 33% × 1,000 = 330, above the $100 minimum; the 2023 minimum age is 0.

Supplied formula-variable cases (the branch recomputes them):

  • al_federal_income_tax_deduction.yaml, "2021 childless filer, NIIT survives the line-6 floor": eitc: 200 restored as on main; expected 120 unchanged.
  • ny_ctc.yaml, "2025 - Updated CTC disabled vs enabled comparison (young child)": ctc: 0 restored as on main; expected 330 unchanged. The file is now identical to main.
  • ny_eitc.yaml, "2021 NY EITC recomputes the federal credit when the ARPA credit is supplied": the review's witness, supplied eitc 500 for the $10,000 parent; ny_eitc 1,020 (34% × 10,000 = 3,400; 30%).
  • ny_ctc_pre_2024.yaml, "2023 pre-TCJA eligibility recomputes a supplied federal qualifying-child flag": child aged 6 with ctc_qualifying_child: false supplied; ny_ctc_pre_2024_eligible true and ny_ctc_pre_2024 330. Moved from the deleted Python file with the expectation this rule gives.

The Python file tests/core/test_credit_recompute_branches_keep_inputs.py is deleted: its take-up tests repeated the YAML cases, and its CTC tests asserted the reverse rule.

Mutants of drop_recomputed_values (not committed), each run on ny_eitc.yaml, ny_ctc_pre_2024.yaml, al_federal_income_tax_deduction.yaml and ny_ctc.yaml in its own process, code restored after:

Mutant Cases that fail (got → expected)
(a) returns before dropping anything ny_eitc: "…without ARPA after the ARPA credit" (373.10 → 120.15), "…when the ARPA credit is supplied" (150 → 1,020). ny_ctc_pre_2024: "…recomputes the federal CTC after the current-law CTC" (633.60 → 330), "…supplied federal qualifying-child flag" (eligible 0 → 1). AL: 5 cases, including both take-up cases and "NIIT survives the line-6 floor" (0 → 120). ny_ctc: "2025 - Updated CTC disabled…" (100 → 330).
(b) deletes plain inputs too (main's rule) ny_eitc: "…does not take up the federal EITC" (1,020 → 0). AL: "…no EITC take-up…" (258.87 → 380). Both CTC files pass: their selection has no plain input.
(c) deletes nothing the caller supplied (previous head's rule) ny_eitc: "…when the ARPA credit is supplied" (150 → 1,020). ny_ctc_pre_2024: "…supplied federal qualifying-child flag" (eligible 0 → 1). AL: "NIIT survives the line-6 floor" (0 → 120). ny_ctc: "2025 - Updated CTC disabled…" (100 → 330).

Run at the final head, one file per process:

File Result
ny_eitc.yaml 15 passed
al_federal_income_tax_deduction.yaml 10 passed
ny_ctc.yaml 19 passed
ny_ctc_pre_2024.yaml 17 passed
ny_supplemental_eitc.yaml 9 passed
nyc_eitc.yaml 3 passed
nyc_income_tax_elimination_credit.yaml 19 passed
contrib ny_a04038_enhanced_escc_infants.yaml 5 passed
contrib ny_working_families_tax_credit.yaml 13 passed
contrib al_eitc.yaml 5 passed
tests/code_health/test_source_conflicts.py 103 passed

I left the rest of the NY and AL state folders, the NYC folder, contrib reforms and microsimulation to CI.

Follow-up (identical on main)

_pin_2020_irc in policyengine_us/tools/pinned_tbs.py pins the EITC, CTC and CDCC parameters to their 2020-01-01 values for TY2021, while _pin_pre_arpa_eitc restores the Rev. Proc. 2020-45 inflation-indexed 2021 amounts. Under the 2021-indexed reading, the new Alabama case would give a 2020-rule EITC of 3,618 − 15.98% × (41,000 − 19,520) = 185.50 and a deduction of 194.50, not 258.87. Not changed here.

impact: TY2021 only: ny_eitc, ny_supplemental_eitc, ny_income_tax, al_federal_income_tax_deduction and al_income_tax move for a tax unit that doesn't take up the federal EITC or that supplies eitc_passive_income_also_in_earned_income. None from 2022: only the 2021 NY EITC and Alabama Part II paths read those inputs, and the review's 80 witnesses for 2022-2028 match main.

axiom: n/a (branch plumbing: no provision's reading changes; take-up is an input)

🤖 Generated with Claude Code

ny_eitc (TY2021), al_federal_income_tax_deduction (TY2021),
ny_ctc_pre_2024 and ny_ctc_pre_2024_eligible each deleted the branch's
arrays for every variable whose name matched a substring list. Core's
Simulation.delete_arrays deletes inputs too, so takes_up_eitc and
eitc_passive_income_also_in_earned_income fell back to their defaults in
the branch: a 2021 New York EITC non-taker still got the state credit.

Add drop_recomputed_values to period_branch, which applies the same name
selection through drop_inherited_values and so keeps every array
set_input stored, and use it in all four formulas. A supplied value for a
recomputed formula variable now carries into the branch too; two YAML
cases that relied on the branch discarding a supplied eitc or ctc no
longer supply it.

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

Copy link
Copy Markdown
Contributor Author

Ready for hub review

Head: 76a7d238880f9ba865c64eb097ad509ffc1640c1 (draft)

Steps

  1. Reproduction on main: NY 2021 (parent 35, $10,000 wages, child 3) gives eitc 3,400 and ny_eitc 1,020 with take-up. Without take-up, eitc is 0 but ny_eitc is still 1,020, as reported. AL 2021 (head of household, $41,000 wages, child 3) gives a deduction of 258.87 both ways. By hand, the non-taker's deduction is 380: Part II line 6 is 380 with no 2020-rule EITC.
  2. Fix: new drop_recomputed_values at policyengine_us/tools/period_branch.py:180. It applies the same substring selection, then drop_inherited_values. The formulas call it at ny_eitc.py:39, al_federal_income_tax_deduction.py:66, ny_ctc_pre_2024.py:59 and ny_ctc_pre_2024_eligible.py:48. Selections are unchanged; I found none wrong. Inputs lost on main: takes_up_eitc and eitc_passive_income_also_in_earned_income (NY EITC and AL). No plain input matches the NY CTC selection, so there only supplied formula variables (ctc, ctc_qualifying_child, ...) were lost. Those branches run in 2018-2024 and 2028+, so 2024 and 2028+ microsimulations reach them.
  3. Caller audit: none of tools/branched_simulation.py, capital_gains_responses.py, the four ssa/revenue files (5 loops) or the three marginal tax rate files deletes an input. The MTR files deliberately reset employment_income. Details are in the body. None was changed.
  4. Tests: new YAML cases in ny_eitc.yaml and al_federal_income_tax_deduction.yaml, and new tests/core/test_credit_recompute_branches_keep_inputs.py (6 tests, all 4 branches).
  5. Runs (one process each): ny_eitc 13, al_federal_income_tax_deduction 10, ny_ctc_pre_2024 15, ny_ctc 19, ny_supplemental_eitc 9, nyc_eitc 3, nyc_income_tax_elimination_credit 19, contrib ny_a04038_enhanced_escc_infants 5, contrib ny_working_families_tax_credit 13, contrib al_eitc 5, new Python test 6, test_source_conflicts 103. All pass. There is no ny_ctc_pre_2024_eligible YAML file.

Mutants (not committed, restored after each)

  • Substring loop restored in ny_eitc.py: the new non-taker case fails (1,020 vs 0).
  • Substring loop restored in the AL file: the new non-taker case fails (258.87 vs 380).
  • All four files at main: the Python test fails 4 of 6 (all four branches); the two take-up-true cases pass.

CI: not run, because the PR is a draft. No microsimulation was run.

Judgment calls, please check

  • Two existing YAML cases supplied a credit and relied on the branch discarding it. Following the brief's rule that supplied values carry into the branch, I removed the supplied value and kept the expected output:
    • al_federal_income_tax_deduction.yaml "NIIT survives the line-6 floor" had eitc: 200. Part I is still 0 via the $200 AOC, and the deduction is still 120.
    • ny_ctc.yaml "Updated CTC disabled vs enabled comparison (young child)" had ctc: 0. The expected 330 is unchanged.
    • The brief's file list didn't name ny_ctc.yaml. It is the only file outside that list I changed, and only to keep an existing case's intent.
  • One helper rather than four copies, since all four calls were the same shape.
  • A supplied post-TCJA ctc now also stands as the pre-TCJA federal CTC in the NY recomputation. That follows from the rule, but a caller who overrides ctc in a pre-TCJA year gets their value, not a recompute.

Generated by Claude Code

@MaxGhenis
MaxGhenis marked this pull request as ready for review November 2, 2026 08:11
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Ready for hub review
VERDICT: REQUEST_CHANGES
76a7d23
METHODOLOGY_CHOICE: yes

Independent review, round 1. The head has not moved since 76a7d23888. No earlier "Hub review in progress" comment exists on this PR, so this review starts from the beginning.

The take-up fix is right and reproduces: on main, a 2021 New York EITC non-taker gets the New York EITC and an Alabama non-taker loses a 2020-rule EITC they never claimed. The PR fixes both. What blocks it is the rule it picks for supplied values of recomputed formula variables. That rule gives new wrong results for anyone who supplies ctc or eitc, which main handled correctly.

Findings

P1. A supplied current-law ctc or eitc now replaces the pinned-rules recomputation

policyengine_us/tools/period_branch.py:180-198 (drop_recomputed_values) passes the selection to drop_inherited_values. That function keeps every array set_input stored, including inputs to formula variables. The branch therefore no longer recomputes a supplied eitc, ctc, refundable_ctc, cdcc, ctc_qualifying_child and so on under the pinned rules. It reads the caller's current-law value instead. New York's 2021 EITC is defined on the federal credit computed without ARPA (Tax Law 607(a) decoupling, cited in ny_eitc.py). The pre-2025 New York child credit is defined on the pre-TCJA federal credit (ny_ctc_pre_2024.py, IT-213 Worksheet A). A supplied post-ARPA EITC or post-TCJA CTC is a different legal amount, not a fact the pinned rules should read. Main got this right, because it deleted those values in the branch. The body's statement that this is "the consistent rule because inputs are leaf values" doesn't hold for formula variables. Those are outputs of the very rules the branch swaps.

Failure scenario: a New York family in 2023 (parent, $30,000 wages, child aged 3) whose caller supplies its federal CTC.

# same household script at main (4bfc548d) and at the PR head
main  2023 ctc supplied 4000: ny_ctc 330   | not supplied: ny_ctc 330  (federal ctc 2000)
PR    2023 ctc supplied 4000: ny_ctc 633.60 | not supplied: ny_ctc 330
PR    2024 ctc supplied 4000: ny_ctc 597.30
main  NY 2021 eitc=500 supplied (parent, $10,000 wages, child 3): ny_eitc 1020
PR    NY 2021 eitc=500 supplied:                                  ny_eitc 150

The same thing happens in the 2021 Alabama Part II for a supplied ctc, refundable_ctc, cdcc or eitc. The two edited YAML cases were edited because of this change; see the second P2.

Replacement: keep plain inputs (variables with no formula), and still delete the branch's values of formula variables, supplied or not. Only two plain inputs match any selection: takes_up_eitc and eitc_passive_income_also_in_earned_income for the EITC and Alabama selections, and none for the NY CTC selection (I enumerated tbs.variables for each selection). So this keeps the whole take-up fix:

def drop_recomputed_values(branch: Simulation, name_parts: Iterable[str]) -> None:
    """Drop the values of formula variables a pinned-rules branch recalculates.

    A branch that swaps in a pinned tax-benefit system recalculates every
    formula variable whose name contains one of ``name_parts``, including one
    the caller supplied: a supplied value is the current-law amount, which the
    pinned rules replace. Plain inputs (no formula), such as ``takes_up_eitc``,
    are facts the pinned rules don't change, so they are kept.
    """
    name_parts = tuple(name_parts)
    for name, variable in branch.tax_benefit_system.variables.items():
        if any(part in name for part in name_parts) and (
            variable.formulas or variable.adds or variable.subtracts
        ):
            branch.delete_arrays(name)

I ran this on a scratch copy:

NY2021 takes_up True  ny_eitc 1020 | False ny_eitc 0
AL2021 takes_up True  al_federal_income_tax_deduction 258.87 | False 380
NY2021 eitc=500 supplied: ny_eitc 1020        (main's value)
2023/2024 ctc supplied 4000: ny_ctc 330        (main's value); parent's ctc still 4000 afterwards
ny_eitc.yaml 13 passed; al_federal_income_tax_deduction.yaml 10 passed with `eitc: 200` restored;
ny_ctc.yaml 19 passed with `ctc: 0` restored (both files as on main)
tests/core/test_credit_recompute_branches_keep_inputs.py: 4 passed, 2 failed
  (the two CTC tests that assert a supplied formula variable carries in; they would be inverted or dropped)

If the hub prefers the PR's rule, the body still needs to say what changes for callers: a supplied ctc or eitc changes ny_ctc (2018-2024, 2028+), ny_eitc (2021) and the 2021 Alabama deduction from main's values. Partner callers who supply federal credits should be told.

P2. The two edited YAML cases no longer pin what they used to pin

  • al_federal_income_tax_deduction.yaml, "2021 childless filer, NIIT survives the line-6 floor": it still pins its name. If the floor came after NIIT, line 6 would be max(-100 + 200, 0) = 100, and the result 100 - 80 = 20, not 120. Removing eitc: 200 also removed the only check that Part II ignores a supplied current-law EITC. Under the P1 replacement, restore eitc: 200 (verified: 10 passed).
  • ny_ctc.yaml, "2025 - Updated CTC disabled vs enabled comparison (young child)": it still pins 330 = 33% × the $1,000 pre-TCJA credit. It no longer pins that a supplied ctc is ignored. Under the P1 replacement, restore ctc: 0 (verified: 19 passed). The PR also edits ny_ctc.yaml, outside the assignment's file list. The hand-off says so.

P3. The New York drops are untested, and the new test misses an order-dependent defect

Mutant: drop_recomputed_values returns before dropping anything (return as its second statement). It survives ny_eitc.yaml (13 passed), ny_ctc_pre_2024.yaml (15 passed) and ny_ctc.yaml (19 passed). Only the AL YAML (4 failed) and the two AL Python tests (2 failed) kill it. The NY branches copy the parent's cache, so they diverge only when the parent has already calculated eitc or ctc. Witness at the PR head and with the mutant (NY 2021, single, age 30, $9,000 wages):

PR      ny_eitc alone 120.15 | eitc first 1377, then ny_eitc 120.15
mutant  ny_eitc alone 120.15 | eitc first 1377, then ny_eitc 373.10

Add to ny_eitc.yaml (YAML outputs are calculated in order, so eitc is listed first):

- name: 2021 NY EITC recomputes the federal credit without ARPA after the ARPA credit is calculated
  period: 2021
  absolute_error_margin: 0.01
  input:
    people:
      filer:
        age: 30
        employment_income: 9_000
    households:
      household:
        members: [filer]
        state_code: NY
  output:
    # ARPA childless credit: 15.3% x 9,000 = 1,377, under the 1,502 maximum.
    eitc: 1_377
    # Pre-ARPA (Rev. Proc. 2020-45): 543 - 7.65% x (9,000 - 8,880) = 533.82;
    # 30% = 160.15, less the household credit of 40 (capped at NY tax of 40).
    ny_eitc: 120.15

I took 543, 8,880 and 1,502 from the repo's parameter files, not from the IRS source. 40 is the model's ny_income_tax_before_credits and ny_household_credit for this filer. A matching ny_ctc.yaml case with ctc listed before ny_ctc in a pre-TCJA year would cover the CTC branch. This gap is identical on main (main's loop was untested the same way), so it's a test gap rather than a defect of this PR.

P3. The Python test repeats YAML coverage, and its docstring's claim is wrong

policyengine_us/tests/core/test_credit_recompute_branches_keep_inputs.py:12-15 says YAML "can't separate the two readings for the CTC branches". YAML can supply ctc or ctc_qualifying_child, as the ny_ctc.yaml case did before this PR. Its two take-up tests repeat the new YAML cases exactly, with the same households and values. CLAUDE.md asks for YAML whenever YAML can exercise the behavior, and for elapsed time and memory when a Python test builds models. The file builds 8 full simulations: 6 passed in 41 s locally. CI places it in the Rest Python core group, which the Makefile records at 13.3 GB RSS. The body reports neither. Replacement: move any CTC cases you keep into ny_ctc_pre_2024.yaml (there is no ny_ctc_pre_2024_eligible YAML file; it could go there or in ny_ctc_pre_2024.yaml) and delete the Python file.

P3. The hand-off comment is stale

It says "Head: ... (draft)" and "CI: not run, because the PR is a draft." The PR is not a draft, and CI at this head is green (see below).

Follow-up (identical on main, not this PR's defect): Alabama pins 2020-vintage EITC amounts, while New York uses 2021-indexed pre-ARPA amounts

_pin_2020_irc in policyengine_us/tools/pinned_tbs.py pins the EITC, CTC and CDCC parameters to their 2020-01-01 values for TY2021. The new AL case therefore uses the 2020 amounts: 3,584 − 15.98% × (41,000 − 19,330) = 121.13. _pin_pre_arpa_eitc uses the Rev. Proc. 2020-45 amounts for 2021 (1-child maximum 3,618, phase-out start 19,520). Under that reading, Act 2022-37's "Internal Revenue Code in effect on December 31, 2020" gives 3,618 − 15.98% × (41,000 − 19,520) = 185.50 for this household, and a deduction of 380 − 185.50 = 194.50, not 258.87. The code comment cites the worksheet's "based on 2020 Form 2441 Line 11" for the CDCC, which may also govern the EITC lines. I couldn't fetch the 2021 Alabama worksheet or the Act to settle it. Worth a source-conflict check by someone who can reach revenue.alabama.gov.

Checked and sound

  • The defect reproduces at main (4bfc548; the four formula files are unchanged since the PR's base 97b6d51). NY 2021, parent 35, $10,000 wages, child 3: eitc 3,400 and ny_eitc 1,020 with take-up; eitc 0 and ny_eitc 1,020 without. AL 2021, head of household, $41,000 wages, child 3: eitc 185.50, income_tax_before_credits 2,380, ctc = refundable_ctc 3,600, and the deduction 258.87 both ways. At the PR head: ny_eitc 0 and the AL deduction 380 for non-takers, unchanged for takers.
  • Hand value 380. 41,000 − 18,800 = 22,200 taxable; 10% × 14,200 + 12% × 8,000 = 2,380. The 2020-rule CTC of $2,000 is all nonrefundable against 2,380, so line 6 = 380. Part I = max(2,380 − 3,600 − EITC, 0) = 0. With no recomputed EITC, Part II = 380. With the 2020-vintage EITC of 121.13 (parameters eitc/max.yaml and eitc/phase_out/start.yaml at 2020-01-01), it is 258.87. Whether a non-taker's credit drops out: Part II subtracts the recomputed EITC from tax the filer owes, and a filer who claims no EITC has none to subtract. Removing it matches the model's handling of 2021 Part I, which subtracts the take-up-gated eitc. I couldn't read the worksheet itself (see below).
  • Take-up reaching the state credit. ny_eitc has no take-up of its own. Outside 2021 it reads eitc, which is already multiplied by takes_up_eitc, so on main a non-taker already gets $0 of NY EITC in 2022+. NJ (nj_eitc), OK (ok_federal_eitc), DC (dc_eitc_without_qualifying_child) and WA (wa_working_families_tax_credit) gate on takes_up_eitc explicitly. The PR brings 2021 NY into line with that convention, and ny_supplemental_eitc (25% of ny_eitc in 2021) follows. I couldn't fetch Tax Law 606(d), so I can't confirm whether its text turns on the federal credit "allowed" or "claimed". A web search snippet of older bill text reads "the earned income credit allowed under section thirty-two", but that is not the codified text. I marked METHODOLOGY_CHOICE yes because the PR treats a federal non-claimant as ineligible for the state credit, and I couldn't confirm the statute's wording.
  • Selections. Enumerating the variables of each selection in the system: NY EITC 109 variables and AL 298, each with exactly two plain inputs (takes_up_eitc, eitc_passive_income_also_in_earned_income); NY CTC 92 variables and no plain inputs. These match the body.
  • Mechanism. The installed policyengine-core is 3.33.2, and its Simulation.delete_arrays docstring reads "Deleted inputs stop counting as inputs". drop_inherited_values keeps (name, branch, period) keys from _user_input_keys on visible branches and replaces _fast_cache with {}. The social_security_inputs wrapper rewraps a plain dict (social_security_inputs.py:804-805), and get_override_branch already relied on this.
  • Year reach. ny_eitc's branch runs only when eitc.decoupled (true only in 2021). The AL branch runs only in 2021. The NY CTC branch runs when ctc.pre_tcja is set and post_2024.in_effect is false (true 2025-2027 only), so 2018-2024 and 2028+.
  • Caller audit (spot-checked two). tools/branched_simulation.py deletes, on exit, only the variables first stored while the clone was open. ssa/revenue/tob_revenue_total.py skips names in branch.input_variables. Both match the body. Callers that make branches but that the body doesn't name: spm.py, tools/general.py, tools/social_security_inputs.py (which wraps delete_arrays), behavioral_response_measurements.py, and the get_override_branch users: medicaid_enrolled_for_ssi_state_supplement, co_oap_medicaid_enrolled, both de_income_tax_if_claiming_*_eitc, both id_income_tax_if_receiving_aged_or_disabled_*, both mo_tanf_if_non_parent_caretaker_*, ny_recomputed_federal_agi, both va_income_tax_if_claiming_*_eitc and va_reduced_itemized_deductions. None of these calls delete_arrays (grep). The override users go through drop_inherited_values, which keeps inputs. system.py's delete_input_values calls rewrite inputs, as the body says. The NY CTC branch's parent can be ny_recomputed_federal_agi_for_child_credit, which sets eitc, income_tax_before_credits, adjusted_gross_income, charitable_contributions_ald and tax_unit_itemizes as inputs. None contains ctc or residential_clean_energy_credit, so the new rule carries none of them in.
  • Author's mutants, rerun. Main's loop in ny_eitc.py: ny_eitc.yaml 1 failed, 12 passed. Main's loop in the AL file: AL YAML 1 failed, 9 passed. All four formula files at main: Python test 4 failed ([False] NY, [False-380] AL, both CTC tests), 2 passed. My mutant (no drop at all) is described under P3. I restored the scratch copy after each run and checked git status was clean.
  • Test runs at the head, one file per process. ny_eitc.yaml 13 passed; al_federal_income_tax_deduction.yaml 10 passed; ny_ctc.yaml 19 passed; ny_ctc_pre_2024.yaml 15 passed; test_credit_recompute_branches_keep_inputs.py 6 passed (41 s).
  • CI at the head. Run 41411261682, "Pull request", completed: success. Every job succeeded, including Household API Partners, Microsimulation, all Baseline, Contrib and Rest Python shards, and PR validation. The other run, 41408674473, was skipped.
  • Readers of the changed variables. ny_eitc is read by ny_supplemental_eitc, gov.states.household.state_eitcs and NY refundable credits. al_federal_income_tax_deduction is read by al_deductions. ny_ctc_pre_2024 is read by ny_ctc (adds), and ny_ctc_pre_2024_eligible by ny_ctc_pre_2024. None is made worse beyond P1.
  • Scope. Nothing under .github/ or tests/policy/baseline/partners/. The changelog fragment is a top-level changelog.d/*.fixed.md. The impact: and axiom: lines are present. Open PRs mentioning the changed files: Fix the 2021 CTC split and surviving-spouse phaseout threshold #10306 (text match only, no shared file) and Follow state statutes where forms differ: NM medical exemption, VT CDCC, NY/NYC household credit, PR EITC #10300, which edits ny_household_credit_potential.py. ny_eitc subtracts the capped household credit, but the new NY cases have zero NY tax, so Follow state statutes where forms differ: NM medical exemption, VT CDCC, NY/NYC household credit, PR EITC #10300 can't move them.
  • Baseline movement and the hub's impact pair. At baseline with population data, the PR changes outputs only where the data supplies a matching input: takes_up_eitc or eitc_passive_income_also_in_earned_income (2021 NY and AL), or, under the PR's rule, any supplied formula variable whose name contains ctc, cdcc, eitc, residential_clean_energy_credit or dependent_care_assistance_exclusion. Variables for the impact pair: ny_eitc, ny_supplemental_eitc, ny_refundable_credits, ny_income_tax (2021); ny_ctc_pre_2024, ny_ctc (2024 and 2028; expected unchanged unless the data supplies a ctc* formula variable); al_federal_income_tax_deduction, al_deductions, al_taxable_income, al_income_tax (2021); plus state_income_tax and household_net_income for NY and AL in those years.

What I couldn't check

  • Primary sources. From this VM the proxy refused www.revenue.alabama.gov and alison.legislature.state.al.us (403 on CONNECT), and nysenate.gov and legislation.nysenate.gov don't resolve. tax.ny.gov, govinfo.gov, uscode.house.gov, irs.gov and law.justia.com gave no connection. So I couldn't read Tax Law 606(d) or 607(a), the 2021 IT-215 instructions, Act 2022-37 or the 2021 Alabama worksheet. Every legal statement above rests on the code's comments and the repo's parameter references, not on fetched text. The P1 point rests on the code's own description of what each branch computes.
  • Population impact. Whether the default datasets supply takes_up_eitc for 2021, or any matching formula variable, needs the hub's data.
  • The hand-off's results for the other NY and NYC files and the contrib files: I didn't rerun those; CI's shards passed.

Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Picking this up for the US hub. I'll post a hand-off comment here when it's ready.


Generated by Claude Code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A supplied eitc, ctc or ctc_qualifying_child is the current-law amount,
which the pre-ARPA, 2020-IRC and pre-TCJA branches must replace, so
drop_recomputed_values now deletes every formula variable in its selection
(supplied or not, as main did) and keeps only plain inputs such as
takes_up_eitc, which the pinned rules don't change.

Restore the supplied eitc and ctc inputs the previous head removed from
the AL and NY CTC YAML cases, add order-dependent NY cases that list the
current-law federal credit before the state credit, and replace the Python
test file with YAML cases.

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

Copy link
Copy Markdown
Contributor Author

Ready for hub review

Head: 65b1bf6216700ee2835972151eb1574d731ca152

Started from 76a7d238880f9ba865c64eb097ad509ffc1640c1 (no new commits on the branch). Merged origin/main (f8142b0223) with merge commit 8edf0496fb: no conflicts. Fix commit 65b1bf6216.

Findings

1 (P1). Formula variables are recomputed; plain inputs carry in. policyengine_us/tools/period_branch.py:180-194: drop_recomputed_values is the review's replacement. It calls branch.delete_arrays(name) for each selected variable with formulas, adds or subtracts. Module docstring (:29-32) and get_branch_for_period docstring (:259-262) reworded to match. Comments at the four call sites now say formula variables are recalculated, a supplied current-law value included: ny_eitc.py:37-39, al_federal_income_tax_deduction.py:64-66, ny_ctc_pre_2024.py:57-58, ny_ctc_pre_2024_eligible.py:46-47.

  • Checked in installed policyengine-core 3.33.2: Simulation.delete_arrays (simulations/simulation.py:2425) purges the calling branch, its ancestors and the default branch "from this simulation's private holder storage", and leaves the parent simulation's storage alone. Variable.is_input_variable is len(formulas) == 0 and adds is None and subtracts is None. My predicate gives the same split as not is_input_variable() for every variable in all three selections (0 mismatches).
  • Plain inputs per selection, enumerated from CountryTaxBenefitSystem(): NY EITC (eitc, 109 variables) and Alabama (298 variables): takes_up_eitc and eitc_passive_income_also_in_earned_income. NY CTC (ctc + residential_clean_energy_credit, 92 variables): none.
  • Witnesses at this head (household simulations): 2023 NY $30,000 parent, child 3, supplied ctc 4,000 → ny_ctc 330, the same as without it; the parent's ctc is still 4,000 after. NY 2021 supplied eitc 500 → ny_eitc 1,020. Take-up fix kept: NY 2021 non-taker 0; AL 2021 taker 258.87, non-taker 380.
  • drop_inherited_values is left in place. It still has callers outside get_override_branch: capital_gains_responses.py:210, behavioral_response_measurements.py:92, and core tests.

2 (P2). Inputs restored. al_federal_income_tax_deduction.yaml, "2021 childless filer, NIIT survives the line-6 floor": eitc: 200 and its comment are back as on main, and the expected 120 is unchanged. git diff origin/main on that file shows only the PR's two new cases. ny_ctc.yaml is checked out from main (ctc: 0, 330), and its git diff origin/main is empty.

3 (P3). Order-dependent cases.

  • ny_eitc.yaml, "2021 NY EITC recomputes the federal credit without ARPA after the ARPA credit" (childless, age 30, $9,000). The output lists eitc 1,377 first: 15.3% × 9,000, under the 1,502 maximum and the 11,610 ARPA phase-out start (gov/irs/credits/eitc/phase_in_rate.yaml, max.yaml, phase_out/start.yaml, 2021). Then ny_income_tax_before_credits 40 and ny_household_credit 40, which are the model's values for this filer, asserted in the case. Then ny_eitc 120.15. Pre-ARPA uses 543 (gov/states/ny/tax/income/credits/eitc/pre_arpa/childless_max.yaml) and 8,880 (childless_phase_out_start.yaml), both citing Rev. Proc. 2020-45, and the 2020 childless phase-out rate of 7.65% (phase_out/rate.yaml): 543 − 0.0765 × 120 = 533.82; × 30% = 160.146; − min(40, 40) = 120.15.
  • ny_ctc_pre_2024.yaml, "2023 pre-TCJA credit recomputes the federal CTC after the current-law CTC" ($30,000 parent, child aged 3). The output lists ctc 2,000 first, then ny_ctc_pre_2024 330. Pre-TCJA gives $1,000 per child, with no phase-out under the $75,000 head-of-household threshold. Tax is (30,000 − 20,800) × 10% = 920, so 920 is nonrefundable. The refundable part is min(1,000 − 920, 15% × (30,000 − 3,000) = 4,050) = 80. Total 1,000; 33% = 330, which is more than the $100 minimum. The 2023 minimum age is 0 (ctc/minimum_age.yaml).
  • Mutant (a) fails both, as shown below.

4 (P3). policyengine_us/tests/core/test_credit_recompute_branches_keep_inputs.py is deleted.

  • Its take-up tests were already in YAML.
  • Its supplied-ctc test is covered under the new rule by the restored ny_ctc.yaml ctc: 0 case.
  • Its supplied-ctc_qualifying_child test was not covered. It moved to ny_ctc_pre_2024.yaml as "2023 pre-TCJA eligibility recomputes a supplied federal qualifying-child flag": child 6, ctc_qualifying_child: false supplied, so ny_ctc_pre_2024_eligible true and ny_ctc_pre_2024 330.

5 (P3). Body. PATCHed from a file and read back. Content matches my file. The only difference is the attribution footer, which the server rewrote. Changes:

  • The rule paragraph replaced with finding 1's rule, including "for a caller who supplies ctc or eitc, nothing changes from main".
  • The two "updated" bullets dropped.
  • "Fix" names the kept plain inputs per selection.
  • Tests section, mutant table and counts replaced with this round's.
  • "Follow-up (identical on main)" added. I read both pins: _pin_2020_irc (pinned_tbs.py:107-172) pins credits.eitc, .ctc, .cdcc and section 129 to 2020-01-01 for 2021, while _pin_pre_arpa_eitc (:62-93) restores the 2021 indexed max, phase_out.start and joint_bonus. With 2021 parameters 3,618 and 19,520: 3,618 − 0.1598 × 21,480 = 185.50, so the deduction is 380 − 185.50 = 194.50.
  • impact: and axiom: lines kept.

Changelog fragment: "Keep inputs" → "Keep plain inputs".

Tests at the final head (one file per process)

File Result
ny_eitc.yaml 15 passed
al_federal_income_tax_deduction.yaml 10 passed
ny_ctc.yaml 19 passed
ny_ctc_pre_2024.yaml 17 passed
ny_supplemental_eitc.yaml 9 passed
nyc_eitc.yaml 3 passed
nyc_income_tax_elimination_credit.yaml 19 passed
contrib ny_a04038_enhanced_escc_infants.yaml 5 passed
contrib ny_working_families_tax_credit.yaml 13 passed
contrib al_eitc.yaml 5 passed
code_health/test_source_conflicts.py 103 passed

make format: clean (ruff check passed). It also reformatted the unrelated docs/usage/social-security-pia-ratio.md, which I reverted.

Mutants

Each mutant ran in its own process on ny_eitc, ny_ctc_pre_2024, al_federal_income_tax_deduction and ny_ctc. The file was restored after each one, confirmed by cmp.

  • (a) returns before dropping: killed.
    • ny_eitc: "…without ARPA after the ARPA credit" (373.10 vs 120.15) and "…when the ARPA credit is supplied" (150 vs 1,020).
    • ny_ctc_pre_2024: "…recomputes the federal CTC after the current-law CTC" (633.60 vs 330) and "…supplied federal qualifying-child flag" (eligible 0 vs 1).
    • AL: 5 cases, including both take-up cases and "NIIT survives the line-6 floor" (0 vs 120).
    • ny_ctc: "2025 - Updated CTC disabled vs enabled comparison (young child)" (100 vs 330).
  • (b) deletes plain inputs too (main's rule): killed.
    • ny_eitc: "2021 NY EITC is zero for a parent who does not take up the federal EITC" (1,020 vs 0).
    • AL: "…no EITC take-up - Part II recomputes no EITC" (258.87 vs 380).
    • Both CTC files pass, as expected: their selection has no plain input.
  • (c) deletes nothing the caller supplied (previous head's rule): killed.
    • ny_eitc: "…when the ARPA credit is supplied" (150 vs 1,020).
    • ny_ctc_pre_2024: "…supplied federal qualifying-child flag".
    • AL: "NIIT survives the line-6 floor" (0 vs 120).
    • ny_ctc: "2025 - Updated CTC disabled…" (100 vs 330).

CI

Pull request run 41545073963 at 65b1bf6216: still in progress 60 minutes after the push (00:25 UTC). 32 of 34 jobs passed. They include PR validation, Household API Partners, Quick Feedback, Package and compatibility, every Baseline shard (irs, reform, household, ssa-usda, rest-a, rest-b, contrib-hhs, states 1-4), Microsimulation, Python shards 1-7, and Contrib congress, states 1-4, other-2a and other-2b. Still running: Contrib other-shard-1 and other-shard-3, both started 23:56 UTC. No job has failed.

Judgment calls

  • I added one case the brief didn't list: ny_eitc.yaml "2021 NY EITC recomputes the federal credit when the ARPA credit is supplied", the review's own witness (supplied eitc 500 gives 1,020). Without it, mutant (c) survived every ny_eitc.yaml case, because no NY EITC case supplied eitc.
  • I didn't move the deleted file's supplied-ctc: 0 2023 test. The restored 2025 ny_ctc.yaml case covers the same branch and the same rule.
  • The two ny_ctc_pre_2024.py / _eligible.py comments say nothing about plain inputs, since that selection has none.
  • No microsimulation was run, and I measured no impact.

Proposed follow-up

Alabama 2021 Part II: use 2021-indexed EITC amounts in the 2020-IRC pin. _pin_2020_irc pins the EITC maximum and phase-out start to 2020 values for TY2021. The pre-ARPA NY pin keeps the Rev. Proc. 2020-45 indexed 2021 amounts instead. Decide which reading Act 2022-37 intends (the new AL case would go from 258.87 to 194.50), and register it if the sources conflict.


Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Hub review in progress

Round 2 review of 65b1bf6216700ee2835972151eb1574d731ca152 (head unchanged since the hand-off). Interim findings so far:

  • CI run 41545073963 finished: 34 of 34 jobs succeeded, including Contrib other-shard-1 and other-shard-3.
  • The defect reproduces at main (0d933918): a NY 2021 non-taker gets ny_eitc 1,020, and an AL 2021 non-taker gets 258.87. At this head they get 0 and 380, whatever order eitc, ny_eitc and al_federal_income_tax_deduction run in, including both in one simulation.
  • Nested NY CTC branch (2020, charitable ALD, so get_ny_recomputed_branch returns a branch): ny_ctc_pre_2024 is 330 in every order tried. The root and recomputed branches keep their values, including a supplied ctc of 4,000.
  • No primary source is reachable from this VM (irs.gov, revenue.alabama.gov, the NY sites, govinfo, uscode, ecfr, archive.org).

Still to do: rerun the mutants, run the YAML files, the 2024/2026 witnesses, and the final write-up.


Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Ready for hub review
VERDICT: APPROVE
65b1bf6
METHODOLOGY_CHOICE: yes

Independent review, round 2. The head has not moved since 65b1bf6216 (fix commit 65b1bf6216 on top of merge commit 8edf0496fb, which merges main f8142b0223). I compared it with main at 0d933918a8. Main has moved two commits past the PR's base, and neither commit touches the four formula files or period_branch.py.

Each round 1 finding is resolved, and the new rule holds in every order, branch nesting and year I tried. Nothing blocks the PR. Two P3 notes follow, plus a follow-up that is identical on main.

Findings

P3. The impact: line says nothing about 2021, the one year this PR moves outputs

The body ends with impact: none for 2022 and later (plain inputs matter only in TY2021, ...). The round 2 brief asked for impact: the hub will measure to be kept. The current line is correct as far as it goes: 80 witnesses for 2022, 2024, 2026 and 2028 match main exactly (see below). But it never says that 2021 outputs move, and 2021 is the only year that needs the population pair. Suggested replacement:

impact: TY2021 only. NY (`ny_eitc`, `ny_supplemental_eitc`, `ny_income_tax`) and AL (`al_federal_income_tax_deduction`, `al_income_tax`) change for tax units whose `takes_up_eitc` is false or whose `eitc_passive_income_also_in_earned_income` is supplied. 2022 and later: none (the hub will measure 2021).

P3 / follow-up (identical on main). Alabama 2020-vintage EITC amounts: worth a source-conflict check

_pin_2020_irc (policyengine_us/tools/pinned_tbs.py) pins every gov.irs.credits.eitc parameter to its 2020-01-01 value for 2021. For the new Alabama case, that gives 3,584 − 15.98% × (41,000 − 19,330) = 121.13 and a deduction of 258.87. _pin_pre_arpa_eitc keeps the 2021 indexed max, phase_out.start and joint_bonus. That reading gives 3,618 − 15.98% × (41,000 − 19,520) = 185.50 and 194.50. I checked all of these figures against eitc/max.yaml and eitc/phase_out/start.yaml, and the 15.98% one-child rate from the parameter tree. One argument favors indexed amounts. The pinned code's own quote says "Internal Revenue Code in effect on December 31, 2020". That IRC includes the section 32(j) indexing, and Rev. Proc. 2020-45 applied that indexing to 2021 before ARPA. The code's comment, though, cites the worksheet's "based on 2020 Form 2441 Line 11" for the CDCC. If the worksheet also tells filers to use 2020 EIC tables, the agency's form departs from the statute, and per CONTRIBUTING.md that needs a source-conflict entry.

I couldn't reach any source (see below). The hub should read:

  1. Act 2022-37 (HB 231, 2022 RS), enrolled text: the operative sentence "as if the individual paid the federal income tax that would otherwise have been paid under the provisions of the Internal Revenue Code in effect on December 31, 2020". Check whether it says anything about inflation adjustments or which year's tables apply.
  2. The ADOR 2021 "Federal Income Tax Deduction Worksheet", Part II, and its instructions (ADOR's ARPA tax relief page links to it). Check the lines for the 2020-law EIC, CTC and CDCC. Third-party summaries say these are lines 3, 4, 8b and 9e. Look for whether they point to 2020 or 2021 EIC tables or amounts.

If the worksheet uses 2020 tables and the Act is silent, follow the statute: the indexed amounts. Register the conflict and change _pin_2020_irc in a separate PR. If both point to 2020 amounts, keep the code and cite the worksheet. A proposed issue: "AL 2021 Part II: 2020-vintage vs 2021-indexed EITC amounts in _pin_2020_irc", with this household as the witness (258.87 now, 194.50 under the indexed reading).

Round 1 findings, checked at this head

  • P1 (supplied ctc/eitc replaced the recomputation): fixed. period_branch.py:180-194 deletes selected variables that have formulas, adds or subtracts, and keeps the rest. Witnesses, run at this head and at main with the same script:
    2024 NY, $30,000 parent, child 5, ctc=4000 supplied: ny_ctc 330 (PR) | 330 (main)
    2023 NY, ctc_maximum=4000 supplied:                  ny_ctc 330 (PR)
    2023 NY, non_refundable_ctc=3000 supplied:           ny_ctc 330 (PR)
    2021 NY eitc=500 supplied: covered by the new ny_eitc.yaml case (1,020); mutant (c) gives 150
    
  • P2 (restored inputs): fixed. git diff origin/main 65b1bf6216 -- .../ny_ctc.yaml is empty. The AL "NIIT survives the line-6 floor" case has eitc: 200 again (line 93), and the expected 120 is unchanged.
  • P3 (order-dependent cases): fixed. ny_eitc.yaml and ny_ctc_pre_2024.yaml each list the current-law credit first. Mutant (a) kills both (below).
  • P3 (Python test file): fixed. tests/core/test_credit_recompute_branches_keep_inputs.py doesn't exist at the head, and the PR's file list doesn't include it, so it adds no CI cost. Its ctc_qualifying_child case moved to ny_ctc_pre_2024.yaml.
  • P3 (stale hand-off): fixed for draft status. The round 2 hand-off said CI was still running. It has since finished green (below).

Checked and sound

  • The defect, at main 0d933918 (NY 2021, parent 35 with $10,000 wages and a child aged 3; AL 2021, head of household with $41,000 wages and a child aged 3):
    main  NY non-taker: eitc 0, ny_eitc 1020 (either order) | PR: ny_eitc 0 (either order)
    main  AL non-taker: al_federal_income_tax_deduction 258.87 | PR: 380
    takers: ny_eitc 1020, AL 258.87 on both
    
  • Order, including both pinned credits in one simulation. Two households in one simulation (the NY one above and the AL one above), all four orders of eitc, ny_eitc and al_federal_income_tax_deduction, with take-up on and off. At the PR, every order gives takers ny_eitc 1,020 / AL 258.87 and non-takers 0 / 380, and eitc reads 3,400 / 185.50 (or 0 / 0) afterwards. At main, the non-taker rows read 1,020 / 258.87 in every order. Reverse order within NY 2021 (ny_eitc before eitc, and the other way round): the PR gives the same values in both orders.
  • Where the deletion reaches (core 3.33.2). I read Simulation.delete_arrays (simulations/simulation.py:2425). It loops holder.delete_arrays(period, branch_name) over self._get_visible_branch_names(), on the branch's own holders, then clears the branch's _fast_cache. get_branch gives each branch "its own index of this simulation's arrays", and its docstring says "nothing done through the branch changes this simulation's values". Nested case: NY 2020, $30,000 parent with $500 of cash gifts (charitable ALD 300) and a child aged 5, so get_ny_recomputed_branch(..., keep_reported_tax_and_eitc=True) returns a branch and pre_tcja_ctc branches from it. I tried 8 orders mixing ny_ctc, ny_ctc_pre_2024, ny_ctc_pre_2024_eligible, ctc, refundable_ctc, non_refundable_ctc, ctc_limiting_tax_liability, eitc, ny_eitc and ny_income_tax, each with and without a supplied ctc of 4,000. Every order gives ny_ctc_pre_2024 330 (pre-TCJA 1,000, tax as reported 1,105, so 1,000 nonrefundable; 33%). Afterwards, the root still reads ctc 2,000 (or 4,000), eitc 1,878.93 and AGI 29,700. The recomputed branch still reads AGI 30,000, charitable_contributions_ald 0, the supplied ctc 4,000 and eitc 1,878.93. The pre_tcja_ctc branch reads ctc 1,000 and AGI 30,000. All 16 result lines match main exactly (diff empty).
  • The rule's split. Enumerated with CountryTaxBenefitSystem(): NY EITC selection 109 variables, AL 298, NY CTC 92. The plain inputs are exactly takes_up_eitc and eitc_passive_income_also_in_earned_income for NY EITC and AL, and none for NY CTC. The predicate formulas or adds or subtracts matches not is_input_variable() for every selected variable (0 mismatches). Formula variables are deleted exactly as main deleted them, so the PR differs from main only by keeping those two plain inputs.
  • Same names on two paths. capital_gains_responses.py:210 and behavioral_response_measurements.py:92 keep drop_inherited_values, which is right for them. The capital-gains branch keeps the tax-benefit system and only raises long_term_capital_gains, so a supplied formula-variable value is still the caller's fact under the same rules. The behavioural branch swaps to the baseline policy, and keeping supplied values matches how a reform simulation treats supplied inputs. Neither needs the new rule.
  • Impact reading (the hub's none for 2022+). The gates I read: ny_eitc branches only when eitc.decoupled (true 2021-01-01, false 2022-01-01). The AL formula returns part_i when period.start.year != 2021. The NY CTC selection has no plain input. I tried to break that reading with 80 witnesses: 2022, 2024, 2026 and 2028; NY and AL; $10,000 and $30,000 wages with a child aged 5; take-up default, take-up false, supplied ctc 4,000, supplied eitc 500, and take-up false with supplied ctc. Outputs: ctc, eitc, ny_ctc, ny_eitc, ny_supplemental_eitc, ny_income_tax, al_federal_income_tax_deduction, al_income_tax and household_net_income. Main and the PR give identical output (diff empty). Example, 2024 NY $30,000: ny_ctc 330, ny_eitc 889.90, household_net_income 33,412.33 on both.
  • Hand values, computed from parameter files before I read the YAML.
    • NY 2021 childless, $9,000: ARPA 15.3% × 9,000 = 1,377 ≤ 1,502. Pre-ARPA (pre_arpa/childless_max 543, childless_phase_out_start 8,880, 2020 rate 7.65%): 543 − 9.18 = 533.82; × 30% = 160.15; − 40 = 120.15. The 40 is NY tax: (9,000 − 8,000) × 4%. ny_household_credit is the applied credit, which is the potential 45 (household_credit/single.yaml, $7,000–$20,000 bracket) capped at that tax.
    • NY 2021 one child, $10,000: 34% × 10,000 = 3,400 ≤ 3,618; × 30% = 1,020.
    • AL: 41,000 − 18,800 = 22,200; 1,420 + 960 = 2,380; line 6 = 380; 2020-rule EITC 121.13; deduction 258.87, or 380 for a non-taker.
    • NY 2023 CTC: 30,000 − 20,800 = 9,200; tax 920; pre-TCJA 1,000 (920 nonrefundable + min(80, 15% × 27,000)); 33% = 330 ≥ $100; 2023 minimum age 0.
  • Tests at the head, one file per process: ny_eitc.yaml 15 passed; al_federal_income_tax_deduction.yaml 10 passed; ny_ctc_pre_2024.yaml 17 passed; ny_ctc.yaml 19 passed. The new cases assert the claim itself: the federal credit is listed first, then the pinned output. They aren't generated ranges, so no boundary question arises.
  • Mutants of drop_recomputed_values, each run on a scratch worktree in its own process, restored after (git status clean at the end):
    • (a) return before dropping. Killed: ny_eitc 2 cases ("…after the ARPA credit", "…when the ARPA credit is supplied"); AL 5; ny_ctc_pre_2024 2 ("…after the current-law CTC", "…supplied federal qualifying-child flag"); ny_ctc 1 ("2025 - Updated CTC disabled vs enabled…").
    • (b) delete every selected name (main's rule). Killed: ny_eitc "…does not take up the federal EITC"; AL "…no EITC take-up - Part II recomputes no EITC". Both CTC files pass, as expected with no plain input.
    • (c) the previous head's code (drop_inherited_values on the selection). Killed: ny_eitc "…when the ARPA credit is supplied"; AL "NIIT survives the line-6 floor"; ny_ctc_pre_2024 "…supplied federal qualifying-child flag"; ny_ctc "2025 - Updated CTC…".
    • (d), mine: delete only variables with formulas, so adds-only variables such as ctc_maximum and non_refundable_ctc escape. Killed: AL 4 cases (including both take-up cases and the $9,000 dependent-care case); ny_ctc_pre_2024 "…after the current-law CTC". It survives ny_eitc.yaml and ny_ctc.yaml. Witness under (d): 2023 NY with ctc_maximum 4,000 supplied gives ny_ctc 633.60, against 330 at the head.
  • Readers. ny_eitc: ny_supplemental_eitc, gov.states.household.state_eitcs, NY refundable.yaml. The contrib WFTC reform replaces ny_eitc with its own formula, which doesn't branch. al_federal_income_tax_deduction: al_deductions. ny_ctc_pre_2024: ny_ctc. ny_ctc_pre_2024_eligible: ny_ctc_pre_2024. The only new reader is drop_recomputed_values, called by the four formulas. No reader is made worse.
  • Body and docs. I checked each statement in "Fix", "Tests", the mutant table and "Follow-up" against code or runs, and found none false. The impact: and axiom: lines are present. The changelog fragment is a top-level changelog.d/*.fixed.md.
  • CI. Run 41545073963 ("Pull request", at 65b1bf6216) completed with success: 34 of 34 jobs succeeded, including Contrib other-shard-1 and other-shard-3, Household API Partners, Microsimulation and every Baseline, Contrib and Rest Python shard. No job was cancelled or skipped.
  • Scope. The files are the changelog fragment, three YAML files, period_branch.py and the four formula files. Nothing is under .github/ or tests/policy/baseline/partners/. Open PRs that mention the changed files: Add Arkansas's claim of right deduction (Ark. Code § 26-51-457) #10203 edits the period_branch.py docstring, and git merge-tree auto-merges that file with this head (Add Arkansas's claim of right deduction (Ark. Code § 26-51-457) #10203's 8 conflicts are with main already). Fix the 2021 CTC split and surviving-spouse phaseout threshold #10306 matches only on text.
  • Baseline movement. Outputs can move at baseline only in TY2021, for NY and AL tax units with takes_up_eitc false or eitc_passive_income_also_in_earned_income supplied. That calls for a 2021 population pair on ny_eitc, ny_supplemental_eitc, ny_income_tax, al_federal_income_tax_deduction, al_income_tax, state_income_tax and household_net_income, and none for 2022+.

What I couldn't check

  • Primary sources. From this VM, irs.gov, revenue.alabama.gov, alison.legislature.state.al.us, nysenate.gov, legislation.nysenate.gov, tax.ny.gov, govinfo.gov, uscode.house.gov, ecfr.gov, web.archive.org, law.cornell.edu and drakesoftware.com all failed: DNS failure or a proxy 403. I confirmed no statute or form text. Every legal statement here rests on the code's comments and the parameter files. That includes Tax Law 606(d) "allowed" versus "claimed", which is why METHODOLOGY_CHOICE stays yes: the PR treats a federal non-claimant as having no NY 2021 EITC. It also includes the Act 2022-37 reading above.
  • Population file. I couldn't open populace_us_2024.h5, so I can't name which formula-variable columns it stores within these selections. Candidates from the enumeration that a dataset might carry are ctc_qualifying_child, is_eitc_qualifying_child, the meets_*_identification_requirements and filer_meets_* variables, and dependent_care_assistance_exclusion. Whatever is stored, the branch discards those values just as main did, so they can't produce a main-to-PR difference. Only takes_up_eitc (stored on tax_unit, per the brief) and eitc_passive_income_also_in_earned_income (not stored, per the brief) can.
  • I didn't rerun ny_supplemental_eitc.yaml, the NYC files, the contrib files or test_source_conflicts.py. CI's shards passed them at this head.

Generated by Claude Code

@MaxGhenis
MaxGhenis merged commit c0a4cf4 into main Nov 3, 2026
34 checks passed
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

US + core hub merge audit, us#10587 at 7b73d42e244830174d2b7d10e67b9f5ecdd195cd (squash): Keep inputs when NY and AL recompute federal credits under pinned rules.

  • Review: cloud review r1 asked for changes. After the fix round, the delta review (Opus 5.5, independent) approved at 65b1bf6216 with P3 notes only: it checked each round 1 finding at the head, ran every order of the pinned credits in one simulation and 8 orders across nested branches, and its four mutants of drop_recomputed_values died. The hub applied its impact wording.
  • Branch update: GitHub's branch update merged main 6fa4eaf6d3 into the reviewed head 65b1bf6216; each file's changes match the reviewed head's.
  • Gates checked live at merge:
    • gh pr checks exits 0;
    • the latest Pull request run at the head concluded success;
    • MERGEABLE;
    • not a draft;
    • no reviewer's latest review requests changes;
    • the semantic-overlap guard;
    • head pinned.
  • Impact: TY2021 only, for NY and AL tax units that don't take up the federal EITC. None from 2022: the review's 80 witnesses for 2022-2028 match main, so the hub's 2026 population run can't move.
  • Axiom: n/a (branch plumbing: no provision's reading changes; take-up is an input)

@MaxGhenis
MaxGhenis deleted the credit-recompute-branches-keep-inputs branch November 3, 2026 05:03
MaxGhenis pushed a commit that referenced this pull request Nov 3, 2026
Picks up #10587's Alabama federal-credit recomputation change before the final P3 round.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6H28mCRxTXCRraPQpH
MaxGhenis pushed a commit that referenced this pull request Nov 3, 2026
…-wages

Brings in #10587's Alabama cases and later main commits before the
round-3 fixes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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