# 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 ```bash # 1. Clone the repository git clone 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: ```python # 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](https://www.conventionalcommits.org/): ``` 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 ``` ## Architecture Decisions ### Why No Database? Google Sheets serves as the database because: 1. Club members can view and correct data without special tools 2. No database server to manage or back up 3. Built-in version history and collaborative editing 4. 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 1. Add the route function in `app.py`: ```python @app.route("/new-page") def new_page(): # Fetch data record_step("fetch_data") # Process record_step("process_data") return render_template("new_page.html", ...) ``` 2. Create `templates/new_page.html` (copy structure from `fees.html`) 3. Add a link in the nav bar across all templates: ```html [New Page] ``` 4. Add a test in `tests/test_app.py` ### Adding a New Script 1. Create `scripts/new_script.py` 2. Add a Makefile target: ```makefile new-target: $(PYTHON) $(PYTHON) scripts/new_script.py ``` 3. Update `make help` output 4. Add the `.PHONY` declaration ### Modifying Fee Rules Fee rules are defined as constants in `scripts/attendance.py`: ```python FEE_FULL = 750 # 2+ practices FEE_SINGLE = 200 # 1 practice ``` The calculation logic is in `calculate_fee()`: ```python 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`: ```python CZECH_MONTHS = { "leden": 1, "ledna": 1, "lednu": 1, "lednem": 1, # New instrumental case ... } ``` ## Project History The project evolved through distinct phases: 1. **Design phase** — Initial brainstorming captured in `docs/project-notes.md` 2. **CLI tools** — `calculate_fees.py` and `match_payments.py` for command-line workflows 3. **Bank integration** — `fio_utils.py` for transparent page scraping, later API support 4. **Google Sheets sync** — `sync_fio_to_sheets.py` + `infer_payments.py` for the ledger pipeline 5. **Web dashboard** — `app.py` with the `/fees`, `/reconcile`, and `/payments` pages 6. **Interactive features** — Modal popups, QR payments, keyboard navigation, search filter 7. **Fee exceptions** — Manual override system via the `exceptions` sheet tab 8. **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.py` is 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: ```bash 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.*