# AGENTS.md — Pub Quiz Dashboard ## Overview Single-page Flask dashboard that reads `data.csv` (one row per quiz night) and renders statistics, a player table, and Plotly charts via a Jinja2 template. No database — the CSV is the sole data store. ## Running the App ```bash uv sync # install dependencies (uses uv.lock / pyproject.toml) cd src && python app.py # start Flask dev server at http://127.0.0.1:5000 ``` > **Critical:** `data.csv` is read with a bare relative path (`"data.csv"`), so the app **must** be launched from the `src/` directory, where Flask's CWD resolves to the project root via the relative path `../data.csv` — actually, `data.csv` sits at project root and `src/` is the CWD, so Flask resolves it as `../data.csv` would fail. The app reads `data.csv` directly, so run from `src/` only after confirming the path resolves (currently works because Flask's CWD is `src/` and `data.csv` is read as a sibling — verify if moving files). ## Data Shape (`data.csv`) - **One row = one quiz night** - Columns: `Date` (DD/MM/YYYY), `Absolute Position`, `Relative Position` (float 0–1, where 0=1st, 1=last), `Number of Players`, `Number of Teams`, `Points on Scattergories`, then one binary column per player (1=attended, 0=absent). - Date parsing is always `dayfirst=True`; the DataFrame is sorted by date on load. ## Module Responsibilities | File | Role | |---|---| | `src/app.py` | Flask routes, all Plotly figure generation, data loading | | `src/stats.py` | Summary statistics dict (streaks, averages) passed to template as `stats` | | `src/player_table.py` | List-of-lists table `[header, ...rows..., footer]`; cost fixed at **£3/quiz** per player | | `src/constants.py` | Single source of truth for player names, regression features, and colour scheme | | `src/templates/index.html` | Renders `stats` dict, `player_table` list, and `plots` dict of JSON-serialised Plotly figures | ## Key Conventions ### Adding/Removing a Player 1. Update `constants.PLAYER_NAME_COLUMNS` (ordered list — controls display order). 2. Update `constants.FEATURE_COLUMNS` (set — controls regression inputs). 3. Add the new column to `data.csv` with `0`/`1` values. ### Plots Pipeline Figures are built with Plotly in `app.py`, serialised to JSON with `plotly.utils.PlotlyJSONEncoder`, stored in a `plots` dict keyed by a snake_case name, and rendered client-side via `Plotly.newPlot("{{ key }}", figure.data, figure.layout)` in the template. The dict key becomes both the HTML `id` and the JS variable target — keep keys unique and valid as HTML ids. ### `player_table` Structure A flat list-of-lists: index `[0]` = header row, `[1:-1]` = data rows, `[-1]` = footer row. The template iterates `player_table[1:]` for `
`, so the footer is rendered as a regular row — style it in CSS if distinction is needed. ### `Relative Position` Semantics Lower is better (0 = first place). The trendline in `generate_relative_position_over_time` extrapolates towards `target_value = 0.08` (≈top 8%). Adjust this constant to change the goal projection. ## Frontend No build step. Tailwind CSS and Plotly are loaded from CDN in `index.html`. All styling is inline Tailwind utility classes.