Skip to main content

PitchEcho

AlphaGo-inspired football pass analysis — find the optimal pass target in any match snapshot.

Just as AlphaGo evaluates every possible move on the board, Threader freezes a moment in a football match and asks: who is the best player to pass to?

Scenario: De Bruyne receives the ball at the center circle
  1st choice → Haaland       Pass Score: 78
  2nd choice → Foden          Pass Score: 76
  3rd choice → Rodri          Pass Score: 61

Built on FIFA World Cup 2022 data (64 matches, PFF FC format). Interactive Dash web app with Plotly pitch visualizations and tracking-data animations.


Quick Start

Requirements: Python ≥ 3.12, uv

# Clone & install
git clone https://github.com/your-username/Threader.git
cd Threader
uv sync

# Run the web app
uv run threader
# → http://127.0.0.1:8050

Optional extras

uv sync --extra dev          # pytest + ruff
uv sync --extra validation   # scipy + tabulate (for validation scripts)
uv sync --extra learning     # scikit-learn + xgboost (for ML weight training)
uv sync --extra deploy       # gunicorn (for production deployment)

How It Works

The AlphaGo Analogy

AlphaGo Threader
Board state (19×19 grid + stones) Match snapshot (22 player positions)
Move selection Pass target selection
Win rate evaluation Pass Score evaluation (0–100)
1st / 2nd / 3rd choice ranking Pass option ranking

Pass Score — The Core Metric

Every potential pass target is evaluated across 5 dimensions, combined into a single 0–100 score:

Pass Score = (
    completion × zone_value × 4.27     # Expected value
  + penetration × 1.00                 # Forward progress bonus
  + space × 0.0001                     # Space tiebreaker
) × (1 − pressure/10 × 0.01)          # Pressure multiplier
  × 100
Dimension What it measures Range
Completion Probability Can this pass reach its target? (distance decay + passing lane obstruction) 0 – 1
Zone Value (xT) How dangerous is the receiver's position? (Expected Threat grid) 0 – 0.45
Receiving Pressure Will the receiver be under pressure? (3 nearest defenders) 0 – 10
Space Available How much room does the receiver have? (capped at 15m) 0 – 15m
Penetration Score How much forward progress does this pass achieve? (distance + defenders bypassed) -0.3 – 1.0

Weights were optimized via scipy.optimize.differential_evolution maximizing Spearman ρ(Pass Score, ΔxT) across all 64 World Cup matches (ρ = 0.654, AUC = 0.768).


Web App

The interactive Dash application lets you explore every pass in the tournament:

  • Match selector — browse all 64 FIFA World Cup 2022 matches
  • Pass event browser — pick any pass event with period, clock, passer → target, and outcome
  • Interactive pitch — Plotly rendering with players, ball, and ranked pass arrows (gold / silver / bronze)
  • Analysis cards — ranked pass options with 5-dimension breakdowns
  • Tracking animation — "Play Pass" replays the actual pass sequence from 25fps tracking data
  • Click-to-highlight — click any option card to highlight that pass on the pitch

Production Deployment

uv sync --extra deploy
uv run gunicorn threader.app:server -b 0.0.0.0:8050

Project Structure

src/threader/
├── app.py                  # Dash web application
├── models.py               # Core data models (Player, Snapshot, PassOption, etc.)
├── analysis/
│   └── analyzer.py         # Pass analysis engine (analyze_snapshot / analyze_pass_event)
├── scoring/
│   ├── pass_score.py       # Master Pass Score formula + ScoringWeights
│   ├── completion.py       # Completion probability (distance + lane blocking)
│   ├── zone_value.py       # xT Expected Threat grid (12×8)
│   ├── pressure.py         # Receiving pressure (weighted nearest defenders)
│   ├── space.py            # Space available (distance to nearest opponent)
│   └── penetration.py      # Penetration score (forward gain + defenders bypassed)
├── geometry/
│   ├── distance.py         # Euclidean distance, point-to-segment distance
│   └── passing_lane.py     # Passing lane obstruction detection
├── data/
│   ├── events.py           # PFF event data JSON parser
│   ├── metadata.py         # Match metadata + roster loading
│   ├── tracking.py         # Tracking data loader (kloppy, .jsonl.bz2)
│   └── tracking_frames.py  # Fast animation frames (Parquet primary, ~20-50ms)
├── viz/
│   ├── plotly_pitch.py     # Plotly pitch rendering
│   ├── plotly_passes.py    # Plotly pass analysis visualization
│   ├── plotly_animation.py # Plotly tracking-data animation builder
│   ├── pitch.py            # Matplotlib pitch rendering
│   └── passes.py           # Matplotlib pass visualization
├── validation/
│   ├── collector.py        # Batch analysis + ground truth collection
│   ├── baselines.py        # Baseline comparison models
│   ├── significance.py     # Statistical significance tests
│   ├── sensitivity.py      # Weight perturbation + ablation analysis
│   ├── consistency.py      # Internal consistency checks
│   └── repeatability.py    # Cross-match reliability + bootstrap CI
└── learning/
    ├── dataset.py          # Feature matrix preparation
    ├── models.py           # Linear / Ridge / XGBoost model layers
    ├── evaluate.py         # Re-scoring with learned weights
    └── interpret.py        # Coefficient → ScoringWeights mapping

