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:
69
docs/fee-calculation-spec.md
Normal file
69
docs/fee-calculation-spec.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# Fee Calculation Spec — Tuesday Practices
|
||||
|
||||
## Data Source
|
||||
|
||||
- Google Sheet: `1E2e_gT_K5AwSRCDLDTa2UetZTkHmBOcz0kFbBUNUNBA`
|
||||
- Sheet: first sheet only (Tuesday practices, 20:30–22:00)
|
||||
- Public export URL (CSV): `https://docs.google.com/spreadsheets/d/1E2e_gT_K5AwSRCDLDTa2UetZTkHmBOcz0kFbBUNUNBA/export?format=csv`
|
||||
|
||||
## Sheet Structure
|
||||
|
||||
| Row | Content |
|
||||
| --- | --- |
|
||||
| 1 | Header: title in col A, dates in cols D+ (format `M/D/YYYY`) |
|
||||
| 2 | Venue per date (irrelevant for pricing) |
|
||||
| 3 | Total attendees per date |
|
||||
| 4+ | Member rows: Name (col A), Tier (col B), Total (col C), attendance TRUE/FALSE (cols D+) |
|
||||
|
||||
Member rows end when the Name column is empty.
|
||||
|
||||
## Tiers
|
||||
|
||||
| Code | Meaning | Pays from this sheet? |
|
||||
| --- | --- | --- |
|
||||
| A | Adult | Yes |
|
||||
| J | Junior | No (paid via separate attendance sheet) |
|
||||
| X | Exempt | No |
|
||||
|
||||
## Fee Rules (Adults only)
|
||||
|
||||
Fees are calculated per calendar month based on the number of attended practices in that month.
|
||||
|
||||
| Practices in month | Fee |
|
||||
| --- | --- |
|
||||
| 0 | 0 CZK |
|
||||
| 1 | 200 CZK |
|
||||
| 2 or more | 750 CZK |
|
||||
|
||||
## Payment Matching
|
||||
|
||||
### Bank Account
|
||||
|
||||
- Fio banka transparent account: `2800359168/2010`
|
||||
- Owner: Nathan Heilmann
|
||||
- Public view: `https://ib.fio.cz/ib/transparent?a=2800359168`
|
||||
|
||||
### Data Access
|
||||
|
||||
- **Without API token**: scrape the public transparent account HTML page
|
||||
- **With API token**: Fio REST API at `https://fioapi.fio.cz/v1/rest/periods/{token}/{from}/{to}/transactions.json`
|
||||
- Token is generated in Fio internetbanking (Settings → API)
|
||||
- Rate limit: 1 request per 30 seconds per token
|
||||
- Available fields: date, amount, currency, sender account, sender name, VS, SS, KS, user identification, message, type, and more
|
||||
|
||||
### Matching Approach
|
||||
|
||||
Payments are matched to members using best-effort heuristics, with uncertain matches flagged for manual review.
|
||||
|
||||
1. **Name matching**: Normalize (strip diacritics, lowercase) sender name and message text, compare against member names and nicknames
|
||||
2. **Month parsing**: Extract Czech month names (leden, únor, ...) and numeric patterns (01/26, 1/2026) from the message
|
||||
3. **Amount validation**: Check if amount aligns with expected fees (200, 750, or multiples)
|
||||
4. **Multi-person splitting**: When a message references multiple members, split the payment across them
|
||||
|
||||
### Advance Payments
|
||||
|
||||
If a payment references a month with no attendance data yet, it is tracked as **credit** on the member's account. The credit is applied once that month's attendance is recorded.
|
||||
|
||||
## PII Constraint
|
||||
|
||||
No member names or personal data are committed to git. All data is fetched at runtime from the Google Sheet and bank account.
|
||||
61
docs/project-notes.md
Normal file
61
docs/project-notes.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# Sports Club Financial Management — Project Notes
|
||||
|
||||
> **Context for Claude Code:** This document captures an ongoing brainstorming session
|
||||
> started in Claude.ai chat. The owner is an experienced SRE/programmer. We are still
|
||||
> in the discovery/design phase — no code has been written yet. Next steps: fill in
|
||||
> current state (Section 3), then move into incremental automation design and implementation.
|
||||
|
||||
## 1. Project Goal
|
||||
|
||||
Design and incrementally automate financial and operational management for a small sports club.
|
||||
|
||||
## 2. Domain Entities (Draft)
|
||||
|
||||
- **Members** — roster, roles (player/coach/parent), contact info, membership status
|
||||
- **Fees** — recurring (monthly/seasonal), one-off (tournament entry), per-member or per-family
|
||||
- **Attendance** — practice sessions, matches, tournaments; who showed up
|
||||
- **Expenses** — facility rental, equipment, travel, referee fees, insurance
|
||||
- **Ledger** — income (fees, sponsors, fundraising) vs. expenses; balance tracking
|
||||
|
||||
## 3. Current State
|
||||
|
||||
_TODO: To be filled — critical input needed before design/implementation._
|
||||
|
||||
- Club size (members, teams):
|
||||
- Current tooling (spreadsheets? paper? existing app?):
|
||||
- System users (besides owner):
|
||||
- Biggest pain point / what to solve first:
|
||||
|
||||
## 4. Automation Candidates (by estimated ROI)
|
||||
|
||||
1. Fee billing & payment tracking (reminders, status per member)
|
||||
2. Attendance logging (check-in mechanism)
|
||||
3. Expense categorization & reporting (monthly summaries, budget vs. actual)
|
||||
4. Tournament management (signup, fee collection, travel)
|
||||
|
||||
## 5. Tech Considerations
|
||||
|
||||
- Who operates / interacts with the system?
|
||||
- Complexity spectrum: Spreadsheet + Apps Script → lightweight web app → full platform
|
||||
- Integration points: Slack, Google Forms, payment gateways, etc.
|
||||
- **PII caution:** member data (names, emails, payment info) must stay out of git from day one. Enforce config/data separation early.
|
||||
|
||||
## 6. Suggested Approach
|
||||
|
||||
1. **Map domain** — finalize entities and workflows (Section 2 & 3)
|
||||
2. **Identify pain points** — what's the worst manual step today?
|
||||
3. **Design automation incrementally** — start with highest-ROI item
|
||||
4. **Build** — iterate in this repo
|
||||
|
||||
## 7. Open Questions
|
||||
|
||||
- All items in Section 3 are unresolved.
|
||||
- Tech stack TBD — depends on who the users are and complexity needs.
|
||||
|
||||
## 8. Decision Log
|
||||
|
||||
| Date | Decision | Rationale |
|
||||
|------------|---------------------------------------|--------------------------------------------------------------|
|
||||
| 2025-02-11 | Store project docs in git repo | Markdown-native, versioned, natural evolution toward code |
|
||||
| 2025-02-11 | Hybrid workflow: chat → Claude Code | Chat better for brainstorming; Claude Code for building |
|
||||
| 2025-02-11 | PII stays out of repo from day one | Avoid retrofitting data separation later |
|
||||
79
docs/scripts.md
Normal file
79
docs/scripts.md
Normal 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 |
|
||||
Reference in New Issue
Block a user