# 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). ## 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.