Files
pub-quiz/AGENTS.md
John Gatward 80a66bc44c enhanced
2026-03-19 21:43:34 +00:00

63 lines
3.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AGENTS.md — The Hope Pub Quiz Dashboard
## Overview
Single-page Flask dashboard that reads `data.csv` (one row per quiz night) and renders summary statistics, a player cost table, and five Plotly charts via a Jinja2 template. No database — the CSV is the sole data store.
## Running the App
```bash
uv sync # install deps from uv.lock / pyproject.toml
PYTHONPATH=src python src/app.py # run from project root — resolves data.csv correctly
```
> **Path gotcha:** `app.py` reads `data.csv` with a bare relative path, so the CWD must be the **project root**. `PYTHONPATH=src` puts `src/` on the import path so local modules resolve.
## Data Shape (`data.csv`)
- **One row = one quiz night**
- Columns: `Date` (DD/MM/YYYY), `Absolute Position`, `Relative Position` (float 01, 0=1st/best, 1=last), `Number of Players`, `Number of Teams`, `Points on Scattergories`, then one binary column per player (1=attended, 0=absent).
- Date parsing uses `dayfirst=True`; the DataFrame is sorted ascending by date on every load.
## Module Responsibilities
| File | Role |
|---|---|
| `src/app.py` | Flask route, five Plotly chart builders, data loading |
| `src/stats.py` | `generate_stats(df)``(stats_dict, highlights_list)` tuple |
| `src/player_table.py` | `generate_player_table(df)` → flat list-of-lists; cost hard-coded at **£3/quiz** |
| `src/constants.py` | Player names, regression features, colour scheme, `ordinal(n)` helper |
| `src/templates/index.html` | Renders `highlights` list, `stats` dict, `player_table`, and `plots` dict |
## Key Conventions
### Adding/Removing a Player
1. Update `constants.PLAYER_NAME_COLUMNS` (ordered list — controls display order everywhere).
2. Update `constants.FEATURE_COLUMNS` (set — controls which columns feed the regression model).
3. Add the new binary column to `data.csv`.
### `generate_stats` Return Value
Returns a **tuple** `(stats, highlights)`:
- `highlights` — list of `{"label": str, "value": str, "detail": str}` dicts; rendered as 6 KPI cards.
- `stats` — plain `dict` of human-readable `label: value` pairs; rendered as a secondary list.
The `index()` route unpacks both: `stats, highlights = generate_stats(df)`.
### Plots Pipeline
1. Build a Plotly figure in `app.py` using `Relative Position` directly (or `Relative Position * 100` for percentile display), where **lower = better**.
2. Serialise: `json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)`.
3. Store in the `plots` dict under a **snake_case key** (e.g. `"position_trend"`).
4. The template renders every entry automatically: `Plotly.newPlot("{{ key }}", ...)` — key is both the `<div id>` and JS target.
Current charts (in render order): `position_trend`, `player_impact`, `scattergories_vs_position`, `player_participation`, `calendar`.
### `player_table` Structure
`[0]` = header row, `[1:-1]` = data rows sorted by appearances descending, `[-1]` = totals footer. The template uses `player_table[1:-1]` for `<tbody>` and `player_table[-1]` for `<tfoot>`.
### `Relative Position` Convention
Raw data stores `Relative Position` (0=best). The dashboard keeps this convention everywhere: lower values are better in stats, tables, and chart labels. If a chart uses percentile text, it is `Relative Position * 100` (still lower = better).
### `ordinal(n)` Helper
Lives in `constants.py`. Returns e.g. `"1st"`, `"22nd"`, `"63rd"`. Import where needed: `from constants import ordinal`.
### Player Impact Chart
Shows average relative percentile when each player attends. Only players with **>= 3 appearances** are shown (`MIN_APPEARANCES = 3` in `generate_player_impact`). Green bar = below overall average (better); red = above (worse).
## Frontend
No build step. Tailwind CSS (`cdn.tailwindcss.com`) and Plotly (`cdn.plot.ly/plotly-3.0.1.min.js`) loaded from CDN. Charts use `{ responsive: true, displayModeBar: false }` config.