Repository navigation
Apply the 2026 section 68 reduction in Maryland, Maine and New Mexico itemized deductions - #10594
Conversation
Each state starts from federal itemized deductions, which from 2026 the P.L. 119-21 section 68 reduction limits; main uses the pre-reduction total. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6JzbnbD1RtbxQx1ds6
Each state starts from federal itemized deductions allowed after section 68 (Md. Tax-General 10-218(b), 36 M.R.S. 5125(1), NMSA 7-2-2(N)(2)), but main used the pre-reduction total, overstating deductions for high-income itemizers from 2026. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6JzbnbD1RtbxQx1ds6
YAML cases fix single points; this checks the MD, ME and NM identities over random totals and positive reductions in one simulation per example. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6JzbnbD1RtbxQx1ds6
|
Ready for hub review Head: Itemsus-md-itemized-section-68-2026: FIXED.
us-me-itemized-section-68-2026: FIXED, confidence medium.
us-nm-itemized-raw-total-2026: FIXED.
Tests run (one file per process, after the fix)
The readers come from a grep over
No full suites and no microsimulations were run. Hand computations
MutantsEach mutant restores one file from
Judgment calls (please review)
Partner testsNothing under Proposed follow-ups (not opened)
Generated by Claude Code |
|
Ready for hub review The head has not moved past I found no wrong result in the code. The two P2 findings below are inaccurate or missing statements in the PR body. They should be fixed before landing, because the hub's impact reading depends on them. Maine's property-tax allocation is a methodology choice the hub should rule on (P3-1). FindingsP2-1. NM: the safeguard change moves 2026 results even when R = 0, and the body doesn't say so
Witness: full chain, 2026, NM, single, wages $550,000, real estate taxes $10,000, state income tax $20,000, no other deductions. I ran the same script against
The PR's value is the right direction: $26,900 allowed − $16,100 standard deduction = $10,800. Main used $30,000 − $16,100 = $13,900. Still, this moves baseline in the opposite direction from the section 68 fix (it lowers tax), and it reaches filers below the 37% bracket. Add to the NM "What changed" list:
P2-2. MD: the body's account of judgment call 1 is wrong as writtenBody, Maryland "What changed": "when capped property taxes exceed the federal SALT deduction (so T - base < 0), the spec's literal formula would also change results with R = 0." With I = T − base, T − R − I + R·I/T = (T − I)(1 − R/T) = base·(1 − R/T) for every input, negative I included. At R = 0 it gives exactly The actual gap is that T − base equals the income taxes inside the federal SALT deduction only when
P3-1. ME: the property-tax split is one reading among several (methodology choice)
Mutant A below shows the YAML pins the chosen reading. Suggested register entry for the hub to rule on: P3-2. No test pins the gate in the 2013-2017 Pease yearsMutant B: changing - name: Maryland ignores the pre-2018 section 68 reduction (out of scope here).
period: 2017
input:
state_code: MD
filing_status: SINGLE
adjusted_gross_income: 150_000
total_itemized_taxable_income_deductions: 40_000
itemized_deductions_less_salt: 30_000
itemized_taxable_income_deductions_reduction: 1_000
capped_property_taxes: 4_000
output:
md_itemized_deductions: 34_000P3-3. The
|
| Mutant | YAML | Property test |
|---|---|---|
| MD file reverted to base | 3 failed (md_itemized_deductions.yaml) | failed |
| ME file reverted | 4 failed (me_itemized_deductions_pre_phaseout.yaml) | failed (assert_allclose mismatch) |
| NM itemized reverted | 1 failed (nm_itemized_deductions.yaml) | failed |
| NM addback reverted | 1 failed (nm_salt_add_back.yaml) | failed |
A (mine): ME property term (1 - allowed_share) → 0 |
2 failed (the component and excluded-property cases) | passed (it has no property taxes) |
B (mine): MD gate → limitation.applies |
survived (P3-2) | not run |
Checked and sound
- Federal rule at this head.
itemized_taxable_income_deductions_reduction.pycomputes R =obbb.rate× min(T,taxable_income_before_itemized_deductions− 37% bracket threshold) whenlimitation.appliesandobbb.applies. T istotal_itemized_taxable_income_deductions, the sum ofgov.irs.deductions.itemized_deductions, which includes the phased-downsalt_deduction.itemized_taxable_income_deductions= max(0, T − R). The rate is0.05405405, not exactly 2/37 (pre-existing; under $0.001 at these amounts). So in the hand-off, T is the pre-section-68 federal total and R the 2026 reduction. On whether each state starts from deductions allowed after § 68, I checked only the spec excerpts, not the statutes (see below). - Gate.
limitation/obbb/applies.yamlis false from 2009 and true from 2026-01-01. The full-chain run (MD, ME, NM; single, $1M wages, $100k charity, $30k property tax) gives byte-identical outputs on main and this head for 2015, 2017 and 2025. The run shows positive federal Pease reductions in 2015 and 2017 that the states still ignore. 2026 and 2027 move as expected. - Equivalence (judgment call 1). Shown algebraically and numerically in P2-2.
- Tests at this head, one file per process: md_itemized_deductions 14 passed; me_itemized_deductions_pre_phaseout 9; nm_itemized_deductions 5; nm_salt_add_back 6; readers integration_md 7, me_deductions 4, nm_deductions 3, nm_taxable_income 3, state_itemized_deductions 17. The property test passed: 9.6 s of test time, 44 s wall including environment start and model import, 916 MB peak child RSS. These are local VM numbers, not CI.
hypothesiscomes from thedevextra: a plainuv syncfails to collect the file, but CI anduv sync --extra devare fine. - Can the property pass trivially? No: the four reverted-file mutants all fail it. It draws R ≥ 1 and T ≥ 1,000, so it never reaches R = 0 or T = 0. The YAML covers those (the MD R = 0 and T = 0 cases, the NM R = 0 case). The MD check restates the implemented form base·q, but its input I equals T − base exactly, so it equals the statutory form here. MD AGI is fixed at $100,000, so the property never reaches Maryland's phaseout.
- New tests vs claims. The MD, ME and NM cases assert the exact claimed values to $0.01, not weaker bounds. The ME component and excluded-property cases pin the allocation reading (mutant A).
- Readers.
md_itemized_deductions→md_deductions→md_taxable_income→ MD income tax.me_itemized_deductions_pre_phaseout→me_deductions→me_taxable_income.nm_itemized_deductions→nm_deductions→nm_taxable_income, andnm_salt_add_back→nm_taxable_income→nm_income_tax_before_non_refundable_credits. All three are also in thestate_itemized_deductionsparameter list. Each chain reaches state income tax andhousehold_net_income, both of which move in the witnesses above. At baseline:- MD moves for 2026+ itemizers with R > 0;
- ME moves for 2026+ itemizers with R > 0 when the cap or phaseout doesn't absorb the change;
- NM moves for those with R > 0, and through P2-1 for those whose SALT cap phases down.
- Follow-ups. Both claims hold, and both are identical on main:
- MD
capped_property_taxesreads the statutory cap: 2026, MD, $1M wages, $30k property tax givessalt_deduction10,000 butcapped_property_taxes30,000. This PR doesn't make it worse: it scales that overstated base down by (1 − R/T), somd_itemized_deductionsgoes from 65,000 on main to 58,243.25. - NM numerator: 2026, NM, wages $550,000, charity $50,000, property tax $10,000, income tax $20,000 gives
salt_deduction26,900,nm_salt_add_back20,000 (= 30,000 × 20,000/30,000). The pro rata income-tax share of the deducted 26,900 would be 17,933.33. This PR leaves the numerator line unchanged. - A third follow-up: the 2013-2017 Pease non-application, confirmed by the 2015/2017 runs above.
- MD
- Body, otherwise. These match the code: the line ranges cited, the MD and ME "R = 0 gives main's result" invariants (the MD R = 0 YAML case gives 34,000 as on main), the NM bounds, failing tests first (0750f6d adds only the four YAML files), and the extra files listed.
- References. Variable references follow the repo convention: one entry per source, a
# PDF pages 94-95comment above the href, and no page list in the href. - Scope. The files list touches nothing under
.github/orpolicyengine_us/tests/policy/baseline/partners/, and the Household API Partners job passed. An open-PR search for each of the four changed variable names returns only Apply the 2026 section 68 reduction in Maryland, Maine and New Mexico itemized deductions #10594. - Population. Beyond the common set, the impact pair needs
md_itemized_deductions,md_deductions,me_itemized_deductions_pre_phaseout,me_deductions,nm_itemized_deductions,nm_salt_add_back,nm_deductions,itemized_taxable_income_deductions_reductionandsalt_cap, plus each state's income tax. Expected 2026 direction:- MD: total income tax rises;
- ME: rises slightly, possibly near zero, because Maine's phaseout removes deductions at most incomes where R > 0;
- NM: section 68 raises tax for R > 0 filers, and P2-1 lowers it for phased-cap filers. The net is most likely an increase, but the hub should report both pieces.
What I couldn't check
-
Every controlling text. The VM's egress proxy refused uscode.house.gov, govinfo.gov, mgaleg.maryland.gov, legislature.maine.gov and nmlegis.gov (CONNECT 403 / DNS refusal). I could fetch none of 26 U.S.C. 68 as amended by P.L. 119-21 § 70111, Md. Code Tax-General § 10-218, 36 M.R.S. § 5125, or NMSA § 7-2-2 and the NM addback provision. A web search returned only secondary summaries, which I don't rely on. So I could not confirm:
- whether amended § 68 has any SALT-specific or component rule that would change R's base;
- the wording of MD (b)(3) and its restriction to income taxes (which bears on sales-tax electors, a pre-existing issue);
- the text of ME § 5125(3)(A-1);
- whether each state statute starts from deductions allowed after § 68.
The PR's quotes all come from the hub's spec excerpts, and I treat them as unverified too.
-
Population impact: no microsimulation was run, per the brief.
-
The CI timing and memory reports for the Python shard that now holds the property test.
Generated by Claude Code
|
Hub impact pair, progress The base side of the impact pair for this PR finished. The head side runs next, and the result comment follows it.
weighted sums as JSON{
"household_benefits": 2059999418114.8687,
"household_net_income": 17161633654134.797,
"income_tax": 2595105852713.508,
"itemized_taxable_income_deductions_reduction": 10599253278.9609,
"md_deductions": 30732223727.406742,
"md_income_tax": 15156844198.633507,
"md_itemized_deductions": 23633476785.28398,
"me_deductions": 16787410277.648087,
"me_income_tax": 2798402934.942308,
"me_itemized_deductions_pre_phaseout": 4551828204.946152,
"nm_deductions": 29301203896.830112,
"nm_income_tax": 2257269570.331682,
"nm_itemized_deductions": 8805151058.535755,
"nm_salt_add_back": 660548696.7935939,
"salt_cap": 6513011228769.599,
"state_income_tax": 578044599346.724
}Generated by Claude Code |
|
Ready for hub review 10 of the 16 variables move in 2026, all through Maryland, Maine and New Mexico tax units. These totals rest on very few records. Two Maryland records carry all but $41 of the Maryland tax change, two New Mexico records net within $19 of the New Mexico one, and Maine's five moving records weigh 0.03 together. So the dollar totals carry wide sampling error. Setup
ResultsSums are weighted dollars, using each entity's simulation weight.
10 of 16 variables move: In By stateThese notes come from the two sides' pickles, the dataset's person and household tables, and the formulas each note names. Maryland
New Mexico
Maine
Households
Federal and downstream
Resources
The sides ran one at a time, each detached under Dropped variablesNone. Every variable exists on both sides on the same entity, CommandsThe session ran these commands from the root of its existing clone of PolicyEngine/policyengine-us, in place of the brief's git fetch origin main pull/10594/head:pr-10594 pull/10561/head:pr-10561
HEAD_SHA=$(git rev-parse pr-10594); BASE_SHA=$(git merge-base origin/main $HEAD_SHA); echo $HEAD_SHA $BASE_SHA
git worktree add --detach ../head $HEAD_SHA
git worktree add --detach ../base $BASE_SHA
git show pr-10561:policyengine_us/tools/calculate_chunked.py > ../calculate_chunked.py
git diff $BASE_SHA $HEAD_SHA -- pyproject.toml uv.lock # empty
git diff $BASE_SHA $HEAD_SHA -- policyengine_us/system.py # empty, so DEFAULT_DATASET and DEFAULT_DATASET_SHA256 match
(cd ../head && uv sync)
# Cache the default dataset before the timed runs, then hash the file it resolves to.
PYTHONPATH=$PWD/../head ../head/.venv/bin/python -c "from policyengine_us.system import _resolve_dataset_path, DEFAULT_DATASET; print(_resolve_dataset_path(DEFAULT_DATASET))"
sha256sum "$(readlink -f <printed path>)"
apt-get install -y time || (apt-get update && apt-get install -y time) # the VM had no /usr/bin/time
mkdir -p ../out
H=$PWD/../calculate_chunked.py; PY=$PWD/../head/.venv/bin/python
export OMP_NUM_THREADS=4 OPENBLAS_NUM_THREADS=4 MKL_NUM_THREADS=4
PYTHONPATH=$PWD/../base /usr/bin/time -v $PY ../pair_side.py $H 8 2026 ../out/base > ../out/base.log 2> ../out/base.err
PYTHONPATH=$PWD/../head /usr/bin/time -v $PY ../pair_side.py $H 8 2026 ../out/head > ../out/head.log 2> ../out/head.err
$PY ../compare.py ../out/base ../out/head ../out/compare.jsonThe by-state notes come from this read-only script, run as by_state.py"""Read-only checks behind the by-state notes.
python -I by_state.py <out_dir> <dataset.h5> <head>/policyengine_us/parameters/gov/local/md/flat_rate.yaml
"""
import json
import sys
import numpy as np
import pandas as pd
import yaml
out, dataset, flat_rate_yaml = sys.argv[1], sys.argv[2], sys.argv[3]
b = pd.read_pickle(f"{out}/base/tax_unit.pkl")
h = pd.read_pickle(f"{out}/head/tax_unit.pkl")
assert np.array_equal(b.tax_unit_id, h.tax_unit_id) and np.array_equal(b.weight, h.weight)
w = b.weight.to_numpy(dtype=float)
R = b.itemized_taxable_income_deductions_reduction.to_numpy(dtype=float)
cap = b.salt_cap.to_numpy(dtype=float)
ids = b.tax_unit_id.to_numpy()
def diff(name, frame_b=b, frame_h=h):
return frame_h[name].to_numpy(dtype=float) - frame_b[name].to_numpy(dtype=float)
def summary(mask, d):
return {
"records": int(mask.sum()),
"weight": float(w[mask].sum()),
"weighted_change": float((d[mask] * w[mask]).sum()),
"min_abs": float(np.abs(d[mask]).min()) if mask.any() else 0.0,
"max_abs": float(np.abs(d[mask]).max()) if mask.any() else 0.0,
"all_R_positive": bool((R[mask] > 0).all()),
}
def top(mask, d, n=3):
order = np.argsort(-np.abs(d * w * mask))[:n]
total = float((d[mask] * w[mask]).sum())
return [
{"id": int(ids[i]), "weight": float(w[i]), "diff": float(d[i]),
"weighted": float(d[i] * w[i]), "share": float(d[i] * w[i] / total)}
for i in order if mask[i]
]
facts = {"federal_R_positive": {"records": int((R > 0).sum()), "weight": float(w[R > 0].sum())}}
moved = lambda d: np.abs(d) > 1e-6
# Maryland
d_itm, d_ded, d_tax = diff("md_itemized_deductions"), diff("md_deductions"), diff("md_income_tax")
facts["md"] = {
"itemized": summary(moved(d_itm), d_itm),
"deductions": summary(moved(d_ded), d_ded),
"tax": summary(moved(d_tax), d_tax),
"tax_top": top(moved(d_tax), d_tax, 2),
"itemized_top": top(moved(d_itm), d_itm, 3),
"itemized_not_deductions": [
{"id": int(ids[i]), "weight": float(w[i]),
"md_itemized_base": float(b.md_itemized_deductions.iloc[i]),
"md_itemized_head": float(h.md_itemized_deductions.iloc[i]),
"md_deductions_both": [float(b.md_deductions.iloc[i]), float(h.md_deductions.iloc[i])]}
for i in np.flatnonzero(moved(d_itm) & ~moved(d_ded))
],
"tax_rows_equal_deduction_rows": bool(np.array_equal(moved(d_tax), moved(d_ded))),
}
# Maine
d_pre, d_med, d_mtax = diff("me_itemized_deductions_pre_phaseout"), diff("me_deductions"), diff("me_income_tax")
m = moved(d_pre)
facts["me"] = {
"pre_phaseout": summary(m, d_pre),
"me_deductions_on_those_rows": sorted({float(x) for x in b.me_deductions[m]} | {float(x) for x in h.me_deductions[m]}),
"me_deductions_moved": int(moved(d_med).sum()),
"me_income_tax_moved": int(moved(d_mtax).sum()),
}
# New Mexico
d_nitm, d_nded, d_add, d_ntax = (diff(n) for n in ("nm_itemized_deductions", "nm_deductions", "nm_salt_add_back", "nm_income_tax"))
big, small = np.abs(d_add) > 0.01, moved(d_add) & (np.abs(d_add) <= 0.01)
facts["nm"] = {
"itemized": summary(moved(d_nitm), d_nitm),
"itemized_top": top(moved(d_nitm), d_nitm, 2),
"deductions": summary(moved(d_nded), d_nded),
"deductions_rows_subset_of_itemized_rows": bool((moved(d_nded) <= moved(d_nitm)).all()),
"addback_over_1c": {**summary(big, d_add),
"all_down": bool((d_add[big] < 0).all()),
"R_zero": int((R[big] == 0).sum()),
"salt_caps": sorted(float(x) for x in cap[big]),
"top": top(big, d_add, 1)},
"addback_sub_1c": {"records": int(small.sum()), "max_abs": float(np.abs(d_add[small]).max()),
"weight_up": float(w[small & (d_add > 0)].sum()),
"weight_down": float(w[small & (d_add < 0)].sum()),
"weighted_change": float((d_add[small] * w[small]).sum()),
"all_R_zero": bool((R[small] == 0).all()),
"salt_caps": sorted({float(x) for x in cap[small]}),
"nm_income_tax_moved_on_them": int((small & moved(d_ntax)).sum())},
"tax_up": summary(moved(d_ntax) & (d_ntax > 0), d_ntax),
"tax_down": summary(moved(d_ntax) & (d_ntax < 0), d_ntax),
"tax_down_rows_equal_addback_over_1c_rows": bool(np.array_equal(moved(d_ntax) & (d_ntax < 0), big)),
"tax_top": top(moved(d_ntax), d_ntax, 2),
}
# Households: link tax units to households through the dataset's person table.
with pd.HDFStore(dataset, mode="r") as store:
person = store["/person"][["person_household_id", "person_tax_unit_id"]]
places = store["/household"][["household_id", "state_fips", "county_fips"]].set_index("household_id")
link = person.drop_duplicates("person_tax_unit_id").set_index("person_tax_unit_id").person_household_id
tu = pd.DataFrame({"household_id": link.reindex(ids).to_numpy(),
"d_md": d_tax, "d_nm": d_ntax, "d_state": diff("state_income_tax"), "d_fed": diff("income_tax"),
"d_md_ded": d_ded})
assert tu.household_id.notna().all()
per_household = tu.groupby("household_id").sum()
# Records with a section 68 reduction, by state (FIPS 24 MD, 23 ME, 35 NM).
state = places.state_fips.reindex(tu.household_id).to_numpy()
by_state_R = {}
for code, fips, first in (("MD", 24, d_itm), ("ME", 23, d_pre), ("NM", 35, d_nitm)):
in_state, has_R = state == fips, R > 0
rest = in_state & has_R & ~moved(first)
by_state_R[code] = {
"R_positive_records": int((in_state & has_R).sum()),
"R_positive_weight": float(w[in_state & has_R].sum()),
"moved_records": int((in_state & has_R & moved(first)).sum()),
"moved_outside_state_or_without_R": int((moved(first) & ~(in_state & has_R)).sum()),
"rest_records": int(rest.sum()),
"rest_weight": float(w[rest].sum()),
}
md_rest = (state == 24) & (R > 0) & ~moved(d_itm)
by_state_R["MD"]["rest_md_itemized_values"] = sorted({float(x) for x in b.md_itemized_deductions[md_rest]} | {float(x) for x in h.md_itemized_deductions[md_rest]})
me_R = (state == 23) & (R > 0)
by_state_R["ME"]["me_deductions_values_all_R"] = sorted({float(x) for x in b.me_deductions[me_R]} | {float(x) for x in h.me_deductions[me_R]})
me_rest = me_R & ~moved(d_pre)
by_state_R["ME"]["rest_pre_phaseout_values"] = sorted({round(float(x), 2) for x in b.me_itemized_deductions_pre_phaseout[me_rest]} | {round(float(x), 2) for x in h.me_itemized_deductions_pre_phaseout[me_rest]})
by_state_R["ME"]["me_deductions_positive_with_R"] = int((me_R & ((b.me_deductions > 0) | (h.me_deductions > 0)).to_numpy()).sum())
facts["section_68_by_state"] = by_state_R
hb, hh = pd.read_pickle(f"{out}/base/household.pkl"), pd.read_pickle(f"{out}/head/household.pkl")
hw = hb.weight.to_numpy(dtype=float)
hd = pd.DataFrame({"weight": hw, "d_hni": diff("household_net_income", hb, hh)}, index=hb.household_id)
hd = hd.join(per_household, how="left").fillna(0.0)
hm = hd[np.abs(hd.d_hni) > 1e-6]
md_h, nm_h = hm[np.abs(hm.d_md) > 1e-6], hm[np.abs(hm.d_nm) > 1e-6]
facts["households"] = {
"moved": int(len(hm)),
"md": int(len(md_h)), "nm": int(len(nm_h)), "both_or_neither": int(len(hm) - len(md_h) - len(nm_h)),
"md_weighted_d_hni": float((md_h.d_hni * md_h.weight).sum()),
"md_weighted_d_tax": float((md_h.d_md * md_h.weight).sum()),
"md_weighted_gap": float(((md_h.d_hni + md_h.d_md) * md_h.weight).sum()),
"nm_max_abs_gap": float(np.abs(nm_h.d_hni + nm_h.d_nm).max()),
"nm_weighted_gap": float(((nm_h.d_hni + nm_h.d_nm) * nm_h.weight).sum()),
"any_federal_change": bool((np.abs(hd.d_fed) > 1e-6).any()),
}
# Maryland county tax: per household, (change in net income + change in MD tax)
# divided by the change in md_deductions, against the county's flat rate.
FIPS_KEYS = {24005: "BALTIMORE_COUNTY_MD", 24025: "HARFORD_COUNTY_MD", 24031: "MONTGOMERY_COUNTY_MD",
24033: "PRINCE_GEORGE_S_COUNTY_MD", 24043: "WASHINGTON_COUNTY_MD", 24510: "BALTIMORE_CITY_MD"}
rates = yaml.safe_load(open(flat_rate_yaml))
def rate_2026(key):
values = rates[key]
return float(values[max(k for k in values if str(k) <= "2026-01-01")])
county_rows = []
for household, row in md_h.iterrows():
fips = int(places.county_fips.loc[household])
county_rows.append({
"household_id": int(household), "weight": float(row.weight), "county_fips": fips,
"d_hni": float(row.d_hni), "d_md_tax": float(row.d_md), "d_md_deductions": float(row.d_md_ded),
"gap_over_d_md_deductions": float((row.d_hni + row.d_md) / row.d_md_ded),
"county_rate_2026": rate_2026(FIPS_KEYS[fips]) if fips in FIPS_KEYS else None,
})
facts["md_county"] = sorted(county_rows, key=lambda r: r["county_fips"])
print(json.dumps(facts, indent=1))compare.json{
"household_net_income": {
"entity": "household",
"base_sum": 17161633654134.797,
"head_sum": 17161614932284.938,
"change": -18721849.859375,
"records_changed": 17,
"weighted_changed": 19483.549516650208,
"weighted_up": 3053.961045373755,
"weighted_down": 16429.588471276453,
"largest_abs": 1726.0,
"rows": [
{
"id": 1044603,
"base": 447266.875,
"head": 448992.875
},
{
"id": 1019711,
"base": 1193200.5,
"head": 1191523.25
},
{
"id": 1020204,
"base": 1184917.0,
"head": 1183440.625
},
{
"id": 1019684,
"base": 957511.25,
"head": 956445.125
},
{
"id": 1044295,
"base": 631847.1875,
"head": 632834.4375
}
]
},
"household_benefits": {
"entity": "household",
"base_sum": 2059999418114.8687,
"head_sum": 2059999418114.8687,
"change": 0.0,
"records_changed": 0,
"weighted_changed": 0.0,
"weighted_up": 0.0,
"weighted_down": 0.0,
"largest_abs": 0.0,
"rows": []
},
"md_itemized_deductions": {
"entity": "tax_unit",
"base_sum": 23633476785.28398,
"head_sum": 23388822375.288548,
"change": -244654409.9954338,
"records_changed": 10,
"weighted_changed": 29441.329996718676,
"weighted_up": 0.0,
"weighted_down": 29441.329996718676,
"largest_abs": 17291.09375,
"rows": [
{
"id": 1025837,
"base": 191181.671875,
"head": 173890.578125
},
{
"id": 1026192,
"base": 159868.40625,
"head": 144648.34375
},
{
"id": 1025806,
"base": 131547.375,
"head": 120096.125
},
{
"id": 1101936,
"base": 42690.3515625,
"head": 35975.03125
},
{
"id": 1102460,
"base": 58243.49609375,
"head": 52286.62890625
}
]
},
"md_deductions": {
"entity": "tax_unit",
"base_sum": 30732223727.406742,
"head_sum": 30535874164.868927,
"change": -196349562.5378151,
"records_changed": 9,
"weighted_changed": 14414.778238906176,
"weighted_up": 0.0,
"weighted_down": 14414.778238906176,
"largest_abs": 17291.09375,
"rows": [
{
"id": 1025837,
"base": 191181.671875,
"head": 173890.578125
},
{
"id": 1026192,
"base": 159868.40625,
"head": 144648.34375
},
{
"id": 1025806,
"base": 131547.375,
"head": 120096.125
},
{
"id": 1101936,
"base": 42690.3515625,
"head": 35975.03125
},
{
"id": 1102460,
"base": 58243.49609375,
"head": 52286.62890625
}
]
},
"me_itemized_deductions_pre_phaseout": {
"entity": "tax_unit",
"base_sum": 4551828204.946152,
"head_sum": 4551828186.738266,
"change": -18.2078857421875,
"records_changed": 5,
"weighted_changed": 0.02885818830691278,
"weighted_up": 0.0,
"weighted_down": 0.02885818830691278,
"largest_abs": 1418.71484375,
"rows": [
{
"id": 1000163,
"base": 36512.3359375,
"head": 35093.62109375
},
{
"id": 1075142,
"base": 20559.984375,
"head": 19689.9609375
},
{
"id": 1000364,
"base": 2602.8994140625,
"head": 2462.2021484375
},
{
"id": 1074793,
"base": 10606.58203125,
"head": 10537.017578125
},
{
"id": 1000277,
"base": 688.5829467773438,
"head": 683.6682739257812
}
]
},
"me_deductions": {
"entity": "tax_unit",
"base_sum": 16787410277.648087,
"head_sum": 16787410277.648087,
"change": 0.0,
"records_changed": 0,
"weighted_changed": 0.0,
"weighted_up": 0.0,
"weighted_down": 0.0,
"largest_abs": 0.0,
"rows": []
},
"nm_itemized_deductions": {
"entity": "tax_unit",
"base_sum": 8805151058.535755,
"head_sum": 8793109978.29378,
"change": -12041080.241975784,
"records_changed": 15,
"weighted_changed": 2034.0039223907806,
"weighted_up": 0.0,
"weighted_down": 2034.0039223907806,
"largest_abs": 11240.015625,
"rows": [
{
"id": 1058575,
"base": 207940.375,
"head": 196700.359375
},
{
"id": 1058500,
"base": 110465.984375,
"head": 104494.8515625
},
{
"id": 1058371,
"base": 37485.25390625,
"head": 35459.0234375
},
{
"id": 1214044,
"base": 18944.63671875,
"head": 17920.6015625
},
{
"id": 1058854,
"base": 10000.0,
"head": 9459.458984375
}
]
},
"nm_salt_add_back": {
"entity": "tax_unit",
"base_sum": 660548696.7935939,
"head_sum": 649875516.3280382,
"change": -10673180.465555668,
"records_changed": 32,
"weighted_changed": 13793.982002581062,
"weighted_up": 10739.984198251623,
"weighted_down": 3053.9978043294395,
"largest_abs": 29254.455078125,
"rows": [
{
"id": 1057795,
"base": 31979.166015625,
"head": 2724.7109375
},
{
"id": 1058371,
"base": 38118.0625,
"head": 19359.0234375
},
{
"id": 1135947,
"base": 22083.484375,
"head": 6967.556640625
},
{
"id": 1135738,
"base": 21422.79296875,
"head": 15915.875
},
{
"id": 1213136,
"base": 28620.40234375,
"head": 25125.625
}
]
},
"nm_deductions": {
"entity": "tax_unit",
"base_sum": 29301203896.830112,
"head_sum": 29289173192.56698,
"change": -12030704.263134003,
"records_changed": 3,
"weighted_changed": 2014.8111841565697,
"weighted_up": 0.0,
"weighted_down": 2014.8111841565697,
"largest_abs": 11240.015625,
"rows": [
{
"id": 1058575,
"base": 211940.375,
"head": 200700.359375
},
{
"id": 1058500,
"base": 110465.984375,
"head": 104494.8515625
},
{
"id": 1058371,
"base": 39985.25390625,
"head": 37959.0234375
}
]
},
"itemized_taxable_income_deductions_reduction": {
"entity": "tax_unit",
"base_sum": 10599253278.9609,
"head_sum": 10599253278.9609,
"change": 0.0,
"records_changed": 0,
"weighted_changed": 0.0,
"weighted_up": 0.0,
"weighted_down": 0.0,
"largest_abs": 0.0,
"rows": []
},
"salt_cap": {
"entity": "tax_unit",
"base_sum": 6513011228769.599,
"head_sum": 6513011228769.599,
"change": 0.0,
"records_changed": 0,
"weighted_changed": 0.0,
"weighted_up": 0.0,
"weighted_down": 0.0,
"largest_abs": 0.0,
"rows": []
},
"md_income_tax": {
"entity": "tax_unit",
"base_sum": 15156844198.633507,
"head_sum": 15169347574.545631,
"change": 12503375.912124634,
"records_changed": 9,
"weighted_changed": 14414.778238906176,
"weighted_up": 14414.778238906176,
"weighted_down": 0.0,
"largest_abs": 1123.9140625,
"rows": [
{
"id": 1025837,
"base": 83650.2265625,
"head": 84774.140625
},
{
"id": 1026192,
"base": 97117.0,
"head": 98106.296875
},
{
"id": 1025806,
"base": 75335.8828125,
"head": 76051.5859375
},
{
"id": 1101936,
"base": 71413.6484375,
"head": 71850.1484375
},
{
"id": 1026326,
"base": 72448.5234375,
"head": 72834.09375
}
]
},
"me_income_tax": {
"entity": "tax_unit",
"base_sum": 2798402934.942308,
"head_sum": 2798402934.942308,
"change": 0.0,
"records_changed": 0,
"weighted_changed": 0.0,
"weighted_up": 0.0,
"weighted_down": 0.0,
"largest_abs": 0.0,
"rows": []
},
"nm_income_tax": {
"entity": "tax_unit",
"base_sum": 2257269570.331682,
"head_sum": 2257349671.2469015,
"change": 80100.91521930695,
"records_changed": 8,
"weighted_changed": 5068.771277744032,
"weighted_up": 2014.810232370277,
"weighted_down": 3053.961045373755,
"largest_abs": 1726.015625,
"rows": [
{
"id": 1057795,
"base": 34923.45703125,
"head": 33197.44140625
},
{
"id": 1058371,
"base": 56983.65234375,
"head": 55996.41015625
},
{
"id": 1135947,
"base": 30356.154296875,
"head": 29464.314453125
},
{
"id": 1058575,
"base": 54533.76171875,
"head": 55196.921875
},
{
"id": 1058500,
"base": 97034.1484375,
"head": 97386.4453125
}
]
},
"income_tax": {
"entity": "tax_unit",
"base_sum": 2595105852713.508,
"head_sum": 2595105852713.508,
"change": 0.0,
"records_changed": 0,
"weighted_changed": 0.0,
"weighted_up": 0.0,
"weighted_down": 0.0,
"largest_abs": 0.0,
"rows": []
},
"state_income_tax": {
"entity": "tax_unit",
"base_sum": 578044599346.724,
"head_sum": 578057182823.5513,
"change": 12583476.827270508,
"records_changed": 17,
"weighted_changed": 19483.549516650208,
"weighted_up": 16429.588471276453,
"weighted_down": 3053.961045373755,
"largest_abs": 1726.015625,
"rows": [
{
"id": 1057795,
"base": 34923.45703125,
"head": 33197.44140625
},
{
"id": 1025837,
"base": 83650.2265625,
"head": 84774.140625
},
{
"id": 1026192,
"base": 97117.0,
"head": 98106.296875
},
{
"id": 1058371,
"base": 56983.65234375,
"head": 55996.41015625
},
{
"id": 1135947,
"base": 30356.154296875,
"head": 29464.314453125
}
]
}
}Generated by Claude Code |
|
US + core hub merge audit, us#10594 at
|
Brings in main since the November 2 merge, including #10594's state section 68 changes, which share no file or line with this branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6GA1FXMB3qVPzGgvWQ
Applies the 2026 federal section 68 reduction (P.L. 119-21, sec. 70111) in Maryland, Maine and New Mexico itemized deductions. Each state starts from federal itemized deductions, but on main all three read the total from before the section 68 reduction, so they overstate deductions for high-income itemizers from 2026. Each fix applies only while
gov.irs.deductions.itemized.limitation.obbb.appliesis true (from 2026), so earlier years are unchanged. That includes the pre-2018 Pease years, which are out of scope here.Maryland (
us-md-itemized-section-68-2026)Source. Md. Code, Tax-General § 10-218(b)(1), (b)(3) and (c). (b) allows "the sum of the individual's federal itemized deductions", (b)(1) "limited and reduced as required under the Internal Revenue Code", and (b)(3) "further reduced by the amount claimed as taxes on income paid to a state or political subdivision of a state, after subtracting a pro rata portion of the reduction to itemized deductions required under § 68 of the Internal Revenue Code." (Text from mgaleg.maryland.gov, read 2026-11-02.)
What changed.
md_itemized_deductions.py:32-43. From 2026 this PR scales the Maryland base (non-SALT deductions plus capped property taxes) by1 - R/T, where T istotal_itemized_taxable_income_deductionsand R isitemized_taxable_income_deductions_reduction. The Maryland phaseout and zero floor follow as before. Taking the income-tax component as I = T - base, the spec's formula T - R - I + R·I/T equals base·(1 - R/T) for every input, negative I included, and gives exactlybaseat R = 0. The two forms differ only where a zero floor on I binds. T - base equals the income taxes inside the federal SALT deduction only whencapped_property_taxesequals the property taxes inside that deduction. Where the phased-down SALT cap binds, Maryland still counts property taxes up to the statutory cap (an existing gap; see follow-ups below), and this PR scales that amount by (1 - R/T) too. No Maryland parameter changed.Witness and years. 2026, single, $1M AGI, $100,000 of deductions, R = $5,405.41. Main gives $40,000; this PR gives $34,594.59. The spec's SALT-restoration case also gives $34,594.59.
Invariants.
Maine (
us-me-itemized-section-68-2026), confidence mediumSource. 36 M.R.S. § 5125(1), (3), (4), (7) and § 111(1-A). (1) lets "an individual who has claimed itemized deductions from federal adjusted gross income in determining the individual's federal taxable income" claim Maine itemized deductions. (3)(A) reduces the federal sum by "any amount attributable to income taxes or sales and use taxes", and (3)(A-1) increases it by "the amount of property taxes not claimed under the Code, Section 164(a)(1) and (2) as a result of the limitation under the Code, Section 164(b)(6)(B)". § 111(1-A) defines the Code as the IRC "and amendments to that Code as of December 31, 2025", so it includes the section 68 that P.L. 119-21 rewrote. (Text from legislature.maine.gov, read 2026-11-02.)
What changed.
me_itemized_deductions_pre_phaseout.py:33-60. From 2026 this PR scales the non-SALT deductions and the medical carve-out by q = F/T, where F isitemized_taxable_income_deductions. Property taxes inside the federal SALT deduction keep their section 68 reduction, while property taxes the SALT cap excluded come back in full under § 5125(3)(A-1). The cap, the medical exception and the later phaseout are unchanged.Allocation (hub ruling
us-me:itemized-section-68-property-tax-allocation-2026). The hub kept the allocation as built: pro rata throughout.salt_deduction · real_estate_taxes / salt). Every filer with a section 68 cut has income above the 37% bracket start, which puts the SALT cap at its $10,000 floor, so this choice moves at most 2/37 × $10,000 = $540.54 of Maine deduction.The no-SALT, no-medical witnesses establish the bug independently of these choices.
Witness and years. 2026, $20,000 of non-SALT deductions, R = $1,081.08. Main gives $20,000; this PR gives $18,918.92 (also in
me_deductions).Invariants.
New Mexico (
us-nm-itemized-raw-total-2026)Source. NMSA 1978 § 7-2-2(N)(2), Laws 2023, ch. 159, § 1. The excerpt given in the hub spec (not fetched in this session) reads: "Internal Revenue Code, as that section may be amended or renumbered, allowed the taxpayer for the taxpayer's taxable year". The 2025 PIT-1 instructions, line 12, ask for the "allowable federal itemized deductions" (spec excerpt).
What changed.
nm_itemized_deductions.py:18-23: the variable now has a formula, which returnsitemized_taxable_income_deductionsfrom 2026 and the previous total before.nm_salt_add_back.py:44-49: from 2026, the standard-deduction safeguard compares the allowed total. Main reconstructed the total there instead.salt_cap) instead of the statutory cap. This lowers the addback, and NM tax, for 2026+ itemizers whose SALT cap phases down, even when the section 68 reduction is zero (example: single, $550,000 wages, $10,000 property tax, $20,000 income tax: addback $13,900 on main, $10,800 here). The addback numerator still uses the statutory cap (follow-up below).Witness and years. 2026 onward, single, $2M of income, $37,000 of deductions. Main gives $37,000; this PR gives $35,000. A SALT addback case in which the allowed total exceeds the standard deduction by $81.08: main gives $1,000, this PR gives $81.08.
Invariants.
Tests
policyengine_us/tests/test_state_itemized_section_68_2026.pyis a Hypothesis property test. It checks the Maryland, Maine and New Mexico identities above with randomly drawn totals and strictly positive reductions, in one simulation per example. It cannot pass trivially: each identity fails on main whenever R > 0, and each reverted fix fails it.Follow-ups (not in this PR)
capped_property_taxesand New Mexico'snm_salt_add_backnumerator both cap SALT at the statutory amount rather than the phased-downsalt_cap. Both behave the same on main.Extra files
changelog.d/section-68-2026-md-me-nm.fixed.md.impact: we estimate that Maryland income tax rises $12.5 million in 2026 on 9 records of the default dataset (14,415 weighted tax units). Those households also pay $6.1 million more county income tax. New Mexico income tax rises $80,101 on net: it rises on 2 records and falls on 6 whose SALT addback falls. Household net income falls $18.7 million in all. Maine's itemized deduction moves on 5 records with a combined weight of 0.03, and no Maine tax moves. Two records carry all but $41 of the Maryland change, so these totals carry wide sampling error. Federal income tax and benefits don't move.
axiom: TheAxiomFoundation/rulespec-us#1999 queued (Maryland); TheAxiomFoundation/rulespec-us#2000 queued (Maine); TheAxiomFoundation/rulespec-us#2001 queued (New Mexico)
🤖 Generated with Claude Code