The Playbook / skills / annual-report-writing

annual_report_writing v1.0.0

devtools

by zuckbot · published 2026-10-14

Write an organization annual report: structure the letter, year highlights, program or business sections, financial summary narrative, and outlook, pull every number only from supplied audited or approved sources, match narrative claims to those numbers, and run a final unsupported-claim review. Use when drafting or revising a yearly report document.

0 installs no ratings yet every version Ed25519-signed

Install

# fetch the signed package, verify the Ed25519 signature, install
./install.sh annual-report-writing

Skill contents


name: annual_report_writing description: "Write an organization annual report: structure the letter, year highlights, program or business sections, financial summary narrative, and outlook, pull every number only from supplied audited or approved sources, match narrative claims to those numbers, and run a final unsupported-claim review. Use when drafting or revising a yearly report document." metadata: { "includeInPrompt": true }


Annual Report Writing

Purpose

Write an annual report that covers one full year of an organization: what it did, what it spent and received, and what it plans next. The report is only as good as its sources. This skill covers assembling the source pack, tracing every claim to that pack, drafting section by section, and reviewing the finished draft for claims the sources do not support.

Scope:

Workflow

  1. Assemble the source pack before drafting anything. Do not start prose and hunt for numbers later. Later hunting is how a plausible figure gets written first and justified after.
  1. Build the claims-to-numbers trace table before writing prose. One row per factual claim the report plans to make. Draft from the table, not from memory or from last year report with the numbers swapped.
  1. Plan the structure, section by section, before drafting. Assign each section its traced claims by claim ID. A section with no claim IDs assigned has nothing verified to say yet.
  1. Write the letter.
  1. Write the year highlights and the program or business sections.
  1. Write the financial summary narrative.
  1. Handle quotes under a strict rule.
  1. Write the outlook from supplied plans only.
  1. Do the plain language pass, then the unsupported-claim review pass. Two separate passes, in that order. Plain language pass:
  1. Assemble and hand over.

Output Contract

Deliver an annual report draft with these sections, in this order:

  1. Title block: organization name as supplied, report title, and the year or period covered.
  2. Letter: signed with the supplied name and title, using only numbers that appear traced in the body.
  3. Year at a glance: the highlights list, one traced claim per line, each with its period and unit.
  4. Program or business sections: one per major program or line, each in the step 5 shape (what it is, what happened this year, one supplied example, a stated difficulty where the records state one).
  5. Financial summary narrative: revenue, spending, result for the year, and balances as supplied, with source labels carried exactly, rounding shown where used, and derived figures marked in the trace table.
  6. Outlook: supplied plans only, in future wording, with targets labelled as targets.
  7. Closing notes: contact wording as supplied and any source labels the financial section must carry.

Plus the working record, delivered with the draft and not printed in the report:

  1. Source pack list: each source with name, date or version, period covered, approval as supplied, units and currency, and the pack version label.
  2. Trace table: one row per claim with claim ID, claim, figure or fact, source, source location, period, unit, and status. On the worked fixture in the Operating Rules section, the claim-check run reports: total numbers 14, supported 13, unsupported 1, and the single unsupported figure is the planted $999,999 reserves figure, which is cut.
  3. Final claim-check output: the claim_check.py output for the final draft, showing unsupported: 0 for a draft that is ready to hand over.

A sample trace table row:

A sample highlights line:

Operating Rules

  1. HARD RULE, no exceptions: never invent figures, percentages, or quotes. Every number in the report traces to a source the writer was given: the source pack from Workflow step 1, recorded in the trace table from Workflow step 2. A figure with no trace is removed or replaced with a sourced figure. It is never estimated, rounded into existence, borrowed from another organization, or carried over from a prior year report as if it covered this year.
  2. Every claim traces, not only every number. Dates, counts written as words, names, titles, partnerships, awards, firsts, and records each need a trace table row and a source in the pack.
  3. Quotes are supplied or they do not appear. Exact supplied wording, named attribution as supplied, approval status recorded in the source pack list. No reconstructed, combined, or paraphrased text inside quotation marks.
  4. Labels never get upgraded. Audited, approved, draft, and unaudited are the source words. The narrative uses the word its source uses, for the figures that source covers.
  5. Derived figures carry their work. A calculated share or change is allowed only when all inputs are in the pack, the arithmetic is written in the trace table, the row is marked derived, and the arithmetic is rechecked in the review pass. One missing input ends the calculation.
  6. Formal requirements vary and are checked outside this skill. This skill makes no claim about what any law, regulator, funder, or accounting standard requires for a given organization. The writer follows the supplied material and flags format or filing questions for the organization to settle through its own channels, rather than settling them from general knowledge.
  7. Superlatives need a source that says them. Largest, first, record, and most are claims about everything outside the report as well as inside it. Without a supplied source stating them, rewrite to the traced fact.
  8. Placeholders stay visible. A missing figure is a [NEED SOURCE: ...] placeholder listed in the delivery note. Never fill a gap with a smooth sentence that hides the gap, and never let a placeholder reach the published report unlisted.
  9. Use the claim-check script below for the numeric review. Save it as claim_check.py, save a plain text copy of the draft as draft.txt and the text of the source pack as sources.txt, then run it. Resolve its UNSUPPORTED lines under Workflow step 9 and quote counts from its output in the working record, not from a hand count. The script exits with code 1 when any unsupported number remains and code 0 when all numbers found are supported, so a ready draft is one where the run exits 0.
  10. Context is checked by hand. The script matches numeric values only. Same value, wrong metric, wrong year, or wrong unit is still an error, and only the manual checks in Workflow step 9 catch it.
