Report Ops · automation blueprint · prepared 9 July 2026

No more silly mistakes.

One pipeline from the execution team's Google Sheet to the client-ready Slides deck — generated by the system, checked at a QC gate, timestamped at every handoff, and logged forever. Nobody retypes a number again.

0 hand-copied numbers every handoff timestamped QC gate before every send history you can query by client
The report lifecycle — tracked end to end
Request
exec team submits via form
received 09:14
Sheet
data lands in a locked template that validates itself
data ready 11:02
no human hands
Deck
generated from template — charts stay linked to the sheet
generated 11:03
QC gate
a second pair of eyes approves — or rejects with a reason
approved 14:20
PIC
deck + PDF delivered, submission recorded
submitted 14:31
LEAD TIME · 5h 17m — measured automatically, request to submission every timestamp flows to the dashboard; nothing depends on someone updating Asana
AThe problem today

Three leaks in one manual workflow.

Today the execution team hands over a Google Sheet, and the reporting team re-types its numbers into Google Slides by hand. That single manual hop causes all three problems at once.

LEAK 01

Every paste is a chance to be wrong

A deck carries dozens of figures. Copying them by hand means transposed digits, stale periods, the wrong client's number in the wrong box — and the mistake is only as visible as the reviewer is careful. Sometimes it reaches the PIC.

LEAK 02

Lead time is invisible

Asana said done or not done — it never said where the days went. You can't answer "how long do we actually take, and which stage is slow?" because nobody records the handoffs.

LEAK 03

No gate, no memory

Whatever the maker exports is what goes out. There's no structural checkpoint before the send, no log of what got caught, and no history to compare performance across client categories.

BThe new pipeline

Six stages. Every one leaves a timestamp.

The whole flow stays inside tools the team already lives in — Google Forms, Sheets, Slides — with a small script doing the dangerous part (moving numbers) and a webhook reporting every handoff to the dashboard.

Stage 0intake

Request comes in through a form, not a chat message

The execution team (or the PIC) submits a short Google Form: client, category, report type, period, due date. The submission auto-creates a row in the tracker with a request ID — no request exists outside the system.

Google Form + tracker Sheet
captures received_at, client, category, PIC, due date
Stage 1data contract

Data lands in a locked template that refuses to be wrong

The execution team fills a standard template sheet — fixed tabs, named ranges, dropdown validation, protected formulas. A validation tab computes checks (totals reconcile, no blanks, period matches the request) into one PASS / FAIL cell. Bad data is caught at the source, by the sheet itself.

Sheets — validation + protected ranges
captures data_ready_at
Stage 2generate

One click builds the deck — no human touches a number

A script duplicates the client category's master Slides template, then runs one API call that fills every {{placeholder}} from named ranges, merges tables cell by cell, and refreshes linked Sheets charts in place. The figures in the deck are the figures in the sheet — transcription error is structurally impossible.

Apps Script + Slides API
captures generated_at (≈ 1 minute)
Stage 3QC gate

Maker–checker: a second person approves, with evidence

The reviewer gets the deck preview plus an auto-computed assertion summary (totals, anomalies vs last period). They approve, or reject with a reason code — and the rejection reason is data too, feeding the error taxonomy on the dashboard.

review checklist + approve action
captures qc_at, qc_by, result + reason
Stage 4submit

Approved decks go out; unapproved decks can't

On approval, one atomic submit step runs: the final deck is rendered with static (unlinked) charts, exported to a hashed PDF for the archive, and the Slides file is locked read-only — the version the PIC received can never silently change. It goes out by email from a dedicated reports@ account, with an optional WhatsApp notification whose delivered receipt stops the clock objectively.

PDF export + Drive lock + Gmail / WhatsApp
captures submitted_at → lead time closes
Stage 5log

Every transition becomes a row in the archive

Each stage change POSTs a small event to the dashboard's API on Cloudflare. The tracker sheet stays the working view; the database becomes the permanent memory — queryable by client, category, month, reviewer, error type. Events carry idempotent IDs, and a nightly sweep reconciles sheet vs database so one missed webhook can't corrupt the stats.

Cloudflare Worker + D1
stores the full event history
CThe engine

Why mistakes become impossible, not just less likely.

The fix isn't "be more careful" — it's removing the manual hop entirely. Three mechanisms do that.

The data contract CATCHES BAD INPUT

The execution team fills a fixed template, not a freeform sheet. Named ranges define exactly where each figure lives; validation rules and protected ranges stop typos at entry; a formula-computed PASS/FAIL cell blocks generation until the data is clean.

Placeholder merge MOVES TEXT

Every text figure in the deck is a tag, not a typed value. The generator replaces each tag from its named range — the mapping is written once, reviewed once, and then it's deterministic forever.

Linked charts, merged tables MOVE VISUALS

