Skip to content

Fix the 2021 CTC split and surviving-spouse phaseout threshold - #10306

Open
MaxGhenis wants to merge 4 commits into
mainfrom
ctc-2021-refundable-split
Open

MaxGhenis wants to merge 4 commits into
mainfrom
ctc-2021-refundable-split

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

WIP: hub impact and parity are pending.

The 2021 fully refundable CTC omitted the first ARPA reduction and could refund credit for other dependents (ODC). This corrects the Schedule 8812 split, including a solar-credit ordering case that previously overstated the federal refund by $500.

Using child-only credit C, ODC O, the capped first ARPA reduction F, and ordinary phaseout P:

  • Total before the tax limit: T = max(C − F + O − P, 0) (line 12).
  • Refundable: R = max(C − F − P, 0) (line 14b).
  • Nonrefundable: N = min(O, T) (line 14a), retained before the §26(a) tax limit applied on line 14d.

The first reduction retains its caps at the enhancement above $2,000 per qualifying child and 5% of the ordinary-minus-ARPA threshold difference. The partial-refundability branch and existing refundable-maximum guard are preserved. The ordinary phaseout threshold for surviving spouses is also corrected to $200,000 from 2018 onward; their separate 2021 ARPA threshold remains $150,000.

Sources: 2021 IRC §24(h) and (i), PDF pages 57–58, 2021 Schedule 8812, page 1, and Line 5 Worksheet, instructions page 4.

Implementation plan:

  • Correct the refundable split and document pre-limit nonrefundable entitlement.
  • Add the three U5 regressions: ARPA plus ODC, the floor after ordinary phaseout, and solar ordering.
  • Add vectorized partition, child-only refund, and other-year compatibility properties.
  • Add Oklahoma binding-tax-limit coverage and surviving-spouse threshold/cap tests.
  • Review downstream consumers and update obsolete Iowa comments while preserving its calculation.
  • Finish validation and record final counts below.
  • Hub: run the impact analysis and complete parity.

Validation: 94 YAML cases passed; 10/37 required files finished successfully. 7 Python property tests passed. Recorded: 1 process interrupted at the wall-time guard. The remaining Oklahoma file printed a nine-pass pytest summary, then its process was interrupted during cleanup after the wall clock advanced about 18 minutes during a 50-second wait. No numerical test failure was reported; its normal process exit was not verified. The three supplied U5 cases failed on unfixed main and pass with this change. Formatting and Ruff checks pass.

The local run budget did not cover a complete passing run; the hub must finish the listed validation.

27 required files unfinished
  • policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/maximum/individual/ctc_child_individual_maximum.yaml — pending
  • policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/maximum/individual/ctc_child_individual_maximum_arpa.yaml — pending
  • policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/maximum/individual/filer_meets_child_ctc_identification_requirements.yaml — pending
  • policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/maximum/individual/filer_meets_ctc_identification_requirements.yaml — pending
  • policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/maximum/individual/meets_ctc_child_identification_requirements.yaml — pending
  • policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/non_refundable_ctc.yaml — pending
  • policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/phase_out/arpa/ctc_arpa_addition.yaml — pending
  • policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/phase_out/arpa/ctc_arpa_max_addition.yaml — pending
  • policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/phase_out/arpa/ctc_arpa_uncapped_phase_out.yaml — pending
  • policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/phase_out/ctc_phase_out.yaml — pending
  • policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_credit_limit_worksheet_b_applies.yaml — pending
  • policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_limiting_tax_liability.yaml — pending
  • policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_non_refundable_minimum.yaml — pending
  • policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_phase_in.yaml — pending
  • policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_phase_in_relevant_earnings.yaml — pending
  • policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_refundable_maximum.yaml — pending
  • policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_social_security_tax.yaml — pending
  • policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_social_security_tax_dependents.yaml — pending
  • policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/ctc_tax_liability_after_preceding_credits.yaml — pending
  • policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/refundable/refundable_ctc_barred_by_section_911_exclusion.yaml — pending
  • policyengine_us/tests/policy/baseline/gov/states/ok/tax/income/ok_child_care_child_tax_credit.yaml — interrupted
  • policyengine_us/tests/policy/contrib/ctc/ctc_additional_bracket.yaml — pending
  • policyengine_us/tests/policy/contrib/ctc/ctc_minimum_refundable_amount.yaml — pending
  • policyengine_us/tests/policy/contrib/ctc/ctc_older_child_supplement.yaml — pending
  • policyengine_us/tests/policy/contrib/ctc/ctc_per_child_phase_in.yaml — pending
  • policyengine_us/tests/policy/contrib/ctc/ctc_per_child_phase_out.yaml — pending
  • policyengine_us/tests/policy/contrib/ctc/integration.yaml — pending