scripts/
├── validate_scoring.py     # Full validation report (CLI)
├── optimize_weights.py     # Weight optimization (differential evolution)
├── train_scoring_model.py  # ML weight learning pipeline
└── convert_to_parquet.py   # Tracking data → Parquet conversion

Data

Uses the PFF FC FIFA World Cup 2022 open dataset:

  • 64 matches with event data, metadata, rosters, and tracking data
  • Event Data — JSON files with pass events + embedded 22-player freeze-frames
  • Tracking Data — .jsonl.bz2 files at 25fps (convertible to Parquet for fast loading)
  • Coordinate system — PFF center-origin: x ∈ [-52.5, 52.5], y ∈ [-34, 34]
uv run python scripts/convert_to_parquet.py

This enables ~20-50ms animation frame queries via PyArrow predicate pushdown, vs streaming .jsonl.bz2.


Scripts

Validate the scoring system

uv run python scripts/validate_scoring.py

Produces a comprehensive report: data summary, baseline comparisons, statistical significance (Spearman ρ, AUC), sensitivity analysis, and cross-match repeatability.

Optimize scoring weights

uv run python scripts/optimize_weights.py

Uses differential evolution to find weights that maximize Spearman ρ(Pass Score, ΔxT).

Train ML models

uv run python scripts/train_scoring_model.py

Trains Linear / Ridge / XGBoost models to learn optimal dimension weights from data.


Testing

uv run pytest

Tests cover the Pass Score formula, completion probability, geometry algorithms, and analyzer integration.


Design Philosophy

A pure tactical optimization AI — only evaluating the optimal solution at the current instant.

What Threader does:

  • Evaluates pass targets (who to pass to)
  • Static snapshot analysis (freeze a moment, find the optimal solution)
  • Multi-dimensional scoring with explainable breakdowns
  • Clear 1st / 2nd / 3rd choice ranking

What Threader does NOT do (by design):

  • No match context (score, time, leading/trailing)
  • No risk preference (conservative vs aggressive)
  • No chain reaction prediction (what happens after the pass)
  • No pass trajectory analysis (ball physics)

See CLAUDE.md for the full design document.


Tech Stack

Layer Technology
Language Python 3.12+
Web framework Dash + Plotly
Data processing pandas, NumPy, PyArrow
Tracking data kloppy
Optimization SciPy (differential evolution)
ML (optional) scikit-learn, XGBoost
Testing pytest
Linting Ruff
Packaging hatchling + uv

License

GPL-3.0-or-later

Metadata

Release files for pitch-echo 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pitch-echo 0.1.0
File Size Uploaded
pitch_echo-0.1.0.tar.gz 240.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pitch-echo 0.1.0
File Interpreter ABI Platform
pitch_echo-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 347.6 kB

Release files / pitch_echo-0.1.0.tar.gz

Download URL pitch_echo-0.1.0.tar.gz
Size 240.4 kB
Tags Source
SHA-256 checksum
How to use checksums
a0f81dbe9fb72860af31818e304a3372013247cb3ab6645cd509b20a9fde4f75
BLAKE2b-256 checksum
How to use checksums
e96ba1564c70ff802f8bebb21b937ff10dd029231a8fcd45dbc033534df40188
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.16 {"installer":{"name":"uv","version":"0.9.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / pitch_echo-0.1.0-py3-none-any.whl

Download URL pitch_echo-0.1.0-py3-none-any.whl
Size 107.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3e1791f50a6d9207180598311d1b97251058d43827fbda24a3661f23f8f98143
BLAKE2b-256 checksum
How to use checksums
8a8effdf5af1c839151acce580c5082c42752a1af6b1f4e9581cac92668d3465
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.16 {"installer":{"name":"uv","version":"0.9.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page