Build log · one system, start to finish · July 2026

How I built the AI Business Pulse — and what it taught me.

Small businesses want a weekly report. Almost none of them want to maintain the tool that produces it. So I built a pipeline where a single workflow can serve many businesses and the report writes its own summary — then spent most of the time making sure it could be trusted. This is the honest version: the decisions, and the bugs that caused them.

A retro computer displaying {a_w} on its screen
// the machine this log is about One n8n workflow, a config sheet of businesses, and an AI step that writes each report — with formulas doing every calculation before the model writes a word.
4
bugs found and documented — each one left a permanent check behind
1
workflow can serve many businesses — multi-tenant by design, adding one is a single row
0
numbers written by the AI — formulas compute, the model only narrates
n8nGoogle Apps ScriptGoogle Sheets API Claude (API / Code / MCP)PythonClay GitSEO / Schema

Built and operated solo · Gainesville, FL

How I build

I find where a business is losing money or time to a manual process, and I ship the working fix — end to end, alone, fast. The stack is chosen for that: n8n for orchestration, Google Apps Script for live dashboards, Python for build pipelines, Clay for enrichment, and the Claude platform for the AI layer.

// the one rule everything obeys

Formulas compute every number before the model writes a word.

Language models are strong writers and unreliable arithmetic engines. So the sheet calculates the totals and flags deterministically, and the AI only narrates figures that were already verified. It never invents one.

That rule wasn’t theoretical. During an early build the model summed a column itself and got it wrong. The fix wasn’t a better prompt — it was removing the model’s opportunity to do arithmetic at all, and injecting the precomputed total into the prompt instead.

The problem

A weekly business review is genuinely useful and genuinely tedious. The numbers live in a spreadsheet, someone assembles them by hand, and the moment that person is busy the report stops happening. Reporting tools exist, but they ask a small business to adopt and maintain a new system — which is the reason the spreadsheet won in the first place.

So the constraint I set: the business keeps its spreadsheet, and the reporting comes to them. No new tool to learn, no migration.

The system

PIPELINE ARCHITECTURE MULTI-TENANT CONFIG SHEET · SAMPLE Gator Shine ACTIVE Perkins & Boone ACTIVE Tioga Center PAUSED one row per client · add a client = add a row FILTER Status = Active paused rows never run LOOP one pass per client isolated read each time FOR EACH ACTIVE CLIENT 01 · READ their own sheet never another client’s data 02 · COMPUTE totals by formula deterministic, before any AI 03 · NARRATE AI writes prose around numbers it cannot change 04 · SEND their inbox chase emails wait for a human OK THE TRUST RULE Formulas compute every number before the model writes a word.
// One workflow, multi-tenant by design — the config sheet is the whole control surface
01

A config sheet is the control surface

One sheet lists every client: name, their metrics sheet ID, recipient, and a status column. Adding a client is a row, not a deployment. Deactivating one is a cell edit. This is the design decision that makes it scale — ten clients would be one maintained workflow, not ten.

02

Filter, then loop per client

The workflow reads the config, filters to active rows, and iterates. Each client’s own sheet is read in isolation, so one client’s data can never appear in another’s report.

03

The sheet does the math, the model does the prose

Totals, deltas, and status flags are computed by formula. Those verified figures are handed to the AI step, which writes two or three sentences of plain English: what moved, what needs attention, what to do next.

04

A human gate on anything outbound

The reporting digest sends on a schedule. But the companion product — escalating payment-chase emails — deliberately does not send on its own. It drafts, and a person approves. That is a product decision, not a technical limitation.

The engineering judgment

The interesting work wasn’t wiring the happy path — a model does that before lunch. It was the four times I didn’t trust it yet. Each one left a permanent check behind.

INCIDENT 01

Proving isolation instead of assuming it

A loop setting (executeOnce) looked like it might collapse a multi-client run into a single send. The docs didn’t settle it, so I stopped reading and ran it live.

execution record — 2-client test run #30   2 sends, 6s apart  ·  distinct narratives per client  ✓ isolated

The assumption was wrong and the setting was fine — but now it’s a fact read from the execution record, not a belief taken from a doc.

INCIDENT 02

Driving the API when the UI fought back

The visual canvas was unreliable for precise edits — drags landed wrong, a stray paste could corrupt a whole workflow. So I stopped clicking and drove it from the page context.

the move fetch('/rest/workflows/{id}', { headers: { 'browser-id': … } })

Exact, reviewable changes instead of hopeful ones. Choosing the less obvious interface because it’s the more verifiable one is a tradeoff I’d make again.

INCIDENT 03

Deriving status colors instead of typing them

Google Sheets drops conditional formatting on xlsx import, so the demo workbooks bake colors in statically — and static colors drift from the formula that computes the visible tier.

shipped wrong row shows “STABLE” in green  ·  its own formula evaluates to −$317
the control now recompute the flag in Python from the same inputs → color from that
build log prints every input → computed tier so a mismatch is visible
INCIDENT 04

Two sources of truth that never met

The one that still stings. A dashboard shipped with a headline that flatly contradicted the rows beneath it:

shipped wrong MONEY LOST ON OVER-BUDGET JOBS   $0
  ▸ 2 jobs flagged OVER BUDGET   −$500   −$600

Five summary formulas were off by one column, so “total quoted” was summing actual cost. It survived review because the build log computed totals independently in Python and printed the right answer while the sheet showed the wrong one — two sources of truth that never met.

the control now for each summary cell: does it aggregate the column its own label names?
label “Total quoted” → sums col E (Actual cost)  ✗ flagged

None of these were caught by being careful. They were caught by building the thing that makes carelessness visible — and then the check outlives the bug.

What it does now

The pipeline runs on a schedule against sample sheets, computes the totals, writes its own summaries, and emails a per-client digest — end to end, unattended. Adding a business is one row. The checks that caught the bugs above now run automatically before anything ships.

Honest scope: this is a month-one business. Every figure in the demos is sample data and is labeled as such on the artifact itself. I’m describing what I built and operate — not a client roster.

// your turn

Send me your messiest spreadsheet.

I’ll build you a working, self-updating version of it and send it back within a day — free, and yours to keep whether we ever work together or not. Same checks described above run on it.

Hiring rather than outsourcing? I’m also open to automation, AI-operations, and solutions-engineering roles — the work above is the work. Background and résumé are on LinkedIn, or email me.