Replaces the "do not use tea/gh/Gitea API" rule with explicit guidance to run `tea pr create` and print the resulting PR URL. tea is already authenticated on this machine. Merging stays a manual user action in Gitea — neither tea nor git CLI may merge or delete branches. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
160 lines
7.2 KiB
Markdown
160 lines
7.2 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## Project Status
|
|
|
|
Flask-based financial management system for FUJ (Frisbee Ultimate Jablonec). Handles attendance-based fee calculation, Fio bank transaction sync, payment reconciliation, and a web dashboard.
|
|
|
|
## Key Constraints
|
|
|
|
- **PII separation**: Member data (names, emails, payment info) must never be committed to git. Enforce config/data separation from day one.
|
|
- **Configuration**: External service IDs, credentials, and tunable parameters are centralized in `scripts/config.py`. Domain-specific constants (fees, merged months) stay in their respective modules.
|
|
|
|
## Development Setup
|
|
|
|
This project uses `uv` for dependency management.
|
|
|
|
```bash
|
|
uv venv && uv sync
|
|
source .venv/bin/activate
|
|
```
|
|
|
|
Set `PYTHONPATH=scripts:.` when running scripts directly (the Makefile does this automatically).
|
|
|
|
## Commands
|
|
|
|
```bash
|
|
make web # Start dashboard at http://localhost:5001
|
|
make web-debug # Same with FLASK_DEBUG=1
|
|
make test # Run all tests (unittest discover)
|
|
make test-v # Tests with verbose output
|
|
make fees # Print fee report from attendance sheet
|
|
make sync-2026 # Sync Fio bank transactions for 2026 to Google Sheets
|
|
make infer # Auto-fill Person/Purpose/Amount columns in payments sheet
|
|
make reconcile # Print balance report from Google Sheets data
|
|
make image # Build Docker image
|
|
```
|
|
|
|
Run a single test:
|
|
```bash
|
|
PYTHONPATH=scripts:. python -m unittest tests.test_app.TestWebApp.test_adults_route
|
|
```
|
|
|
|
## Architecture
|
|
|
|
### Data flow
|
|
|
|
```
|
|
Google Sheets (attendance) ──► attendance.py ──► reconcile() ──► Flask routes ──► templates/
|
|
Google Sheets (payments) ──► match_payments.py ──┘
|
|
Fio Bank API ──► sync_fio_to_sheets.py ──► Google Sheets (payments)
|
|
```
|
|
|
|
### Key modules
|
|
|
|
- `app.py` — Flask app; routes for `/adults`, `/juniors`, `/payments`, `/sync-bank`, `/qr`, `/flush-cache`
|
|
- `scripts/attendance.py` — Fetches attendance CSV from Google Sheets, computes per-member per-month fees. Contains fee rate constants (`ADULT_FEE_DEFAULT`, `JUNIOR_FEE_DEFAULT`) and `ADULT_MERGED_MONTHS` / `JUNIOR_MERGED_MONTHS` dicts.
|
|
- `scripts/match_payments.py` — `reconcile()` matches transactions to members/months. `fetch_sheet_data()` reads the payments sheet. `fetch_exceptions()` reads the `exceptions` tab.
|
|
- `scripts/cache_utils.py` — Invalidation via Google Drive API `modifiedTime`; falls back to 5-minute TTL buckets when Drive API is unavailable. Cache files live in `tmp/`.
|
|
- `scripts/sync_fio_to_sheets.py` — Pulls Fio bank transactions and appends them to the payments Google Sheet.
|
|
- `scripts/infer_payments.py` — Fills in Person/Purpose/Inferred Amount columns using name-matching heuristics.
|
|
- `scripts/config.py` — All external IDs, paths, and tunable TTLs. Override via env vars (`CREDENTIALS_PATH`, `BANK_ACCOUNT`, `CACHE_TTL_SECONDS`).
|
|
|
|
### Member tiers
|
|
|
|
Tiers are set in column B of the attendance sheet:
|
|
- `A` — Adult, pays fees (750 CZK/month for 2+ sessions, 200 CZK for exactly 1)
|
|
- `J` — Junior attending adult practices; their attendance is merged with the junior sheet
|
|
- `X` — Excluded from junior fee calculation (coaches, etc.)
|
|
|
|
### Fee calculation
|
|
|
|
- Adults: 0 sessions → 0, 1 session → 200 CZK, 2+ sessions → monthly rate (default 750 CZK)
|
|
- Juniors: 0 → 0, 1 → `"?"` (manual review required), 2+ → monthly rate (default 500 CZK)
|
|
- Per-member per-month overrides live in the `exceptions` tab of the payments sheet (columns: Name, Period YYYY-MM, Amount, Note). Exceptions are keyed by `(normalize(name), normalize(period))`.
|
|
|
|
### Merged months
|
|
|
|
`ADULT_MERGED_MONTHS` / `JUNIOR_MERGED_MONTHS` in `attendance.py` map a source month to a target month (e.g., `"2025-12": "2026-01"` merges December into January billing). The target month accumulates attendance from both months.
|
|
|
|
### Caching
|
|
|
|
`get_cached_data()` in `app.py` checks the Drive API `modifiedTime` before each request and serves a JSON file from `tmp/` when the sheet hasn't changed. Cache is warmed up at startup (`warmup_cache()`). Flush via `/flush-cache` (POST) or `flush_cache()`.
|
|
|
|
### Payments sheet columns
|
|
|
|
`Date | Amount | manual fix | Person | Purpose | Inferred Amount | Sender | VS | Message | Bank ID | Sync ID`
|
|
|
|
`Person` and `Purpose` are written by `infer_payments.py` and can be manually corrected. `manual fix` column presence disables re-inference for that row. Multiple people or months are comma-separated in Person/Purpose.
|
|
|
|
### QR codes
|
|
|
|
`/qr?account=…&amount=…&message=…` generates a Czech QR Platba PNG (SPD format).
|
|
|
|
## Branching & merge requests
|
|
|
|
The remote is Gitea (`gitea.home.hrajfrisbee.cz/kacerr/fuj-management`).
|
|
For **features**, do not commit to `main` directly. Use a branch + merge
|
|
request flow:
|
|
|
|
1. **Create a branch off `main`** before starting work:
|
|
- `feat/<slug>` for features (e.g. `feat/qr-code-overlay`)
|
|
- `fix/<slug>` for bug-fix branches the user explicitly asks for
|
|
- `<slug>` is short kebab-case
|
|
2. **Commit on the branch** following the existing commit conventions
|
|
(Co-Authored-By trailer, etc.).
|
|
3. **Push the branch** to `origin` with `-u` so it tracks.
|
|
4. **Open the MR with `tea`** rather than printing a compare URL:
|
|
|
|
```bash
|
|
tea pr create \
|
|
--title "<short title>" \
|
|
--description "<body>" \
|
|
--base main \
|
|
--head <branch>
|
|
```
|
|
|
|
`tea` is already authenticated against the Gitea instance; just run it.
|
|
Print the resulting PR URL for the user. If `tea` is unavailable for
|
|
some reason, fall back to printing the compare URL
|
|
(`https://gitea.home.hrajfrisbee.cz/kacerr/fuj-management/compare/main...<branch>`)
|
|
and let the user open the MR manually.
|
|
5. **Do not merge or delete the branch** from the CLI — neither via `tea`,
|
|
`gh`, nor `git push --delete`. The user does that in Gitea.
|
|
|
|
**Exceptions — when committing straight to `main` is fine:**
|
|
- Small bug fixes / hotfixes the user describes as such.
|
|
- Typo / comment / formatting tweaks.
|
|
- Edits the user explicitly says to push to `main`.
|
|
|
|
When uncertain whether something is a feature or a small fix, ask before
|
|
committing.
|
|
|
|
## Git Commits
|
|
|
|
When making git commits, always append yourself as co-author trailer to the end of the commit message to indicate AI assistance
|
|
|
|
## Changelog
|
|
|
|
Maintain a running changelog in `CHANGELOG.md` at the repo root. After every significant change, fix, or update — once the user confirms it works — append a new entry **at the top** of the file in this format:
|
|
|
|
```markdown
|
|
## YYYY-MM-DD HH:MM TZ — short title
|
|
|
|
- One-line summary of what changed and why.
|
|
- Key files touched (optional, only if useful for traceability).
|
|
```
|
|
|
|
Get the timestamp with `date "+%Y-%m-%d %H:%M %Z"`. Skip trivial edits (typos, formatting, comment tweaks); only log changes a future reader would care about.
|
|
|
|
## Plans
|
|
|
|
When Claude Code's plan mode is used, save the plan file inside the repo at
|
|
`docs/plans/YYYY-MM-DD-HHMM-<slug>.md` instead of the default `~/.claude/plans/`
|
|
location. Get the timestamp with `date "+%Y-%m-%d-%H%M"` (matches the changelog
|
|
convention). The `<slug>` should be a short kebab-case summary of the plan's topic.
|
|
|
|
Create the `docs/plans/` directory on first use. Plan files are committed to the
|
|
repo so other contributors can review historical decisions.
|