feat(workpaper): the audit workpaper as a deterministic PDF (P8)
One page set a CPA can read: what was checked, through which day and state of the books, every finding with its code and severity. No clock and no random id, so the same findings give the same bytes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
This commit is contained in:
1 parent
6b7dccd1ee
commit
d45e842873
3 files changed
+224
No files matched your search
@@ -0,0 +1,77 @@
|
||||
// An audit workpaper: what was checked, through which date and which
|
||||
// books' state, and every finding with its code and severity.
|
||||
//
|
||||
// Everything arrives as data in sys.inputs.data (JSON), built by
|
||||
// ledger-import::workpaper. The template only typesets: no arithmetic, and
|
||||
// no text is ever parsed as markup. It holds no clock: the same data gives
|
||||
// the same bytes.
|
||||
//
|
||||
// Shape: { company, title, scope, through, through_seq, program_version,
|
||||
// tb_hash, findings_sha256, summary: str,
|
||||
// findings: [{ severity, code, subject, message }],
|
||||
// checks: [{ code, severity, predicate }] }
|
||||
|
||||
#let d = json(bytes(sys.inputs.data))
|
||||
#let ink = luma(25)
|
||||
#let quiet = luma(110)
|
||||
#let hair = 0.5pt + luma(160)
|
||||
|
||||
#set document(title: d.company + " — " + d.title, author: d.company)
|
||||
#set text(font: ("Inter", "Noto Sans CJK SC", "Noto Sans Arabic UI", "Noto Sans Hebrew", "Noto Sans Thai", "Noto Sans Devanagari"), size: 9pt, fill: ink, number-width: "tabular", hyphenate: false)
|
||||
#set page(
|
||||
paper: "us-letter",
|
||||
margin: (x: 0.8in, top: 0.8in, bottom: 0.8in),
|
||||
footer: context {
|
||||
set text(7.5pt, fill: quiet)
|
||||
grid(
|
||||
columns: (1fr, auto),
|
||||
[#d.program_version · head #d.through_seq],
|
||||
align(right)[Page #counter(page).display() of #counter(page).final().first()],
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
#align(center)[
|
||||
#text(15pt, fill: luma(45), d.company) \
|
||||
#v(4pt)
|
||||
#text(10.5pt, weight: "bold", d.title) \
|
||||
#v(2pt)
|
||||
Through #d.through · scope #d.scope
|
||||
]
|
||||
#v(12pt)
|
||||
|
||||
#grid(
|
||||
columns: (auto, 1fr),
|
||||
row-gutter: 5pt,
|
||||
column-gutter: 12pt,
|
||||
text(fill: quiet)[Books' figure head], [#d.through_seq],
|
||||
text(fill: quiet)[Trial balance hash], text(size: 7.5pt)[#d.tb_hash],
|
||||
text(fill: quiet)[Findings hash], text(size: 7.5pt)[#d.findings_sha256],
|
||||
text(fill: quiet)[Result], [#d.summary],
|
||||
)
|
||||
#v(12pt)
|
||||
|
||||
#text(10pt, weight: "bold")[Findings]
|
||||
#v(4pt)
|
||||
#if d.findings.len() == 0 [
|
||||
No findings.
|
||||
] else [
|
||||
#table(
|
||||
columns: (auto, auto, auto, 1fr),
|
||||
stroke: (x: none, y: hair),
|
||||
inset: (x: 4pt, y: 4pt),
|
||||
table.header([*Severity*], [*Code*], [*About*], [*Finding*]),
|
||||
..d.findings.map(f => (f.severity, f.code, f.subject, f.message)).flatten(),
|
||||
)
|
||||
]
|
||||
#v(14pt)
|
||||
|
||||
#text(10pt, weight: "bold")[What was checked]
|
||||
#v(4pt)
|
||||
#table(
|
||||
columns: (auto, auto, 1fr),
|
||||
stroke: (x: none, y: hair),
|
||||
inset: (x: 4pt, y: 4pt),
|
||||
table.header([*Code*], [*Severity*], [*True when it is reported*]),
|
||||
..d.checks.map(c => (c.code, c.severity, c.predicate)).flatten(),
|
||||
)
|
||||
Reference in new issue
Block a user