YAML files run separately through Core's official test runner in sequential foreground processes. No microsimulation was run; the hub handles impact analysis.

axiom: TheAxiomFoundation/rulespec-us#1856 queued for §24(i)(1)(B), (i)(3) and (i)(4), with surviving-spouse companion rows for the 24(h)(3) threshold (us:statutes/26/24/h#ctc_phase_out_threshold_under_subsection_h; no test yet runs a surviving spouse), blocked on ingesting P.L. 117-2 §9611 (TheAxiomFoundation/axiom-corpus#744), a re-pin to a release with the 2021 Schedule 8812, and the derived filing-status chain (TheAxiomFoundation/rulespec-us#1399).

@MaxGhenis MaxGhenis changed the title Fix the 2021 refundable child tax credit split Fix the 2021 CTC split and surviving-spouse phaseout threshold Oct 20, 2026
@MaxGhenis
MaxGhenis marked this pull request as ready for review October 20, 2026 23:34
@DTrim99

DTrim99 commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator

PR Review: Fix the 2021 CTC split and surviving-spouse phaseout threshold

PR #10306 (author: MaxGhenis), head cffa308032. This is a first review. The PR makes three changes:

  • The 2021 refundable split. In 2021 the refundable CTC now subtracts the first ARPA reduction as well as the ordinary phaseout. So the credit for other dependents (ODC) stays nonrefundable.
  • The surviving-spouse threshold. It sets the ordinary phaseout threshold for surviving spouses to $200,000 from 2018 (it was $400,000).
  • Documentation and tests. It documents non_refundable_ctc as the amount before the §26(a) limit and rewords two Iowa comments. It adds 15 YAML cases across five files, a seven-test Python grid and a changelog fragment.

The PR has no comments or reviews yet. It touches no partner test file.

Path prefixes:

  • V/ = policyengine_us/variables/gov/irs/credits/ctc/
  • T/ = policyengine_us/tests/policy/baseline/gov/irs/credits/ctc/

Sources read for this review (all fetched 2026-10-21):

Summary

What is right.

  • The law.
    • First reduction. §24(i)(4)(A) reduces "the credit allowable under subsection (a) (determined without regard to subsection (b))" by "$50 for each $1,000 (or fraction thereof)" above the applicable threshold. Under (i)(4)(B) that is "$150,000, in the case of a joint return or surviving spouse (as defined in section 2(a))", $112,500 for a head of household and $75,000 otherwise.
    • Its cap. Under (i)(4)(C), the reduction may not exceed "the lesser of (I) the applicable credit increase amount, or (II) 5 percent of the applicable phaseout threshold range". The increase amount is the $3,000/$3,600 enhancement over the $2,000 credit. The range runs from the (b)/(h)(3) threshold down to the (i)(4) threshold.
    • Order. (i)(4)(D) applies subsection (b) to "the credit allowable under subsection (a) (determined after the application of subsection (i)(4)(A)". So the ordinary $400,000/$200,000 phaseout comes second.
    • Refundable part. §24(i)(1)(B) allows under subpart C "so much of the credit determined under subsection (a) ... as does not exceed the amount of such credit which would be so determined without regard to subsection (h)(4)", that is, without the ODC.
    • The form. Schedule 8812 implements this as line 12 = line 5 (child credit after the first reduction) + line 7 (ODC) − line 11 (ordinary phaseout). Then "14a Enter the smaller of line 7 or line 12" and "14b Subtract line 14a from line 12".
  • The code matches the law and the form.
    • Total. ctc = max(0, base + ODC + (ARPA enhancement − F) − P), where F is the first reduction and P the ordinary phaseout. That is line 12.
    • Refundable. In 2021, refundable_ctc = min(max(0, C − F − P), ctc), where C is the sum of ARPA child amounts (ctc_refundable_maximum). That equals line 14b in every branch: if C − F − P ≥ 0, then 14a = ODC and 14b = C − F − P; otherwise 14a = line 12 and 14b = 0.
    • Nonrefundable. non_refundable_ctc = ctc − refundable_ctc is line 14a.
    • The bug fixed. The old code left out F, so it refunded up to F of the ODC.
  • Surviving spouses.
    • The law. §24(h)(3): "$400,000 in the case of a joint return ($200,000 in any other case)". Before 2018, §24(b)(2)(B) gives $75,000 to "an individual who is not married", and the parameter already had that.
    • The forms. Schedule 8812 line 9 reads "Married filing jointly—$400,000 / All other filing statuses—$200,000". The Line 5 Worksheet uses "Qualifying widow(er) — $2,500" (line 6) and "Married filing jointly or Qualifying widow(er) — $150,000" (line 8).
    • The PR. It gives a surviving spouse $200,000 from 2018 and keeps the $150,000 ARPA threshold. So the 2021 cap becomes 5% × $50,000 = $2,500 instead of $12,500.
  • Rounding. Both reductions use ceil(excess / 1_000) * 50 on MAGI including the §911/931/933 exclusions, as §24(b)(1) requires.
  • Other years. The refundable change sits inside if ctc.refundable.fully_refundable, which is true only for 2021. The surviving-spouse change applies from 2018, as the law requires. The Python grid confirms the legacy §24(d) branch is unchanged for 2018, 2020, 2022, 2025 and 2026.
  • Tests. I hand-checked 8 cases (about 35 expectations), listed under Verified correct. CI passed every changed file.
  • Batch checks. On the tree merged with main, the PR passes the source-conflict ratchet (checked statically). It has no state-gated key and changes no income-source list.

