---
name: money-check
description: Run the monthly money check when a new month's bank export (a YYYY-MM.csv file with columns date,description,amount) has been added to the money practice folder. Use it to categorize the new month's rows by appending them to categories.csv, run money-referee.py over every month, and write money-report-YYYY-MM.md with category totals, recurring charges, price changes, new merchants and possible duplicates compared against earlier months.
---

# Monthly money check

## Folder and files

Everything lives in one folder: the folder that contains `categories.csv`, `money-referee.py` and `.claude/skills/money-check/` (this skill). Work only inside that folder.

- Bank exports: one file per month named `YYYY-MM.csv` (for example `2026-01.csv`, `2026-02.csv`, `2026-03.csv`). A new month's export will sit in the same folder under the same naming, for example `2026-04.csv`. Columns are `date,description,amount`, dates are `YYYY-MM-DD`, spending is negative and money in is positive.
- `categories.csv`: one line per export row across all months.
- `money-referee.py`: the independent checker.
- Reports: `money-report.md` covers 2026-01 to 2026-03. Each new month gets `money-report-YYYY-MM.md` named for the new month (for example `money-report-2026-04.md`).

## Safety rules (always)

- Do not use the network. Do not install anything. Use only `python3` and its standard library already on the computer.
- Do not read anything outside this folder. Do not write any file outside this folder, including temporary files in /tmp. Do not create helper scripts or scratch files: run any one-off Python by piping it on standard input (`python3 -I - <<'EOF' ... EOF`).
- Never edit the bank exports.
- Never rewrite or reorder existing lines of `categories.csv`; only append.
- Create only the new month's lines in `categories.csv` and the new `money-report-YYYY-MM.md`. Do not change `money-referee.py` or earlier reports.
- Handle money as `Decimal` or integer cents, never floats.
- Do not use em dashes or en dashes in anything you write.
- Do not ask the user to reply.
- If `python3` is unavailable, report that the checks could not run and never claim they passed.

## Step 1: read the new export

Read every row of the new `YYYY-MM.csv`. Confirm the header is exactly `date,description,amount`. Note any description that is not in the category list below.

## Step 2: append the new month to categories.csv

Layout of `categories.csv` (header written once, at the top):

```
source_file,row,date,description,amount,category
```

- `source_file`: the export file name only, for example `2026-04.csv`.
- `row`: the data row number inside that export, starting at 1 for the first line after the header.
- `date`, `description`, `amount`: copied exactly as text from the export (keep `-49.99`, keep two decimals as written).
- `category`: exactly one nonempty category per row.

Category names in use and the fixed merchant mapping (keep it consistent; the category is chosen by the exact description):

| Description | Category |
|---|---|
| Lanternworks Studio payroll | Income |
| Brightwater Property Management rent | Housing |
| Hearth Internet | Utilities |
| Ferncall Mobile | Utilities |
| Lantern Hollow Power and Water | Utilities |
| Tallow & Pine Grocers | Groceries |
| Lantern Hollow Transit reload | Transport |
| Northfield Gas and Go | Transport |
| Mill Road Coffee | Dining |
| Corner Diner | Dining |
| Willow Street Pharmacy | Health |
| Granite Peak Fitness | Health |
| Cedar Books | Shopping |
| Hollow Hardware | Shopping |
| Harbor Street Florist | Shopping |
| Pixelstream | Entertainment |

Categories: Income, Housing, Utilities, Groceries, Transport, Dining, Health, Shopping, Entertainment. Refunds and credits keep their merchant's category (Hollow Hardware 23.48 on 2026-02-11 is Shopping). For a merchant not in the table, pick the closest existing category; add a new category name only when none fits, and use that same category for that merchant in every later month. Mention any new merchant to category choice in the report's category section.

Append only, never rewrite earlier rows. Refuse to append if the file is already present. Example for April 2026 (adjust `NEW` and add entries to `CAT` for new merchants):

```
python3 -I - <<'EOF'
import csv
NEW = "2026-04.csv"
CAT = {
 "Lanternworks Studio payroll": "Income",
 "Brightwater Property Management rent": "Housing",
 "Hearth Internet": "Utilities", "Ferncall Mobile": "Utilities",
 "Lantern Hollow Power and Water": "Utilities",
 "Tallow & Pine Grocers": "Groceries",
 "Lantern Hollow Transit reload": "Transport", "Northfield Gas and Go": "Transport",
 "Mill Road Coffee": "Dining", "Corner Diner": "Dining",
 "Willow Street Pharmacy": "Health", "Granite Peak Fitness": "Health",
 "Cedar Books": "Shopping", "Hollow Hardware": "Shopping", "Harbor Street Florist": "Shopping",
 "Pixelstream": "Entertainment",
}
with open("categories.csv", newline="", encoding="utf-8") as h:
    if any(r and r[0] == NEW for r in csv.reader(h)):
        raise SystemExit(NEW + " is already in categories.csv; nothing appended")
with open(NEW, newline="", encoding="utf-8-sig") as h:
    rows = list(csv.reader(h))
assert rows[0] == ["date", "description", "amount"], rows[0]
missing = sorted({r[1] for r in rows[1:] if r[1] not in CAT})
if missing:
    raise SystemExit("No category for: " + ", ".join(missing))
with open("categories.csv", "a", newline="", encoding="utf-8") as out:
    w = csv.writer(out, lineterminator="\n")
    for i, (d, desc, a) in enumerate(rows[1:], 1):
        w.writerow([NEW, i, d, desc, a, CAT[desc]])
EOF
```

