Part 1 of 7 · Weekly report builder series ~5 min read

A weekly report builder on AWS for a few dollars a month

Most small-business owners can’t tell you on a Monday morning how last week actually went. The numbers exist — sales are in one place, new customers in another, cash in and out in the bank export, the top-selling items buried in the point-of-sale — but nobody has time to open four tabs, line them up against the week before, and work out what changed. So the question goes unanswered, week after week, until something is badly wrong and it’s obvious to everyone. This post walks through the design of a small builder that gathers all those numbers, compares them, writes a short plain-English summary of how the week went, and emails it every Monday — reporting only what’s really in your data.

Key takeaways

  • Three pieces: a gather step, a writer step, and a sender step. The report goes out every Monday.
  • Every figure is computed by plain Python from your data before any words are written.
  • Each number is compared to last week and last month, so the report says what changed, not just what happened.
  • One AI call a week turns the real numbers into a short paragraph; it never sources a number itself.
  • Designed on AWS for about $2/month at typical small-business volume.

The whole system on one page

Before any code, here’s the shape of what we’re designing.

System: your sources, three pieces inside AWS At the top, three external boxes in a row. Far left, "Your sources" — the places your real numbers already live: a sales export sheet, a Stripe or bank CSV in a Drive folder, and a point-of-sale daily summary. Centre, "Config and voice" — a short config doc that lists each source and which figures matter, plus a voice doc with the tone and shape of the summary paragraph. Far right, "The owner" — the person who gets the report; it lands in their email inbox every Monday morning in their own timezone. Each connects via an arrow to the AWS account container below. Your sources have an outgoing arrow into AWS. Config and voice feed in to ground every figure and every sentence. The owner receives the report with the plain-English summary up top, the numbers table below, comparisons to last week and last month, and a "Needs a look" section for anything that tripped a check. Inside the AWS account are three components in a row, mirroring the layout above. On the left, the Gather — reads each source from S3, normalizes it, and computes this week's figures plus the comparisons. In the middle, the Writer — takes the already-computed figures and calls Bedrock once to write a short plain-English summary, never sourcing a number itself. On the right, the Sender — formats the email with summary and table, and sends it via SES every Monday in the owner's timezone. Internal arrows flow left to right. A note at the bottom reads: every number in the report comes from your data — the writer never invents a figure. Your sources sales, cash, POS Config and voice sources, figures, tone The owner where the report lands numbers in grounds report every Monday AWS account Gather read, normalize, compute the figures Writer one Bedrock call, describes real numbers only Sender email via SES, Monday, owner’s timezone figures report Every number in the report comes from your data — the writer never invents a figure.
Fig 1. Your sources outside, three pieces inside AWS. Numbers flow in from a sales sheet, a cash export, and a point-of-sale summary. The Gather computes the figures, the Writer turns them into a short paragraph, and the Sender emails the report every Monday.

What you set up once (the outside)

  • Your sources. The places your real numbers already live. A sales export in a Google Sheet, a Stripe or bank CSV dropped in a Drive folder, a point-of-sale daily summary. You don’t move any of this — you just point the builder at it. Each source is listed once in the config doc with where it lives, which columns hold the figures that matter (revenue, order count, new customers, refunds), and how often it updates. Adding a new source later is one new line in the doc.
  • A config and voice folder. Two short Google Docs in a Drive folder. The config doc lists every source and the figures to pull from each, plus the thresholds that decide what counts as a notable change (“flag any week-over-week move bigger than 25%”) and what makes a number look off enough to flag. The voice doc holds the tone and shape of the summary paragraph — short, plain, owner-facing, lead with the headline number. Changing either is an edit to a doc, never a deploy.
  • The owner. The person the report is for. They get the email every Monday morning in their own timezone, with the plain-English summary at the top, the numbers table right below it, the comparisons to last week and last month, and a Needs a look section for anything the checks flagged. No login, no dashboard to remember — it’s just in the inbox.

What runs every Monday (the inside)

  • The gather. Reads each source. A small source-sync Lambda mirrors every source to S3 on a schedule, so the Monday run reads from S3 and never depends on a live API at 7am. The gather step normalizes each source into one clean shape, computes this week’s figures (total sales, order count, new customers, cash in and out, top items), and computes the same figures for last week and the four-week average — so every number arrives with its comparison already attached. This step is plain Python. No model.
  • The writer. Takes the already-computed figures and the comparisons and makes exactly one Bedrock Haiku 4.5 call to write a short paragraph: “here’s how the week went, here’s what changed.” The prompt hands the model the real numbers and tells it to describe only those numbers — no figure may appear in the summary that wasn’t computed in the gather step. The model writes prose, not data. Everything it says is checked back against the table before sending.
  • The sender. Formats the email: the summary paragraph up top, the numbers table below it with this week / last week / four-week average columns, and the Needs a look section if anything tripped a check. Sends via SES outbound at the configured Monday time in the owner’s timezone. Every send is recorded in DynamoDB so the run is auditable and so next week’s comparison has a clean record of what was reported. A link in the footer hits a Function URL if the owner wants the full source table for any figure.

In plain words

It’s Monday, 7am. Over the weekend the bank export landed in the Drive folder, the point-of-sale rolled up Saturday’s numbers, and the sales sheet got its last few rows. The builder wakes up, reads all three, and works out the week: $18,400 in sales (up 12% on last week, a touch above the four-week average), 142 orders, 9 new customers (down from 14 — the slowest week in a month), cash out higher than usual because the quarterly insurance premium cleared. It writes one short paragraph saying exactly that, in plain words, and emails it to the owner with the table underneath. The owner reads it over coffee in ninety seconds and knows two things they wouldn’t otherwise: sales are fine, but new customers slowed down — worth a look at the ad spend.

The cost of running this is about $2 a month at SMB volume. The cost of not running it is the quarter where new-customer growth quietly stalled for six weeks and nobody noticed until the pipeline ran dry.

Design rules that shaped every decision

  • Every figure is computed by plain Python from your data first. The writer only describes numbers that already exist.
  • Every number ships with its comparison — last week and the four-week average — so the report says what changed.
  • The summary and the table travel together. Any sentence can be checked against the numbers right below it.
  • Anything missing, stale, or out of range is flagged, not guessed. A blank is never reported as a real result.
  • The config lives in Drive. Adding a source or changing a threshold doesn’t need a deploy.
  • Every send is logged. Pull up any past Monday and you can see exactly what was reported and why.

Why this shape

Most owners get their numbers in one of three ways: a stack of dashboards nobody opens, a bookkeeper’s month-end report that arrives three weeks too late to act on, or a gut feel that’s right until it isn’t. The dashboards fail because looking at them is a chore you have to remember to do. The month-end report fails because by the time it lands, the week it describes is ancient history. And gut feel fails the first week something moves quietly — new customers, refund rate, average order — in a direction the owner wasn’t watching.

The setup above leaves your numbers where they already live, but adds a small system that reads them every week and does the lining-up and comparing for you. The report comes early enough in the week to act on. It says what changed, not just what happened. It shows the table next to the words so you can trust any sentence. And it flags what looks off instead of papering over it. The builder is invisible six days a week; visible only on Monday, when it answers the one question the owner never has time to answer themselves.

The next four posts walk through each piece in turn: how the numbers get gathered, how the weekly report gets written, how the report reaches the owner, and how a number that looks off gets flagged. One diagram per post. A cost breakdown and a final engineering reference at the end.

All posts