@invaro/opentax 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bundle/index.js CHANGED
@@ -1035,15 +1035,15 @@ var require_codegen = __commonJS({
1035
1035
  }
1036
1036
  exports.not = not;
1037
1037
  var andCode = mappend(exports.operators.AND);
1038
- function and(...args) {
1038
+ function and2(...args) {
1039
1039
  return args.reduce(andCode);
1040
1040
  }
1041
- exports.and = and;
1041
+ exports.and = and2;
1042
1042
  var orCode = mappend(exports.operators.OR);
1043
- function or(...args) {
1043
+ function or2(...args) {
1044
1044
  return args.reduce(orCode);
1045
1045
  }
1046
- exports.or = or;
1046
+ exports.or = or2;
1047
1047
  function mappend(op) {
1048
1048
  return (x, y) => x === code_1.nil ? y : y === code_1.nil ? x : (0, code_1._)`${par(x)} ${op} ${par(y)}`;
1049
1049
  }
@@ -3104,15 +3104,16 @@ var require_data = __commonJS({
3104
3104
  }
3105
3105
  });
3106
3106
 
3107
- // ../../node_modules/.pnpm/fast-uri@3.1.3/node_modules/fast-uri/lib/utils.js
3107
+ // ../../node_modules/.pnpm/fast-uri@4.1.1/node_modules/fast-uri/lib/utils.js
3108
3108
  var require_utils = __commonJS({
3109
- "../../node_modules/.pnpm/fast-uri@3.1.3/node_modules/fast-uri/lib/utils.js"(exports, module) {
3109
+ "../../node_modules/.pnpm/fast-uri@4.1.1/node_modules/fast-uri/lib/utils.js"(exports, module) {
3110
3110
  "use strict";
3111
3111
  var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
3112
3112
  var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
3113
3113
  var isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu);
3114
3114
  var isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu);
3115
3115
  var isPathCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/]$/iu);
3116
+ var isQueryFragmentCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/?]$/iu);
3116
3117
  function stringArrayToHexStripped(input) {
3117
3118
  let acc = "";
3118
3119
  let code = 0;
@@ -3255,7 +3256,7 @@ var require_utils = __commonJS({
3255
3256
  continue;
3256
3257
  }
3257
3258
  } else if (input[0] === "/") {
3258
- if (input[1] === "." || input[1] === "/") {
3259
+ if (input[1] === ".") {
3259
3260
  output.push("/");
3260
3261
  break;
3261
3262
  }
@@ -3337,10 +3338,30 @@ var require_utils = __commonJS({
3337
3338
  }
3338
3339
  return output;
3339
3340
  }
3341
+ var BYTE_HEX = new Array(256);
3342
+ {
3343
+ const HEX_DIGITS = "0123456789ABCDEF";
3344
+ for (let i = 0; i < 256; i++) {
3345
+ BYTE_HEX[i] = "%" + HEX_DIGITS[i >> 4] + HEX_DIGITS[i & 15];
3346
+ }
3347
+ }
3348
+ function isEscapeSafe(cp) {
3349
+ return cp >= 48 && cp <= 57 || cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122 || cp === 42 || cp === 43 || cp === 45 || cp === 46 || cp === 47 || cp === 64 || cp === 95;
3350
+ }
3351
+ function percentEncodeNonAscii(cp) {
3352
+ if (cp < 2048) {
3353
+ return BYTE_HEX[192 | cp >> 6] + BYTE_HEX[128 | cp & 63];
3354
+ }
3355
+ if (cp < 65536) {
3356
+ return BYTE_HEX[224 | cp >> 12] + BYTE_HEX[128 | cp >> 6 & 63] + BYTE_HEX[128 | cp & 63];
3357
+ }
3358
+ return BYTE_HEX[240 | cp >> 18] + BYTE_HEX[128 | cp >> 12 & 63] + BYTE_HEX[128 | cp >> 6 & 63] + BYTE_HEX[128 | cp & 63];
3359
+ }
3340
3360
  function normalizePathEncoding(input) {
3341
3361
  let output = "";
3342
3362
  for (let i = 0; i < input.length; i++) {
3343
- if (input[i] === "%" && i + 2 < input.length) {
3363
+ const ch = input[i];
3364
+ if (ch === "%" && i + 2 < input.length) {
3344
3365
  const hex = input.slice(i + 1, i + 3);
3345
3366
  if (isHexPair(hex)) {
3346
3367
  const normalizedHex = hex.toUpperCase();
@@ -3354,10 +3375,66 @@ var require_utils = __commonJS({
3354
3375
  continue;
3355
3376
  }
3356
3377
  }
3357
- if (isPathCharacter(input[i])) {
3358
- output += input[i];
3378
+ if (isPathCharacter(ch)) {
3379
+ output += ch;
3359
3380
  } else {
3360
- output += escape(input[i]);
3381
+ const code = input.charCodeAt(i);
3382
+ if (code < 128) {
3383
+ output += isEscapeSafe(code) ? ch : BYTE_HEX[code];
3384
+ } else if (code < 55296 || code > 57343) {
3385
+ output += percentEncodeNonAscii(code);
3386
+ } else if (code <= 56319 && i + 1 < input.length) {
3387
+ const low = input.charCodeAt(i + 1);
3388
+ if (low >= 56320 && low <= 57343) {
3389
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3390
+ i++;
3391
+ } else {
3392
+ output += percentEncodeNonAscii(65533);
3393
+ }
3394
+ } else {
3395
+ output += percentEncodeNonAscii(65533);
3396
+ }
3397
+ }
3398
+ }
3399
+ return output;
3400
+ }
3401
+ function normalizeQueryFragmentEncoding(input) {
3402
+ let output = "";
3403
+ for (let i = 0; i < input.length; i++) {
3404
+ const ch = input[i];
3405
+ if (ch === "%" && i + 2 < input.length) {
3406
+ const hex = input.slice(i + 1, i + 3);
3407
+ if (isHexPair(hex)) {
3408
+ const normalizedHex = hex.toUpperCase();
3409
+ const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
3410
+ if (isUnreserved(decoded)) {
3411
+ output += decoded;
3412
+ } else {
3413
+ output += "%" + normalizedHex;
3414
+ }
3415
+ i += 2;
3416
+ continue;
3417
+ }
3418
+ }
3419
+ if (isQueryFragmentCharacter(ch)) {
3420
+ output += ch;
3421
+ } else {
3422
+ const code = input.charCodeAt(i);
3423
+ if (code < 128) {
3424
+ output += isEscapeSafe(code) ? ch : BYTE_HEX[code];
3425
+ } else if (code < 55296 || code > 57343) {
3426
+ output += percentEncodeNonAscii(code);
3427
+ } else if (code <= 56319 && i + 1 < input.length) {
3428
+ const low = input.charCodeAt(i + 1);
3429
+ if (low >= 56320 && low <= 57343) {
3430
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3431
+ i++;
3432
+ } else {
3433
+ output += percentEncodeNonAscii(65533);
3434
+ }
3435
+ } else {
3436
+ output += percentEncodeNonAscii(65533);
3437
+ }
3361
3438
  }
3362
3439
  }
3363
3440
  return output;
@@ -3365,7 +3442,8 @@ var require_utils = __commonJS({
3365
3442
  function escapePreservingEscapes(input) {
3366
3443
  let output = "";
3367
3444
  for (let i = 0; i < input.length; i++) {
3368
- if (input[i] === "%" && i + 2 < input.length) {
3445
+ const ch = input[i];
3446
+ if (ch === "%" && i + 2 < input.length) {
3369
3447
  const hex = input.slice(i + 1, i + 3);
3370
3448
  if (isHexPair(hex)) {
3371
3449
  output += "%" + hex.toUpperCase();
@@ -3373,7 +3451,22 @@ var require_utils = __commonJS({
3373
3451
  continue;
3374
3452
  }
3375
3453
  }
3376
- output += escape(input[i]);
3454
+ const code = input.charCodeAt(i);
3455
+ if (code < 128) {
3456
+ output += isEscapeSafe(code) ? ch : BYTE_HEX[code];
3457
+ } else if (code < 55296 || code > 57343) {
3458
+ output += percentEncodeNonAscii(code);
3459
+ } else if (code <= 56319 && i + 1 < input.length) {
3460
+ const low = input.charCodeAt(i + 1);
3461
+ if (low >= 56320 && low <= 57343) {
3462
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3463
+ i++;
3464
+ } else {
3465
+ output += percentEncodeNonAscii(65533);
3466
+ }
3467
+ } else {
3468
+ output += percentEncodeNonAscii(65533);
3469
+ }
3377
3470
  }
3378
3471
  return output;
3379
3472
  }
@@ -3407,6 +3500,7 @@ var require_utils = __commonJS({
3407
3500
  reescapeHostDelimiters,
3408
3501
  normalizePercentEncoding,
3409
3502
  normalizePathEncoding,
3503
+ normalizeQueryFragmentEncoding,
3410
3504
  escapePreservingEscapes,
3411
3505
  removeDotSegments,
3412
3506
  isIPv4,
@@ -3417,9 +3511,9 @@ var require_utils = __commonJS({
3417
3511
  }
3418
3512
  });
3419
3513
 
3420
- // ../../node_modules/.pnpm/fast-uri@3.1.3/node_modules/fast-uri/lib/schemes.js
3514
+ // ../../node_modules/.pnpm/fast-uri@4.1.1/node_modules/fast-uri/lib/schemes.js
3421
3515
  var require_schemes = __commonJS({
3422
- "../../node_modules/.pnpm/fast-uri@3.1.3/node_modules/fast-uri/lib/schemes.js"(exports, module) {
3516
+ "../../node_modules/.pnpm/fast-uri@4.1.1/node_modules/fast-uri/lib/schemes.js"(exports, module) {
3423
3517
  "use strict";
3424
3518
  var { isUUID } = require_utils();
3425
3519
  var URN_REG = /([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-.:;=@]|%[\da-f]{2})+)/iu;
@@ -3627,11 +3721,11 @@ var require_schemes = __commonJS({
3627
3721
  }
3628
3722
  });
3629
3723
 
3630
- // ../../node_modules/.pnpm/fast-uri@3.1.3/node_modules/fast-uri/index.js
3724
+ // ../../node_modules/.pnpm/fast-uri@4.1.1/node_modules/fast-uri/index.js
3631
3725
  var require_fast_uri = __commonJS({
3632
- "../../node_modules/.pnpm/fast-uri@3.1.3/node_modules/fast-uri/index.js"(exports, module) {
3726
+ "../../node_modules/.pnpm/fast-uri@4.1.1/node_modules/fast-uri/index.js"(exports, module) {
3633
3727
  "use strict";
3634
- var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
3728
+ var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, normalizeQueryFragmentEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
3635
3729
  var { SCHEMES, getSchemeHandler } = require_schemes();
3636
3730
  function normalize(uri, options) {
3637
3731
  if (typeof uri === "string") {
@@ -3770,6 +3864,7 @@ var require_fast_uri = __commonJS({
3770
3864
  return uriTokens.join("");
3771
3865
  }
3772
3866
  var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
3867
+ var AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/;
3773
3868
  function getParseError(parsed, matches) {
3774
3869
  if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
3775
3870
  return 'URI path must start with "/" when authority is present.';
@@ -3799,9 +3894,14 @@ var require_fast_uri = __commonJS({
3799
3894
  uri = "//" + uri;
3800
3895
  }
3801
3896
  }
3897
+ const authorityMatch = uri.match(AUTHORITY_PREFIX);
3898
+ if (authorityMatch !== null && authorityMatch[1].indexOf("\\") !== -1) {
3899
+ parsed.error = "URI authority must not contain a literal backslash.";
3900
+ malformedAuthorityOrPort = true;
3901
+ }
3802
3902
  const matches = uri.match(URI_PARSE);
3803
3903
  if (matches) {
3804
- parsed.scheme = matches[1];
3904
+ parsed.scheme = matches[1] === void 0 ? void 0 : matches[1].toLowerCase();
3805
3905
  parsed.userinfo = matches[3];
3806
3906
  parsed.host = matches[4];
3807
3907
  parsed.port = parseInt(matches[5], 10);
@@ -3860,12 +3960,11 @@ var require_fast_uri = __commonJS({
3860
3960
  if (parsed.path) {
3861
3961
  parsed.path = normalizePathEncoding(parsed.path);
3862
3962
  }
3963
+ if (parsed.query) {
3964
+ parsed.query = normalizeQueryFragmentEncoding(parsed.query);
3965
+ }
3863
3966
  if (parsed.fragment) {
3864
- try {
3865
- parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment));
3866
- } catch {
3867
- parsed.error = parsed.error || "URI malformed";
3868
- }
3967
+ parsed.fragment = normalizeQueryFragmentEncoding(parsed.fragment);
3869
3968
  }
3870
3969
  }
3871
3970
  if (schemeHandler && schemeHandler.parse) {
@@ -7501,12 +7600,12 @@ var $ZodRealError = $constructor("$ZodError", initializer, { Parent: Error });
7501
7600
  function flattenError(error2, mapper = (issue2) => issue2.message) {
7502
7601
  const fieldErrors = {};
7503
7602
  const formErrors = [];
7504
- for (const sub of error2.issues) {
7505
- if (sub.path.length > 0) {
7506
- fieldErrors[sub.path[0]] = fieldErrors[sub.path[0]] || [];
7507
- fieldErrors[sub.path[0]].push(mapper(sub));
7603
+ for (const sub3 of error2.issues) {
7604
+ if (sub3.path.length > 0) {
7605
+ fieldErrors[sub3.path[0]] = fieldErrors[sub3.path[0]] || [];
7606
+ fieldErrors[sub3.path[0]].push(mapper(sub3));
7508
7607
  } else {
7509
- formErrors.push(mapper(sub));
7608
+ formErrors.push(mapper(sub3));
7510
7609
  }
7511
7610
  }
7512
7611
  return { formErrors, fieldErrors };
@@ -13015,7 +13114,7 @@ var StdioServerTransport = class {
13015
13114
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
13016
13115
  import { homedir } from "node:os";
13017
13116
  import { join } from "node:path";
13018
- var VERSION = "0.3.2";
13117
+ var VERSION = "0.4.0";
13019
13118
  var ENDPOINT = "https://opentax.invaro.ai/api/t";
13020
13119
  function optedOut() {
13021
13120
  return process.env.OPENTAX_TELEMETRY === "0" || process.env.OPENTAX_TELEMETRY === "false" || process.env.DO_NOT_TRACK === "1";
@@ -13334,8 +13433,8 @@ var ZodError2 = class _ZodError extends Error {
13334
13433
  constructor(issues) {
13335
13434
  super();
13336
13435
  this.issues = [];
13337
- this.addIssue = (sub) => {
13338
- this.issues = [...this.issues, sub];
13436
+ this.addIssue = (sub3) => {
13437
+ this.issues = [...this.issues, sub3];
13339
13438
  };
13340
13439
  this.addIssues = (subs = []) => {
13341
13440
  this.issues = [...this.issues, ...subs];
@@ -13402,13 +13501,13 @@ var ZodError2 = class _ZodError extends Error {
13402
13501
  flatten(mapper = (issue2) => issue2.message) {
13403
13502
  const fieldErrors = {};
13404
13503
  const formErrors = [];
13405
- for (const sub of this.issues) {
13406
- if (sub.path.length > 0) {
13407
- const firstEl = sub.path[0];
13504
+ for (const sub3 of this.issues) {
13505
+ if (sub3.path.length > 0) {
13506
+ const firstEl = sub3.path[0];
13408
13507
  fieldErrors[firstEl] = fieldErrors[firstEl] || [];
13409
- fieldErrors[firstEl].push(mapper(sub));
13508
+ fieldErrors[firstEl].push(mapper(sub3));
13410
13509
  } else {
13411
- formErrors.push(mapper(sub));
13510
+ formErrors.push(mapper(sub3));
13412
13511
  }
13413
13512
  }
13414
13513
  return { formErrors, fieldErrors };
@@ -21635,12 +21734,12 @@ function loadCorpus(input) {
21635
21734
  const byId = /* @__PURE__ */ new Map();
21636
21735
  const factById2 = /* @__PURE__ */ new Map();
21637
21736
  const overriders = /* @__PURE__ */ new Map();
21638
- for (const fact41 of input.facts) {
21639
- if (factById2.has(fact41.id))
21640
- issues.push(`duplicate fact id "${fact41.id}"`);
21641
- factById2.set(fact41.id, fact41);
21642
- if (fact41.type === "enum" && !fact41.enumValues?.length) {
21643
- issues.push(`enum fact "${fact41.id}" must declare enumValues`);
21737
+ for (const fact44 of input.facts) {
21738
+ if (factById2.has(fact44.id))
21739
+ issues.push(`duplicate fact id "${fact44.id}"`);
21740
+ factById2.set(fact44.id, fact44);
21741
+ if (fact44.type === "enum" && !fact44.enumValues?.length) {
21742
+ issues.push(`enum fact "${fact44.id}" must declare enumValues`);
21644
21743
  }
21645
21744
  }
21646
21745
  const seenVersions = /* @__PURE__ */ new Set();
@@ -22495,6 +22594,90 @@ function lookupParameters(corpus2, query, asOf) {
22495
22594
  hits.sort((a, b) => b.score - a.score || a.ruleId.localeCompare(b.ruleId));
22496
22595
  return hits.slice(0, 5);
22497
22596
  }
22597
+ var SNIPPET_LEN = 280;
22598
+ function makeSnippet(excerpt, tokens) {
22599
+ if (excerpt.length <= SNIPPET_LEN)
22600
+ return excerpt;
22601
+ const lower = excerpt.toLowerCase();
22602
+ let at = -1;
22603
+ for (const t of tokens) {
22604
+ const i = lower.indexOf(t);
22605
+ if (i !== -1 && (at === -1 || i < at))
22606
+ at = i;
22607
+ }
22608
+ const start = Math.max(0, Math.min(at === -1 ? 0 : at - 60, excerpt.length - SNIPPET_LEN));
22609
+ const cut = excerpt.slice(start, start + SNIPPET_LEN);
22610
+ return `${start > 0 ? "\u2026" : ""}${cut}${start + SNIPPET_LEN < excerpt.length ? "\u2026" : ""}`;
22611
+ }
22612
+ function searchRules(corpus2, query, asOf, limit = 8) {
22613
+ const allTokens = tokenize(query);
22614
+ if (allTokens.length === 0)
22615
+ return [];
22616
+ const haystacks = corpus2.rules.map((rule) => {
22617
+ const paramWords = Object.keys(rule.parameters ?? {}).map((n) => n.replace(/([A-Z])/g, " $1").toLowerCase()).join(" ");
22618
+ return {
22619
+ rule,
22620
+ // "_" is a regex word character — split snake_case so \b-anchored
22621
+ // tokens ("tax") still reach id segments ("kiddie_tax").
22622
+ id: rule.id.toLowerCase().replace(/[._]/g, " "),
22623
+ title: rule.title.toLowerCase(),
22624
+ meta: `${paramWords} ${JURISDICTION_NAMES[rule.jurisdiction] ?? ""}`.toLowerCase(),
22625
+ weak: `${rule.citation.source} ${rule.citation.section} ${rule.citation.excerpt ?? ""}`.toLowerCase()
22626
+ };
22627
+ });
22628
+ const matchers = new Map(allTokens.map((t) => [t, new RegExp(`\\b${t}`)]));
22629
+ const has = (hay, t) => matchers.get(t).test(hay);
22630
+ const df = (t) => haystacks.filter((h) => has(h.id, t) || has(h.title, t) || has(h.meta, t) || has(h.weak, t)).length;
22631
+ const rare = allTokens.filter((t) => df(t) <= haystacks.length * 0.35);
22632
+ const tokens = rare.length > 0 ? rare : allTokens;
22633
+ const boost = new Map(tokens.map((t) => [t, df(t) <= haystacks.length * 0.05 ? 2 : 1]));
22634
+ const minScore = tokens.length >= 2 ? 2 : 1;
22635
+ const hits = [];
22636
+ for (const { rule, id, title, meta, weak } of haystacks) {
22637
+ if (asOf && !(rule.effectiveFrom <= asOf && (rule.effectiveTo === void 0 || asOf < rule.effectiveTo))) {
22638
+ continue;
22639
+ }
22640
+ let raw = 0;
22641
+ let score = 0;
22642
+ let matched = 0;
22643
+ let sawStrong = false;
22644
+ for (const t of tokens) {
22645
+ let s = 0;
22646
+ if (has(id, t))
22647
+ s += 2;
22648
+ if (has(title, t))
22649
+ s += 2;
22650
+ if (has(meta, t))
22651
+ s += 1;
22652
+ if (s > 0)
22653
+ sawStrong = true;
22654
+ else if (has(weak, t))
22655
+ s = 1;
22656
+ if (s > 0)
22657
+ matched += 1;
22658
+ raw += s;
22659
+ score += s * (boost.get(t) ?? 1);
22660
+ }
22661
+ if (raw < minScore || matched < Math.ceil(tokens.length / 2))
22662
+ continue;
22663
+ if (tokens.length >= 2 && !sawStrong)
22664
+ continue;
22665
+ hits.push({
22666
+ ruleId: rule.id,
22667
+ version: rule.version,
22668
+ title: rule.title,
22669
+ jurisdiction: rule.jurisdiction,
22670
+ citation: rule.citation,
22671
+ effectiveFrom: rule.effectiveFrom,
22672
+ effectiveTo: rule.effectiveTo,
22673
+ snippet: makeSnippet(rule.citation.excerpt ?? "", tokens),
22674
+ hasParameters: Object.keys(rule.parameters ?? {}).length > 0,
22675
+ score
22676
+ });
22677
+ }
22678
+ hits.sort((a, b) => b.score - a.score || a.ruleId.localeCompare(b.ruleId));
22679
+ return hits.slice(0, Math.max(1, Math.min(limit, 20)));
22680
+ }
22498
22681
  function factCheck(corpus2, query, claimedCents, asOf, filingStatus) {
22499
22682
  const hits = lookupParameters(corpus2, query, asOf);
22500
22683
  if (hits.length === 0) {
@@ -22796,6 +22979,119 @@ function composeNY(input, evalStateTax, notes) {
22796
22979
  };
22797
22980
  }
22798
22981
 
22982
+ // ../compose/dist/pa.js
22983
+ var classLine = (p, s) => p > 0n || s > 0n ? max02(p) + max02(s) : p + s;
22984
+ function composePA(input, evalStateTax, notes) {
22985
+ const box16 = input.paGrossCompensation !== void 0 ? c(input.paGrossCompensation) : c(input.wages);
22986
+ if (input.paGrossCompensation === void 0 && input.wages !== void 0) {
22987
+ notes.push("PA line 1a: W-2 Box 16 total not transcribed \u2014 federal Box 1 wages used (401(k)/elective deferrals are PA-taxable; pass paGrossCompensation when Box 16 differs)");
22988
+ }
22989
+ const l1a = rd(box16);
22990
+ const l1b = rd(c(input.paUnreimbursedExpenses));
22991
+ const l1c = max02(l1a - l1b);
22992
+ const l2 = rd(c(input.paInterest));
22993
+ const l3 = rd(c(input.paDividends));
22994
+ const bizP = rd(c(input.paBusinessNet));
22995
+ const bizS = rd(c(input.paSpouseBusinessNet));
22996
+ const propP = rd(c(input.paPropertyNet));
22997
+ const propS = rd(c(input.paSpousePropertyNet));
22998
+ const rentP = rd(c(input.paRentRoyaltyNet));
22999
+ const rentS = rd(c(input.paSpouseRentRoyaltyNet));
23000
+ const l4 = classLine(bizP, bizS);
23001
+ const l5 = classLine(propP, propS);
23002
+ const l6 = classLine(rentP, rentS);
23003
+ const l7 = rd(c(input.paEstateTrust));
23004
+ const l8 = rd(c(input.paGambling));
23005
+ const extra = {
23006
+ wages: box16,
23007
+ paUnreimbursedBusinessExpenses: c(input.paUnreimbursedExpenses),
23008
+ paInterestIncome: c(input.paInterest),
23009
+ paDividendIncome: c(input.paDividends),
23010
+ paBusinessNetIncome: c(input.paBusinessNet),
23011
+ paSpouseBusinessNetIncome: c(input.paSpouseBusinessNet),
23012
+ paPropertyGainNet: c(input.paPropertyNet),
23013
+ paSpousePropertyGainNet: c(input.paSpousePropertyNet),
23014
+ paRentRoyaltyNet: c(input.paRentRoyaltyNet),
23015
+ paSpouseRentRoyaltyNet: c(input.paSpouseRentRoyaltyNet),
23016
+ paEstateTrustIncome: c(input.paEstateTrust),
23017
+ paGamblingWinnings: c(input.paGambling),
23018
+ studentLoanInterest: c(input.paStudentLoanInterest),
23019
+ pa529Contributions: c(input.pa529Contributions),
23020
+ paAbleContributions: c(input.paAbleContributions),
23021
+ paMsaHsaContributions: c(input.paMsaHsaContributions)
23022
+ };
23023
+ const l9 = rd(evalStateTax("us.pa.taxable_income", 0n, extra));
23024
+ const l10 = rd(evalStateTax("us.pa.other_deductions", 0n, extra));
23025
+ const l11 = max02(l9 - l10);
23026
+ const l12 = rd(evalStateTax("us.pa.income_tax", 0n, extra));
23027
+ const l13 = rd(c(input.stateWithholding));
23028
+ const l14 = rd(c(input.priorYearOverpaymentCredited));
23029
+ const l15 = rd(c(input.estimatedPayments));
23030
+ const l16 = rd(c(input.extensionPayment));
23031
+ const l17 = rd(c(input.paNrk1Withholding));
23032
+ const l18 = l14 + l15 + l16 + l17;
23033
+ const spDeps = input.paSpDependentChildren ?? 0;
23034
+ const l21 = rd(evalStateTax("us.pa.tax_forgiveness", 0n, {
23035
+ ...extra,
23036
+ paSpDependentChildren: spDeps,
23037
+ paEligibilityIncomeAddbacks: c(input.paEligibilityAddbacks),
23038
+ paResidentCredit: c(input.paResidentCredit)
23039
+ }));
23040
+ const l20 = l9 + rd(c(input.paEligibilityAddbacks));
23041
+ const l22 = rd(c(input.paResidentCredit));
23042
+ const l23 = rd(c(input.paScheduleDcCredit)) + rd(c(input.paScheduleOcCredits));
23043
+ const l24 = l13 + l18 + l21 + l22 + l23;
23044
+ const l25 = rd(c(input.useTax));
23045
+ const l26 = max02(l12 + l25 - l24);
23046
+ const l27 = rd(c(input.paPenaltiesInterest));
23047
+ const l28 = l26 + l27;
23048
+ const l29 = max02(l24 - l12 - l25 - l27);
23049
+ const fedEITC = c(input.federalEITC);
23050
+ if (fedEITC > 0n) {
23051
+ const wptc = min2(rd(fedEITC * 10n / 100n), 80500n);
23052
+ notes.push(`PA Working Pennsylvanians Tax Credit ${fmtD(wptc)} (10% of federal EITC, max $805, refundable) \u2014 NO line on the printed 2025 PA-40; DOR applies it in processing from the attached federal return`);
23053
+ }
23054
+ notes.push("PA local earned income tax (Act 32) is filed separately with the local collector \u2014 out of scope for the PA-40");
23055
+ if (l29 > 0n && l29 <= 100n)
23056
+ notes.push("PA does not issue refunds of $1.00 or less");
23057
+ if (l28 > 0n && l28 <= 100n)
23058
+ notes.push("PA does not require payment of $1.00 or less");
23059
+ return {
23060
+ "1a_gross_compensation": fmtD(l1a),
23061
+ "1b_unreimbursed_expenses": fmtD(l1b),
23062
+ "1c_net_compensation": fmtD(l1c),
23063
+ "2_interest": fmtD(l2),
23064
+ "3_dividends": fmtD(l3),
23065
+ "4_business_net": fmtD(l4),
23066
+ "5_property_net": fmtD(l5),
23067
+ "6_rent_royalty_net": fmtD(l6),
23068
+ "7_estate_trust": fmtD(l7),
23069
+ "8_gambling": fmtD(l8),
23070
+ "9_total_taxable_income": fmtD(l9),
23071
+ "10_other_deductions": fmtD(l10),
23072
+ "11_adjusted_taxable_income": fmtD(l11),
23073
+ "12_tax": fmtD(l12),
23074
+ "13_withholding": fmtD(l13),
23075
+ "14_prior_year_credit": fmtD(l14),
23076
+ "15_estimated_payments": fmtD(l15),
23077
+ "16_extension_payment": fmtD(l16),
23078
+ "17_nonresident_withholding": fmtD(l17),
23079
+ "18_total_estimated_and_credits": fmtD(l18),
23080
+ "19b_sp_dependents": String(spDeps),
23081
+ "20_eligibility_income": fmtD(l20),
23082
+ "21_tax_forgiveness": fmtD(l21),
23083
+ "22_resident_credit": fmtD(l22),
23084
+ "23_other_credits": fmtD(l23),
23085
+ "24_total_payments_credits": fmtD(l24),
23086
+ "25_use_tax": fmtD(l25),
23087
+ "26_tax_due": fmtD(l26),
23088
+ "27_penalties_interest": fmtD(l27),
23089
+ "28_total_due": fmtD(l28),
23090
+ "29_overpayment": fmtD(l29),
23091
+ "30_refund": fmtD(l29)
23092
+ };
23093
+ }
23094
+
22799
23095
  // ../compose/dist/va-worksheets.js
22800
23096
  var VA_STD_DEDUCTION_JOINT = 1750000n;
22801
23097
  var VA_STD_DEDUCTION_OTHER = 875000n;
@@ -23012,11 +23308,11 @@ function composeVA(input, evalStateTax, notes) {
23012
23308
  // ../compose/dist/shape.js
23013
23309
  var usd = external_exports.number().finite();
23014
23310
  var shared = {
23015
- jurisdiction: external_exports.enum(["il", "va", "ca", "ny"]),
23311
+ jurisdiction: external_exports.enum(["il", "va", "ca", "ny", "pa"]),
23016
23312
  filingStatus: external_exports.enum(["single", "mfj", "mfs", "hoh", "qss"]).optional().describe("REQUIRED in practice: the federal filing status \u2014 drives the state bracket schedule, standard deduction column, and exemption structure. The filingJoint/filingHoh/filingHohOrQss booleans are legacy aliases; when filingStatus is present it wins."),
23017
23313
  // federal substrate values, computed by compute_return in the SAME session
23018
23314
  // (pass them verbatim — whole dollars)
23019
- federalAGI: usd.describe("federal Form 1040 line 11 (from compute_return, verbatim)"),
23315
+ federalAGI: usd.optional().describe("federal Form 1040 line 11 (from compute_return, verbatim). REQUIRED for il/va/ca/ny \u2014 the composer refuses without it. NOT used by PA (class-based: pass the pa* class fields instead)."),
23020
23316
  federalEITC: usd.optional().describe("federal EIC, line 27a (from compute_return)"),
23021
23317
  wages: usd.optional().describe("federal line 1a wages (NY IT-201 line 1)"),
23022
23318
  additions: usd.optional().describe("total state additions to federal AGI (e.g. NY 414(h) A-104 + IRC-125 A-101; VA Schedule ADJ line 2 codes). GATE RULE: coded addition/subtraction line-item arrays sitting under a false 'do you have additions/subtractions' boolean are inactive template rows (especially $1-$4 placeholder amounts) \u2014 transcribe $0 for them and disclose; the gate controls for these arrays"),
@@ -23024,7 +23320,7 @@ var shared = {
23024
23320
  exemptions: external_exports.number().int().optional().describe("personal + dependent exemption COUNT (self + spouse + dependents)"),
23025
23321
  ageOrBlindBoxes: external_exports.number().int().optional().describe("count of age-65+/blind boxes checked (taxpayer/spouse, per box)"),
23026
23322
  dependents: external_exports.number().int().optional().describe("dependent count (CA dependent exemption credits; NY $1,000 exemptions)"),
23027
- stateWithholding: usd.optional().describe("state income tax withheld (IL line 25 / VA 19a / CA 71 / NY 72). CONVENTIONS: IL line 25 sums state withholding from EVERY document (W-2s + all 1099s). NY line 72 = W-2 box 17 NYS withholding PLUS NY-coded state withholding from 1099s whose PAYER has an in-state (NY) address; NY-coded withholding printed by an OUT-OF-STATE-addressed payer is NOT included (reference-composition convention, validated across ty25-ny-003 \u2014 NY payers, included \u2014 and ty25-ny-008 \u2014 TX-addressed payer, excluded); disclose any excluded amount in notes. VA 19a = the PRIMARY taxpayer's withholding from EVERY document type (W-2, 1099, VK-1 \u2014 Form 760 line 19 instructions name all three; the payer's address does NOT matter for VA, unlike NY); a jointly-issued document's state withholding splits 50/50 between 19a/19b with the odd dollar to the primary."),
23323
+ stateWithholding: usd.optional().describe("state income tax withheld (IL line 25 / VA 19a / CA 71 / NY 72). CONVENTIONS: IL line 25 sums state withholding from EVERY document (W-2s + all 1099s). NY line 72 = W-2 box 17 NYS withholding PLUS NY-coded state withholding from 1099s whose PAYER has an in-state (NY) address; NY-coded withholding printed by an OUT-OF-STATE-addressed payer is NOT included; disclose any excluded amount in notes. VA 19a = the PRIMARY taxpayer's withholding from EVERY document type (W-2, 1099, VK-1 \u2014 Form 760 line 19 instructions name all three; the payer's address does NOT matter for VA, unlike NY); a jointly-issued document's state withholding splits 50/50 between 19a/19b with the odd dollar to the primary."),
23028
23324
  spouseStateWithholding: usd.optional().describe("VA line 19b spouse withholding (spouse's own W-2/1099/VK-1 boxes + spouse's half of jointly-issued documents' withholding, odd dollar to the primary)"),
23029
23325
  cityWithholding: usd.optional().describe("NY line 73 NYC withholding"),
23030
23326
  yonkersWithholding: usd.optional().describe("NY line 74 Yonkers withholding (W-2 box 19 with a Yonkers locality)"),
@@ -23097,7 +23393,38 @@ var ny = {
23097
23393
  nycHouseholdCredit: usd.optional().describe("NYC household credit from table 5"),
23098
23394
  yonkersSurcharge: usd.optional().describe("us.ny.yonkers_surcharge result (pass the oracle target's answer) \u2014 16.75% of the Yonkers worksheet's netted base (nyYonkersBase). Added into line 62's total and printed on its own line (IT-201 LINE 55, not 54 \u2014 line 54 is MCTMT) when nonzero.")
23099
23395
  };
23100
- var stateReturnShape = { ...shared, ...il, ...va, ...ca, ...ny };
23396
+ var pa = {
23397
+ // PA is CLASS-BASED (eight classes, 72 P.S. § 7303) — federalAGI is NOT the
23398
+ // PA base. Transcribe the class amounts below; the composer runs the corpus
23399
+ // targets (class netting, Schedule O, 3.07% tax, Schedule SP) itself.
23400
+ paGrossCompensation: usd.optional().describe("PA-40 line 1a: W-2 BOX 16 total (NOT Box 1 \u2014 401(k)/elective deferrals are PA-taxable; eligible retirement distributions are exempt and excluded). Falls back to the shared wages input when omitted (composer discloses). Include taxable early-distribution amounts under the cost-recovery method."),
23401
+ paUnreimbursedExpenses: usd.optional().describe("PA-40 line 1b: Schedule UE unreimbursed employee business expenses (a compensation-class expense, never a line-10 deduction)"),
23402
+ paInterest: usd.optional().describe("PA-40 line 2: PA-taxable interest (gross class \u2014 no expenses; includes commercial-annuity interest taxable as PA interest)"),
23403
+ paDividends: usd.optional().describe("PA-40 line 3: PA-taxable dividends INCLUDING mutual-fund capital-gain distributions (PA classifies them as dividends, not gains)"),
23404
+ paBusinessNet: usd.optional().describe("PA-40 line 4, TAXPAYER's own net business/profession/farm income or LOSS (negative allowed; within-class netting of the taxpayer's own activities only \u2014 a loss never crosses classes or spouses)"),
23405
+ paSpouseBusinessNet: usd.optional().describe("PA-40 line 4, SPOUSE's own net business income or loss (kept separate: PA never nets one spouse's loss against the other's income)"),
23406
+ paPropertyNet: usd.optional().describe("PA-40 line 5, taxpayer's own net gain/loss from sale/exchange/disposition of property (negative allowed; no carryover)"),
23407
+ paSpousePropertyNet: usd.optional().describe("PA-40 line 5, spouse's own net property gain/loss"),
23408
+ paRentRoyaltyNet: usd.optional().describe("PA-40 line 6, taxpayer's own net rents/royalties/patents/copyrights (short-term rentals under 30 days are BUSINESS income, line 4)"),
23409
+ paSpouseRentRoyaltyNet: usd.optional().describe("PA-40 line 6, spouse's own net rent/royalty amount"),
23410
+ paEstateTrust: usd.optional().describe("PA-40 line 7: estate/trust income (PA Schedule J; an estate or trust cannot distribute a loss \u2014 never negative)"),
23411
+ paGambling: usd.optional().describe("PA-40 line 8: gambling and lottery winnings net of wager costs (noncash PA Lottery prizes exempt; cash prizes taxable)"),
23412
+ paStudentLoanInterest: usd.optional().describe("Schedule O code S: student loan interest PAID (new deduction for 2025; the composer caps at $2,500 \u2014 pass the uncapped amount)"),
23413
+ pa529Contributions: usd.optional().describe("Schedule O code T: \xA7 529 contributions, ALREADY capped at $19,000 per beneficiary per taxpayer-spouse (2025); no deduction for rollovers/beneficiary changes"),
23414
+ paAbleContributions: usd.optional().describe("Schedule O code A: PA ABLE contributions, capped at the federal gift-tax exclusion ($19,000 for 2025)"),
23415
+ paMsaHsaContributions: usd.optional().describe("Schedule O codes M/H: MSA + HSA contributions at the federally-allowed amounts"),
23416
+ paSpDependentChildren: external_exports.number().int().optional().describe("Schedule SP dependent CHILDREN count (child/stepchild/adopted; grandchild of a grandparent; foster child of a foster parent \u2014 never other relatives) claimable as federal dependents; each adds $9,500 to the Tax Forgiveness eligibility-income threshold"),
23417
+ paEligibilityAddbacks: usd.optional().describe("Schedule SP Section III nontaxable add-backs (gifts, inheritances, insurance proceeds, non-PA income, nontaxable military pay, excluded home-sale gain, educational assistance, outside cash support). NOT Social Security, eligible retirement benefits, child support, or workers' comp."),
23418
+ paResidentCredit: usd.optional().describe("PA-40 line 22: resident credit for tax paid other states (Schedule G-L; not for reciprocal-state compensation: IN/MD/NJ/OH/VA/WV). Subtracts BEFORE Tax Forgiveness \u2014 the composer handles the ordering."),
23419
+ paScheduleDcCredit: usd.optional().describe("PA-40 line 23 component: the Child and Dependent Care Enhancement credit \u2014 pass us.pa.cdcc's computed answer (= 100% of the federal Form 2441 line 9a tentative credit; refundable)"),
23420
+ paScheduleOcCredits: usd.optional().describe("PA-40 line 23 component: Schedule OC restricted credits total (transcribed; no oracle target)"),
23421
+ paNrk1Withholding: usd.optional().describe("PA-40 line 17: nonresident tax withheld from PA Schedule(s) NRK-1"),
23422
+ paPenaltiesInterest: usd.optional().describe("PA-40 line 27: penalties and interest incl. estimated-underpayment penalty (REV-1630)")
23423
+ // PA line 13 withholding uses the shared stateWithholding input: sum state
23424
+ // tax withheld from EVERY document type (W-2 box 17, W-2G box 15, 1099-R
23425
+ // box 14, 1099-MISC box 15, 1099-NEC box 5).
23426
+ };
23427
+ var stateReturnShape = { ...shared, ...il, ...va, ...ca, ...ny, ...pa };
23101
23428
 
23102
23429
  // ../compose/dist/index.js
23103
23430
  function makeStateTaxEvaluator(runTarget, input) {
@@ -23112,7 +23439,7 @@ function makeStateTaxEvaluator(runTarget, input) {
23112
23439
  spouseItemizes: { type: "bool", value: false }
23113
23440
  };
23114
23441
  for (const [k, v] of Object.entries(extraFacts ?? {})) {
23115
- facts2[k] = typeof v === "boolean" ? { type: "bool", value: v } : { type: "money", value: String(v) };
23442
+ facts2[k] = typeof v === "boolean" ? { type: "bool", value: v } : typeof v === "number" ? { type: "int", value: String(v) } : { type: "money", value: String(v) };
23116
23443
  }
23117
23444
  return runTarget(facts2, target);
23118
23445
  };
@@ -23126,12 +23453,17 @@ function composeStateReturn(input, evalStateTax) {
23126
23453
  input.filingHohOrQss = fs === "hoh" || fs === "qss" || fs === "mfj";
23127
23454
  }
23128
23455
  const j = input.jurisdiction;
23456
+ if (j !== "pa" && typeof input.federalAGI !== "number") {
23457
+ throw new Error("federalAGI is required for il/va/ca/ny state returns \u2014 run compute_return first and pass Form 1040 line 11 verbatim");
23458
+ }
23129
23459
  if (j === "il")
23130
23460
  return { lines: composeIL(input, evalStateTax, notes), notes };
23131
23461
  if (j === "va")
23132
23462
  return { lines: composeVA(input, evalStateTax, notes), notes };
23133
23463
  if (j === "ca")
23134
23464
  return { lines: composeCA(input, evalStateTax, notes), notes };
23465
+ if (j === "pa")
23466
+ return { lines: composePA(input, evalStateTax, notes), notes };
23135
23467
  return { lines: composeNY(input, evalStateTax, notes), notes };
23136
23468
  }
23137
23469
 
@@ -23682,12 +24014,32 @@ var facts = [
23682
24014
  id: "netCapitalLoss",
23683
24015
  type: "money",
23684
24016
  min: "0",
23685
- description: "Net capital LOSS for the year from Schedule D netting, entered as a positive number (26 U.S.C. \xA7 1211(b): up to $3,000/$1,500 MFS against ordinary income; remainder carries over). Mutually exclusive with longTermCapitalGains. In dollars.",
24017
+ description: "Net capital LOSS for the year from Schedule D netting, entered as a positive number (26 U.S.C. \xA7 1211(b): up to $3,000/$1,500 MFS against ordinary income; remainder carries over). Mutually exclusive with the gain facts AND with the per-bucket loss facts (shortTermCapitalLoss/longTermCapitalLoss) \u2014 this is the single OVERALL net result; for mixed years (gain in one bucket, loss in the other) use the per-bucket facts and the engine performs the \xA7 1222 netting. In dollars.",
23686
24018
  default: {
23687
24019
  value: "0",
23688
24020
  rationale: "Assumed no net capital loss absent contrary input"
23689
24021
  }
23690
24022
  },
24023
+ {
24024
+ id: "shortTermCapitalLoss",
24025
+ type: "money",
24026
+ min: "0",
24027
+ description: "Net short-term capital LOSS for the year (Schedule D line 7 when negative, entered positive). The ST bucket's single NET result \u2014 mutually exclusive with shortTermCapitalGains and with netCapitalLoss. Combine with longTermCapitalGains for mixed years: the engine performs the \xA7 1222 cross-netting (a net ST loss first reduces net LT gain per \xA7 1222(11); any overall loss is capped by \xA7 1211(b)). In dollars.",
24028
+ default: {
24029
+ value: "0",
24030
+ rationale: "Assumed no net short-term capital loss absent contrary input"
24031
+ }
24032
+ },
24033
+ {
24034
+ id: "longTermCapitalLoss",
24035
+ type: "money",
24036
+ min: "0",
24037
+ description: "Net long-term capital LOSS for the year (Schedule D line 15 when negative, entered positive). The LT bucket's single NET result \u2014 mutually exclusive with longTermCapitalGains and with netCapitalLoss. Combine with shortTermCapitalGains for mixed years: the engine performs the \xA7 1222 cross-netting (a net LT loss reduces the ordinary-rate net ST gain per \xA7 1222(9); any overall loss is capped by \xA7 1211(b)). In dollars.",
24038
+ default: {
24039
+ value: "0",
24040
+ rationale: "Assumed no net long-term capital loss absent contrary input"
24041
+ }
24042
+ },
23691
24043
  {
23692
24044
  id: "qualifiedDividends",
23693
24045
  type: "money",
@@ -23722,7 +24074,7 @@ var facts = [
23722
24074
  id: "selfEmploymentNetProfit",
23723
24075
  type: "money",
23724
24076
  min: "0",
23725
- description: "Net profit from self-employment (Schedule C line 31: gross receipts minus ACTUAL Schedule C expenses). Do NOT subtract intake fields like 'W-2 Wages Paid' that exist for the \xA7 199A wage limit \u2014 those belong in qbiW2Wages, not here. A loss year goes in scheduleCNetLoss instead (leave this at 0). GROSS-RECEIPTS COMPOSITION (intake convention): the intake's own gross-receipts field PLUS every 1099-NEC and 1099-K nested under (or issued to) the business PLUS any 1099-MISC box 3 'other income' issued to a filer who operates a Schedule C business (reference-composition convention: a payer's MISC box-3 payment to an active sole proprietor is business income, not Schedule 1 line 8z) \u2014 1099-MISC box 1 rents belong on Schedule E, never here. A 1099 whose recipient TIN matches NEITHER spouse is not this return's income: exclude it and disclose. PLACEHOLDER-PAYER DECOYS (reference-validated ty25-ca-007/ca-008 vs ty25-il-008): a 1099 whose payer block is the blank template \u2014 no payer name, address line 'City, AK 99999' or similar all-9s placeholder \u2014 is an INACTIVE TEMPLATE document, not income: exclude every box on it (income AND withholding) and disclose; a 1099 from a NAMED payer at a real-looking address is live data even when the structured intake has no matching entry (il-008's Chicago 'Misc Payer' box 3 belongs in receipts). In dollars.",
24077
+ description: "Net profit from self-employment (Schedule C line 31: gross receipts minus ACTUAL Schedule C expenses). Do NOT subtract intake fields like 'W-2 Wages Paid' that exist for the \xA7 199A wage limit \u2014 those belong in qbiW2Wages, not here. A loss year goes in scheduleCNetLoss instead (leave this at 0). GROSS-RECEIPTS COMPOSITION: the intake's own gross-receipts field PLUS every 1099-NEC and 1099-K nested under (or issued to) the business PLUS any 1099-MISC box 3 'other income' issued to a filer who operates a Schedule C business (a payer's MISC box-3 payment to an active sole proprietor is business income, not Schedule 1 line 8z) \u2014 1099-MISC box 1 rents belong on Schedule E, never here. A 1099 whose recipient TIN matches NEITHER spouse is not this return's income: exclude it and disclose. PLACEHOLDER/TEMPLATE DOCUMENTS: a 1099 whose payer block is an unfilled template \u2014 no payer name, address line 'City, AK 99999' or a similar all-9s placeholder \u2014 is an inactive template document, not income: exclude every box on it (income AND withholding) and disclose; a 1099 from a NAMED payer at a real-looking address is live data even when the structured intake has no matching entry. In dollars.",
23726
24078
  default: {
23727
24079
  value: "0",
23728
24080
  rationale: "Assumed no self-employment income absent contrary input"
@@ -23817,7 +24169,7 @@ var facts = [
23817
24169
  id: "sepContribution",
23818
24170
  type: "money",
23819
24171
  min: "0",
23820
- description: "Employer contribution to the self-employed taxpayer's own SEP-IRA or solo-401(k) profit-sharing (\xA7 404(h): up to 20% of net SE earnings after \xBD SE tax, capped at the \xA7 415(c) limit \u2014 $70,000 for 2025, $72,000 for 2026). In dollars. DEDUCTIBLE-LIMIT CONVENTION (reference-validated ty25-ca-007): when a documented SEP/Keogh contribution exceeds the limit, the deducted amount = 20% \xD7 (0.9235 \xD7 total Schedule C net profit \u2212 the \xBD-SE-tax deduction), rounded half-up \u2014 i.e. the \xA7 401(c)(2)/\xA7 1402(a)(12) reading that applies BOTH the 92.35% factor AND the \xBD-SE subtraction (ca-007: 20% \xD7 (0.9235 \xD7 3,813 \u2212 270) = 650). Pub. 560's worksheet omits the 92.35% factor; the reference uses it \u2014 pass the documented contribution and apply this cap when self-computing the Schedule 1 line 16 amount.",
24172
+ description: "Employer contribution to the self-employed taxpayer's own SEP-IRA or solo-401(k) profit-sharing (\xA7 404(h): up to 20% of net SE earnings after \xBD SE tax, capped at the \xA7 415(c) limit \u2014 $70,000 for 2025, $72,000 for 2026). In dollars. DEDUCTIBLE-LIMIT CONVENTION: when a documented SEP/Keogh contribution exceeds the limit, the deducted amount = 20% \xD7 (0.9235 \xD7 total Schedule C net profit \u2212 the \xBD-SE-tax deduction), rounded half-up \u2014 the \xA7 401(c)(2)/\xA7 1402(a)(12) reading that applies BOTH the 92.35% factor AND the \xBD-SE subtraction. (Note: Pub. 560's worksheet applies only the \xBD-SE subtraction; this engine follows the statutory \xA7 1402(a)(12) reduction.) Pass the documented contribution and apply this cap when self-computing the Schedule 1 line 16 amount.",
23821
24173
  default: {
23822
24174
  value: "0",
23823
24175
  rationale: "Assumed no self-employed retirement contribution absent contrary input"
@@ -24036,6 +24388,109 @@ var facts = [
24036
24388
  rationale: "Assumed no non-QBI Schedule E income absent contrary input"
24037
24389
  }
24038
24390
  },
24391
+ {
24392
+ id: "pensionGrossPayments",
24393
+ type: "money",
24394
+ min: "0",
24395
+ description: "TOTAL pension/annuity payments received this year from a qualified-plan annuity with after-tax cost basis (1099-R box 1) \u2014 the \xA7 72(d) Simplified Method computes the taxable part. Use taxablePensionsAndAnnuities instead (or additionally, for other pensions) when the taxable amount is already known. In dollars.",
24396
+ default: {
24397
+ value: "0",
24398
+ rationale: "Assumed no simplified-method annuity absent contrary input"
24399
+ }
24400
+ },
24401
+ {
24402
+ id: "pensionCostBasis",
24403
+ type: "money",
24404
+ min: "0",
24405
+ description: "Investment in the contract as of the annuity starting date \u2014 employee after-tax contributions (1099-R box 9b). The \xA7 72(d)(1)(B) monthly exclusion is this divided by the anticipated-payments table number; lifetime recovery is capped at this amount. In dollars.",
24406
+ default: {
24407
+ value: "0",
24408
+ rationale: "Assumed zero after-tax basis (fully taxable pension) absent contrary input"
24409
+ }
24410
+ },
24411
+ {
24412
+ id: "pensionAgeAtStart",
24413
+ type: "int",
24414
+ min: "0",
24415
+ max: "120",
24416
+ description: "Primary annuitant's age at the ANNUITY STARTING DATE (not current age) \u2014 picks the \xA7 72(d)(1)(B)(iii) single-life anticipated-payments band (\u226455: 360, 56-60: 310, 61-65: 260, 66-70: 210, 71+: 160). Required when pensionCostBasis is set and the annuity is single-life.",
24417
+ default: {
24418
+ value: "0",
24419
+ rationale: "Unset; the simplified-method rule refuses rather than assume an age band"
24420
+ }
24421
+ },
24422
+ {
24423
+ id: "pensionIsJointAndSurvivor",
24424
+ type: "bool",
24425
+ description: "True for an annuity payable over more than one life with a starting date after 1997 \u2014 the \xA7 72(d)(1)(B)(iv) COMBINED-ages table applies (set pensionCombinedAgesAtStart).",
24426
+ default: {
24427
+ value: false,
24428
+ rationale: "Assumed a single-life annuity absent contrary input"
24429
+ }
24430
+ },
24431
+ {
24432
+ id: "pensionCombinedAgesAtStart",
24433
+ type: "int",
24434
+ min: "0",
24435
+ max: "240",
24436
+ description: "Combined ages of both annuitants at the annuity starting date, for joint-and-survivor annuities (\xA7 72(d)(1)(B)(iv): \u2264110: 410, 111-120: 360, 121-130: 310, 131-140: 260, 141+: 210).",
24437
+ default: {
24438
+ value: "0",
24439
+ rationale: "Unset; the simplified-method rule refuses rather than assume a band"
24440
+ }
24441
+ },
24442
+ {
24443
+ id: "pensionMonthsThisYear",
24444
+ type: "int",
24445
+ min: "1",
24446
+ max: "12",
24447
+ description: "Number of monthly annuity payments received this tax year (Simplified Method Worksheet line 4 multiplier). 12 for a full year.",
24448
+ default: {
24449
+ value: "12",
24450
+ rationale: "Assumed a full year of monthly payments absent contrary input"
24451
+ }
24452
+ },
24453
+ {
24454
+ id: "pensionBasisPreviouslyRecovered",
24455
+ type: "money",
24456
+ min: "0",
24457
+ description: "Cost basis already recovered tax-free in prior years (Simplified Method Worksheet line 6) \u2014 lifetime recovery cannot exceed pensionCostBasis for post-1986 starting dates. In dollars.",
24458
+ default: {
24459
+ value: "0",
24460
+ rationale: "Assumed the first year of the annuity absent contrary input"
24461
+ }
24462
+ },
24463
+ {
24464
+ id: "rentalDepreciableBasis",
24465
+ type: "money",
24466
+ min: "0",
24467
+ description: "Depreciable basis of residential rental BUILDING(S) \u2014 cost basis excluding land (land never depreciates, \xA7 167; Pub 527 ch. 2). Drives the \xA7 168 GDS 27.5-year straight-line mid-month depreciation (Pub 946 Table A-6). Set rentalPlacedInServiceMonth for a first-year property; leave it 0 for property in service the whole year. In dollars.",
24468
+ default: {
24469
+ value: "0",
24470
+ rationale: "Assumed no depreciable rental property absent contrary input"
24471
+ }
24472
+ },
24473
+ {
24474
+ id: "rentalPlacedInServiceMonth",
24475
+ type: "int",
24476
+ min: "0",
24477
+ max: "12",
24478
+ description: "Month (1-12) the residential rental property was placed in service THIS tax year \u2014 first-year depreciation uses the \xA7 168(d)(2) mid-month convention (Pub 946 Table A-6 row 1). 0 (the default) = placed in service in a PRIOR year: the steady-state 3.636% year applies.",
24479
+ default: {
24480
+ value: "0",
24481
+ rationale: "Assumed the property was in service before this year (steady-state 3.636% year) absent contrary input"
24482
+ }
24483
+ },
24484
+ {
24485
+ id: "rentalIncomeBeforeDepreciation",
24486
+ type: "money",
24487
+ min: "0",
24488
+ description: "Schedule E rental result BEFORE depreciation: rents received minus cash operating expenses (management, repairs, insurance, taxes, mortgage interest\u2026). The engine subtracts the computed \xA7 168 depreciation to reach net rental income. Use scheduleENetIncome instead when you already have the after-depreciation net; the two inputs add (separate properties). In dollars.",
24489
+ default: {
24490
+ value: "0",
24491
+ rationale: "Assumed no rental activity reported through the depreciation path absent contrary input"
24492
+ }
24493
+ },
24039
24494
  {
24040
24495
  id: "businessMilesDriven",
24041
24496
  type: "int",
@@ -24320,7 +24775,7 @@ var facts = [
24320
24775
  id: "hsaQualifiedMedicalExpenses",
24321
24776
  type: "money",
24322
24777
  min: "0",
24323
- description: "Unreimbursed qualified medical expenses paid from HSA distributions (Form 8889 line 15) \u2014 ONLY the amount the intake EXPLICITLY attests was spent on qualified medical expenses. Absent an explicit attestation, leave this 0 and the full distribution is taxable (\xA7 223(f)) \u2014 never assume qualified use from silence (reference-composition convention, validated on ty25-ca-008/ca-009: undocumented distributions were taxed in full). In dollars.",
24778
+ description: "Unreimbursed qualified medical expenses paid from HSA distributions (Form 8889 line 15) \u2014 ONLY the amount the intake EXPLICITLY attests was spent on qualified medical expenses. Absent an explicit attestation, leave this 0 and the full distribution is taxable (\xA7 223(f)) \u2014 never assume qualified use from silence; the taxpayer bears the burden of substantiating qualified use. In dollars.",
24324
24779
  default: { value: "0", rationale: "Assumed distributions were not for qualified medical expenses absent contrary input (conservative: taxable)" }
24325
24780
  },
24326
24781
  {
@@ -24381,7 +24836,7 @@ var facts = [
24381
24836
  id: "iraContribution",
24382
24837
  type: "money",
24383
24838
  min: "0",
24384
- description: "Traditional IRA contributions for the year (26 U.S.C. \xA7 219). Roth contributions are not deductible and do not belong here. In dollars. GATE-vs-DOLLAR (reference-validated ty25-ca-007/ca-008): an intake screener like 'Made contributions to a traditional or Roth IRA = false' does NOT nullify a populated traditional-IRA dollar field \u2014 IRA contributions are a DEDUCTION item, so the documented dollar controls (transcribe it; any \xA7 219(g) phase-out still applies, including the \xA7 219(g)(4) lived-apart-all-year MFS carve-out). The boolean-gate-controls rule is for PAYMENT fields only.",
24839
+ description: "Traditional IRA contributions for the year (26 U.S.C. \xA7 219). Roth contributions are not deductible and do not belong here. In dollars. GATE-vs-DOLLAR: an intake screener like 'Made contributions to a traditional or Roth IRA = false' does NOT nullify a populated traditional-IRA dollar field \u2014 IRA contributions are a DEDUCTION item, so the documented dollar controls (transcribe it; any \xA7 219(g) phase-out still applies, including the \xA7 219(g)(4) lived-apart-all-year MFS carve-out). The boolean-gate-controls rule is for PAYMENT fields only.",
24385
24840
  default: {
24386
24841
  value: "0",
24387
24842
  rationale: "Assumed no traditional IRA contributions absent contrary input"
@@ -25349,6 +25804,133 @@ var facts = [
25349
25804
  type: "bool",
25350
25805
  description: "Spouse itemizes deductions on a separate return (26 U.S.C. \xA7 63(c)(6)(A)) \u2014 if so, an MFS filer's standard deduction is zero."
25351
25806
  // no default — only demanded for married-filing-separately evaluations
25807
+ },
25808
+ // ---- Pennsylvania deep pack (state-pa.ts) -------------------------------
25809
+ // PA classes are TRANSCRIBED separately from the federal facts: PA
25810
+ // classification diverges (capital-gain distributions are dividends,
25811
+ // 401(k) deferrals are taxable compensation, eligible retirement income is
25812
+ // exempt), so nothing here is auto-mapped from taxableInterest etc.
25813
+ {
25814
+ id: "paCompensationAdjustment",
25815
+ type: "money",
25816
+ description: "PA compensation adjustment: W-2 Box 16 total MINUS Box 1 total (PA taxes 401(k)/elective deferrals as compensation; active-duty pay outside PA and other exempt items reduce it). May be negative. Default 0 assumes Box 16 = Box 1. Taxable early retirement-plan/IRA distributions under PA's cost-recovery method also go here. In dollars.",
25817
+ default: { value: "0", rationale: "PA W-2 Box 16 assumed equal to federal Box 1 wages absent contrary input" }
25818
+ },
25819
+ {
25820
+ id: "paUnreimbursedBusinessExpenses",
25821
+ type: "money",
25822
+ min: "0",
25823
+ description: "PA Schedule UE unreimbursed employee business expenses (PA-40 line 1b) \u2014 a compensation-class expense, not a line-10 deduction. In dollars.",
25824
+ default: { value: "0", rationale: "No unreimbursed employee business expenses claimed absent contrary input" }
25825
+ },
25826
+ {
25827
+ id: "paInterestIncome",
25828
+ type: "money",
25829
+ min: "0",
25830
+ description: "PA-taxable interest income (PA-40 line 2, gross class \u2014 no expenses; forfeited-interest penalties may offset within the class before entry). Includes commercial-annuity income taxable as PA interest. In dollars.",
25831
+ default: { value: "0", rationale: "No PA interest income absent contrary input" }
25832
+ },
25833
+ {
25834
+ id: "paDividendIncome",
25835
+ type: "money",
25836
+ min: "0",
25837
+ description: "PA-taxable dividend income (PA-40 line 3, gross class) INCLUDING mutual-fund capital gains distributions, which PA classifies as dividends. In dollars.",
25838
+ default: { value: "0", rationale: "No PA dividend income absent contrary input" }
25839
+ },
25840
+ {
25841
+ id: "paBusinessNetIncome",
25842
+ type: "money",
25843
+ description: "Taxpayer's own net income or LOSS from business/profession/farm (PA-40 line 4 class, after within-class netting of the taxpayer's own activities). Negative allowed; a loss never offsets other classes or the spouse. In dollars.",
25844
+ default: { value: "0", rationale: "No business income or loss absent contrary input" }
25845
+ },
25846
+ {
25847
+ id: "paSpouseBusinessNetIncome",
25848
+ type: "money",
25849
+ description: "Spouse's own net income or LOSS from business/profession/farm (PA-40 line 4 class). Negative allowed; never netted against the taxpayer's. In dollars.",
25850
+ default: { value: "0", rationale: "No spouse business income or loss absent contrary input" }
25851
+ },
25852
+ {
25853
+ id: "paPropertyGainNet",
25854
+ type: "money",
25855
+ description: "Taxpayer's own net gain or LOSS from sale/exchange/disposition of property (PA-40 line 5 class). Negative allowed; no carryover, no cross-class or spousal offset. In dollars.",
25856
+ default: { value: "0", rationale: "No property gain or loss absent contrary input" }
25857
+ },
25858
+ {
25859
+ id: "paSpousePropertyGainNet",
25860
+ type: "money",
25861
+ description: "Spouse's own net gain or LOSS from disposition of property (PA-40 line 5 class). Negative allowed; never netted against the taxpayer's. In dollars.",
25862
+ default: { value: "0", rationale: "No spouse property gain or loss absent contrary input" }
25863
+ },
25864
+ {
25865
+ id: "paRentRoyaltyNet",
25866
+ type: "money",
25867
+ description: "Taxpayer's own net income or LOSS from rents/royalties/patents/copyrights (PA-40 line 6 class). Negative allowed; short-term rentals (<30 days) belong in the business class instead. In dollars.",
25868
+ default: { value: "0", rationale: "No rent/royalty income or loss absent contrary input" }
25869
+ },
25870
+ {
25871
+ id: "paSpouseRentRoyaltyNet",
25872
+ type: "money",
25873
+ description: "Spouse's own net income or LOSS from rents/royalties/patents/copyrights (PA-40 line 6 class). Negative allowed; never netted against the taxpayer's. In dollars.",
25874
+ default: { value: "0", rationale: "No spouse rent/royalty income or loss absent contrary input" }
25875
+ },
25876
+ {
25877
+ id: "paEstateTrustIncome",
25878
+ type: "money",
25879
+ min: "0",
25880
+ description: "PA-taxable income from estates or trusts (PA-40 line 7, Schedule J) \u2014 an estate or trust cannot distribute a loss, so this is never negative. In dollars.",
25881
+ default: { value: "0", rationale: "No estate/trust income absent contrary input" }
25882
+ },
25883
+ {
25884
+ id: "paGamblingWinnings",
25885
+ type: "money",
25886
+ min: "0",
25887
+ description: "PA-taxable gambling and lottery winnings (PA-40 line 8, Schedule T) net of wager costs (PA Lottery ticket costs deductible only for tickets bought on/after 1/1/2016); noncash PA Lottery prizes are exempt. In dollars.",
25888
+ default: { value: "0", rationale: "No gambling winnings absent contrary input" }
25889
+ },
25890
+ {
25891
+ id: "pa529Contributions",
25892
+ type: "money",
25893
+ min: "0",
25894
+ description: "PA Schedule O \xA7 529 tuition-program contribution deduction, ALREADY capped by the caller at $19,000 per beneficiary per taxpayer-spouse (2025) \u2014 the corpus cannot see per-beneficiary detail. No deduction for 529-to-529 rollovers or beneficiary changes. In dollars.",
25895
+ default: { value: "0", rationale: "No 529 contributions claimed absent contrary input" }
25896
+ },
25897
+ {
25898
+ id: "paAbleContributions",
25899
+ type: "money",
25900
+ min: "0",
25901
+ description: "PA Schedule O \xA7 529A ABLE contribution deduction, capped by the caller at the annual federal gift-tax exclusion ($19,000 for 2025). In dollars.",
25902
+ default: { value: "0", rationale: "No ABLE contributions claimed absent contrary input" }
25903
+ },
25904
+ {
25905
+ id: "paMsaHsaContributions",
25906
+ type: "money",
25907
+ min: "0",
25908
+ description: "PA Schedule O Medical Savings Account + Health Savings Account contribution deductions \u2014 limited to the amounts allowed for FEDERAL income tax purposes (supply the federal-allowed total). In dollars.",
25909
+ default: { value: "0", rationale: "No MSA/HSA deduction claimed absent contrary input" }
25910
+ },
25911
+ {
25912
+ id: "paSpDependentChildren",
25913
+ type: "int",
25914
+ min: "0",
25915
+ description: "PA Schedule SP dependent CHILDREN count (natural/adopted/step; grandchild of a grandparent; foster child of a foster parent \u2014 never aunts/uncles/unrelated persons) claimable as federal dependents; adult qualifying children count. Raises the Tax Forgiveness eligibility-income threshold $9,500 each.",
25916
+ default: {
25917
+ value: "0",
25918
+ rationale: "Conservative: no SP dependent children assumed \u2014 understates the forgiveness threshold, never overstates the credit"
25919
+ }
25920
+ },
25921
+ {
25922
+ id: "paEligibilityIncomeAddbacks",
25923
+ type: "money",
25924
+ min: "0",
25925
+ description: "PA Schedule SP Section III nontaxable add-backs to eligibility income: nontaxable interest/dividends/gains, alimony received, insurance proceeds and inheritances (incl. 1099-R code-4 box 1), gifts/awards/prizes (incl. noncash PA Lottery), non-PA income, nontaxable military pay (not combat), excluded home-sale gain, nontaxable educational assistance, outside cash support. Do NOT add Social Security/RRB, eligible retirement benefits, child support, military pensions, workers' comp, personal-injury damages, or sick/disability pay. In dollars.",
25926
+ default: { value: "0", rationale: "No nontaxable eligibility-income add-backs absent contrary input" }
25927
+ },
25928
+ {
25929
+ id: "paResidentCredit",
25930
+ type: "money",
25931
+ min: "0",
25932
+ description: "PA resident credit for income tax paid to other states (PA-40 line 22, Schedule G-L; not allowed for reciprocal-state compensation: IN, MD, NJ, OH, VA, WV). Subtracts from tax BEFORE Tax Forgiveness per Schedule SP Section IV. In dollars.",
25933
+ default: { value: "0", rationale: "No other-state tax credit claimed absent contrary input" }
25352
25934
  }
25353
25935
  ];
25354
25936
 
@@ -25856,8 +26438,8 @@ function ordinaryRateTaxExpr(base, statusFactId, year) {
25856
26438
  var incomeTaxRules = [
25857
26439
  {
25858
26440
  id: "us.federal.ordinary_taxable_income",
25859
- version: 2,
25860
- // v1 predated the standalone qualifiedDividends fact (dividends had to be folded into longTermCapitalGains)
26441
+ version: 3,
26442
+ // v2 read the raw longTermCapitalGains fact; the § 1(h) amount is now the § 1222(11) netted result from schedule_d
25861
26443
  jurisdiction: "us.federal",
25862
26444
  title: "Taxable income taxed at ordinary rates (excludes preferential gains and qualified dividends)",
25863
26445
  citation: {
@@ -25874,7 +26456,10 @@ var incomeTaxRules = [
25874
26456
  arg: {
25875
26457
  kind: "sub",
25876
26458
  left: taxable,
25877
- right: { kind: "add", args: [fact2("longTermCapitalGains"), fact2("qualifiedDividends")] }
26459
+ right: {
26460
+ kind: "add",
26461
+ args: [ruleRef2("us.federal.schedule_d.preferential_lt_gain"), fact2("qualifiedDividends")]
26462
+ }
25878
26463
  }
25879
26464
  }
25880
26465
  },
@@ -26126,6 +26711,8 @@ var OUT_OF_SCOPE = {
26126
26711
  gt0(fact4("taxableInterest")),
26127
26712
  gt0(fact4("longTermCapitalGains")),
26128
26713
  gt0(fact4("netCapitalLoss")),
26714
+ gt0(fact4("shortTermCapitalLoss")),
26715
+ gt0(fact4("longTermCapitalLoss")),
26129
26716
  gt0(fact4("qualifiedTips")),
26130
26717
  gt0(fact4("qualifiedOvertimePremium")),
26131
26718
  gt0(fact4("carLoanInterest")),
@@ -26274,7 +26861,8 @@ function quarterRule(q, year, from, to, proratedLimitCents, proratedLabel, withQ
26274
26861
  };
26275
26862
  return {
26276
26863
  id: `us.federal.estimated.annualized_installment_q${q.n}`,
26277
- version: year === "2025" ? 1 : 2,
26864
+ version: year === "2025" ? 3 : 4,
26865
+ // v1/v2 predated the per-bucket capital-loss facts in the scope guard
26278
26866
  jurisdiction: J2,
26279
26867
  title: `Annualized-income required installment #${q.n} (\xA7 6654(d)(2), TY${year})`,
26280
26868
  citation: {
@@ -26321,14 +26909,14 @@ var CITE = {
26321
26909
  var capitalLossRules = [
26322
26910
  {
26323
26911
  id: "us.federal.capital_loss_ordinary_offset",
26324
- version: 2,
26325
- // v1 predated the standalone shortTermCapitalGains fact (the "both entered" guard now also catches it)
26912
+ version: 3,
26913
+ // v2 predated schedule_d netting: the loss is now the § 1222(10) netted result (legacy netCapitalLoss passes through; mixed-bucket years compute), and the contradiction refusal lives in the schedule_d rules
26326
26914
  jurisdiction: J3,
26327
26915
  title: "Capital loss allowed against ordinary income ($3,000 / $1,500 MFS limit)",
26328
26916
  citation: {
26329
26917
  ...CITE,
26330
26918
  section: "\xA7 1211(b)",
26331
- excerpt: "\u2026losses from sales or exchanges of capital assets shall be allowed only to the extent of the gains from such sales or exchanges, plus (if such losses exceed such gains) the lower of $3,000 ($1,500 in the case of a married individual filing a separate return), or the excess of such losses over such gains. [The fact model takes Schedule D's NET result \u2014 a net gain (long and/or short term) or a net loss, not both; both at once refuses rather than guess the netting.]"
26919
+ excerpt: "\u2026losses from sales or exchanges of capital assets shall be allowed only to the extent of the gains from such sales or exchanges, plus (if such losses exceed such gains) the lower of $3,000 ($1,500 in the case of a married individual filing a separate return), or the excess of such losses over such gains. [The loss here is the \xA7 1222(10) net capital loss after Schedule D netting \u2014 computed by us.federal.schedule_d.net_loss from the per-bucket facts, or passed through from the legacy overall netCapitalLoss fact; contradictory fact combinations refuse there.]"
26332
26920
  },
26333
26921
  ...FROM,
26334
26922
  output: { type: "money" },
@@ -26339,45 +26927,27 @@ var capitalLossRules = [
26339
26927
  // $1,500
26340
26928
  },
26341
26929
  formula: {
26342
- kind: "if",
26343
- cond: { kind: "cmp", op: "gt", left: fact5("netCapitalLoss"), right: zero3 },
26344
- then: {
26345
- kind: "if",
26346
- cond: {
26347
- kind: "or",
26348
- args: [
26349
- { kind: "cmp", op: "gt", left: fact5("longTermCapitalGains"), right: zero3 },
26350
- { kind: "cmp", op: "gt", left: fact5("shortTermCapitalGains"), right: zero3 }
26351
- ]
26352
- },
26353
- then: {
26354
- kind: "unsupported",
26355
- reason: "both a net capital gain and a net capital loss were entered \u2014 provide the single NET result of Schedule D netting"
26356
- },
26357
- else: {
26358
- kind: "min",
26359
- args: [
26360
- fact5("netCapitalLoss"),
26361
- {
26362
- kind: "if",
26363
- cond: {
26364
- kind: "cmp",
26365
- op: "eq",
26366
- left: fact5("filingStatus"),
26367
- right: { kind: "enum", value: "mfs" }
26368
- },
26369
- then: param3("limitMfs"),
26370
- else: param3("limit")
26371
- }
26372
- ]
26930
+ kind: "min",
26931
+ args: [
26932
+ ruleRef5("us.federal.schedule_d.net_loss"),
26933
+ {
26934
+ kind: "if",
26935
+ cond: {
26936
+ kind: "cmp",
26937
+ op: "eq",
26938
+ left: fact5("filingStatus"),
26939
+ right: { kind: "enum", value: "mfs" }
26940
+ },
26941
+ then: param3("limitMfs"),
26942
+ else: param3("limit")
26373
26943
  }
26374
- },
26375
- else: zero3
26944
+ ]
26376
26945
  }
26377
26946
  },
26378
26947
  {
26379
26948
  id: "us.federal.capital_loss_carryover",
26380
- version: 1,
26949
+ version: 2,
26950
+ // v1 read the legacy netCapitalLoss fact directly; the netted schedule_d result also covers mixed-bucket years
26381
26951
  jurisdiction: J3,
26382
26952
  title: "Capital loss carryover to the following year (simplified)",
26383
26953
  citation: {
@@ -26391,7 +26961,7 @@ var capitalLossRules = [
26391
26961
  kind: "max0",
26392
26962
  arg: {
26393
26963
  kind: "sub",
26394
- left: fact5("netCapitalLoss"),
26964
+ left: ruleRef5("us.federal.schedule_d.net_loss"),
26395
26965
  right: ruleRef5("us.federal.capital_loss_ordinary_offset")
26396
26966
  }
26397
26967
  }
@@ -29061,9 +29631,9 @@ function eitcRule(version2, effectiveFrom, effectiveTo, p, yearLabel, revProc, o
29061
29631
  kind: "add",
29062
29632
  args: [
29063
29633
  fact17("taxableInterest"),
29064
- fact17("longTermCapitalGains"),
29634
+ ruleRef14("us.federal.schedule_d.preferential_lt_gain"),
29065
29635
  fact17("qualifiedDividends"),
29066
- fact17("shortTermCapitalGains"),
29636
+ ruleRef14("us.federal.schedule_d.ordinary_st_gain"),
29067
29637
  fact17("ordinaryDividends")
29068
29638
  ]
29069
29639
  },
@@ -29171,19 +29741,21 @@ var eitcRules = [
29171
29741
  // v5/v6: table method default (§ 32(f)) + eitcAdditionalQualifyingChildren
29172
29742
  // v8/v9: § 32(i) investment-income limit also counts the standalone qualifiedDividends fact
29173
29743
  // v10/v11: § 32(i) investment-income limit also counts shortTermCapitalGains/ordinaryDividends
29174
- eitcRule(12, "2025-01-01", "2026-01-01", TY20252, "2025", "Rev. Proc. 2024-40 \xA7 2.06"),
29175
- eitcRule(13, "2026-01-01", "2027-01-01", TY20262, "2026", "Rev. Proc. 2025-32 \xA7 4.06"),
29744
+ // v12/v13 read the raw gain facts; the § 32(i) test now takes the schedule_d
29745
+ // netted results ("capital gain net income", § 32(i)(1)(C))
29746
+ eitcRule(14, "2025-01-01", "2026-01-01", TY20252, "2025", "Rev. Proc. 2024-40 \xA7 2.06"),
29747
+ eitcRule(15, "2026-01-01", "2027-01-01", TY20262, "2026", "Rev. Proc. 2025-32 \xA7 4.06"),
29176
29748
  // us.federal.eitc_no_age_gate (new): the same § 32(a)-(b),(f),(i) formula with
29177
29749
  // the childless § 32(c)(1)(A)(ii) age gate removed, for state EITC statutes
29178
29750
  // that decouple from it (35 ILCS 5/212(b-5)/(b-10), IL P.A. 102-700) — see
29179
29751
  // us.il.eitc, which is 20% of THIS rule's answer, not us.federal.eitc's.
29180
- eitcRule(1, "2025-01-01", "2026-01-01", TY20252, "2025", "Rev. Proc. 2024-40 \xA7 2.06", {
29752
+ eitcRule(3, "2025-01-01", "2026-01-01", TY20252, "2025", "Rev. Proc. 2024-40 \xA7 2.06", {
29181
29753
  ruleId: "us.federal.eitc_no_age_gate",
29182
29754
  ageGate: false,
29183
29755
  titleSuffix: "Earned income credit, \xA7 32(c)(1)(A)(ii) childless age gate REMOVED (TY2025; for state decoupling statutes only)",
29184
29756
  extraExcerpt: NO_AGE_GATE_EXCERPT
29185
29757
  }),
29186
- eitcRule(2, "2026-01-01", "2027-01-01", TY20262, "2026", "Rev. Proc. 2025-32 \xA7 4.06", {
29758
+ eitcRule(4, "2026-01-01", "2027-01-01", TY20262, "2026", "Rev. Proc. 2025-32 \xA7 4.06", {
29187
29759
  ruleId: "us.federal.eitc_no_age_gate",
29188
29760
  ageGate: false,
29189
29761
  titleSuffix: "Earned income credit, \xA7 32(c)(1)(A)(ii) childless age gate REMOVED (TY2026; for state decoupling statutes only)",
@@ -30108,8 +30680,8 @@ var FROM10 = { effectiveFrom: "2025-01-01" };
30108
30680
  var incomeRules = [
30109
30681
  {
30110
30682
  id: "us.federal.gross_income",
30111
- version: 14,
30112
- // v13 lacked taxable HSA distributions; // v11 predated the standalone shortTermCapitalGains/ordinaryDividends facts
30683
+ version: 15,
30684
+ // v14 read the raw gain facts; capital gains now enter via schedule_d netting (mixed gain/loss years compute instead of refusing), and rental income can enter pre-depreciation via us.federal.rental.net_income
30113
30685
  jurisdiction: J16,
30114
30686
  title: "Gross income (simplified: wages + interest + capital gains (long/short-term) + qualified/ordinary dividends + SE net profit + K-1 pass-through income \u2212 allowed capital loss \u2212 \xA7 911 exclusion)",
30115
30687
  citation: {
@@ -30127,14 +30699,15 @@ var incomeRules = [
30127
30699
  args: [
30128
30700
  fact24("wages"),
30129
30701
  fact24("taxableInterest"),
30130
- fact24("longTermCapitalGains"),
30702
+ ruleRef19("us.federal.schedule_d.preferential_lt_gain"),
30131
30703
  fact24("qualifiedDividends"),
30132
- fact24("shortTermCapitalGains"),
30704
+ ruleRef19("us.federal.schedule_d.ordinary_st_gain"),
30133
30705
  fact24("ordinaryDividends"),
30134
30706
  fact24("selfEmploymentNetProfit"),
30135
30707
  fact24("k1OrdinaryBusinessIncome"),
30136
30708
  fact24("taxableIraDistributions"),
30137
30709
  fact24("taxablePensionsAndAnnuities"),
30710
+ ruleRef19("us.federal.pension.simplified_method_taxable"),
30138
30711
  fact24("unemploymentCompensation"),
30139
30712
  fact24("otherOrdinaryIncome"),
30140
30713
  fact24("alimonyReceivedPre2019"),
@@ -30148,6 +30721,7 @@ var incomeRules = [
30148
30721
  }
30149
30722
  },
30150
30723
  fact24("scheduleENetIncome"),
30724
+ ruleRef19("us.federal.rental.net_income"),
30151
30725
  ruleRef19("us.federal.taxable_social_security")
30152
30726
  ]
30153
30727
  },
@@ -30172,7 +30746,7 @@ var incomeRules = [
30172
30746
  source: "26 U.S.C. \xA7 62(a)(1), (a)(2)(D), (a)(6), (a)(9), (a)(17), (a)(19)",
30173
30747
  section: "\xA7 62(a)",
30174
30748
  url: "https://www.law.cornell.edu/uscode/text/26/62",
30175
- excerpt: "adjusted gross income means gross income minus\u2026 the deduction allowed by section 164(f) [half of SE tax]\u2026 the deduction allowed by section 223 [HSA]\u2026 the deductions allowed by section 404 [self-employed retirement plans]\u2026 the deduction allowed by section 162(l) [self-employed health insurance]\u2026 the deductions\u2026 [for] expenses\u2026 of eligible educators (\xA7 62(a)(2)(D) \u2014 $300 cap PER ELIGIBLE EDUCATOR for 2025 (an MFJ couple of two educators deducts up to $600: use educatorExpenses for the taxpayer and spouseEducatorExpenses for the spouse, each capped separately), the indexed amount, reference-validated against the TaxCalcBench ty25-ca-002 return)\u2026 [and] penalties forfeited because of premature withdrawal of funds from time savings accounts or deposits (\xA7 62(a)(9), uncapped)."
30749
+ excerpt: "adjusted gross income means gross income minus\u2026 the deduction allowed by section 164(f) [half of SE tax]\u2026 the deduction allowed by section 223 [HSA]\u2026 the deductions allowed by section 404 [self-employed retirement plans]\u2026 the deduction allowed by section 162(l) [self-employed health insurance]\u2026 the deductions\u2026 [for] expenses\u2026 of eligible educators (\xA7 62(a)(2)(D) \u2014 $300 cap PER ELIGIBLE EDUCATOR for 2025 (an MFJ couple of two educators deducts up to $600: use educatorExpenses for the taxpayer and spouseEducatorExpenses for the spouse, each capped separately), the 2025 indexed amount)\u2026 [and] penalties forfeited because of premature withdrawal of funds from time savings accounts or deposits (\xA7 62(a)(9), uncapped)."
30176
30750
  },
30177
30751
  ...FROM10,
30178
30752
  output: { type: "money" },
@@ -30739,8 +31313,8 @@ var amThreshold = {
30739
31313
  var investmentTaxRules = [
30740
31314
  {
30741
31315
  id: "us.federal.niit",
30742
- version: 5,
30743
- // v4 predated the standalone shortTermCapitalGains/ordinaryDividends facts
31316
+ version: 6,
31317
+ // v5 read the raw gain facts; § 1411 net investment income now takes the schedule_d netted results (losses reduce gains before NIIT)
30744
31318
  jurisdiction: "us.federal",
30745
31319
  title: "Net investment income tax (investment income = interest + capital gains (LT/ST) + dividends (qualified/ordinary) \u2212 allocable deductions; MAGI \u2248 AGI)",
30746
31320
  citation: {
@@ -30765,9 +31339,9 @@ var investmentTaxRules = [
30765
31339
  kind: "add",
30766
31340
  args: [
30767
31341
  fact26("taxableInterest"),
30768
- fact26("longTermCapitalGains"),
31342
+ ruleRef21("us.federal.schedule_d.preferential_lt_gain"),
30769
31343
  fact26("qualifiedDividends"),
30770
- fact26("shortTermCapitalGains"),
31344
+ ruleRef21("us.federal.schedule_d.ordinary_st_gain"),
30771
31345
  fact26("ordinaryDividends")
30772
31346
  ]
30773
31347
  },
@@ -31640,12 +32214,15 @@ function kiddieTaxRule(version2, from, to, year) {
31640
32214
  { kind: "cmp", op: "gt", left: fact30("longTermCapitalGains"), right: zero18 },
31641
32215
  { kind: "cmp", op: "gt", left: fact30("qualifiedDividends"), right: zero18 },
31642
32216
  { kind: "cmp", op: "gt", left: fact30("shortTermCapitalGains"), right: zero18 },
31643
- { kind: "cmp", op: "gt", left: fact30("ordinaryDividends"), right: zero18 }
32217
+ { kind: "cmp", op: "gt", left: fact30("ordinaryDividends"), right: zero18 },
32218
+ { kind: "cmp", op: "gt", left: fact30("shortTermCapitalLoss"), right: zero18 },
32219
+ { kind: "cmp", op: "gt", left: fact30("longTermCapitalLoss"), right: zero18 },
32220
+ { kind: "cmp", op: "gt", left: fact30("netCapitalLoss"), right: zero18 }
31644
32221
  ]
31645
32222
  },
31646
32223
  then: {
31647
32224
  kind: "unsupported",
31648
- reason: "a \xA7 1(g) child with capital gains, qualified dividends, or ordinary dividends needs the Form 8615 capital-gains worksheets, and/or is unearned income this corpus's 'unearned income \u2248 taxable interest' approximation would silently drop \u2014 not modeled"
32225
+ reason: "a \xA7 1(g) child with capital gains/losses, qualified dividends, or ordinary dividends needs the Form 8615 capital-gains worksheets, and/or is unearned income this corpus's 'unearned income \u2248 taxable interest' approximation would silently drop \u2014 not modeled"
31649
32226
  },
31650
32227
  else: {
31651
32228
  kind: "if",
@@ -31705,8 +32282,9 @@ var kiddieRules = [
31705
32282
  // v3/v4: the § 1(g) capital-gains refusal guard also catches the standalone qualifiedDividends fact
31706
32283
  // v5/v6: the refusal guard also catches shortTermCapitalGains/ordinaryDividends (unearned income
31707
32284
  // the "unearned income ≈ taxable interest" approximation in net_unearned_income would otherwise drop)
31708
- kiddieTaxRule(5, "2025-01-01", "2026-01-01", "2025"),
31709
- kiddieTaxRule(6, "2026-01-01", "2027-01-01", "2026"),
32285
+ // v5/v6: the refusal guard predated the per-bucket capital-loss facts and netCapitalLoss
32286
+ kiddieTaxRule(7, "2025-01-01", "2026-01-01", "2025"),
32287
+ kiddieTaxRule(8, "2026-01-01", "2027-01-01", "2026"),
31710
32288
  {
31711
32289
  id: "us.federal.income_tax_before_credits.kiddie",
31712
32290
  version: 1,
@@ -31923,7 +32501,7 @@ function mileageRule(version2, from, to, rateNum, rateLabel, noticeLabel) {
31923
32501
  source: `${noticeLabel}; Rev. Proc. 2019-46`,
31924
32502
  section: `${noticeLabel} \xA7 3`,
31925
32503
  url: "https://www.irs.gov/pub/irs-drop/n-26-10.pdf",
31926
- excerpt: `The standard mileage rate for transportation or travel expenses is ${rateLabel} per mile for all miles of business use (${noticeLabel}, verbatim rate). [A STANDALONE determination in lieu of actual vehicle expenses (Rev. Proc. 2019-46 rules attested): subtract the result from your Schedule C profit yourself \u2014 it changes SE tax and QBI. Depreciation-component basis tracking and the actual-expense method are not modeled.] ADD-ON EXPENSES (Pub. 463 ch. 4, reference-validated ty25-va-007/va-010): the mileage rate covers depreciation/gas/repairs ONLY. PARKING FEES and TOLLS for business trips deduct IN FULL in addition. The vehicle's PERSONAL PROPERTY TAX and (for the self-employed) CAR LOAN INTEREST deduct in addition but ONLY at the BUSINESS-USE PERCENTAGE = business miles \xF7 TOTAL miles driven (business + commuting + other/personal), computed and rounded (half-up to the dollar) PER VEHICLE: per-vehicle deduction = miles \xD7 rate + parking + tolls + businessPct \xD7 (property tax + loan interest). A vehicle with no commuting/other miles listed is 100% business use (add-ons in full).`
32504
+ excerpt: `The standard mileage rate for transportation or travel expenses is ${rateLabel} per mile for all miles of business use (${noticeLabel}, verbatim rate). [A STANDALONE determination in lieu of actual vehicle expenses (Rev. Proc. 2019-46 rules attested): subtract the result from your Schedule C profit yourself \u2014 it changes SE tax and QBI. Depreciation-component basis tracking and the actual-expense method are not modeled.] ADD-ON EXPENSES (Pub. 463 ch. 4): the mileage rate covers depreciation/gas/repairs ONLY. PARKING FEES and TOLLS for business trips deduct IN FULL in addition. The vehicle's PERSONAL PROPERTY TAX and (for the self-employed) CAR LOAN INTEREST deduct in addition but ONLY at the BUSINESS-USE PERCENTAGE = business miles \xF7 TOTAL miles driven (business + commuting + other/personal), computed and rounded (half-up to the dollar) PER VEHICLE: per-vehicle deduction = miles \xD7 rate + parking + tolls + businessPct \xD7 (property tax + loan interest). A vehicle with no commuting/other miles listed is 100% business use (add-ons in full).`
31927
32505
  },
31928
32506
  effectiveFrom: from,
31929
32507
  effectiveTo: to,
@@ -31942,57 +32520,376 @@ var mileageRules = [
31942
32520
  mileageRule(2, "2026-01-01", "2027-01-01", "725", "72.5 cents", "Notice 2026-10")
31943
32521
  ];
31944
32522
 
31945
- // ../corpus-us-federal/dist/rules/schedule-r.js
32523
+ // ../corpus-us-federal/dist/rules/pension.js
31946
32524
  var J23 = "us.federal";
32525
+ var FROM13 = { effectiveFrom: "2025-01-01" };
31947
32526
  var fact32 = (factId) => ({ kind: "fact", factId });
31948
32527
  var money29 = (cents) => ({ kind: "money", cents });
32528
+ var int2 = (value) => ({ kind: "int", value });
31949
32529
  var ruleRef27 = (ruleId) => ({ kind: "rule", ruleId });
31950
32530
  var zero20 = money29("0");
32531
+ var cost = fact32("pensionCostBasis");
32532
+ var months = fact32("pensionMonthsThisYear");
32533
+ var annualAt = (n) => ({
32534
+ kind: "mulInt",
32535
+ base: { kind: "mulRate", base: cost, rate: { num: "1", den: n }, round: "half-up" },
32536
+ count: months
32537
+ });
32538
+ var ladder = (on, bands, last) => {
32539
+ let expr = annualAt(last);
32540
+ for (let i = bands.length - 1; i >= 0; i--) {
32541
+ const [limit, n] = bands[i];
32542
+ expr = {
32543
+ kind: "if",
32544
+ cond: { kind: "cmp", op: "le", left: on, right: int2(limit) },
32545
+ then: annualAt(n),
32546
+ else: expr
32547
+ };
32548
+ }
32549
+ return expr;
32550
+ };
32551
+ var needsAge = (ageFact) => ({
32552
+ kind: "cmp",
32553
+ op: "eq",
32554
+ left: fact32(ageFact),
32555
+ right: int2("0")
32556
+ });
32557
+ var pensionRules = [
32558
+ {
32559
+ id: "us.federal.pension.simplified_method_exclusion",
32560
+ version: 1,
32561
+ jurisdiction: J23,
32562
+ title: "Simplified Method tax-free portion (\xA7 72(d): cost \xF7 anticipated payments \xD7 months, capped at unrecovered cost)",
32563
+ citation: {
32564
+ source: "26 U.S.C. \xA7 72(d); IRS Pub 575 (Simplified Method Worksheet, Tables 1-2)",
32565
+ section: "\xA7 72(d)(1)(B)(iii)-(iv)",
32566
+ url: "https://www.law.cornell.edu/uscode/text/26/72",
32567
+ excerpt: "Gross income shall not include so much of any monthly annuity payment\u2026 as does not exceed the amount obtained by dividing (I) the investment in the contract (as of the annuity starting date), by (II) the number of anticipated payments\u2026 Age at annuity starting date: not more than 55 \u2192 360; more than 55 but not more than 60 \u2192 310; more than 60 but not more than 65 \u2192 260; more than 65 but not more than 70 \u2192 210; more than 70 \u2192 160. Combined ages (more than one life): not more than 110 \u2192 410; \u2026120 \u2192 360; \u2026130 \u2192 310; \u2026140 \u2192 260; more than 140 \u2192 210. [Lifetime recovery capped at total cost (Pub 575); monthly division keeps cents per the Pub 575 worked example. The rule refuses when the basis is set but the required age fact is not.]"
32568
+ },
32569
+ ...FROM13,
32570
+ output: { type: "money" },
32571
+ formula: {
32572
+ kind: "if",
32573
+ cond: { kind: "cmp", op: "eq", left: cost, right: zero20 },
32574
+ then: zero20,
32575
+ else: {
32576
+ kind: "if",
32577
+ cond: fact32("pensionIsJointAndSurvivor"),
32578
+ then: {
32579
+ kind: "if",
32580
+ cond: needsAge("pensionCombinedAgesAtStart"),
32581
+ then: {
32582
+ kind: "unsupported",
32583
+ reason: "a joint-and-survivor simplified-method annuity needs pensionCombinedAgesAtStart (combined ages at the annuity starting date) \u2014 refusing rather than assume a \xA7 72(d)(1)(B)(iv) band"
32584
+ },
32585
+ else: {
32586
+ kind: "min",
32587
+ args: [
32588
+ ladder(fact32("pensionCombinedAgesAtStart"), [
32589
+ ["110", "410"],
32590
+ ["120", "360"],
32591
+ ["130", "310"],
32592
+ ["140", "260"]
32593
+ ], "210"),
32594
+ { kind: "max0", arg: { kind: "sub", left: cost, right: fact32("pensionBasisPreviouslyRecovered") } }
32595
+ ]
32596
+ }
32597
+ },
32598
+ else: {
32599
+ kind: "if",
32600
+ cond: needsAge("pensionAgeAtStart"),
32601
+ then: {
32602
+ kind: "unsupported",
32603
+ reason: "a simplified-method annuity needs pensionAgeAtStart (age at the annuity starting date, NOT current age) \u2014 refusing rather than assume a \xA7 72(d)(1)(B)(iii) band"
32604
+ },
32605
+ else: {
32606
+ kind: "min",
32607
+ args: [
32608
+ ladder(fact32("pensionAgeAtStart"), [
32609
+ ["55", "360"],
32610
+ ["60", "310"],
32611
+ ["65", "260"],
32612
+ ["70", "210"]
32613
+ ], "160"),
32614
+ { kind: "max0", arg: { kind: "sub", left: cost, right: fact32("pensionBasisPreviouslyRecovered") } }
32615
+ ]
32616
+ }
32617
+ }
32618
+ }
32619
+ }
32620
+ },
32621
+ {
32622
+ id: "us.federal.pension.simplified_method_taxable",
32623
+ version: 1,
32624
+ jurisdiction: J23,
32625
+ title: "Taxable pension under the Simplified Method (gross payments minus the \xA7 72(d) exclusion)",
32626
+ citation: {
32627
+ source: "26 U.S.C. \xA7 72(a), (d); IRS Pub 575",
32628
+ section: "\xA7 72(a), (d)(1)(B)(i)",
32629
+ url: "https://www.law.cornell.edu/uscode/text/26/72",
32630
+ excerpt: "Except as otherwise provided in this chapter, gross income includes any amount received as an annuity\u2026 gross income shall not include so much of any monthly annuity payment\u2026 as does not exceed [the \xA7 72(d) exclusion]. [Feeds gross income alongside the already-taxable taxablePensionsAndAnnuities fact \u2014 the two inputs are additive for separate pensions.]"
32631
+ },
32632
+ ...FROM13,
32633
+ output: { type: "money" },
32634
+ formula: {
32635
+ kind: "max0",
32636
+ arg: {
32637
+ kind: "sub",
32638
+ left: fact32("pensionGrossPayments"),
32639
+ right: ruleRef27("us.federal.pension.simplified_method_exclusion")
32640
+ }
32641
+ }
32642
+ }
32643
+ ];
32644
+
32645
+ // ../corpus-us-federal/dist/rules/rental.js
32646
+ var J24 = "us.federal";
32647
+ var FROM14 = { effectiveFrom: "2025-01-01" };
32648
+ var fact33 = (factId) => ({ kind: "fact", factId });
32649
+ var money30 = (cents) => ({ kind: "money", cents });
32650
+ var int3 = (value) => ({ kind: "int", value });
32651
+ var ruleRef28 = (ruleId) => ({ kind: "rule", ruleId });
32652
+ var zero21 = money30("0");
32653
+ var YEAR1_RATE_PER_100K = [
32654
+ "3485",
32655
+ // Jan
32656
+ "3182",
32657
+ // Feb
32658
+ "2879",
32659
+ // Mar
32660
+ "2576",
32661
+ // Apr
32662
+ "2273",
32663
+ // May
32664
+ "1970",
32665
+ // Jun
32666
+ "1667",
32667
+ // Jul
32668
+ "1364",
32669
+ // Aug
32670
+ "1061",
32671
+ // Sep
32672
+ "758",
32673
+ // Oct
32674
+ "455",
32675
+ // Nov
32676
+ "152"
32677
+ // Dec
32678
+ ];
32679
+ var STEADY_RATE_PER_100K = "3636";
32680
+ var rateOf = (per100k) => ({
32681
+ kind: "mulRate",
32682
+ base: fact33("rentalDepreciableBasis"),
32683
+ rate: { num: per100k, den: "100000" },
32684
+ round: "half-up"
32685
+ });
32686
+ var monthTable = () => {
32687
+ let expr = rateOf(STEADY_RATE_PER_100K);
32688
+ for (let m = 12; m >= 1; m--) {
32689
+ expr = {
32690
+ kind: "if",
32691
+ cond: {
32692
+ kind: "cmp",
32693
+ op: "eq",
32694
+ left: fact33("rentalPlacedInServiceMonth"),
32695
+ right: int3(String(m))
32696
+ },
32697
+ then: rateOf(YEAR1_RATE_PER_100K[m - 1]),
32698
+ else: expr
32699
+ };
32700
+ }
32701
+ return expr;
32702
+ };
32703
+ var CITE_BASE = {
32704
+ source: "26 U.S.C. \xA7 168; IRS Pub 946 Table A-6; IRS Pub 527 ch. 2",
32705
+ url: "https://www.irs.gov/publications/p946"
32706
+ };
32707
+ var rentalRules = [
32708
+ {
32709
+ id: "us.federal.rental.depreciation",
32710
+ version: 1,
32711
+ jurisdiction: J24,
32712
+ title: "Residential rental depreciation (\xA7 168 GDS: 27.5-yr straight line, mid-month)",
32713
+ citation: {
32714
+ ...CITE_BASE,
32715
+ section: "\xA7 168(b)(3)(B), (c), (d)(2)",
32716
+ excerpt: "In the case of residential rental property\u2026 the applicable depreciation method is the straight line method\u2026 the applicable recovery period is 27.5 years\u2026 the applicable convention is the mid-month convention. [Pub 946 Table A-6 year-1 percentages by placed-in-service month: 3.485, 3.182, 2.879, 2.576, 2.273, 1.970, 1.667, 1.364, 1.061, 0.758, 0.455, 0.152; full years thereafter 3.636 \u2014 the printed three-decimal table, which is what filed returns use. Basis is the BUILDING only (land never depreciates, \xA7 167). Not modeled, disclosed: ADS elections, separately-dated improvements, the final recovery year's stub, and mid-year dispositions.]"
32717
+ },
32718
+ ...FROM14,
32719
+ output: { type: "money" },
32720
+ formula: {
32721
+ kind: "if",
32722
+ cond: { kind: "cmp", op: "eq", left: fact33("rentalDepreciableBasis"), right: zero21 },
32723
+ then: zero21,
32724
+ else: monthTable()
32725
+ }
32726
+ },
32727
+ {
32728
+ id: "us.federal.rental.net_income",
32729
+ version: 1,
32730
+ jurisdiction: J24,
32731
+ title: "Net rental income: Schedule E result before depreciation minus \xA7 168 depreciation",
32732
+ citation: {
32733
+ ...CITE_BASE,
32734
+ section: "\xA7 62(a)(4); Schedule E (Form 1040) line 18",
32735
+ excerpt: "Deductions attributable to rents\u2026 are allowed in arriving at adjusted gross income (\xA7 62(a)(4)); Schedule E line 18: 'Depreciation expense or depletion.' [When the computed depreciation EXCEEDS the pre-depreciation rental result, the year is a rental LOSS governed by the \xA7 469 passive-activity rules \u2014 enter it through rentalActiveParticipationLosses (the $25,000 allowance machinery) instead; this rule refuses rather than route a loss around \xA7 469.]"
32736
+ },
32737
+ ...FROM14,
32738
+ output: { type: "money" },
32739
+ formula: {
32740
+ kind: "if",
32741
+ cond: {
32742
+ kind: "cmp",
32743
+ op: "gt",
32744
+ left: ruleRef28("us.federal.rental.depreciation"),
32745
+ right: fact33("rentalIncomeBeforeDepreciation")
32746
+ },
32747
+ then: {
32748
+ kind: "if",
32749
+ cond: { kind: "cmp", op: "eq", left: fact33("rentalIncomeBeforeDepreciation"), right: zero21 },
32750
+ then: zero21,
32751
+ // depreciation facts present but no income routed through this path: nothing to net
32752
+ else: {
32753
+ kind: "unsupported",
32754
+ reason: "depreciation exceeds the pre-depreciation rental result \u2014 this year is a rental LOSS subject to the \xA7 469 passive-activity limits: enter it via rentalActiveParticipationLosses (the $25,000 active-participation allowance applies) rather than through this income path"
32755
+ }
32756
+ },
32757
+ else: {
32758
+ kind: "sub",
32759
+ left: fact33("rentalIncomeBeforeDepreciation"),
32760
+ right: ruleRef28("us.federal.rental.depreciation")
32761
+ }
32762
+ }
32763
+ }
32764
+ ];
32765
+
32766
+ // ../corpus-us-federal/dist/rules/schedule-d.js
32767
+ var J25 = "us.federal";
32768
+ var FROM15 = { effectiveFrom: "2025-01-01" };
32769
+ var fact34 = (factId) => ({ kind: "fact", factId });
32770
+ var money31 = (cents) => ({ kind: "money", cents });
32771
+ var zero22 = money31("0");
32772
+ var gt09 = (e) => ({ kind: "cmp", op: "gt", left: e, right: zero22 });
32773
+ var max03 = (arg) => ({ kind: "max0", arg });
32774
+ var sub = (left, right) => ({ kind: "sub", left, right });
32775
+ var add = (...args) => ({ kind: "add", args });
32776
+ var and = (...args) => ({ kind: "and", args });
32777
+ var or = (...args) => ({ kind: "or", args });
32778
+ var stG = fact34("shortTermCapitalGains");
32779
+ var stL = fact34("shortTermCapitalLoss");
32780
+ var ltG = fact34("longTermCapitalGains");
32781
+ var ltL = fact34("longTermCapitalLoss");
32782
+ var legacyLoss = fact34("netCapitalLoss");
32783
+ var CONTRADICTORY = or(and(gt09(stG), gt09(stL)), and(gt09(ltG), gt09(ltL)), and(gt09(legacyLoss), or(gt09(stG), gt09(ltG), gt09(stL), gt09(ltL))));
32784
+ var refuse = (computation) => ({
32785
+ kind: "if",
32786
+ cond: CONTRADICTORY,
32787
+ then: {
32788
+ kind: "unsupported",
32789
+ reason: "contradictory Schedule D facts \u2014 each capital fact is a NET result: a bucket takes a gain OR a loss (never both), and the overall netCapitalLoss cannot be combined with per-bucket facts"
32790
+ },
32791
+ else: computation
32792
+ });
32793
+ var CITE5 = {
32794
+ source: "26 U.S.C. \xA7 1222; 2025 Schedule D (Form 1040)",
32795
+ url: "https://www.law.cornell.edu/uscode/text/26/1222"
32796
+ };
32797
+ var scheduleDRules = [
32798
+ {
32799
+ id: "us.federal.schedule_d.preferential_lt_gain",
32800
+ version: 1,
32801
+ jurisdiction: J25,
32802
+ title: "Net capital gain (\xA7 1222(11)): net LT gain reduced by any net ST loss",
32803
+ citation: {
32804
+ ...CITE5,
32805
+ section: "\xA7 1222(11)",
32806
+ excerpt: "The term 'net capital gain' means the excess of the net long-term capital gain for the taxable year over the net short-term capital loss for such year. [Schedule D: line 16 combines line 7 (net short-term) and line 15 (net long-term); when line 7 is a loss and line 15 a gain, the remainder keeps long-term character and prices through the \xA7 1(h) stack. Inputs are per-bucket NET results; contradictory combinations refuse.]"
32807
+ },
32808
+ ...FROM15,
32809
+ output: { type: "money" },
32810
+ formula: refuse(max03(sub(sub(ltG, ltL), max03(sub(stL, stG)))))
32811
+ },
32812
+ {
32813
+ id: "us.federal.schedule_d.ordinary_st_gain",
32814
+ version: 1,
32815
+ jurisdiction: J25,
32816
+ title: "Net short-term capital gain surviving netting (\xA7 1222(5), (9)): ordinary-rate",
32817
+ citation: {
32818
+ ...CITE5,
32819
+ section: "\xA7 1222(5), (9)",
32820
+ excerpt: "'Net short-term capital gain' means the excess of short-term capital gains for the taxable year over the short-term capital losses for such year\u2026 'net long-term capital loss' means the excess of long-term capital losses\u2026 over the long-term capital gains. [Schedule D line 16: a net LT loss reduces the net ST gain; whatever short-term gain survives is ordinary income \u2014 \xA7 1(h) prices only the \xA7 1222(11) net capital gain.]"
32821
+ },
32822
+ ...FROM15,
32823
+ output: { type: "money" },
32824
+ formula: refuse(max03(sub(sub(stG, stL), max03(sub(ltL, ltG)))))
32825
+ },
32826
+ {
32827
+ id: "us.federal.schedule_d.net_loss",
32828
+ version: 1,
32829
+ jurisdiction: J25,
32830
+ title: "Net capital loss after netting (\xA7 1222(10)), feeding the \xA7 1211(b) offset",
32831
+ citation: {
32832
+ ...CITE5,
32833
+ section: "\xA7 1222(10)",
32834
+ excerpt: "The term 'net capital loss' means the excess of the losses from sales or exchanges of capital assets over the sum allowed under section 1211. [Computed as the overall negative of Schedule D line 16, entered here positive: the legacy netCapitalLoss fact passes through unchanged, or the per-bucket facts net to a loss when bucket losses exceed bucket gains. \xA7 1211(b) caps the ordinary-income offset; \xA7 1212(b) carries the rest forward.]"
32835
+ },
32836
+ ...FROM15,
32837
+ output: { type: "money" },
32838
+ formula: refuse(add(legacyLoss, max03(sub(add(stL, ltL), add(stG, ltG)))))
32839
+ }
32840
+ ];
32841
+
32842
+ // ../corpus-us-federal/dist/rules/schedule-r.js
32843
+ var J26 = "us.federal";
32844
+ var fact35 = (factId) => ({ kind: "fact", factId });
32845
+ var money32 = (cents) => ({ kind: "money", cents });
32846
+ var ruleRef29 = (ruleId) => ({ kind: "rule", ruleId });
32847
+ var zero23 = money32("0");
31951
32848
  var isStatus9 = (v) => ({
31952
32849
  kind: "cmp",
31953
32850
  op: "eq",
31954
- left: fact32("filingStatus"),
32851
+ left: fact35("filingStatus"),
31955
32852
  right: { kind: "enum", value: v }
31956
32853
  });
31957
32854
  var qualified = {
31958
32855
  kind: "or",
31959
- args: [fact32("isAge65OrOlder"), fact32("isRetiredOnTotalDisability")]
32856
+ args: [fact35("isAge65OrOlder"), fact35("isRetiredOnTotalDisability")]
31960
32857
  };
31961
32858
  var initialByStatus = {
31962
32859
  kind: "if",
31963
32860
  cond: isStatus9("mfs"),
31964
- then: money29("375000"),
32861
+ then: money32("375000"),
31965
32862
  else: {
31966
32863
  kind: "if",
31967
32864
  cond: {
31968
32865
  kind: "and",
31969
32866
  args: [
31970
32867
  isStatus9("mfj"),
31971
- fact32("spouseIsAge65OrOlder")
32868
+ fact35("spouseIsAge65OrOlder")
31972
32869
  // spouse-disabled qualification not modeled — disclosed
31973
32870
  ]
31974
32871
  },
31975
- then: money29("750000"),
31976
- else: money29("500000")
32872
+ then: money32("750000"),
32873
+ else: money32("500000")
31977
32874
  }
31978
32875
  };
31979
32876
  var initial = {
31980
32877
  kind: "if",
31981
- cond: fact32("isAge65OrOlder"),
32878
+ cond: fact35("isAge65OrOlder"),
31982
32879
  then: initialByStatus,
31983
- else: { kind: "min", args: [initialByStatus, fact32("scheduleRDisabilityIncome")] }
32880
+ else: { kind: "min", args: [initialByStatus, fact35("scheduleRDisabilityIncome")] }
31984
32881
  };
31985
32882
  var agiThreshold = {
31986
32883
  kind: "if",
31987
32884
  cond: isStatus9("mfj"),
31988
- then: money29("1000000"),
32885
+ then: money32("1000000"),
31989
32886
  // $10,000
31990
32887
  else: {
31991
32888
  kind: "if",
31992
32889
  cond: isStatus9("mfs"),
31993
- then: money29("500000"),
32890
+ then: money32("500000"),
31994
32891
  // $5,000
31995
- else: money29("750000")
32892
+ else: money32("750000")
31996
32893
  // $7,500
31997
32894
  }
31998
32895
  };
@@ -32004,12 +32901,12 @@ var section22Amount = {
32004
32901
  right: {
32005
32902
  kind: "add",
32006
32903
  args: [
32007
- fact32("nontaxableBenefitsForScheduleR"),
32904
+ fact35("nontaxableBenefitsForScheduleR"),
32008
32905
  {
32009
32906
  kind: "mulRate",
32010
32907
  base: {
32011
32908
  kind: "max0",
32012
- arg: { kind: "sub", left: ruleRef27("us.federal.agi"), right: agiThreshold }
32909
+ arg: { kind: "sub", left: ruleRef29("us.federal.agi"), right: agiThreshold }
32013
32910
  },
32014
32911
  rate: { num: "50", den: "100" },
32015
32912
  round: "half-up"
@@ -32028,7 +32925,7 @@ var scheduleRRules = [
32028
32925
  {
32029
32926
  id: "us.federal.schedule_r.tentative",
32030
32927
  version: 1,
32031
- jurisdiction: J23,
32928
+ jurisdiction: J26,
32032
32929
  title: "Credit for the elderly or the disabled \u2014 tentative (\xA7 22, Schedule R)",
32033
32930
  citation: CITATION2,
32034
32931
  effectiveFrom: "2025-01-01",
@@ -32042,7 +32939,7 @@ var scheduleRRules = [
32042
32939
  qualified,
32043
32940
  {
32044
32941
  kind: "or",
32045
- args: [{ kind: "not", arg: isStatus9("mfs") }, fact32("mfsLivedApartAllYear")]
32942
+ args: [{ kind: "not", arg: isStatus9("mfs") }, fact35("mfsLivedApartAllYear")]
32046
32943
  }
32047
32944
  ]
32048
32945
  },
@@ -32056,13 +32953,13 @@ var scheduleRRules = [
32056
32953
  },
32057
32954
  mode: "half-up"
32058
32955
  },
32059
- else: zero20
32956
+ else: zero23
32060
32957
  }
32061
32958
  },
32062
32959
  {
32063
32960
  id: "us.federal.schedule_r_credit",
32064
32961
  version: 1,
32065
- jurisdiction: J23,
32962
+ jurisdiction: J26,
32066
32963
  title: "Schedule R credit, limited to remaining tax (\xA7 26(a) after CDCC + education + saver's + adoption)",
32067
32964
  citation: CITATION2,
32068
32965
  effectiveFrom: "2025-01-01",
@@ -32070,7 +32967,7 @@ var scheduleRRules = [
32070
32967
  formula: {
32071
32968
  kind: "min",
32072
32969
  args: [
32073
- ruleRef27("us.federal.schedule_r.tentative"),
32970
+ ruleRef29("us.federal.schedule_r.tentative"),
32074
32971
  {
32075
32972
  kind: "max0",
32076
32973
  arg: {
@@ -32078,17 +32975,17 @@ var scheduleRRules = [
32078
32975
  left: {
32079
32976
  kind: "add",
32080
32977
  args: [
32081
- ruleRef27("us.federal.income_tax_before_credits"),
32082
- ruleRef27("us.federal.amt")
32978
+ ruleRef29("us.federal.income_tax_before_credits"),
32979
+ ruleRef29("us.federal.amt")
32083
32980
  ]
32084
32981
  },
32085
32982
  right: {
32086
32983
  kind: "add",
32087
32984
  args: [
32088
- ruleRef27("us.federal.cdcc"),
32089
- ruleRef27("us.federal.education.nonrefundable"),
32090
- ruleRef27("us.federal.savers_credit"),
32091
- ruleRef27("us.federal.adoption.nonrefundable")
32985
+ ruleRef29("us.federal.cdcc"),
32986
+ ruleRef29("us.federal.education.nonrefundable"),
32987
+ ruleRef29("us.federal.savers_credit"),
32988
+ ruleRef29("us.federal.adoption.nonrefundable")
32092
32989
  ]
32093
32990
  }
32094
32991
  }
@@ -32099,23 +32996,23 @@ var scheduleRRules = [
32099
32996
  ];
32100
32997
 
32101
32998
  // ../corpus-us-federal/dist/rules/state-helpers.js
32102
- var fact33 = (factId) => ({ kind: "fact", factId });
32103
- var money30 = (cents) => ({ kind: "money", cents });
32104
- var ruleRef28 = (ruleId) => ({ kind: "rule", ruleId });
32999
+ var fact36 = (factId) => ({ kind: "fact", factId });
33000
+ var money33 = (cents) => ({ kind: "money", cents });
33001
+ var ruleRef30 = (ruleId) => ({ kind: "rule", ruleId });
32105
33002
  var isStatus10 = (v) => ({
32106
33003
  kind: "cmp",
32107
33004
  op: "eq",
32108
- left: fact33("filingStatus"),
33005
+ left: fact36("filingStatus"),
32109
33006
  right: { kind: "enum", value: v }
32110
33007
  });
32111
33008
  var printedSchedule = (base, rows) => {
32112
33009
  const rowExpr = (r) => ({
32113
33010
  kind: "add",
32114
33011
  args: [
32115
- money30(r.fixedCents),
33012
+ money33(r.fixedCents),
32116
33013
  {
32117
33014
  kind: "mulRate",
32118
- base: { kind: "sub", left: base, right: money30(r.thresholdCents) },
33015
+ base: { kind: "sub", left: base, right: money33(r.thresholdCents) },
32119
33016
  rate: r.rate,
32120
33017
  round: "half-up"
32121
33018
  }
@@ -32125,7 +33022,7 @@ var printedSchedule = (base, rows) => {
32125
33022
  for (let i = rows.length - 2; i >= 0; i--) {
32126
33023
  expr = {
32127
33024
  kind: "if",
32128
- cond: { kind: "cmp", op: "le", left: base, right: money30(rows[i + 1].thresholdCents) },
33025
+ cond: { kind: "cmp", op: "le", left: base, right: money33(rows[i + 1].thresholdCents) },
32129
33026
  then: rowExpr(rows[i]),
32130
33027
  else: expr
32131
33028
  };
@@ -32145,7 +33042,7 @@ var ilRules = [
32145
33042
  source: "35 ILCS 5/201(b)(5.4); 2025 Form IL-1040 instructions",
32146
33043
  section: "IL-1040 line 12",
32147
33044
  url: "https://tax.illinois.gov/content/dam/soi/en/web/tax/forms/incometax/documents/currentyear/individual/il-1040-instr.pdf",
32148
- excerpt: "Residents: Multiply Line 11 by 4.95% (.0495). Cannot be less than zero (2025 IL-1040 line 12, verbatim). LINE FLOORS (2025 instructions): line 9 base income AND line 11 net income 'may not be less than zero \u2014 if the result is a negative number, enter 0'. Related 2025 parameters (IDOR 2025 IL-1040 + Schedule ICR + Schedule 1299-C instructions + Pub-132, web-verified July 2026): exemption allowance $2,850 per exemption PLUS $1,000 per age-65+ box and $1,000 per blind box (taxpayer/spouse, per box \u2014 65 AND blind = $2,000); the ENTIRE line 10 allowance is $0 if federal AGI exceeds $500,000 MFJ / $250,000 all others. Schedule ICR (nonrefundable, total cannot exceed tax due, same AGI caps): property tax credit = 5% of IL property tax on the principal residence (exclude business-use portion); K-12 education expense credit = 25% of qualified expenses OVER $250, max $750 \u2014 HOME-SCHOOL expenses are NOT qualified (35 ILCS 5/201(m): public/nonpublic school tuition, book and lab fees only; exclude school_type home entries before summing). Schedule 1299-C K-12 Instructional Materials & Supplies credit (900+ hour educators): up to $500 ($1,000 MFJ both educators). Illinois EITC = 20% of the federal EITC (refundable) \u2014 BUT for a taxpayer age 18-24 or 65+ with NO qualifying children, 35 ILCS 5/212(b-5)/(b-10) (P.A. 102-700) decouples from the federal \xA7 32(c)(1)(A)(ii) childless age floor: use the us.il.eitc target (20% of us.federal.eitc_no_age_gate) instead of 20% x the ordinary federal EITC, which is correctly $0 for that population and would understate line 29. Illinois Child Tax Credit (TY2025) = 40% of the Illinois EITC for taxpayers with a dependent child under 12. [Input: IL net income (line 11 \u2014 base income minus exemptions), computed by the preparer; reference-validated: 30,485 \xD7 4.95% = 1,509 = ty25-il-003.] ESTIMATED TAX (IL-2210, 2025): required if IL tax after withholding/credits will exceed $1,000. SCHEDULE NR: part-year and nonresidents apportion base income on Schedule NR \u2014 this target computes RESIDENT tax on already-apportioned net income only; the Schedule NR apportionment worksheet itself is not modeled (refuses/agent-composed)."
33045
+ excerpt: "Residents: Multiply Line 11 by 4.95% (.0495). Cannot be less than zero (2025 IL-1040 line 12, verbatim). LINE FLOORS (2025 instructions): line 9 base income AND line 11 net income 'may not be less than zero \u2014 if the result is a negative number, enter 0'. Related 2025 parameters (IDOR 2025 IL-1040 + Schedule ICR + Schedule 1299-C instructions + Pub-132, web-verified July 2026): exemption allowance $2,850 per exemption PLUS $1,000 per age-65+ box and $1,000 per blind box (taxpayer/spouse, per box \u2014 65 AND blind = $2,000); the ENTIRE line 10 allowance is $0 if federal AGI exceeds $500,000 MFJ / $250,000 all others. Schedule ICR (nonrefundable, total cannot exceed tax due, same AGI caps): property tax credit = 5% of IL property tax on the principal residence (exclude business-use portion); K-12 education expense credit = 25% of qualified expenses OVER $250, max $750 \u2014 HOME-SCHOOL expenses are NOT qualified (35 ILCS 5/201(m): public/nonpublic school tuition, book and lab fees only; exclude school_type home entries before summing). Schedule 1299-C K-12 Instructional Materials & Supplies credit (900+ hour educators): up to $500 ($1,000 MFJ both educators). Illinois EITC = 20% of the federal EITC (refundable) \u2014 BUT for a taxpayer age 18-24 or 65+ with NO qualifying children, 35 ILCS 5/212(b-5)/(b-10) (P.A. 102-700) decouples from the federal \xA7 32(c)(1)(A)(ii) childless age floor: use the us.il.eitc target (20% of us.federal.eitc_no_age_gate) instead of 20% x the ordinary federal EITC, which is correctly $0 for that population and would understate line 29. Illinois Child Tax Credit (TY2025) = 40% of the Illinois EITC for taxpayers with a dependent child under 12. [Input: IL net income (line 11 \u2014 base income minus exemptions), computed by the preparer; worked example: 30,485 \xD7 4.95% = 1,509.] ESTIMATED TAX (IL-2210, 2025): required if IL tax after withholding/credits will exceed $1,000. SCHEDULE NR: part-year and nonresidents apportion base income on Schedule NR \u2014 this target computes RESIDENT tax on already-apportioned net income only; the Schedule NR apportionment worksheet itself is not modeled (refuses/agent-composed)."
32149
33046
  },
32150
33047
  effectiveFrom: "2025-01-01",
32151
33048
  effectiveTo: "2026-01-01",
@@ -32176,7 +33073,7 @@ var ilRules = [
32176
33073
  },
32177
33074
  formula: {
32178
33075
  kind: "mulRate",
32179
- base: { kind: "max0", arg: fact33("stateTaxableIncome") },
33076
+ base: { kind: "max0", arg: fact36("stateTaxableIncome") },
32180
33077
  rate: { num: "495", den: "10000" },
32181
33078
  round: "half-up"
32182
33079
  }
@@ -32202,7 +33099,7 @@ var ilRules = [
32202
33099
  kind: "roundToDollar",
32203
33100
  value: {
32204
33101
  kind: "mulRate",
32205
- base: ruleRef28("us.federal.eitc_no_age_gate"),
33102
+ base: ruleRef30("us.federal.eitc_no_age_gate"),
32206
33103
  rate: { num: "20", den: "100" },
32207
33104
  round: "half-up"
32208
33105
  },
@@ -32274,35 +33171,35 @@ var ilRules = [
32274
33171
  },
32275
33172
  formula: {
32276
33173
  kind: "if",
32277
- cond: { kind: "cmp", op: "le", left: ruleRef28("us.federal.agi"), right: { kind: "param", name: "tier1Max" } },
33174
+ cond: { kind: "cmp", op: "le", left: ruleRef30("us.federal.agi"), right: { kind: "param", name: "tier1Max" } },
32278
33175
  then: { kind: "param", name: "tier1Tax" },
32279
33176
  else: {
32280
33177
  kind: "if",
32281
- cond: { kind: "cmp", op: "le", left: ruleRef28("us.federal.agi"), right: { kind: "param", name: "tier2Max" } },
33178
+ cond: { kind: "cmp", op: "le", left: ruleRef30("us.federal.agi"), right: { kind: "param", name: "tier2Max" } },
32282
33179
  then: { kind: "param", name: "tier2Tax" },
32283
33180
  else: {
32284
33181
  kind: "if",
32285
- cond: { kind: "cmp", op: "le", left: ruleRef28("us.federal.agi"), right: { kind: "param", name: "tier3Max" } },
33182
+ cond: { kind: "cmp", op: "le", left: ruleRef30("us.federal.agi"), right: { kind: "param", name: "tier3Max" } },
32286
33183
  then: { kind: "param", name: "tier3Tax" },
32287
33184
  else: {
32288
33185
  kind: "if",
32289
- cond: { kind: "cmp", op: "le", left: ruleRef28("us.federal.agi"), right: { kind: "param", name: "tier4Max" } },
33186
+ cond: { kind: "cmp", op: "le", left: ruleRef30("us.federal.agi"), right: { kind: "param", name: "tier4Max" } },
32290
33187
  then: { kind: "param", name: "tier4Tax" },
32291
33188
  else: {
32292
33189
  kind: "if",
32293
- cond: { kind: "cmp", op: "le", left: ruleRef28("us.federal.agi"), right: { kind: "param", name: "tier5Max" } },
33190
+ cond: { kind: "cmp", op: "le", left: ruleRef30("us.federal.agi"), right: { kind: "param", name: "tier5Max" } },
32294
33191
  then: { kind: "param", name: "tier5Tax" },
32295
33192
  else: {
32296
33193
  kind: "if",
32297
- cond: { kind: "cmp", op: "le", left: ruleRef28("us.federal.agi"), right: { kind: "param", name: "tier6Max" } },
33194
+ cond: { kind: "cmp", op: "le", left: ruleRef30("us.federal.agi"), right: { kind: "param", name: "tier6Max" } },
32298
33195
  then: { kind: "param", name: "tier6Tax" },
32299
33196
  else: {
32300
33197
  kind: "if",
32301
- cond: { kind: "cmp", op: "le", left: ruleRef28("us.federal.agi"), right: { kind: "param", name: "tier7Max" } },
33198
+ cond: { kind: "cmp", op: "le", left: ruleRef30("us.federal.agi"), right: { kind: "param", name: "tier7Max" } },
32302
33199
  then: { kind: "param", name: "tier7Tax" },
32303
33200
  else: {
32304
33201
  kind: "roundToDollar",
32305
- value: { kind: "mulRate", base: ruleRef28("us.federal.agi"), rate: { num: "5", den: "10000" }, round: "half-up" },
33202
+ value: { kind: "mulRate", base: ruleRef30("us.federal.agi"), rate: { num: "5", den: "10000" }, round: "half-up" },
32306
33203
  mode: "half-up"
32307
33204
  }
32308
33205
  }
@@ -32327,7 +33224,7 @@ var vaRules = [
32327
33224
  source: "Va. Code \xA7 58.1-320; 2025 Form 760 instructions (tax.virginia.gov PDF)",
32328
33225
  section: "\xA7 58.1-320; Form 760 line 16",
32329
33226
  url: "https://www.tax.virginia.gov/sites/default/files/vatax-pdf/2025-760-instructions.pdf",
32330
- excerpt: "2% on the first $3,000; 3% over $3,000 to $5,000; 5% over $5,000 to $17,000; 5.75% over $17,000 (Va. Code \xA7 58.1-320, single schedule for all statuses). THE TABLE CONVENTION (this rule's default): the published VA Tax Table's rows are back-solved from clean whole-dollar tax increments \u2014 $2 per row above $5,000 on an ODD-dollar grid \u2014 so table tax = the nearest odd dollar to the rate-schedule value (ties up); verified against the printed row 44,026\u201344,061 \u2192 $2,275 (2024/2023 booklets; the 2025 booklet points to the same online table). useFormulaMethod=true gives the raw schedule with half-up rounding instead (the 2025 instructions' own $90,000 example: 4,917.50 \u2192 4,918). Related 2025 parameters (2025 Form 760 instructions, web-verified July 2026): standard deduction $8,750 single/MFS / $17,500 MFJ (TY2025\u20132026, see us.va.standard_deduction for the enacted TY2027+ windows); exemption $930/person + $800 each age-65-or-blind (taxpayer/spouse only); AGE DEDUCTION (Va. Code \xA7 58.1-322.03(2)): $12,000 per person 65+, UNREDUCED if born on/before 1/1/1939, else reduced $1-for-$1 by AFAGI over $50,000 single / $75,000 married (joint AFAGI); Credit for Low Income Individuals: $300 \xD7 (personal + dependent exemptions, EXCLUDING 65/blind extras), nonrefundable, gated on family VAGI \u2264 poverty guideline (2025: $15,650 for 1 + $5,500 each additional person) \u2014 mutually exclusive with the Virginia EITC; Virginia EITC 2025: 20% of federal EITC, REFUNDABLE at 20% for TY2025\u20132029 (Va. Code \xA7 58.1-339.8, as amended by the 2026 Appropriation Act \u2014 the refundable 20% rate was extended through TY2029, superseding the earlier TY2026 sunset; do not use the stale 15% or a TY2026 cutoff) or nonrefundable 20%. VA has no HOH status: federal HOH files as single-column. NONRESIDENT/PART-YEAR RETURNS: Form 763 (nonresident) and 760PY (part-year) compute Virginia-source-income allocation on their own worksheets \u2014 NOT modeled here (refuses/agent-composed); this target is the RESIDENT Form 760 computation on already-determined Virginia taxable income only. [Input: Virginia taxable income (Form 760 line 15); reference-validated: tax(44,060) = 2,275 = ty25-va-002 (table); tax(34,030) = 1,699 = ty25-va-001 (both methods agree).]"
33227
+ excerpt: "2% on the first $3,000; 3% over $3,000 to $5,000; 5% over $5,000 to $17,000; 5.75% over $17,000 (Va. Code \xA7 58.1-320, single schedule for all statuses). THE TABLE CONVENTION (this rule's default): the published VA Tax Table's rows are back-solved from clean whole-dollar tax increments \u2014 $2 per row above $5,000 on an ODD-dollar grid \u2014 so table tax = the nearest odd dollar to the rate-schedule value (ties up); verified against the printed row 44,026\u201344,061 \u2192 $2,275 (2024/2023 booklets; the 2025 booklet points to the same online table). useFormulaMethod=true gives the raw schedule with half-up rounding instead (the 2025 instructions' own $90,000 example: 4,917.50 \u2192 4,918). Related 2025 parameters (2025 Form 760 instructions, web-verified July 2026): standard deduction $8,750 single/MFS / $17,500 MFJ (TY2025\u20132026, see us.va.standard_deduction for the enacted TY2027+ windows); exemption $930/person + $800 each age-65-or-blind (taxpayer/spouse only); AGE DEDUCTION (Va. Code \xA7 58.1-322.03(2)): $12,000 per person 65+, UNREDUCED if born on/before 1/1/1939, else reduced $1-for-$1 by AFAGI over $50,000 single / $75,000 married (joint AFAGI); Credit for Low Income Individuals: $300 \xD7 (personal + dependent exemptions, EXCLUDING 65/blind extras), nonrefundable, gated on family VAGI \u2264 poverty guideline (2025: $15,650 for 1 + $5,500 each additional person) \u2014 mutually exclusive with the Virginia EITC; Virginia EITC 2025: 20% of federal EITC, REFUNDABLE at 20% for TY2025\u20132029 (Va. Code \xA7 58.1-339.8, as amended by the 2026 Appropriation Act \u2014 the refundable 20% rate was extended through TY2029, superseding the earlier TY2026 sunset; do not use the stale 15% or a TY2026 cutoff) or nonrefundable 20%. VA has no HOH status: federal HOH files as single-column. NONRESIDENT/PART-YEAR RETURNS: Form 763 (nonresident) and 760PY (part-year) compute Virginia-source-income allocation on their own worksheets \u2014 NOT modeled here (refuses/agent-composed); this target is the RESIDENT Form 760 computation on already-determined Virginia taxable income only. [Input: Virginia taxable income (Form 760 line 15); worked examples: tax(44,060) = 2,275 (table); tax(34,030) = 1,699 (both methods agree).]"
32331
33228
  },
32332
33229
  effectiveFrom: "2025-01-01",
32333
33230
  effectiveTo: "2026-01-01",
@@ -32359,12 +33256,12 @@ var vaRules = [
32359
33256
  },
32360
33257
  formula: {
32361
33258
  kind: "if",
32362
- cond: fact33("useFormulaMethod"),
33259
+ cond: fact36("useFormulaMethod"),
32363
33260
  then: {
32364
33261
  kind: "roundToDollar",
32365
33262
  value: {
32366
33263
  kind: "brackets",
32367
- base: { kind: "max0", arg: fact33("stateTaxableIncome") },
33264
+ base: { kind: "max0", arg: fact36("stateTaxableIncome") },
32368
33265
  table: [
32369
33266
  { threshold: "0", rate: { num: "2", den: "100" } },
32370
33267
  { threshold: "300000", rate: { num: "3", den: "100" } },
@@ -32380,14 +33277,14 @@ var vaRules = [
32380
33277
  cond: {
32381
33278
  kind: "cmp",
32382
33279
  op: "le",
32383
- left: { kind: "max0", arg: fact33("stateTaxableIncome") },
33280
+ left: { kind: "max0", arg: fact36("stateTaxableIncome") },
32384
33281
  right: { kind: "money", cents: "500000" }
32385
33282
  },
32386
33283
  then: {
32387
33284
  kind: "roundToDollar",
32388
33285
  value: {
32389
33286
  kind: "brackets",
32390
- base: { kind: "max0", arg: fact33("stateTaxableIncome") },
33287
+ base: { kind: "max0", arg: fact36("stateTaxableIncome") },
32391
33288
  table: [
32392
33289
  { threshold: "0", rate: { num: "2", den: "100" } },
32393
33290
  { threshold: "300000", rate: { num: "3", den: "100" } }
@@ -32397,14 +33294,14 @@ var vaRules = [
32397
33294
  },
32398
33295
  // the printed VA Tax Table ends at $100,000 taxable income — at or
32399
33296
  // above it the form's own instruction is the RATE SCHEDULE, exact and
32400
- // rounded to the nearest dollar (reference-validated: 440,857 →
33297
+ // rounded to the nearest dollar (worked example: 440,857 →
32401
33298
  // 25,092, the half-up schedule value, NOT the $2-grid's 25,091)
32402
33299
  else: {
32403
33300
  kind: "if",
32404
33301
  cond: {
32405
33302
  kind: "cmp",
32406
33303
  op: "ge",
32407
- left: { kind: "max0", arg: fact33("stateTaxableIncome") },
33304
+ left: { kind: "max0", arg: fact36("stateTaxableIncome") },
32408
33305
  right: { kind: "money", cents: "10000000" }
32409
33306
  // $100,000
32410
33307
  },
@@ -32412,7 +33309,7 @@ var vaRules = [
32412
33309
  kind: "roundToDollar",
32413
33310
  value: {
32414
33311
  kind: "brackets",
32415
- base: { kind: "max0", arg: fact33("stateTaxableIncome") },
33312
+ base: { kind: "max0", arg: fact36("stateTaxableIncome") },
32416
33313
  table: [
32417
33314
  { threshold: "0", rate: { num: "2", den: "100" } },
32418
33315
  { threshold: "300000", rate: { num: "3", den: "100" } },
@@ -32434,7 +33331,7 @@ var vaRules = [
32434
33331
  kind: "stepUnits",
32435
33332
  value: {
32436
33333
  kind: "brackets",
32437
- base: { kind: "max0", arg: fact33("stateTaxableIncome") },
33334
+ base: { kind: "max0", arg: fact36("stateTaxableIncome") },
32438
33335
  table: [
32439
33336
  { threshold: "0", rate: { num: "2", den: "100" } },
32440
33337
  { threshold: "300000", rate: { num: "3", den: "100" } },
@@ -32493,7 +33390,7 @@ var vaRules = [
32493
33390
  source: "Va. Code \xA7 58.1-320; 2025 Form 760 instructions, Spouse Tax Adjustment worksheet (p.12-13)",
32494
33391
  section: "Form 760 line 17",
32495
33392
  url: "https://www.tax.virginia.gov/sites/default/files/vatax-pdf/2025-760-instructions.pdf",
32496
- excerpt: "2025 Spouse Tax Adjustment (STA): available ONLY on a joint (MFJ) return where BOTH spouses have income and their COMBINED Virginia taxable income exceeds $3,000; maximum credit $259 (footer statement + Line 5 shortcut, both confirmed in the TY2025 booklet). NARRATIVE (p.13, verbatim): 'Couples filing jointly under Filing Status 2 may reduce their tax by up to $259 with the STA if both have taxable income to report and their combined taxable income on Line 15 is more than $3,000 ... Using the STA, couples filing joint returns will not pay higher taxes than if they had filed separate returns.' HOW IT WORKS (verbatim): 'Virginia tax rates increase with income: 2% up to $3,000; 3% from $3,001 to $5,000; 5% from $5,001 to $17,000; and 5.75% for income over $17,000. The STA lets both incomes reported on jointly filed returns benefit from the lower tax rates.'\n\nTHE ALLOCATION IS PRESCRIBED, NOT DISCRETIONARY \u2014 built on income-SOURCE attribution, never a proportional wage-ratio or a benefit-maximizing choice. VERBATIM WORKSHEET, PART 1: SEPARATE YOUR INCOME AND EXEMPTIONS (per spouse): '1. Enter the portion of the Virginia Adjusted Gross Income (VAGI) on Line 9 of Form 760 that is related to each spouse. Use the worksheet at the bottom of the page to compute the separate VAGI for each spouse.' '2. Enter separate personal exemption amounts. Enter a 1 in the boxes that apply and multiply the total by $800. Add $930 to the total to compute the personal exemptions for you and spouse. You: 65 or over + Blind = Total x $800 + $930 = ___. Spouse: 65 or over + Blind = Total x $800 + $930 = ___.' (i.e., PER SPOUSE: $930 + $800 x that spouse's own 65-or-over/blind box count \u2014 a fixed formula, never proportional to income.) '3. Subtract Line 2 from Line 1. If either amount is 0 or less, stop here; you do not qualify for this credit.'\n\nVERBATIM WORKSHEET, PART 2: CALCULATE YOUR TAX ADJUSTMENT: '4. Enter the taxable income from Line 15 on Form 760.' '5. Enter the smaller amount from Line 3 above. If this amount is larger than $17,000 and Line 4 is larger than $34,000, skip to Line 12 and enter $259 as the credit.' '6. Subtract Line 5 from Line 4 (if $0 or less, enter $0).' '7. Divide the amount on Line 4 by 2.' '8. Enter the tax on the smaller amount from Line 5 or Line 7. Refer to the tax table or rate schedule.' '9. Enter the tax on the larger amount from Line 6 or Line 7. Refer to the tax table or rate schedule.' (Lines 8-9 use us.va.income_tax's own rate schedule/table on the stated amount \u2014 NOT a fresh computation.) '10. Add Lines 8 and 9.' '11. Enter the tax from Line 16 on Form 760.' '12. TAX ADJUSTMENT: Subtract Line 10 from Line 11. Enter this amount on Line 17 of Form 760.' 'The Spouse Tax Adjustment cannot exceed $259.'\n\nVERBATIM 'WORKSHEET FOR DETERMINING SEPARATE VIRGINIA ADJUSTED GROSS INCOME' (bottom of p.12) \u2014 feeds Part 1 Line 1 above: STEP 1 - Determine Separate Federal Adjusted Gross Income (You / Spouse columns, LITERAL income-source attribution \u2014 each spouse's OWN items, never a ratio): '1. Wages, salaries, etc. 2. Taxable interest and dividend income. 3. Taxable refunds, adjustments, or offsets of state and local income tax. 4. Business income. 5. Capital gains/losses and other gains/losses. 6. Taxable pensions, annuities, and IRA distributions. 7. Rents, royalties, partnerships, estates, trusts, etc. 8. Other income (farm income, taxable social security, etc.). 9. Gross income - add Lines 1 through 8. 10. Adjustments to gross income. 11. FAGI - subtract Line 10 from Line 9. (The total of both columns should equal your joint FAGI reported on your 1040)' STEP 2 - Determine Separate Virginia Adjusted Gross Income: '12. Total additions to FAGI (Form 760, Line 2). 13. Subtotal - add Lines 11 and 12. 14. Age Deduction (Form 760, Line 4). 15. Social Security Act and Tier 1 Railroad Retirement Act Benefits (Form 760, Line 5). 16. State income tax refund or overpayment credit reported as income on your federal return (Form 760, Line 6). 17. Other Subtractions (Form 760, Line 7). 18. Total Subtractions from FAGI - add Lines 14, 15, 16, and 17. 19. Subtract Line 18 from Line 13. These are your separate VAGI amounts to be used on Line 1 of the Spouse Tax Adjustment Worksheet. (The total of both columns should equal your combined VAGI reported on Line 9 of your 760)'\n\nAGE DEDUCTION 50/50 SPLIT RULE (feeds Step 2 Line 14 above \u2014 the ONE genuinely joint item on this worksheet, verbatim): 'If both spouses are claiming an income-based age deduction, regardless of whether filing jointly or separately, the married taxpayers must compute a joint age deduction first, then allocate half of the joint deduction to each spouse.' (Compute the joint age deduction once via us.va.income_tax's ageDeductionBase/threshold parameters on COMBINED AFAGI, then split it exactly 50/50 \u2014 not proportional to either spouse's income or age-deduction eligibility share.)\n\nOWNERSHIP ATTRIBUTION FOR STEP 1 (reference-validated ty25-va-004/va-006/va-007): a document belongs to the spouse the STRUCTURED INTAKE's ownership tag names (who_applies_to / whose-account fields: taxpayer | spouse | joint) \u2014 the intake tag CONTROLS over the name printed on the PDF (va-007's third 1099-B prints only the primary's name but is intake-tagged joint). taxpayer/spouse-tagged items go WHOLE to that spouse; joint-tagged items (interest, dividends INCLUDING their capital-gain distributions, jointly-tagged 1099-Bs) split 50/50 with the ODD DOLLAR TO THE PRIMARY. Adjustments follow their owner: each spouse's own \xBD-SE-tax (from their own Schedule SE), own educator cap, and the student-loan interest to the named 1098-E borrower. (va-007 worked example: primary = SchC 16,391 + own crypto LT 999 + joint splits 2,778 + 426 + 44 + 425 \u2212 \xBDSE 1,158 \u2212 SL 2,500 \u2212 educator 300 = 17,105; spouse = 55,123; STA = 245 exactly.)\n\nSUMMARY OF WHAT IS AND ISN'T DISCRETIONARY: (1) Separate-VAGI Step 1 items are attributed by literal ownership (each spouse's own wages/interest/business/capital-gain/pension/rent/other-income line, cross-checked to equal the joint FAGI). (2) Personal exemptions use the fixed $930+$800-per-box formula, per spouse \u2014 NOT proportional to income. (3) The age deduction (a genuinely joint item) is computed once, jointly, then split EXACTLY 50/50 \u2014 not proportional to either spouse's income or share of the joint deduction. (4) The standard/itemized deduction is NEVER split on this worksheet \u2014 VAGI is computed pre-deduction, and Part 2's Lines 4-12 arithmetic (min/max sequence below) handles the deduction's effect implicitly through the FIXED sequence: Line 5 = smaller of the two Line-3 (exemption-adjusted VAGI) amounts; Line 6 = Line 4 minus Line 5 (floored at 0); Line 7 = Line 4 divided by 2; tax is computed on min(Line 5, Line 7) and max(Line 6, Line 7) via us.va.income_tax and summed (Line 10), then subtracted from the actual joint tax (Line 11 = Form 760 Line 16) for the STA (Line 12), capped at $259. There is NO discretionary maximization step anywhere in this sequence \u2014 given the source-attributed Step-1/Step-2 inputs, the worksheet produces exactly one number. DISCLOSED SCOPE: this rule still does not COMPUTE the worksheet end-to-end (the per-spouse Separate-VAGI Step-1 income-source figures are themselves agent-transcribed facts, not derived by this corpus) \u2014 it now states the COMPLETE prescription (every line, every allocation rule) rather than only the gating conditions and cap, so an agent following it mechanically reaches the one correct number instead of inventing a wage-ratio or benefit-maximizing allocation."
33393
+ excerpt: "2025 Spouse Tax Adjustment (STA): available ONLY on a joint (MFJ) return where BOTH spouses have income and their COMBINED Virginia taxable income exceeds $3,000; maximum credit $259 (footer statement + Line 5 shortcut, both confirmed in the TY2025 booklet). NARRATIVE (p.13, verbatim): 'Couples filing jointly under Filing Status 2 may reduce their tax by up to $259 with the STA if both have taxable income to report and their combined taxable income on Line 15 is more than $3,000 ... Using the STA, couples filing joint returns will not pay higher taxes than if they had filed separate returns.' HOW IT WORKS (verbatim): 'Virginia tax rates increase with income: 2% up to $3,000; 3% from $3,001 to $5,000; 5% from $5,001 to $17,000; and 5.75% for income over $17,000. The STA lets both incomes reported on jointly filed returns benefit from the lower tax rates.'\n\nTHE ALLOCATION IS PRESCRIBED, NOT DISCRETIONARY \u2014 built on income-SOURCE attribution, never a proportional wage-ratio or a benefit-maximizing choice. VERBATIM WORKSHEET, PART 1: SEPARATE YOUR INCOME AND EXEMPTIONS (per spouse): '1. Enter the portion of the Virginia Adjusted Gross Income (VAGI) on Line 9 of Form 760 that is related to each spouse. Use the worksheet at the bottom of the page to compute the separate VAGI for each spouse.' '2. Enter separate personal exemption amounts. Enter a 1 in the boxes that apply and multiply the total by $800. Add $930 to the total to compute the personal exemptions for you and spouse. You: 65 or over + Blind = Total x $800 + $930 = ___. Spouse: 65 or over + Blind = Total x $800 + $930 = ___.' (i.e., PER SPOUSE: $930 + $800 x that spouse's own 65-or-over/blind box count \u2014 a fixed formula, never proportional to income.) '3. Subtract Line 2 from Line 1. If either amount is 0 or less, stop here; you do not qualify for this credit.'\n\nVERBATIM WORKSHEET, PART 2: CALCULATE YOUR TAX ADJUSTMENT: '4. Enter the taxable income from Line 15 on Form 760.' '5. Enter the smaller amount from Line 3 above. If this amount is larger than $17,000 and Line 4 is larger than $34,000, skip to Line 12 and enter $259 as the credit.' '6. Subtract Line 5 from Line 4 (if $0 or less, enter $0).' '7. Divide the amount on Line 4 by 2.' '8. Enter the tax on the smaller amount from Line 5 or Line 7. Refer to the tax table or rate schedule.' '9. Enter the tax on the larger amount from Line 6 or Line 7. Refer to the tax table or rate schedule.' (Lines 8-9 use us.va.income_tax's own rate schedule/table on the stated amount \u2014 NOT a fresh computation.) '10. Add Lines 8 and 9.' '11. Enter the tax from Line 16 on Form 760.' '12. TAX ADJUSTMENT: Subtract Line 10 from Line 11. Enter this amount on Line 17 of Form 760.' 'The Spouse Tax Adjustment cannot exceed $259.'\n\nVERBATIM 'WORKSHEET FOR DETERMINING SEPARATE VIRGINIA ADJUSTED GROSS INCOME' (bottom of p.12) \u2014 feeds Part 1 Line 1 above: STEP 1 - Determine Separate Federal Adjusted Gross Income (You / Spouse columns, LITERAL income-source attribution \u2014 each spouse's OWN items, never a ratio): '1. Wages, salaries, etc. 2. Taxable interest and dividend income. 3. Taxable refunds, adjustments, or offsets of state and local income tax. 4. Business income. 5. Capital gains/losses and other gains/losses. 6. Taxable pensions, annuities, and IRA distributions. 7. Rents, royalties, partnerships, estates, trusts, etc. 8. Other income (farm income, taxable social security, etc.). 9. Gross income - add Lines 1 through 8. 10. Adjustments to gross income. 11. FAGI - subtract Line 10 from Line 9. (The total of both columns should equal your joint FAGI reported on your 1040)' STEP 2 - Determine Separate Virginia Adjusted Gross Income: '12. Total additions to FAGI (Form 760, Line 2). 13. Subtotal - add Lines 11 and 12. 14. Age Deduction (Form 760, Line 4). 15. Social Security Act and Tier 1 Railroad Retirement Act Benefits (Form 760, Line 5). 16. State income tax refund or overpayment credit reported as income on your federal return (Form 760, Line 6). 17. Other Subtractions (Form 760, Line 7). 18. Total Subtractions from FAGI - add Lines 14, 15, 16, and 17. 19. Subtract Line 18 from Line 13. These are your separate VAGI amounts to be used on Line 1 of the Spouse Tax Adjustment Worksheet. (The total of both columns should equal your combined VAGI reported on Line 9 of your 760)'\n\nAGE DEDUCTION 50/50 SPLIT RULE (feeds Step 2 Line 14 above \u2014 the ONE genuinely joint item on this worksheet, verbatim): 'If both spouses are claiming an income-based age deduction, regardless of whether filing jointly or separately, the married taxpayers must compute a joint age deduction first, then allocate half of the joint deduction to each spouse.' (Compute the joint age deduction once via us.va.income_tax's ageDeductionBase/threshold parameters on COMBINED AFAGI, then split it exactly 50/50 \u2014 not proportional to either spouse's income or age-deduction eligibility share.)\n\nOWNERSHIP ATTRIBUTION FOR STEP 1: a document belongs to the spouse the STRUCTURED INTAKE's ownership tag names (who_applies_to / whose-account fields: taxpayer | spouse | joint) \u2014 the intake tag CONTROLS over the name printed on the PDF (a 1099-B may print only the primary's name yet be intake-tagged joint). taxpayer/spouse-tagged items go WHOLE to that spouse; joint-tagged items (interest, dividends INCLUDING their capital-gain distributions, jointly-tagged 1099-Bs) split 50/50 with the ODD DOLLAR TO THE PRIMARY. Adjustments follow their owner: each spouse's own \xBD-SE-tax (from their own Schedule SE), own educator cap, and the student-loan interest to the named 1098-E borrower. (Worked example: primary = SchC 16,391 + own crypto LT 999 + joint splits 2,778 + 426 + 44 + 425 \u2212 \xBDSE 1,158 \u2212 SL 2,500 \u2212 educator 300 = 17,105; spouse = 55,123; STA = 245.)\n\nSUMMARY OF WHAT IS AND ISN'T DISCRETIONARY: (1) Separate-VAGI Step 1 items are attributed by literal ownership (each spouse's own wages/interest/business/capital-gain/pension/rent/other-income line, cross-checked to equal the joint FAGI). (2) Personal exemptions use the fixed $930+$800-per-box formula, per spouse \u2014 NOT proportional to income. (3) The age deduction (a genuinely joint item) is computed once, jointly, then split EXACTLY 50/50 \u2014 not proportional to either spouse's income or share of the joint deduction. (4) The standard/itemized deduction is NEVER split on this worksheet \u2014 VAGI is computed pre-deduction, and Part 2's Lines 4-12 arithmetic (min/max sequence below) handles the deduction's effect implicitly through the FIXED sequence: Line 5 = smaller of the two Line-3 (exemption-adjusted VAGI) amounts; Line 6 = Line 4 minus Line 5 (floored at 0); Line 7 = Line 4 divided by 2; tax is computed on min(Line 5, Line 7) and max(Line 6, Line 7) via us.va.income_tax and summed (Line 10), then subtracted from the actual joint tax (Line 11 = Form 760 Line 16) for the STA (Line 12), capped at $259. There is NO discretionary maximization step anywhere in this sequence \u2014 given the source-attributed Step-1/Step-2 inputs, the worksheet produces exactly one number. DISCLOSED SCOPE: this rule still does not COMPUTE the worksheet end-to-end (the per-spouse Separate-VAGI Step-1 income-source figures are themselves agent-transcribed facts, not derived by this corpus) \u2014 it now states the COMPLETE prescription (every line, every allocation rule) rather than only the gating conditions and cap, so an agent following it mechanically reaches the one correct number instead of inventing a wage-ratio or benefit-maximizing allocation."
32497
33394
  },
32498
33395
  effectiveFrom: "2025-01-01",
32499
33396
  effectiveTo: "2026-01-01",
@@ -32537,8 +33434,8 @@ var vaRules = [
32537
33434
  cond: {
32538
33435
  kind: "or",
32539
33436
  args: [
32540
- { kind: "cmp", op: "eq", left: fact33("filingStatus"), right: { kind: "enum", value: "mfj" } },
32541
- { kind: "cmp", op: "eq", left: fact33("filingStatus"), right: { kind: "enum", value: "qss" } }
33437
+ { kind: "cmp", op: "eq", left: fact36("filingStatus"), right: { kind: "enum", value: "mfj" } },
33438
+ { kind: "cmp", op: "eq", left: fact36("filingStatus"), right: { kind: "enum", value: "qss" } }
32542
33439
  ]
32543
33440
  },
32544
33441
  then: { kind: "money", cents: "1750000" },
@@ -32566,8 +33463,8 @@ var vaRules = [
32566
33463
  cond: {
32567
33464
  kind: "or",
32568
33465
  args: [
32569
- { kind: "cmp", op: "eq", left: fact33("filingStatus"), right: { kind: "enum", value: "mfj" } },
32570
- { kind: "cmp", op: "eq", left: fact33("filingStatus"), right: { kind: "enum", value: "qss" } }
33466
+ { kind: "cmp", op: "eq", left: fact36("filingStatus"), right: { kind: "enum", value: "mfj" } },
33467
+ { kind: "cmp", op: "eq", left: fact36("filingStatus"), right: { kind: "enum", value: "qss" } }
32571
33468
  ]
32572
33469
  },
32573
33470
  then: { kind: "money", cents: "1840000" },
@@ -32595,8 +33492,8 @@ var vaRules = [
32595
33492
  cond: {
32596
33493
  kind: "or",
32597
33494
  args: [
32598
- { kind: "cmp", op: "eq", left: fact33("filingStatus"), right: { kind: "enum", value: "mfj" } },
32599
- { kind: "cmp", op: "eq", left: fact33("filingStatus"), right: { kind: "enum", value: "qss" } }
33495
+ { kind: "cmp", op: "eq", left: fact36("filingStatus"), right: { kind: "enum", value: "mfj" } },
33496
+ { kind: "cmp", op: "eq", left: fact36("filingStatus"), right: { kind: "enum", value: "qss" } }
32600
33497
  ]
32601
33498
  },
32602
33499
  then: { kind: "money", cents: "1860000" },
@@ -32619,7 +33516,7 @@ var caRules = [
32619
33516
  source: "2025 California Tax Rate Schedules (FTB); 2025 California Tax Table (FTB 540 booklet); Rev. & Tax. Code \xA7 17041",
32620
33517
  section: "FTB 2025 Schedule X / Schedule Y / Schedule Z; 2025 California Tax Table",
32621
33518
  url: "https://www.ftb.ca.gov/forms/2025/2025-540-tax-rate-schedules.pdf",
32622
- excerpt: `2025 Schedule Z (head of household), FTB: 1% to $22,173; 2% to $52,530; 4% to $67,716; 6% to $83,805; 8% to $98,990; 9.3% to $505,208 \u2014 income above $505,208 REFUSES here (higher 10.3/11.3/12.3% tiers and the 1% MHSA surcharge not yet encoded from source). Schedule X (single/MFS), 2025 FTB, web-verified July 2026: 1% to $11,079; 2% to $26,264; 4% to $41,452; 6% to $57,542; 8% to $72,724; 9.3% above (same $505,208 shared refusal boundary as this rule's HOH branch, applied before differentiating single from HOH) \u2014 corrected in v5 from stale/mistyped thresholds that broke the Schedule-Y-is-2x-Schedule-X identity below. SCHEDULE Y (married filing jointly / qualifying surviving spouse, 2025, FTB): thresholds are EXACTLY 2\xD7 Schedule X's \u2014 $0/$22,158/$52,528/$82,904/$115,084/$145,448/$742,958 at 1/2/4/6/8/9.3/10.3/11.3/12.3% \u2014 encoded PER-BRACKET (marginal accumulation over the doubled thresholds) rather than by doubling Schedule X's own printed (already-rounded) fixed-dollar constants, because independently rounding at each schedule drifts by pennies in the upper brackets; income above $742,958 REFUSES here on the same basis as X/Z (10.3%+ tiers not yet encoded). CALIFORNIA TAX TABLE CONVENTION (2025 FTB 540 booklet, 'California Tax Table', pp.68-75; DEFAULT method for CA taxable income under $100,000, per Form 540/540NR line 19 instructions: 'Read down the column labeled "If Your Taxable Income Is..." to find the range that includes your taxable income... Read across the columns labeled "The Tax For Filing Status"...'): rows are $1-$50 as a SPECIAL FIRST ROW (tax $0 for every filing status), then $100-WIDE brackets thereafter (51-150, 151-250, ..., 99,951-100,000 \u2014 the last row is only $50 wide because the table truncates exactly at $100,000). Table tax = apply the Schedule X/Y/Z marginal formula above at the bracket's MIDPOINT, rounded to the nearest dollar (the SAME midpoint-of-bracket convention already used for the VA and NY tax tables \u2014 decisively confirmed for CA too, not merely assumed). Verified 9-for-9 exact against three incomes \xD7 three filing-status columns (FTB 2025 tax table + rate schedules, web-verified July 2026): row 40,051-40,150 (mid $40,100.50) \u2192 Single $968 / MFJ $580 / HOH $580; row 70,051-70,150 (mid $70,100.50) \u2192 Single $2,992 / MFJ $1,532 / HOH $1,579; row 95,051-95,150 (mid $95,100.50) \u2192 Single $5,283 / MFJ $2,776 / HOH $3,305. The truncated last row (99,951-100,000) uses its OWN true midpoint $99,975.50 (not the phantom $100,000.50 the general $100-bracket formula would otherwise produce) \u2014 verified: Single at that row = $5,736. CA taxable income of $100,000 OR ABOVE uses the RATE SCHEDULE directly (unrounded to the cent, per the schedule's own marginal math) \u2014 the table stops at exactly $100,000. useFormulaMethod=true bypasses the table entirely and uses the raw rate schedule at every income level (matches the pre-v5 default behavior). Related 2025 parameters (FTB, web-verified July 2026): standard deduction $5,706 single/MFS, $11,412 MFJ/HOH/QSS; personal exemption CREDIT $153; dependent exemption credit $475 (cross-validated against the ty25-ca-002 reference: credits 153+153+475=781, deduction 11,412). EXEMPTION CREDIT PHASEOUT (R&TC \xA7 17054(a), (b); 2025 FTB Schedule CA/540 instructions): credits (personal + dependent, but NOT senior) phase out $6 for each $2,500 (or fraction thereof \u2014 the quotient is ROUNDED UP) of federal AGI over $252,203 single/MFS ($1,250 divisor increments instead of $2,500 for MFS \u2014 i.e. the SAME $6 reduction per HALF the increment), $504,411 MFJ/QSS, $378,310 HOH; fully phased out credits are not reduced below $0. [Input: California taxable income (Form 540 line 19). SCHEDULE CA (2025 instructions, line 7): California EXCLUDES unemployment compensation (and CA PFL benefits) from taxable income \u2014 subtract it from federal AGI when composing CA AGI. SCHEDULE CA (2025 instructions, line 6, column B): California ALSO EXCLUDES social security benefits \u2014 the federally-taxable amount (the \xA7 86 includible amount, us.federal.taxable_social_security) is likewise subtracted from federal AGI when composing CA AGI. CalEITC \u2192 us.ca.caleitc; Young Child Tax Credit \u2192 us.ca.yctc; renter's credit \u2192 us.ca.renters_credit; CDCC \u2192 us.ca.cdcc; misc. parameters (use tax, SDI) \u2192 us.ca.parameters.]`
33519
+ excerpt: `2025 Schedule Z (head of household), FTB: 1% to $22,173; 2% to $52,530; 4% to $67,716; 6% to $83,805; 8% to $98,990; 9.3% to $505,208 \u2014 income above $505,208 REFUSES here (higher 10.3/11.3/12.3% tiers and the 1% MHSA surcharge not yet encoded from source). Schedule X (single/MFS), 2025 FTB, web-verified July 2026: 1% to $11,079; 2% to $26,264; 4% to $41,452; 6% to $57,542; 8% to $72,724; 9.3% above (same $505,208 shared refusal boundary as this rule's HOH branch, applied before differentiating single from HOH) \u2014 corrected in v5 from stale/mistyped thresholds that broke the Schedule-Y-is-2x-Schedule-X identity below. SCHEDULE Y (married filing jointly / qualifying surviving spouse, 2025, FTB): thresholds are EXACTLY 2\xD7 Schedule X's \u2014 $0/$22,158/$52,528/$82,904/$115,084/$145,448/$742,958 at 1/2/4/6/8/9.3/10.3/11.3/12.3% \u2014 encoded PER-BRACKET (marginal accumulation over the doubled thresholds) rather than by doubling Schedule X's own printed (already-rounded) fixed-dollar constants, because independently rounding at each schedule drifts by pennies in the upper brackets; income above $742,958 REFUSES here on the same basis as X/Z (10.3%+ tiers not yet encoded). CALIFORNIA TAX TABLE CONVENTION (2025 FTB 540 booklet, 'California Tax Table', pp.68-75; DEFAULT method for CA taxable income under $100,000, per Form 540/540NR line 19 instructions: 'Read down the column labeled "If Your Taxable Income Is..." to find the range that includes your taxable income... Read across the columns labeled "The Tax For Filing Status"...'): rows are $1-$50 as a SPECIAL FIRST ROW (tax $0 for every filing status), then $100-WIDE brackets thereafter (51-150, 151-250, ..., 99,951-100,000 \u2014 the last row is only $50 wide because the table truncates exactly at $100,000). Table tax = apply the Schedule X/Y/Z marginal formula above at the bracket's MIDPOINT, rounded to the nearest dollar (the SAME midpoint-of-bracket convention already used for the VA and NY tax tables \u2014 decisively confirmed for CA too, not merely assumed). Verified 9-for-9 exact against three incomes \xD7 three filing-status columns (FTB 2025 tax table + rate schedules, web-verified July 2026): row 40,051-40,150 (mid $40,100.50) \u2192 Single $968 / MFJ $580 / HOH $580; row 70,051-70,150 (mid $70,100.50) \u2192 Single $2,992 / MFJ $1,532 / HOH $1,579; row 95,051-95,150 (mid $95,100.50) \u2192 Single $5,283 / MFJ $2,776 / HOH $3,305. The truncated last row (99,951-100,000) uses its OWN true midpoint $99,975.50 (not the phantom $100,000.50 the general $100-bracket formula would otherwise produce) \u2014 verified: Single at that row = $5,736. CA taxable income of $100,000 OR ABOVE uses the RATE SCHEDULE directly (unrounded to the cent, per the schedule's own marginal math) \u2014 the table stops at exactly $100,000. useFormulaMethod=true bypasses the table entirely and uses the raw rate schedule at every income level (matches the pre-v5 default behavior). Related 2025 parameters (FTB, web-verified July 2026): standard deduction $5,706 single/MFS, $11,412 MFJ/HOH/QSS; personal exemption CREDIT $153; dependent exemption credit $475. EXEMPTION CREDIT PHASEOUT (R&TC \xA7 17054(a), (b); 2025 FTB Schedule CA/540 instructions): credits (personal + dependent, but NOT senior) phase out $6 for each $2,500 (or fraction thereof \u2014 the quotient is ROUNDED UP) of federal AGI over $252,203 single/MFS ($1,250 divisor increments instead of $2,500 for MFS \u2014 i.e. the SAME $6 reduction per HALF the increment), $504,411 MFJ/QSS, $378,310 HOH; fully phased out credits are not reduced below $0. [Input: California taxable income (Form 540 line 19). SCHEDULE CA (2025 instructions, line 7): California EXCLUDES unemployment compensation (and CA PFL benefits) from taxable income \u2014 subtract it from federal AGI when composing CA AGI. SCHEDULE CA (2025 instructions, line 6, column B): California ALSO EXCLUDES social security benefits \u2014 the federally-taxable amount (the \xA7 86 includible amount, us.federal.taxable_social_security) is likewise subtracted from federal AGI when composing CA AGI. CalEITC \u2192 us.ca.caleitc; Young Child Tax Credit \u2192 us.ca.yctc; renter's credit \u2192 us.ca.renters_credit; CDCC \u2192 us.ca.cdcc; misc. parameters (use tax, SDI) \u2192 us.ca.parameters.]`
32623
33520
  },
32624
33521
  effectiveFrom: "2025-01-01",
32625
33522
  effectiveTo: "2026-01-01",
@@ -32664,7 +33561,7 @@ var caRules = [
32664
33561
  kind: "cmp",
32665
33562
  op: "gt",
32666
33563
  left: base2,
32667
- right: money30("74295800")
33564
+ right: money33("74295800")
32668
33565
  // $742,958 — Schedule Y's 9.3% band ends here
32669
33566
  },
32670
33567
  then: {
@@ -32691,7 +33588,7 @@ var caRules = [
32691
33588
  kind: "cmp",
32692
33589
  op: "gt",
32693
33590
  left: base2,
32694
- right: money30("50520800")
33591
+ right: money33("50520800")
32695
33592
  // $505,208 — encoded bands end here
32696
33593
  },
32697
33594
  then: {
@@ -32735,26 +33632,26 @@ var caRules = [
32735
33632
  }
32736
33633
  }
32737
33634
  });
32738
- const base = { kind: "max0", arg: fact33("stateTaxableIncome") };
33635
+ const base = { kind: "max0", arg: fact36("stateTaxableIncome") };
32739
33636
  const lower = {
32740
33637
  kind: "add",
32741
33638
  args: [
32742
- money30("5100"),
33639
+ money33("5100"),
32743
33640
  // $51
32744
33641
  {
32745
33642
  kind: "mulInt",
32746
- base: money30("10000"),
33643
+ base: money33("10000"),
32747
33644
  // $100
32748
33645
  count: {
32749
33646
  kind: "stepUnits",
32750
- value: { kind: "sub", left: base, right: money30("5100") },
33647
+ value: { kind: "sub", left: base, right: money33("5100") },
32751
33648
  unitCents: "10000",
32752
33649
  mode: "floor"
32753
33650
  }
32754
33651
  }
32755
33652
  ]
32756
33653
  };
32757
- const generalMidpoint = { kind: "add", args: [lower, money30("4950")] };
33654
+ const generalMidpoint = { kind: "add", args: [lower, money33("4950")] };
32758
33655
  const midpoint = {
32759
33656
  kind: "if",
32760
33657
  cond: {
@@ -32773,7 +33670,7 @@ var caRules = [
32773
33670
  cond: {
32774
33671
  kind: "or",
32775
33672
  args: [
32776
- fact33("useFormulaMethod"),
33673
+ fact36("useFormulaMethod"),
32777
33674
  {
32778
33675
  kind: "cmp",
32779
33676
  op: "ge",
@@ -32793,7 +33690,7 @@ var caRules = [
32793
33690
  right: { kind: "param", name: "tableFirstRowCeiling" }
32794
33691
  // <= $50
32795
33692
  },
32796
- then: money30("0"),
33693
+ then: money33("0"),
32797
33694
  else: { kind: "roundToDollar", value: scheduleFor(midpoint), mode: "half-up" }
32798
33695
  }
32799
33696
  };
@@ -32809,7 +33706,7 @@ var caRules = [
32809
33706
  source: "R&TC \xA7 17052; 2025 FTB 3514 booklet (EITC table + General Information)",
32810
33707
  section: "R&TC \xA7 17052",
32811
33708
  url: "https://www.ftb.ca.gov/forms/2025/2025-3514-booklet.pdf",
32812
- excerpt: "2025 CalEITC: income ceiling $32,900 (all family sizes, both earned income AND federal AGI must be under $32,901 or the credit is $0); maximum credit $302 (0 kids, peak bracket $4,651-$4,700) / $2,016 (1 kid, peak $6,951-$7,000) / $3,339 (2 kids, peak $9,801-$9,850) / $3,756 (3+ kids, same $9,801-$9,850 peak). DECODED STRUCTURE (July 2026 web-verification: the printed table was parsed in full, all 658 $50-wide brackets from $1-$32,900, `pdftotext -layout`, and formula-fit): no plateau \u2014 every column peaks at a single $50 bracket and immediately begins declining. The decline is NOT one straight line: it has a KINK where the phase-out slope changes from steep to gentle, at 0 kids $5,450/$5,451, 1 kid $11,800/$11,801, 2 kids $17,800/$17,801, 3+ kids $18,000/$18,001 \u2014 confirmed by row-to-row deltas dropping from roughly $17-19 per $50 bracket (steep) to roughly $2-3 per bracket (gentle) exactly at each boundary. THIS RULE NOW COMPUTES ALL THREE SEGMENTS: (1) PHASE-IN (unchanged from v1): credit = round(bracket-midpoint \xD7 combined rate), combined rate = federal \xA7 32(b) phase-in rate \xD7 the 85% \xA7 17052(c) adjustment factor (7.65/34/40/45% \xD7 85% = 6.5025/28.90/34.00/38.25%), using the floor-aligned $50-bracket midpoint \u2014 verified against the printed row 2,201-2,250 \u2192 851 for 3+ children (2,225.50 \xD7 45% \xD7 85% = 851.25 \u2192 851). (2) STEEP PHASE-OUT (peak to kink): the printed table declines at (within rounding) the SAME rate magnitude as the phase-in line (a symmetric V through the peak) \u2014 credit = round(anchor value \u2212 combined phase-in rate \xD7 (bracket-midpoint \u2212 anchor-bracket-midpoint)), using the TRUE (upper-bound-aligned) $50-bracket midpoint. For 1/2/3+ kids the line is anchored to an actual printed table row (1 kid: bracket 9,451-9,500, $1,306; 2 kids and 3+ kids: bracket 9,951-10,000, $3,288/$3,699) rather than the theoretical peak itself \u2014 the peak POINT sits fractionally off this line for 1 kid specifically (a peak-anchored construction understates by roughly $13 there), so a real table row is used everywhere data exists. Verified EXACT against every printed row in this zone for 1/2/3+ kids (e.g. 2 kids: $10,000 \u2192 3,288 exact, $16,500 \u2192 1,078 exact, $17,500 \u2192 738 exact; 3+ kids: $16,500 \u2192 1,213 exact; 1 kid: $9,500 \u2192 1,306 exact, $11,000 \u2192 873 exact). (3) GENTLE PHASE-OUT (kink to ceiling): a SEPARATE, shallower line anchored to a printed table row just past the kink, at the independently-fit rate (0 kids = \u22120.9%/$1 i.e. \u22129/1000 per $1 of midpoint, CONFIRMED against the printed 2025 FTB 3514 booklet row $12,951-$13,000 \u2192 $183 exact, web-verified July 2026 \u2014 corrects a prior unverified anchor that produced $184 for that row; 1 kid \u2248 \u22123.01%/$1 i.e. \u2212541/18000, 2 kids \u2248 \u22124.19%/$1 i.e. \u221225/596, 3+ kids \u2248 \u22124.26%/$1 i.e. \u2212613/14400) \u2014 verified within \u2264$1 across every printed row from the kink to $32,900 (e.g. 0 kids: $17,000 \u2192 147 exact; 2 kids: $18,000 \u2192 626 exact, $25,000 \u2192 333 vs. computed 332, $30,000 \u2192 123 vs. computed 123). Max residual across all 658 rows, all four columns: \u2264 $2 pre-rounding (a single pre-existing phase-in-region anomaly just below the 2/3+-kid peak, at $9,500, runs to $17-19 \u2014 inherited from the v1 floor-aligned phase-in formula, unchanged here per the phase-in region's own design, and confined to that one boundary row). The 0-kid STEEP segment (peak to $5,450) has no printed table data in the range this session could source ($9,500+) to verify against; it uses the peak-anchor construction (disclosed as unverified-but-consistent with the confirmed symmetric-V structure elsewhere) \u2014 only the GENTLE segment's 0-kid anchor is table-confirmed as of v5. Earned income here = wages (line 1z, INCLUDING any taxable dependent-care benefits on line 1e) + max(0, SE profit \u2212 \xBDSE tax) \u2212 Schedule C loss, same composition as the federal \xA7 32(c)(2) input. CAUTION (validated on ty25-ca-003): when a spouse's own Schedule C loss makes that spouse's Form 2441 earned income zero or negative, the \xA7 129 exclusion for W-2 box 10 dependent care benefits is $0 \u2014 the FULL box-10 amount becomes taxable wages (line 1e), raising both federal AGI and this credit's earned income (federal earned 10,500 + 6,000 fully-taxable DCB \u2192 CA earned 16,500 \u2192 credit 1,078 from the printed row). Kids = qualifyingChildren + eitcAdditionalQualifyingChildren."
33709
+ excerpt: "2025 CalEITC: income ceiling $32,900 (all family sizes, both earned income AND federal AGI must be under $32,901 or the credit is $0); maximum credit $302 (0 kids, peak bracket $4,651-$4,700) / $2,016 (1 kid, peak $6,951-$7,000) / $3,339 (2 kids, peak $9,801-$9,850) / $3,756 (3+ kids, same $9,801-$9,850 peak). DECODED STRUCTURE (July 2026 web-verification: the printed table was parsed in full, all 658 $50-wide brackets from $1-$32,900, `pdftotext -layout`, and formula-fit): no plateau \u2014 every column peaks at a single $50 bracket and immediately begins declining. The decline is NOT one straight line: it has a KINK where the phase-out slope changes from steep to gentle, at 0 kids $5,450/$5,451, 1 kid $11,800/$11,801, 2 kids $17,800/$17,801, 3+ kids $18,000/$18,001 \u2014 confirmed by row-to-row deltas dropping from roughly $17-19 per $50 bracket (steep) to roughly $2-3 per bracket (gentle) exactly at each boundary. THIS RULE NOW COMPUTES ALL THREE SEGMENTS: (1) PHASE-IN (unchanged from v1): credit = round(bracket-midpoint \xD7 combined rate), combined rate = federal \xA7 32(b) phase-in rate \xD7 the 85% \xA7 17052(c) adjustment factor (7.65/34/40/45% \xD7 85% = 6.5025/28.90/34.00/38.25%), using the floor-aligned $50-bracket midpoint \u2014 verified against the printed row 2,201-2,250 \u2192 851 for 3+ children (2,225.50 \xD7 45% \xD7 85% = 851.25 \u2192 851). (2) STEEP PHASE-OUT (peak to kink): the printed table declines at (within rounding) the SAME rate magnitude as the phase-in line (a symmetric V through the peak) \u2014 credit = round(anchor value \u2212 combined phase-in rate \xD7 (bracket-midpoint \u2212 anchor-bracket-midpoint)), using the TRUE (upper-bound-aligned) $50-bracket midpoint. For 1/2/3+ kids the line is anchored to an actual printed table row (1 kid: bracket 9,451-9,500, $1,306; 2 kids and 3+ kids: bracket 9,951-10,000, $3,288/$3,699) rather than the theoretical peak itself \u2014 the peak POINT sits fractionally off this line for 1 kid specifically (a peak-anchored construction understates by roughly $13 there), so a real table row is used everywhere data exists. Verified EXACT against every printed row in this zone for 1/2/3+ kids (e.g. 2 kids: $10,000 \u2192 3,288 exact, $16,500 \u2192 1,078 exact, $17,500 \u2192 738 exact; 3+ kids: $16,500 \u2192 1,213 exact; 1 kid: $9,500 \u2192 1,306 exact, $11,000 \u2192 873 exact). (3) GENTLE PHASE-OUT (kink to ceiling): a SEPARATE, shallower line anchored to a printed table row just past the kink, at the independently-fit rate (0 kids = \u22120.9%/$1 i.e. \u22129/1000 per $1 of midpoint, CONFIRMED against the printed 2025 FTB 3514 booklet row $12,951-$13,000 \u2192 $183 exact, web-verified July 2026 \u2014 corrects a prior unverified anchor that produced $184 for that row; 1 kid \u2248 \u22123.01%/$1 i.e. \u2212541/18000, 2 kids \u2248 \u22124.19%/$1 i.e. \u221225/596, 3+ kids \u2248 \u22124.26%/$1 i.e. \u2212613/14400) \u2014 verified within \u2264$1 across every printed row from the kink to $32,900 (e.g. 0 kids: $17,000 \u2192 147 exact; 2 kids: $18,000 \u2192 626 exact, $25,000 \u2192 333 vs. computed 332, $30,000 \u2192 123 vs. computed 123). Max residual across all 658 rows, all four columns: \u2264 $2 pre-rounding (a single pre-existing phase-in-region anomaly just below the 2/3+-kid peak, at $9,500, runs to $17-19 \u2014 inherited from the v1 floor-aligned phase-in formula, unchanged here per the phase-in region's own design, and confined to that one boundary row). The 0-kid STEEP segment (peak to $5,450) has no printed table data in the range this session could source ($9,500+) to verify against; it uses the peak-anchor construction (disclosed as unverified-but-consistent with the confirmed symmetric-V structure elsewhere) \u2014 only the GENTLE segment's 0-kid anchor is table-confirmed as of v5. Earned income here = wages (line 1z, INCLUDING any taxable dependent-care benefits on line 1e) + max(0, SE profit \u2212 \xBDSE tax) \u2212 Schedule C loss, same composition as the federal \xA7 32(c)(2) input. CAUTION: when a spouse's own Schedule C loss makes that spouse's Form 2441 earned income zero or negative, the \xA7 129 exclusion for W-2 box 10 dependent care benefits is $0 \u2014 the FULL box-10 amount becomes taxable wages (line 1e), raising both federal AGI and this credit's earned income (federal earned 10,500 + 6,000 fully-taxable DCB \u2192 CA earned 16,500 \u2192 credit 1,078 from the printed row). Kids = qualifyingChildren + eitcAdditionalQualifyingChildren."
32813
33710
  },
32814
33711
  effectiveFrom: "2025-01-01",
32815
33712
  effectiveTo: "2026-01-01",
@@ -32894,33 +33791,33 @@ var caRules = [
32894
33791
  left: {
32895
33792
  kind: "add",
32896
33793
  args: [
32897
- fact33("wages"),
33794
+ fact36("wages"),
32898
33795
  {
32899
33796
  kind: "max0",
32900
33797
  arg: {
32901
33798
  kind: "sub",
32902
- left: fact33("selfEmploymentNetProfit"),
33799
+ left: fact36("selfEmploymentNetProfit"),
32903
33800
  right: { kind: "rule", ruleId: "us.federal.se_tax_half_deduction" }
32904
33801
  }
32905
33802
  }
32906
33803
  ]
32907
33804
  },
32908
- right: fact33("scheduleCNetLoss")
33805
+ right: fact36("scheduleCNetLoss")
32909
33806
  }
32910
33807
  };
32911
33808
  const midCeil = {
32912
33809
  kind: "sub",
32913
33810
  left: {
32914
33811
  kind: "mulInt",
32915
- base: money30("5000"),
33812
+ base: money33("5000"),
32916
33813
  count: { kind: "stepUnits", value: earned2, unitCents: "5000", mode: "ceil" }
32917
33814
  },
32918
- right: money30("2450")
33815
+ right: money33("2450")
32919
33816
  // $24.50
32920
33817
  };
32921
33818
  const kids = {
32922
33819
  kind: "add",
32923
- args: [fact33("qualifyingChildren"), fact33("eitcAdditionalQualifyingChildren")]
33820
+ args: [fact36("qualifyingChildren"), fact36("eitcAdditionalQualifyingChildren")]
32924
33821
  };
32925
33822
  const kidsEq = (n) => ({
32926
33823
  kind: "cmp",
@@ -33022,12 +33919,12 @@ var caRules = [
33022
33919
  {
33023
33920
  kind: "cmp",
33024
33921
  op: "ge",
33025
- left: ruleRef28("us.federal.agi"),
33922
+ left: ruleRef30("us.federal.agi"),
33026
33923
  right: { kind: "param", name: "ceiling" }
33027
33924
  }
33028
33925
  ]
33029
33926
  },
33030
- then: money30("0"),
33927
+ then: money33("0"),
33031
33928
  else: {
33032
33929
  kind: "if",
33033
33930
  cond: kidsEq("0"),
@@ -33077,28 +33974,28 @@ var caRules = [
33077
33974
  left: {
33078
33975
  kind: "add",
33079
33976
  args: [
33080
- fact33("wages"),
33977
+ fact36("wages"),
33081
33978
  {
33082
33979
  kind: "max0",
33083
33980
  arg: {
33084
33981
  kind: "sub",
33085
- left: fact33("selfEmploymentNetProfit"),
33982
+ left: fact36("selfEmploymentNetProfit"),
33086
33983
  right: { kind: "rule", ruleId: "us.federal.se_tax_half_deduction" }
33087
33984
  }
33088
33985
  }
33089
33986
  ]
33090
33987
  },
33091
- right: fact33("scheduleCNetLoss")
33988
+ right: fact36("scheduleCNetLoss")
33092
33989
  }
33093
33990
  };
33094
33991
  return {
33095
33992
  kind: "if",
33096
- cond: { kind: "not", arg: fact33("hasChildUnderSix") },
33097
- then: money30("0"),
33993
+ cond: { kind: "not", arg: fact36("hasChildUnderSix") },
33994
+ then: money33("0"),
33098
33995
  else: {
33099
33996
  kind: "if",
33100
33997
  cond: { kind: "cmp", op: "ge", left: earned2, right: { kind: "param", name: "phaseOutComplete" } },
33101
- then: money30("0"),
33998
+ then: money33("0"),
33102
33999
  else: {
33103
34000
  kind: "if",
33104
34001
  cond: { kind: "cmp", op: "le", left: earned2, right: { kind: "param", name: "phaseOutStart" } },
@@ -33138,8 +34035,8 @@ var caRules = [
33138
34035
  },
33139
34036
  formula: {
33140
34037
  kind: "if",
33141
- cond: { kind: "not", arg: fact33("caRentedPrincipalResidence") },
33142
- then: money30("0"),
34038
+ cond: { kind: "not", arg: fact36("caRentedPrincipalResidence") },
34039
+ then: money33("0"),
33143
34040
  else: {
33144
34041
  kind: "if",
33145
34042
  cond: {
@@ -33151,22 +34048,22 @@ var caRules = [
33151
34048
  cond: {
33152
34049
  kind: "cmp",
33153
34050
  op: "le",
33154
- left: ruleRef28("us.federal.agi"),
34051
+ left: ruleRef30("us.federal.agi"),
33155
34052
  right: { kind: "param", name: "agiCeilingJointHohQss" }
33156
34053
  },
33157
34054
  then: { kind: "param", name: "creditJointHohQss" },
33158
- else: money30("0")
34055
+ else: money33("0")
33159
34056
  },
33160
34057
  else: {
33161
34058
  kind: "if",
33162
34059
  cond: {
33163
34060
  kind: "cmp",
33164
34061
  op: "le",
33165
- left: ruleRef28("us.federal.agi"),
34062
+ left: ruleRef30("us.federal.agi"),
33166
34063
  right: { kind: "param", name: "agiCeilingSingleMfs" }
33167
34064
  },
33168
34065
  then: { kind: "param", name: "creditSingleMfs" },
33169
- else: money30("0")
34066
+ else: money33("0")
33170
34067
  }
33171
34068
  }
33172
34069
  }
@@ -33210,7 +34107,7 @@ var caRules = [
33210
34107
  source: "R&TC \xA7\xA7 17062, 17062.1; 2025 FTB Schedule P (540) instructions",
33211
34108
  section: "R&TC \xA7 17062(a)-(e); \xA7 17062.1",
33212
34109
  url: "https://www.ftb.ca.gov/forms/2025/2025-540-p-instructions.html",
33213
- excerpt: "THIS RULE TAKES caAmti AS AN INPUT \u2014 it applies the exemption/TMT/comparison mechanics only; it does not construct AMTI itself. CONSTRUCTION OF caAmti (disclosed on the fact, repeated here): California individual AMT is FROZEN to IRC \xA7\xA7 55-59 as they read on January 1, 2015 (R&TC \xA7 17062.1, re-enacted by SB 711 (Stats. 2025, Ch. 231) WITHOUT moving the freeze forward, even though SB 711 rolled CA's general IRC conformity date to January 1, 2025 for other provisions). Starting from CA taxable income (Form 540 line 19), the STANDARD DEDUCTION IS ADDED BACK (R&TC \xA7 17062(c)(1), denying \xA7 56(b)(1)(E)). The ISO exercise spread follows \xA7 56(b)(3) as modified by R&TC \xA7 17062(c)(2) (plus the California Qualified Stock Option addback, R&TC \xA7 17502). There is NO tax-exempt private-activity-bond-interest preference (R&TC \xA7 17062(d) turns off \xA7 57(a)(5)) and NO AMT foreign tax credit (R&TC \xA7 17062(e) turns off \xA7 59(a)). A taxpayer with aggregate trade/business gross receipts under $1,000,000 (a flat threshold, NOT doubled for MFJ) excludes ALL trade/business income, adjustments, and preferences from AMTI (R&TC \xA7 17062(b)(4), the CA small-business AMTI exclusion). 2025 exemption/phase-out amounts (Exemption Worksheet, 2025 FTB Schedule P (540) instructions, web-verified July 2026): exemption $92,749 single/HOH, $123,667 MFJ/QSS, $61,830 MFS; phased out 25 cents per dollar of caAmti over $347,808 single/HOH, $463,745 MFJ/QSS, $231,868 MFS, floored at $0. Rate \u2014 7% (R&TC \xA7 17062(b)(3)(A)(iv)) of caAmti minus the exemption = the tentative minimum tax. AMT (R&TC \xA7 17062(a)) = the excess, if any, of the tentative minimum tax over the regular CA tax (caRegularTax \u2014 Schedule P (540) line 25: the Form 540 LINE 31 regular tax BEFORE exemption credits; reference-validated TMT 25,484 \u2212 line-31 12,038 = AMT 13,446 \u2014 passing the after-credits line 48 amount overstates AMT by exactly the exemption credits). MFS AMTI STACKING ADD-BACK: a caAmti above $479,188 for an MFS filer requires adding to AMTI, before this rule's own exemption/phase-out math, the lesser of 25% of the excess over $479,188 or $61,830 (Schedule P (540) 'Line 21' worksheet) \u2014 NOT modeled here; this rule REFUSES for MFS above that threshold rather than silently omitting the add-back. Also not modeled: the \xA7 59(j) certain-children-under-24 exemption cap, and Part III credit ordering (e.g. the PTE elective tax credit's below-TMT exception)."
34110
+ excerpt: "THIS RULE TAKES caAmti AS AN INPUT \u2014 it applies the exemption/TMT/comparison mechanics only; it does not construct AMTI itself. CONSTRUCTION OF caAmti (disclosed on the fact, repeated here): California individual AMT is FROZEN to IRC \xA7\xA7 55-59 as they read on January 1, 2015 (R&TC \xA7 17062.1, re-enacted by SB 711 (Stats. 2025, Ch. 231) WITHOUT moving the freeze forward, even though SB 711 rolled CA's general IRC conformity date to January 1, 2025 for other provisions). Starting from CA taxable income (Form 540 line 19), the STANDARD DEDUCTION IS ADDED BACK (R&TC \xA7 17062(c)(1), denying \xA7 56(b)(1)(E)). The ISO exercise spread follows \xA7 56(b)(3) as modified by R&TC \xA7 17062(c)(2) (plus the California Qualified Stock Option addback, R&TC \xA7 17502). There is NO tax-exempt private-activity-bond-interest preference (R&TC \xA7 17062(d) turns off \xA7 57(a)(5)) and NO AMT foreign tax credit (R&TC \xA7 17062(e) turns off \xA7 59(a)). A taxpayer with aggregate trade/business gross receipts under $1,000,000 (a flat threshold, NOT doubled for MFJ) excludes ALL trade/business income, adjustments, and preferences from AMTI (R&TC \xA7 17062(b)(4), the CA small-business AMTI exclusion). 2025 exemption/phase-out amounts (Exemption Worksheet, 2025 FTB Schedule P (540) instructions, web-verified July 2026): exemption $92,749 single/HOH, $123,667 MFJ/QSS, $61,830 MFS; phased out 25 cents per dollar of caAmti over $347,808 single/HOH, $463,745 MFJ/QSS, $231,868 MFS, floored at $0. Rate \u2014 7% (R&TC \xA7 17062(b)(3)(A)(iv)) of caAmti minus the exemption = the tentative minimum tax. AMT (R&TC \xA7 17062(a)) = the excess, if any, of the tentative minimum tax over the regular CA tax (caRegularTax \u2014 Schedule P (540) line 25: the Form 540 LINE 31 regular tax BEFORE exemption credits; worked example: TMT 25,484 \u2212 line-31 12,038 = AMT 13,446 \u2014 passing the after-credits line 48 amount overstates AMT by exactly the exemption credits). MFS AMTI STACKING ADD-BACK: a caAmti above $479,188 for an MFS filer requires adding to AMTI, before this rule's own exemption/phase-out math, the lesser of 25% of the excess over $479,188 or $61,830 (Schedule P (540) 'Line 21' worksheet) \u2014 NOT modeled here; this rule REFUSES for MFS above that threshold rather than silently omitting the add-back. Also not modeled: the \xA7 59(j) certain-children-under-24 exemption cap, and Part III credit ordering (e.g. the PTE elective tax credit's below-TMT exception)."
33214
34111
  },
33215
34112
  effectiveFrom: "2025-01-01",
33216
34113
  effectiveTo: "2026-01-01",
@@ -33234,7 +34131,7 @@ var caRules = [
33234
34131
  formula: (() => {
33235
34132
  const byStatus = (single, joint, mfs) => ({
33236
34133
  kind: "match",
33237
- on: fact33("filingStatus"),
34134
+ on: fact36("filingStatus"),
33238
34135
  cases: [
33239
34136
  { when: "single", value: { kind: "param", name: single } },
33240
34137
  { when: "hoh", value: { kind: "param", name: single } },
@@ -33252,7 +34149,7 @@ var caRules = [
33252
34149
  left: exemption2,
33253
34150
  right: {
33254
34151
  kind: "mulRate",
33255
- base: { kind: "max0", arg: { kind: "sub", left: fact33("caAmti"), right: threshold2 } },
34152
+ base: { kind: "max0", arg: { kind: "sub", left: fact36("caAmti"), right: threshold2 } },
33256
34153
  rate: { num: "25", den: "100" },
33257
34154
  round: "half-up"
33258
34155
  }
@@ -33260,11 +34157,11 @@ var caRules = [
33260
34157
  };
33261
34158
  const tmt = {
33262
34159
  kind: "mulRate",
33263
- base: { kind: "max0", arg: { kind: "sub", left: fact33("caAmti"), right: exemptionPhased } },
34160
+ base: { kind: "max0", arg: { kind: "sub", left: fact36("caAmti"), right: exemptionPhased } },
33264
34161
  rate: { num: "7", den: "100" },
33265
34162
  round: "half-up"
33266
34163
  };
33267
- const amt = { kind: "max0", arg: { kind: "sub", left: tmt, right: fact33("caRegularTax") } };
34164
+ const amt = { kind: "max0", arg: { kind: "sub", left: tmt, right: fact36("caRegularTax") } };
33268
34165
  return {
33269
34166
  kind: "if",
33270
34167
  cond: {
@@ -33274,7 +34171,7 @@ var caRules = [
33274
34171
  {
33275
34172
  kind: "cmp",
33276
34173
  op: "gt",
33277
- left: fact33("caAmti"),
34174
+ left: fact36("caAmti"),
33278
34175
  right: { kind: "param", name: "mfsStackingThreshold" }
33279
34176
  }
33280
34177
  ]
@@ -33309,7 +34206,7 @@ var caRules = [
33309
34206
  kind: "mulRate",
33310
34207
  base: {
33311
34208
  kind: "max0",
33312
- arg: { kind: "sub", left: fact33("stateTaxableIncome"), right: { kind: "param", name: "threshold" } }
34209
+ arg: { kind: "sub", left: fact36("stateTaxableIncome"), right: { kind: "param", name: "threshold" } }
33313
34210
  },
33314
34211
  rate: { num: "1", den: "100" },
33315
34212
  round: "half-up"
@@ -33353,7 +34250,7 @@ var nyRules = [
33353
34250
  source: "N.Y. Tax Law \xA7 614; 2025 Form IT-201 instructions",
33354
34251
  section: "Tax Law \xA7 614",
33355
34252
  url: "https://www.tax.ny.gov/pit/file/standard_deductions.htm",
33356
- excerpt: "Standard deductions (Tax Law \xA7 614, 2025-confirmed): $8,000 single; $16,050 MFJ/QSS; $11,200 HOH (reference-validated against ty25-ny-002); $8,000 MFS; $3,100 dependent filer. Dependent exemptions $1,000 each (IT-201 line 36). CREDITS & MODIFICATIONS (all web-verified from the 2025 IT-201-I / IT-213-I / IT-215-I / IT-216-I / IT-214-I / IT-225-I / IT-272-I / IT-196-I): NYS EIC = 30% of federal EIC, reduced by the household credit ACTUALLY ABSORBED against NYS tax (min(line 39 tax, household credit) \u2014 when tax is $0 the reduction is $0 and the full 30% is refundable; IT-215 worksheet). NYC EIC = federal EIC \xD7 a sliding NYAGI rate: 30% \u2264 $7,500; 25% flat $7,500\u2013$15,000; phasing 25\u219220% to $17,500; 20% to $20,000; phasing 20\u219215% to $22,500; 15% to $40,000; phasing 15\u219210% to $42,500; 10% above. EMPIRE STATE CHILD CREDIT 2025 (IT-213, decoupled from federal): $1,000 per qualifying child UNDER FOUR + $330 per child 4\u201316 (TY2025; rises to $500 for the under-4 group's OLDER siblings \u2014 the ESCC ages-4-16 amount is $500 for TY2026-27 only, not yet in effect for TY2025), reduced $16.50 per $1,000 of FAGI (rounded DOWN to the nearest $1,000) over $110,000 MFJ / $75,000 single-HOH-QSS / $55,000 MFS.\n\nNYS HOUSEHOLD CREDIT (IT-201 line 40, IT-201-I p.13, keyed to FEDERAL AGI = Form IT-201 line 19) \u2014 THREE TABLES, verbatim 2025:\nTable 1 (single only): FAGI over \u2013$5,000 \u2192 $75; $5,000\u2013$6,000 \u2192 $60; $6,000\u2013$7,000 \u2192 $50; $7,000\u2013$20,000 \u2192 $45; $20,000\u2013$25,000 \u2192 $40; $25,000\u2013$28,000 \u2192 $20; over $28,000 \u2192 no credit.\nTable 2 (MFJ/HOH/QSS), by (dependents + 1 for filer + 1 for spouse if MFJ) = 1/2/3/4/5/6/7/each-over-7: FAGI \u2013$5,000 \u2192 90/105/120/135/150/165/180/+15; $5,000\u2013$6,000 \u2192 75/90/105/120/135/150/165/+15; $6,000\u2013$7,000 \u2192 65/80/95/110/125/140/155/+15; $7,000\u2013$20,000 \u2192 60/75/90/105/120/135/150/+15; $20,000\u2013$22,000 \u2192 60/70/80/90/100/110/120/+10; $22,000\u2013$25,000 \u2192 50/60/70/80/90/100/110/+10; $25,000\u2013$28,000 \u2192 40/45/50/55/60/65/70/+5; $28,000\u2013$32,000 \u2192 20/25/30/35/40/45/50/+5; over $32,000 \u2192 no credit.\nTable 3 (MFS only, dependents from BOTH returns + 1 for self + 1 for spouse), same 1-7/+over-7 columns: FAGI \u2013$5,000 \u2192 45/53/60/68/75/83/90/+8; $5,000\u2013$6,000 \u2192 38/45/53/60/68/75/83/+8; $6,000\u2013$7,000 \u2192 33/40/48/55/63/70/78/+8; $7,000\u2013$20,000 \u2192 30/38/45/53/60/68/75/+8; $20,000\u2013$22,000 \u2192 30/35/40/45/50/55/60/+5; $22,000\u2013$25,000 \u2192 25/30/35/40/45/50/55/+5; $25,000\u2013$28,000 \u2192 20/23/25/28/30/33/35/+3; $28,000\u2013$32,000 \u2192 10/13/15/18/20/23/25/+3; over $32,000 \u2192 no credit (table 3 amounts are pre-rounded per the form's Note 5).\n\nNYC HOUSEHOLD CREDIT (line 48, IT-201-I p.14) \u2014 THREE TABLES, verbatim 2025:\nTable 4 (single only): FAGI \u2013$10,000 \u2192 $15; $10,000\u2013$12,500 \u2192 $10; over $12,500 \u2192 no credit.\nTable 5 (MFJ/HOH/QSS), 1/2/3/4/5/6/7/each-over-7: FAGI \u2013$15,000 \u2192 30/60/90/120/150/180/210/+30; $15,000\u2013$17,500 \u2192 25/50/75/100/125/150/175/+25; $17,500\u2013$20,000 \u2192 15/30/45/60/75/90/105/+15; $20,000\u2013$22,500 \u2192 10/20/30/40/50/60/70/+10; over $22,500 \u2192 no credit.\nTable 6 (MFS only, dependents both returns + self + spouse), 1/2/3/4/5/6/7/each-over-7: FAGI \u2013$15,000 \u2192 15/30/45/60/75/90/105/+15; $15,000\u2013$17,500 \u2192 13/25/38/50/63/75/88/+13; $17,500\u2013$20,000 \u2192 8/15/23/30/38/45/53/+8; $20,000\u2013$22,500 \u2192 5/10/15/20/25/30/35/+5; over $22,500 \u2192 no credit (pre-rounded, Note 5).\n\nIT-216 CDCC (2025 IT-216-I, verbatim factor tables \u2014 replaces the earlier '1.10 \u2264 $25k' shorthand): base = qualifying expenses NET of employer dependent-care benefits excluded on W-2 box 10. NYS LINE-13 FACTOR (NYAGI-keyed, NON-MONOTONIC SAWTOOTH, IT-216-I p.7 'credit limitation table', $200-wide rows \u2014 segment anchors verbatim): flat 1.100 for NYAGI \u2013$25,000; descends in small ($0.001\u2013$0.002-per-$200) steps from 1.099 at $25,000 down to 1.000 by $40,000; FLAT 1.000 for $40,000\u2013$50,000; JUMPS to 1.162 at $50,000 and descends to 1.000 by $52,600\u2013$52,800, continuing down to 0.601 by $59,800\u2013$60,000; JUMPS AGAIN to 1.070 at $60,000 and descends to 0.476 by $64,800\u2013$65,000; FLAT 0.600 for $65,000\u2013$150,000; FLAT 0.200 above $150,000. NYS LINE-10 FACTOR (separate table, FAGI-keyed, monotonic): .35 for FAGI \u2013$15,000, stepping down by .01 per $2,000 of FAGI to .20 at $43,000 and above (verbatim anchors: $15,000\u2013$17,000 \u2192 .34 \u2026 $41,000\u2013$43,000 \u2192 .21; $43,000+ \u2192 .20). NYC CHILD CARE CREDIT (Worksheet 2 \u2014 now a standalone computable target, us.ny.nyc_cdcc): requires a qualifying child UNDER 4 and FAGI $30,000 or less; the worksheet PRORATES THE NYS CREDIT ITSELF (IT-216 line 14 \u2192 Worksheet 2 line 1) by the under-4 expense share (line 23 \xF7 line 3a, capped at 1.0000), THEN multiplies by the line-6 FAGI factor (0.750 for FAGI \u2013$25,000, descending 0.030 per $200 to 0.000 at $30,000). NEVER prorate the raw expenses and re-apply the line-10/line-13 factors \u2014 that understates the credit; feed nyIt216StateCredit/nyIt216Under4Expenses/nyIt216TotalExpenses to us.ny.nyc_cdcc and use its answer.\n\nNYC SCHOOL TAX CREDIT fixed (line 69, income \u2264 $250,000): $63 single/MFS/HOH, $125 MFJ/QSS; plus the line 69a rate-reduction amount (\u2264 $500,000): HOH 0.171% of city taxable income to $14,400 then $25 + 0.228% of excess (single/MFS threshold $12,000/$21; MFJ $21,600/$37). IT-214 REAL PROPERTY TAX CREDIT (Form IT-214 (2025), read directly from the printed form \u2014 SECOND CORRECTION, July 2026: line 12 uses a FIXED PERCENTAGE MENU, not actual utility dollar subtraction): line 8 FAGI \u2264 $18,000 stop; line 12 adjusted rent = line 11 total rent x the printed menu \u2014 rent includes heat+gas+electricity+furnishings+board \u2192 50%; heat+gas+electricity+furnishings \u2192 75%; heat+gas+electricity \u2192 80%; heat (or heat+gas) \u2192 85%; none \u2192 100%; line 13 average monthly adjusted rent (line 12 \xF7 months paid) \u2264 $450 stop; line 14 = 25% x line 12; homeowners line 17 = property taxes + special assessments; line 18 (renters' 14 / homeowners' 17) must be positive and must EXCEED line 19 = FAGI x Table 1 rate (0.035 to 0.065 by FAGI bracket). THE CREDIT (line 20) is a FLAT Table A/B lookup by FAGI. ALL of this arithmetic is COMPUTED by us.ny.it214 \u2014 feed it nyIt214Fagi, nyIt214TotalRent, nyIt214RentPercent (50/75/80/85/100 from the checkbox menu), nyIt214MonthsPaid, nyIt214HomeownerTaxes, isAge65OrOlder (any household member 65+ routes to Table A: $375/$330/$300/$260/$230/$200/$150), and nyIt214Eligible (Step 2 attestations only). Pass its answer into refundableCredits; never hand-compute any IT-214 line.\n\nIT-272 COLLEGE TUITION CREDIT (2025 IT-272-I): full-year NYS RESIDENTS only (part-year and nonresidents cannot claim it); qualified tuition expenses CAPPED at $10,000 per eligible student; credit = the LESSER of $400 or 4% of qualified expenses PER STUDENT (so the $400 max binds at $10,000 of expenses \u2014 confirm the 4% literal rate on the current worksheet before treating it as fixed across years); taxpayers may instead claim the college tuition ITEMIZED DEDUCTION on IT-201-D \u2014 mutually exclusive per student, elect whichever is larger.\n\nIT-229 REAL PROPERTY TAX RELIEF CREDIT: EXPIRED after TY2023 \u2014 DOES NOT EXIST for TY2025 or TY2026; do not encode or claim it. IT-214 (circuit breaker: FAGI \u2264 $18,000, property value \u2264 $85,000, rent \u2264 $450/month) is the live real-property-tax credit for TY2025.\n\n529 COLLEGE SAVINGS SUBTRACTION (Tax Law \xA7 612(c)(32)): up to $5,000 single/HOH/MFS, $10,000 MFJ, of contributions to a New York 529 account per year (recapture applies to nonqualified withdrawals of previously subtracted amounts). PENSION AND ANNUITY EXCLUSION (Tax Law \xA7 612(c)(3-a)): up to $20,000 EACH for taxpayer and spouse (no spousal sharing of unused exclusion) once age 59\xBD, for private/out-of-state pensions and annuities; NYS/local government and federal government pensions are excluded IN FULL regardless of age (a separate, unlimited subtraction, not capped at $20,000).\n\n\xA7 685(c) ESTIMATED TAX: no addition to tax if the amount owed after withholding is under $300 (de minimis); safe harbor is the LESSER of 90% of the current year's tax or 100% of the prior year's tax (110% of prior year's tax if the prior year's NYAGI exceeded $150,000).\n\nIT-196 ITEMIZED CHARITABLE LIMITATION (Tax Law \xA7 615, active through TY2029 per current law): for taxpayers with NYAGI over $1,000,000, the itemized charitable contribution deduction is limited to 50% of the federal amount; over $10,000,000 NYAGI, limited to 25% of the federal amount. UNRESOLVED (do NOT encode): the broader \xA7 615(f) general itemized-deduction phase-down for high-NYAGI filers could not be reconciled against the 2025 IT-196 instructions worksheet during this web-verification \u2014 pull the actual worksheet before modeling it; only the $1M/$10M charitable-specific limitation above is confirmed active.\n\nIT-225 MODIFICATIONS: \xA7 414(h) public-employee pickup = addition on IT-201 line 21 directly (code A-104, NOT via IT-225); NYC IRC \xA7 125 flexible-benefits = addition A-101; ALIMONY (NY did NOT conform to TCJA \xA7 11051): post-2018-instrument alimony PAID = subtraction S-136, alimony RECEIVED = addition A-119. S-120 \u2014 New York Higher Education Loan Program (HELP) loan interest (2025 IT-225-I p.6 subtraction-modifications chart and detail section, applicable to IT-201/IT-203 filers, not IT-204/IT-205), quoted verbatim: chart entry 'S-120 New York Higher Education Loan Program (HELP)'; detail text 'S-120: New York Higher Education Loan Program (HELP) \u2014 Enter any interest you paid in 2025 on loans made to you under HELP.' STATUTE CITE UNRESOLVED: the IT-225-I chart/detail text does not itself cite a Tax Law \xA7 612(c) paragraph number for S-120 (unlike some neighboring codes), and a web search of the codified \xA7 612 (nysenate.gov) did not turn up a matching paragraph as of this July 2026 verification \u2014 treat the CODE as confirmed-current for TY2025 via the 2025 IT-225-I form itself (the authority cited here), with the enabling paragraph number unresolved rather than guessed. Do not confuse with the separate, prospective, general student-loan-interest deduction tracked for a future Tax Law \xA7 612(c) paragraph (TY2026+, following 26 U.S.C. \xA7 221) \u2014 that is a different, not-yet-effective provision and does not affect the TY2025 S-120 answer.\n\nTY2026 ENACTED CHANGES (not encoded this pass \u2014 informational only, so a 2026 asOf refusal has useful context): a new subtraction for tips (up to $25,000, OBBBA conformity) begins TY2026; a new 'POWER credit' applies for TY2026 ONLY; a new REFUNDABLE CDCC under \xA7 606(c-2) replaces the nonrefundable IT-216 coupling for TY2026+ (55% of federal CDCC at NYAGI \u2264 $15,000, phasing down to a 4% floor, reduced further above $750,000 NYAGI); the Empire State Child Credit's $330 per-child (age 4-16) amount rises to $500 for TY2026-2027. Brackets, IT-272, the 529 subtraction, the household-credit tables, and IT-214 are UNCHANGED for TY2026 per the sources reviewed. Tax computation \u2192 us.ny.income_tax; NYC resident tax \u2192 us.ny.nyc_income_tax."
34253
+ excerpt: "Standard deductions (Tax Law \xA7 614, 2025-confirmed): $8,000 single; $16,050 MFJ/QSS; $11,200 HOH; $8,000 MFS; $3,100 dependent filer. Dependent exemptions $1,000 each (IT-201 line 36). CREDITS & MODIFICATIONS (all web-verified from the 2025 IT-201-I / IT-213-I / IT-215-I / IT-216-I / IT-214-I / IT-225-I / IT-272-I / IT-196-I): NYS EIC = 30% of federal EIC, reduced by the household credit ACTUALLY ABSORBED against NYS tax (min(line 39 tax, household credit) \u2014 when tax is $0 the reduction is $0 and the full 30% is refundable; IT-215 worksheet). NYC EIC = federal EIC \xD7 a sliding NYAGI rate: 30% \u2264 $7,500; 25% flat $7,500\u2013$15,000; phasing 25\u219220% to $17,500; 20% to $20,000; phasing 20\u219215% to $22,500; 15% to $40,000; phasing 15\u219210% to $42,500; 10% above. EMPIRE STATE CHILD CREDIT 2025 (IT-213, decoupled from federal): $1,000 per qualifying child UNDER FOUR + $330 per child 4\u201316 (TY2025; rises to $500 for the under-4 group's OLDER siblings \u2014 the ESCC ages-4-16 amount is $500 for TY2026-27 only, not yet in effect for TY2025), reduced $16.50 per $1,000 of FAGI (rounded DOWN to the nearest $1,000) over $110,000 MFJ / $75,000 single-HOH-QSS / $55,000 MFS.\n\nNYS HOUSEHOLD CREDIT (IT-201 line 40, IT-201-I p.13, keyed to FEDERAL AGI = Form IT-201 line 19) \u2014 THREE TABLES, verbatim 2025:\nTable 1 (single only): FAGI over \u2013$5,000 \u2192 $75; $5,000\u2013$6,000 \u2192 $60; $6,000\u2013$7,000 \u2192 $50; $7,000\u2013$20,000 \u2192 $45; $20,000\u2013$25,000 \u2192 $40; $25,000\u2013$28,000 \u2192 $20; over $28,000 \u2192 no credit.\nTable 2 (MFJ/HOH/QSS), by (dependents + 1 for filer + 1 for spouse if MFJ) = 1/2/3/4/5/6/7/each-over-7: FAGI \u2013$5,000 \u2192 90/105/120/135/150/165/180/+15; $5,000\u2013$6,000 \u2192 75/90/105/120/135/150/165/+15; $6,000\u2013$7,000 \u2192 65/80/95/110/125/140/155/+15; $7,000\u2013$20,000 \u2192 60/75/90/105/120/135/150/+15; $20,000\u2013$22,000 \u2192 60/70/80/90/100/110/120/+10; $22,000\u2013$25,000 \u2192 50/60/70/80/90/100/110/+10; $25,000\u2013$28,000 \u2192 40/45/50/55/60/65/70/+5; $28,000\u2013$32,000 \u2192 20/25/30/35/40/45/50/+5; over $32,000 \u2192 no credit.\nTable 3 (MFS only, dependents from BOTH returns + 1 for self + 1 for spouse), same 1-7/+over-7 columns: FAGI \u2013$5,000 \u2192 45/53/60/68/75/83/90/+8; $5,000\u2013$6,000 \u2192 38/45/53/60/68/75/83/+8; $6,000\u2013$7,000 \u2192 33/40/48/55/63/70/78/+8; $7,000\u2013$20,000 \u2192 30/38/45/53/60/68/75/+8; $20,000\u2013$22,000 \u2192 30/35/40/45/50/55/60/+5; $22,000\u2013$25,000 \u2192 25/30/35/40/45/50/55/+5; $25,000\u2013$28,000 \u2192 20/23/25/28/30/33/35/+3; $28,000\u2013$32,000 \u2192 10/13/15/18/20/23/25/+3; over $32,000 \u2192 no credit (table 3 amounts are pre-rounded per the form's Note 5).\n\nNYC HOUSEHOLD CREDIT (line 48, IT-201-I p.14) \u2014 THREE TABLES, verbatim 2025:\nTable 4 (single only): FAGI \u2013$10,000 \u2192 $15; $10,000\u2013$12,500 \u2192 $10; over $12,500 \u2192 no credit.\nTable 5 (MFJ/HOH/QSS), 1/2/3/4/5/6/7/each-over-7: FAGI \u2013$15,000 \u2192 30/60/90/120/150/180/210/+30; $15,000\u2013$17,500 \u2192 25/50/75/100/125/150/175/+25; $17,500\u2013$20,000 \u2192 15/30/45/60/75/90/105/+15; $20,000\u2013$22,500 \u2192 10/20/30/40/50/60/70/+10; over $22,500 \u2192 no credit.\nTable 6 (MFS only, dependents both returns + self + spouse), 1/2/3/4/5/6/7/each-over-7: FAGI \u2013$15,000 \u2192 15/30/45/60/75/90/105/+15; $15,000\u2013$17,500 \u2192 13/25/38/50/63/75/88/+13; $17,500\u2013$20,000 \u2192 8/15/23/30/38/45/53/+8; $20,000\u2013$22,500 \u2192 5/10/15/20/25/30/35/+5; over $22,500 \u2192 no credit (pre-rounded, Note 5).\n\nIT-216 CDCC (2025 IT-216-I, verbatim factor tables \u2014 replaces the earlier '1.10 \u2264 $25k' shorthand): base = qualifying expenses NET of employer dependent-care benefits excluded on W-2 box 10. NYS LINE-13 FACTOR (NYAGI-keyed, NON-MONOTONIC SAWTOOTH, IT-216-I p.7 'credit limitation table', $200-wide rows \u2014 segment anchors verbatim): flat 1.100 for NYAGI \u2013$25,000; descends in small ($0.001\u2013$0.002-per-$200) steps from 1.099 at $25,000 down to 1.000 by $40,000; FLAT 1.000 for $40,000\u2013$50,000; JUMPS to 1.162 at $50,000 and descends to 1.000 by $52,600\u2013$52,800, continuing down to 0.601 by $59,800\u2013$60,000; JUMPS AGAIN to 1.070 at $60,000 and descends to 0.476 by $64,800\u2013$65,000; FLAT 0.600 for $65,000\u2013$150,000; FLAT 0.200 above $150,000. NYS LINE-10 FACTOR (separate table, FAGI-keyed, monotonic): .35 for FAGI \u2013$15,000, stepping down by .01 per $2,000 of FAGI to .20 at $43,000 and above (verbatim anchors: $15,000\u2013$17,000 \u2192 .34 \u2026 $41,000\u2013$43,000 \u2192 .21; $43,000+ \u2192 .20). NYC CHILD CARE CREDIT (Worksheet 2 \u2014 now a standalone computable target, us.ny.nyc_cdcc): requires a qualifying child UNDER 4 and FAGI $30,000 or less; the worksheet PRORATES THE NYS CREDIT ITSELF (IT-216 line 14 \u2192 Worksheet 2 line 1) by the under-4 expense share (line 23 \xF7 line 3a, capped at 1.0000), THEN multiplies by the line-6 FAGI factor (0.750 for FAGI \u2013$25,000, descending 0.030 per $200 to 0.000 at $30,000). NEVER prorate the raw expenses and re-apply the line-10/line-13 factors \u2014 that understates the credit; feed nyIt216StateCredit/nyIt216Under4Expenses/nyIt216TotalExpenses to us.ny.nyc_cdcc and use its answer.\n\nNYC SCHOOL TAX CREDIT fixed (line 69, income \u2264 $250,000): $63 single/MFS/HOH, $125 MFJ/QSS; plus the line 69a rate-reduction amount (\u2264 $500,000): HOH 0.171% of city taxable income to $14,400 then $25 + 0.228% of excess (single/MFS threshold $12,000/$21; MFJ $21,600/$37). IT-214 REAL PROPERTY TAX CREDIT (Form IT-214 (2025), read directly from the printed form \u2014 SECOND CORRECTION, July 2026: line 12 uses a FIXED PERCENTAGE MENU, not actual utility dollar subtraction): line 8 FAGI \u2264 $18,000 stop; line 12 adjusted rent = line 11 total rent x the printed menu \u2014 rent includes heat+gas+electricity+furnishings+board \u2192 50%; heat+gas+electricity+furnishings \u2192 75%; heat+gas+electricity \u2192 80%; heat (or heat+gas) \u2192 85%; none \u2192 100%; line 13 average monthly adjusted rent (line 12 \xF7 months paid) \u2264 $450 stop; line 14 = 25% x line 12; homeowners line 17 = property taxes + special assessments; line 18 (renters' 14 / homeowners' 17) must be positive and must EXCEED line 19 = FAGI x Table 1 rate (0.035 to 0.065 by FAGI bracket). THE CREDIT (line 20) is a FLAT Table A/B lookup by FAGI. ALL of this arithmetic is COMPUTED by us.ny.it214 \u2014 feed it nyIt214Fagi, nyIt214TotalRent, nyIt214RentPercent (50/75/80/85/100 from the checkbox menu), nyIt214MonthsPaid, nyIt214HomeownerTaxes, isAge65OrOlder (any household member 65+ routes to Table A: $375/$330/$300/$260/$230/$200/$150), and nyIt214Eligible (Step 2 attestations only). Pass its answer into refundableCredits; never hand-compute any IT-214 line.\n\nIT-272 COLLEGE TUITION CREDIT (2025 IT-272-I): full-year NYS RESIDENTS only (part-year and nonresidents cannot claim it); qualified tuition expenses CAPPED at $10,000 per eligible student; credit = the LESSER of $400 or 4% of qualified expenses PER STUDENT (so the $400 max binds at $10,000 of expenses \u2014 confirm the 4% literal rate on the current worksheet before treating it as fixed across years); taxpayers may instead claim the college tuition ITEMIZED DEDUCTION on IT-201-D \u2014 mutually exclusive per student, elect whichever is larger.\n\nIT-229 REAL PROPERTY TAX RELIEF CREDIT: EXPIRED after TY2023 \u2014 DOES NOT EXIST for TY2025 or TY2026; do not encode or claim it. IT-214 (circuit breaker: FAGI \u2264 $18,000, property value \u2264 $85,000, rent \u2264 $450/month) is the live real-property-tax credit for TY2025.\n\n529 COLLEGE SAVINGS SUBTRACTION (Tax Law \xA7 612(c)(32)): up to $5,000 single/HOH/MFS, $10,000 MFJ, of contributions to a New York 529 account per year (recapture applies to nonqualified withdrawals of previously subtracted amounts). PENSION AND ANNUITY EXCLUSION (Tax Law \xA7 612(c)(3-a)): up to $20,000 EACH for taxpayer and spouse (no spousal sharing of unused exclusion) once age 59\xBD, for private/out-of-state pensions and annuities; NYS/local government and federal government pensions are excluded IN FULL regardless of age (a separate, unlimited subtraction, not capped at $20,000).\n\n\xA7 685(c) ESTIMATED TAX: no addition to tax if the amount owed after withholding is under $300 (de minimis); safe harbor is the LESSER of 90% of the current year's tax or 100% of the prior year's tax (110% of prior year's tax if the prior year's NYAGI exceeded $150,000).\n\nIT-196 ITEMIZED CHARITABLE LIMITATION (Tax Law \xA7 615, active through TY2029 per current law): for taxpayers with NYAGI over $1,000,000, the itemized charitable contribution deduction is limited to 50% of the federal amount; over $10,000,000 NYAGI, limited to 25% of the federal amount. UNRESOLVED (do NOT encode): the broader \xA7 615(f) general itemized-deduction phase-down for high-NYAGI filers could not be reconciled against the 2025 IT-196 instructions worksheet during this web-verification \u2014 pull the actual worksheet before modeling it; only the $1M/$10M charitable-specific limitation above is confirmed active.\n\nIT-225 MODIFICATIONS: \xA7 414(h) public-employee pickup = addition on IT-201 line 21 directly (code A-104, NOT via IT-225); NYC IRC \xA7 125 flexible-benefits = addition A-101; ALIMONY (NY did NOT conform to TCJA \xA7 11051): post-2018-instrument alimony PAID = subtraction S-136, alimony RECEIVED = addition A-119. S-120 \u2014 New York Higher Education Loan Program (HELP) loan interest (2025 IT-225-I p.6 subtraction-modifications chart and detail section, applicable to IT-201/IT-203 filers, not IT-204/IT-205), quoted verbatim: chart entry 'S-120 New York Higher Education Loan Program (HELP)'; detail text 'S-120: New York Higher Education Loan Program (HELP) \u2014 Enter any interest you paid in 2025 on loans made to you under HELP.' STATUTE CITE UNRESOLVED: the IT-225-I chart/detail text does not itself cite a Tax Law \xA7 612(c) paragraph number for S-120 (unlike some neighboring codes), and a web search of the codified \xA7 612 (nysenate.gov) did not turn up a matching paragraph as of this July 2026 verification \u2014 treat the CODE as confirmed-current for TY2025 via the 2025 IT-225-I form itself (the authority cited here), with the enabling paragraph number unresolved rather than guessed. Do not confuse with the separate, prospective, general student-loan-interest deduction tracked for a future Tax Law \xA7 612(c) paragraph (TY2026+, following 26 U.S.C. \xA7 221) \u2014 that is a different, not-yet-effective provision and does not affect the TY2025 S-120 answer.\n\nTY2026 ENACTED CHANGES (not encoded this pass \u2014 informational only, so a 2026 asOf refusal has useful context): a new subtraction for tips (up to $25,000, OBBBA conformity) begins TY2026; a new 'POWER credit' applies for TY2026 ONLY; a new REFUNDABLE CDCC under \xA7 606(c-2) replaces the nonrefundable IT-216 coupling for TY2026+ (55% of federal CDCC at NYAGI \u2264 $15,000, phasing down to a 4% floor, reduced further above $750,000 NYAGI); the Empire State Child Credit's $330 per-child (age 4-16) amount rises to $500 for TY2026-2027. Brackets, IT-272, the 529 subtraction, the household-credit tables, and IT-214 are UNCHANGED for TY2026 per the sources reviewed. Tax computation \u2192 us.ny.income_tax; NYC resident tax \u2192 us.ny.nyc_income_tax."
33357
34254
  },
33358
34255
  effectiveFrom: "2025-01-01",
33359
34256
  effectiveTo: "2026-01-01",
@@ -33485,19 +34382,19 @@ var nyRules = [
33485
34382
  },
33486
34383
  formula: (() => {
33487
34384
  const param22 = (name) => ({ kind: "param", name });
33488
- const fagi = fact33("nyIt214Fagi");
34385
+ const fagi = fact36("nyIt214Fagi");
33489
34386
  const adjustedRent = {
33490
34387
  kind: "mulDiv",
33491
- a: { kind: "max0", arg: fact33("nyIt214TotalRent") },
33492
- b: { kind: "mulInt", base: money30("1"), count: fact33("nyIt214RentPercent") },
33493
- c: money30("100"),
34388
+ a: { kind: "max0", arg: fact36("nyIt214TotalRent") },
34389
+ b: { kind: "mulInt", base: money33("1"), count: fact36("nyIt214RentPercent") },
34390
+ c: money33("100"),
33494
34391
  round: "half-up"
33495
34392
  };
33496
34393
  const avgMonthlyRent = {
33497
34394
  kind: "mulDiv",
33498
34395
  a: adjustedRent,
33499
- b: money30("1"),
33500
- c: { kind: "max", args: [money30("1"), { kind: "mulInt", base: money30("1"), count: fact33("nyIt214MonthsPaid") }] },
34396
+ b: money33("1"),
34397
+ c: { kind: "max", args: [money33("1"), { kind: "mulInt", base: money33("1"), count: fact36("nyIt214MonthsPaid") }] },
33501
34398
  round: "half-up"
33502
34399
  };
33503
34400
  const line14 = {
@@ -33508,15 +34405,15 @@ var nyRules = [
33508
34405
  };
33509
34406
  const line18 = {
33510
34407
  kind: "add",
33511
- args: [line14, { kind: "max0", arg: fact33("nyIt214HomeownerTaxes") }]
34408
+ args: [line14, { kind: "max0", arg: fact36("nyIt214HomeownerTaxes") }]
33512
34409
  };
33513
34410
  const table1Rate = (perMille, threshold2, next) => ({
33514
34411
  kind: "if",
33515
- cond: { kind: "cmp", op: "le", left: fagi, right: money30(threshold2) },
33516
- then: { kind: "mulDiv", a: fagi, b: money30(perMille), c: money30("1000"), round: "half-up" },
34412
+ cond: { kind: "cmp", op: "le", left: fagi, right: money33(threshold2) },
34413
+ then: { kind: "mulDiv", a: fagi, b: money33(perMille), c: money33("1000"), round: "half-up" },
33517
34414
  else: next
33518
34415
  });
33519
- const line19 = table1Rate("35", "300000", table1Rate("40", "500000", table1Rate("45", "700000", table1Rate("50", "900000", table1Rate("55", "1100000", table1Rate("60", "1400000", { kind: "mulDiv", a: fagi, b: money30("65"), c: money30("1000"), round: "half-up" }))))));
34416
+ const line19 = table1Rate("35", "300000", table1Rate("40", "500000", table1Rate("45", "700000", table1Rate("50", "900000", table1Rate("55", "1100000", table1Rate("60", "1400000", { kind: "mulDiv", a: fagi, b: money33("65"), c: money33("1000"), round: "half-up" }))))));
33520
34417
  const tableA = {
33521
34418
  kind: "if",
33522
34419
  cond: { kind: "cmp", op: "le", left: fagi, right: param22("tableAThreshold1") },
@@ -33545,7 +34442,7 @@ var nyRules = [
33545
34442
  kind: "if",
33546
34443
  cond: { kind: "cmp", op: "le", left: fagi, right: param22("fagiLimit") },
33547
34444
  then: param22("tableAValue7"),
33548
- else: money30("0")
34445
+ else: money33("0")
33549
34446
  }
33550
34447
  }
33551
34448
  }
@@ -33569,7 +34466,7 @@ var nyRules = [
33569
34466
  kind: "if",
33570
34467
  cond: { kind: "cmp", op: "le", left: fagi, right: param22("fagiLimit") },
33571
34468
  then: param22("tableBValue4"),
33572
- else: money30("0")
34469
+ else: money33("0")
33573
34470
  }
33574
34471
  }
33575
34472
  }
@@ -33579,21 +34476,21 @@ var nyRules = [
33579
34476
  cond: {
33580
34477
  kind: "or",
33581
34478
  args: [
33582
- { kind: "not", arg: fact33("nyIt214Eligible") },
34479
+ { kind: "not", arg: fact36("nyIt214Eligible") },
33583
34480
  { kind: "cmp", op: "gt", left: fagi, right: param22("fagiLimit") },
33584
34481
  // line 8 stop
33585
34482
  { kind: "cmp", op: "gt", left: avgMonthlyRent, right: param22("monthlyRentCap") },
33586
34483
  // line 13 stop
33587
- { kind: "cmp", op: "le", left: line18, right: money30("0") },
34484
+ { kind: "cmp", op: "le", left: line18, right: money33("0") },
33588
34485
  // line 18 stop
33589
34486
  { kind: "cmp", op: "ge", left: line19, right: line18 }
33590
34487
  // line 19 stop
33591
34488
  ]
33592
34489
  },
33593
- then: money30("0"),
34490
+ then: money33("0"),
33594
34491
  else: {
33595
34492
  kind: "if",
33596
- cond: fact33("isAge65OrOlder"),
34493
+ cond: fact36("isAge65OrOlder"),
33597
34494
  then: tableA,
33598
34495
  else: tableB
33599
34496
  }
@@ -33629,10 +34526,10 @@ var nyRules = [
33629
34526
  // 0.030 per $200 row
33630
34527
  },
33631
34528
  formula: (() => {
33632
- const stateCredit = { kind: "max0", arg: fact33("nyIt216StateCredit") };
33633
- const under4 = { kind: "max0", arg: fact33("nyIt216Under4Expenses") };
33634
- const total = { kind: "max0", arg: fact33("nyIt216TotalExpenses") };
33635
- const fagi = ruleRef28("us.federal.agi");
34529
+ const stateCredit = { kind: "max0", arg: fact36("nyIt216StateCredit") };
34530
+ const under4 = { kind: "max0", arg: fact36("nyIt216Under4Expenses") };
34531
+ const total = { kind: "max0", arg: fact36("nyIt216TotalExpenses") };
34532
+ const fagi = ruleRef30("us.federal.agi");
33636
34533
  const line5 = {
33637
34534
  kind: "min",
33638
34535
  args: [
@@ -33650,15 +34547,15 @@ var nyRules = [
33650
34547
  kind: "max0",
33651
34548
  arg: {
33652
34549
  kind: "sub",
33653
- left: money30("765"),
33654
- right: { kind: "mulInt", base: money30("30"), count: rowIndex }
34550
+ left: money33("765"),
34551
+ right: { kind: "mulInt", base: money33("30"), count: rowIndex }
33655
34552
  }
33656
34553
  };
33657
34554
  const line7 = (factor) => ({
33658
34555
  kind: "mulDiv",
33659
34556
  a: line5,
33660
34557
  b: factor,
33661
- c: money30("1000"),
34558
+ c: money33("1000"),
33662
34559
  round: "half-up"
33663
34560
  });
33664
34561
  return {
@@ -33667,18 +34564,18 @@ var nyRules = [
33667
34564
  kind: "or",
33668
34565
  args: [
33669
34566
  { kind: "cmp", op: "gt", left: fagi, right: { kind: "param", name: "fagiCutoff" } },
33670
- { kind: "cmp", op: "le", left: under4, right: money30("0") },
33671
- { kind: "cmp", op: "le", left: total, right: money30("0") }
34567
+ { kind: "cmp", op: "le", left: under4, right: money33("0") },
34568
+ { kind: "cmp", op: "le", left: total, right: money33("0") }
33672
34569
  ]
33673
34570
  },
33674
- then: money30("0"),
34571
+ then: money33("0"),
33675
34572
  else: {
33676
34573
  kind: "roundToDollar",
33677
34574
  mode: "half-up",
33678
34575
  value: {
33679
34576
  kind: "if",
33680
34577
  cond: { kind: "cmp", op: "le", left: fagi, right: { kind: "param", name: "fagiFlatCeiling" } },
33681
- then: line7(money30("750")),
34578
+ then: line7(money33("750")),
33682
34579
  else: line7(factorDeclining)
33683
34580
  }
33684
34581
  }
@@ -33707,7 +34604,7 @@ var nyRules = [
33707
34604
  // $107,650 (NYAGI-keyed)
33708
34605
  },
33709
34606
  formula: (() => {
33710
- const base = { kind: "max0", arg: fact33("stateTaxableIncome") };
34607
+ const base = { kind: "max0", arg: fact36("stateTaxableIncome") };
33711
34608
  const scheduleFor = (b, status) => {
33712
34609
  const rows = {
33713
34610
  single: [
@@ -33737,10 +34634,10 @@ var nyRules = [
33737
34634
  args: [
33738
34635
  {
33739
34636
  kind: "mulInt",
33740
- base: money30("5000"),
34637
+ base: money33("5000"),
33741
34638
  count: { kind: "stepUnits", value: base, unitCents: "5000", mode: "floor" }
33742
34639
  },
33743
- money30("2500")
34640
+ money33("2500")
33744
34641
  ]
33745
34642
  };
33746
34643
  const byStatus = (b) => ({
@@ -33770,15 +34667,15 @@ var nyRules = [
33770
34667
  kind: "and",
33771
34668
  args: [
33772
34669
  { kind: "cmp", op: "lt", left: base, right: { kind: "param", name: "tableThreshold" } },
33773
- { kind: "not", arg: fact33("useFormulaMethod") }
34670
+ { kind: "not", arg: fact36("useFormulaMethod") }
33774
34671
  ]
33775
34672
  },
33776
34673
  then: {
33777
34674
  // printed tax-table bottom row: $0-$13 -> $0 (the generic $50-midpoint
33778
34675
  // fiction would otherwise charge $1 on zero income)
33779
34676
  kind: "if",
33780
- cond: { kind: "cmp", op: "lt", left: base, right: money30("1300") },
33781
- then: money30("0"),
34677
+ cond: { kind: "cmp", op: "lt", left: base, right: money33("1300") },
34678
+ then: money33("0"),
33782
34679
  else: { kind: "roundToDollar", value: byStatus(mid50), mode: "half-up" }
33783
34680
  },
33784
34681
  else: { kind: "roundToDollar", value: byStatus(base), mode: "half-up" }
@@ -33805,7 +34702,7 @@ var nyRules = [
33805
34702
  // $65,000
33806
34703
  },
33807
34704
  formula: (() => {
33808
- const base = { kind: "max0", arg: fact33("stateTaxableIncome") };
34705
+ const base = { kind: "max0", arg: fact36("stateTaxableIncome") };
33809
34706
  const bracketsFor = (b, status) => {
33810
34707
  const rows = {
33811
34708
  single: [
@@ -33834,10 +34731,10 @@ var nyRules = [
33834
34731
  args: [
33835
34732
  {
33836
34733
  kind: "mulInt",
33837
- base: money30("5000"),
34734
+ base: money33("5000"),
33838
34735
  count: { kind: "stepUnits", value: base, unitCents: "5000", mode: "floor" }
33839
34736
  },
33840
- money30("2500")
34737
+ money33("2500")
33841
34738
  ]
33842
34739
  };
33843
34740
  const byStatus = (b) => ({
@@ -33880,15 +34777,225 @@ var nyRules = [
33880
34777
  },
33881
34778
  formula: {
33882
34779
  kind: "mulRate",
33883
- base: { kind: "max0", arg: fact33("nyYonkersBase") },
34780
+ base: { kind: "max0", arg: fact36("nyYonkersBase") },
33884
34781
  rate: { num: "1675", den: "10000" },
33885
34782
  round: "half-up"
33886
34783
  }
33887
34784
  }
33888
34785
  ];
33889
34786
 
34787
+ // ../corpus-us-federal/dist/rules/state-pa.js
34788
+ var max04 = (arg) => ({ kind: "max0", arg });
34789
+ var add2 = (...args) => ({ kind: "add", args });
34790
+ var sub2 = (left, right) => ({ kind: "sub", left, right });
34791
+ var rd2 = (value) => ({ kind: "roundToDollar", value, mode: "half-up" });
34792
+ var isMarried = {
34793
+ kind: "or",
34794
+ args: [
34795
+ { kind: "cmp", op: "eq", left: fact36("filingStatus"), right: { kind: "enum", value: "mfj" } },
34796
+ { kind: "cmp", op: "eq", left: fact36("filingStatus"), right: { kind: "enum", value: "mfs" } }
34797
+ ]
34798
+ };
34799
+ var spForgiveness = (netTax, ei, t100) => {
34800
+ let expr = money33("0");
34801
+ for (let k = 9; k >= 0; k--) {
34802
+ expr = {
34803
+ kind: "if",
34804
+ cond: {
34805
+ kind: "cmp",
34806
+ op: "le",
34807
+ left: ei,
34808
+ right: k === 0 ? t100 : add2(t100, money33(String(k * 25e3)))
34809
+ },
34810
+ then: {
34811
+ kind: "mulRate",
34812
+ base: netTax,
34813
+ rate: { num: String(100 - 10 * k), den: "100" },
34814
+ round: "half-up"
34815
+ },
34816
+ else: expr
34817
+ };
34818
+ }
34819
+ return expr;
34820
+ };
34821
+ var taxableIncomeRule = (version2, from, to) => ({
34822
+ id: "us.pa.taxable_income",
34823
+ version: version2,
34824
+ jurisdiction: "us.pa",
34825
+ title: "Pennsylvania total taxable income (PA-40 line 9) \u2014 positive class amounts only",
34826
+ citation: {
34827
+ source: "72 P.S. \xA7 7303(a)(1)-(8); 2025 PA-40 Instructions (rev 04-25) pp. 8-20",
34828
+ section: "72 P.S. \xA7 7303; PA-40 lines 1a-9",
34829
+ url: "https://www.pa.gov/content/dam/copapwp-pagov/en/revenue/documents/formsandpublications/formsforindividuals/pit/documents/2025/2025_pa-40in.pdf",
34830
+ excerpt: "Line 9 (2025 PA-40, verbatim): 'Add only the positive income amounts from Lines 1c, 2, 3, 4, 5, 6, 7 and 8. DO NOT ADD any losses.' Eight classes per \xA7 7303(a): compensation; interest; dividends (mutual-fund capital gains DISTRIBUTIONS are dividends, Line 3); net business/profession/farm; net gains from disposition of property; net rents/royalties/patents/copyrights; estate or trust income (cannot be negative); gambling and lottery winnings (noncash PA Lottery prizes exempt; cash prizes taxable, ticket costs for tickets bought on/after 1/1/2016 deductible from winnings). NO cross-class offset, NO carryforward/carryback, and NO spousal sharing: on a joint return, per class, one spouse's loss NEVER offsets the other's income (both-positive add; one-income-one-loss counts ONLY the income) \u2014 encoded as max0(taxpayer net) + max0(spouse net) for each loss-capable class (lines 4/5/6; PA-40 IN p.15, Mary & Ben examples). COMPENSATION: PA compensation is W-2 BOX 16, not Box 1 \u2014 401(k)/elective deferrals ARE taxable compensation; IRA contributions are NOT deductible; supply the Box16-Box1 difference via paCompensationAdjustment (default 0 assumes Box 16 = Box 1). Retirement income is NOT compensation when paid by an eligible employer plan after meeting the plan's age/years-of-service conditions AND retiring (code 7), and Social Security / RR / military pensions / Civil Service annuities / SERS / PSERS are always exempt; early distributions (codes 1-2) and pre-59\xBD IRA distributions are taxable compensation under the COST RECOVERY method (distribution minus previously-taxed contributions; PA has NO federal-style early-withdrawal exceptions) \u2014 cost-recovery basis math is agent-composed into the class facts, disclosed. Unemployment compensation, workers' comp, third-party sick pay, child support, alimony, and W-2 box-10 dependent care up to $5,000 are not taxable. APPROXIMATIONS (disclosed): compensation and its unreimbursed-expense offset (Sch UE, line 1b) are modeled at the return level, not per spouse \u2014 a spouse whose UE exceeds own compensation on a joint return would be overstated (rare; compose per-spouse and supply via paCompensationAdjustment if it matters). Reciprocal compensation states (IN, MD, NJ, OH, VA, WV) and part-year/nonresident apportionment are out of scope for this resident-return target."
34831
+ },
34832
+ effectiveFrom: from,
34833
+ effectiveTo: to,
34834
+ output: { type: "money" },
34835
+ formula: add2(
34836
+ // line 1c: net compensation (never negative on the printed form)
34837
+ max04(sub2(add2(fact36("wages"), fact36("paCompensationAdjustment")), fact36("paUnreimbursedBusinessExpenses"))),
34838
+ fact36("paInterestIncome"),
34839
+ // line 2 (gross class)
34840
+ fact36("paDividendIncome"),
34841
+ // line 3 (gross class)
34842
+ max04(fact36("paBusinessNetIncome")),
34843
+ // line 4, taxpayer
34844
+ max04(fact36("paSpouseBusinessNetIncome")),
34845
+ // line 4, spouse
34846
+ max04(fact36("paPropertyGainNet")),
34847
+ // line 5, taxpayer
34848
+ max04(fact36("paSpousePropertyGainNet")),
34849
+ // line 5, spouse
34850
+ max04(fact36("paRentRoyaltyNet")),
34851
+ // line 6, taxpayer
34852
+ max04(fact36("paSpouseRentRoyaltyNet")),
34853
+ // line 6, spouse
34854
+ fact36("paEstateTrustIncome"),
34855
+ // line 7 (cannot be negative)
34856
+ fact36("paGamblingWinnings")
34857
+ )
34858
+ });
34859
+ var otherDeductionsRule = (version2, from, to) => ({
34860
+ id: "us.pa.other_deductions",
34861
+ version: version2,
34862
+ jurisdiction: "us.pa",
34863
+ title: "Pennsylvania other deductions (PA-40 line 10, Schedule O) \u2014 the exhaustive five",
34864
+ citation: {
34865
+ source: "2025 PA-40 Instructions (rev 04-25) pp. 9, 20-21; 2025 PA Schedule O + instructions",
34866
+ section: "PA-40 line 10; Schedule O",
34867
+ url: "https://www.pa.gov/content/dam/copapwp-pagov/en/revenue/documents/formsandpublications/formsforindividuals/pit/documents/2025/2025_pa-40o.pdf",
34868
+ excerpt: "'PA law does not allow standard deductions, deductions for personal exemptions, itemized deductions, or deductions for personal expenses' (PA-40 IN p.9). Exactly FIVE line-10 deductions exist for 2025: (M) Medical Savings Account contributions and (H) Health Savings Account contributions, each limited to the amount 'allowed for federal income tax purposes'; (S) STUDENT LOAN INTEREST \u2014 NEW for 2025 \u2014 up to $2,500 per taxable year (no PA income phase-out; the federal studentLoanInterest fact is reused as the interest-paid input and capped here); (T) IRC \xA7 529 qualified tuition program contributions \u2014 maximum $19,000 PER BENEFICIARY, PER TAXPAYER-SPOUSE (2025; no deduction for 529-to-529 rollovers or beneficiary changes); (A) \xA7 529A PA ABLE contributions capped at the annual federal gift-tax exclusion ($19,000 for 2025). The per-beneficiary 529 cap and the ABLE cap CANNOT be enforced without per-beneficiary detail \u2014 the caller must supply already-capped totals (disclosed). Line 10 cannot exceed line 9; the Schedule O Section III PER-SPOUSE cap (each spouse limited to own line-9 income) is approximated at the return level (disclosed). Unreimbursed employee business expenses are a line-1b class expense (Schedule UE), never a line-10 deduction. The 2026 federal gift-exclusion amount for the ABLE cap must be re-verified at 2026 encoding."
34869
+ },
34870
+ effectiveFrom: from,
34871
+ effectiveTo: to,
34872
+ output: { type: "money" },
34873
+ parameters: {
34874
+ studentLoanInterestCap: { value: "250000", type: "money" },
34875
+ // $2,500 (2025 PA cap)
34876
+ per529BeneficiaryCap: { value: "1900000", type: "money" },
34877
+ // $19,000 (disclosure)
34878
+ ableGiftExclusionCap: { value: "1900000", type: "money" }
34879
+ // $19,000 (2025)
34880
+ },
34881
+ formula: {
34882
+ kind: "min",
34883
+ args: [
34884
+ add2({ kind: "min", args: [fact36("studentLoanInterest"), { kind: "param", name: "studentLoanInterestCap" }] }, fact36("pa529Contributions"), fact36("paAbleContributions"), fact36("paMsaHsaContributions")),
34885
+ ruleRef30("us.pa.taxable_income")
34886
+ ]
34887
+ }
34888
+ });
34889
+ var incomeTaxRule2 = (version2, from, to, yearLabel) => ({
34890
+ id: "us.pa.income_tax",
34891
+ version: version2,
34892
+ jurisdiction: "us.pa",
34893
+ title: `Pennsylvania income tax \u2014 3.07% flat on adjusted PA taxable income (PA-40 line 12, ${yearLabel})`,
34894
+ citation: {
34895
+ source: "72 P.S. \xA7 7302 (current as of Jan 1, 2026); 2025 PA-40 line 12",
34896
+ section: "72 P.S. \xA7 7302",
34897
+ url: "https://codes.findlaw.com/pa/title-72-ps-taxation-and-fiscal-affairs/pa-st-sect-72-7302/",
34898
+ excerpt: "\xA7 7302 imposes 'three and seven hundredths per cent' (3.07%) on residents' taxable income; PA-40 line 12: 'Multiply Line 11 by 3.07 percent (0.0307)', where line 11 = line 9 total PA taxable income minus line 10 other deductions. Rate verified unchanged for TY2026 (statute text current as of Jan 1, 2026; the Senate's proposed 2.8% cut was NOT enacted). This target computes GROSS tax (line 12); Tax Forgiveness (Schedule SP) is the separate us.pa.tax_forgiveness target, and the resident credit / WPTC / PA CDCC net against tax in the return composition. Local earned income tax (Act 32) is filed separately with local collectors and is out of scope. Rounding: PA-40 uses whole dollars (drop <$0.50, round up \u2265$0.50 = half-up). Filing threshold: PA gross taxable income over $33 (or any loss transaction) requires a return."
34899
+ },
34900
+ effectiveFrom: from,
34901
+ effectiveTo: to,
34902
+ output: { type: "money" },
34903
+ parameters: {
34904
+ filingThreshold: { value: "3300", type: "money" }
34905
+ // $33 gross-income filing trigger
34906
+ },
34907
+ formula: rd2({
34908
+ kind: "mulRate",
34909
+ base: max04(sub2(ruleRef30("us.pa.taxable_income"), ruleRef30("us.pa.other_deductions"))),
34910
+ rate: { num: "307", den: "10000" },
34911
+ round: "half-up"
34912
+ })
34913
+ });
34914
+ var paRules = [
34915
+ taxableIncomeRule(1, "2025-01-01", "2027-01-01"),
34916
+ otherDeductionsRule(1, "2025-01-01", "2027-01-01"),
34917
+ incomeTaxRule2(2, "2025-01-01", "2026-01-01", "TY2025"),
34918
+ incomeTaxRule2(3, "2026-01-01", "2027-01-01", "TY2026 \u2014 rate unchanged"),
34919
+ {
34920
+ id: "us.pa.tax_forgiveness",
34921
+ version: 1,
34922
+ jurisdiction: "us.pa",
34923
+ title: "Pennsylvania Tax Forgiveness credit (Schedule SP, 72 P.S. \xA7 7304) \u2014 PA-40 line 21",
34924
+ citation: {
34925
+ source: "72 P.S. \xA7 7304; 2025 PA Schedule SP + instructions (PA-40 SP IN 04-25); 2025 PA-40 Instructions p. 36-39 (eligibility income tables)",
34926
+ section: "Schedule SP Sections III-IV; PA-40 lines 19a-21",
34927
+ url: "https://www.pa.gov/content/dam/copapwp-pagov/en/revenue/documents/formsandpublications/formsforindividuals/pit/documents/2025/2025_pa-40sp.pdf",
34928
+ excerpt: "Printed 2025 eligibility-income tables (statutory, NOT indexed \u2014 structure verified against every printed cell): unmarried/separated/deceased base $6,500 at 100% forgiveness; MARRIED (even filing separately, using JOINT eligibility income) base $13,000; PLUS $9,500 per dependent child on every column; each additional $250 of eligibility income drops the forgiveness percentage 10 points (100/90/.../10%), so income beyond base+dependents+$2,250 gets nothing. Forgiveness applies to line 12 tax MINUS the line 22 resident credit (SP Section IV: the resident credit subtracts BEFORE forgiveness), never below zero. ELIGIBILITY INCOME = PA-40 line 9 taxable income PLUS nontaxable add-backs supplied via paEligibilityIncomeAddbacks: nontaxable interest/dividends/gains, alimony received, insurance proceeds and inheritances (incl. 1099-R code-4 box 1), gifts/awards/prizes (incl. noncash PA Lottery), non-PA income, nontaxable military pay (not combat), excluded home-sale gain, nontaxable educational assistance, and outside cash support (foster payments, spousal support from outside the household, cafeteria-plan benefits). NOT added back: Social Security/RRB, eligible retirement benefits, child support, military pensions, workers' comp, personal-injury damages, sick/disability pay. DEPENDENT = a CHILD (natural/adopted/step; grandchild of a grandparent; foster child of a foster parent \u2014 never aunts/uncles/unrelated persons) claimable as a federal dependent; adult qualifying children count. A claimant who is someone else's federal dependent is ineligible unless that person also qualifies (not modeled \u2014 disclose). 'Separated' (apart the last six months of the year or under written separation agreement) uses the unmarried table \u2014 supply via filingStatus; QSS/widowed is NOT married (Table 1); MFS IS married (Table 2, joint eligibility income \u2014 return-level approximation disclosed). Deceased-claimant annualization not modeled (refuse-by-disclosure). Defaults are CONSERVATIVE: paSpDependentChildren defaults to 0 (understates the threshold, never overstates the credit)."
34929
+ },
34930
+ effectiveFrom: "2025-01-01",
34931
+ effectiveTo: "2027-01-01",
34932
+ output: { type: "money" },
34933
+ parameters: {
34934
+ baseUnmarried: { value: "650000", type: "money" },
34935
+ // $6,500
34936
+ baseMarried: { value: "1300000", type: "money" },
34937
+ // $13,000
34938
+ perDependentChild: { value: "950000", type: "money" },
34939
+ // $9,500
34940
+ stepWidth: { value: "25000", type: "money" }
34941
+ // $250 per 10-point drop
34942
+ },
34943
+ formula: rd2(spForgiveness(
34944
+ // net tax: line 12 minus resident credit, floored at zero
34945
+ max04(sub2(ruleRef30("us.pa.income_tax"), fact36("paResidentCredit"))),
34946
+ // eligibility income: line 9 + add-backs
34947
+ add2(ruleRef30("us.pa.taxable_income"), fact36("paEligibilityIncomeAddbacks")),
34948
+ // 100% threshold: base + $9,500 x dependent children
34949
+ add2({ kind: "if", cond: isMarried, then: { kind: "param", name: "baseMarried" }, else: { kind: "param", name: "baseUnmarried" } }, { kind: "mulInt", base: { kind: "param", name: "perDependentChild" }, count: fact36("paSpDependentChildren") })
34950
+ ))
34951
+ },
34952
+ {
34953
+ id: "us.pa.cdcc",
34954
+ version: 1,
34955
+ jurisdiction: "us.pa",
34956
+ title: "Pennsylvania Child and Dependent Care Enhancement Tax Credit (Schedule DC) \u2014 100% of the federal \xA7 21 credit, refundable (TY2025)",
34957
+ citation: {
34958
+ source: "2025 PA Schedule DC + instructions (PA-40 DC IN 04-25); 2025 PA-40 Instructions pp. 22-23",
34959
+ section: "Schedule DC; PA-40 line 23",
34960
+ url: "https://www.pa.gov/content/dam/copapwp-pagov/en/revenue/documents/formsandpublications/formsforindividuals/pit/documents/2025/2025_pa-40dc.pdf",
34961
+ excerpt: "2025 Schedule DC: for PA residents receiving the federal IRC \xA7 21 credit, 'the credit is 100 percent of that amount' \u2014 the federal Form 2441 LINE 9a tentative credit (expenses capped $3,000 one / $6,000 two-or-more qualifying persons at the 35%\u219220% AGI slide), i.e. the PRE-liability-limit federal amount, because the PA credit is REFUNDABLE. Encoded as 100% of us.federal.cdcc.tentative (which already carries the \xA7 21 expense caps, earned-income limits, AGI percentage slide, and the MFS disallowance). The DC chart's printed caps ($1,050/$600 one dependent, $2,100/$1,200 two-plus at the $43,000 household-income break) are the federal formula's own outputs, not separate limits. Federal Form 2441 + Schedule 3 must be attached or DOR removes the credit (procedural, not modeled). TY2025 window only: the TY2026 federal \xA7 21 percentages change under OBBBA \xA7 70405 and PA's 100%-match treatment for 2026 was not yet verified at encoding \u2014 extend only after re-verification (refuses loudly for 2026 rather than guessing)."
34962
+ },
34963
+ effectiveFrom: "2025-01-01",
34964
+ effectiveTo: "2026-01-01",
34965
+ output: { type: "money" },
34966
+ formula: rd2(ruleRef30("us.federal.cdcc.tentative"))
34967
+ },
34968
+ {
34969
+ id: "us.pa.wptc",
34970
+ version: 1,
34971
+ jurisdiction: "us.pa",
34972
+ title: "Working Pennsylvanians Tax Credit \u2014 10% of the federal EITC, max $805, refundable (first PA state EITC, TY2025+)",
34973
+ citation: {
34974
+ source: "2025-26 Pennsylvania budget (signed Nov 12, 2025); PA DOR Working Pennsylvanians Tax Credit page; PA DOR press release Jan 27, 2026 (web-verified 2026-07-24)",
34975
+ section: "PA DOR WPTC guidance",
34976
+ url: "https://www.pa.gov/agencies/revenue/resources/tax-types-and-information/personal-income-tax/working-pennsylvanians-tax-credit",
34977
+ excerpt: "DOR: 'If you receive the federal EITC, you automatically qualify' \u2014 the credit is 10 percent of the federal Earned Income Tax Credit, maximum $805, REFUNDABLE, and 'the Department of Revenue will automatically calculate your WPTC' from the attached federal 1040. Effective beginning with the 2026 filing season (TY2025 returns, per the DOR's Jan 27, 2026 press release promoting the credit for the then-current season). NOTE: the printed 2025 PA-40 (rev 04-25, which predates the November 2025 budget) has NO WPTC line \u2014 DOR applies it in processing; composers should report it as a DOR-applied credit note. CAVEAT (disclosed): the statutory text's effective-tax-year language should be re-verified against the enrolled act when published; this encoding follows the agency's own filing-season guidance. The $805 cap can bind only via rounding in TY2025 (max federal EITC $8,046 \u2192 10% = $804.60)."
34978
+ },
34979
+ effectiveFrom: "2025-01-01",
34980
+ effectiveTo: "2027-01-01",
34981
+ output: { type: "money" },
34982
+ parameters: {
34983
+ pctOfFederal: { value: "10", type: "int" },
34984
+ cap: { value: "80500", type: "money" }
34985
+ // $805
34986
+ },
34987
+ formula: rd2({
34988
+ kind: "min",
34989
+ args: [
34990
+ { kind: "mulRate", base: ruleRef30("us.federal.eitc"), rate: { num: "10", den: "100" }, round: "half-up" },
34991
+ { kind: "param", name: "cap" }
34992
+ ]
34993
+ })
34994
+ }
34995
+ ];
34996
+
33890
34997
  // ../corpus-us-federal/dist/rules/state-other.js
33891
- var flatBase = { kind: "max0", arg: fact33("stateTaxableIncome") };
34998
+ var flatBase = { kind: "max0", arg: fact36("stateTaxableIncome") };
33892
34999
  function flatTax(args) {
33893
35000
  return {
33894
35001
  id: `us.${args.st}.income_tax`,
@@ -33969,7 +35076,7 @@ var otherStateRules = [
33969
35076
  output: { type: "money" },
33970
35077
  formula: {
33971
35078
  kind: "if",
33972
- cond: { kind: "cmp", op: "gt", left: fact33("longTermCapitalGains"), right: { kind: "money", cents: "0" } },
35079
+ cond: { kind: "cmp", op: "gt", left: fact36("longTermCapitalGains"), right: { kind: "money", cents: "0" } },
33973
35080
  then: {
33974
35081
  kind: "unsupported",
33975
35082
  reason: "Washington taxes no wages, but long-term capital gains are present \u2014 the RCW 82.87 7% capital-gains excise (indexed ~$270k deduction, real-estate/retirement exemptions) is not modeled; compute it separately and disclose"
@@ -33978,15 +35085,8 @@ var otherStateRules = [
33978
35085
  }
33979
35086
  },
33980
35087
  // ---- flat-rate states (TY2025, web-verified) ---------------------------
33981
- flatTax({
33982
- st: "pa",
33983
- name: "Pennsylvania",
33984
- ratePctTimes100: "307",
33985
- source: "72 Pa. Stat. \xA7 7302; Tax Foundation 2025 state rate survey (web-verified July 2026)",
33986
- section: "72 P.S. \xA7 7302",
33987
- url: "https://www.pa.gov/agencies/revenue.html",
33988
- excerpt: "Pennsylvania: 3.07% flat on PA taxable income. NO standard deduction and NO personal exemption. CAUTION: PA taxes EIGHT SEPARATE INCOME CLASSES (compensation, interest, dividends, net profits, gains, rents, estates, gambling) with NO netting of a loss in one class against income in another, and retirement income (401(k)/IRA distributions after retirement age, Social Security) is fully EXEMPT. PA Tax Forgiveness (Schedule SP) can eliminate tax at low incomes \u2014 not modeled, disclose. Compose stateTaxableIncome per the class rules before calling."
33989
- }),
35088
+ // (PA graduated to the deep pack: see state-pa.ts — us.pa.income_tax v2+
35089
+ // computes the class-netted base itself; the thin v1 is superseded.)
33990
35090
  flatTax({
33991
35091
  st: "in",
33992
35092
  name: "Indiana",
@@ -34175,19 +35275,20 @@ var stateParameterRules = [
34175
35275
  ...vaRules,
34176
35276
  ...caRules,
34177
35277
  ...nyRules,
35278
+ ...paRules,
34178
35279
  ...otherStateRules
34179
35280
  ];
34180
35281
 
34181
35282
  // ../corpus-us-federal/dist/rules/qbi.js
34182
- var fact34 = (factId) => ({ kind: "fact", factId });
34183
- var money31 = (cents) => ({ kind: "money", cents });
34184
- var ruleRef29 = (ruleId) => ({ kind: "rule", ruleId });
35283
+ var fact37 = (factId) => ({ kind: "fact", factId });
35284
+ var money34 = (cents) => ({ kind: "money", cents });
35285
+ var ruleRef31 = (ruleId) => ({ kind: "rule", ruleId });
34185
35286
  var param17 = (name) => ({ kind: "param", name });
34186
- var zero21 = money31("0");
35287
+ var zero24 = money34("0");
34187
35288
  var isStatus11 = (status) => ({
34188
35289
  kind: "cmp",
34189
35290
  op: "eq",
34190
- left: fact34("filingStatus"),
35291
+ left: fact37("filingStatus"),
34191
35292
  right: { kind: "enum", value: status }
34192
35293
  });
34193
35294
  var pctOf2 = (num, den, base) => ({
@@ -34207,31 +35308,31 @@ var qbi = {
34207
35308
  kind: "max0",
34208
35309
  arg: {
34209
35310
  kind: "sub",
34210
- left: fact34("selfEmploymentNetProfit"),
35311
+ left: fact37("selfEmploymentNetProfit"),
34211
35312
  right: {
34212
35313
  kind: "add",
34213
35314
  args: [
34214
- ruleRef29("us.federal.se_tax_half_deduction"),
34215
- ruleRef29("us.federal.sep_deduction"),
34216
- ruleRef29("us.federal.sehi_deduction")
35315
+ ruleRef31("us.federal.se_tax_half_deduction"),
35316
+ ruleRef31("us.federal.sep_deduction"),
35317
+ ruleRef31("us.federal.sehi_deduction")
34217
35318
  ]
34218
35319
  }
34219
35320
  }
34220
35321
  },
34221
- fact34("k1OrdinaryBusinessIncome")
35322
+ fact37("k1OrdinaryBusinessIncome")
34222
35323
  ]
34223
35324
  },
34224
- right: fact34("qbiLossOffset")
35325
+ right: fact37("qbiLossOffset")
34225
35326
  }
34226
35327
  };
34227
35328
  var hasBusinessIncome = {
34228
35329
  kind: "or",
34229
35330
  args: [
34230
- { kind: "cmp", op: "gt", left: fact34("selfEmploymentNetProfit"), right: zero21 },
34231
- { kind: "cmp", op: "gt", left: fact34("k1OrdinaryBusinessIncome"), right: zero21 }
35331
+ { kind: "cmp", op: "gt", left: fact37("selfEmploymentNetProfit"), right: zero24 },
35332
+ { kind: "cmp", op: "gt", left: fact37("k1OrdinaryBusinessIncome"), right: zero24 }
34232
35333
  ]
34233
35334
  };
34234
- var taxableBeforeQbi = ruleRef29("us.federal.taxable_income_before_qbi");
35335
+ var taxableBeforeQbi = ruleRef31("us.federal.taxable_income_before_qbi");
34235
35336
  var wageLimitOf = (w2, ubia) => ({
34236
35337
  kind: "max",
34237
35338
  args: [
@@ -34266,8 +35367,8 @@ function qbiRule(version2, effectiveFrom, effectiveTo, yearLabel, threshold2, ba
34266
35367
  };
34267
35368
  const excessCapped = { kind: "min", args: [excess, band] };
34268
35369
  const tentative = pctOf2("20", "100", qbi);
34269
- const w2 = fact34("qbiW2Wages");
34270
- const ubia = fact34("qbiUBIA");
35370
+ const w2 = fact37("qbiW2Wages");
35371
+ const ubia = fact37("qbiUBIA");
34271
35372
  const nonSstb = {
34272
35373
  kind: "if",
34273
35374
  cond: { kind: "cmp", op: "ge", left: excess, right: band },
@@ -34285,22 +35386,25 @@ function qbiRule(version2, effectiveFrom, effectiveTo, yearLabel, threshold2, ba
34285
35386
  const sstb = {
34286
35387
  kind: "if",
34287
35388
  cond: { kind: "cmp", op: "ge", left: excess, right: band },
34288
- then: zero21,
35389
+ then: zero24,
34289
35390
  else: phasedReduction(pctOf2("20", "100", scale(qbi)), wageLimitOf(scale(w2), scale(ubia)), excessCapped, band)
34290
35391
  };
34291
35392
  const businessPart = {
34292
35393
  kind: "if",
34293
- cond: { kind: "cmp", op: "eq", left: excess, right: zero21 },
35394
+ cond: { kind: "cmp", op: "eq", left: excess, right: zero24 },
34294
35395
  then: tentative,
34295
35396
  // at or below the threshold: no SSTB question, no limits
34296
- else: { kind: "if", cond: fact34("businessIsSSTB"), then: sstb, else: nonSstb }
35397
+ else: { kind: "if", cond: fact37("businessIsSSTB"), then: sstb, else: nonSstb }
34297
35398
  };
34298
35399
  const overall = pctOf2("20", "100", {
34299
35400
  kind: "max0",
34300
35401
  arg: {
34301
35402
  kind: "sub",
34302
35403
  left: taxableBeforeQbi,
34303
- right: { kind: "add", args: [fact34("longTermCapitalGains"), fact34("qualifiedDividends")] }
35404
+ right: {
35405
+ kind: "add",
35406
+ args: [ruleRef31("us.federal.schedule_d.preferential_lt_gain"), fact37("qualifiedDividends")]
35407
+ }
34304
35408
  }
34305
35409
  });
34306
35410
  const limited = { kind: "min", args: [businessPart, overall] };
@@ -34339,23 +35443,23 @@ function qbiRule(version2, effectiveFrom, effectiveTo, yearLabel, threshold2, ba
34339
35443
  kind: "if",
34340
35444
  cond: hasBusinessIncome,
34341
35445
  then: withFloor,
34342
- else: zero21
35446
+ else: zero24
34343
35447
  }
34344
35448
  };
34345
35449
  }
34346
35450
  var qbiRules = [
34347
35451
  qbiRule(
34348
- 7,
34349
- // v3 refused above the threshold; v5 added the W-2/UBIA + SSTB mechanics; v7 counts qualifiedDividends in the § 199A(a)(2)/(e)(3) overall limit
35452
+ 9,
35453
+ // v7 counted qualifiedDividends in the § 199A(a)(2)/(e)(3) overall limit; v9 takes the schedule_d netted LT gain there
34350
35454
  "2025-01-01",
34351
35455
  "2026-01-01",
34352
35456
  "2025",
34353
35457
  {
34354
35458
  kind: "if",
34355
35459
  cond: isStatus11("mfj"),
34356
- then: money31("39460000"),
35460
+ then: money34("39460000"),
34357
35461
  // $394,600
34358
- else: money31("19730000")
35462
+ else: money34("19730000")
34359
35463
  // $197,300 (incl. MFS)
34360
35464
  },
34361
35465
  "5000000",
@@ -34365,23 +35469,23 @@ var qbiRules = [
34365
35469
  false
34366
35470
  ),
34367
35471
  qbiRule(
34368
- 8,
34369
- // v4 refused above the threshold; v6 added the mechanics + OBBBA band; v8 counts qualifiedDividends in the overall limit
35472
+ 10,
35473
+ // v8 counted qualifiedDividends in the overall limit; v10 takes the schedule_d netted LT gain there
34370
35474
  "2026-01-01",
34371
35475
  "2027-01-01",
34372
35476
  "2026",
34373
35477
  {
34374
35478
  kind: "match",
34375
- on: fact34("filingStatus"),
35479
+ on: fact37("filingStatus"),
34376
35480
  cases: [
34377
- { when: "mfj", value: money31("40350000") },
35481
+ { when: "mfj", value: money34("40350000") },
34378
35482
  // $403,500
34379
- { when: "mfs", value: money31("20177500") },
35483
+ { when: "mfs", value: money34("20177500") },
34380
35484
  // $201,775
34381
- { when: "single", value: money31("20175000") },
35485
+ { when: "single", value: money34("20175000") },
34382
35486
  // $201,750
34383
- { when: "hoh", value: money31("20175000") },
34384
- { when: "qss", value: money31("20175000") }
35487
+ { when: "hoh", value: money34("20175000") },
35488
+ { when: "qss", value: money34("20175000") }
34385
35489
  ]
34386
35490
  },
34387
35491
  "7500000",
@@ -34393,12 +35497,12 @@ var qbiRules = [
34393
35497
  ];
34394
35498
 
34395
35499
  // ../corpus-us-federal/dist/rules/self-employment.js
34396
- var fact35 = (factId) => ({ kind: "fact", factId });
34397
- var money32 = (cents) => ({ kind: "money", cents });
34398
- var ruleRef30 = (ruleId) => ({ kind: "rule", ruleId });
35500
+ var fact38 = (factId) => ({ kind: "fact", factId });
35501
+ var money35 = (cents) => ({ kind: "money", cents });
35502
+ var ruleRef32 = (ruleId) => ({ kind: "rule", ruleId });
34399
35503
  var param18 = (name) => ({ kind: "param", name });
34400
- var zero22 = money32("0");
34401
- var seNetEarnings = ruleRef30("us.federal.se_net_earnings");
35504
+ var zero25 = money35("0");
35505
+ var seNetEarnings = ruleRef32("us.federal.se_net_earnings");
34402
35506
  function seTaxRule(version2, effectiveFrom, effectiveTo, wageBaseCents, yearLabel) {
34403
35507
  return {
34404
35508
  id: "us.federal.se_tax",
@@ -34422,10 +35526,10 @@ function seTaxRule(version2, effectiveFrom, effectiveTo, wageBaseCents, yearLabe
34422
35526
  formula: {
34423
35527
  kind: "if",
34424
35528
  cond: { kind: "cmp", op: "lt", left: seNetEarnings, right: param18("seFloor") },
34425
- then: zero22,
35529
+ then: zero25,
34426
35530
  else: {
34427
35531
  kind: "if",
34428
- cond: fact35("useFormulaMethod"),
35532
+ cond: fact38("useFormulaMethod"),
34429
35533
  then: { kind: "add", args: [oasdiComponent, medicareComponent] },
34430
35534
  else: {
34431
35535
  // printed Schedule SE: lines 10 and 11 each entered in whole dollars
@@ -34441,9 +35545,9 @@ function seTaxRule(version2, effectiveFrom, effectiveTo, wageBaseCents, yearLabe
34441
35545
  }
34442
35546
  var wagesForBase = {
34443
35547
  kind: "if",
34444
- cond: { kind: "cmp", op: "gt", left: fact35("socialSecurityWages"), right: zero22 },
34445
- then: fact35("socialSecurityWages"),
34446
- else: fact35("wages")
35548
+ cond: { kind: "cmp", op: "gt", left: fact38("socialSecurityWages"), right: zero25 },
35549
+ then: fact38("socialSecurityWages"),
35550
+ else: fact38("wages")
34447
35551
  };
34448
35552
  var oasdiComponent = {
34449
35553
  kind: "mulRate",
@@ -34487,7 +35591,7 @@ var selfEmploymentRules = [
34487
35591
  output: { type: "money" },
34488
35592
  formula: {
34489
35593
  kind: "mulRate",
34490
- base: fact35("selfEmploymentNetProfit"),
35594
+ base: fact38("selfEmploymentNetProfit"),
34491
35595
  rate: { num: "9235", den: "10000" },
34492
35596
  round: "half-up"
34493
35597
  }
@@ -34514,10 +35618,10 @@ var selfEmploymentRules = [
34514
35618
  output: { type: "money" },
34515
35619
  formula: {
34516
35620
  kind: "if",
34517
- cond: fact35("useFormulaMethod"),
35621
+ cond: fact38("useFormulaMethod"),
34518
35622
  then: {
34519
35623
  kind: "mulRate",
34520
- base: ruleRef30("us.federal.se_tax"),
35624
+ base: ruleRef32("us.federal.se_tax"),
34521
35625
  rate: { num: "50", den: "100" },
34522
35626
  round: "half-up"
34523
35627
  },
@@ -34525,7 +35629,7 @@ var selfEmploymentRules = [
34525
35629
  kind: "roundToDollar",
34526
35630
  value: {
34527
35631
  kind: "mulRate",
34528
- base: ruleRef30("us.federal.se_tax"),
35632
+ base: ruleRef32("us.federal.se_tax"),
34529
35633
  rate: { num: "50", den: "100" },
34530
35634
  round: "half-up"
34531
35635
  },
@@ -34536,17 +35640,17 @@ var selfEmploymentRules = [
34536
35640
  ];
34537
35641
 
34538
35642
  // ../corpus-us-federal/dist/rules/senior-deduction.js
34539
- var fact36 = (factId) => ({ kind: "fact", factId });
34540
- var money33 = (cents) => ({ kind: "money", cents });
34541
- var ruleRef31 = (ruleId) => ({ kind: "rule", ruleId });
35643
+ var fact39 = (factId) => ({ kind: "fact", factId });
35644
+ var money36 = (cents) => ({ kind: "money", cents });
35645
+ var ruleRef33 = (ruleId) => ({ kind: "rule", ruleId });
34542
35646
  var param19 = (name) => ({ kind: "param", name });
34543
35647
  var isStatus12 = (status) => ({
34544
35648
  kind: "cmp",
34545
35649
  op: "eq",
34546
- left: fact36("filingStatus"),
35650
+ left: fact39("filingStatus"),
34547
35651
  right: { kind: "enum", value: status }
34548
35652
  });
34549
- var zero23 = money33("0");
35653
+ var zero26 = money36("0");
34550
35654
  function perSeniorNet() {
34551
35655
  return {
34552
35656
  kind: "max0",
@@ -34559,8 +35663,8 @@ function perSeniorNet() {
34559
35663
  kind: "max0",
34560
35664
  arg: {
34561
35665
  kind: "sub",
34562
- left: ruleRef31("us.federal.agi"),
34563
- right: ruleRef31("us.federal.senior_deduction.magi_threshold")
35666
+ left: ruleRef33("us.federal.agi"),
35667
+ right: ruleRef33("us.federal.senior_deduction.magi_threshold")
34564
35668
  }
34565
35669
  },
34566
35670
  rate: { num: "6", den: "100" },
@@ -34597,17 +35701,17 @@ var seniorDeductionRules = [
34597
35701
  // § 151(d)(5)(C)(v): married taxpayers must file jointly — MFS gets $0.
34598
35702
  kind: "if",
34599
35703
  cond: isStatus12("mfs"),
34600
- then: zero23,
35704
+ then: zero26,
34601
35705
  else: {
34602
35706
  // Only compute (and only demand the threshold) when a senior exists.
34603
35707
  kind: "if",
34604
35708
  cond: {
34605
35709
  kind: "or",
34606
35710
  args: [
34607
- fact36("isAge65OrOlder"),
35711
+ fact39("isAge65OrOlder"),
34608
35712
  {
34609
35713
  kind: "and",
34610
- args: [isStatus12("mfj"), fact36("spouseIsAge65OrOlder")]
35714
+ args: [isStatus12("mfj"), fact39("spouseIsAge65OrOlder")]
34611
35715
  }
34612
35716
  ]
34613
35717
  },
@@ -34619,22 +35723,22 @@ var seniorDeductionRules = [
34619
35723
  args: [
34620
35724
  {
34621
35725
  kind: "if",
34622
- cond: fact36("isAge65OrOlder"),
35726
+ cond: fact39("isAge65OrOlder"),
34623
35727
  then: perSeniorNet(),
34624
- else: zero23
35728
+ else: zero26
34625
35729
  },
34626
35730
  {
34627
35731
  kind: "if",
34628
35732
  cond: {
34629
35733
  kind: "and",
34630
- args: [isStatus12("mfj"), fact36("spouseIsAge65OrOlder")]
35734
+ args: [isStatus12("mfj"), fact39("spouseIsAge65OrOlder")]
34631
35735
  },
34632
35736
  then: perSeniorNet(),
34633
- else: zero23
35737
+ else: zero26
34634
35738
  }
34635
35739
  ]
34636
35740
  },
34637
- else: zero23
35741
+ else: zero26
34638
35742
  }
34639
35743
  }
34640
35744
  },
@@ -34654,17 +35758,17 @@ var seniorDeductionRules = [
34654
35758
  output: { type: "money" },
34655
35759
  formula: {
34656
35760
  kind: "match",
34657
- on: fact36("filingStatus"),
35761
+ on: fact39("filingStatus"),
34658
35762
  cases: [
34659
35763
  // $150,000 only "in the case of a joint return"; QSS is not a joint
34660
35764
  // return. MFS is excluded from the deduction entirely (never reached).
34661
- { when: "mfj", value: money33("15000000") },
35765
+ { when: "mfj", value: money36("15000000") },
34662
35766
  // $150,000
34663
- { when: "single", value: money33("7500000") },
35767
+ { when: "single", value: money36("7500000") },
34664
35768
  // $75,000
34665
- { when: "hoh", value: money33("7500000") },
35769
+ { when: "hoh", value: money36("7500000") },
34666
35770
  // $75,000
34667
- { when: "qss", value: money33("7500000") }
35771
+ { when: "qss", value: money36("7500000") }
34668
35772
  // $75,000
34669
35773
  ]
34670
35774
  }
@@ -34672,9 +35776,9 @@ var seniorDeductionRules = [
34672
35776
  ];
34673
35777
 
34674
35778
  // ../corpus-us-federal/dist/rules/social-security.js
34675
- var fact37 = (factId) => ({ kind: "fact", factId });
34676
- var money34 = (cents) => ({ kind: "money", cents });
34677
- var ruleRef32 = (ruleId) => ({ kind: "rule", ruleId });
35779
+ var fact40 = (factId) => ({ kind: "fact", factId });
35780
+ var money37 = (cents) => ({ kind: "money", cents });
35781
+ var ruleRef34 = (ruleId) => ({ kind: "rule", ruleId });
34678
35782
  var param20 = (name) => ({ kind: "param", name });
34679
35783
  var pct2 = (num, base) => ({
34680
35784
  kind: "mulRate",
@@ -34689,51 +35793,53 @@ var magiBase = {
34689
35793
  left: {
34690
35794
  kind: "add",
34691
35795
  args: [
34692
- fact37("wages"),
34693
- fact37("taxableInterest"),
34694
- fact37("taxExemptInterest"),
35796
+ fact40("wages"),
35797
+ fact40("taxableInterest"),
35798
+ fact40("taxExemptInterest"),
34695
35799
  // § 86(b)(2)(B): "increased by the amount of interest… exempt from tax"
34696
- fact37("longTermCapitalGains"),
34697
- fact37("qualifiedDividends"),
34698
- fact37("shortTermCapitalGains"),
34699
- fact37("ordinaryDividends"),
34700
- fact37("selfEmploymentNetProfit"),
34701
- fact37("k1OrdinaryBusinessIncome"),
34702
- fact37("taxableIraDistributions"),
34703
- fact37("taxablePensionsAndAnnuities"),
34704
- fact37("unemploymentCompensation"),
34705
- fact37("otherOrdinaryIncome"),
34706
- fact37("scheduleENetIncome")
35800
+ ruleRef34("us.federal.schedule_d.preferential_lt_gain"),
35801
+ fact40("qualifiedDividends"),
35802
+ ruleRef34("us.federal.schedule_d.ordinary_st_gain"),
35803
+ fact40("ordinaryDividends"),
35804
+ fact40("selfEmploymentNetProfit"),
35805
+ fact40("k1OrdinaryBusinessIncome"),
35806
+ fact40("taxableIraDistributions"),
35807
+ fact40("taxablePensionsAndAnnuities"),
35808
+ ruleRef34("us.federal.pension.simplified_method_taxable"),
35809
+ fact40("unemploymentCompensation"),
35810
+ fact40("otherOrdinaryIncome"),
35811
+ fact40("scheduleENetIncome"),
35812
+ ruleRef34("us.federal.rental.net_income")
34707
35813
  ]
34708
35814
  },
34709
35815
  right: {
34710
35816
  kind: "add",
34711
35817
  args: [
34712
- ruleRef32("us.federal.capital_loss_ordinary_offset"),
34713
- fact37("nonpassiveScheduleELoss")
35818
+ ruleRef34("us.federal.capital_loss_ordinary_offset"),
35819
+ fact40("nonpassiveScheduleELoss")
34714
35820
  ]
34715
35821
  }
34716
35822
  },
34717
35823
  right: {
34718
35824
  kind: "add",
34719
35825
  args: [
34720
- ruleRef32("us.federal.above_the_line_adjustments"),
34721
- ruleRef32("us.federal.ira_deduction")
35826
+ ruleRef34("us.federal.above_the_line_adjustments"),
35827
+ ruleRef34("us.federal.ira_deduction")
34722
35828
  ]
34723
35829
  }
34724
35830
  };
34725
- var ss = fact37("socialSecurityBenefits");
35831
+ var ss = fact40("socialSecurityBenefits");
34726
35832
  var provisional = { kind: "add", args: [magiBase, pct2("50", ss)] };
34727
35833
  var isMfs3 = {
34728
35834
  kind: "cmp",
34729
35835
  op: "eq",
34730
- left: fact37("filingStatus"),
35836
+ left: fact40("filingStatus"),
34731
35837
  right: { kind: "enum", value: "mfs" }
34732
35838
  };
34733
35839
  var isMfj = {
34734
35840
  kind: "cmp",
34735
35841
  op: "eq",
34736
- left: fact37("filingStatus"),
35842
+ left: fact40("filingStatus"),
34737
35843
  right: { kind: "enum", value: "mfj" }
34738
35844
  };
34739
35845
  var byJoint = (joint, other) => ({
@@ -34745,8 +35851,8 @@ var byJoint = (joint, other) => ({
34745
35851
  var socialSecurityRules = [
34746
35852
  {
34747
35853
  id: "us.federal.taxable_social_security",
34748
- version: 7,
34749
- // v4 predated the nonpassive Schedule E loss fact; v5 predated the standalone qualifiedDividends fact; v6 predated shortTermCapitalGains/ordinaryDividends
35854
+ version: 8,
35855
+ // v7 read the raw gain facts; the § 86 income measure now takes the schedule_d netted results, the simplified-method pension taxable amount, and computed net rental income
34750
35856
  jurisdiction: "us.federal",
34751
35857
  title: "Taxable Social Security benefits (\xA7 86: the 0/50/85% worksheet)",
34752
35858
  citation: {
@@ -34774,8 +35880,8 @@ var socialSecurityRules = [
34774
35880
  },
34775
35881
  formula: {
34776
35882
  kind: "if",
34777
- cond: { kind: "cmp", op: "eq", left: ss, right: money34("0") },
34778
- then: money34("0"),
35883
+ cond: { kind: "cmp", op: "eq", left: ss, right: money37("0") },
35884
+ then: money37("0"),
34779
35885
  else: {
34780
35886
  kind: "if",
34781
35887
  cond: isMfs3,
@@ -34828,14 +35934,14 @@ var socialSecurityRules = [
34828
35934
  ];
34829
35935
 
34830
35936
  // ../corpus-us-federal/dist/rules/standard-deduction.js
34831
- var J24 = "us.federal";
34832
- var fact38 = (factId) => ({ kind: "fact", factId });
34833
- var money35 = (cents) => ({ kind: "money", cents });
34834
- var ruleRef33 = (ruleId) => ({ kind: "rule", ruleId });
35937
+ var J27 = "us.federal";
35938
+ var fact41 = (factId) => ({ kind: "fact", factId });
35939
+ var money38 = (cents) => ({ kind: "money", cents });
35940
+ var ruleRef35 = (ruleId) => ({ kind: "rule", ruleId });
34835
35941
  var isStatus13 = (status) => ({
34836
35942
  kind: "cmp",
34837
35943
  op: "eq",
34838
- left: fact38("filingStatus"),
35944
+ left: fact41("filingStatus"),
34839
35945
  right: { kind: "enum", value: status }
34840
35946
  });
34841
35947
  var standardDeductionRules = [
@@ -34843,7 +35949,7 @@ var standardDeductionRules = [
34843
35949
  id: "us.federal.standard_deduction.base_amount",
34844
35950
  version: 2,
34845
35951
  // v1 held pre-OBBBA Rev. Proc. 2024-40 amounts ($15,000/$30,000/$22,500)
34846
- jurisdiction: J24,
35952
+ jurisdiction: J27,
34847
35953
  title: "Basic standard deduction amount by filing status (TY2025, as amended by OBBBA)",
34848
35954
  citation: {
34849
35955
  source: "26 U.S.C. \xA7 63(c), as amended by Pub. L. 119-21 (OBBBA) \xA7 70102",
@@ -34856,17 +35962,17 @@ var standardDeductionRules = [
34856
35962
  output: { type: "money" },
34857
35963
  formula: {
34858
35964
  kind: "match",
34859
- on: fact38("filingStatus"),
35965
+ on: fact41("filingStatus"),
34860
35966
  cases: [
34861
- { when: "single", value: money35("1575000") },
35967
+ { when: "single", value: money38("1575000") },
34862
35968
  // $15,750
34863
- { when: "mfs", value: money35("1575000") },
35969
+ { when: "mfs", value: money38("1575000") },
34864
35970
  // $15,750 (same as single)
34865
- { when: "mfj", value: money35("3150000") },
35971
+ { when: "mfj", value: money38("3150000") },
34866
35972
  // $31,500
34867
- { when: "qss", value: money35("3150000") },
35973
+ { when: "qss", value: money38("3150000") },
34868
35974
  // $31,500 (§ 1(j)(2)(A): "…and Surviving Spouses")
34869
- { when: "hoh", value: money35("2362500") }
35975
+ { when: "hoh", value: money38("2362500") }
34870
35976
  // $23,625
34871
35977
  ]
34872
35978
  }
@@ -34874,7 +35980,7 @@ var standardDeductionRules = [
34874
35980
  {
34875
35981
  id: "us.federal.standard_deduction.base_amount",
34876
35982
  version: 3,
34877
- jurisdiction: J24,
35983
+ jurisdiction: J27,
34878
35984
  title: "Basic standard deduction amount by filing status (TY2026)",
34879
35985
  citation: {
34880
35986
  source: "26 U.S.C. \xA7 63(c); Rev. Proc. 2025-32",
@@ -34887,17 +35993,17 @@ var standardDeductionRules = [
34887
35993
  output: { type: "money" },
34888
35994
  formula: {
34889
35995
  kind: "match",
34890
- on: fact38("filingStatus"),
35996
+ on: fact41("filingStatus"),
34891
35997
  cases: [
34892
- { when: "single", value: money35("1610000") },
35998
+ { when: "single", value: money38("1610000") },
34893
35999
  // $16,100
34894
- { when: "mfs", value: money35("1610000") },
36000
+ { when: "mfs", value: money38("1610000") },
34895
36001
  // $16,100 ("single or married filing separately")
34896
- { when: "mfj", value: money35("3220000") },
36002
+ { when: "mfj", value: money38("3220000") },
34897
36003
  // $32,200
34898
- { when: "qss", value: money35("3220000") },
36004
+ { when: "qss", value: money38("3220000") },
34899
36005
  // $32,200 (surviving spouse)
34900
- { when: "hoh", value: money35("2415000") }
36006
+ { when: "hoh", value: money38("2415000") }
34901
36007
  // $24,150
34902
36008
  ]
34903
36009
  }
@@ -34905,7 +36011,7 @@ var standardDeductionRules = [
34905
36011
  {
34906
36012
  id: "us.federal.standard_deduction.base",
34907
36013
  version: 1,
34908
- jurisdiction: J24,
36014
+ jurisdiction: J27,
34909
36015
  title: "Basic standard deduction",
34910
36016
  citation: {
34911
36017
  source: "26 U.S.C. \xA7 63(c)(2)",
@@ -34916,12 +36022,12 @@ var standardDeductionRules = [
34916
36022
  effectiveFrom: "2025-01-01",
34917
36023
  // structural: open-ended
34918
36024
  output: { type: "money" },
34919
- formula: ruleRef33("us.federal.standard_deduction.base_amount")
36025
+ formula: ruleRef35("us.federal.standard_deduction.base_amount")
34920
36026
  },
34921
36027
  {
34922
36028
  id: "us.federal.standard_deduction.dependent_limit",
34923
36029
  version: 1,
34924
- jurisdiction: J24,
36030
+ jurisdiction: J27,
34925
36031
  title: "Limitation on basic standard deduction of a dependent",
34926
36032
  citation: {
34927
36033
  source: "26 U.S.C. \xA7 63(c)(5); Rev. Proc. 2024-40 \xA7 2.15(2); Rev. Proc. 2025-32 \xA7 4.14(2)",
@@ -34933,19 +36039,19 @@ var standardDeductionRules = [
34933
36039
  effectiveTo: "2027-01-01",
34934
36040
  // same dollar amounts apply in 2025 and 2026
34935
36041
  output: { type: "money" },
34936
- applicability: fact38("isClaimedAsDependent"),
36042
+ applicability: fact41("isClaimedAsDependent"),
34937
36043
  // min(normal base amount, max($1,350, earned income + $450))
34938
36044
  // simplified: earned income approximated as wages
34939
36045
  formula: {
34940
36046
  kind: "min",
34941
36047
  args: [
34942
- ruleRef33("us.federal.standard_deduction.base_amount"),
36048
+ ruleRef35("us.federal.standard_deduction.base_amount"),
34943
36049
  {
34944
36050
  kind: "max",
34945
36051
  args: [
34946
- money35("135000"),
36052
+ money38("135000"),
34947
36053
  // $1,350
34948
- { kind: "add", args: [fact38("wages"), money35("45000")] }
36054
+ { kind: "add", args: [fact41("wages"), money38("45000")] }
34949
36055
  // + $450
34950
36056
  ]
34951
36057
  }
@@ -34956,7 +36062,7 @@ var standardDeductionRules = [
34956
36062
  {
34957
36063
  id: "us.federal.standard_deduction.mfs_spouse_itemizes",
34958
36064
  version: 1,
34959
- jurisdiction: J24,
36065
+ jurisdiction: J27,
34960
36066
  title: "MFS standard deduction is zero when the spouse itemizes",
34961
36067
  citation: {
34962
36068
  source: "26 U.S.C. \xA7 63(c)(6)(A)",
@@ -34972,10 +36078,10 @@ var standardDeductionRules = [
34972
36078
  applicability: {
34973
36079
  kind: "if",
34974
36080
  cond: isStatus13("mfs"),
34975
- then: fact38("spouseItemizes"),
36081
+ then: fact41("spouseItemizes"),
34976
36082
  else: { kind: "bool", value: false }
34977
36083
  },
34978
- formula: money35("0"),
36084
+ formula: money38("0"),
34979
36085
  // § 63(c)(6) zeroes the ENTIRE standard deduction (basic + additional),
34980
36086
  // so this overrides the sum rule, not just the basic component.
34981
36087
  overrides: { ruleId: "us.federal.standard_deduction", priority: 10 }
@@ -34995,7 +36101,7 @@ var standardDeductionRules = [
34995
36101
  {
34996
36102
  id: "us.federal.standard_deduction",
34997
36103
  version: 1,
34998
- jurisdiction: J24,
36104
+ jurisdiction: J27,
34999
36105
  title: "Standard deduction",
35000
36106
  citation: {
35001
36107
  source: "26 U.S.C. \xA7 63(c)(1)",
@@ -35009,8 +36115,8 @@ var standardDeductionRules = [
35009
36115
  formula: {
35010
36116
  kind: "add",
35011
36117
  args: [
35012
- ruleRef33("us.federal.standard_deduction.base"),
35013
- ruleRef33("us.federal.standard_deduction.additional")
36118
+ ruleRef35("us.federal.standard_deduction.base"),
36119
+ ruleRef35("us.federal.standard_deduction.additional")
35014
36120
  ]
35015
36121
  }
35016
36122
  }
@@ -35018,7 +36124,7 @@ var standardDeductionRules = [
35018
36124
  function additionalRule(version2, effectiveFrom, effectiveTo, marriedCents, unmarriedCents, label) {
35019
36125
  const perStatusAmount = {
35020
36126
  kind: "match",
35021
- on: fact38("filingStatus"),
36127
+ on: fact41("filingStatus"),
35022
36128
  cases: [
35023
36129
  // the higher amount is for "unmarried AND not a surviving spouse" —
35024
36130
  // MFS and QSS get the married amount (read from both Rev. Procs.)
@@ -35033,12 +36139,12 @@ function additionalRule(version2, effectiveFrom, effectiveTo, marriedCents, unma
35033
36139
  kind: "if",
35034
36140
  cond,
35035
36141
  then: perStatusAmount,
35036
- else: money35("0")
36142
+ else: money38("0")
35037
36143
  });
35038
36144
  return {
35039
36145
  id: "us.federal.standard_deduction.additional",
35040
36146
  version: version2,
35041
- jurisdiction: J24,
36147
+ jurisdiction: J27,
35042
36148
  title: `Additional standard deduction for the aged and blind (TY${label.year})`,
35043
36149
  citation: {
35044
36150
  source: `26 U.S.C. \xA7 63(f); ${label.sourceRevProc}`,
@@ -35056,15 +36162,15 @@ function additionalRule(version2, effectiveFrom, effectiveTo, marriedCents, unma
35056
36162
  formula: {
35057
36163
  kind: "add",
35058
36164
  args: [
35059
- addIf(fact38("isAge65OrOlder")),
35060
- addIf(fact38("isBlind")),
36165
+ addIf(fact41("isAge65OrOlder")),
36166
+ addIf(fact41("isBlind")),
35061
36167
  addIf({
35062
36168
  kind: "and",
35063
- args: [isStatus13("mfj"), fact38("spouseIsAge65OrOlder")]
36169
+ args: [isStatus13("mfj"), fact41("spouseIsAge65OrOlder")]
35064
36170
  }),
35065
36171
  addIf({
35066
36172
  kind: "and",
35067
- args: [isStatus13("mfj"), fact38("spouseIsBlind")]
36173
+ args: [isStatus13("mfj"), fact41("spouseIsBlind")]
35068
36174
  })
35069
36175
  ]
35070
36176
  }
@@ -35072,12 +36178,12 @@ function additionalRule(version2, effectiveFrom, effectiveTo, marriedCents, unma
35072
36178
  }
35073
36179
 
35074
36180
  // ../corpus-us-federal/dist/rules/tips-eligibility.js
35075
- var fact39 = (factId) => ({ kind: "fact", factId });
36181
+ var fact42 = (factId) => ({ kind: "fact", factId });
35076
36182
  var boolLit = (value) => ({ kind: "bool", value });
35077
36183
  var isStatus14 = (status) => ({
35078
36184
  kind: "cmp",
35079
36185
  op: "eq",
35080
- left: fact39("filingStatus"),
36186
+ left: fact42("filingStatus"),
35081
36187
  right: { kind: "enum", value: status }
35082
36188
  });
35083
36189
  var WINDOW = { effectiveFrom: "2025-01-01", effectiveTo: "2029-01-01" };
@@ -35097,7 +36203,7 @@ var tipsEligibilityRules = [
35097
36203
  output: { type: "bool" },
35098
36204
  formula: {
35099
36205
  kind: "match",
35100
- on: fact39("occupation"),
36206
+ on: fact42("occupation"),
35101
36207
  cases: [
35102
36208
  ...TIPPED_OCCUPATIONS.map((o) => ({ when: o.slug, value: boolLit(true) })),
35103
36209
  { when: "other", value: boolLit(false) }
@@ -35124,23 +36230,23 @@ var tipsEligibilityRules = [
35124
36230
  args: [
35125
36231
  { kind: "not", arg: isStatus14("mfs") },
35126
36232
  { kind: "rule", ruleId: "us.federal.eligible.tips_occupation" },
35127
- fact39("tipsWereVoluntary"),
35128
- { kind: "not", arg: fact39("employerIsSSTB") }
36233
+ fact42("tipsWereVoluntary"),
36234
+ { kind: "not", arg: fact42("employerIsSSTB") }
35129
36235
  ]
35130
36236
  }
35131
36237
  }
35132
36238
  ];
35133
36239
 
35134
36240
  // ../corpus-us-federal/dist/rules/tips-overtime.js
35135
- var fact40 = (factId) => ({ kind: "fact", factId });
35136
- var money36 = (cents) => ({ kind: "money", cents });
35137
- var ruleRef34 = (ruleId) => ({ kind: "rule", ruleId });
36241
+ var fact43 = (factId) => ({ kind: "fact", factId });
36242
+ var money39 = (cents) => ({ kind: "money", cents });
36243
+ var ruleRef36 = (ruleId) => ({ kind: "rule", ruleId });
35138
36244
  var param21 = (name) => ({ kind: "param", name });
35139
- var zero24 = money36("0");
36245
+ var zero27 = money39("0");
35140
36246
  var isStatus15 = (status) => ({
35141
36247
  kind: "cmp",
35142
36248
  op: "eq",
35143
- left: fact40("filingStatus"),
36249
+ left: fact43("filingStatus"),
35144
36250
  right: { kind: "enum", value: status }
35145
36251
  });
35146
36252
  function cappedPhasedDeduction(qualifiedFactId, cap, ineligible = isStatus15("mfs")) {
@@ -35148,22 +36254,22 @@ function cappedPhasedDeduction(qualifiedFactId, cap, ineligible = isStatus15("mf
35148
36254
  kind: "if",
35149
36255
  // LAZY FIRST: with no qualified amount, no eligibility facts are ever
35150
36256
  // demanded — a taxpayer without tips is never asked their occupation
35151
- cond: { kind: "cmp", op: "gt", left: fact40(qualifiedFactId), right: zero24 },
36257
+ cond: { kind: "cmp", op: "gt", left: fact43(qualifiedFactId), right: zero27 },
35152
36258
  then: {
35153
36259
  kind: "if",
35154
36260
  cond: ineligible,
35155
- then: zero24,
36261
+ then: zero27,
35156
36262
  else: {
35157
36263
  kind: "max0",
35158
36264
  arg: {
35159
36265
  kind: "sub",
35160
36266
  left: {
35161
36267
  kind: "min",
35162
- args: [fact40(qualifiedFactId), cap]
36268
+ args: [fact43(qualifiedFactId), cap]
35163
36269
  },
35164
36270
  right: {
35165
36271
  kind: "mulInt",
35166
- base: money36("10000"),
36272
+ base: money39("10000"),
35167
36273
  // $100 per step
35168
36274
  count: {
35169
36275
  kind: "stepUnits",
@@ -35171,7 +36277,7 @@ function cappedPhasedDeduction(qualifiedFactId, cap, ineligible = isStatus15("mf
35171
36277
  kind: "max0",
35172
36278
  arg: {
35173
36279
  kind: "sub",
35174
- left: ruleRef34("us.federal.agi"),
36280
+ left: ruleRef36("us.federal.agi"),
35175
36281
  right: {
35176
36282
  kind: "if",
35177
36283
  cond: isStatus15("mfj"),
@@ -35189,7 +36295,7 @@ function cappedPhasedDeduction(qualifiedFactId, cap, ineligible = isStatus15("mf
35189
36295
  }
35190
36296
  }
35191
36297
  },
35192
- else: zero24
36298
+ else: zero27
35193
36299
  };
35194
36300
  }
35195
36301
  var WINDOW2 = { effectiveFrom: "2025-01-01", effectiveTo: "2029-01-01" };
@@ -35256,7 +36362,7 @@ var tipsOvertimeRules = [
35256
36362
  // ../corpus-us-federal/dist/index.js
35257
36363
  var corpusInput = {
35258
36364
  name: "@invaro/opentax-corpus-us-federal",
35259
- version: "0.35.0",
36365
+ version: "0.37.0",
35260
36366
  rules: [
35261
36367
  ...corporateRules,
35262
36368
  ...corporate1120Rules,
@@ -35270,6 +36376,9 @@ var corpusInput = {
35270
36376
  ...educationCreditRules,
35271
36377
  ...iraRules,
35272
36378
  ...ira8606Rules,
36379
+ ...scheduleDRules,
36380
+ ...rentalRules,
36381
+ ...pensionRules,
35273
36382
  ...capitalLossRules,
35274
36383
  ...kiddieRules,
35275
36384
  ...incomeRules,
@@ -35339,6 +36448,39 @@ var documentsShape = external_exports.object({
35339
36448
  box5: usd2.describe("net benefits (box 5) \u2014 includible amount computed by the \xA7 86 0/50/85% formula, not judged here"),
35340
36449
  box6: usd2.optional().describe("voluntary federal income tax withheld")
35341
36450
  }).strict()).optional().describe("one entry per SSA-1099 (or RRB-1099 tier-1 equivalent); box 5 sums into socialSecurityBenefits, box 6 into withholding"),
36451
+ f1099necs: external_exports.array(external_exports.object({
36452
+ box1: usd2.describe("nonemployee compensation (gross)"),
36453
+ box4: usd2.optional().describe("federal income tax withheld")
36454
+ }).strict()).optional().describe("one entry per 1099-NEC; box 1 sums into Schedule C gross receipts and nets against scheduleCExpensesTotal"),
36455
+ f1099ks: external_exports.array(external_exports.object({
36456
+ box1a: usd2.describe("gross amount of payment card / third-party network transactions"),
36457
+ box4: usd2.optional().describe("federal income tax withheld")
36458
+ }).strict()).optional().describe("one entry per 1099-K (business receipts); box 1a sums into Schedule C gross receipts alongside 1099-NECs"),
36459
+ scheduleCExpensesTotal: usd2.optional().describe("TOTAL Schedule C expenses to net against the 1099-NEC/1099-K gross receipts (one business; the taxpayer's own expense total, transcribed not judged)"),
36460
+ f1099ints: external_exports.array(external_exports.object({
36461
+ box1: usd2.optional().describe("interest income"),
36462
+ box3: usd2.optional().describe("U.S. Treasury/savings-bond interest \u2014 federally taxable (state-exempt; noted)"),
36463
+ box4: usd2.optional().describe("federal income tax withheld"),
36464
+ box8: usd2.optional().describe("tax-exempt interest (municipal) \u2014 feeds \xA7 86 provisional income, not taxable income")
36465
+ }).strict()).optional().describe("one entry per 1099-INT; boxes 1+3 sum into taxableInterest, box 8 into taxExemptInterest"),
36466
+ f1099divs: external_exports.array(external_exports.object({
36467
+ box1a: usd2.describe("total ordinary dividends (INCLUDES box 1b)"),
36468
+ box1b: usd2.optional().describe("qualified dividends (subset of 1a)"),
36469
+ box2a: usd2.optional().describe("total capital gain distributions \u2014 long-term by statute (\xA7 852(b)(3)(B)), summed into the LT bucket"),
36470
+ box4: usd2.optional().describe("federal income tax withheld")
36471
+ }).strict()).optional().describe("one entry per 1099-DIV; the compiler performs the 1a-minus-1b split the fact model expects and errors if 1b exceeds 1a"),
36472
+ f1099bs: external_exports.array(external_exports.object({
36473
+ proceeds: usd2.describe("box 1d proceeds"),
36474
+ basis: usd2.describe("box 1e cost or other basis"),
36475
+ term: external_exports.enum(["short", "long"]).describe("holding period per the form's Part I/II section"),
36476
+ washSaleLossDisallowed: usd2.optional().describe("box 1g \u2014 disallowed loss added back to this lot's result"),
36477
+ box4: usd2.optional().describe("federal income tax withheld")
36478
+ }).strict()).optional().describe("one entry per 1099-B lot or summary line; per-bucket ST/LT results are netted here and the engine's \xA7 1222 Schedule D netting rules do the cross-bucket math"),
36479
+ f1099gs: external_exports.array(external_exports.object({
36480
+ box1: usd2.optional().describe("unemployment compensation"),
36481
+ box2: usd2.optional().describe("state/local income tax refund \u2014 NOT auto-included (taxable only if it produced a benefit when itemizing the prior year, \xA7 111); surfaced as a note"),
36482
+ box4: usd2.optional().describe("federal income tax withheld")
36483
+ }).strict()).optional().describe("one entry per 1099-G; box 1 sums into unemploymentCompensation"),
35342
36484
  dependents: external_exports.array(external_exports.object({
35343
36485
  dateOfBirth: dob,
35344
36486
  relationship: external_exports.enum(["child_or_descendant", "sibling_or_descendant", "parent_or_other_relative"]).optional().describe("\xA7 152(c)(2) lineage: children/grandchildren and siblings/nieces/nephews can be QUALIFYING CHILDREN; a parent or other relative can only be a qualifying RELATIVE (ODC, never CTC/EIC-child). TRANSCRIBE the document's own label: son/daughter/grandchild/foster/sibling/niece \u2192 child_or_descendant or sibling_or_descendant; an explicit 'other' relationship on a source form \u2192 parent_or_other_relative (never default it back to child lineage). The child_or_descendant default is ONLY for documents that state a child relationship or say nothing at all."),
@@ -35376,7 +36518,7 @@ function compileDocuments(docs, asOf) {
35376
36518
  const ints = {};
35377
36519
  const bools = {};
35378
36520
  const notes = [];
35379
- const add = (id, c2) => {
36521
+ const add3 = (id, c2) => {
35380
36522
  sums[id] = (sums[id] ?? 0n) + c2;
35381
36523
  };
35382
36524
  const born65Cutoff = (dobStr) => ageAtYearEnd(dobStr, taxYear) >= 65;
@@ -35398,20 +36540,20 @@ function compileDocuments(docs, asOf) {
35398
36540
  }
35399
36541
  let w2Box1Cents = 0n;
35400
36542
  for (const [i, w] of (docs.w2s ?? []).entries()) {
35401
- add("wages", toCents(w.box1));
36543
+ add3("wages", toCents(w.box1));
35402
36544
  w2Box1Cents += toCents(w.box1);
35403
36545
  if (w.box2 !== void 0)
35404
- add("federalTaxWithheld", toCents(w.box2));
36546
+ add3("federalTaxWithheld", toCents(w.box2));
35405
36547
  if (w.box3 !== void 0 && !multiW2)
35406
- add("socialSecurityWages", toCents(w.box3));
36548
+ add3("socialSecurityWages", toCents(w.box3));
35407
36549
  if (w.box5 !== void 0) {
35408
36550
  const b5 = toCents(w.box5);
35409
- add("medicareWages", b5);
36551
+ add3("medicareWages", b5);
35410
36552
  if (w.box6 !== void 0 && b5 > 20000000n) {
35411
36553
  const regular = (b5 * 145n + 5000n) / 10000n;
35412
36554
  const excess = toCents(w.box6) - regular;
35413
36555
  if (excess > 0n) {
35414
- add("federalTaxWithheld", excess);
36556
+ add3("federalTaxWithheld", excess);
35415
36557
  notes.push(`W-2 #${i + 1}: Form 8959 Part IV \u2014 box 6 exceeds 1.45% of box 5 by $${dollars2(excess)}; added to withholding`);
35416
36558
  }
35417
36559
  }
@@ -35423,46 +36565,133 @@ function compileDocuments(docs, asOf) {
35423
36565
  const PENALTY_EXEMPT_CODES = /* @__PURE__ */ new Set(["2", "3", "4", "7", "G", "H", "Q", "T", "C"]);
35424
36566
  for (const [i, r] of (docs.f1099rs ?? []).entries()) {
35425
36567
  if (r.box4 !== void 0)
35426
- add("federalTaxWithheld", toCents(r.box4));
36568
+ add3("federalTaxWithheld", toCents(r.box4));
35427
36569
  const taxable3 = toCents(r.box2a);
35428
36570
  if (r.rolledOver || r.box7.toUpperCase().includes("G")) {
35429
36571
  notes.push(`1099-R #${i + 1}: treated as ROLLOVER (${r.rolledOver ? "interview-confirmed" : "code G"}) \u2014 gross on 4a/5a only, $0 taxable`);
35430
36572
  continue;
35431
36573
  }
35432
36574
  if (r.disabilityBeforeRetirementAge) {
35433
- add("wages", taxable3);
35434
- add("scheduleRDisabilityIncome", taxable3);
36575
+ add3("wages", taxable3);
36576
+ add3("scheduleRDisabilityIncome", taxable3);
35435
36577
  notes.push(`1099-R #${i + 1}: code-3 disability before minimum retirement age \u2014 $${dollars2(taxable3)} reported as WAGES (Pub. 525, Form 1040 line 1h \u2014 NOT line 1a, which is W-2 box 1 only) and counted as \xA7 22 disability income`);
35436
36578
  continue;
35437
36579
  }
35438
36580
  if (r.iraSepSimple)
35439
- add("taxableIraDistributions", taxable3);
36581
+ add3("taxableIraDistributions", taxable3);
35440
36582
  else
35441
- add("taxablePensionsAndAnnuities", taxable3);
36583
+ add3("taxablePensionsAndAnnuities", taxable3);
35442
36584
  const dobStr = r.recipient === "spouse" ? docs.spouseDateOfBirth : docs.taxpayerDateOfBirth;
35443
36585
  const code = r.box7.toUpperCase();
35444
36586
  if ([...code].some((c2) => c2 === "1")) {
35445
36587
  if (dobStr && ageYearsExact(dobStr, taxYear) >= 59.5) {
35446
36588
  notes.push(`1099-R #${i + 1}: payer code 1 (early) but the ${r.recipient ?? "taxpayer"} is over 59\xBD \u2014 no \xA7 72(t) penalty (age controls, not the box code)`);
35447
36589
  } else if ([...code].every((c2) => !PENALTY_EXEMPT_CODES.has(c2))) {
35448
- add("earlyDistributionSubjectToPenalty", taxable3);
36590
+ add3("earlyDistributionSubjectToPenalty", taxable3);
35449
36591
  notes.push(`1099-R #${i + 1}: code 1 and no age exception established \u2014 $${dollars2(taxable3)} subject to the 10% \xA7 72(t) tax`);
35450
36592
  }
35451
36593
  }
35452
36594
  }
35453
36595
  for (const s of docs.ssa1099s ?? []) {
35454
- add("socialSecurityBenefits", toCents(s.box5));
36596
+ add3("socialSecurityBenefits", toCents(s.box5));
35455
36597
  if (s.box6 !== void 0)
35456
- add("federalTaxWithheld", toCents(s.box6));
36598
+ add3("federalTaxWithheld", toCents(s.box6));
36599
+ }
36600
+ let seGross = 0n;
36601
+ for (const n of docs.f1099necs ?? []) {
36602
+ seGross += toCents(n.box1);
36603
+ if (n.box4 !== void 0)
36604
+ add3("federalTaxWithheld", toCents(n.box4));
36605
+ }
36606
+ for (const k of docs.f1099ks ?? []) {
36607
+ seGross += toCents(k.box1a);
36608
+ if (k.box4 !== void 0)
36609
+ add3("federalTaxWithheld", toCents(k.box4));
36610
+ }
36611
+ if (seGross > 0n || docs.scheduleCExpensesTotal !== void 0) {
36612
+ const expenses = docs.scheduleCExpensesTotal !== void 0 ? toCents(docs.scheduleCExpensesTotal) : 0n;
36613
+ const net = seGross - expenses;
36614
+ if (net >= 0n) {
36615
+ if (net > 0n)
36616
+ add3("selfEmploymentNetProfit", net);
36617
+ notes.push(`Schedule C: $${dollars2(seGross)} gross (1099-NEC/K) \u2212 $${dollars2(expenses)} expenses = $${dollars2(net)} net profit \u2192 SE tax + QBI machinery engage on it`);
36618
+ } else {
36619
+ add3("scheduleCNetLoss", -net);
36620
+ notes.push(`Schedule C: expenses exceed 1099-NEC/K gross by $${dollars2(-net)} \u2014 recorded as scheduleCNetLoss`);
36621
+ }
36622
+ }
36623
+ for (const [i, t] of (docs.f1099ints ?? []).entries()) {
36624
+ if (t.box1 !== void 0)
36625
+ add3("taxableInterest", toCents(t.box1));
36626
+ if (t.box3 !== void 0 && toCents(t.box3) > 0n) {
36627
+ add3("taxableInterest", toCents(t.box3));
36628
+ notes.push(`1099-INT #${i + 1}: box 3 Treasury interest $${dollars2(toCents(t.box3))} is federally taxable (state returns exempt it \u2014 the state composers handle that subtraction)`);
36629
+ }
36630
+ if (t.box8 !== void 0)
36631
+ add3("taxExemptInterest", toCents(t.box8));
36632
+ if (t.box4 !== void 0)
36633
+ add3("federalTaxWithheld", toCents(t.box4));
36634
+ }
36635
+ for (const [i, d3] of (docs.f1099divs ?? []).entries()) {
36636
+ const total = toCents(d3.box1a);
36637
+ const qualified2 = d3.box1b !== void 0 ? toCents(d3.box1b) : 0n;
36638
+ if (qualified2 > total) {
36639
+ throw new Error(`1099-DIV #${i + 1}: box 1b (qualified, $${dollars2(qualified2)}) exceeds box 1a (total, $${dollars2(total)}) \u2014 transcription error`);
36640
+ }
36641
+ if (qualified2 > 0n)
36642
+ add3("qualifiedDividends", qualified2);
36643
+ if (total - qualified2 > 0n)
36644
+ add3("ordinaryDividends", total - qualified2);
36645
+ if (d3.box2a !== void 0 && toCents(d3.box2a) > 0n) {
36646
+ add3("__ltProceeds", toCents(d3.box2a));
36647
+ notes.push(`1099-DIV #${i + 1}: box 2a capital gain distributions $${dollars2(toCents(d3.box2a))} \u2014 long-term by statute (\xA7 852(b)(3)(B)), joined to the Schedule D long-term bucket`);
36648
+ }
36649
+ if (d3.box4 !== void 0)
36650
+ add3("federalTaxWithheld", toCents(d3.box4));
36651
+ }
36652
+ let stNet = 0n;
36653
+ let ltNet = sums.__ltProceeds ?? 0n;
36654
+ delete sums.__ltProceeds;
36655
+ let sawB = (docs.f1099bs ?? []).length > 0 || ltNet !== 0n;
36656
+ for (const b of docs.f1099bs ?? []) {
36657
+ let lot = toCents(b.proceeds) - toCents(b.basis);
36658
+ if (b.washSaleLossDisallowed !== void 0)
36659
+ lot += toCents(b.washSaleLossDisallowed);
36660
+ if (b.term === "short")
36661
+ stNet += lot;
36662
+ else
36663
+ ltNet += lot;
36664
+ if (b.box4 !== void 0)
36665
+ add3("federalTaxWithheld", toCents(b.box4));
36666
+ }
36667
+ if (sawB) {
36668
+ if (stNet > 0n)
36669
+ add3("shortTermCapitalGains", stNet);
36670
+ else if (stNet < 0n)
36671
+ add3("shortTermCapitalLoss", -stNet);
36672
+ if (ltNet > 0n)
36673
+ add3("longTermCapitalGains", ltNet);
36674
+ else if (ltNet < 0n)
36675
+ add3("longTermCapitalLoss", -ltNet);
36676
+ notes.push(`Schedule D buckets from 1099-B/DIV: short-term net $${dollars2(stNet)}, long-term net $${dollars2(ltNet)} \u2014 the \xA7 1222 netting rules combine them (character preserved, \xA7 1211(b) caps any overall loss)`);
36677
+ }
36678
+ for (const [i, g] of (docs.f1099gs ?? []).entries()) {
36679
+ if (g.box1 !== void 0)
36680
+ add3("unemploymentCompensation", toCents(g.box1));
36681
+ if (g.box4 !== void 0)
36682
+ add3("federalTaxWithheld", toCents(g.box4));
36683
+ if (g.box2 !== void 0 && toCents(g.box2) > 0n) {
36684
+ notes.push(`1099-G #${i + 1}: box 2 state refund $${dollars2(toCents(g.box2))} NOT auto-included \u2014 taxable only to the extent the prior-year SALT deduction produced a benefit (\xA7 111); add it to otherOrdinaryIncome yourself if it did`);
36685
+ }
35457
36686
  }
35458
36687
  for (const dep of docs.dependents ?? []) {
35459
36688
  const age = ageAtYearEnd(dep.dateOfBirth, taxYear);
35460
36689
  const disabled = dep.isPermanentlyDisabled === true;
35461
36690
  const student = dep.isFullTimeStudent === true;
35462
36691
  const ssn = dep.hasRequiredSSN !== false;
35463
- const months = dep.monthsLivedWithTaxpayer ?? 12;
36692
+ const months2 = dep.monthsLivedWithTaxpayer ?? 12;
35464
36693
  const childLineage = (dep.relationship ?? "child_or_descendant") !== "parent_or_other_relative";
35465
- if (months <= 6 && !disabled) {
36694
+ if (months2 <= 6 && !disabled) {
35466
36695
  notes.push(`dependent (b. ${dep.dateOfBirth}): lived \u22646 months \u2014 residency test not met, excluded (divorced-parents release etc. via determine_dependent)`);
35467
36696
  continue;
35468
36697
  }
@@ -35497,14 +36726,14 @@ function compileDocuments(docs, asOf) {
35497
36726
 
35498
36727
  // dist/schema.js
35499
36728
  var corpus = getCorpus();
35500
- var money37 = external_exports.union([external_exports.number(), external_exports.string()]).describe('dollars, e.g. 50000 or "1234.56"');
36729
+ var money40 = external_exports.union([external_exports.number(), external_exports.string()]).describe('dollars, e.g. 50000 or "1234.56"');
35501
36730
  function zodForFact(f) {
35502
36731
  if (f.id === "occupation") {
35503
36732
  return external_exports.string().describe('tipped occupation, fuzzy-matched to the Treasury Tipped Occupation list (Treas. Reg. \xA7 1.224-1) \u2014 e.g. "bartender", "nanny" \u2014 or "other" if genuinely unlisted. Only needed when qualifiedTips > 0.');
35504
36733
  }
35505
36734
  switch (f.type) {
35506
36735
  case "money":
35507
- return money37.describe(f.description);
36736
+ return money40.describe(f.description);
35508
36737
  case "bool":
35509
36738
  return external_exports.boolean().describe(f.description);
35510
36739
  case "int":
@@ -35569,6 +36798,11 @@ var INDIVIDUAL_GROUPS = {
35569
36798
  "shortTermCapitalGains",
35570
36799
  "ordinaryDividends",
35571
36800
  "netCapitalLoss",
36801
+ "shortTermCapitalLoss",
36802
+ "longTermCapitalLoss",
36803
+ "rentalDepreciableBasis",
36804
+ "rentalPlacedInServiceMonth",
36805
+ "rentalIncomeBeforeDepreciation",
35572
36806
  "unemploymentCompensation",
35573
36807
  "otherOrdinaryIncome",
35574
36808
  "socialSecurityWages",
@@ -35590,6 +36824,14 @@ var INDIVIDUAL_GROUPS = {
35590
36824
  "taxableIraDistributions",
35591
36825
  "taxablePensionsAndAnnuities",
35592
36826
  "earlyDistributionSubjectToPenalty",
36827
+ // § 72(d) Simplified Method — the engine computes the taxable portion
36828
+ "pensionGrossPayments",
36829
+ "pensionCostBasis",
36830
+ "pensionAgeAtStart",
36831
+ "pensionIsJointAndSurvivor",
36832
+ "pensionCombinedAgesAtStart",
36833
+ "pensionMonthsThisYear",
36834
+ "pensionBasisPreviouslyRecovered",
35593
36835
  // Form 8606 basis pro-rata — standalone target us.federal.ira8606.*
35594
36836
  "ira8606Basis",
35595
36837
  "ira8606NondeductibleContributions",
@@ -35705,7 +36947,25 @@ var INDIVIDUAL_GROUPS = {
35705
36947
  "nyIt214HomeownerTaxes",
35706
36948
  "nyIt216StateCredit",
35707
36949
  "nyIt216Under4Expenses",
35708
- "nyIt216TotalExpenses"
36950
+ "nyIt216TotalExpenses",
36951
+ "paCompensationAdjustment",
36952
+ "paUnreimbursedBusinessExpenses",
36953
+ "paInterestIncome",
36954
+ "paDividendIncome",
36955
+ "paBusinessNetIncome",
36956
+ "paSpouseBusinessNetIncome",
36957
+ "paPropertyGainNet",
36958
+ "paSpousePropertyGainNet",
36959
+ "paRentRoyaltyNet",
36960
+ "paSpouseRentRoyaltyNet",
36961
+ "paEstateTrustIncome",
36962
+ "paGamblingWinnings",
36963
+ "pa529Contributions",
36964
+ "paAbleContributions",
36965
+ "paMsaHsaContributions",
36966
+ "paSpDependentChildren",
36967
+ "paEligibilityIncomeAddbacks",
36968
+ "paResidentCredit"
35709
36969
  ],
35710
36970
  household_employer: ["householdEmployeeCashWages", "householdFutaTestMet"],
35711
36971
  payments_estimates: [
@@ -35995,7 +37255,7 @@ function createServer() {
35995
37255
  }
35996
37256
  });
35997
37257
  server2.registerTool("compute_state_return", {
35998
- description: "Compose a STATE return's printed-form line set deterministically (2025 IL-1040 / VA 760 / CA 540 / NY IT-201) \u2014 correct line NUMBERS from the printed forms and whole-dollar rounding, with the state tax computed by the oracle targets internally. Workflow: run compute_return first for the federal substrate, compute any state-specific components the citations describe (additions, subtractions, credits without targets \u2014 disclose each), then call this ONCE and report its line set VERBATIM. Never hand-assemble state line numbers: transposed lines on correct dollars are the dominant state error mode. ALWAYS pass taxableSocialSecurity and unemploymentCompensation when nonzero (VA/CA/NY subtractions are applied by the composer). ALWAYS transcribe the intake's state-specific block (e.g. ca_tax_return.ca_form540_schca: AB 5 employee-classification additions; va_sch_a fields; county/use-tax questions) \u2014 those fields drive composer inputs. For VA MFJ, pass vaYourVagi/vaSpouseVagi (the separate-VAGI worksheet) so the composer can run the Spouse Tax Adjustment worksheet itself.",
37258
+ description: "Compose a STATE return's printed-form line set deterministically (2025 IL-1040 / VA 760 / CA 540 / NY IT-201 / PA-40) \u2014 correct line NUMBERS from the printed forms and whole-dollar rounding, with the state tax computed by the oracle targets internally. PA is CLASS-BASED: transcribe the pa* class fields (Box 16 compensation, per-spouse loss classes) \u2014 federalAGI is NOT the PA base; the composer runs the class netting, Schedule O, and Tax Forgiveness targets itself, and reports the WPTC as a note (no printed line). Workflow: run compute_return first for the federal substrate, compute any state-specific components the citations describe (additions, subtractions, credits without targets \u2014 disclose each), then call this ONCE and report its line set VERBATIM. Never hand-assemble state line numbers: transposed lines on correct dollars are the dominant state error mode. ALWAYS pass taxableSocialSecurity and unemploymentCompensation when nonzero (VA/CA/NY subtractions are applied by the composer). ALWAYS transcribe the intake's state-specific block (e.g. ca_tax_return.ca_form540_schca: AB 5 employee-classification additions; va_sch_a fields; county/use-tax questions) \u2014 those fields drive composer inputs. For VA MFJ, pass vaYourVagi/vaSpouseVagi (the separate-VAGI worksheet) so the composer can run the Spouse Tax Adjustment worksheet itself.",
35999
37259
  inputSchema: external_exports.object({ ...stateReturnShape, asOf: external_exports.string().describe("year-end date, e.g. 2025-12-31 \u2014 REQUIRED"), filingJoint: external_exports.boolean().optional(), filingHoh: external_exports.boolean().optional(), filingHohOrQss: external_exports.boolean().optional() }).strict()
36000
37260
  }, async (args) => {
36001
37261
  try {
@@ -36013,7 +37273,7 @@ function createServer() {
36013
37273
  }
36014
37274
  });
36015
37275
  server2.registerTool("compute_return", {
36016
- description: "Compute the COMPLETE scored Form 1040 line set in one call \u2014 the exact 17 lines TaxCalcBench-style evaluations grade (1a, 9, 10, 11, 12e, 15, 16, 17 AMT, 19, 22, 23, 24, 25d, 27a, 28, 32, 33, 34/37), each whole-dollar rounded by the engine. Takes the SAME input as calculate_tax (prefer the documents block: transcribe W-2/1099-R/SSA-1099 boxes and dependent birth dates \u2014 SSA-1099s are first-class, box 5 and box 6 are summed for you; the tool derives ages, classifications, Part IV withholding, and penalties deterministically). Never assemble return lines by hand \u2014 this tool is the return. TRANSCRIPTION CONVENTIONS: (1) a PRIOR-YEAR Form 1040 in the file supplies CONTINUING conditions the current-year interview omits \u2014 the 'Someone can claim: You as a dependent' checkbox and the blindness boxes carry forward unless the current-year data contradicts them; (2) COMMUNITY PROPERTY: do NOT split income 50/50 between MFS spouses when they lived apart all year with no transfers (\xA7 66(a) allocates earned income to the earner) or when a written separation agreement ended the community \u2014 transcribe each document to its named earner; never invent a Form 8958 split the intake does not request; (3) bonus depreciation for assets placed in service 1/1-1/19/2025 is 40% (\xA7 168(k) pre-OBBBA phase-down; 100% only for property ACQUIRED after 1/19/2025).",
37276
+ description: "Compute the COMPLETE Form 1040 bottom-line set in one call \u2014 the 17 lines that determine the return (1a, 9, 10, 11, 12e, 15, 16, 17 AMT, 19, 22, 23, 24, 25d, 27a, 28, 32, 33, 34/37), each whole-dollar rounded by the engine. Takes the SAME input as calculate_tax (prefer the documents block: transcribe W-2/1099-R/SSA-1099 boxes and dependent birth dates \u2014 SSA-1099s are first-class, box 5 and box 6 are summed for you; the tool derives ages, classifications, Part IV withholding, and penalties deterministically). Never assemble return lines by hand \u2014 this tool is the return. TRANSCRIPTION CONVENTIONS: (1) a PRIOR-YEAR Form 1040 in the file supplies CONTINUING conditions the current-year interview omits \u2014 the 'Someone can claim: You as a dependent' checkbox and the blindness boxes carry forward unless the current-year data contradicts them; (2) COMMUNITY PROPERTY: do NOT split income 50/50 between MFS spouses when they lived apart all year with no transfers (\xA7 66(a) allocates earned income to the earner) or when a written separation agreement ended the community \u2014 transcribe each document to its named earner; never invent a Form 8958 split the intake does not request; (3) bonus depreciation for assets placed in service 1/1-1/19/2025 is 40% (\xA7 168(k) pre-OBBBA phase-down; 100% only for property ACQUIRED after 1/19/2025).",
36017
37277
  inputSchema: external_exports.object(individualNestedShape).strict()
36018
37278
  }, async (args) => {
36019
37279
  try {
@@ -36022,7 +37282,7 @@ function createServer() {
36022
37282
  const { value } = evaluate(corpus, facts2, { asOf, target });
36023
37283
  return value.type === "money" ? value.cents : 0n;
36024
37284
  };
36025
- const rd2 = (c2) => {
37285
+ const rd3 = (c2) => {
36026
37286
  const neg = c2 < 0n;
36027
37287
  const abs = neg ? -c2 : c2;
36028
37288
  const r = (abs + 50n) / 100n * 100n;
@@ -36049,11 +37309,11 @@ function createServer() {
36049
37309
  const extension = extFact && extFact.type === "money" ? BigInt(extFact.value) : 0n;
36050
37310
  const estFact = facts2.federalEstimatedPayments;
36051
37311
  const estimated = estFact && estFact.type === "money" ? BigInt(estFact.value) : 0n;
36052
- const total24 = rd2(after) + rd2(other);
36053
- const payments = rd2(withheld) + rd2(refundable) + rd2(extension) + rd2(estimated);
37312
+ const total24 = rd3(after) + rd3(other);
37313
+ const payments = rd3(withheld) + rd3(refundable) + rd3(extension) + rd3(estimated);
36054
37314
  const balance = payments - total24;
36055
37315
  const { proof } = evaluate(corpus, facts2, { asOf, target: "us.federal.net_tax" });
36056
- const d3 = (c2) => fmt2(rd2(c2));
37316
+ const d3 = (c2) => fmt2(rd3(c2));
36057
37317
  return ok({
36058
37318
  ok: true,
36059
37319
  asOf,
@@ -36080,7 +37340,7 @@ function createServer() {
36080
37340
  "28_actc": d3(actc),
36081
37341
  "29_aotc_refundable": d3(aotcRef),
36082
37342
  "32_refundable_credits": d3(refundable),
36083
- ...extension > 0n ? { "31_other_payments_incl_extension": fmt2(rd2(extension)) } : {},
37343
+ ...extension > 0n ? { "31_other_payments_incl_extension": fmt2(rd3(extension)) } : {},
36084
37344
  "33_total_payments": fmt2(payments),
36085
37345
  "34_refund_or_37_owed": balance >= 0n ? `refund ${fmt2(balance)}` : `owed ${fmt2(-balance)}`
36086
37346
  },
@@ -36096,7 +37356,7 @@ function createServer() {
36096
37356
  description: `Verify a claimed tax amount (yours, a user's, or another tool's) against the law. Returns verdict 'verified' or 'refuted' with the correct value. Use this as a self-check before presenting any tax number. Put asOf (and target, if any) INSIDE the facts object \u2014 e.g. facts: {..., "asOf": "2025-12-31"} \u2014 otherwise the claim is checked under today's law.`,
36097
37357
  inputSchema: external_exports.object({
36098
37358
  facts: factsObjectParam,
36099
- claimedAmount: money37.describe("the amount to verify (negative = refund)"),
37359
+ claimedAmount: money40.describe("the amount to verify (negative = refund)"),
36100
37360
  toleranceDollars: external_exports.number().min(0).optional().describe("default 1")
36101
37361
  }).strict()
36102
37362
  }, async (args) => {
@@ -36238,11 +37498,37 @@ function createServer() {
36238
37498
  corpusMerkleRoot: corpus.merkleRoot
36239
37499
  });
36240
37500
  });
37501
+ server2.registerTool("search_tax_rules", {
37502
+ description: "Full-text search over the encoded tax-law corpus ('kiddie tax', 'NIIT threshold', 'california renters credit'). Returns matching rules: id, title, statutory citation, effective window, and a verbatim excerpt of the law text. A hit means the engine computes this; zero hits means it is outside the corpus \u2014 say so rather than guessing. Follow up with explain_rule for a hit's full formula, or lookup_tax_parameter for its dollar amounts.",
37503
+ inputSchema: external_exports.object({
37504
+ query: external_exports.string().describe("plain-English search, e.g. 'kiddie tax'"),
37505
+ asOf: external_exports.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
37506
+ limit: external_exports.number().int().min(1).max(20).optional()
37507
+ }).strict()
37508
+ }, async ({ query, asOf, limit }) => {
37509
+ const hits = searchRules(corpus, query, asOf, limit ?? 8);
37510
+ return ok({
37511
+ ok: true,
37512
+ query,
37513
+ ...asOf ? { asOf } : {},
37514
+ covered: hits.length > 0,
37515
+ hits: hits.map((h) => ({
37516
+ ruleId: h.ruleId,
37517
+ title: h.title,
37518
+ jurisdiction: h.jurisdiction,
37519
+ citation: `${h.citation.source} ${h.citation.section}`,
37520
+ effective: `[${h.effectiveFrom}, ${h.effectiveTo ?? "open"})`,
37521
+ excerpt: h.snippet,
37522
+ hasDollarParameters: h.hasParameters
37523
+ })),
37524
+ corpusMerkleRoot: corpus.merkleRoot
37525
+ });
37526
+ });
36241
37527
  server2.registerTool("verify_fact", {
36242
37528
  description: "Fact-check a claimed dollar amount about tax law ('the 2026 MFJ standard deduction is $32,200', 'CTC is $2,000 per child') against the corpus. Returns verified / refuted (with the correct value and citation) / unknown. Never states a verdict it cannot ground.",
36243
37529
  inputSchema: external_exports.object({
36244
37530
  query: external_exports.string().describe("what the amount is, e.g. 'standard deduction'"),
36245
- claimedAmount: money37,
37531
+ claimedAmount: money40,
36246
37532
  filingStatus: external_exports.enum(["single", "mfj", "mfs", "hoh", "qss"]).optional(),
36247
37533
  asOf: external_exports.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional()
36248
37534
  }).strict()