Charts are native Sheets charts linked into Slides, refreshed via the API at generation time — no one draws them. Tables are merged cell by cell from named ranges: the Slides API can refresh linked charts but not linked tables (a gap open since 2017), and the per-cell merge is Google's own documented pattern — equally typo-proof.

One generator, every client SCALES

Each client category gets its own master template deck; the same script serves all of them. Adding a client is a template + a mapping row — not a new manual process.

DThe QC gate

Nothing reaches a PIC without a recorded yes.

Automation removes transcription errors; the gate catches everything else — wrong framing, odd numbers upstream, a period the client didn't ask for. The reviewer sees evidence, not just slides.

Assertions run before a human looks

The generator computes sanity checks the moment the deck is built — reconciliation, blanks, date ranges, and a delta-vs-last-period anomaly band. The reviewer starts from evidence, so review time drops and attention goes where it matters.

Maker–checker, enforced

The person who prepared the data can't approve their own deck. Approval is one click, recorded with who and when — and the send step exists only on the approved branch, so the gate is enforced by structure, not discipline. That's the QC audit trail that never existed before.

Rejections teach the system

Every rejection carries a reason code (data issue, framing, template bug, upstream numbers). The dashboard turns those into an error taxonomy — so you can see what kind of mistakes still happen, and fix the class, not the instance.

EThe control tower

One screen answers "where is every report, and how are we doing?"

Hosted on Cloudflare — one Worker serving the dashboard and a tiny API, with a D1 database as the permanent event store, fed by the pipeline's webhooks, protected by Cloudflare Access (free for 50 users, logs in with Google). This is the QC overview, the Asana replacement, and the performance archive in one place. Set SLA targets only after 4–6 weeks of measured baseline — commit to "85% of reports within X days", never averages. Below, a preview with representative numbers.

report-ops · control tower — July 2026 (month to date) live · fed by pipeline events
Median lead time
3.2 days
▼ 0.8d vs May
Reports submitted
47
31 in Jul · 5 in flight
On-time to PIC
94%
▲ 3pp vs May
Caught at QC gate
18%
errors stopped before the PIC
Median lead time, weekly
request received → submitted to PIC · days
6 4 2 0 target 3.0d 3.2d Apr May Jun Jul
Reports by client category
submitted per month
Telco Banking FMCG Public sector
30 20 10 0 20 Feb 22 Mar 24 Apr 25 May 29 Jun 31 Jul Telco 12 Banking 8 FMCG 6 Public 5
Where the 3.2 days actually go
median time in each stage · this month
request submitted · 3.2d waiting for data · 1.4d generate · 0.1d QC review · 1.2d send · 0.5d

This is the chart Asana could never draw. Generation costs minutes; the days live in waiting-for-data and QC. Once you can see that, you know exactly which conversation to have — with the execution team, not the deck-makers.

In flight right now
every open request, its stage, and how long it has sat there
RequestReportCategoryStageTime in stageDue / lead time
REQ-1047Telco QBR deckTelcoQC review0.4ddue today
REQ-1046Monthly performanceBankingGenerating2 mindue in 2d
REQ-1045Campaign wrap-upFMCGWaiting for data2.6d ⚠due in 1d
REQ-1044SLA compliance reportPublic sectorSubmitted ✓lead 2.9d
REQ-1043Weekly ops summaryTelcoSubmitted ✓lead 1.8d
FBuild vs buy

What each route costs, honestly.

Researched July 2026. The recommended stack is the first two rows — everything the pipeline needs, at roughly RM 0/month.

RouteWhat it gives youCostVerdict
Apps Script + Slides APIbuild — in Google Workspace The generator itself: template copy, then one batch API call doing every placeholder, table cell, and chart refresh; PDF export, emails, webhooks. A deck costs ~2–5 API calls against per-minute quotas with no daily cap — roughly 100–1000× headroom at this volume. RM 0 CORE — BUILD THIS
Cloudflare Worker + D1build — dashboard & archive The control tower: one Worker serves the dashboard and API, D1 stores the event history (free tier 5M row-reads/day, 100k writes/day — ~1000× headroom), Cloudflare Access adds Google login free for up to 50 users. Pages is in maintenance mode in 2026 — a single Worker with static assets is the recommended shape. RM 0 CORE — BUILD THIS
n8n (self-hosted)orchestrate & notify Already running in-house, free for internal use. First-class approval gates (Send-and-Wait on Gmail/Telegram/WhatsApp), an official WhatsApp Cloud API node with delivered/read webhooks, hourly SLA-breach sweeps, error workflows. Its Slides node is text-only with a known silent-failure bug — orchestrate here, keep the merge in Apps Script. RM 0 (existing) ADD — GATE + NOTIFY
Looker Studiozero-build dashboard Free, native Sheets connector, ~15-minute data freshness — an afternoon of setup. No immutable event history, no QC actions, strictly read-only. The right week-one stopgap while the Worker + D1 build lands. RM 0 STOPGAP — PHASE 1
SlideFill / Document Studiono-code merge add-ons SlideFill is free and unlimited; Document Studio's free tier is 25 rows/month. Both do Sheets→Slides merges if scripting ever stalls — but they are third-party OAuth grants with no SLA, and neither logs the timestamps this whole plan runs on. RM 0 – US$~30/yr FALLBACK ONLY
Make / Zapierhosted automation Make's Slides template module is the most polished of the three platforms (Core US$9/mo); Zapier needs Professional at US$19.99+/mo because its free tier can't run multi-step pipelines. Neither does anything the self-hosted n8n doesn't already do. US$ 9–20+/mo SKIP — HAVE N8N
RollstackSaaS — Sheets → Slides sync The only product that genuinely automates Sheets → Slides decks, and the feature benchmark for this build. But Starter is US$1,249/month billed annually (~RM 66k/yr), and it still ships no intake, no QC gate, and no lead-time tracking. US$ 1,249+/mo BENCHMARK, NOT A BUY
AgencyAnalytics / Swydo / DashThis / Whatagraph / Databoxclient-reporting SaaS All replace the Slides deliverable with their own PDF/portal — the PIC stops receiving a deck. Only AgencyAnalytics (US$20/client/mo) has a real approval gate; none track request-to-submission lead time or expose history for category analytics. US$ 20/client – 463/mo DOESN'T FIT
GRollout

