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.
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:
- grant-writing-basics is pre-award work: proposals and letters of inquiry written to win funding. This skill is post-award work only. It starts once an award exists and its terms, budget, and reporting duty are set, and it ends when the report for a period is submitted and filed.
- This skill owns progress against promised milestones, budget spend versus plan with variance explanations, evidence and outputs, problems and changes, and the next-period plan, all in the funder's format.
- This skill does not cover writing the original proposal, negotiating award terms, bookkeeping during the period, or fundraising from other sources. It uses the proposal, the award terms, and the ledger as inputs. If those inputs disagree, step 2 records the disagreement instead of hiding it.
- No funder portal, form, or submission rule is named in this skill, because those differ by funder and change over time. Work from the funder's own report template and the award terms for the grant being reported. If the template and the award terms conflict, ask the funder which one governs and record the answer in the report file.
Workflow
- Read the award terms and the funder's report template before drafting anything.
- Pull the exact documents that govern this report: the award letter or agreement, the approved budget, any approved amendments, and the report template or instructions the funder supplied for this report. Read the reporting period, the due date, the required sections, and any word or page limits from those documents, not from a prior report.
- List every explicit reporting duty in one place: sections required, figures required, attachments required, signatures required, and the format required for each table. This list becomes the checklist in step 9.
- Note any rule the award terms place on the work itself, such as prior approval for budget moves between lines, for changes to key staff, or for changes to scope or timeline. Reports often fail on an unapproved change, not on weak writing, and the place to catch that is here, before the draft frames a change as normal.
- Check whether this is an interim report or a final report. A final report also has to close the whole award: total spend against total budget, all deliverables accounted for, and what happened to any remaining funds or equipment, as the award terms require.
- Build a promise inventory from the proposal and the award.
- Extract every commitment the funded proposal made, one row per commitment: the milestone or deliverable, the promised amount or count, the promised date, and where the promise appears (proposal section, approved work plan, or amendment). Include outputs (things produced) and outcomes (changes the project said it would cause), and keep them as separate rows, because the evidence for each differs.
- Use the approved version of each promise. If an amendment changed a date, a count, or a budget line, the amended value is the one to report against, and the inventory row notes the amendment date. Reporting against a superseded promise makes an on-track project look late and a late project look on track.
- Flag any promise that is vague in the source, such as a count with no unit or a date given as a quarter. Report it as written, state how the project measured it, and do not quietly rewrite it into an easier target.
- This inventory is the spine of the report. Every later section maps back to a row in it, and any delivered work that was never promised goes in a separate short list, not mixed into the milestone rows.
- Fix the reporting period and the cut-off, then hold them.
- State the period start, the period end, and the cut-off date for figures and activity. Work after the cut-off belongs to the next report, even if it helps the story. Mixing periods is the most common cause of figures that will not reconcile later.
- Pick one status word per milestone and use it the same way through the whole report: complete, partial, pending, or missed are workable values. Define partial in the report: what fraction or which parts are done, and what remains. A milestone that is 90 percent done is partial, not complete.
- Decide how partial progress will be counted before counting it. For a promised count, report the delivered count next to the promised count (for example 8 of 12). Do not report a percent alone; a percent hides the base number the funder approved.
- Collect evidence for every claim before writing the narrative.
- For each inventory row, name the record that proves it: attendance sheets, dated outputs, delivery receipts, published files, payroll or hiring records, partner confirmations, dated photos where the funder accepts them, or system exports. One claim, one named record at minimum.
- Check each record for three things: it is dated inside the period or clearly tied to the milestone, it names the project or grant, and the number it supports matches the number the draft will claim. A record that supports a smaller number than the draft claims means the draft number is wrong.
- Separate evidence from anecdote. A short participant quote or case note can show what an output meant, but it does not prove the output happened. Keep quotes in a clearly marked place and never let a quote stand as the only support for a count.
- Keep an evidence index: milestone, claim, record name, and where the record is filed. If the funder asks for attachments, the index is the attachment list. If the funder does not ask, the index still goes in the internal report file (step 9) so a later audit can find each record.
- Mark rows with no evidence as gaps now. A gap found in step 4 can still be closed with a real record. A gap found by the funder reads as a claim that was never true.
- Reconcile the budget before writing any budget text.
- Take spend figures from the accounting ledger for the same period and cut-off as step 3, not from receipts in a drawer or from a project spreadsheet that nobody reconciled. If a project spreadsheet is the working record, reconcile it to the ledger first and report the ledger figures.
- Lay the budget out line by line in the funder's categories: category, amount budgeted for the period or for the award to date (use the basis the template asks for), amount spent, amount remaining, and percent spent. Keep the basis consistent across every line. Mixing period figures on one line and to-date figures on the next makes the total meaningless.
- Check the arithmetic with the variance check script in the Operating Rules section. On its worked fixture it reports: 5 budget lines, total budgeted 80000.00, total spent 77100.00, total remaining 2900.00, overall spent 96.4 percent, 1 overspent line (Equipment at 109.2 percent), and 1 line below 75 percent spent (Travel at 71.2 percent). Run it on the real tables and work from its output, not from hand totals.
- Separate committed spend from actual spend if the template or terms require it. Money committed under a signed contract but not yet paid is not spent; report it in the place the template gives for it, or name it in the variance note for that line, and never add it into the spent figure.
- Confirm the treatment of indirect costs, cost share, or match if the award requires them. Report them on their own lines with the basis the award terms set, and do not fold them into direct cost lines to make a line look balanced.
- Draft the progress narrative from the inventory, row by row.
- For each milestone, write four short parts in this order: what was promised, what was delivered in the period, the evidence for it, and the status word from step 3. Short and checkable beats long and general. A reader should be able to verify one row without reading the whole report.
- Use the delivered count next to the promised count for every counted promise. Where a milestone is complete, say so plainly and stop; do not pad a completed item with background the funder already has from the proposal.
- Write about outcomes with the same discipline. State what was measured, how it was measured, how many were measured, and the result. If the outcome data is not in yet, say when it will be and report the outputs only. Do not present an output count (people reached, sessions held) as an outcome (skills gained, behaviour changed).
- Keep the narrative in period order or inventory order and use the same order as the template. If the template has its own order, the template wins.
- Write variance explanations for the budget and for the schedule.
- Explain every line the script flags, and any line the funder's template asks about: overspent lines, lines well under spent for the stage of the award, and lines where the work and the spend point in different directions (work done but little spent, or money spent but little work done).
- A usable variance note has three parts: the size of the variance in money and percent, the cause in plain terms, and the effect on the rest of the award. Example shape, using the fixture figures: Equipment spent 13100.00 against 12000.00 budgeted, a variance of 1100.00 over, caused by a higher quoted price at purchase, with the overage covered by the Travel underspend if the award terms allow that move and approval was obtained or is now requested.
- Name causes honestly and specifically: a post started late, a supplier price rose, an event was cancelled, a partner delivered late, demand was lower than planned. Vague causes such as timing differences, with no date or event attached, explain nothing and draw follow-up questions.
- Do the same for schedule variance. For each partial, pending, or missed milestone, state what slipped, why, and the new expected date. If the new date falls after the award end date, say so and state what the project is asking for, such as a no-cost extension, in the terms the award allows.
- Never balance a variance by moving figures between lines on paper. If funds were actually moved, report the move, its approval status, and both lines as they stand after the move.
- Record problems and changes as their own section.
- List each material problem in the period: what happened, when it was found, what it affected (milestone, budget line, or both), what was done about it, and whether it is resolved or still open. Include problems that were fixed inside the period; a report with no problems at all is hard to trust on a project of any size.
- List each change from the approved plan separately from problems: scope changes, timeline changes, staff changes, partner changes, and budget moves. For each, state whether the funder approved it in advance, approved it after the fact, or has not yet been asked. Do not present an unapproved change as if it were approved.
- State plainly anything that will not be delivered as promised. Naming a dropped or reduced deliverable in the report, with the reason and the effect on the project aims, is far safer than letting the final report surprise the funder with it.
- Keep blame out of this section. Name events, dates, and effects. A factual record protects the project; a section that argues with partners or staff does not.
- Write the next-period plan, then check and file the report.
- For the next period, list each open or upcoming milestone with its target count, target date, and responsible role, plus the planned spend by budget line. Tie each item back to an inventory row so the funder can see the through line from promise to plan. For a final report, replace this with the close-out items the award terms require and a short statement of what continues, if anything, after the award ends.
- Check the finished draft against the step 1 checklist, item by item: every required section present, every required figure present, attachments present, limits met. Then run a consistency pass: every number in the narrative matches the budget table or the inventory, every date falls in the period it claims, and every milestone keeps the same status word everywhere it appears.
- Have a second reviewer who did not draft the report check the figures against the ledger and the evidence index. Figure errors are the errors a drafter is least likely to catch in their own draft.
- Submit by the method and to the destination stated in the funder's own instructions for this report. Keep a complete filed copy: the submitted report, the template or instructions used, the budget and milestone tables, the evidence index, and proof of submission with its date. The filed copy is what answers a question raised months later.
Output Contract
Deliver the grant report plus its working tables, in this order:
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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))
No ratings yet — be the first to rate it.