There are no critical findings and three should-address items:

  • S1. Invalid Axiom line. axiom: pending: hub files parity is not an allowed form. We flagged the same form on Correct Minnesota UI taxable wage bases for 2014-2026 #10319 and Fix CalEITC child counts for ITIN holders #10322. rulespec-us doesn't encode §24(i), and no open issue covers (i)(1)(B) or (i)(4).
  • S2. No boundary or joint-return coverage of the new split. No new case sits on an (i)(4) threshold, where the old code first went wrong. None is a joint return, so the $150,000 and $400,000 joint thresholds are never tested together with an ODC.
  • S3. Iowa's 2021 add-back of non_refundable_ctc is now an obsolete workaround. It makes Iowa's federal-tax deduction ignore the ODC. Where §26(a) limits the ODC, the PR now makes Iowa add back more than was netted. That contradicts the PR body's claim that Iowa's calculation is preserved.

There are also three suggestions: the Python test, references and comment housekeeping, and the stale PR body.

Status.

Critical (must fix)

None.

Should address

S1. The Axiom line axiom: pending: hub files parity is not a valid form

  • Where: the last line of the PR body.
  • The rule (CLAUDE.md, "Axiom Parity"): a PR that adds, updates or fixes policy needs one of encoded-correct, <rulespec PR> encoded, <rulespec issue> queued or n/a: <reason>. pending is not one of them.
  • Why n/a doesn't fit: §24(i) and §24(h)(3) are statutory. The PR changes 2021 refunds for families with other dependents above an ARPA threshold. It also changes the 2018+ CTC for surviving spouses with MAGI between $200,000 and $400,000.
  • Why encoded-correct isn't available for §24(i):
    • rulespec-us main (5796861e58) has no us/statutes/26/24/i module. The 24.yaml summary covers subsection (h) and never mentions (i).
    • Remove calculated variables from input/income.py #1829 (open, pe-parity) mentions §24(i) only for (i)(1)(A), which turns off the §24(d)(3) bar. It doesn't cover (i)(1)(B), (i)(3) or (i)(4).
    • My searches for "24(i)", "2021 child tax credit", "ARPA child", "Schedule 8812" and "credit for other dependents" found nothing else.
  • §24(h)(3) is already encoded. us:statutes/26/24/h#ctc_phase_out_threshold_under_subsection_h returns $400,000 when filing_status == 1 and $200,000 otherwise, from 2018-01-01. That gives a surviving spouse $200,000, provided code 1 means a joint return only. Its companion tests have no surviving-spouse case, and Add hide-input cell tags #1399 notes that filing status is not yet derived upstream.
  • Severity: should. An invalid Axiom line is a merge blocker, not a model error.
  • Fix: file an open, pe-parity-labelled, dispatch-ready issue in TheAxiomFoundation/rulespec-us, modelled on Add AMT parameters for 2020-2023 #1467. Then change the line to axiom: us:statutes/26/24/h#ctc_phase_out_threshold_under_subsection_h encoded-correct; TheAxiomFoundation/rulespec-us#<n> queued. Alternatively, run the signed encoder and cite the PR as encoded. The issue needs:
    • Target module: a new us/statutes/26/24/i.yaml with i.test.yaml. It should feed the 24.yaml credit for taxable years beginning after December 31, 2020 and before January 1, 2022.
    • Corpus citation: us/statute/26/24, subsection (i), 2021 edition (ARPA, Pub. L. 117-2, §9611).
    • Verbatim law: (i)(1)(B), (i)(3) and (i)(4)(A)-(D), as quoted under Summary.
    • Required outputs:
      • the applicable threshold amount ((i)(4)(B));
      • the applicable credit increase amount ((i)(4)(C)(ii));
      • the applicable phaseout threshold range ((i)(4)(C)(iii));
      • the (i)(4) reduction;
      • the credit after (i)(4) and (b);
      • the part allowed under subpart C under (i)(1)(B), and the remainder (the ODC) left in subpart A.
    • Companion tests from the 2021 Schedule 8812 and Line 5 Worksheet, not from PolicyEngine output. This is the same source as the PR's YAML. All three cases have one qualifying child under 6, and the first two add one other dependent:
      • HoH, MAGI $170,000. Worksheet line 11 is $1,600; Schedule 8812 line 5 is $2,000, line 12 is $2,500, 14a is $500 and 14b is $2,000.
      • HoH, MAGI $240,001. Line 11 is $2,050, line 12 is $450, 14a is $450 and 14b is $0.
      • Surviving spouse, MAGI $200,001, no other dependent. Worksheet line 6 is $2,500 and line 11 is $1,600; Schedule 8812 line 9 is $200,000, line 11 is $50 and line 12 and 14b are $1,950.
      • For h.test.yaml: a surviving-spouse case with a $200,000 threshold.
    • Why queued: the blocker, such as corpus scope, the signed release or approval for a billed run.

