# SETUP - recipe R2, weekly report

What this is: a small program that reads the CSV you already export every week
and writes you a report of this week against last week. It is one recipe from
the Back-Office Autopilot pack. Written 2026-11-10 (read-date), and it is the
same file the reports in `out/` were made from.

You do not need to be a programmer. Nothing here installs anything, and nothing
here touches the internet.

---

## 1. What you need

- **Python 3.12 or newer.** The version this was built and tested on is 3.12.3.
  Check yours:

  ```
  python3 --version
  ```

  If that command is not found, install Python from python.org and re-open a
  terminal.
- **Nothing else.** No install, no account, no API key, no network. The program
  uses only the parts of Python that come with Python.

---

## 2. The commands, exactly

Open a terminal, go into the recipe's folder, and run it on the sample that
ships with it:

```
cd path/to/recipes/r2
python3 weekly_report.py --input samples/weekly_sales.csv
```

You will get a table on the screen and two files:

| File | What it is for |
|---|---|
| `out/report.txt` | the same numbers as plain text - paste it into an email or a ticket |
| `out/report.html` | the same numbers, styled, for reading in a browser or forwarding |

Both are written into the folder named by `--outdir`, which is `out` unless you
say otherwise. If the folder does not exist, the program makes it. If a report
is already there, it is replaced - the report is always the numbers as of this
run, never a pile of old runs.

Then point it at your own export:

```
python3 weekly_report.py --input /path/to/your/monday_export.csv --title "Weekly numbers - Acme"
```

That is the whole product. Everything below is for when your file is not named
`date` and your numbers are not obviously the ones you want.

---

## 3. Every choice you can make

| Flag | Default | What it does |
|---|---|---|
| `--input` | `samples/weekly_sales.csv` | the CSV to read |
| `--outdir` | `out` | where `report.txt` and `report.html` are written |
| `--date-column` | `date` | the column holding the date |
| `--metrics` | (empty) | comma list of the number columns to total. Left empty, it totals every column whose values read as numbers |
| `--title` | names the input file | the heading across the top of both reports |
| `--now` | right now, UTC | the stamp printed on the report, e.g. `2026-11-10T14:00:00Z`. Only useful for making a report reproducible |
| `--quiet` | off | write both files and print nothing (for when you put this on a schedule) |

Run `python3 weekly_report.py --help` to see this list from the program itself.

---

## 4. Pointing it at your own export

1. Export the file the way you do today. It must be a `.csv` whose **first line
   names its columns**.
2. Find the column holding the date. If it is not called `date`, add it:
   `--date-column "Order Date"`. The name is matched ignoring case and spacing,
   so `order date` finds `Order Date`.
3. Decide what you want totalled. Left alone, the program totals every column
   whose values are numbers, and it prints, at the top of the report, every
   column it did **not** total and why. If you want exactly two of them:
   `--metrics units,revenue`.
4. Run it. Read the line `WHAT WAS LEFT OUT, AND WHY` before you read the
   table. That line is the report telling you which of your own cells it could
   not use.

### Dates it reads

| Written like | Read as |
|---|---|
| `2026-11-02` | 2 November 2026 |
| `2026-11-02T14:30:00` or `2026-11-02 09:15` | 2 November 2026 (the time of day is not used) |
| `2026/11/02` | 2 November 2026 |
| `11/02/2026` | 2 November 2026 - **month first**, the North American reading |
| `25/11/2026` | 25 November 2026 - a first number over 12 cannot be a month, so this one is settled by the calendar |
| `Nov 2, 2026` / `2 November 2026` | 2 November 2026 |

The report prints how many dates of each shape it met. If your export is
day-first and you see `slash month first` in that line where you expected
nothing, your dates are being read the other way round: re-export them as
`2026-11-02` and the ambiguity is gone. This is the one place where a report
could quietly be wrong, so it is the one place the program shows its work.

### Numbers it reads

`1200` · `1200.5` · `-12.5` · `+7` · `$1,200.50` · `1 200` (a space as the
thousands separator) · `42 EUR` · `0`

### Numbers it will not read, on purpose

| Written like | Why it is left out, and named, instead of read |
|---|---|
| `49,95` | that comma is a decimal mark. Read as 4995 it becomes a wrong total, which is worse than a missing one |
| `12%` | twelve percent of an unstated base is a judgement, not a figure |
| `(500)` | parentheses meaning "minus" is a house style; the program does not assume yours |
| `n/a`, `-`, `pending` | not numbers |
| a blank cell | treated as **no figure**, never as 0. A 0 that your export never said is a lie in a report |

Each of these is counted, and the report names the row and the value.

---

## 5. When it errors, and what it means

Every failure prints one line beginning `ERROR:`, names the file, and exits
with code 2. It writes no report when it fails - a missing report is easier to
notice than a wrong one.

| The line says | What it means, and the fix |
|---|---|
| `no such file. Pass --input the path of your CSV export.` | wrong path. `ls` the folder, or give the full path |
| `no header row was found, so no column can be named` | the file is empty, or starts at row 1 with data. Add the header line the export should have written |
| `it has a header line and no data rows at all` | your export came out empty. Check the export before you check this program |
| `no date column of that name in the file. Its columns are ...` | the date column is named something else. Use `--date-column`, and the name you want is probably in the list it printed |
| `no metric column of that name` | same for `--metrics` |
| `none of its N data row(s) has a date this recipe can read` | your dates are in a shape that is not in the table above. The message quotes your own values, row by row, so you can see what it is looking at |
| `no numeric column to total` | every column is text. Name the columns to total with `--metrics` |
| `that is the date column of this file, so it cannot also be a metric` | take the date out of your `--metrics` list |

It does not stop on one bad row. One row with an unreadable number or date is
counted, named in the report, and left out of the totals while the run carries
on - which is what you want at 07:00 on a Monday with a report to send.

---

## 6. Checking it works, on your machine, in two minutes

```
cd path/to/recipes/r2
python3 weekly_report.py --input samples/weekly_sales.csv --now 2026-11-10T14:00:00Z
```

Open `out/report.txt`. You should see `2026-45` as this week and `2026-44` as
last week, `units` 48 against 39 (+9, +23.1%), `revenue` 3,546 against 2,691,
and under `WHAT WAS LEFT OUT, AND WHY` the four things the sample deliberately
gets wrong: two revenue cells that are not numbers, one blank units cell, and
one date that exists in no calendar. The sample file contains all four on
purpose, so that you can see the honest-reporting path work before you trust it
with your own numbers.

The whole test suite, if you want the proof rather than the sample:

```
cd path/to/recipes
PYTHONDONTWRITEBYTECODE=1 python3 -m pytest r2/test_weekly_report.py -q -p no:cacheprovider
```

---

## 7. Where your data goes

Nowhere but the two files in `--outdir`. The program reads one CSV by path and
writes two files by path. There is no mail, no upload, no login and no network
call in it - and `test_weekly_report.py` reads the program's own imports and
call names to prove that sentence rather than asking you to believe it.
