Learn  /  Guides

How to stop building the same report every week

A complete, working pattern for turning a manual weekly report into one that builds itself.

Guide9 min readUpdated 30 September 2026
PythonAutomationData quality
All guides and cheat sheets

Almost every business has a report that someone builds by hand, every week, in exactly the same way. It is one of the easiest and most valuable things to automate. This guide walks through a complete pattern you can adapt.

The pipeline in this guide
CollectCSV files into pandas
Cleandedupe and calculate
Checkstop if anything looks wrong
BuildExcel workbook
Sendemail, every Monday

Step 1: write the manual process down

Before any code, list what happens today: which files are downloaded and from where, what is cleaned or combined, which calculations are made, and who receives the result. If you can describe it step by step, it can almost certainly be automated.

Step 2: collect and combine the inputs

Python
from pathlib import Path
import pandas as pd

INPUT = Path("inputs")

frames = []
for file in sorted(INPUT.glob("sales_*.csv")):
    df = pd.read_csv(file, parse_dates=["order_date"])
    df["source_file"] = file.name
    frames.append(df)

sales = pd.concat(frames, ignore_index=True)

Step 3: clean and calculate

Python
sales = (
    sales.drop_duplicates(subset="order_id")
         .assign(
             revenue=lambda d: d["quantity"] * d["unit_price"],
             week=lambda d: d["order_date"].dt.to_period("W-SUN").dt.start_time,
         )
)

weekly = (
    sales.groupby(["week", "region"], as_index=False)
         .agg(revenue=("revenue", "sum"), orders=("order_id", "nunique"))
)

Step 4: check before you send

Automation is only valuable if you can trust it. Stop the run if something looks wrong, rather than sending a misleading report. More patterns are in the data quality guide.

Python
last_week = weekly["week"].max()
latest = weekly[weekly["week"] == last_week]

assert not sales["order_id"].duplicated().any(), "Duplicate orders found"
assert len(latest) > 0, "No data for the latest week"
assert (latest["revenue"] >= 0).all(), "Negative revenue found"

Step 5: build the output

Python
output = Path("outputs") / f"weekly_report_{last_week:%Y-%m-%d}.xlsx"
output.parent.mkdir(exist_ok=True)

with pd.ExcelWriter(output) as writer:
    latest.to_excel(writer, sheet_name="This week", index=False)
    weekly.to_excel(writer, sheet_name="History", index=False)

Step 6: send it

Python
import os
import smtplib
from email.message import EmailMessage

msg = EmailMessage()
msg["Subject"] = f"Weekly sales report, week of {last_week:%d %B %Y}"
msg["From"] = os.environ["REPORT_SENDER"]
msg["To"] = os.environ["REPORT_RECIPIENTS"]
msg.set_content(f"Hi all,\n\nRevenue last week: £{latest['revenue'].sum():,.0f}.\n"
                "The full report is attached.\n")
msg.add_attachment(output.read_bytes(), maintype="application",
                   subtype="vnd.openxmlformats-officedocument.spreadsheetml.sheet",
                   filename=output.name)

with smtplib.SMTP_SSL(os.environ["SMTP_HOST"], 465) as smtp:
    smtp.login(os.environ["SMTP_USER"], os.environ["SMTP_PASSWORD"])
    smtp.send_message(msg)

Step 7: schedule it

On macOS or Linux, one line in crontab -e runs the report every Monday at 07:00:

Terminal
0 7 * * 1 cd /path/to/weekly-report && /usr/local/bin/uv run report.py >> run.log 2>&1

On Windows, Task Scheduler does the same. For data that already lives in the cloud, a scheduled GitHub Action, a Snowflake task or a Google Apps Script trigger are good alternatives.

Deciding what to automate first

Frequency

Time saved
  • Daily and weekly reports save the most time

Twenty minutes a day is more than eighty hours a year.

Error risk

Fewer mistakes
  • Anything involving a lot of copying and pasting

Every manual step is a chance for a silent error.

Importance

Better decisions
  • Reports that drive real decisions, such as cash, stock or staffing

Reliability matters most where the stakes are highest.

Check your understanding

1/3What should an automated report do when a data check fails?

2/3Where should the SMTP password live?

3/3What does the cron schedule 0 7 * * 1 mean?

Want the checks to be more thorough? Read Data quality tests every pipeline needs, and see uv for keeping the script's dependencies tidy.

Alessandro Ecclesie AgazziWritten by Alessandro Ecclesie Agazzi, analytics engineer in London. Updated 30 September 2026. About me

Found this useful?

New guides and "what's new" explainers on data and AI land here regularly. Follow along on LinkedIn, or share this one with a colleague.