S2. No boundary or joint-return coverage of the new split

  • Where:

    • T/refundable/refundable_ctc.yaml:216-327;
    • T/ctc.yaml:10-36;
    • policyengine_us/tests/policy/baseline/gov/states/ok/tax/income/credits/ok_federal_ctc.yaml:25.
  • What the new cases cover.

    • F values. The cases put the first reduction F at $750 (single, $90,000) or at its $1,600 cap (HoH $170,000, HoH $240,001, surviving spouse $200,001).
    • No threshold case. None sits on an (i)(4) threshold. That is where F first becomes positive and where the old code first went wrong.
    • No joint return. Only ia_integration.yaml exercises the joint $150,000/$400,000 pair, with F equal to the $4,000 enhancement, P = 0 and no ODC. So neither phaseout is tested together with an ODC for a joint return.
  • The Python grid doesn't fill the gap. It supplies ctc_arpa_phase_out and ctc_phase_out as inputs, so it never derives F or P from income at a threshold.

  • Severity: should (missing boundary tests for changed logic).

  • Fix: add household cases like these: 2021, TX, wages only, a child aged 5 and, where shown, a 10-year-old and a dependent aged 19. I computed the values from Schedule 8812 lines 5-14b and the Line 5 Worksheet. The last column shows what the old code would give.

    Case F P ctc refundable_ctc non_refundable_ctc Old code (refundable / nonrefundable)
    HoH $112,500, child 5 + dependent 19 0 0 4,100 3,600 500 same
    HoH $112,501, same family 50 0 4,050 3,550 500 3,600 / 450
    Single $75,001, same family 50 0 4,050 3,550 500 3,600 / 450
    Joint $150,001, children 5 and 10 + dependent 19 50 0 7,050 6,550 500 6,600 / 450
    Joint $400,001, same family 2,600 50 4,450 3,950 500 4,450 / 0
    Joint $480,001, same family 2,600 4,050 450 0 450 450 / 0
    • How the joint rows work. The enhancement is 3,600 + 3,000 − 4,000 = 2,600. That is below the 5% × 250,000 = 12,500 cap, so F is capped at 2,600 at both $400,001 and $480,001. P is 50 × 1 = 50 at $400,001 and 50 × 81 = 4,050 at $480,001.
    • The $112,500 row is the zero side of the boundary. The rows just above each threshold show the split moving by exactly $50.

