# 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 (per-month rate from `ADULT_FEE_MONTHLY_RATE`, fallback 700 CZK 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 700 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/` for features (e.g. `feat/qr-code-overlay`) - `fix/` for bug-fix branches the user explicitly asks for - `` 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 "" \ --description "" \ --base main \ --head ``` `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...`) 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-.md` instead of the default `~/.claude/plans/` location. Get the timestamp with `date "+%Y-%m-%d-%H%M"` (matches the changelog convention). The `` 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.