liveMay 2026

Automated client reporting

Every Monday, a finished report deck lands in an inbox without anyone touching a keyboard. The pipeline pulls the week's metrics from a BI tool, runs three parallel Claude conversations to draft the commentary, validates every number before it is allowed near a slide, and sends the deck itself.

01

At a glance

Role
I designed the pipeline architecture, decomposed the metric set into parallel task groups, wrote the task prompts, and defined every failure boundary.
Cadence
Every Monday at 08:00 UTC. No manual trigger, and no human step in the happy path.
Recipients
Account leadership and the client, by email, on a fixed distribution list.
Built on
The Anthropic API called directly.
Sources
A BI tool's saved queries and one ad-hoc SQL query, reached through a governed MCP proxy.
Writes to
A copied Google Slides deck, filled by API and emailed out. No document is touched by hand.
02

The problem

A weekly executive deck every Monday morning.

  1. Nine numbers, every week, by hand

    Pulling main performance metrics out of Redash, writing commentary around them, and assembling a deck is the same sequence of steps every Monday. All of it takes a person's morning.

03

Architecture

Scroll to see the full diagram

Cron — Monday 08:00 UTC Orchestrator Builds three independent task prompts from the metric dependency graph generate-report.ts → runParallelTasks() Task A · 180s timeout 8 saved-query metrics 1-week window Anthropic API + Redash MCP tool-use Task B · 120s timeout 1 metric, week-over-week Same query, 2-week window Anthropic API + Redash MCP tool-use Task C · 120s timeout 1 metric, ad-hoc SQL Independent SLA calc Anthropic API + Redash MCP tool-use mergeTaskResults() Deterministic validation — no model involved Rejects missing, duplicate, or out-of-ownership metrics Any failure here → cron returns 500. No deck, no email. Google Slides Google Slides API Copies the template deck, fills every placeholder Gmail Gmail API Sends the deck link to the recipient list
The load-bearing detail is the merge step: nothing downstream runs on a partial result. Any task failure, timeout, or malformed response stops the run before a slide is touched.
04

Design decisions

Most of the engineering here is in what the system refuses to do.

  1. Split the metric graph, not the code

    The nine metrics decompose into three independent groups: eight metrics sharing one saved query on a one-week window, one metric on the same query with a two-week window for week-over-week, and one metric from ad-hoc SQL. None depend on each other, so they run as three concurrent Claude conversations instead of one long one.

  2. No silent fallbacks, anywhere

    Every boundary in the pipeline throws with the specific failure identified, and the cron returns 500 rather than sending something incomplete.

  3. A proxy that never holds a credential

    The MCP server that executes queries carries no stored credentials. The caller passes them per request as headers, and the pipeline forwards only the two tools it is allowed to use — everything else the proxy offers is filtered out before Claude ever sees the tool list.

  4. Zero-touch auth

    Google access uses a refresh-token flow: the access token is exchanged automatically, cached for about an hour, and silently renewed. There is no interactive login anywhere in the runtime path — the only human dependency is granting the OAuth scopes once, up front.

05

Where it stands

9metrics compiled into every deck, from three independent data pulls
3parallel Claude conversations per run, one per disjoint metric group
6named failure boundaries, each with a defined response — none of them “send anyway”
Weeklycadence, every Monday, with zero manual steps in the happy path
0partial-failure decks possible — the merge step vetoes an incomplete run before it reaches a slide
06

What I did

I designed and built this end to end.

  1. Decomposed the metric set into a dependency graph

    Mapping which metrics share a query, a window, or a source is what made the parallel split possible.

  2. Wrote the merge contract

    Defined exactly what a valid combined result looks like — no missing metric, no duplicate, no metric claimed by the wrong task — and made that check deterministic code rather than another model call.

  3. Specified every failure boundary before the happy path

    Each row in “Where it can fail” was written before the corresponding feature, not discovered after an incident. The rule throughout: fail closed, name the boundary, never guess.

  4. Covered it with tests, not just a live run

    The test suite covers week-range math, the prompt builders, the merge contract, MCP client behaviour, environment-variable handling, cron auth, and the email builder — so the pipeline's failure modes are verifiable without waiting for next Monday.