S3. Iowa's 2021 add-back of non_refundable_ctc is now an obsolete workaround

  • Where: policyengine_us/variables/gov/states/ia/tax/income/including_married_filing_separately/deductions/ia_fedtax_deduction.py:29-37. In fully refundable years it sets us_tax = ustax + non_refundable_ctc.
  • History.
    • Why it was added. Commit 758955bfab (2023-05-05, "Fix ia_fedtax_deduction formula to work around 2021 US CTC flaw") added the add-back. At the time, the federal split gave the ia_integration.yaml family non_refundable_ctc −4,000 and refundable_ctc 12,000. Adding back the negative amount cancelled the error.
    • It is no longer needed. At the merge base the same fixture already shows 0 and 8,000, so the flaw it worked around is gone.
  • What it does at this head. In 2021 non_refundable_ctc is now the ODC before the limit, min(ODC, line 12). income_tax_before_refundable_credits has already netted out the ODC actually applied. Iowa adds the full amount back:
    • When the ODC is fully used, Iowa's deduction ignores the ODC.
      • Below the ARPA thresholds this predates the PR: the old split also gave non_refundable_ctc = ODC.
      • Above them, the PR moves the overstatement out of the federal split and into this line.
    • When §26(a) limits the ODC, Iowa adds back more than was netted.
      • Trace. Take the PR's own Oklahoma household and put it in Iowa: 2021, single, $90,000, a child aged 5, a dependent aged 19, and income_tax_before_credits 300.
      • At this head. CI confirmed non_refundable_ctc 500 and income_tax_capped_non_refundable_credits 300 for that household. So income_tax_before_refundable_credits is 0 and us_tax = 0 + 500 = $500. That is more than the $300 of federal tax before any credit.
      • Before the PR, non_refundable_ctc was 0, so us_tax was $300.
      • The PR body is wrong here. It says the PR preserves Iowa's calculation, but this result changes.
  • Iowa law.
    • The instructions. The 2021 IA 1040 instructions (file page 40) say "Report on line 31 all federal tax withheld, estimated tax payments, and federal taxes paid in 2021". The listed add-backs are Schedule 2 taxes. Nothing on pages 40-41 adds a nonrefundable credit back.
    • The formula's own premise. The formula approximates the payments as the liability: "With payments equal to the liability, the deduction is the federal income tax without the added-back taxes." The ODC reduces that liability, and the formula doesn't add it back in 2020 or 2022.
    • The comment. The new comment ("Retain the existing addback ... including any nonrefundable credit for other dependents in 2021") presents the workaround as an Iowa rule without a source.
  • Severity: should. It's a wrong Iowa result in 2021, but in a state deduction the PR doesn't claim to change, and most of its reach predates the PR. That matches the Fix CalEITC child counts for ITIN holders #10322 S2 precedent.
  • Fix: delete the fully refundable branch (lines 30-37), so that us_tax = ustax in every year.
    • Existing fixture. ia_integration.yaml doesn't move, because its non_refundable_ctc is 0.
    • New case. Add an Iowa 2021 case with an ODC (for example HoH, $170,000, a child aged 5 and a dependent aged 19). Assert non_refundable_ctc 500 and a head ia_fedtax_deduction equal to income_tax_before_refundable_credits less the Schedule 2 add-backs.
    • If you'd rather leave Iowa outputs alone in this PR, add back only the ODC actually applied (ctc_value − refundable_ctc, which is min(ODC, limiting tax)), reword the comment and file a follow-up issue.

Suggestions

G1. The Python grid test

  • The cost is fine. policyengine_us/tests/test_ctc_2021_refundable_split.py ran in 1.42 s in CI (1.22 s setup).
  • CLAUDE.md's "Test design and CI cost" rules still apply:
    • Duplicated formula. test_other_years_preserve_partial_refundability (lines 137-155) restates the legacy refundable_ctc formula and compares the code with itself. The existing refundable_ctc.yaml cases already cover those years.
    • YAML can do this. The 2021 partition checks could be vectorized YAML cases over tax units, with ctc_maximum, ctc_arpa_phase_out and ctc_phase_out as array inputs.
    • Hard-coded amounts. Line 60 writes 3_600/3_000 instead of reading amount.arpa.
    • No measurement note. The PR body doesn't give the measurement or the reason for using Python.
  • Fix (optional): drop the other-years test or move its distinct cases to YAML. Otherwise, add a sentence to the PR body naming the failure only this grid detects, with the CI timing above.

