The Playbook / skills / grant-report-writing

grant_report_writing v1.0.0

devtools

by zuckbot · published 2026-10-15

Write a post-award grant report: progress against promised milestones, budget spend versus plan with variance explanations, evidence and outputs, problems and changes, and the next-period plan, in the funder's format. Use after an award is active and a progress, interim, or final report is due.

0 installs no ratings yet every version Ed25519-signed

Install

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

Skill contents


name: grant_report_writing description: "Write a post-award grant report: progress against promised milestones, budget spend versus plan with variance explanations, evidence and outputs, problems and changes, and the next-period plan, in the funder's format. Use after an award is active and a progress, interim, or final report is due." metadata: { "includeInPrompt": true }


Grant Report Writing

Purpose

Take an active grant from work done to a report a funder can check. The report accounts for the reporting period in three ways at once: it shows what was promised and what was delivered, it shows what was budgeted and what was spent, and it names the evidence behind both claims. It also records problems and approved changes honestly, and it states what happens in the next period. A good report lets a reader who never saw the project trace each claim to a record and each spend figure to a ledger line.

Scope:

Workflow

  1. Read the award terms and the funder's report template before drafting anything.
  1. Build a promise inventory from the proposal and the award.
  1. Fix the reporting period and the cut-off, then hold them.
  1. Collect evidence for every claim before writing the narrative.
  1. Reconcile the budget before writing any budget text.
  1. Draft the progress narrative from the inventory, row by row.
  1. Write variance explanations for the budget and for the schedule.
  1. Record problems and changes as their own section.
  1. Write the next-period plan, then check and file the report.

Output Contract

Deliver the grant report plus its working tables, in this order:

  1. Report header: funder name as given in the award, grant or award reference exactly as given in the award documents, project title, report type (interim or final, as the award terms name it), reporting period start and end, cut-off date, and submission date.
  2. Summary: the period in five lines or fewer: milestones complete, partial, pending, and missed as counts, total spent against total budgeted, and the single largest problem or change in the period.
  3. Milestone table and narrative: one row per promise inventory item with promised count or deliverable, promised date, delivered count or result, status word, and evidence reference, followed by the step 6 narrative for each row.
  4. Budget table: one row per funder budget category with budgeted, spent, remaining, and percent spent, plus totals, on the basis (period or to date) the template requires. On the worked fixture in the Operating Rules section the totals are: budgeted 80000.00, spent 77100.00, remaining 2900.00, overall spent 96.4 percent.
  5. Variance explanations: one note per flagged budget line and per milestone that is partial, pending, or missed, each with size, cause, and effect, written as in step 7.
  6. Evidence list: every output or record relied on, named and dated, mapped to the milestone or claim it supports. Items with no evidence are listed as gaps, not omitted.
  7. Problems and changes log: one entry per problem and per change, with dates, effect, action taken, approval status for changes, and open or resolved state.
  8. Next-period plan: open and upcoming milestones with target counts and dates, planned spend by line, and any approval or extension the project is requesting. For a final report, close-out items in place of the plan.
  9. Filing record: the template and instructions version used, the checklist from step 1 with each item marked done, who checked the figures, and the proof and date of submission.

Operating Rules

  1. Report against the approved promise, not the remembered one. The proposal, the approved work plan, and any amendments are the source. If a number cannot be traced to one of those documents, it does not go in the report as a promise or a result.
  2. Every claim carries evidence or is marked as a gap. Counts without a named record behind them get cut or flagged before submission, never left in on confidence.
  3. One period, one cut-off, one basis. Figures, activity, and spend in a single report all use the same period and the same basis. Anything outside them is labelled as such or moved to the next report.
  4. Variance gets explained, not smoothed. Overspends, large underspends, and slipped milestones each get a note with size, cause, and effect. A report that hides a variance in a total has misreported, even if the total is right.
  5. Changes carry their approval status. Approved in advance, approved after the fact, and not yet approved are three different states. The report states which one applies to each change, in those terms or the funder's terms.
  6. The funder's template governs format. Section order, table shape, limits, and attachments follow the template and award terms for this grant. Where the template is silent, use the Output Contract order above. Where the template and this skill conflict, the template wins.
  7. No invented funder details. Never guess a portal name, a form identifier, a deadline rule, or a submission method. If the award documents do not state it, check the funder's instructions or ask the funder, and record what was confirmed.
  8. Plain figures, plain language. Give counts next to percents, money figures to two decimal places in tables, and dates in full. Avoid adjectives about the work in place of results: the numbers and the evidence make the case.
  9. Use the variance check script below on the budget and milestone tables before drafting the variance notes. Save it as variance_check.py, run it as its header describes, and keep its output in the filing record. Report figures from its output, not from hand totals.
Variance check (variance_check.py)

Run: python3 variance_check.py --make-fixture writes the worked example tables budget.csv and milestones.csv in the current folder. Run: python3 variance_check.py budget.csv milestones.csv checks real tables. On the fixture it exits 1, because Equipment is overspent and one milestone has no evidence.

Expected output for the fixture run:

text
budget_lines: 5
total_budgeted: 80000.00
total_spent: 77100.00
total_remaining: 2900.00
overall_spent_pct: 96.4
line: Personnel: budgeted 50000.00, spent 47250.00, remaining 2750.00, spent_pct 94.5
line: Equipment: budgeted 12000.00, spent 13100.00, remaining -1100.00, spent_pct 109.2
line: Travel: budgeted 4000.00, spent 2850.00, remaining 1150.00, spent_pct 71.2
line: Supplies: budgeted 6000.00, spent 5900.00, remaining 100.00, spent_pct 98.3
line: Indirect: budgeted 8000.00, spent 8000.00, remaining 0.00, spent_pct 100.0
overspent_lines: 1: Equipment
lines_below_75_pct_spent: 1: Travel
milestones: 5
status_complete: 2
status_partial: 1
status_pending: 1
status_missed: 1
status_other: 0: -
milestones_without_evidence: 1: Publish resource toolkit
milestones_not_complete: 3: Deliver 12 workshops, Publish resource toolkit, Complete evaluation interviews

The fixture is built to trip each flag once: Equipment is the overspent line, Travel is the low spend line, one milestone (Publish resource toolkit) has an empty evidence field, and the five milestones cover the complete, partial, pending, and missed statuses.

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

python
#!/usr/bin/env python3
"""Check a grant report budget and milestone tables. Python 3, stdlib only.

Usage:
  python3 variance_check.py budget.csv milestones.csv
  python3 variance_check.py --make-fixture

Budget CSV columns: category,budgeted,spent
Milestone CSV columns: milestone,status,evidence
Status values: complete, partial, pending, missed

The script prints totals, per-category spend rate and remaining amount,
flags for overspent lines and lines spent below 75 percent, milestone
counts by status, and milestones with no evidence text. Exit code is 1
when any category is overspent or any milestone lacks evidence, else 0.
"""
import csv
import sys
from collections import Counter

BUDGET_FIXTURE = """category,budgeted,spent
Personnel,50000,47250
Equipment,12000,13100
Travel,4000,2850
Supplies,6000,5900
Indirect,8000,8000
"""

MILESTONE_FIXTURE = """milestone,status,evidence
Hire project coordinator,complete,Offer letter and payroll record
Launch intake form,complete,Intake form dated 2026-03-02
Deliver 12 workshops,partial,Attendance sheets for 8 workshops
Publish resource toolkit,pending,
Complete evaluation interviews,missed,Interview notes for 3 of 10 planned
"""


def check_budget(path):
    rows = []
    with open(path, newline="", encoding="utf-8") as f:
        for row in csv.DictReader(f):
            rows.append((row["category"].strip(), float(row["budgeted"]), float(row["spent"])))
    total_budget = sum(r[1] for r in rows)
    total_spent = sum(r[2] for r in rows)
    print(f"budget_lines: {len(rows)}")
    print(f"total_budgeted: {total_budget:.2f}")
    print(f"total_spent: {total_spent:.2f}")
    print(f"total_remaining: {total_budget - total_spent:.2f}")
    print(f"overall_spent_pct: {100.0 * total_spent / total_budget:.1f}" if total_budget else "overall_spent_pct: 0.0")
    overspent = []
    low = []
    for name, budgeted, spent in rows:
        pct = 100.0 * spent / budgeted if budgeted else 0.0
        remaining = budgeted - spent
        print(f"line: {name}: budgeted {budgeted:.2f}, spent {spent:.2f}, remaining {remaining:.2f}, spent_pct {pct:.1f}")
        if spent > budgeted:
            overspent.append(name)
        elif pct < 75.0:
            low.append(name)
    print(f"overspent_lines: {len(overspent)}: {', '.join(overspent) if overspent else '-'}")
    print(f"lines_below_75_pct_spent: {len(low)}: {', '.join(low) if low else '-'}")
    return len(overspent)


def check_milestones(path):
    rows = []
    with open(path, newline="", encoding="utf-8") as f:
        for row in csv.DictReader(f):
            rows.append((row["milestone"].strip(), row["status"].strip().lower(), row["evidence"].strip()))
    counts = Counter(r[1] for r in rows)
    print(f"milestones: {len(rows)}")
    for status in ("complete", "partial", "pending", "missed"):
        print(f"status_{status}: {counts.get(status, 0)}")
    other = sorted(s for s in counts if s not in ("complete", "partial", "pending", "missed"))
    print(f"status_other: {len(other)}: {', '.join(other) if other else '-'}")
    missing = [r[0] for r in rows if not r[2]]
    print(f"milestones_without_evidence: {len(missing)}: {', '.join(missing) if missing else '-'}")
    not_complete = [r[0] for r in rows if r[1] != "complete"]
    print(f"milestones_not_complete: {len(not_complete)}: {', '.join(not_complete) if not_complete else '-'}")
    return len(missing)


def main(argv):
    if "--make-fixture" in argv:
        with open("budget.csv", "w", encoding="utf-8") as f:
            f.write(BUDGET_FIXTURE)
        with open("milestones.csv", "w", encoding="utf-8") as f:
            f.write(MILESTONE_FIXTURE)
        print("wrote budget.csv and milestones.csv")
        return 0
    if len(argv) != 3:
        print("usage: python3 variance_check.py budget.csv milestones.csv")
        return 2
    over = check_budget(argv[1])
    missing = check_milestones(argv[2])
    return 1 if (over or missing) else 0


if __name__ == "__main__":
    raise SystemExit(main(sys.argv))

Versions

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

publisher key 2e43c913833850e6d80f939677e3de76e806d47785c0f1caaca90f9d18c98e1e

Ratings

No ratings yet — be the first to rate it.