Before appending, check that `categories.csv` ends with a newline (`tail -c 1 categories.csv | od -c` shows `\n`).

## Step 3: run the referee over all months

Run it over every month's export, oldest first, and capture the real exit status. For April 2026 the exact command is:

```
python3 money-referee.py categories.csv 2026-01.csv 2026-02.csv 2026-03.csv 2026-04.csv; echo "exit status: $?"
```

(For later months keep listing every `YYYY-MM.csv` in the folder in date order, then the new one last.) The referee re-reads the exports itself, prints every mismatch and exits 0 only if every export row appears exactly once in `categories.csv` with the same date, description and amount and a nonempty category, and the category totals equal the export total to the cent. Record the exit status exactly as printed. If it is not 0, still write the report, state the real exit status and list the mismatches it printed; never describe a nonzero run as passing.

## Step 4: the analysis rules

Apply these mechanically across all months in `categories.csv` (the new month is the "latest month"). Compute with `Decimal`.

**Recurring charges.** A charge is recurring when the same description and the exact same amount come back at a regular interval. Regular means either:
- (a) it appears in every month from its first month to the latest month (at least two months), the same number of times each month, with each occurrence falling within 3 days of the same day of the month (once a month, or several times a month); or
- (b) the gaps between its dates (rows with the same date counted once) are all within 1 day of each other and are 6 to 8 days (weekly), 13 to 16 days (every two weeks) or 27 to 33 days (monthly).
Rule (b) means a charge that first appeared within the new month and already repeated there (for example twice 7 days apart) counts as recurring; list it separately as "started in YYYY-MM". If none started, say "none" and name single new charges seen once.
Known recurring set as of 2026-03: payroll 2840.00 twice a month; rent -1450.00 monthly; Ferncall Mobile -42.00 monthly; Granite Peak Fitness -29.00 monthly; Transit reload -20.00 twice a month and -15.00 monthly; Mill Road Coffee -4.75 twice a month and -5.50 monthly. Recompute; do not just copy.

**Price changes.** A spending merchant (negative amounts) with exactly one charge in every month from its first month to the latest month (identical same-day rows counted once), each charge within 3 days of the same day of the month, and the amount not the same in every month. Show each month-to-month change with both dates and both amounts as quoted from the files, and describe the difference in words as "X.XX more" or "X.XX less". Never put a plus or minus sign on the difference. As of 2026-03: Hearth Internet -49.99, -54.99, -59.99; Lantern Hollow Power and Water -88.12, -94.60, -81.37.

**New merchants.** Descriptions in the latest month that do not appear in any earlier month. Give date, amount and category.

**Possible duplicates.** Same description, same date and same amount appearing more than once (any month). Give the export file and row numbers. Rows on the same date with different amounts are not duplicates (note them as not flagged).

## Step 5: the report

Write `money-report-YYYY-MM.md` for the new month. It compares the new month against all earlier months. Layout, in this order:

1. Title `# Money report: YYYY-MM` and a sources line: each export file with its row count, and the total row count.
2. `## Referee`: the exact command run and its real exit status, plus the matched sum from its output (or the mismatches if nonzero).
3. `## Totals by category per month`: a table with one column per month (all months, oldest first), a total column, and a bold Net (file total) row. Amounts as signed numbers with two decimals. Short notes for credits and duplicate rows that affect a total.
4. `## Recurring charges`: state the rule, then a table Merchant | Amount | Pattern | Dates, then the line for charges started in the new month, then charges that narrowly miss the rule.
5. `## Price changes on the same merchant`: state the rule, then a table Merchant | Earlier | Later | Change ("X.XX more" or "X.XX less").
6. `## Merchants appearing for the first time in YYYY-MM`: table Merchant | Date | Amount | Category, or "none".
7. `## Possible duplicate charges`: state the rule, then a table Merchant | Date | Amount | Times | Rows, or "none".

Report rules: use only flags and numbers, quoting dates and amounts from the files. Never say what the user should do about anything (no advice, no suggestions, no "consider" or "you may want to"). Do not use em dashes or en dashes. Do not ask the user to reply.

## Finish

Tell the user which files changed (the appended `categories.csv` lines and the new report), the referee's real exit status, and the flags found.