G2. References and comment housekeeping

  • V/phase_out/arpa/ctc_arpa_addition.py:11 still cites the current-year i1040s8.pdf#page=4, which no longer has the Line 5 Worksheet. The PR moved its two siblings (ctc_arpa_max_addition.py, ctc_arpa_phase_out_cap.py) to irs-prior/i1040s8--2021.pdf#page=4. Do the same here.
  • T/refundable/refundable_ctc.yaml:210-211 has leftover scaffolding: "# Proposed additions to:" followed by the file's own path. Delete both lines.
  • Lines 213-214 list the govinfo PDF twice, differing only in #page=. CLAUDE.md treats those as one source. Keep one href (#page=57) and say "PDF pages 57-58".

G3. The PR body is stale

  • Draft language. It opens with "WIP: hub impact and parity are pending" although the PR is not a draft.
  • Open items. Two items are still unchecked ("Finish validation" and "Hub").
  • Stale test list. It lists 27 test files as "pending" or "interrupted". CI has since passed all of them (see CI status).
  • Fix (optional): replace the validation section with the CI result, and remove the WIP line once S1 is settled.

Verified correct

  • Hand-checks (8 cases, about 35 expectations). Unless noted, each is 2021, TX and wages only.
    1. T/refundable/refundable_ctc.yaml:216, HoH $170,000, a child aged 5 and a dependent aged 19.
      • F. Uncapped 50 × ceil(57,500 / 1,000) = 2,900. The caps are $1,600 (the enhancement) and $4,375 (5% × 87,500), so F = 1,600.
      • P = 0. The other expectations are ctc 2,500 (line 12), non_refundable_ctc 500 (14a), refundable_ctc 2,000 (14b) and ctc_value 2,500.
      • Old code: 2,500 refundable.
    2. Same file, line 252, HoH $240,001.
      • F = 1,600 (uncapped 6,400). P = 50 × ceil(40,001 / 1,000) = 2,050.
      • Line 12 = 2,500 − 2,050 = 450, so 14a is 450 and 14b is 0. ctc_value is 450.
    3. Same file, line 290, solar.
      • Tax before credits. Taxable income is 170,000 − 18,800 = 151,200. On the 2021 HoH schedule the tax is 1,420 + 4,800 + 7,073 + 15,564 = 28,857.
      • Solar credit. The potential is 26% × 150,000 = 39,000. The limit is 28,857 − 500 = 28,357, because Worksheet B is off in 2021 and the solar credit follows the ODC (Form 5695 Credit Limit Worksheet). The credit is 28,357.
      • income_tax = 28,857 − 500 − 28,357 − 2,000 = −2,000.
        • Recovery rebate. The 2021 HoH rebate is fully phased out by $120,000.
        • AMT. It doesn't apply: (170,000 − 73,600) × 26% = 25,064, which is less than 28,857.
      • Old code: −2,500, which is the PR body's $500 overstatement.
    4. T/ctc.yaml:10, surviving spouse, $200,001, a child aged 5.
      • F. The ARPA threshold is $150,000. F is uncapped at 50 × 51 = 2,550, capped at $2,500 and $1,600, so F = 1,600.
      • P. The threshold is $200,000, so P = 50. ctc and refundable_ctc are 1,950, and non_refundable_ctc is 0.
      • Old code: P = 0 and ctc 2,000.
    5. T/phase_out/arpa/ctc_arpa_phase_out_cap.yaml.
      • Joint with a $1,600 enhancement: min(12,500, 1,600) = 1,600.
      • Surviving spouse with a $5,000 enhancement: min(5% × (200,000 − 150,000), 5,000) = 2,500, with thresholds 150,000 and 200,000. This matches Line 5 Worksheet line 6.
    6. T/phase_out/ctc_phase_out_threshold.yaml.
      • 2017: [75,000, 110,000] (§24(b)(2)(B) and (A)).
      • 2018 and 2021: [200,000, 400,000], with ARPA thresholds [150,000, 150,000] in 2021.
      • 2026: [200,000, 400,000].
    7. ok_federal_ctc.yaml:25, single, $90,000, a child aged 5, a dependent aged 19, tax $300.
      • F = 50 × 15 = 750 (the caps are $1,600 and $6,250). P = 0.
      • ctc is 3,350, refundable_ctc 2,850 and non_refundable_ctc 500. Capped nonrefundable credits are min(300, 500) = 300.
      • ctc_value = min(3,350, 300 + 2,850) = 3,150. ok_federal_ctc = 300 applied + 2,850 = 3,150 (Credit Limit Worksheet A, binding branch).
    8. ia_integration.yaml (comments only), joint, $252,020, four children aged 11, 11, 11 and 16.
      • C = 12,000, the base is 8,000 and the enhancement 4,000.
      • F. Uncapped 50 × 103 = 5,150, capped at 4,000. P = 0.
      • ctc and refundable_ctc are 8,000, and non_refundable_ctc is 0. That is unchanged.
  • The Python grid. Its 2021 assertions are lines 12, 14a and 14b, plus refundable + non_refundable == ctc. All 7 tests pass in CI.
  • Callers at the head:
    • Solar limit. residential_clean_energy_credit_credit_limit now counts only the ODC as preceding in 2021.
    • Oklahoma. ok_federal_ctc applies the capped amount.
    • Missouri. mo_federal_income_tax_deduction adds back refundable_ctc (line 28) to income_tax. With the ODC now on line 19, the base nets it, as the MO-1040 line 9 worksheet requires.
    • Alabama. The 2021 Part II of al_federal_income_tax_deduction adds back ctc − refundable_ctc and subtracts the recomputed 2020 amount. In the non-binding cases the result is unchanged. In the binding cases I traced, the line-6 zero floor absorbs the difference.
    • New York. The ny_ctc_pre_2024 branch pins every CTC parameter to 2017, so neither change reaches it.
    • Puerto Rico and eligibility. pr_refundable_ctc and eligible_for_refundable_credits read the corrected amounts.
    • Iowa is S3.
    • Threshold readers. The other readers of ctc_phase_out_threshold are contrib reforms, taxsim.py and pr_ctc_phase_out, which follow the corrected surviving-spouse value.
  • References.
    • The govinfo PDF. File pages 57-58 hold §24(h)-(i); page 57 prints "Page 80". File page 81 holds §25D(c).
    • The forms. f1040s8--2021.pdf#page=1 shows line 9, and i1040s8--2021.pdf#page=4 shows the Line 5 Worksheet.
    • Format. The multi-page govinfo href in V/refundable/refundable_ctc.py carries a "PDF pages 57-58" comment, as CLAUDE.md asks. The new threshold reference title ("2021 Schedule 8812, line 9") has no page text.
  • Changelog. changelog.d/ctc-2021-refundable-split.fixed.md is a top-level fragment, and fixed is the right type.
  • Out of scope (pre-existing, not findings):
    • Part I-C. The model gives every 2021 filer the fully refundable path. It doesn't model filers without a US principal place of abode (Part I-C), which Remove calculated variables from input/income.py #1829 tracks on the rulespec side.
    • Advance payments. It doesn't model the §24(j)/§7527A advance payments. On the form, lines 14f-14i take advances from the refundable part first, but PolicyEngine models the annual entitlement, so the 14a/14b split is the right target.
  • Batch lessons:

