7.2 KiB
Development Guide
Development Environment
Required Tools
| Tool | Version | Purpose |
|---|---|---|
| Python | 3.13+ | Runtime |
| uv | Latest | Dependency management |
| Docker | Latest | Container builds |
| Git | Any | Version control |
| Make | Any | Build automation |
Initial Setup
# 1. Clone the repository
git clone <repo-url>
cd fuj-management
# 2. Install dependencies (creates .venv automatically)
uv sync
# 3. Activate the virtual environment
source .venv/bin/activate
# 4. Set up credentials
mkdir -p .secret
# Copy your Google service account JSON here:
cp ~/Downloads/fuj-management-bot-credentials.json .secret/
# 5. (Optional) Set Fio API token
export FIO_API_TOKEN=your_token_here
IDE Configuration
The .vscode/ directory contains workspace settings. If using VS Code, the Python interpreter should automatically detect the .venv directory.
PYTHONPATH note: When running scripts from the project root, the Makefile sets PYTHONPATH=scripts:$PYTHONPATH. If your IDE doesn't do this, you may see import errors in match_payments.py and other scripts that import sibling modules.
Project Dependencies
Defined in pyproject.toml:
| Dependency | Version | Purpose |
|---|---|---|
flask |
≥3.1.3 | Web framework |
google-api-python-client |
≥2.162.0 | Google Sheets API |
google-auth-httplib2 |
≥0.2.0 | Google auth transport |
google-auth-oauthlib |
≥1.2.1 | OAuth2 support |
qrcode[pil] |
≥8.0 | QR code generation (with PIL/Pillow backend) |
The project uses uv with package = false in [tool.uv], meaning it's not an installable package — dependencies are synced directly to the virtual environment.
Coding Conventions
Python Style
- No linter or formatter is configured — the codebase uses a pragmatic, readable style
- Type hints are used for function signatures but not exhaustively
- Docstrings follow Google-style format on key functions
- Scripts use
if __name__ == "__main__": main()pattern
Import Pattern
Scripts in the scripts/ directory import from each other as top-level modules:
# In match_payments.py:
from attendance import get_members_with_fees
from czech_utils import normalize, parse_month_references
from sync_fio_to_sheets import get_sheets_service, DEFAULT_SPREADSHEET_ID
This works because scripts/ is added to sys.path at runtime (by app.py on startup, by Makefile via PYTHONPATH, or by scripts adding their own directory to sys.path).
Template Style
- All CSS is inline (no external stylesheets)
- No CSS preprocessors or frameworks
- No JavaScript frameworks — plain DOM manipulation
- Terminal-inspired aesthetic: monospace fonts, green-on-black, dashed borders
Commit Conventions
The project uses Conventional Commits:
feat: add keyboard navigation to member popup
fix: correct diacritic-insensitive search filter
chore: update dependencies
AI commits include a co-author trailer:
Co-authored-by: Antigravity <antigravity@google.com>
Architecture Decisions
Why No Database?
Google Sheets serves as the database because:
- Club members can view and correct data without special tools
- No database server to manage or back up
- Built-in version history and collaborative editing
- Good enough for ~40 members and ~hundreds of transactions
Why No Template Inheritance?
Each HTML template is self-contained. While this means CSS duplication, it keeps each page fully independent and easy to understand. For a 3-page app, the duplication cost is minimal.
Why Flask Development Server in Production?
The Docker container runs Flask's built-in server (python3 app.py) rather than gunicorn or waitress. This is intentional — the dashboard is an internal tool accessed by one person at a time. The simplicity outweighs the performance cost.
Why Scrape HTML When There's an API?
The Fio transparent page scraping exists as a zero-configuration fallback. Not everyone has an API token, and the transparent page is always publicly accessible. The API is preferred when available (richer data, stable IDs).
Common Development Tasks
Adding a New Web Route
- Add the route function in
app.py:
@app.route("/new-page")
def new_page():
# Fetch data
record_step("fetch_data")
# Process
record_step("process_data")
return render_template("new_page.html", ...)
-
Create
templates/new_page.html(copy structure fromfees.html) -
Add a link in the nav bar across all templates:
<a href="/new-page">[New Page]</a>
- Add a test in
tests/test_app.py
Adding a New Script
- Create
scripts/new_script.py - Add a Makefile target:
new-target: $(PYTHON)
$(PYTHON) scripts/new_script.py
- Update
make helpoutput - Add the
.PHONYdeclaration
Modifying Fee Rules
Fee rules are defined as constants in scripts/attendance.py:
FEE_FULL = 750 # 2+ practices
FEE_SINGLE = 200 # 1 practice
The calculation logic is in calculate_fee():
def calculate_fee(attendance_count: int) -> int:
if attendance_count == 0: return 0
if attendance_count == 1: return FEE_SINGLE
return FEE_FULL
Adding a New Czech Month Form
If you encounter a Czech month declension not yet supported, add it to CZECH_MONTHS in scripts/czech_utils.py:
CZECH_MONTHS = {
"leden": 1, "ledna": 1, "lednu": 1,
"lednem": 1, # New instrumental case
...
}
Project History
The project evolved through distinct phases:
- Design phase — Initial brainstorming captured in
docs/project-notes.md - CLI tools —
calculate_fees.pyandmatch_payments.pyfor command-line workflows - Bank integration —
fio_utils.pyfor transparent page scraping, later API support - Google Sheets sync —
sync_fio_to_sheets.py+infer_payments.pyfor the ledger pipeline - Web dashboard —
app.pywith the/fees,/reconcile, and/paymentspages - Interactive features — Modal popups, QR payments, keyboard navigation, search filter
- Fee exceptions — Manual override system via the
exceptionssheet tab - CI/CD — Gitea Actions for Docker builds and Kubernetes deployment
Troubleshooting
"No data." on the web dashboard
The attendance Google Sheet couldn't be fetched, or it returned empty data. Check:
- Internet connectivity
- The sheet ID in
attendance.pyis still valid - The sheet's public sharing settings haven't changed
Slow page loads
Each page fetches data from Google Sheets on every request (no caching). Typical load times are 1-3 seconds. If significantly slower:
- Check the performance breakdown (click the render time in the footer)
- Google Sheets API rate limiting may be the cause
Import errors in scripts
Ensure PYTHONPATH includes the scripts/ directory:
export PYTHONPATH=scripts:$PYTHONPATH
Or use the Makefile, which sets this automatically.
"Could not fetch exceptions" warning
The exceptions tab doesn't exist in the Payments Google Sheet. This is non-fatal — reconciliation proceeds without fee overrides.
Development guide generated from comprehensive code analysis on 2026-03-03.