Skip to main content

btctax_forms/
lib.rs

1//! **btctax-forms** — fill the OFFICIAL IRS fillable PDFs (Form 8949 + Schedule D) from btctax's
2//! already-computed tax data. Offline, deterministic, and **geometry-verified**: every fill is read
3//! back from its own serialized bytes and each value's widget `/Rect` is checked against a
4//! map-independent column/row band re-derived from the PDF itself. A mis-mapped cell fails closed.
5//!
6//! ## Sub-project 1 (TY2025)
7//! - **Form 8949** — Bitcoin under **Box I** (short-term) / **Box L** (long-term), the 1099-DA
8//!   revision (NOT Box C/F, which read "other than digital asset transactions"). 11 rows per part
9//!   per page.
10//! - **Schedule D** — lines 3 & 7 (ST), 10 & 15 (LT), 16 (total), **17** (derived from the printed
11//!   lines 15 and 16), QOF = No. Lines 18-22 are scoped out (the caller prints a notice) — each needs
12//!   a fact the crypto slice never collects.
13//! - These are **static XFA-hybrid** PDFs: the fill removes the `/AcroForm` `/XFA` layer (else
14//!   Acrobat shows blank), sets `/NeedAppearances`, and pins determinism (drops `/Info` dates + the
15//!   trailer `/ID`).
16//!
17//! The tax data is REUSED verbatim from the projection (`btctax_core::form_8949` /
18//! `btctax_core::schedule_d`) — this crate never recomputes gains.
19
20mod cells;
21mod error;
22mod fill8949;
23mod fill8949_full;
24mod form1040;
25mod form1040_full;
26mod form6251;
27mod form8275;
28mod form8283;
29mod form8959;
30mod form8960;
31mod form8995;
32mod form8995a;
33mod map;
34mod overflow;
35mod packet;
36mod pdf;
37mod schedule23;
38mod schedule_a;
39mod schedule_b;
40mod schedule_c;
41mod schedule_d;
42mod schedule_d_full;
43mod schedule_se;
44mod schedule_se_full;
45mod transcribe;
46mod verify;
47mod watermark;
48mod wrap;
49
50pub use error::FormsError;
51pub use form1040::{Form1040Fill, Form1040Inputs};
52pub use form8275::PartIiCapacity;
53pub use map::{
54    Form1040Map, Form8275Map, Form8283Map, Form8949Map, Form8959Map, Form8960Map, Form8995Map,
55    Schedule1Map, Schedule2Map, Schedule3Map, ScheduleAMap, ScheduleBMap, ScheduleCMap,
56    ScheduleDMap, ScheduleSeMap,
57};
58pub use schedule_se::SE_FLOOR;
59pub use wrap::PartIiOverflow;
60
61use btctax_core::conventions::{TaxDate, Usd};
62use btctax_core::{Form8949Row, ScheduleDTotals};
63use time::macros::format_description;
64
65/// The tax years this build bundles forms + maps for. Each fill dispatches to the year's committed
66/// map + bundled PDF via the `Map::for_year` constructors; an unlisted year fails closed with
67/// [`FormsError::UnsupportedYear`].
68pub const SUPPORTED_YEARS: &[i32] = &[2017, 2024, 2025];
69
70/// Format a date as **MM/DD/YYYY** — Form 8949's native date format for columns (b)/(c).
71pub(crate) fn fmt_date(d: TaxDate) -> Result<String, FormsError> {
72    let fmt = format_description!("[month]/[day]/[year]");
73    d.format(&fmt)
74        .map_err(|e| FormsError::Structure(format!("date format: {e}")))
75}
76
77/// Format money exactly as the `form8949.csv` / `schedule_d.csv` do — the raw `Decimal` Display
78/// (native scale, no `$`, no thousands separators). Keeping this identical to the CSV is what lets
79/// `schedule_d_totals_match_form8949_and_csv` cross-check the three artifacts.
80pub(crate) fn fmt_money(d: Usd) -> String {
81    d.to_string()
82}
83
84/// Fill **Form 8949** (Part I + Part II) for `year` from the projection rows and return the PDF
85/// bytes. The box is year-aware: Bitcoin is filed under the digital-asset Box I/L from TY2025, and
86/// the securities Box C/F on the pre-2025 revisions. Parts that overflow the revision's grid (11 rows
87/// per part on the 2025 form, 14 on 2024/2017) paginate: ⌈rows/grid⌉ page copies per part, each with
88/// its own totals; the copies are merged with per-copy field renaming so no two share a value. Every
89/// copy is geometry-verified before merge.
90pub fn fill_form_8949(rows: &[Form8949Row], year: i32) -> Result<Vec<u8>, FormsError> {
91    let map = Form8949Map::for_year(year)?;
92    let cap = map.rows_per_page;
93    let (st, lt) = fill8949::split_parts(rows);
94    let n_pages = div_ceil(st.len(), cap).max(div_ceil(lt.len(), cap)).max(1);
95
96    if n_pages == 1 {
97        let short = fill8949::part_data(&st)?;
98        let long = fill8949::part_data(&lt)?;
99        return fill8949::fill_8949_parts(&short, &long, &map);
100    }
101
102    let mut copies = Vec::with_capacity(n_pages);
103    for k in 0..n_pages {
104        let st_chunk: Vec<&Form8949Row> = st.iter().skip(k * cap).take(cap).copied().collect();
105        let lt_chunk: Vec<&Form8949Row> = lt.iter().skip(k * cap).take(cap).copied().collect();
106        let short = fill8949::part_data(&st_chunk)?;
107        let long = fill8949::part_data(&lt_chunk)?;
108        // Each copy is filled on ORIGINAL names and geometry-verified here (fails closed).
109        copies.push(fill8949::fill_8949_parts(&short, &long, &map)?);
110    }
111    overflow::merge_copies(&copies)
112}
113
114fn div_ceil(n: usize, d: usize) -> usize {
115    n.div_ceil(d)
116}
117
118/// Stamp a diagonal `DRAFT — ESTIMATE, NOT FOR FILING` watermark on every page of a filled form.
119/// Applied by the CLI when the ledger is pseudo-reconciled (an estimate). The overlay carries its own
120/// embedded standard font resource, orthogonal to `/NeedAppearances`.
121pub fn stamp_draft_watermark(pdf_bytes: &[u8]) -> Result<Vec<u8>, FormsError> {
122    watermark::stamp_draft(pdf_bytes)
123}
124
125/// Stamp a diagonal `WORKSHEET — NOT A COMPLETE FORM 1040` watermark on every page of a filled form.
126///
127/// For the **crypto-slice** Form 1040 only ([`fill_form_1040_capgains`]), which fills exactly two
128/// cells — the digital-asset question and line 7a — and leaves every income, deduction and tax line
129/// blank. Without it the artifact renders as a Form 1040 with a populated line 7a and a blank line
130/// 1a, and its only caveat is a note on stderr that the document outlives. Runs on the OPPOSITE
131/// diagonal from [`stamp_draft_watermark`], so a pseudo-reconciled slice carries both legibly.
132///
133/// **Never applied to the full-return `f1040.pdf`**, which IS complete (see the
134/// `crypto_slice_1040_is_watermarked_and_the_full_return_1040_is_not` KAT).
135pub fn stamp_partial_worksheet_watermark(pdf_bytes: &[u8]) -> Result<Vec<u8>, FormsError> {
136    watermark::stamp_partial_worksheet(pdf_bytes)
137}
138
139/// Fill **Schedule D** for `year` from the part totals and return the PDF bytes.
140pub fn fill_schedule_d(totals: &ScheduleDTotals, year: i32) -> Result<Vec<u8>, FormsError> {
141    let map = ScheduleDMap::for_year(year)?;
142    schedule_d::fill_schedule_d_totals(totals, &map)
143}
144
145/// Fill **Schedule SE** (Form 1040) for `year` from the computed §1401 `SeTaxResult`, the filer's
146/// Form W-2 Social Security wages (line 8a), and the year's Social Security wage base (line 7).
147/// Returns `Ok(None)` when net SE earnings are **below the $400 floor** (no SE tax owed — the form is
148/// not written). Line 12 = SS + regular Medicare only (the 0.9% Additional Medicare Tax is a Form 8959
149/// item, not on Schedule SE); when `se.addl > 0` the caller prints a Form 8959 advisory.
150pub fn fill_schedule_se(
151    se: &btctax_core::SeTaxResult,
152    w2_ss_wages: Usd,
153    ss_wage_base: Usd,
154    year: i32,
155) -> Result<Option<Vec<u8>>, FormsError> {
156    let map = ScheduleSeMap::for_year(year)?;
157    schedule_se::fill_schedule_se_with_map(se, w2_ss_wages, ss_wage_base, &map)
158}
159
160/// Fill **Form 8959** (Additional Medicare Tax) for `year` from the core-derived line chain
161/// (`btctax_core::tax::other_taxes::form_8959_lines`). Returns `Ok(None)` when the form is not
162/// required — line 18 (the tax) AND line 24 (the withholding reconciliation) are both zero.
163///
164/// Line 24 is not redundant with line 18: an employer withholds the 0.9% on ITS OWN wages over
165/// $200,000 with no knowledge of a spouse or a second job, so a taxpayer who owes NO Additional
166/// Medicare Tax can still have had some withheld — and that excess is a credit on 1040 line 25c.
167/// Skipping the form on line 18 alone would silently forfeit it.
168pub fn fill_form_8959(
169    lines: &btctax_core::tax::other_taxes::Form8959Lines,
170    header: &btctax_core::tax::packet::ReturnHeader,
171    year: i32,
172) -> Result<Option<Vec<u8>>, FormsError> {
173    let map = Form8959Map::for_year(year)?;
174    form8959::fill_form_8959_with_map(lines, header, &map)
175}
176
177/// Fill **Form 8960** (Net Investment Income Tax, §1411) for `year` from the core-derived line chain
178/// (`btctax_core::tax::other_taxes::form_8960_lines`, which returns `None` when no NIIT is owed —
179/// there is then no form to file).
180pub fn fill_form_8960(
181    lines: &btctax_core::tax::other_taxes::Form8960Lines,
182    header: &btctax_core::tax::packet::ReturnHeader,
183    year: i32,
184) -> Result<Vec<u8>, FormsError> {
185    let map = Form8960Map::for_year(year)?;
186    form8960::fill_form_8960_with_map(lines, header, &map)
187}
188
189/// Fill **Form 8995** (QBI deduction, simplified) for `year` from the core-derived line chain
190/// (`btctax_core::tax::qbi::form_8995_lines`, which returns `None` when there is no QBI).
191///
192/// FAILS CLOSED if a parenthesized cell (line 7/16/17) carries a negative value: the form pre-prints
193/// the parentheses, so a negative would render as a POSITIVE number on the filed return.
194pub fn fill_form_8995(
195    lines: &btctax_core::tax::qbi::Form8995Lines,
196    header: &btctax_core::tax::packet::ReturnHeader,
197    year: i32,
198) -> Result<Vec<u8>, FormsError> {
199    let map = Form8995Map::for_year(year)?;
200    form8995::fill_form_8995_with_map(lines, header, &map)
201}
202
203/// Fill **Schedule 1** (Additional Income and Adjustments to Income) for `year` from the core-derived
204/// printed chain (`btctax_core::tax::printed::schedule_1_lines`, which returns `None` when there is
205/// neither additional income nor an adjustment — the schedule is then not filed).
206pub fn fill_schedule_1(
207    lines: &btctax_core::tax::printed::Schedule1Lines,
208    header: &btctax_core::tax::packet::ReturnHeader,
209    year: i32,
210) -> Result<Vec<u8>, FormsError> {
211    let map = Schedule1Map::for_year(year)?;
212    schedule23::fill_schedule_1_with_map(lines, header, &map)
213}
214
215/// Fill **Schedule 2** (Additional Taxes) for `year` from the core-derived printed chain
216/// (`btctax_core::tax::printed::schedule_2_lines`, which returns `None` when there are no other
217/// taxes to report — the schedule is then not filed).
218pub fn fill_schedule_2(
219    lines: &btctax_core::tax::printed::Schedule2Lines,
220    header: &btctax_core::tax::packet::ReturnHeader,
221    year: i32,
222) -> Result<Vec<u8>, FormsError> {
223    let map = Schedule2Map::for_year(year)?;
224    schedule23::fill_schedule_2_with_map(lines, header, &map)
225}
226
227/// Fill **Schedule 3** (Additional Credits and Payments) for `year` from the core-derived printed
228/// chain (`btctax_core::tax::printed::schedule_3_lines`, which returns `None` when there is neither a
229/// foreign tax credit nor an excess-Social-Security credit).
230pub fn fill_schedule_3(
231    lines: &btctax_core::tax::printed::Schedule3Lines,
232    header: &btctax_core::tax::packet::ReturnHeader,
233    year: i32,
234) -> Result<Vec<u8>, FormsError> {
235    let map = Schedule3Map::for_year(year)?;
236    schedule23::fill_schedule_3_with_map(lines, header, &map)
237}
238
239/// Fill **Schedule A** (Itemized Deductions) for `year` from the core-derived printed chain
240/// (`btctax_core::tax::printed::schedule_a_lines`, which returns `None` unless the return actually
241/// itemizes — Schedule A is computed even when the standard deduction wins, but only FILED when it is
242/// the deduction claimed).
243pub fn fill_schedule_a(
244    lines: &btctax_core::tax::printed::ScheduleALines,
245    header: &btctax_core::tax::packet::ReturnHeader,
246    year: i32,
247) -> Result<Vec<u8>, FormsError> {
248    let map = ScheduleAMap::for_year(year)?;
249    schedule_a::fill_schedule_a_with_map(lines, header, &map)
250}
251
252/// Fill the **FULL-RETURN Form 1040** for `year` from the core-derived printed chain
253/// (`btctax_core::tax::printed::form_1040_lines`) — every line, not just the capital-gain cluster.
254///
255/// This is NOT [`fill_form_1040_capgains`], which writes only line 7 + the Digital-Asset question for
256/// the crypto-slice export. That one stays: for a year with no `ReturnInputs` it is what the filer
257/// wants.
258pub fn fill_form_1040_full(
259    lines: &btctax_core::tax::printed::Form1040Lines,
260    header: &btctax_core::tax::packet::ReturnHeader,
261    status: btctax_core::tax::types::FilingStatus,
262    year: i32,
263) -> Result<Vec<u8>, FormsError> {
264    let map = Form1040Map::for_year(year)?;
265    form1040_full::fill_form_1040_full_with_map(lines, header, status, &map)
266}
267
268/// Fill the **FULL-RETURN Schedule D** for `year` from the core-derived printed chain
269/// (`btctax_core::tax::printed::schedule_d_lines`), including Part III's SPEC §7.2 routing.
270///
271/// This is NOT [`fill_schedule_d`], which is the **crypto-slice** fill: that one writes only lines
272/// 3/7/10/15/16 from the ledger totals, with no line 13 (1099-DIV box-2a capital-gain distributions)
273/// and no lines 6/14 (capital-loss carryovers). For a crypto-only year that is complete and correct.
274/// For a full return it would be a complete-LOOKING form with income missing.
275///
276/// FAILS CLOSED if a parenthesized cell (line 6/14/21) carries a negative: the form pre-prints the
277/// parentheses, so a negative renders as a POSITIVE number — turning a capital loss into a gain.
278pub fn fill_schedule_d_full(
279    lines: &btctax_core::tax::printed::ScheduleDLines,
280    header: &btctax_core::tax::packet::ReturnHeader,
281    year: i32,
282) -> Result<Vec<u8>, FormsError> {
283    let map = ScheduleDMap::for_year(year)?;
284    schedule_d_full::fill_schedule_d_full_with_map(lines, header, &map)
285}
286
287/// Fill **Form 8275** (Disclosure Statement) for `year` from the T13 printed disclosure
288/// (`btctax_core::tax::printed::printed_8275`) + the FILER's identity. `Ok(None)` when there is no
289/// Part I content to disclose.
290///
291/// Year coverage is MANDATORY, not conditional: Form 8275 is REVISION-versioned, not
292/// tax-year-versioned, so the single bundled Rev. 10-2024 asset is aliased to EVERY `SUPPORTED_YEAR`
293/// (2017/2024/2025) — a promoted disposal filed in any supported year gets a real fillable
294/// disclosure, never a permanent refusal for want of a "2025 revision" that does not exist.
295pub fn fill_form_8275(
296    printed: &btctax_core::tax::printed::Printed8275,
297    header: &btctax_core::tax::packet::ReturnHeader,
298    year: i32,
299) -> Result<Option<Vec<u8>>, FormsError> {
300    form8275::fill_form_8275(printed, header, year)
301}
302
303/// Fill **Form 8275** for `year` on the **crypto-slice** `export-irs-pdf` path (Task 16) — NO filer
304/// identity (mirrors [`fill_form_8283`], whose crypto-slice fill also writes no identity block): the
305/// disclosure still rides the export even when there is no `ReturnInputs` on file. `Ok(None)` when
306/// there is no Part I content to disclose.
307pub fn fill_form_8275_slice(
308    printed: &btctax_core::tax::printed::Printed8275,
309    year: i32,
310) -> Result<Option<Vec<u8>>, FormsError> {
311    form8275::fill_form_8275_slice(printed, year)
312}
313
314/// Whether a Form 8275 Part II `narrative` fits `year`'s bundled revision, WITHOUT filling anything —
315/// runs the identical wrap the real fill does, so a caller can pre-flight BEFORE writing any packet
316/// file (T-f8275-part-ii-overflow round 2 finding 2). See [`form8275::part_ii_capacity_check`]'s doc
317/// comment for the full rationale.
318pub fn part_ii_capacity_check(narrative: &str, year: i32) -> Result<PartIiCapacity, FormsError> {
319    form8275::part_ii_capacity_check(narrative, year)
320}
321
322/// Fill the **full-return Form 8283** for `year` — whole-dollar rows plus the FILER's identity block
323/// (which the crypto slice never writes). `Ok(None)` when there are no donation rows.
324pub fn fill_form_8283_full(
325    printed: &btctax_core::tax::printed::Printed8283Rows,
326    header: &btctax_core::tax::packet::ReturnHeader,
327    year: i32,
328) -> Result<Option<Vec<u8>>, FormsError> {
329    let map = Form8283Map::for_year(year)?;
330    form8283::fill_form_8283_full(printed, header, &map)
331}
332
333/// Fill the **full-return Form 8949** for `year` from the core-derived printed chain
334/// (`btctax_core::tax::printed::form_8949_printed`). Whole dollars, and column (h) is DERIVED from the
335/// printed (d) − (e). Schedule D lines 3/10 ARE this form's printed column totals.
336pub fn fill_8949_full(
337    printed: &btctax_core::tax::printed::Printed8949,
338    header: &btctax_core::tax::packet::ReturnHeader,
339    year: i32,
340) -> Result<Vec<u8>, FormsError> {
341    let map = Form8949Map::for_year(year)?;
342    fill8949_full::fill_8949_full_with_map(printed, header, &map)
343}
344
345/// Fill the **full-return Schedule SE** for `year` from the core-derived printed chain
346/// (`btctax_core::tax::printed::schedule_se_lines`). Whole dollars — the crypto slice's
347/// `fill_schedule_se` keeps its exact-cents rendering and is untouched.
348pub fn fill_schedule_se_full(
349    lines: &btctax_core::tax::printed::ScheduleSeLines,
350    header: &btctax_core::tax::packet::ReturnHeader,
351    year: i32,
352) -> Result<Vec<u8>, FormsError> {
353    let map = ScheduleSeMap::for_year(year)?;
354    schedule_se_full::fill_schedule_se_full_with_map(lines, header, &map)
355}
356
357/// Fill **Schedule B** (Interest and Ordinary Dividends) for `year` from the core-derived printed
358/// chain (`btctax_core::tax::printed::schedule_b_lines`, which returns `None` when Schedule B is not
359/// required — interest and dividends both at or under $1,500 and no declared foreign account).
360///
361/// REFUSES when there are more payers than the form has rows (14 interest / 15 dividend). Truncating
362/// the list would leave a form whose printed rows do not add up to its own total.
363pub fn fill_schedule_b(
364    lines: &btctax_core::tax::printed::ScheduleBLines,
365    header: &btctax_core::tax::packet::ReturnHeader,
366    year: i32,
367) -> Result<Vec<u8>, FormsError> {
368    let map = ScheduleBMap::for_year(year)?;
369    schedule_b::fill_schedule_b_with_map(lines, header, &map)
370}
371
372/// Fill **Schedule C** (Profit or Loss From Business) for `year` from the core-derived printed chain
373/// (`btctax_core::tax::printed::schedule_c_lines`, which returns `None` when there is no crypto trade
374/// or business).
375pub fn fill_schedule_c(
376    lines: &btctax_core::tax::printed::ScheduleCLines,
377    header: &btctax_core::tax::packet::ReturnHeader,
378    year: i32,
379) -> Result<Vec<u8>, FormsError> {
380    let map = ScheduleCMap::for_year(year)?;
381    schedule_c::fill_schedule_c_with_map(lines, header, &map)
382}
383
384/// Fill **Form 8283** (Noncash Charitable Contributions, Rev. 12-2025) for `year` from the projected
385/// donation rows + `DonationDetails`. Returns `Ok(None)` when there are no donations in the year.
386/// Fills the donee/appraiser IDENTITY + per-row property data (and, for Section B, checks the "k
387/// Digital assets" property-type box); leaves BLANK every OTHER party's declaration/signature (a
388/// LOUD partial-scope notice is the caller's). More rows than a section holds (4 in Section A / 3 in
389/// Section B) overflow onto additional form copies via [`overflow::merge_copies`].
390pub fn fill_form_8283(
391    rows: &[btctax_core::Form8283Row],
392    year: i32,
393) -> Result<Option<Vec<u8>>, FormsError> {
394    let map = Form8283Map::for_year(year)?;
395    form8283::fill_form_8283(rows, &map)
396}
397
398/// Fill the capital-gains cells of **Form 1040** for `year`: line 7a (only when Schedule D is ACTIVE
399/// and line 16 ≥ 0; active-and-zero → "-0-") and the Digital-Asset Yes/No question (YES iff there is
400/// btctax-evidenced qualifying activity). Returns `Ok(None)` — **skip the whole 1040** — when there is
401/// no reportable activity (the DA answer would be blank and there is no 7a value). 7b checkboxes are
402/// left untouched; a NET LOSS leaves 7a blank (the §1211 line-21 cap is the filer's). A partial-scope
403/// notice enumerating exactly what was filled is the caller's.
404pub fn fill_form_1040_capgains(
405    inputs: &Form1040Inputs,
406    year: i32,
407) -> Result<Option<Form1040Fill>, FormsError> {
408    let map = Form1040Map::for_year(year)?;
409    form1040::fill_form_1040_capgains(inputs, &map)
410}
411
412/// **[I5]** How many rows might belong on a SEPARATE broker-reported Form 8949 — i.e. disposals on an
413/// exchange that may have issued broker basis reporting. The separate boxes and this export's filed
414/// boxes are year-aware (see `btctax-cli`'s `broker_reporting_advisory`): a 1099-B / Box A/B/D/E, filed
415/// under C/F pre-TY2025; a 1099-DA / Box G/H/J/K, filed under I/L from TY2025. A non-zero count is a
416/// loud advisory, not a refusal.
417pub fn rows_possibly_broker_reported(rows: &[Form8949Row]) -> usize {
418    rows.iter().filter(|r| r.box_needs_review).count()
419}
420
421// ── Internals exposed for the KATs (fault injection needs a corruptible map + the verifier). ──────
422#[doc(hidden)]
423pub use packet::{fill_full_return, FiledPacket, NamedForm, NamedStatement};
424
425pub mod testonly {
426    // ★ The raw AcroForm write path, for `xtask label-proof`: it fills EVERY box on a blank form
427    // with the label the census assigned it, so a human can see the label→box join on the printed
428    // page. Exported here rather than reimplemented in xtask, because a second writer would be a
429    // second truth — and the point of the proof render is that it exercises the SHIPPED path.
430    pub use crate::cells::fmt_money_pair;
431    pub use crate::fill8949::{fill_8949_parts, part_data, pdf_has_xfa, split_parts, PartData};
432    pub use crate::fill8949_full::fill_8949_full_with_map;
433    pub use crate::form1040::{fill_form_1040_capgains as fill_1040_with_map, Form1040Fill};
434    pub use crate::form1040_full::fill_form_1040_full_with_map;
435    pub use crate::form6251::fill_form_6251_with_map;
436    pub use crate::form8275::fill_form_8275_with_map as fill_8275_with_map;
437    pub use crate::form8283::fill_form_8283 as fill_8283_with_map;
438    pub use crate::form8283::fill_form_8283_full as fill_8283_full_with_map;
439    pub use crate::form8959::fill_form_8959_with_map;
440    pub use crate::form8960::fill_form_8960_with_map;
441    pub use crate::form8995::fill_form_8995_with_map;
442    pub use crate::form8995a::fill_form_8995a_with_map;
443    pub use crate::pdf::{
444        apply_writes, drop_xfa_and_set_needappearances, save, strip_nondeterminism, FieldValue,
445    };
446    // The committed map TOML, for the line-keyed inverse transcriber (`extract_lines`). Downstream
447    // read-back tests need the map itself, not just its parsed struct.
448    pub use crate::map::{
449        AmountCols, AmountColsNoAdjustment, Form1040Map, Form6251Map, Form8275Map, Form8275Row,
450        Form8283Map, Form8949Map, Form8959Map, Form8960Map, Form8995AMap, Form8995Map, MoneyCell,
451        MoneyPair, PartMap, Schedule1Map, Schedule2Map, Schedule3Map, ScheduleAMap, ScheduleBMap,
452        ScheduleCMap, ScheduleDMap, ScheduleSeMap,
453    };
454    pub use crate::map::{
455        F1040_MAP_2024, F8275_MAP_2024, F8283_MAP_2024, F8949_MAP_2024, F8959_MAP_2024,
456        F8960_MAP_2024, F8995_MAP_2024, SCHEDULE_1_MAP_2024, SCHEDULE_2_MAP_2024,
457        SCHEDULE_3_MAP_2024, SCHEDULE_A_MAP_2024, SCHEDULE_B_MAP_2024, SCHEDULE_C_MAP_2024,
458        SCHEDULE_D_MAP_2024, SCHEDULE_SE_MAP_2024,
459    };
460    pub use crate::pdf::{
461        button_on_states, checkbox_on, collect_fields, f6251_pdf, f8995a_pdf, index, load,
462        text_value, Field, F1040_PDF_2017, F1040_PDF_2024, F1040_PDF_2025, F8275_PDF_2024,
463        F8283_PDF_2017, F8283_PDF_2024, F8283_PDF_2025, F8949_PDF_2017, F8949_PDF_2024,
464        F8949_PDF_2025, F8959_PDF_2024, SCHEDULE_D_PDF_2017, SCHEDULE_D_PDF_2024,
465        SCHEDULE_D_PDF_2025, SCHEDULE_SE_PDF_2017, SCHEDULE_SE_PDF_2024, SCHEDULE_SE_PDF_2025,
466    };
467    pub use crate::schedule23::{
468        fill_schedule_1_with_map, fill_schedule_2_with_map, fill_schedule_3_with_map,
469    };
470    pub use crate::schedule_a::fill_schedule_a_with_map;
471    pub use crate::schedule_b::fill_schedule_b_with_map;
472    pub use crate::schedule_c::fill_schedule_c_with_map;
473    pub use crate::schedule_d::fill_schedule_d_totals;
474    pub use crate::schedule_d_full::fill_schedule_d_full_with_map;
475    pub use crate::schedule_se::fill_schedule_se_with_map;
476    pub use crate::schedule_se_full::fill_schedule_se_full_with_map;
477    pub use crate::transcribe::extract_lines;
478    pub use crate::verify::{
479        no_unmapped_filled, topmost_yes_no_pair, verify_8949, verify_flat, FlatPlacement, Geo,
480        Placement,
481    };
482}