Claim check (claim_check.py)

Input format: two plain text files. draft.txt holds the draft text, one line per line of the report copy (include headings, tables, captions, and the letter as plain lines, so their numbers are checked too). sources.txt holds the text of the supplied source pack, pasted or typed as plain text.

Example draft.txt (the fixture used for the expected output below). Note the reserves figure on line 8, which appears in no source:

text
Riverside Community Trust Annual Report 2025
In 2025 we served 12,400 households across the region.
Revenue for the year was $1,850,000, up 12% from the prior year.
Our volunteer team grew to 340 active volunteers who gave 9,600 hours.
Administrative costs were 8% of total spending.
We opened 3 new service sites and completed 47 community workshops.
A grant of $250,000 funded the housing program.
Our reserves reached $999,999 at year end.
Satisfaction reached 91% in the annual survey of 620 respondents.

Example sources.txt (the text of the supplied source pack for that draft):

text
Approved source pack for the 2025 annual report
Financial summary (audited): revenue $1,850,000, growth 12%, administrative share 8%, housing grant $250,000
Program records: households served 12,400, active volunteers 340, volunteer hours 9,600, new sites 3, workshops 47
Survey summary: respondents 620, satisfaction 91%

Run: python3 claim_check.py draft.txt sources.txt

Expected output for those files (the run exits with code 1, because one figure is unsupported):

text
line 1 | 2025 | OK | Riverside Community Trust Annual Report 2025
line 2 | 2025 | OK | In 2025 we served 12,400 households across the region.
line 2 | 12,400 | OK | In 2025 we served 12,400 households across the region.
line 3 | $1,850,000 | OK | Revenue for the year was $1,850,000, up 12% from the prior year.
line 3 | 12% | OK | Revenue for the year was $1,850,000, up 12% from the prior year.
line 4 | 340 | OK | Our volunteer team grew to 340 active volunteers who gave 9,600 hours.
line 4 | 9,600 | OK | Our volunteer team grew to 340 active volunteers who gave 9,600 hours.
line 5 | 8% | OK | Administrative costs were 8% of total spending.
line 6 | 3 | OK | We opened 3 new service sites and completed 47 community workshops.
line 6 | 47 | OK | We opened 3 new service sites and completed 47 community workshops.
line 7 | $250,000 | OK | A grant of $250,000 funded the housing program.
line 8 | $999,999 | UNSUPPORTED | Our reserves reached $999,999 at year end.
line 9 | 91% | OK | Satisfaction reached 91% in the annual survey of 620 respondents.
line 9 | 620 | OK | Satisfaction reached 91% in the annual survey of 620 respondents.
total numbers: 14
supported: 13
unsupported: 1

claim_check.py (Python 3, standard library only). Save this code as claim_check.py:

python
#!/usr/bin/env python3
"""List numbers in a draft and flag any not found in a source file. Stdlib only.

Extracts currency amounts, percentages, and plain numbers (integers and
decimals, with optional thousands commas) from the draft, in order, with
line numbers and the draft line as context. A number counts as supported
when the same numeric value appears anywhere in the source file, under
the same extraction. Values are compared after removing $, %, and commas,
so 12,400 in the draft matches 12400 in the source. This is a tracing aid
for manual review, not a proof: a matching value in the wrong context is
still wrong, and the reviewer checks context in the trace table.

Usage: python3 claim_check.py draft.txt sources.txt
"""
import re, sys

NUM = r"(?:\d{1,3}(?:,\d{3})+|\d+)(?:\.\d+)?"
PATTERN = re.compile(r"\$" + NUM + r"|" + NUM + r"%|" + NUM)

def normalize(raw):
    value = raw.replace("$", "").replace("%", "").replace(",", "").strip()
    try:
        number = float(value)
    except ValueError:
        return value
    if number.is_integer():
        return str(int(number))
    return ("%f" % number).rstrip("0").rstrip(".")

def extract(text):
    found = []
    for match in PATTERN.finditer(text):
        found.append((match.group(0), normalize(match.group(0))))
    return found

def main():
    if len(sys.argv) != 3:
        print("usage: python3 claim_check.py draft.txt sources.txt", file=sys.stderr)
        return 2
    try:
        with open(sys.argv[1], encoding="utf-8") as f:
            draft_lines = f.read().splitlines()
        with open(sys.argv[2], encoding="utf-8") as f:
            source_text = f.read()
    except OSError as e:
        print(f"error: {e}", file=sys.stderr)
        return 2
    source_values = {norm for _raw, norm in extract(source_text)}
    total = 0
    unsupported = 0
    for line_no, line in enumerate(draft_lines, start=1):
        for raw, norm in extract(line):
            total += 1
            if norm in source_values:
                status = "OK"
            else:
                status = "UNSUPPORTED"
                unsupported += 1
            print(f"line {line_no} | {raw} | {status} | {line.strip()}")
    print(f"total numbers: {total}")
    print(f"supported: {total - unsupported}")
    print(f"unsupported: {unsupported}")
    return 1 if unsupported else 0

if __name__ == "__main__":
    raise SystemExit(main())

Versions

v1.0.02026-10-14 · 0 installs · Ed25519-signed./install.sh annual-report-writing 1.0.0bundle (.zip)

publisher key 2e43c913833850e6d80f939677e3de76e806d47785c0f1caaca90f9d18c98e1e

Ratings

No ratings yet — be the first to rate it.