Repository navigation
Fix pre-2018 AMT child exemption and income base - #10308
Conversation
PR Review: Fix pre-2018 AMT child exemption and income basePR #10308 (author: MaxGhenis), head
The PR has no comments or reviews yet. It touches no partner test file. Path prefixes:
Sources read for this review (all fetched 2026-10-21):
SummaryWhat is right.
There are no critical findings and three should-address items:
There are also four suggestions: the stale PR body, gaps left in the child test, pre-existing pre-2018 AMTI gaps, and references for the pre-2019 child amounts. Status.
Critical (must fix)None. Should addressS1. The Axiom line
|
| Case | Taxable income | AMTI | amt_exemption |
amt_income_less_exemptions |
amt_base_tax |
Old base |
|---|---|---|---|---|---|---|
| Itemizer: interest 60,000; SALT 6,000; misc 1,500 (2,700 of expenses less 2% × 60,000); charity 1,000; exemption 4,050 | 47,450 | 59,000 | 7,500 | 51,500 | 13,390 | 39,950 |
| Wages 46,000 + interest 30,000; standard deduction 6,350; exemption 4,050 | 65,600 | 76,000 | 53,500 (cap binds) | 22,500 | 5,850 | 12,100 |
| Wages 47,000 + interest 30,000; same deductions | 66,600 | 77,000 | 54,300 (cap 54,500 does not bind) | 22,700 | 5,902 | 12,300 |
- The itemizer row. Line 1 = 60,000 − 8,500 = 51,500. Add line 3 (6,000) and line 5 (1,500) to get 59,000. The charitable deduction stays deducted. AGI is below the $261,500 Pease threshold.
- The wage rows sit on either side of the boundary at earned income of $46,800, where 46,800 + 7,500 = 54,300.
S3. The Python property test keeps old formulas, locks in the old predicate and has no cost report
- Where:
T/test_post_2017_child_amt_invariance.py. - What it does. Lines 36-74 restate both old formulas as a
Reform. Lines 77-79 build that reformed system in a module fixture. The test then compares the two systems over 2018-2030 for about 365 tax units in each of 3 Hypothesis examples. - It locks in the old predicate. Line 216 asserts that
amt_kiddie_tax_applieschanges only for JOINT rows. Line 214 asserts that it changes somewhere in every year.- Why it matters. The predicate still omits the §1(g)(2)(A)(ii)(II) support test for 18-year-olds and students (G2). A later legal fix there would change only pre-2018 results, yet it would fail this test.
- What it really checks. The test pins the variable to the legacy formula, which is more than its docstring ("post-2017 AMT unchanged") says.
- CLAUDE.md ("Test design and CI cost"):
- Duplicated formula. "Do not duplicate a policy formula in Python merely to test it against itself." After merge, the reform is a frozen copy of code the PR deletes.
- YAML can do this. The post-2017 claim is already covered in YAML: the 2018 transition case, the 2021 and 2022 controls, and the 2025 and 2026 cases.
- No measurement. "Measure simulation-heavy additions" asks for elapsed time, including setup, and peak memory for tests that build models. The PR body gives neither.
- The cost, as measured in CI (Linux; not a controlled before/after):
- Quick Feedback: 11.70 s call and 8.36 s setup.
- Rest Python
policygroup: 17.94 s call and 3.99 s setup, the eighth-slowest of 981 tests. - Memory. That group peaked at 11,954,096 KiB (11.4 GiB). Two PR runs on the same
mainbase peaked at 11,590,704 and 11,608,628 KiB, so about 340-355 MiB more here. The Makefile's stated goal for these groups is about 10 GB per process, on a 16 GB runner.
- Severity: should (a code-pattern violation of the repository's test rules).
- Fix: delete the file, since the YAML controls carry the post-2017 claim. If you keep it, drop the assertions on lines 214-216, say in the PR body which failure only this test catches, and give the CI cost above.
Suggestions
G1. The PR body is stale
- Draft language. It opens with "WIP:" although the PR is not a draft.
- Stale validation section. It says folder validation is incomplete:
qualified_dividend_netting.yamlwas stopped andregular_tax_before_credits.yamlwasn't started. The Baseline (irs) suite has since passed in CI, including both files. - Hub analysis is still listed as pending.
- Fix (optional): replace the validation section with the CI result, and fix the Axiom line (S1).
G2. Gaps left in the child test, all pre-existing
- The support test. §1(g)(2)(A)(ii)(II) applies the cap to an 18-year-old, or a full-time student aged 19-23, only if "earned income ... does not exceed one-half of the amount of the individual's support". The 2017 instructions list this as conditions 2 and 3.
amt_kiddie_tax_appliesdoesn't model it, so the 2017 test "A young separate filer remains subject to the child limitation" (T/kiddie_tax/amt_kiddie_tax_applies.yaml:110) holds only because the filer has no earnings. - The spouse-age clause is now redundant. After the joint exclusion,
no_or_young_spouse(V/kiddie_tax/amt_kiddie_tax_applies.py:49) matters only for a non-joint unit that contains a spouse. That happens only with a hand-setfiling_status. In that case §1(g)(2) looks at the filer alone, so the clause would wrongly drop the cap. - The living-parent condition (§1(g)(2)(B)) is assumed, as the test comment notes.
- Fix (optional):
- Drop the spouse clause.
- Note the support-test approximation in the variable's documentation, or model it from
care_and_support_costsif that is reliable. - Link Unify AMT "kiddie tax" child definition with EITC one #3908 or Refactor the Kiddie Tax logic and separate it from AMT #5087, which track the kiddie-tax definition.
G3. Pre-existing pre-2018 AMTI gaps that child filers now inherit
These predate the PR, which doesn't claim them. Because children now go through amt_income, they apply to child filers too.
- Line 6, the overall limit (Pease). The 2017 instructions say: if Form 1040 line 38 is over $261,500 ($313,800 joint), "enter the amount from line 9 of the Itemized Deductions Worksheet ... Enter it as a negative amount".
amt_incomeadds back SALT and miscellaneous deductions in full but never subtracts the §68 reduction (itemized_taxable_income_deductions_reduction, active 2013-2017). So it overstates 2013-2017 AMTI for high-income itemizers. - Line 7 (tax refunds) isn't modelled either. Draft Leave state tax refunds and the section 68 reduction out of federal AMTI, and fix Colorado AMTI to match #10099 (same author) addresses lines 6 and 7.
- Line 4 (home mortgage interest adjustment) isn't modelled.
- The standard deduction (
gov.irs.deductions.standard.amount) has no values before 2018, so 2017 backdates $12,000/$24,000. The PR's tests work around this by supplying 2017 amounts. AMTI for non-itemizers is AGI either way, but end-to-end 2017 regular tax, and so AMT, is still off. - Fix (optional): mention these in the PR body, and open an issue for the standard deduction and line 4 if none exists.
G4. References for the pre-2019 child amounts
- The gap.
policyengine_us/parameters/gov/irs/income/amt/exemption/child/amount.yamlhas references only from 2019. The PR's fix now depends on its 2013-2018 values ($7,400 for 2015-2016, $7,500 for 2017). - The values are right. Rev. Proc. 2016-55 §3.11 (file page 14) gives "$7,500" for 2017. The 2015 and 2016 Exemption Worksheets, line 7, give "$7,400".
- Fix (optional): add those Rev. Procs. as references.
Verified correct
- Hand-checks (11 cases, about 40 expectations). Each is TX.
T/exemption/amt_exemption.yaml:203, 2017 joint, spouses 18 and 17, wages 29,000, interest 121,000.- Taxable income = 150,000 − 12,700 − 2 × 4,050 = 129,200. Rev. Proc. 2016-55 gives the $12,700 standard deduction (file page 15) and the $4,050 exemption (file page 18).
- AMTI = 129,200 + 8,100 + 12,700 = 150,000. That is below the $160,900 joint phase-out start, so the exemption is $84,500 (§3.10, file page 13). The Exception applies, so
amt_kiddie_tax_appliesis false. - Base = 65,500 and the tentative tax is 26% × 65,500 = 17,030.
- Regular tax = 10,452.50 + 25% × 53,300 = 23,777.50 (Table 1, file page 7), so AMT is 0.
- Old code: cap 29,000 + 7,500 = 36,500, base 129,200 − 36,500 = 92,700, tax 24,102, AMT 324.50.
T/income/amt_income_less_exemptions.yaml:108, 2017 single, age 17, interest 50,000.- Taxable income = 50,000 − 6,350 − 4,050 = 39,600. AMTI = 50,000.
- Exemption = min(54,300, 0 + 7,500) = 7,500 (earned income below the limit). The base is 42,500 and the tax 11,050.
- Old code: base 32,100.
- Same file, line 47, 2017, AMTI 200,000, earnings 10,000.
- Exemption. The phase-out is 25% × 79,300 = 19,825, leaving 34,475, and the cap is 17,500. So the exemption is 17,500, the base 182,500 and the old base 172,500.
T/exemption/amt_exemption.yaml:70(17,500) is unchanged.
- Same file, line 146, 2025 single, age 17, interest 200,000.
- Taxable income = 200,000 − 15,750 = 184,250. AMTI is 200,000 and the exemption 88,100 (no cap after 2017).
- The base is 111,900 and the tax 29,094.
T/exemption/amt_exemption.yaml:252, 2021 joint, wages 29,000.- Taxable income is 29,000 − 25,100 = 3,900, and AMTI is 29,000.
- The exemption is 114,600, so the base and AMT are 0.
- Same file, line 284 (2022). Taxable income is 29,000 − 25,900 = 3,100, the exemption 118,100, and the base 0.
- Same file, lines 134 and 170, joint with both spouses 18.
amt_kiddie_tax_appliesis false. The exemption is 137,000 in 2025 and 140,200 in 2026, unchanged from before. T/income/amt_income_less_exemptions.yaml:88, 2025 joint with both spouses 18, AMTI 100,000. The predicate is false and the base is 0.T/kiddie_tax/amt_kiddie_tax_applies.yaml:- Line 110 (2017, separate return, age 18): true. §1(g)(2)(C) excludes only joint returns.
- Line 118 (2017, joint, students aged 22 and 23): false. The old code gave true, because both are under 24.
- Line 18 (2022, joint, both 18): false.
- 2017 against 2018.
T/exemption/amt_exemption.yaml:86(2018, single, age 18, no earnings): 70,300 with no cap, becausein_effectis false from 2018.- The 2017 cap. For 2017 the cap uses the $7,500 amount.
- The indexed amount.
child/amount.yamlmatches the IRS:- 2015 and 2016: $7,400 (worksheet line 7, file page 10 of each year's instructions).
- 2017: $7,500 (Rev. Proc. 2016-55 §3.11).
- The 2015-2017 form.
- The exemption amounts and phase-out starts in
amount.yamlandphase_out/start.yamlmatch the line 29 charts. In 2017, for example, they are $54,300/$120,700, $84,500/$160,900 and $42,250/$80,450. - The 26% bracket ends at $187,800 in 2017, which matches line 31.
- The exemption amounts and phase-out starts in
- Callers at the head:
amt_kiddie_tax_appliesis read only byamt_exemption, and only whilechild.in_effectis true (before 2018).amt_income_less_exemptionsfeeds the federal AMT, the §911 variables andco_alternative_minimum_taxable_income. Those change only before 2018. The Colorado AMT rate starts in 2021.- California.
ca_amt_exemptionreads onlychild.amountand its own child test, so it is unaffected.
- References.
- The 2017 instructions. File page 10 holds the Exemption Worksheet (
amt_income_less_exemptions.py), and file page 11 holds the joint-return Exception (amt_kiddie_tax_applies.py). - Rev. Proc. 2016-55. The test comments cite pages 7, 15 and 18, which show the rate table, the standard deduction and the exemption.
- §56(b)(1)(D), cited in a test comment, is the standard-deduction and exemption disallowance.
- The 2017 instructions. File page 10 holds the Exemption Worksheet (
- Changelog.
changelog.d/amt-child-pre-2018.fixed.mdis a top-level fragment, andfixedis the right type. - Batch lessons:
- Source-conflict ratchet (Tie source-conflict markers to the register shared with Axiom #10287).
- In CI. The green run's tree contains Tie source-conflict markers to the register shared with Axiom #10287 and Count the FL and SD LIHEAP comments in the source-conflict ratchet baseline #10334.
test_conflict_sounding_comments_do_not_growpassed in the Rest (Python, shard-1) job. - Statically. I also ran
main'sCONFLICT_COMMENT_REover the comment tokens of the two scanned files the PR edits (amt_income_less_exemptions.pyandamt_kiddie_tax_applies.py) at the merge base, the head andmain. Each has 0 matches. - The baseline list. Neither file is in
KNOWN_UNMARKED, andmainhasn't touched either since the merge base. The ratchet passes on the merged tree.
- In CI. The green run's tree contains Tie source-conflict markers to the register shared with Axiom #10287 and Count the FL and SD LIHEAP comments in the source-conflict ratchet baseline #10334.
- NH Add New Hampshire state supplementation for 2026 #10156 (state-gated keys): not applicable. There is no state-gated variable.
- Integrate modeled state unemployment insurance into income flows #9558, Document disjoint health premium inputs and correct consumer treatment #10046 and OK Add Oklahoma state supplementation for 2026 #10165: not applicable. No income-source list or wage definition changes.
- Partner tests: untouched. The Household API Partners check passed.
- Source-conflict ratchet (Tie source-conflict markers to the register shared with Axiom #10287).
Axiom line assessment
Invalid: axiom: pending: hub files parity (S1).
- Recommended form:
axiom: us:statutes/26/59#section_59_j_kiddie_exemption_limit_applies encoded-correct; TheAxiomFoundation/rulespec-us#<n> queued. The queued issue is a new open,pe-parity-labelled, dispatch-ready issue for the pre-2018 §59(j)(1) cap, the §1(g)(2) applicability (including (C)) and the taxable excess. The required contents are listed under S1. Alternatively, cite an encoder PR asencoded. - Not
n/a. These are statutory provisions, and the PR changes 2013-2017 AMT results. encoded-correctcovers only the post-2017 switch. rulespec-usmain(5796861e58) encodes the switch asfalsefrom 2018-01-01, which matches the model. It has no pre-2018 version, and55.yamlcovers only 2026.
CI status
27 pass, 3 skipping, 0 pending, 0 failed. I re-checked gh pr checks 10308 at the end of this review, and the head is still e7b010e809.
- The green run. Run 39608011006 (
pull_request, created 2026-10-21 13:26 UTC) tested merge commit70ec14b2. That merges the head intofec98b87c8(Count the FL and SD LIHEAP comments in the source-conflict ratchet baseline #10334), so CI's tree contains the Tie source-conflict markers to the register shared with Axiom #10287 ratchet and the FL/SD LIHEAP baseline fix. The known main-caused ratchet failure therefore doesn't apply. - The skipped checks are the umbrella jobs of the superseded run 39507938034 (2026-10-20 22:25 UTC).
- Where the changed tests ran.
- Quick Feedback selected and passed:
amt_exemption.yaml(17),amt_income_less_exemptions.yaml(10),amt_kiddie_tax_applies.yaml(11) and the property test (1). - Full suites. The Baseline (irs) suite, the Rest Python
policygroup (which reran the property test), Microsimulation and Household API Partners all passed.
- Quick Feedback selected and passed:
- Locally: I ran no tests or model probes. CI is green at the current head. Every expectation and the S2 values were checked by hand from the code, the parameters and the IRS sources.
Branch status
- Position: 3 commits ahead of
main(328d1721e2) and 24 behind. The merge base isf4b1644230. - Merge: clean.
git merge-tree --write-tree PolicyEngine/main e7b010e809exits 0 (treed3157c9dcf).- Since the merge base,
mainhas touched no file in this PR and no AMT variable, parameter or test.
- Siblings (note only).
- Draft Leave state tax refunds and the section 68 reduction out of federal AMTI, and fix Colorado AMTI to match #10099 (same author) edits
amt_income.py,co_alternative_minimum_taxable_income.pyandamt_income.yaml. It has no file overlap, but it changes the pre-2018 AMTI that child filers now use (G3). - Take the 2020 charitable deduction for non-itemizers above the line #10186 and Phase out the Minnesota AMT exemption at 25% instead of the federal 2026 rate #10256 touch only Minnesota AMT files.
- Draft Leave state tax refunds and the section 68 reduction out of federal AMTI, and fix Colorado AMTI to match #10099 (same author) edits
0 critical, 3 should and 4 suggestions are open at this head.
Verdict: REQUEST_CHANGES
DTrim99
left a comment
There was a problem hiding this comment.
Both changes are correct. s.59(j) caps only the exemption, joint returns are excluded under s.1(g)(2)(C), s.59(j) stays off from 2018 (SECURE Act, and OBBBA s.70107 struck the 2026 end date), and the pre-2018 add-backs match Form 6251. Three should-address items: (1) the 'pending: hub files parity' axiom line is invalid (cite 59(j) encoded-correct, plus a queued issue for the pre-2018 cap and 1(g)(2)); (2) add cases for an itemizing 2017 child and a pair either side of the cap; (3) the Python property test restates the old formulas and locks in the old predicate, with no cost report. Four suggestions. Full report in the comment above.
|
Picking this up for the US hub. I'll post a hand-off comment here when it's ready. Generated by Claude Code |
Review follow-up. Section 1(g)(2)(A)(ii) covers an 18-year-old, or a full-time student under 24, only when earned income is at most half of support, so the predicate now reads care_and_support_costs. Section 1(g)(2) looks at the filer alone, so the spouse-age clause is dropped; a joint return still excludes the unit under 1(g)(2)(C). The model has no input for a living parent (1(g)(2)(B)), which the variable now documents. Adds cases for an itemizing 2017 child, a pair either side of the 59(j) cap, and the support-test boundary, plus references for the 2015-2017 child amounts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6JJsMauziPgWAqgG3k
The property test restated the pre-fix formulas as a reform and pinned the predicate to them. It now checks what follows from the law: the 59(j) limit bounds a covered child's exemption before 2018, joint returns are never covered under 1(g)(2)(C), and from 2018 a child's exemption equals an identical adult's under 55(d)(4)(A)(iii). It builds one simulation per example and no reformed system, and the file is renamed to match. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6JJsMauziPgWAqgG3k
|
Picking this up for the US hub, continuing an earlier session's work on DTrim99's review (that session pushed no commits here). I'll post a hand-off comment here when it's ready. Generated by Claude Code |
|
Ready for hub review Head: This continues an earlier session's work. That session pushed no commits here, so there was nothing of its to keep or fix. Since then nobody but me has pushed or commented here after 2026-11-05T00:29Z. The head before this push was Path prefixes:
Commits
What the item asked for2. Cases (all 2017, TX, single, not claimed elsewhere, with 2017 upstream return amounts supplied as in the PR's existing cases; Rev. Proc. 2016-55 single exemption 54,300, phase-out start 120,700, child amount 7,500, 26% bracket):
3. Property test (
Five fixed cases ( Cost, measured locally on this 4 vCPU Linux VM (not CI), one pytest process each:
Peak RSS is the whole pytest process, including importing the model. The new file is in the same Rest Python Hub parity inputs: the three §1(g)(2) departures.
I didn't see the hub draft's rows 9 and 10, so the (a) cases are my own, hand-computed above. Suggestions
Tests (one file per process)
Mutants (each in its own process, code restored after each)
The first version of the property test let the joint mutant survive: its fixed joint case was an 18-year-old with earnings and no support, so the support test already excluded it. I changed that case's head to age 17. One mutant run of the property test hung under Hypothesis and was killed; the rerun with a timeout completed. While I was killing it, a backup copy overwrote CIPull request run 41948903573 at Judgment calls
Follow-up issues I'd propose
Reply to DTrim99Thanks for the review. Item by item:
Generated by Claude Code |
|
Hub review in progress Reviewing head
Generated by Claude Code |
|
Ready for hub review This is a round 2 delta review: the fix session's changes from
Path prefixes:
Sources. This VM couldn't reach govinfo.gov, irs.gov, uscode.house.gov, ecfr.gov or law.cornell.edu: the proxy refused each one, through both curl and the fetch tool. I quote no law from memory. On the statute I work from the hub's reading of the 2016 edition of §1(g)(2) and §59(j), taken on 2026-11-05 and given in the brief. On the 2017 Form 6251 and Rev. Proc. 2016-55 I work from DTrim99's quotations in his review. I couldn't reach any of these sources myself (see "What I couldn't check"). Summary. The base fix and the joint-return exclusion hold.
I ask for one change: the PR's reading of an unreported support amount (P2-1). For a common household, a full-time student aged 19 to 23 (or an 18-year-old) with a part-time job, it makes the 2013–2017 result worse than FindingsP2-1. An unreported support amount now removes the §59(j) cap for every 18-year-old or student under 24 with earningsWhere: support = tax_unit.sum(person("care_and_support_costs", period) * is_head)
meets_support_test = earned_income <= support_test.rate * supportWhat the law asks. In the brief's reading, §1(g)(2)(A)(ii)(II) covers an older child whose earned income "does not exceed one-half of the amount of the individual's support". That support is the child's total support from every source, so it is never 0 for a living person. What the code does.
Failure scenario. 2017, single, a full-time college student aged 20, $5,000 of wages, AMTI set to $60,000, support not entered. I ran the same script against each tree, setting
With support entered, the head computes the law's base of 47,500 (60,000 − 12,500). With support left blank it gives 5,700. An 18-year-old with $1 of wages behaves the same way: the head gives 54,300 and the fix gives 7,501. Why I'm asking for a change. The session flagged this as a judgment call, and the law leaves the missing fact open, so METHODOLOGY_CHOICE is yes. I disagree with the reading it chose:
The population impact should be nil, since the PR body says the default data has no pre-2018 records. The change matters for 2013–2017 household calculations. Fix. Replace # Support is never zero for a living person, so a zero amount means
# none was reported. Treat the test as met unless support is given.
support_reported = support > 0
meets_support_test = ~support_reported | (
earned_income <= support_test.rate * support
)Three companion changes go with it:
I applied the code change on a scratch copy. Only the "no reported support" case fails (1 failed, 15 passed). P3-1. The §59(j) cap still counts a spouse's earnings, although the predicate now judges the filer aloneWhere: adj_earnings = tax_unit_non_dep_sum("adjusted_earnings", tax_unit, period)What goes wrong. §59(j)(1)(A) uses "such child's earned income". With the spouse clause gone, a non-joint unit that contains a spouse is now covered. That happens only with a hand-set Failure scenario. 2017,
The same gap leaves a mutant alive. Mutant M8, in which the support test sums every member's earnings instead of only the head's, survives all four changed test files. No case builds a real spouse with earnings. Fix. Replace # IRC 59(j)(1)(A): the child's own earned income. A joint return is
# never covered, so this is the head's.
person = tax_unit.members
adj_earnings = tax_unit.sum(
person("adjusted_earnings", period) * person("is_tax_unit_head", period)
)Then add the case above to P3-2. The hand-off gives a head SHA that doesn't existThe hand-off's "Head:" line reads P3-3. The PR body has no
|
| File | Result |
|---|---|
amt_kiddie_tax_applies.yaml |
16 passed |
amt_income_less_exemptions.yaml |
14 passed |
amt_exemption.yaml |
17 passed |
test_child_amt_exemption_properties.py |
1 passed |
test_source_conflicts.py |
103 passed |
The property test needs the dev extra (hypothesis), which pyproject.toml:57 declares.
Hand computations, done before I read the YAML. All use 2017 single values: exemption 54,300, phase-out from 120,700, child amount 7,500, standard deduction 6,350, personal exemption 4,050, 26% rate. All match the YAML.
- Itemizer (
T/income/amt_income_less_exemptions.yaml:179). AGI 60,000. Itemized deductions are 6,000 + (2,700 − 2% × 60,000) + 1,000 = 8,500, so taxable income is 47,450. Line 1 is 51,500, and AMTI is 51,500 + 6,000 + 1,500 = 59,000. The exemption is min(54,300, 7,500) = 7,500, the base 51,500 and the tax 13,390. - The cap binds (
:219). Taxable income 65,600, AMTI 76,000, exemption min(54,300, 53,500) = 53,500, base 22,500, tax 5,850. - The cap doesn't bind (
:255). Taxable income 66,600, AMTI 77,000, exemption min(54,300, 54,500) = 54,300, base 22,700, tax 5,902. The two cases straddle earned income of 46,800, and they match the hub's companion rows 7 and 8. - Fails the support test (
:293). 10,000 > 0.5 × 15,000, so the predicate is false. The exemption is 54,300 (it would be 17,500 under the cap), and the base is 60,000 − 54,300 = 5,700. - The five predicate cases (
T/kiddie_tax/amt_kiddie_tax_applies.yaml:145-213).- 10,000 ≤ 10,000 is covered (the equal case is right: "does not exceed").
- 10,000 > 9,999 is not covered.
- A student aged 22 with 5,000 ≤ 6,000 is covered.
- A 17-year-old is covered with no support test.
- 18 with no support is not covered under the PR's reading; see P2-1.
The age split (V/kiddie_tax/amt_kiddie_tax_applies.py:36-53) matches the brief's reading of §1(g)(2)(A):
- under 18 is covered with no condition;
- otherwise the filer needs the §152(c)(3) age test (under 19, or under 24 as a full-time student) and the support test.
- The code doesn't use §152(c)(3)(B), the disability exception, which matches "without regard to subparagraph (B)" as the brief summarises it.
- Earned income is
adjusted_earnings, the same proxy for the §911(d)(2) measure thatamt_exemptionalready uses for the cap.
The spouse-age clause. Its removal is consistent with §1(g)(2)(C) as the brief reads it: only a joint return excludes a married child.
- The replacement 2017 separate-filer case (
:12) uses only the tax-unit inputsage_headandage_spouse, and builds no spouse. - It kills mutant M3 (restore the clause). See P3-1 for a stronger case with a real spouse.
Living parent (§1(g)(2)(B)). The documentation and the comment at :55 say the model assumes a living parent.
- A person-level
is_deceasedinput exists, but nothing links a filer to their parents, so the gap is real. - "No input" is loose wording: "no input links the filer to a parent" would be exact.
The property test.
- It no longer restates formulas or builds a reform. It sets
amt_incomeandadjusted_earningsas inputs, so it testsamt_exemptionand the predicate, not the AMTI base; YAML covers the base. - Its
coveredset comes from the model's own predicate. It therefore checks the exemption against the predicate and the joint and adult exclusions, not the support or age tests. Mutants M2, M6, M7 and M8 survive it, as the hand-off says for M2. - Hypothesis draws ages 14–30, which reach the 18, 19 and 24 boundaries.
Cost, measured locally on a 4 vCPU Linux VM (not CI).
- New test: 3.21 s call, no setup, 10.56 s in pytest, about 1.0 GB peak RSS for the pytest process (1,029,980 KiB).
- Old test at
e7b010e809: 16.93 s call, 9.00 s setup, about 2.0 GB peak (1,999,148 KiB). - In CI run 41948903573 the new test passed in a Rest Python shard with a 1.25 s call, and Quick Feedback selected and ran it.
Mutants. Each ran in its own process on a scratch worktree, which I restored after each one; git status was clean every time. The first five are the hand-off's.
| Mutant | Predicate YAML | Base YAML | Exemption YAML | Property test |
|---|---|---|---|---|
M1: drop & ~joint |
3 fail | 1 fail | 3 fail | fails at :114 |
| M2: drop the support test | 2 fail | 1 fail | pass | pass |
| M3: restore the spouse-age clause | 1 fail | pass | pass | pass |
| M4: cap after 2017 | pass | 4 fail | 5 fail | fails at :136 |
| M5: old taxable-income base before 2018 | pass | 5 fail | pass | pass |
M6 (mine): strict < in the support test |
6 fail | 1 fail | 3 fail | pass |
| M7 (mine): 18-year-olds skip the support test | 2 fail | 1 fail | pass | pass |
| M8 (mine): support test sums all members' earnings | pass | pass | pass | pass (survives; P3-1) |
| M9 (mine): predicate always false | 8 fail | 5 fail | 3 fail | fails at :124 |
| M10 (mine): child amount doubled in the cap | pass | 4 fail | 1 fail | fails at :125 |
| M11 (mine): exemption +1 above the statutory amount | pass | 8 fail | 16 fail | fails at :118 |
| M12 (mine): no age test | 5 fail | 1 fail | pass | fails at :116 |
| M13 (mine): cap drops earned income | pass | 3 fail | 1 fail | fails at :127 |
- The hand-off's kill counts for M2, M3 and M5 match exactly.
- M1 and M4 are also killed by YAML files the hand-off didn't list.
- Each property assertion (lines 114, 116, 118, 124, 125, 127 and 136) fails under at least one mutant. Line 133 ("the limit binds for at least one fixed case") is a guard against a vacuous pass and fires only once the earlier assertions hold.
Readers.
amt_kiddie_tax_appliesis read only byamt_exemption, before 2018.amt_income_less_exemptionsis read byalternative_minimum_tax,amt_tax_including_cg, the two §911 variables andco_alternative_minimum_taxable_income. All of them change only before 2018. CO's AMT reads it, and DTrim99 found that CO's rate starts in 2021.- The new
support_testparameters are read only by the predicate. child.amount(references only) is read byamt_exemption.ca_amt_exemptionreads California's own amount.
Parameters.
support_test/age_threshold.yaml(18) andrate.yaml(0.5) have a description, a label, a unit and a single 2013 value.- Each has one reference per document. The hrefs use
#page=only, and the titles carry no page.
References in child/amount.yaml:22-27. The format follows the repository rule, and the pages match the pages DTrim99 cited (Rev. Proc. 2016-55 file page 14; 2015 and 2016 Form 6251 instructions file page 10). I couldn't open the PDFs to confirm them.
The hand-off's reply to DTrim99.
- S2: the three rows are added with his values. The old-base mutant (M5) fails 5 cases in that file, which covers the 3 new rows plus the 2 earlier pre-2018 child cases.
- S3: the property test is rewritten and the cost is reported. I confirmed both.
- G1, G2 and G4 are done as described.
- G3 is listed in the body only.
- The
axiom:line is the hub's, and I left it alone.
The body's claims. Its test description, the 16/14/17 counts, "one simulation per example and no reformed system", and the cost direction all match what I ran.
The changelog changelog.d/amt-child-pre-2018.fixed.md is a top-level fixed fragment.
Main and CI.
origin/mainis still0315452583, and the head is 0 commits behind it.git merge-tree --write-tree --name-only origin/main 65b6b34631exits 0 with tree0ed73f93d1and lists no conflicts.- Pull request run 41948903573 at the head completed with success, 36 of 36 jobs succeeding. That includes Quick Feedback, Baseline (irs), all nine Rest Python shards, Microsimulation and Household API Partners.
Scope.
- Nothing under
.github/orpolicyengine_us/tests/policy/baseline/partners/changes. - Open-PR searches on the changed variable names find only this PR and Leave state tax refunds and the section 68 reduction out of federal AMTI, and fix Colorado AMTI to match #10099. Leave state tax refunds and the section 68 reduction out of federal AMTI, and fix Colorado AMTI to match #10099 has no file overlap, but it changes the pre-2018
amt_incomethat child filers now reach. The new itemizer case has no refunds and AGI below the §68 threshold, so Leave state tax refunds and the section 68 reduction out of federal AMTI, and fix Colorado AMTI to match #10099 shouldn't move it.
What I couldn't check
- The law and the IRS sources. I couldn't read §1(g)(2), §59(j) or §152(c)(3) (2016 edition), the 2015–2017 Form 6251 instructions, or Rev. Proc. 2016-55. The proxy refused govinfo.gov, irs.gov, uscode.house.gov, ecfr.gov and law.cornell.edu. Where the review relies on them, it relies on the brief's reading and DTrim99's quotations.
- The new parameters' references. I couldn't confirm the page numbers in
child/amount.yaml, or the file page (10 or 11) of the 2017 instructions thatsupport_test/*.yamlcites for the "Certain Children Under Age 24" conditions. - Population impact. I couldn't check whether any pre-2018 dataset exists. I ran no microsimulation.
- The old test's CI memory. I didn't re-measure the old property test's memory in CI. The hand-off's local figures are consistent with my local rerun.
Generated by Claude Code
…ild's own earnings `care_and_support_costs` defaults to 0, and every living person has some support, so zero means nobody reported it. The predicate now treats the IRC 1(g)(2)(A)(ii)(II) test as met in that case, which keeps main's cap for a student or 18-year-old with earnings. Supplied support still decides the test. The IRC 59(j) cap now reads the head's earnings alone, as IRC 59(j)(1)(A) says. A new case with a real spouse pins it. The joint-return case now sets its filing status and says why it fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for the review. Here's where each item landed:
🤖 Generated with Claude Code |
Answered in #10308 (comment): S1 cites rulespec-us#1857, S2 adds the three 2017 rows, S3 replaces the property test, and G1 to G4 follow the mapping there.
|
US + core hub merge audit, us#10308 at
🤖 Generated with Claude Code |
Correct the pre-2018 AMT child exemption and income base identified in Yale/PSL unit U4. A child with $50,000 AMTI and a $7,500 exemption should have a $42,500 AMT base, and a 2017 joint return by young spouses should get the ordinary joint exemption.
Plan:
amt_exemption(§55(b)(1)(A)(ii), §59(j)).amt_kiddie_tax_applies(§1(g)(2)(C)).care_and_support_costs(new parametersgov.irs.income.amt.exemption.child.support_test.age_thresholdand.rate). The model treats zero support as unreported and the test as met, so a student or 18-year-old with earnings keepsmain's cap until someone enters support.Sources:
Tests:
test_child_amt_exemption_properties.py(renamed fromtest_post_2017_child_amt_invariance.py) checks, across generated households in 2015, 2017, 2018, 2025 and 2026, that:amt_kiddie_tax_applies.yamlholds 16 cases,amt_income_less_exemptions.yaml14 andamt_exemption.yaml18.78a02e1b52(https://github.com/PolicyEngine/policyengine-us/actions/runs/41971589911), concluded success: all 36 jobs passed.Known gaps this PR doesn't change (pre-2018 AMTI, now inherited by child filers): the line 6 overall-limit (Pease) adjustment, line 7 (tax refunds), line 4 (home mortgage interest), and the missing pre-2018 standard deduction values. #10099 covers lines 6 and 7.
impact: none from 2018 on. In those years
amt_exemptionreturns before it readsamt_kiddie_tax_applies, nothing else reads that variable at this head, andamt_income_less_exemptionsalready usedamt_income. For 2013-2017 the PR changes AMT for child filers in household calculations. The default dataset holds 2024 records and no 2013-2017 data, so we report no aggregate for those years.axiom: us:statutes/26/59#section_59_j_kiddie_exemption_limit_applies encoded-correct for 2018 onward (false from 2018-01-01; companion tests us/statutes/26/59.test.yaml); TheAxiomFoundation/rulespec-us#1857 queued for TY2013-2017 (§59(j), §1(g)(2) including the joint-return exclusion, and the §55(b)(1)(A)(ii) AMTI taxable excess). It waits on three things: axiom-corpus ingestion of the TY2013-2017 statute text and the Form 6251 instructions (TheAxiomFoundation/axiom-corpus#915), a re-pin to a release that carries the 2013-2016 revenue procedures, and approval of the billed encoder run.
🤖 Generated with Claude Code