Four back-office automations, in Python, with their test suites
Four small programs that do the parts of a small business that never get built: a lead that arrives and dies in an inbox, a weekly number nobody assembles, an invoice that goes unpaid because no one wants to be the one who asks, and a listing page that has to be read by hand every Monday. Each is one file, standard library only, with its own test suite and a written setup guide.
What they share: none of them sends anything. Every one of them ends its run by writing a numbered draft to a folder, addressed to nobody, which a human reads and then sends themselves. That is a design decision, not a missing feature: the embarrassing part of an automation is the thing it did at 3am while nobody was watching, and these cannot do that.
Proof: 201 tests, and the command that repeats them
Measured 2026-11-14 on Python 3.12.3 with pytest 9.1.1, against the copy of the code this page serves under /src/, not against a build somewhere else.
$ cd src $ python3 -m pytest recipes/r1 recipes/r2 recipes/r3 recipes/r4 tests -q ============================= 201 passed in 2.87s ==============================
| folder | what it is | tests | program |
|---|---|---|---|
| r1 | lead capture: form or inbox to sheet, plus a reply draft | 32 passed | lead_capture_to_sheets.py |
| r2 | weekly report: two weeks of sales side by side, in Markdown and HTML | 88 passed | weekly_report.py |
| r3 | invoice reminders: who is overdue, and the drafts, escalating by stage | 23 passed | invoice_reminders.py |
| r4 | listing scraper: a page of listings to a clean CSV or JSON, with the cap made visible | 58 passed | scrape_listings.py |
| total on this page | 201 passed | ||
A fifth recipe, a cost-per-unit pricing tool, lives in the same tree and brings the family to 271 passing tests. It is not published here: its licence file names an author, and a page like this one carries one name. Ask and I will send that recipe too.
On a machine whose login session carries a display, a bare test run is refused by this project's own guard so a test can never reach someone's screen; the command above was run inside that box, and needs no such wrapper on an ordinary machine.
What each one actually does, and what it refuses to do
R1, lead capture to a sheet
Reads new leads from a Google Sheet, a JSON file or a CSV file, de-duplicates them against what is
already on the sheet, appends the survivors, and writes one reply draft per lead into an outbox
folder. It writes only to the sheet named in its config. The test suite is strict about the part
people skip: the workbook is read as text and every column is type-checked, because a sheet is a
loose pile of typed-in cells, not a schema, and a date typed as 13/04/2026 by one person
is April 13 by another.
$ python3 recipes/r1/lead_capture_to_sheets.py --leads-file samples/incoming/ --dry-run sample row lead amara@example.com name=Amara source=website form ok
R2, the weekly report
Two weeks of sales rows in, a report out, in Markdown and in HTML, with the totals that matter and the difference between the two weeks stated rather than implied. Its 88 tests are mostly about arithmetic that looks trivial and is not: a "refunds" column is a number in one export and a blank in another, a row dated between the two weeks belongs to neither, and a report that divides by a week that contains nothing must say so instead of printing an infinity.
R3, invoice reminders
Takes invoices and payments and answers who is overdue, by how much, and which stage of asking they are on. It is deliberately conservative: a credit note or a payment applied to a different invoice does not count as paid, a disputed invoice is left alone, and a zero-amount invoice is ignored rather than escalated. It writes numbered drafts and stops there.
R4, the listing scraper
A generic list reader: give it a page and a description of where the rows are, and it produces a CSV or JSON with a provenance header. Its rule is that nothing is dropped quietly: rows that fail to parse come back marked, a result cap that bit is reported as having bitten, and if nothing at all can be kept it exits with a named reason instead of writing an empty file and looking successful.
The licences and the terms
- None of the four programs carries a licence file. So all rights are reserved by the author, and nothing on this page grants a licence to use, copy or redistribute them. Ask, and the answer comes in writing.
- Every sample input on this page is synthetic: the leads are
@example.compeople who do not exist, the invoices are INV-1001 and up, and the sales CSV is invented. Nothing here is a client's data. - Every outbox file under
outbox/is a draft that was written and never sent. Each one says so on its first line.
What I would want to say before selling one of these
- These are working programs with real test coverage, not a hosted service. Someone has to run
them, and the setup guides under each recipe's
SETUP.mdare what that takes. - R1's sheet path is exercised against local files and fakes in the tests; a live Google sheet is a credential you would have to supply, and I would rather say now that the suite proves the logic and not your specific spreadsheet.
- R4 obeys the page it is pointed at. Scraping a site whose terms forbid it is a decision for the person who owns the account, and I would not do it.