Skip to content

Fix 2025 PDF export: correct AcroForm mappings, print-layer emissions, conditional-form gating#1

Open
temujin9 wants to merge 4 commits into
filedcom:mainfrom
temujin9:fix/2025-pdf-export
Open

Fix 2025 PDF export: correct AcroForm mappings, print-layer emissions, conditional-form gating#1
temujin9 wants to merge 4 commits into
filedcom:mainfrom
temujin9:fix/2025-pdf-export

Conversation

@temujin9

Copy link
Copy Markdown

Summary

The 2025 PDF export produced returns that were not safe to file. Exercising it against a simple single-filer return (one W-2, 1099-DIV/INT/B, HSA, nondeductible IRA) surfaced four classes of defect:

  1. Descriptor field mappings didn't match the IRS AcroForm layer. Values landed in wrong fields — the foreign tax credit printed inside Schedule 3's name field, Form 8606 line 1 printed in the home address field, capital-gain distributions landed in Schedule D row 8b's proceeds column, and the Form 1040 "Single" mapping actually checked the §301.9100-2 late-election header box.
  2. Computed line values never reached the printer. Intermediate nodes (schedule3, schedule_b, schedule_d, form8606, form8880, form8889) route results downstream only, so their own pending dicts held raw inputs and the printed schedules came out blank.
  3. Every $0 form was emitted. hasData treated any defined value as content, padding the export with 14 pages of inapplicable forms (Schedule A/EIC/SE, 6251, 8959, 8960, 8962, and an off-by-one-row Form 8880 for a filer 4x over the Saver's Credit phase-out).
  4. Line 37 could disagree with the form's own arithmetic by $1 (computed from the cents-level difference instead of the rounded filed lines).

Changes

  • Nodes self-emit print_* line values into their own pending (the pattern the f1040 output node already uses). Schedule D also splits covered/no-adjustment transactions onto direct-report lines 1a/8a per "Exception 1"; 8889 emits the full Part I ladder only when the form is required; 8880 emits only with a nonzero credit.
  • Descriptors rebuilt from AcroForm field dumps (name/type/rect) of the official 2025 PDFs for f1040, Schedule 3, Schedule B, Schedule D, 8606, 8880, 8889 — including filing-status/digital-assets checkboxes, occupation, name/SSN headers on every schedule, and middle-initial support (general accepted taxpayer_middle_initial but dropped it).
  • includeWhen gate on descriptors (with access to the full normalized pending) encodes each conditional form's statutory trigger: Schedule A only when itemizing, Schedule B only above $1,500, SE/6251/8959/8960 only when the corresponding Schedule 2 tax exists, 8962 only with 1095-A data.
  • printZero field option for lines where an explicit 0 is meaningful (8606 line 2, 8889 lines 2/13).
  • Year-pinned template URLspub/irs-pdf/*.pdf silently changes revision each season; the 2025 module now fetches pub/irs-prior/*--2025.pdf.
  • Lines 34/37 computed from rounded operands so the printed return satisfies its own line 24 − line 33 arithmetic.

Also corrects three pre-existing test expectations that failed against correct engine output:

  • two Form 8889 tests treated employer HSA contributions as deductible on line 13 (IRC §223(a): the deduction is min(line 2, line 12); employer contributions are already excluded from W-2 box 1);
  • the EITC MFJ_vs_single_1_child test used a $30,323 phase-out start where Rev. Proc. 2024-40 §3.11 (and the engine config) has $30,470.

Test plan

  • deno task test: 6,070 passed, 0 failed (baseline on main: 72 failing).
  • End-to-end: exported the exercise return before/after, rendered every page (ghostscript) and verified field-by-field against hand-computed values — 8-page export containing exactly 1040, Schedule 3, Schedule D, 8606, 8889 with all lines, checkboxes and headers correct.

🤖 Generated with Claude Code

temujin9 and others added 4 commits July 19, 2026 16:46
Line 37 (amount owed) and line 34 (overpayment) are the difference of two
whole-dollar filed lines, but were computed from the cents-level difference
and rounded afterward. The two disagree by $1 whenever the operands' cents
round in opposite directions (e.g. 26,357.62 - 25,751.28: rounded-operand
result 607 vs naive 606), which fails the printed form's own arithmetic
(line 24 - line 33) and IRS cross-checks.

Compute the balance from Math.round(line33) - Math.round(line24) and update
the e2e expectations accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The PDF builder can only print keys present in a form's pending dict, but
intermediate nodes (schedule3, schedule_b, schedule_d, form8606, form8880,
form8889) computed their line values internally and routed only the results
downstream — so their pending held raw inputs and the printed schedules came
out blank or wrong. Each node now also emits its computed line values into
its own pending dict under distinct print_* keys (the same self-emission
pattern the f1040 output node already uses).

Details:
- schedule_d: splits covered/no-adjustment transactions (Parts A/D) onto the
  direct-report lines 1a/8a per Schedule D "Exception 1"; answers line 17
  only when line 16 is a gain; line 20 from the 28%/1250 worksheet values.
- form8889: emits the full Part I ladder (lines 2-13); only when
  contribution or distribution activity actually requires the form.
- form8880: emits only when a nonzero credit exists, so ineligible returns
  (AGI above the phase-out) no longer produce a stray all-zero form.
- form8606: emits lines 1/2/3/14 always, distribution/conversion lines when
  applicable.
- schedule_b: emits per-payer rows and Part I/II totals.
- general: forward taxpayer_middle_initial (accepted by the schema but
  previously dropped).
- filer: derive firstNameWithInitial / fullName for PDF name fields.
- form8889 tests: corrected two pre-existing expectations that treated
  employer HSA contributions as deductible on line 13. IRC §223(a) limits
  the deduction to the taxpayer's own contributions (min(line 2, line 12));
  employer contributions are already excluded from W-2 box 1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…itional forms

The 2025 descriptor field mappings did not match the actual IRS AcroForm
layer, and the hasData check emitted every form whose pending held any
defined value. Observable symptoms on a simple single-filer return:

- Form 1040: "Filed pursuant to section 301.9100-2" wrongly checked (c1_1
  is that header checkbox, not Single); filing status unchecked; digital
  assets question unanswered; occupation missing.
- Schedule 3: foreign tax credit printed inside the name field (f1_01);
  line 8 unmapped.
- Schedule D: capital gain distributions printed in row 8b's proceeds
  column; transaction totals, line 15/16 and the 17/20 checkboxes missing.
- Form 8606: line 1 printed in the home-address field; lines 2/3/14 blank.
- Form 8889: employer contributions printed on line 12; everything else
  blank.
- Form 8880: rows off by one (deferrals on line 3) on a form that should
  not print at all above the AGI phase-out.
- 14 pages of inapplicable $0 forms (Schedule A/EIC/SE, 6251, 8959, 8960,
  8962, 8880) padded the export.

Changes:
- Rewrite the f1040, schedule3, schedule_b, schedule_d, f8606, f8880 and
  f8889 descriptors against AcroForm field dumps (name/type/rect) of the
  official 2025 PDFs; fill name/SSN headers on every schedule.
- hasData now requires a meaningful value (nonzero number, true boolean,
  nonempty string) instead of merely-defined.
- New includeWhen gate on descriptors (with access to the full normalized
  pending) encodes each conditional form's statutory filing trigger:
  Schedule A only when itemizing, Schedule B only above $1,500, Schedule SE
  /6251/8959/8960 only when the corresponding Schedule 2 tax exists, 8962
  only with 1095-A data, 8880 only with a nonzero credit.
- New printZero field option for lines where an explicit 0 is meaningful
  (8606 line 2, 8889 lines 2/13).
- Year-pin every descriptor pdfUrl to pub/irs-prior/<form>--2025.pdf; the
  previous pub/irs-pdf URLs silently change revision every filing season.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…4-40

The MFJ_vs_single_1_child test asserted a credit derived from a $30,323
phase-out start, but the TY2025 MFJ threshold for one child is $30,470
(Rev. Proc. 2024-40 §3.11), which is what the engine config already uses
and which is consistent with the published completed-phaseout amount
($57,554 = 30,470 + 4,328/0.1598). The test was failing against correct
engine output: 0.1598 x (45,000 - 30,470) = 2,321.894; round(4,328 -
2,321.894) = 2,006.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant