Metadata-Version: 2.4
Name: tmviz
Version: 0.1.0
Summary: Single-match team report generator for football: 18-page PDF (overview, per-team passing/carries/defense/network, individual maps, shots, momentum, top-performer tables) from SPADL event data.
Author: Yureed Elahi
License: MIT
Project-URL: Repository, https://github.com/yureed/tmviz
Project-URL: Issues, https://github.com/yureed/tmviz/issues
Project-URL: Twitter, https://twitter.com/yureedelahi
Keywords: football,soccer,analytics,spadl,match report,xt,xg
Classifier: Development Status :: 3 - Alpha
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: License :: OSI Approved :: MIT License
Classifier: Topic :: Scientific/Engineering :: Visualization
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pandas>=2.0
Requires-Dist: numpy>=1.24
Requires-Dist: pyyaml>=6.0
Requires-Dist: playwright>=1.40
Requires-Dist: Pillow>=10.0
Dynamic: license-file

# tmviz

Single-match team report generator for football. Feed it one SPADL-style event CSV; get back an 18-page report (per-team passing, carries, defensive activity, individual maps, shot map, match-flow momentum, top performers) plus a stitched PDF.

```
python -m tmviz make <match.csv> --home X --away Y [...flags]   # one-shot, scriptable
python -m tmviz wizard <match.csv>                              # interactive prompts
python -m tmviz render <config.yml>                             # re-render a saved config
python -m tmviz pages                                           # list available pages
```

## Install

```bash
git clone https://github.com/yureed/tmviz.git
cd tmviz
pip install -e .
playwright install chromium             # one-time
```

## Quickstart

```bash
python -m tmviz make match.csv \
    --home "Arsenal" --away "Atletico" \
    --matchday 35 --date 2026-04-26 \
    --venue "Emirates Stadium" \
    --competition "Champions League 25/26" \
    --out-dir out/arsenal_atletico
```

Renders 18 PNGs and one combined PDF to `out/arsenal_atletico/`. Run with `--help` to see every flag.

### Python API

```python
from tmviz import generate_report

generate_report(
    csv="match.csv",
    home="Arsenal", away="Atletico",
    matchday=35, date="2026-04-26",
    venue="Emirates Stadium",
    competition="Champions League 25/26",
    out_dir="out/arsenal_atletico",
)
```

Returns `{"html_paths": [...], "png_paths": [...], "pdf_path": Path}`.

### Wizard

For interactive use — auto-detects team_ids, lists each detected goal so you can flag own-goals, collects metadata, and saves a YAML config you can re-render later:

```bash
python -m tmviz wizard match.csv
```

## Data format

**One row per event.** Required columns:

| column | type | notes |
|---|---|---|
| `type_name` | str | event verb — see vocabulary below |
| `team_id` | int | one of two team ids in the file |
| `period_id` | int | 1, 2 (3, 4 if extra time) |
| `time_seconds` | float | seconds within the period |
| `start_x`, `start_y` | float | Opta 0–100 coordinates |
| `end_x`, `end_y` | float | same scale |

**`type_name` vocabulary** — tmviz uses the SPADL standard. The names that map to common concepts:

| concept | `type_name` value(s) |
|---|---|
| pass | `pass` |
| cross | `cross` |
| **carry** (a player moving with the ball) | `dribble` |
| **take-on** (1v1 dribble past a defender) | `take_on` |
| shot | `shot` |
| direct free-kick shot | `shot_freekick` |
| penalty shot | `shot_penalty` |
| corner delivery (in-swinger / out-swinger) | `corner_crossed` |
| corner played short | `corner_short` |
| free-kick whipped into the box | `freekick_crossed` |
| free-kick played short | `freekick_short` |
| throw-in | `throw_in` |
| tackle | `tackle` |
| interception | `interception` |
| clearance | `clearance` |
| header (any of above when contested in the air) | bodypart_name = `head` |

If you have data from a different schema, you'll need a thin transform to remap your event names to these strings before handing the CSV to tmviz. The vocabulary above is the SPADL convention used by `socceraction` and most public xT/xG models.

**Player names** — exactly one of:
- a `player_name` column inline in the main CSV (preferred), **or**
- a `player_id` column + a separate file passed via `--players-csv` (cols: `player_id, player_name`). tmviz joins it in at load.

**Team names** come from the `--home` / `--away` flags (or wizard prompts). Optional: pass `--teams-csv` (cols: `team_id, team_name`) to give the wizard nicer name suggestions.

### Recommended (better output if present)

| column | what improves |
|---|---|
| `result_name` | success/fail — feeds pass-completion %, take-on success rate, etc. |
| `is_goal` | flags goals on the shot map and in the goals timeline |

### Optional (auto-used if present, ignored if not)

| column | what it does |
|---|---|
| `xG` (or `xg`) | sizes shot circles by chance quality, adds team xG totals + an xG column on the tables page, and switches the match-flow chart to xG-based momentum |
| `xT_added` | per-event expected-threat. **If absent, tmviz auto-computes it on load** using the bundled Karun Singh xT grid — you only need raw coordinates. |

### Coordinate orientation

tmviz expects each team's events in their own attacking direction (low x = own goal, high x = opp goal — SPADL canon). If your input uses absolute coordinates (one fixed system where home attacks right and away attacks left), tmviz detects it from per-team mean shot start_x and flips the away team automatically. You'll see `Note: detected absolute coords — flipped team_id N` in the load output when this happens.

## Pages

| name | what |
|---|---|
| `overview` | Score, goals timeline, both-team shot dots on a mini pitch, head-to-head stat comparison (possession, shots, xG/xT, pass %, prog passes, prog carries, take-ons, box entries, defensive actions) |
| `passing_home`, `passing_away` | Full-pitch open-play pass map per team. Forward = ink, backward = grey, failed = red dotted, **progressive = gold**. Side panel: top 5 progressive passers + key team rates |
| `network_home`, `network_away` | Pass network: nodes at average position sized by passes attempted, edges sized by combination count, top 8 combinations listed |
| `players_pass_home`, `players_pass_away` | One mini-pitch per player who featured, showing their passes for the match |
| `carries_home`, `carries_away` | All carries (progressive in gold) + take-on attempts (won/lost), top 5 carriers + take-on winners |
| `players_carry_home`, `players_carry_away` | One mini-pitch per player, showing their carries + take-ons |
| `defense_home`, `defense_away` | Defensive actions heatmap + shape markers (tackle / interception / clearance / aerial duel won-or-lost) |
| `players_defense_home`, `players_defense_away` | One mini-pitch per player, showing their defensive actions |
| `shots` | Both teams' shots on a single full pitch — colored by team, sized by xG (if present), goals haloed in gold |
| `flow` | Match-flow **momentum graph**: rolling 5-minute danger signal per team plotted around a zero line (above = home on top, below = away). Uses xG if available, otherwise xT. Goals planted on the zero line. |
| `tables` | 6 (or 7 with xG) side-by-side per-team tables: top 5 in xT, prog passes, prog carries, take-ons, defensive actions, shots |

Use `--pages overview,shots,flow,tables` (or any subset) to generate fewer pages.

## Output

For each match:

- `out/<dir>/01_overview.html` … `18_tables.html`
- One PNG per page (rendered at 2× device scale)
- One combined PDF: `<home>_<score>_<away>_md<n>.pdf`

## Themes

`cream` (default) — newsprint paper, ink black, red/gold/blue accents.

To add a theme: drop a dataclass into `tmviz/themes/`, register it in `tmviz/themes/__init__.py:THEMES`. Pages read all colors via CSS variables so a theme swap restyles every page.

## Adding a new page

1. Subclass `tmviz.pages.base.BasePage` and implement `build(cfg, df, derived) -> str` returning the inner HTML (the shell adds top strip + footer + styles).
2. Register in `tmviz/pages/__init__.py:REGISTRY`.
3. Add the page name to `cfg.pages` in any config that wants it.

## License

MIT.