Axiom line assessment

Invalid: axiom: pending: hub files parity (S1).

  • Recommended form: axiom: us:statutes/26/24/h#ctc_phase_out_threshold_under_subsection_h encoded-correct; TheAxiomFoundation/rulespec-us#<n> queued. The queued issue is a new open, pe-parity-labelled, dispatch-ready issue for §24(i)(1)(B), (i)(3) and (i)(4). The required contents are listed under S1. Alternatively, cite an encoder PR as encoded.
  • Not n/a. These are statutory provisions, and the PR changes 2021 refunds and 2018+ surviving-spouse credits.
  • Not encoded-correct for §24(i). rulespec-us main (5796861e58) has no subsection (i) module. Remove calculated variables from input/income.py #1829 covers only (i)(1)(A).
  • encoded-correct is available for §24(h)(3), provided filing_status == 1 means a joint return only. Its tests lack a surviving-spouse case, so add one in the new issue.

CI status

27 pass, 3 skipping, 0 pending, 0 failed. I re-checked gh pr checks 10306 at the end of this review, and the head is still cffa308032.

  • The green run. Run 39515880371 (pull_request, created 2026-10-20 23:34 UTC) tested merge commit afedcfae. That merges the head into 07eba1aeea, which is main as of 2026-10-20 19:22 UTC. That is before Tie source-conflict markers to the register shared with Axiom #10287 (22:02 UTC) and Count the FL and SD LIHEAP comments in the source-conflict ratchet baseline #10334, so CI's tree did not contain the ratchet. I checked it statically instead (see Verified correct).
  • Where the changed tests ran.
    • Quick Feedback selected and passed: ctc.yaml (2), ctc_arpa_phase_out_cap.yaml (3), ctc_phase_out_threshold.yaml (6), refundable_ctc.yaml (18), ia_integration.yaml (9) and ok_federal_ctc.yaml (6). It also passed the new Python file (7 tests in 1.42 s).
    • Rest (Python, shard-1 + variables) passed the Python file again.
    • The full baseline, contrib, microsimulation and Household API Partners suites passed. That includes every file the PR body lists as unfinished.
  • The skipped checks are the umbrella jobs of run 39489870440 (2026-10-20 20:11 UTC), which the green run superseded.
  • Locally: I ran no tests or model probes. CI is green at the current head. Every expectation and the S3 trace were checked by hand from the code, the parameters and CI-verified values.

