FUJ Management — Comprehensive Documentation
FUJ = Frisbee Ultimate Jablonec — a small sports club in the Czech Republic.
What Is This Project?
FUJ Management is a purpose-built financial management system for a small ultimate frisbee club. It automates the tedious process of tracking who attended practice, how much they owe, who has paid, and who still owes money — a workflow that would otherwise require manual cross-referencing between attendance spreadsheets and bank statements.
The system is built around two Google Sheets (one for attendance, one for payments) and a Fio bank transparent account. A set of Python scripts sync and process the data, while a Flask-based web dashboard provides real-time visibility into fees, payments, and reconciliation status.
The Problem It Solves
Before this system, the club treasurer had to:
- Manually count attendance marks for each member each month
- Calculate whether each person owes 0, 200, or 750 CZK based on how many times they showed up
- Cross-reference bank statements to figure out who paid and for which month
- Chase members who hadn't paid, often losing track of partial payments and advance payments
- Handle edge cases like members paying for multiple months at once, using nicknames in payment messages, or paying via a family member's account
This system automates steps 1–4 entirely, and provides tooling for step 5.
System Overview
┌──────────────────────────┐ ┌──────────────────────────┐
│ Attendance Sheet │ │ Fio Bank Account │
│ (Google Sheets) │ │ (transparent account) │
│ │ │ │
│ Members × Dates × ✓/✗ │ │ Incoming payments with │
│ Tier (A/J/X) │ │ sender, amount, message │
└──────────┬───────────────┘ └──────────┬───────────────┘
│ │
│ CSV export │ API / HTML scraping
│ │
▼ ▼
┌─────────────────┐ ┌───────────────────────┐
│ attendance.py │ │ sync_fio_to_sheets.py │
│ │ │ │
│ Fetches sheet, │ │ Syncs bank txns to │
│ computes fees │ │ Payments Google Sheet │
└────────┬────────┘ └───────────┬────────────┘
│ │
│ ▼
│ ┌───────────────────────┐
│ │ Payments Sheet │
│ │ (Google Sheets) │
│ │ │
│ │ Date|Amount|Person| │
│ │ Purpose|Sender|etc. │
│ └───────────┬────────────┘
│ │
│ ┌─────────────────────────┤
│ │ │
│ ▼ ▼
│ ┌──────────────┐ ┌──────────────────┐
│ │infer_payments│ │ match_payments.py │
│ │ .py │ │ │
│ │ │ │ Reconciliation │
│ │ Auto-fills │ │ engine: matches │
│ │ Person, │ │ payments against │
│ │ Purpose, │ │ expected fees │
│ │ Amount │ └────────┬──────────┘
│ └──────────────┘ │
│ │
└────────────────┬───────────────┘
│
▼
┌──────────────────────┐
│ Flask Web App │
│ (app.py) │
│ │
│ /fees – fee │
│ table │
│ /reconcile – balance │
│ matrix │
│ /payments – ledger │
│ /qr – QR code │
└───────────────────────┘
Quick Start
Prerequisites
- Python 3.13+
- uv — fast Python package manager
- Google Sheets API credentials — a service account JSON file placed at
.secret/fuj-management-bot-credentials.json - Optional:
FIO_API_TOKENenvironment variable for Fio REST API access (falls back to transparent page scraping)
Setup
# Clone and install dependencies
git clone <repo-url>
cd fuj-management
uv sync # Installs all dependencies from pyproject.toml
# Place your Google API credentials
mkdir -p .secret
cp /path/to/your/credentials.json .secret/fuj-management-bot-credentials.json
Common Operations
| Command | Purpose |
|---|---|
make web |
Start the web dashboard at http://localhost:5001 |
make sync |
Pull new bank transactions into the Google Sheet |
make infer |
Auto-fill Person/Purpose/Amount for new transactions |
make reconcile |
Print a CLI balance report |
make fees |
Print fee calculation table from attendance |
make test |
Run the test suite |
make image |
Build the Docker container image |
Typical Workflow
make sync → make infer → (manual review in Google Sheets) → make web
↓ ↓ ↓ ↓
Pull new bank Auto-match Fix any [?] View live
transactions payments to flagged rows dashboard
into sheet members/months in the sheet
Documentation Index
| Document | Contents |
|---|---|
| Architecture | System design, data flow diagrams, module dependency graph |
| Web Application | Flask app architecture, routes, templates, interactive features |
| User Guide | End-user guide for the web dashboard — what each page shows |
| Scripts Reference | Detailed reference for all CLI scripts and shared modules |
| Data Model | Google Sheets schemas, fee calculation rules, bank integration |
| Deployment | Docker containerization, Gitea CI/CD, Kubernetes deployment |
| Testing | Test infrastructure, coverage, how to write new tests |
| Development Guide | Local setup, coding conventions, tooling, project history |
Technology Stack
| Layer | Technology |
|---|---|
| Language | Python 3.13+ |
| Web framework | Flask 3.1 |
| Package management | uv + pyproject.toml |
| Data sources | Google Sheets API, Fio Bank API / HTML scraping |
| QR codes | qrcode library (PIL backend) |
| Containerization | Docker (Alpine-based) |
| CI/CD | Gitea Actions |
| Deployment target | Self-hosted Kubernetes |
| Frontend | Server-rendered HTML/CSS/JS (terminal-aesthetic theme) |
Project Structure
fuj-management/
├── app.py # Flask web application (4 routes)
├── Makefile # Build automation (13 targets)
├── pyproject.toml # Python dependencies and metadata
│
├── scripts/
│ ├── attendance.py # Shared: attendance data + fee calculation
│ ├── calculate_fees.py # CLI: print fee table
│ ├── match_payments.py # Core: reconciliation engine + CLI report
│ ├── infer_payments.py # Auto-fill Person/Purpose in Google Sheet
│ ├── sync_fio_to_sheets.py # Sync Fio bank → Google Sheet
│ ├── fio_utils.py # Shared: Fio bank data fetching
│ └── czech_utils.py # Shared: diacritics normalization + Czech month parsing
│
├── templates/
│ ├── fees.html # Attendance/fees dashboard
│ ├── reconcile.html # Payment reconciliation with modals + QR
│ └── payments.html # Payments ledger grouped by member
│
├── tests/
│ ├── test_app.py # Flask route tests (mocked data)
│ └── test_reconcile_exceptions.py # Reconciliation with fee exceptions
│
├── build/
│ ├── Dockerfile # Alpine-based container image
│ └── entrypoint.sh # Container entry point
│
├── .gitea/workflows/
│ ├── build.yaml # CI: build + push Docker image
│ └── kubernetes-deploy.yaml # CD: deploy to K8s cluster
│
├── .secret/ # (gitignored) API credentials
├── docs/ # Project documentation
│ ├── project-notes.md # Original brainstorming and design notes
│ ├── fee-calculation-spec.md # Fee rules and payment matching spec
│ ├── scripts.md # Legacy scripts documentation
│ └── spec/
│ └── fio_to_sheets_sync.md # Fio-to-Sheets sync specification
│
└── CLAUDE.md # AI assistant context file
Key Design Decisions
-
No database — Google Sheets serves as both the data store and the manual editing interface. This keeps the system simple and accessible to non-technical club members who can review and edit data directly in the spreadsheet.
-
PII separation — No member names or personal data are stored in the git repository. All data is fetched at runtime from Google Sheets and the bank account.
-
Idempotent sync — The Fio-to-Sheets sync uses SHA-256 hashes as deduplication keys, making re-runs safe and append-only.
-
Graceful fallbacks — Bank data can be fetched via the REST API (if a token is available) or by scraping the public transparent account page. The system doesn't break if the API token is missing.
-
Czech language support — Payment messages are in Czech and use diacritics. The system normalizes text (strips diacritics) and understands Czech month names in all grammatical declensions.
-
Terminal aesthetic — The web dashboard uses a monospace, dark-themed, terminal-inspired design that matches the project's pragmatic, CLI-first philosophy.
This documentation was generated on 2026-03-03 by Claude Opus, based on a comprehensive analysis of the complete codebase.