Seven weeks, four phases, one pilot first.

Each phase ships something the team uses immediately. The generator is proven against the manual process before anything is retired — including Asana.

Phase 1weeks 1–2

Foundation — intake form, tracker, data contract

  • Google Form intake → tracker sheet with auto request IDs and received_at
  • Data-contract template for the pilot client category: named ranges, validation rules, protected ranges, PASS/FAIL tab
  • Looker Studio stopgap view on the tracker so statuses are visible from day one
exec team submits via the form, and the sheet catches bad data by itself
Phase 2weeks 2–4

The generator — pilot on one client category

  • Master Slides template for the pilot category: {{placeholders}}, per-cell table tags, linked Sheets charts
  • Apps Script generator: copy template, one batch call (merge tags + table cells, refresh charts), save to Drive
  • Run in parallel with the manual process for two cycles — diff the decks, fix the mapping
generated deck matches the manual deck exactly, with zero retyped numbers
Phase 3weeks 4–5

QC gate + submission

  • Auto-assertions (reconciliation, blanks, period, anomaly band) surfaced to the reviewer
  • Approve / reject with reason codes; maker–checker enforced; approval unlocks the send
  • Submit step: static-chart final deck, hashed PDF archive, Drive read-only lock, email from reports@ (optional WhatsApp receipt); submitted_at recorded
nothing reaches a PIC without a recorded approval
Phase 4weeks 5–7

Control tower — dashboard, archive, retire Asana

  • Cloudflare Worker dashboard + D1 event store; Apps Script webhooks on every stage change
  • Backfill recent history from the tracker; lead-time and category analytics go live
  • Roll remaining client categories onto the pipeline; retire Asana gatekeeping
every request's lead time is visible, and monthly category performance comes from the archive

Fine print from the research — traps we already know about

  • Linked tables don't refresh via the API — only charts do. Merge table cells as placeholders (Google's own documented pattern).
  • Simple onEdit triggers can't call external URLs — the webhook POSTs need installable triggers.
  • Timestamps must be script-written — never =NOW(), which recalculates and rewrites history.
  • Pasting bypasses Sheet validation — protected ranges are the load-bearing control; validation rules are the helper.
  • n8n's Slides node is text-only and has a known silent-failure bug — keep the merge in Apps Script; n8n gates and notifies.
  • D1 bills rows scanned, not returned — index the events table from day one.
  • Google plans to bill API quota overages later in 2026 — keep batching and exponential backoff (a deck is only ~2–5 calls anyway).
  • Unofficial WhatsApp gateways get numbers banned (typically within 2–8 weeks) — official Cloud API only; a utility template costs ≈ RM 0.06.
HBefore we build

Five decisions, four numbers to watch.

Decide first

  1. Pilot category — which client category goes first? (Pick the most standardized deck.)
  2. QC ownership — who are the maker–checker pairs, and who breaks ties?
  3. PIC channel — email PDF only, or add a WhatsApp utility template (official Cloud API, ≈ RM 0.06/send) for the delivered-receipt timestamp?
  4. Asana overlap — keep it read-only during the transition, or cut over at Phase 4?
  5. Deck archive — Drive folder taxonomy and naming convention for sent decks (versioned, never edited after send).

KPIs that prove it worked

  • Errors reaching a PIC target 0
  • Median lead time, request → submission baseline, then −25%
  • Reports submitted on time ≥ 95%
  • QC catch rate trending down*

* early on, a high catch rate is good (the gate works); over time it should fall as the data contract stops errors at the source.