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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
| Request | Report | Category | Stage | Time in stage | Due / lead time |
|---|---|---|---|---|---|
| REQ-1047 | Telco QBR deck | Telco | QC review | 0.4d | due today |
| REQ-1046 | Monthly performance | Banking | Generating | 2 min | due in 2d |
| REQ-1045 | Campaign wrap-up | FMCG | Waiting for data | 2.6d ⚠ | due in 1d |
| REQ-1044 | SLA compliance report | Public sector | Submitted ✓ | — | lead 2.9d |
| REQ-1043 | Weekly ops summary | Telco | Submitted ✓ | — | lead 1.8d |
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.
| Route | What it gives you | Cost | Verdict |
|---|---|---|---|
| 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 |
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.
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
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
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
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
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.
Five decisions, four numbers to watch.
Decide first
- Pilot category — which client category goes first? (Pick the most standardized deck.)
- QC ownership — who are the maker–checker pairs, and who breaks ties?
- PIC channel — email PDF only, or add a WhatsApp utility template (official Cloud API, ≈ RM 0.06/send) for the delivered-receipt timestamp?
- Asana overlap — keep it read-only during the transition, or cut over at Phase 4?
- 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.