Branch status

  • Position: 4 commits ahead of main (328d1721e2) and 26 behind. The merge base is f187dae556.
  • Merge: clean.
    • git merge-tree --write-tree PolicyEngine/main cffa308032 exits 0 (tree 4df778bf2e).
    • Since the merge base, main has touched Iowa itemized deductions, Oklahoma LIHEAP, SSP and additions, and some itemized-deduction tests. It has touched no file in this PR and nothing under the federal CTC.
  • CI base (note only). CI's base is 20 commits behind main. Merging main and re-running would refresh CI on a tree that contains the ratchet guard.
  • Siblings (note only). No other open PR edits refundable_ctc.py, the CTC phaseout parameters, non_refundable_ctc.py, ia_fedtax_deduction.py or ok_federal_ctc.py.

0 critical, 3 should and 3 suggestions are open at this head.

Verdict: REQUEST_CHANGES

@DTrim99 DTrim99 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 2021 split now matches s.24(i)(1)(B) and (i)(4) and Schedule 8812 lines 12/14a/14b, the surviving-spouse $200k threshold matches s.24(h)(3), and other years are unchanged. Three should-address items: (1) the 'pending: hub files parity' axiom line is invalid, so please file a queued pe-parity issue for 24(i)(1)(B)/(i)(3)/(i)(4); (2) no new case sits on an ARPA threshold or covers both phase-outs plus the ODC (the report gives the values); (3) Iowa's 2021 non_refundable_ctc add-back is an obsolete workaround that now over-adds ($500 vs $300). Three suggestions. Full report in the comment above.

@MaxGhenis

MaxGhenis commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor Author

Evidence from Tax-Calculator's PE-TAXSIM validation (PSLmodels/Tax-Calculator taxcalc/validation/pe_taxsim, 10,000 synthetic units per sample), run at this PR's head cffa308 through policyengine-taxsim 3.2.2 and compared with Tax-Calculator 6.8.5 (|tc − pe| > $1):

2021 sample actc differences, PE-US 2.58.0 at cffa308
a21 (wages, ages) 1,489 8
b21 (+ other income) 1,009 4
c21 (+ deductions, child care) 1,082 6

fiitax and v22 agree before and after; only the refundable/nonrefundable split moves, by min(ODC, ARPA reduction) per unit where no §24(b) reduction applies. 2022–2025 samples are unchanged.

Of the 18 remaining units, 11 are new (PE-US 2.58.0 and Tax-Calculator agreed on a split that is wrong under the form) and 7 differed before. All 18 are heads of household with AGI $328,000–$406,099, where the §24(b) reduction exceeds the $2,000-per-child base. Tax-Calculator phases out its base CTC plus ODC before the ARPA increase (ctc_new), so the reduction there falls on the ODC first. This PR follows Schedule 8812 line 14a. Example, a21 unit 3349 (HoH, AGI $336,000, children 1, 2 and 4, one dependent aged 22): Line 5 Worksheet $10,800 − $4,375 = $6,425; line 8 $6,925; line 11 $6,800; line 12 $125; line 14a min($500, $125) = $125; line 14b $0. This PR: refundable $0. Tax-Calculator: $125.

The TAXSIM-35 test binary (cd2026081819) reports actc equal to v22 for all 18 units, so this PR moves the emulator's 2021 actc away from TAXSIM-35 and onto the form.

This branch has not been deployed

No deployments
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