ADULT_FEE_DEFAULT is 700 CZK, not 750. The 750 appears in ADULT_FEE_MONTHLY_RATE for most current months but is not the fallback. Rephrase the member-tiers bullet to point at the dict rather than a number that drifts each season; update the fee-calc bullet to match the junior line's style (default 700 vs default 500). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
7.3 KiB
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.
uv venv && uv sync
source .venv/bin/activate
Set PYTHONPATH=scripts:. when running scripts directly (the Makefile does this automatically).
Commands
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:
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-cachescripts/attendance.py— Fetches attendance CSV from Google Sheets, computes per-member per-month fees. Contains fee rate constants (ADULT_FEE_DEFAULT,JUNIOR_FEE_DEFAULT) andADULT_MERGED_MONTHS/JUNIOR_MERGED_MONTHSdicts.scripts/match_payments.py—reconcile()matches transactions to members/months.fetch_sheet_data()reads the payments sheet.fetch_exceptions()reads theexceptionstab.scripts/cache_utils.py— Invalidation via Google Drive APImodifiedTime; falls back to 5-minute TTL buckets when Drive API is unavailable. Cache files live intmp/.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 fromADULT_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 sheetX— 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
exceptionstab 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:
-
Create a branch off
mainbefore 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
-
Commit on the branch following the existing commit conventions (Co-Authored-By trailer, etc.).
-
Push the branch to
originwith-uso it tracks. -
Open the MR with
tearather than printing a compare URL:tea pr create \ --title "<short title>" \ --description "<body>" \ --base main \ --head <branch>teais already authenticated against the Gitea instance; just run it. Print the resulting PR URL for the user. Ifteais 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. -
Do not merge or delete the branch from the CLI — neither via
tea,gh, norgit 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:
## 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.