Files
fuj-management/docs/by-claude-opus/development.md
Jan Novak 9b99f6d33b
All checks were successful
Deploy to K8s / deploy (push) Successful in 8s
docs: experiment with generated documentation, let's keep it in git for
now
2026-03-11 11:57:30 +01:00

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:

  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:
@app.route("/new-page")
def new_page():
    # Fetch data
    record_step("fetch_data")
    # Process
    record_step("process_data")
    return render_template("new_page.html", ...)
  1. Create templates/new_page.html (copy structure from fees.html)

  2. Add a link in the nav bar across all templates:

<a href="/new-page">[New Page]</a>
  1. Add a test in tests/test_app.py

Adding a New Script

  1. Create scripts/new_script.py
  2. Add a Makefile target:
new-target: $(PYTHON)
    $(PYTHON) scripts/new_script.py
  1. Update make help output
  2. Add the .PHONY declaration

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:

  1. Design phase — Initial brainstorming captured in docs/project-notes.md
  2. CLI toolscalculate_fees.py and match_payments.py for command-line workflows
  3. Bank integrationfio_utils.py for transparent page scraping, later API support
  4. Google Sheets syncsync_fio_to_sheets.py + infer_payments.py for the ledger pipeline
  5. Web dashboardapp.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:

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.