Files
fuj-management/CLAUDE.md
Jan Novak 91ac3b37cf docs: Add branch-per-feature + Gitea MR workflow to CLAUDE.md
Feature work now goes on feat/<slug> branches; Claude pushes and prints
the Gitea compare URL for the user to open the MR. Exceptions documented
for small fixes and typo tweaks.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 21:52:25 +02:00

6.9 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-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.pyreconcile() 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. Print the Gitea compare URL so the user can open the MR in the browser: https://gitea.home.hrajfrisbee.cz/kacerr/fuj-management/compare/main...<branch> Do not use tea, gh, or call the Gitea API — the user opens and merges the MR themselves.
  5. Do not merge or delete the branch from the CLI. 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.