feat: initial dashboard implementation and robust attendance parsing

- Added a Makefile to easily run project scripts (fees, match, web, image)
- Modified attendance.py to dynamically handle a variable number of header rows from the Google Sheet
- Updated both attendance calculations and calculate_fees terminal output to show actual attendance counts (e.g., '750 CZK (3)')
- Created a Flask web dashboard (app.py and templates/fees.html) to view member fees in an attractive, condensed, terminal-like UI
- Bound the Flask server to port 5000 and added a routing alias from '/' to '/fees'
- Configured Python virtual environment (.venv) creation directly into the Makefile to resolve global pip install errors on macOS

Co-authored-by: Antigravity <antigravity@deepmind.com>
This commit is contained in:
Jan Novak
2026-02-27 13:19:00 +01:00
commit 3bfea4e0a4
16 changed files with 1322 additions and 0 deletions

79
docs/scripts.md Normal file
View File

@@ -0,0 +1,79 @@
# Scripts
All scripts live in `scripts/` and use Python 3.10+ with stdlib only (no pip dependencies).
## calculate_fees.py
Calculates monthly fees for each Adult member based on Tuesday practice attendance.
```bash
cd scripts && python3 calculate_fees.py
```
Outputs a table of Adult members with their monthly fee (0 / 200 / 750 CZK) and totals per month. Data is fetched live from the Google Sheet.
## match_payments.py
Matches incoming bank payments against expected fees to produce a reconciliation report.
```bash
cd scripts && python3 match_payments.py [--from YYYY-MM-DD] [--to YYYY-MM-DD]
```
| Option | Default | Description |
| --- | --- | --- |
| `--from` | `2025-12-01` | Start of date range for bank transactions |
| `--to` | today | End of date range |
**Bank data access** is controlled by the `FIO_API_TOKEN` environment variable:
- **Set** — uses the Fio REST API (JSON, structured data, all fields)
- **Not set** — scrapes the public transparent account HTML page
```bash
# With API token:
FIO_API_TOKEN=xxx python3 match_payments.py --from 2026-01-01 --to 2026-02-11
# Without (public page):
python3 match_payments.py --from 2026-01-01 --to 2026-02-11
```
**Report sections:**
1. **Summary table** — per member, per month: `OK` / `UNPAID {amount}` / `{paid}/{expected}` + balance
2. **Credits** — advance payments for months without attendance data yet
3. **Unmatched transactions** — payments the script couldn't assign to any member
4. **Matched transaction details** — full breakdown of which payment was assigned where, with `[REVIEW]` tags on low-confidence matches
**Known limitations:**
- Lump-sum payments covering multiple months are split evenly rather than by actual per-month fee
- Messages with no member name and a sender not in the member list cannot be matched
- Common surnames (Novák) are excluded from last-name-only matching to avoid false positives
## Shared modules
### attendance.py
Shared attendance and fee logic, imported by both scripts above.
Key functions:
| Function | Description |
| --- | --- |
| `fetch_csv()` | Fetches the Google Sheet as parsed CSV rows |
| `parse_dates(header_row)` | Extracts `(column_index, date)` pairs from the header |
| `group_by_month(dates)` | Groups column indices by `YYYY-MM` |
| `calculate_fee(count)` | Applies fee rules: 0→0, 1→200, 2+→750 CZK |
| `get_members(rows)` | Parses member rows into `(name, tier, row)` tuples |
| `get_members_with_fees()` | Full pipeline: fetch → parse → compute fees. Returns `(members, sorted_months)` |
### czech_utils.py
Czech language text utilities.
| Function | Description |
| --- | --- |
| `normalize(text)` | Strip diacritics and lowercase (`Štrúdl``strudl`) |
| `parse_month_references(text)` | Extract `YYYY-MM` strings from Czech free text. Handles month names in all declensions (`leden`, `ledna`, `lednu`), numeric formats (`01/26`, `11+12/2025`), dot notation (`12.2025`), and ranges (`listopad-leden`) |
| `CZECH_MONTHS` | Dict mapping normalized Czech month names (all declensions) to month numbers |