Upload folder using huggingface_hub
Browse files- .gitattributes +25 -0
- README.md +47 -5
- app.py +280 -4
- data/demo_matches.json +177 -0
- data/vlm_results/frames/Atletico_Madrid_vs_Dortmund_2024-04-10/frame_000.jpg +3 -0
- data/vlm_results/frames/Atletico_Madrid_vs_Dortmund_2024-04-10/frame_003.jpg +3 -0
- data/vlm_results/frames/Atletico_Madrid_vs_Inter_Milan_2024-03-13/frame_001.jpg +3 -0
- data/vlm_results/frames/Atletico_Madrid_vs_Inter_Milan_2024-03-13/frame_005.jpg +3 -0
- data/vlm_results/frames/Atletico_Madrid_vs_Lazio_2023-12-13/frame_001.jpg +3 -0
- data/vlm_results/frames/Atletico_Madrid_vs_Lazio_2023-12-13/frame_005.jpg +3 -0
- data/vlm_results/frames/Barcelona_vs_Napoli_2024-03-12/frame_000.jpg +3 -0
- data/vlm_results/frames/Barcelona_vs_Napoli_2024-03-12/frame_006.jpg +3 -0
- data/vlm_results/frames/Barcelona_vs_PSG_2024-04-10/frame_002.jpg +0 -0
- data/vlm_results/frames/Barcelona_vs_PSG_2024-04-10/frame_005.jpg +0 -0
- data/vlm_results/frames/Dortmund_vs_Atletico_Madrid_2024-04-16/frame_002.jpg +3 -0
- data/vlm_results/frames/Dortmund_vs_Atletico_Madrid_2024-04-16/frame_006.jpg +3 -0
- data/vlm_results/frames/Dortmund_vs_PSV_2024-03-13/frame_003.jpg +3 -0
- data/vlm_results/frames/Dortmund_vs_PSV_2024-03-13/frame_005.jpg +3 -0
- data/vlm_results/frames/Inter_Milan_vs_Atletico_Madrid_2024-02-20/frame_002.jpg +0 -0
- data/vlm_results/frames/Inter_Milan_vs_Atletico_Madrid_2024-02-20/frame_003.jpg +3 -0
- data/vlm_results/frames/Inter_Milan_vs_Real_Sociedad_2023-12-12/frame_000.jpg +0 -0
- data/vlm_results/frames/Inter_Milan_vs_Real_Sociedad_2023-12-12/frame_001.jpg +3 -0
- data/vlm_results/frames/Man_City_vs_FC_Copenhagen_2024-03-06/frame_001.jpg +0 -0
- data/vlm_results/frames/Man_City_vs_FC_Copenhagen_2024-03-06/frame_003.jpg +3 -0
- data/vlm_results/frames/PSG_vs_Barcelona_2024-04-16/frame_002.jpg +3 -0
- data/vlm_results/frames/PSG_vs_Barcelona_2024-04-16/frame_005.jpg +3 -0
- data/vlm_results/frames/PSG_vs_Dortmund_2024-05-01/frame_000.jpg +3 -0
- data/vlm_results/frames/PSG_vs_Dortmund_2024-05-01/frame_001.jpg +3 -0
- data/vlm_results/frames/Real_Madrid_vs_Man_City_2024-04-09/frame_001.jpg +3 -0
- data/vlm_results/frames/Real_Madrid_vs_Man_City_2024-04-09/frame_004.jpg +3 -0
- data/vlm_results/frames/Real_Madrid_vs_RB_Leipzig_2024-03-06/frame_000.jpg +3 -0
- data/vlm_results/frames/Real_Madrid_vs_RB_Leipzig_2024-03-06/frame_002.jpg +3 -0
- data/vlm_results/frames/Real_Sociedad_vs_PSG_2024-03-05/frame_002.jpg +3 -0
- data/vlm_results/frames/Real_Sociedad_vs_PSG_2024-03-05/frame_004.jpg +3 -0
- data/vlm_results/results.json +481 -0
- requirements.txt +2 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,28 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
data/vlm_results/frames/Atletico_Madrid_vs_Dortmund_2024-04-10/frame_000.jpg filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
data/vlm_results/frames/Atletico_Madrid_vs_Dortmund_2024-04-10/frame_003.jpg filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
data/vlm_results/frames/Atletico_Madrid_vs_Inter_Milan_2024-03-13/frame_001.jpg filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
data/vlm_results/frames/Atletico_Madrid_vs_Inter_Milan_2024-03-13/frame_005.jpg filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
data/vlm_results/frames/Atletico_Madrid_vs_Lazio_2023-12-13/frame_001.jpg filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
data/vlm_results/frames/Atletico_Madrid_vs_Lazio_2023-12-13/frame_005.jpg filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
data/vlm_results/frames/Barcelona_vs_Napoli_2024-03-12/frame_000.jpg filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
data/vlm_results/frames/Barcelona_vs_Napoli_2024-03-12/frame_006.jpg filter=lfs diff=lfs merge=lfs -text
|
| 44 |
+
data/vlm_results/frames/Dortmund_vs_Atletico_Madrid_2024-04-16/frame_002.jpg filter=lfs diff=lfs merge=lfs -text
|
| 45 |
+
data/vlm_results/frames/Dortmund_vs_Atletico_Madrid_2024-04-16/frame_006.jpg filter=lfs diff=lfs merge=lfs -text
|
| 46 |
+
data/vlm_results/frames/Dortmund_vs_PSV_2024-03-13/frame_003.jpg filter=lfs diff=lfs merge=lfs -text
|
| 47 |
+
data/vlm_results/frames/Dortmund_vs_PSV_2024-03-13/frame_005.jpg filter=lfs diff=lfs merge=lfs -text
|
| 48 |
+
data/vlm_results/frames/Inter_Milan_vs_Atletico_Madrid_2024-02-20/frame_003.jpg filter=lfs diff=lfs merge=lfs -text
|
| 49 |
+
data/vlm_results/frames/Inter_Milan_vs_Real_Sociedad_2023-12-12/frame_001.jpg filter=lfs diff=lfs merge=lfs -text
|
| 50 |
+
data/vlm_results/frames/Man_City_vs_FC_Copenhagen_2024-03-06/frame_003.jpg filter=lfs diff=lfs merge=lfs -text
|
| 51 |
+
data/vlm_results/frames/PSG_vs_Barcelona_2024-04-16/frame_002.jpg filter=lfs diff=lfs merge=lfs -text
|
| 52 |
+
data/vlm_results/frames/PSG_vs_Barcelona_2024-04-16/frame_005.jpg filter=lfs diff=lfs merge=lfs -text
|
| 53 |
+
data/vlm_results/frames/PSG_vs_Dortmund_2024-05-01/frame_000.jpg filter=lfs diff=lfs merge=lfs -text
|
| 54 |
+
data/vlm_results/frames/PSG_vs_Dortmund_2024-05-01/frame_001.jpg filter=lfs diff=lfs merge=lfs -text
|
| 55 |
+
data/vlm_results/frames/Real_Madrid_vs_Man_City_2024-04-09/frame_001.jpg filter=lfs diff=lfs merge=lfs -text
|
| 56 |
+
data/vlm_results/frames/Real_Madrid_vs_Man_City_2024-04-09/frame_004.jpg filter=lfs diff=lfs merge=lfs -text
|
| 57 |
+
data/vlm_results/frames/Real_Madrid_vs_RB_Leipzig_2024-03-06/frame_000.jpg filter=lfs diff=lfs merge=lfs -text
|
| 58 |
+
data/vlm_results/frames/Real_Madrid_vs_RB_Leipzig_2024-03-06/frame_002.jpg filter=lfs diff=lfs merge=lfs -text
|
| 59 |
+
data/vlm_results/frames/Real_Sociedad_vs_PSG_2024-03-05/frame_002.jpg filter=lfs diff=lfs merge=lfs -text
|
| 60 |
+
data/vlm_results/frames/Real_Sociedad_vs_PSG_2024-03-05/frame_004.jpg filter=lfs diff=lfs merge=lfs -text
|
README.md
CHANGED
|
@@ -1,15 +1,57 @@
|
|
| 1 |
---
|
| 2 |
title: Offsides Soccer Analytics
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
colorTo: blue
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.14.0
|
| 8 |
-
python_version: '3.
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
| 11 |
license: mit
|
| 12 |
-
short_description:
|
| 13 |
---
|
| 14 |
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
title: Offsides Soccer Analytics
|
| 3 |
+
emoji: ⚽
|
| 4 |
+
colorFrom: green
|
| 5 |
colorTo: blue
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.14.0
|
| 8 |
+
python_version: '3.12'
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
| 11 |
license: mit
|
| 12 |
+
short_description: AI tactical analysis finds edges in UCL prediction markets
|
| 13 |
---
|
| 14 |
|
| 15 |
+
# Offsides — Tactical Edge Detection
|
| 16 |
+
|
| 17 |
+
Multimodal AI analyzes UEFA Champions League footage using **YOLO + Qwen-VL 72B on AMD MI300X** to detect where sports prediction markets are mispriced.
|
| 18 |
+
|
| 19 |
+
## How It Works
|
| 20 |
+
|
| 21 |
+
1. **Extract** — Sample key frames from recent match highlights (both teams, last 3 matches)
|
| 22 |
+
2. **Detect** — YOLO extracts player/ball positions, formation shapes
|
| 23 |
+
3. **Annotate** — OpenCV renders tactical overlays (defensive lines, compactness, team colors)
|
| 24 |
+
4. **Reason** — Qwen-VL 72B reasons over annotated frames + stats + market odds
|
| 25 |
+
5. **Edge** — Identifies where VLM probability diverges from market implied probability
|
| 26 |
+
|
| 27 |
+
## Results
|
| 28 |
+
|
| 29 |
+
Validated on 5 UCL knockout upsets — **3/5 correct edge calls** on outcomes the market got wrong.
|
| 30 |
+
|
| 31 |
+
| Match | VLM Edge | Result |
|
| 32 |
+
|-------|----------|--------|
|
| 33 |
+
| Dortmund vs PSG (SF) | +9pp Home | ✓ Dortmund 1-0 |
|
| 34 |
+
| Dortmund vs Atletico (QF) | +5pp Home | ✓ Dortmund 4-2 |
|
| 35 |
+
| PSG vs Barcelona (QF) | +4pp Home | ✓ PSG 4-1 |
|
| 36 |
+
| Man City vs Real Madrid (QF) | +3pp Home | ✗ Draw (pens) |
|
| 37 |
+
| Atletico vs Inter (R16) | +2pp Draw | ✗ Atletico 2-1 |
|
| 38 |
+
|
| 39 |
+
## Architecture
|
| 40 |
+
|
| 41 |
+
```
|
| 42 |
+
YouTube Highlights → Frame Extraction → YOLO Detection → Annotation (OpenCV)
|
| 43 |
+
↓
|
| 44 |
+
Stats + Market Odds ──────────────────────────→ Qwen-VL 72B (AMD MI300X)
|
| 45 |
+
↓
|
| 46 |
+
Edge Signal + Reasoning
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
## Tech Stack
|
| 50 |
+
|
| 51 |
+
- **GPU:** AMD Instinct MI300X (192GB HBM3) — single GPU fits 72B model
|
| 52 |
+
- **Model:** Qwen/Qwen2.5-VL-72B-Instruct via vLLM on ROCm
|
| 53 |
+
- **Detection:** YOLOv8m + ByteTrack
|
| 54 |
+
- **Annotation:** OpenCV (team colors, defensive lines, compactness ellipses)
|
| 55 |
+
- **Demo:** Gradio (this Space displays pre-computed results)
|
| 56 |
+
|
| 57 |
+
Built for the **AMD Developer Hackathon 2026** (Track 3: Vision & Multimodal AI)
|
app.py
CHANGED
|
@@ -1,7 +1,283 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import gradio as gr
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
-
def greet(name):
|
| 4 |
-
return "Hello " + name + "!!"
|
| 5 |
|
| 6 |
-
|
| 7 |
-
demo.launch()
|
|
|
|
| 1 |
+
"""Offsides — Tactical Edge Detection Demo.
|
| 2 |
+
|
| 3 |
+
Gradio app displaying pre-computed Qwen-VL 72B tactical assessments
|
| 4 |
+
of UEFA Champions League matches on AMD MI300X.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import json
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
|
| 10 |
import gradio as gr
|
| 11 |
+
import plotly.graph_objects as go
|
| 12 |
+
|
| 13 |
+
APP_DIR = Path(__file__).resolve().parent
|
| 14 |
+
RESULTS_PATH = APP_DIR / "data" / "vlm_results" / "results.json"
|
| 15 |
+
DEMO_PATH = APP_DIR / "data" / "demo_matches.json"
|
| 16 |
+
FRAMES_DIR = APP_DIR / "data" / "vlm_results" / "frames"
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def load_results():
|
| 20 |
+
with open(RESULTS_PATH) as f:
|
| 21 |
+
results = json.load(f)
|
| 22 |
+
with open(DEMO_PATH) as f:
|
| 23 |
+
demos = json.load(f)
|
| 24 |
+
demo_lookup = {d["match_id"]: d for d in demos}
|
| 25 |
+
for m in results["matches"]:
|
| 26 |
+
demo = demo_lookup.get(m["match_id"], {})
|
| 27 |
+
m["first_leg"] = demo.get("first_leg", "")
|
| 28 |
+
m["odds"] = demo.get("odds", {})
|
| 29 |
+
m["narrative"] = demo.get("narrative", "")
|
| 30 |
+
return results
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
RESULTS = load_results()
|
| 34 |
+
MATCHES = RESULTS["matches"]
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def result_key(actual_result: str) -> str:
|
| 38 |
+
if actual_result == "home_win":
|
| 39 |
+
return "home"
|
| 40 |
+
if actual_result == "away_win":
|
| 41 |
+
return "away"
|
| 42 |
+
return "draw"
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def get_match_choices():
|
| 46 |
+
choices = []
|
| 47 |
+
for m in MATCHES:
|
| 48 |
+
label = f"{m['home_team']} vs {m['away_team']} — {m['stage']} ({m['date']})"
|
| 49 |
+
choices.append(label)
|
| 50 |
+
return choices
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def get_scorecard():
|
| 54 |
+
correct = 0
|
| 55 |
+
for m in MATCHES:
|
| 56 |
+
edge = m["vlm_assessment"]["edge"]
|
| 57 |
+
actual = result_key(m["actual_result"])
|
| 58 |
+
best = max(edge.items(), key=lambda x: x[1])
|
| 59 |
+
if best[0] == actual:
|
| 60 |
+
correct += 1
|
| 61 |
+
return correct, len(MATCHES)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def make_prob_chart(match):
|
| 65 |
+
market = match["market_odds"]
|
| 66 |
+
vlm = match["vlm_assessment"]["probabilities"]
|
| 67 |
+
|
| 68 |
+
categories = ["Home", "Draw", "Away"]
|
| 69 |
+
market_vals = [market["home"] * 100, market["draw"] * 100, market["away"] * 100]
|
| 70 |
+
vlm_vals = [vlm.get("home", 0) * 100, vlm.get("draw", 0) * 100, vlm.get("away", 0) * 100]
|
| 71 |
+
|
| 72 |
+
fig = go.Figure()
|
| 73 |
+
fig.add_trace(go.Bar(
|
| 74 |
+
name="Market Implied",
|
| 75 |
+
x=categories,
|
| 76 |
+
y=market_vals,
|
| 77 |
+
marker_color="#6366f1",
|
| 78 |
+
text=[f"{v:.0f}%" for v in market_vals],
|
| 79 |
+
textposition="outside",
|
| 80 |
+
))
|
| 81 |
+
fig.add_trace(go.Bar(
|
| 82 |
+
name="VLM Assessment",
|
| 83 |
+
x=categories,
|
| 84 |
+
y=vlm_vals,
|
| 85 |
+
marker_color="#10b981",
|
| 86 |
+
text=[f"{v:.0f}%" for v in vlm_vals],
|
| 87 |
+
textposition="outside",
|
| 88 |
+
))
|
| 89 |
+
fig.update_layout(
|
| 90 |
+
barmode="group",
|
| 91 |
+
title="Probability Comparison: Market vs VLM",
|
| 92 |
+
yaxis_title="Probability (%)",
|
| 93 |
+
yaxis_range=[0, 75],
|
| 94 |
+
template="plotly_dark",
|
| 95 |
+
height=350,
|
| 96 |
+
margin=dict(t=40, b=40),
|
| 97 |
+
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
|
| 98 |
+
)
|
| 99 |
+
return fig
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def get_frame_images(match):
|
| 103 |
+
images = []
|
| 104 |
+
for fp in match.get("frames_used", []):
|
| 105 |
+
parts = Path(fp).parts
|
| 106 |
+
match_dir = parts[2]
|
| 107 |
+
frame_name = parts[-1]
|
| 108 |
+
local_path = FRAMES_DIR / match_dir / frame_name
|
| 109 |
+
if local_path.exists():
|
| 110 |
+
images.append(str(local_path))
|
| 111 |
+
return images
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def format_edge_badge(match):
|
| 115 |
+
edge = match["vlm_assessment"]["edge"]
|
| 116 |
+
actual = result_key(match["actual_result"])
|
| 117 |
+
best = max(edge.items(), key=lambda x: x[1])
|
| 118 |
+
best_outcome, best_val = best
|
| 119 |
+
|
| 120 |
+
correct = best_outcome == actual
|
| 121 |
+
outcome_label = {"home": match["home_team"], "draw": "Draw", "away": match["away_team"]}
|
| 122 |
+
badge = f"**Edge: +{best_val*100:.0f}pp on {outcome_label[best_outcome]}**"
|
| 123 |
+
|
| 124 |
+
if correct:
|
| 125 |
+
return f"### {badge}\n\nActual result: **{match['actual_score']}** ({match['actual_result'].replace('_', ' ')}) — CORRECT"
|
| 126 |
+
else:
|
| 127 |
+
return f"### {badge}\n\nActual result: **{match['actual_score']}** ({match['actual_result'].replace('_', ' ')})"
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def format_reasoning(match):
|
| 131 |
+
a = match["vlm_assessment"]
|
| 132 |
+
lines = []
|
| 133 |
+
lines.append(f"**Confidence:** {a['confidence']}")
|
| 134 |
+
lines.append("")
|
| 135 |
+
lines.append(f"**Reasoning:** {a['reasoning']}")
|
| 136 |
+
lines.append("")
|
| 137 |
+
lines.append("**Visual Evidence:**")
|
| 138 |
+
for ev in a.get("visual_evidence", []):
|
| 139 |
+
lines.append(f"- {ev}")
|
| 140 |
+
lines.append("")
|
| 141 |
+
lines.append(f"**Edge Signal:** {a['edge_signal']}")
|
| 142 |
+
return "\n".join(lines)
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
def format_metrics(match):
|
| 146 |
+
ctx = match.get("metrics_context", {})
|
| 147 |
+
lines = []
|
| 148 |
+
for side, label in [("home", match["home_team"]), ("away", match["away_team"])]:
|
| 149 |
+
data = ctx.get(side, {})
|
| 150 |
+
metrics = data.get("metrics", {})
|
| 151 |
+
if not metrics:
|
| 152 |
+
continue
|
| 153 |
+
lines.append(f"**{label}** (last 3 matches):")
|
| 154 |
+
matches_analyzed = data.get("matches_analyzed", [])
|
| 155 |
+
if matches_analyzed:
|
| 156 |
+
lines.append(f"- Matches: {', '.join(m.replace('_', ' ') for m in matches_analyzed)}")
|
| 157 |
+
if "avg_pressing_speed" in metrics:
|
| 158 |
+
lines.append(f"- Pressing speed: {metrics['avg_pressing_speed']:.4f}")
|
| 159 |
+
if "avg_def_line_movement" in metrics:
|
| 160 |
+
lines.append(f"- Defensive line movement: {metrics['avg_def_line_movement']:.4f}")
|
| 161 |
+
if "avg_compactness_delta" in metrics:
|
| 162 |
+
lines.append(f"- Compactness delta: {metrics['avg_compactness_delta']:.3f}")
|
| 163 |
+
if "avg_transition_speed" in metrics:
|
| 164 |
+
lines.append(f"- Transition speed: {metrics['avg_transition_speed']:.4f}")
|
| 165 |
+
lines.append("")
|
| 166 |
+
return "\n".join(lines)
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
def format_stats(match):
|
| 170 |
+
stats = match.get("stats", {})
|
| 171 |
+
lines = []
|
| 172 |
+
for side in ["home", "away"]:
|
| 173 |
+
s = stats.get(side, {})
|
| 174 |
+
if not s:
|
| 175 |
+
continue
|
| 176 |
+
lines.append(f"**{s.get('team', side.title())}:**")
|
| 177 |
+
lines.append(f"| Metric | Value |")
|
| 178 |
+
lines.append(f"|--------|-------|")
|
| 179 |
+
lines.append(f"| xG/match | {s.get('xg_last5', '-')} |")
|
| 180 |
+
lines.append(f"| xGA/match | {s.get('xga_last5', '-')} |")
|
| 181 |
+
lines.append(f"| PPDA | {s.get('ppda', '-')} |")
|
| 182 |
+
lines.append(f"| Possession | {s.get('possession_pct', '-')}% |")
|
| 183 |
+
lines.append(f"| Form | {s.get('form', '-')} |")
|
| 184 |
+
lines.append(f"| Goals (last 5) | {s.get('goals_scored_last5', '-')}F / {s.get('goals_conceded_last5', '-')}A |")
|
| 185 |
+
lines.append("")
|
| 186 |
+
return "\n".join(lines)
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
def format_match_info(match):
|
| 190 |
+
lines = []
|
| 191 |
+
lines.append(f"**{match['home_team']}** vs **{match['away_team']}**")
|
| 192 |
+
lines.append(f"- Stage: {match['stage']}")
|
| 193 |
+
lines.append(f"- Date: {match['date']}")
|
| 194 |
+
if match.get("first_leg"):
|
| 195 |
+
lines.append(f"- First leg: {match['first_leg']}")
|
| 196 |
+
odds = match.get("odds", {})
|
| 197 |
+
if odds:
|
| 198 |
+
lines.append(f"- Decimal odds: {match['home_team']} {odds.get('home', '-')} / Draw {odds.get('draw', '-')} / {match['away_team']} {odds.get('away', '-')}")
|
| 199 |
+
market = match["market_odds"]
|
| 200 |
+
lines.append(f"- Implied probability: {match['home_team']} {market['home']*100:.0f}% / Draw {market['draw']*100:.0f}% / {match['away_team']} {market['away']*100:.0f}%")
|
| 201 |
+
if match.get("narrative"):
|
| 202 |
+
lines.append(f"\n*{match['narrative']}*")
|
| 203 |
+
return "\n".join(lines)
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
def on_match_select(choice):
|
| 207 |
+
idx = get_match_choices().index(choice)
|
| 208 |
+
match = MATCHES[idx]
|
| 209 |
+
|
| 210 |
+
chart = make_prob_chart(match)
|
| 211 |
+
frames = get_frame_images(match)
|
| 212 |
+
edge_text = format_edge_badge(match)
|
| 213 |
+
reasoning_text = format_reasoning(match)
|
| 214 |
+
metrics_text = format_metrics(match)
|
| 215 |
+
stats_text = format_stats(match)
|
| 216 |
+
info_text = format_match_info(match)
|
| 217 |
+
|
| 218 |
+
return chart, frames, edge_text, reasoning_text, metrics_text, stats_text, info_text
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
correct, total = get_scorecard()
|
| 222 |
+
|
| 223 |
+
with gr.Blocks(title="Offsides — Tactical Edge Detection") as demo:
|
| 224 |
+
gr.Markdown(f"""
|
| 225 |
+
# Offsides — Tactical Edge Detection
|
| 226 |
+
|
| 227 |
+
**Where the market gets it wrong.** Multimodal AI analyzes UEFA Champions League footage using YOLO + Qwen-VL 72B on AMD MI300X to detect mispriced prediction markets.
|
| 228 |
+
|
| 229 |
+
**Scorecard: {correct}/{total} correct edge calls** | Model: {RESULTS['model']} | Generated: {RESULTS['generated_at'][:10]}
|
| 230 |
+
""")
|
| 231 |
+
|
| 232 |
+
with gr.Row():
|
| 233 |
+
match_dropdown = gr.Dropdown(
|
| 234 |
+
choices=get_match_choices(),
|
| 235 |
+
value=get_match_choices()[0],
|
| 236 |
+
label="Select Match",
|
| 237 |
+
interactive=True,
|
| 238 |
+
)
|
| 239 |
+
|
| 240 |
+
with gr.Row():
|
| 241 |
+
with gr.Column(scale=1):
|
| 242 |
+
prob_chart = gr.Plot(label="Probability Comparison")
|
| 243 |
+
edge_badge = gr.Markdown()
|
| 244 |
+
reasoning_box = gr.Markdown(label="VLM Assessment")
|
| 245 |
+
|
| 246 |
+
with gr.Column(scale=1):
|
| 247 |
+
frame_gallery = gr.Gallery(
|
| 248 |
+
label="Annotated Frames (analyzed by VLM)",
|
| 249 |
+
columns=2,
|
| 250 |
+
height=400,
|
| 251 |
+
)
|
| 252 |
+
with gr.Accordion("Tactical Metrics", open=False):
|
| 253 |
+
metrics_box = gr.Markdown()
|
| 254 |
+
with gr.Accordion("Match Statistics", open=False):
|
| 255 |
+
stats_box = gr.Markdown()
|
| 256 |
+
|
| 257 |
+
with gr.Row():
|
| 258 |
+
info_box = gr.Markdown()
|
| 259 |
+
|
| 260 |
+
match_dropdown.change(
|
| 261 |
+
fn=on_match_select,
|
| 262 |
+
inputs=[match_dropdown],
|
| 263 |
+
outputs=[prob_chart, frame_gallery, edge_badge, reasoning_box, metrics_box, stats_box, info_box],
|
| 264 |
+
)
|
| 265 |
+
|
| 266 |
+
demo.load(
|
| 267 |
+
fn=on_match_select,
|
| 268 |
+
inputs=[match_dropdown],
|
| 269 |
+
outputs=[prob_chart, frame_gallery, edge_badge, reasoning_box, metrics_box, stats_box, info_box],
|
| 270 |
+
)
|
| 271 |
+
|
| 272 |
+
gr.Markdown("""
|
| 273 |
+
---
|
| 274 |
+
**Architecture:** YouTube highlights → Frame extraction → YOLO detection → Annotation (OpenCV) → Qwen-VL 72B reasoning (AMD MI300X via vLLM on ROCm)
|
| 275 |
+
|
| 276 |
+
**How it works:** For each upcoming match, the system analyzes the most recent 3 matches for both teams. YOLO detects player positions and ball location. OpenCV renders tactical overlays (defensive lines, compactness ellipses, team colors). Qwen-VL reasons over these annotated frames alongside stats and market odds to identify where the market may be mispriced.
|
| 277 |
+
|
| 278 |
+
Built for the AMD Developer Hackathon 2026 (Track 3: Vision & Multimodal AI)
|
| 279 |
+
""")
|
| 280 |
|
|
|
|
|
|
|
| 281 |
|
| 282 |
+
if __name__ == "__main__":
|
| 283 |
+
demo.launch()
|
data/demo_matches.json
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"match_id": "Dortmund_vs_PSG_2024-05-07",
|
| 4 |
+
"home_team": "Dortmund",
|
| 5 |
+
"away_team": "PSG",
|
| 6 |
+
"date": "2024-05-07",
|
| 7 |
+
"stage": "Semi-final 2nd leg",
|
| 8 |
+
"first_leg": "PSG 0-1 Dortmund",
|
| 9 |
+
"actual_score": "1-0",
|
| 10 |
+
"actual_result": "home_win",
|
| 11 |
+
"odds": {"home": 4.33, "draw": 4.00, "away": 1.80},
|
| 12 |
+
"implied_prob": {"home": 0.23, "draw": 0.25, "away": 0.56},
|
| 13 |
+
"stats": {
|
| 14 |
+
"home": {
|
| 15 |
+
"team": "Dortmund",
|
| 16 |
+
"xg_last5": 1.52,
|
| 17 |
+
"xga_last5": 1.28,
|
| 18 |
+
"ppda": 10.2,
|
| 19 |
+
"possession_pct": 46,
|
| 20 |
+
"form": "WWLWW",
|
| 21 |
+
"goals_scored_last5": 9,
|
| 22 |
+
"goals_conceded_last5": 5
|
| 23 |
+
},
|
| 24 |
+
"away": {
|
| 25 |
+
"team": "PSG",
|
| 26 |
+
"xg_last5": 2.14,
|
| 27 |
+
"xga_last5": 0.72,
|
| 28 |
+
"ppda": 9.6,
|
| 29 |
+
"possession_pct": 58,
|
| 30 |
+
"form": "WWWWL",
|
| 31 |
+
"goals_scored_last5": 12,
|
| 32 |
+
"goals_conceded_last5": 3
|
| 33 |
+
}
|
| 34 |
+
},
|
| 35 |
+
"narrative": "PSG heavily favored to overturn 1st-leg deficit at home but Dortmund's compact defensive shape and rapid transitions from their recent knockout run suggested resilience the market underpriced."
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"match_id": "Dortmund_vs_Atletico_Madrid_2024-04-16",
|
| 39 |
+
"home_team": "Dortmund",
|
| 40 |
+
"away_team": "Atletico Madrid",
|
| 41 |
+
"date": "2024-04-16",
|
| 42 |
+
"stage": "Quarter-final 2nd leg",
|
| 43 |
+
"first_leg": "Atletico Madrid 2-1 Dortmund",
|
| 44 |
+
"actual_score": "4-2",
|
| 45 |
+
"actual_result": "home_win",
|
| 46 |
+
"odds": {"home": 2.50, "draw": 3.60, "away": 2.75},
|
| 47 |
+
"implied_prob": {"home": 0.40, "draw": 0.28, "away": 0.36},
|
| 48 |
+
"stats": {
|
| 49 |
+
"home": {
|
| 50 |
+
"team": "Dortmund",
|
| 51 |
+
"xg_last5": 1.68,
|
| 52 |
+
"xga_last5": 1.34,
|
| 53 |
+
"ppda": 9.8,
|
| 54 |
+
"possession_pct": 48,
|
| 55 |
+
"form": "WLWDW",
|
| 56 |
+
"goals_scored_last5": 10,
|
| 57 |
+
"goals_conceded_last5": 6
|
| 58 |
+
},
|
| 59 |
+
"away": {
|
| 60 |
+
"team": "Atletico Madrid",
|
| 61 |
+
"xg_last5": 1.44,
|
| 62 |
+
"xga_last5": 0.96,
|
| 63 |
+
"ppda": 12.8,
|
| 64 |
+
"possession_pct": 52,
|
| 65 |
+
"form": "DWWWW",
|
| 66 |
+
"goals_scored_last5": 7,
|
| 67 |
+
"goals_conceded_last5": 4
|
| 68 |
+
}
|
| 69 |
+
},
|
| 70 |
+
"narrative": "Atletico held 1st-leg advantage and were favored on aggregate. Dortmund's explosive transition speed and Signal Iduna Park atmosphere fueled a 4-2 comeback the market didn't fully price in."
|
| 71 |
+
},
|
| 72 |
+
{
|
| 73 |
+
"match_id": "PSG_vs_Barcelona_2024-04-16",
|
| 74 |
+
"home_team": "PSG",
|
| 75 |
+
"away_team": "Barcelona",
|
| 76 |
+
"date": "2024-04-16",
|
| 77 |
+
"stage": "Quarter-final 2nd leg",
|
| 78 |
+
"first_leg": "Barcelona 3-2 PSG",
|
| 79 |
+
"actual_score": "4-1",
|
| 80 |
+
"actual_result": "home_win",
|
| 81 |
+
"odds": {"home": 2.10, "draw": 3.80, "away": 3.40},
|
| 82 |
+
"implied_prob": {"home": 0.48, "draw": 0.26, "away": 0.29},
|
| 83 |
+
"stats": {
|
| 84 |
+
"home": {
|
| 85 |
+
"team": "PSG",
|
| 86 |
+
"xg_last5": 2.06,
|
| 87 |
+
"xga_last5": 0.88,
|
| 88 |
+
"ppda": 9.4,
|
| 89 |
+
"possession_pct": 56,
|
| 90 |
+
"form": "WWWDW",
|
| 91 |
+
"goals_scored_last5": 11,
|
| 92 |
+
"goals_conceded_last5": 5
|
| 93 |
+
},
|
| 94 |
+
"away": {
|
| 95 |
+
"team": "Barcelona",
|
| 96 |
+
"xg_last5": 1.82,
|
| 97 |
+
"xga_last5": 1.22,
|
| 98 |
+
"ppda": 10.8,
|
| 99 |
+
"possession_pct": 60,
|
| 100 |
+
"form": "WWLWW",
|
| 101 |
+
"goals_scored_last5": 9,
|
| 102 |
+
"goals_conceded_last5": 6
|
| 103 |
+
}
|
| 104 |
+
},
|
| 105 |
+
"narrative": "Barcelona had 1st-leg advantage and high possession but PSG's aggressive pressing intensity and Dembele's pace on transitions created a 4-1 demolition the aggregate market didn't reflect."
|
| 106 |
+
},
|
| 107 |
+
{
|
| 108 |
+
"match_id": "Man_City_vs_Real_Madrid_2024-04-17",
|
| 109 |
+
"home_team": "Man City",
|
| 110 |
+
"away_team": "Real Madrid",
|
| 111 |
+
"date": "2024-04-17",
|
| 112 |
+
"stage": "Quarter-final 2nd leg",
|
| 113 |
+
"first_leg": "Real Madrid 3-3 Man City",
|
| 114 |
+
"actual_score": "1-1 (Real Madrid won on penalties)",
|
| 115 |
+
"actual_result": "draw",
|
| 116 |
+
"odds": {"home": 1.83, "draw": 4.00, "away": 4.33},
|
| 117 |
+
"implied_prob": {"home": 0.55, "draw": 0.25, "away": 0.23},
|
| 118 |
+
"stats": {
|
| 119 |
+
"home": {
|
| 120 |
+
"team": "Man City",
|
| 121 |
+
"xg_last5": 2.24,
|
| 122 |
+
"xga_last5": 0.86,
|
| 123 |
+
"ppda": 8.2,
|
| 124 |
+
"possession_pct": 64,
|
| 125 |
+
"form": "WWWWW",
|
| 126 |
+
"goals_scored_last5": 13,
|
| 127 |
+
"goals_conceded_last5": 4
|
| 128 |
+
},
|
| 129 |
+
"away": {
|
| 130 |
+
"team": "Real Madrid",
|
| 131 |
+
"xg_last5": 1.76,
|
| 132 |
+
"xga_last5": 1.08,
|
| 133 |
+
"ppda": 11.6,
|
| 134 |
+
"possession_pct": 52,
|
| 135 |
+
"form": "WDWWW",
|
| 136 |
+
"goals_scored_last5": 10,
|
| 137 |
+
"goals_conceded_last5": 5
|
| 138 |
+
}
|
| 139 |
+
},
|
| 140 |
+
"narrative": "Man City dominant favorites at home but Real Madrid's low-block + lethal transitions and penalty-shootout composure saw them through. City's high line was vulnerable to counter-attacks the market discounted."
|
| 141 |
+
},
|
| 142 |
+
{
|
| 143 |
+
"match_id": "Atletico_Madrid_vs_Inter_Milan_2024-03-13",
|
| 144 |
+
"home_team": "Atletico Madrid",
|
| 145 |
+
"away_team": "Inter Milan",
|
| 146 |
+
"date": "2024-03-13",
|
| 147 |
+
"stage": "Round of 16 2nd leg",
|
| 148 |
+
"first_leg": "Inter Milan 1-0 Atletico Madrid",
|
| 149 |
+
"actual_score": "2-1",
|
| 150 |
+
"actual_result": "home_win",
|
| 151 |
+
"odds": {"home": 2.20, "draw": 3.30, "away": 3.40},
|
| 152 |
+
"implied_prob": {"home": 0.45, "draw": 0.30, "away": 0.29},
|
| 153 |
+
"stats": {
|
| 154 |
+
"home": {
|
| 155 |
+
"team": "Atletico Madrid",
|
| 156 |
+
"xg_last5": 1.56,
|
| 157 |
+
"xga_last5": 0.92,
|
| 158 |
+
"ppda": 11.4,
|
| 159 |
+
"possession_pct": 50,
|
| 160 |
+
"form": "WWDWL",
|
| 161 |
+
"goals_scored_last5": 8,
|
| 162 |
+
"goals_conceded_last5": 5
|
| 163 |
+
},
|
| 164 |
+
"away": {
|
| 165 |
+
"team": "Inter Milan",
|
| 166 |
+
"xg_last5": 1.88,
|
| 167 |
+
"xga_last5": 0.78,
|
| 168 |
+
"ppda": 10.2,
|
| 169 |
+
"possession_pct": 54,
|
| 170 |
+
"form": "WWWWD",
|
| 171 |
+
"goals_scored_last5": 10,
|
| 172 |
+
"goals_conceded_last5": 3
|
| 173 |
+
}
|
| 174 |
+
},
|
| 175 |
+
"narrative": "Inter led on aggregate and had the best defensive record in Serie A. Atletico's high-energy pressing at home disrupted Inter's build-up play, creating chaos the market underestimated."
|
| 176 |
+
}
|
| 177 |
+
]
|
data/vlm_results/frames/Atletico_Madrid_vs_Dortmund_2024-04-10/frame_000.jpg
ADDED
|
Git LFS Details
|
data/vlm_results/frames/Atletico_Madrid_vs_Dortmund_2024-04-10/frame_003.jpg
ADDED
|
Git LFS Details
|
data/vlm_results/frames/Atletico_Madrid_vs_Inter_Milan_2024-03-13/frame_001.jpg
ADDED
|
Git LFS Details
|
data/vlm_results/frames/Atletico_Madrid_vs_Inter_Milan_2024-03-13/frame_005.jpg
ADDED
|
Git LFS Details
|
data/vlm_results/frames/Atletico_Madrid_vs_Lazio_2023-12-13/frame_001.jpg
ADDED
|
Git LFS Details
|
data/vlm_results/frames/Atletico_Madrid_vs_Lazio_2023-12-13/frame_005.jpg
ADDED
|
Git LFS Details
|
data/vlm_results/frames/Barcelona_vs_Napoli_2024-03-12/frame_000.jpg
ADDED
|
Git LFS Details
|
data/vlm_results/frames/Barcelona_vs_Napoli_2024-03-12/frame_006.jpg
ADDED
|
Git LFS Details
|
data/vlm_results/frames/Barcelona_vs_PSG_2024-04-10/frame_002.jpg
ADDED
|
data/vlm_results/frames/Barcelona_vs_PSG_2024-04-10/frame_005.jpg
ADDED
|
data/vlm_results/frames/Dortmund_vs_Atletico_Madrid_2024-04-16/frame_002.jpg
ADDED
|
Git LFS Details
|
data/vlm_results/frames/Dortmund_vs_Atletico_Madrid_2024-04-16/frame_006.jpg
ADDED
|
Git LFS Details
|
data/vlm_results/frames/Dortmund_vs_PSV_2024-03-13/frame_003.jpg
ADDED
|
Git LFS Details
|
data/vlm_results/frames/Dortmund_vs_PSV_2024-03-13/frame_005.jpg
ADDED
|
Git LFS Details
|
data/vlm_results/frames/Inter_Milan_vs_Atletico_Madrid_2024-02-20/frame_002.jpg
ADDED
|
data/vlm_results/frames/Inter_Milan_vs_Atletico_Madrid_2024-02-20/frame_003.jpg
ADDED
|
Git LFS Details
|
data/vlm_results/frames/Inter_Milan_vs_Real_Sociedad_2023-12-12/frame_000.jpg
ADDED
|
data/vlm_results/frames/Inter_Milan_vs_Real_Sociedad_2023-12-12/frame_001.jpg
ADDED
|
Git LFS Details
|
data/vlm_results/frames/Man_City_vs_FC_Copenhagen_2024-03-06/frame_001.jpg
ADDED
|
data/vlm_results/frames/Man_City_vs_FC_Copenhagen_2024-03-06/frame_003.jpg
ADDED
|
Git LFS Details
|
data/vlm_results/frames/PSG_vs_Barcelona_2024-04-16/frame_002.jpg
ADDED
|
Git LFS Details
|
data/vlm_results/frames/PSG_vs_Barcelona_2024-04-16/frame_005.jpg
ADDED
|
Git LFS Details
|
data/vlm_results/frames/PSG_vs_Dortmund_2024-05-01/frame_000.jpg
ADDED
|
Git LFS Details
|
data/vlm_results/frames/PSG_vs_Dortmund_2024-05-01/frame_001.jpg
ADDED
|
Git LFS Details
|
data/vlm_results/frames/Real_Madrid_vs_Man_City_2024-04-09/frame_001.jpg
ADDED
|
Git LFS Details
|
data/vlm_results/frames/Real_Madrid_vs_Man_City_2024-04-09/frame_004.jpg
ADDED
|
Git LFS Details
|
data/vlm_results/frames/Real_Madrid_vs_RB_Leipzig_2024-03-06/frame_000.jpg
ADDED
|
Git LFS Details
|
data/vlm_results/frames/Real_Madrid_vs_RB_Leipzig_2024-03-06/frame_002.jpg
ADDED
|
Git LFS Details
|
data/vlm_results/frames/Real_Sociedad_vs_PSG_2024-03-05/frame_002.jpg
ADDED
|
Git LFS Details
|
data/vlm_results/frames/Real_Sociedad_vs_PSG_2024-03-05/frame_004.jpg
ADDED
|
Git LFS Details
|
data/vlm_results/results.json
ADDED
|
@@ -0,0 +1,481 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"generated_at": "2026-05-07T21:03:43.559765",
|
| 3 |
+
"model": "Qwen/Qwen2.5-VL-72B-Instruct",
|
| 4 |
+
"matches": [
|
| 5 |
+
{
|
| 6 |
+
"match_id": "Dortmund_vs_PSG_2024-05-07",
|
| 7 |
+
"home_team": "Dortmund",
|
| 8 |
+
"away_team": "PSG",
|
| 9 |
+
"date": "2024-05-07",
|
| 10 |
+
"stage": "Semi-final 2nd leg",
|
| 11 |
+
"market_odds": {
|
| 12 |
+
"home": 0.23,
|
| 13 |
+
"draw": 0.25,
|
| 14 |
+
"away": 0.56
|
| 15 |
+
},
|
| 16 |
+
"actual_result": "home_win",
|
| 17 |
+
"actual_score": "1-0",
|
| 18 |
+
"vlm_assessment": {
|
| 19 |
+
"probabilities": {
|
| 20 |
+
"home": 0.32,
|
| 21 |
+
"draw": 0.28,
|
| 22 |
+
"away": 0.4
|
| 23 |
+
},
|
| 24 |
+
"edge": {
|
| 25 |
+
"home": 0.09,
|
| 26 |
+
"draw": 0.03,
|
| 27 |
+
"away": -0.16
|
| 28 |
+
},
|
| 29 |
+
"reasoning": "Dortmund's aggressive pressing and high defensive line, as seen in Frame 3, combined with their compactness, suggest they can create chances against PSG. However, PSG\u2019s solid defensive structure and transition speed indicate they could exploit counter-attacks effectively.",
|
| 30 |
+
"visual_evidence": [
|
| 31 |
+
"In Frame 3, Dortmund's defensive line is very high up the pitch, indicating an aggressive approach which could lead to scoring opportunities but also leaves them vulnerable on the counter.",
|
| 32 |
+
"Frames 5 and 6 show PSG maintaining a compact defensive shape, limiting space for Barcelona to attack, suggesting they can neutralize Dortmund's offensive play.",
|
| 33 |
+
"The compactness delta for both teams indicates that while Dortmund presses aggressively, PSG remains organized defensively."
|
| 34 |
+
],
|
| 35 |
+
"confidence": "medium",
|
| 36 |
+
"edge_signal": "The market underprices a Dortmund win due to their recent tactical success in pressing and creating chances against strong opponents. A 23% implied probability seems low given their form and tactical setup."
|
| 37 |
+
},
|
| 38 |
+
"frames_used": [
|
| 39 |
+
"data/frames/PSG_vs_Dortmund_2024-05-01/annotated/frame_001.jpg",
|
| 40 |
+
"data/frames/PSG_vs_Dortmund_2024-05-01/annotated/frame_000.jpg",
|
| 41 |
+
"data/frames/Dortmund_vs_Atletico_Madrid_2024-04-16/annotated/frame_002.jpg",
|
| 42 |
+
"data/frames/Dortmund_vs_Atletico_Madrid_2024-04-16/annotated/frame_006.jpg",
|
| 43 |
+
"data/frames/PSG_vs_Barcelona_2024-04-16/annotated/frame_002.jpg",
|
| 44 |
+
"data/frames/PSG_vs_Barcelona_2024-04-16/annotated/frame_005.jpg"
|
| 45 |
+
],
|
| 46 |
+
"metrics_context": {
|
| 47 |
+
"home": {
|
| 48 |
+
"team": "Dortmund",
|
| 49 |
+
"matches_analyzed": [
|
| 50 |
+
"PSG_vs_Dortmund_2024-05-01",
|
| 51 |
+
"Dortmund_vs_Atletico_Madrid_2024-04-16",
|
| 52 |
+
"Atletico_Madrid_vs_Dortmund_2024-04-10"
|
| 53 |
+
],
|
| 54 |
+
"metrics": {
|
| 55 |
+
"avg_pressing_speed": 0.0037,
|
| 56 |
+
"avg_def_line_movement": -0.0103,
|
| 57 |
+
"avg_compactness_delta": -0.2123,
|
| 58 |
+
"avg_transition_speed": 0.152
|
| 59 |
+
}
|
| 60 |
+
},
|
| 61 |
+
"away": {
|
| 62 |
+
"team": "PSG",
|
| 63 |
+
"matches_analyzed": [
|
| 64 |
+
"PSG_vs_Dortmund_2024-05-01",
|
| 65 |
+
"PSG_vs_Barcelona_2024-04-16",
|
| 66 |
+
"Barcelona_vs_PSG_2024-04-10"
|
| 67 |
+
],
|
| 68 |
+
"metrics": {
|
| 69 |
+
"avg_pressing_speed": 0.003,
|
| 70 |
+
"avg_def_line_movement": -0.035,
|
| 71 |
+
"avg_compactness_delta": -0.047,
|
| 72 |
+
"avg_transition_speed": 0.141
|
| 73 |
+
}
|
| 74 |
+
}
|
| 75 |
+
},
|
| 76 |
+
"stats": {
|
| 77 |
+
"home": {
|
| 78 |
+
"team": "Dortmund",
|
| 79 |
+
"xg_last5": 1.52,
|
| 80 |
+
"xga_last5": 1.28,
|
| 81 |
+
"ppda": 10.2,
|
| 82 |
+
"possession_pct": 46,
|
| 83 |
+
"form": "WWLWW",
|
| 84 |
+
"goals_scored_last5": 9,
|
| 85 |
+
"goals_conceded_last5": 5
|
| 86 |
+
},
|
| 87 |
+
"away": {
|
| 88 |
+
"team": "PSG",
|
| 89 |
+
"xg_last5": 2.14,
|
| 90 |
+
"xga_last5": 0.72,
|
| 91 |
+
"ppda": 9.6,
|
| 92 |
+
"possession_pct": 58,
|
| 93 |
+
"form": "WWWWL",
|
| 94 |
+
"goals_scored_last5": 12,
|
| 95 |
+
"goals_conceded_last5": 3
|
| 96 |
+
}
|
| 97 |
+
},
|
| 98 |
+
"inference_time_s": 18.504204750061035
|
| 99 |
+
},
|
| 100 |
+
{
|
| 101 |
+
"match_id": "Dortmund_vs_Atletico_Madrid_2024-04-16",
|
| 102 |
+
"home_team": "Dortmund",
|
| 103 |
+
"away_team": "Atletico Madrid",
|
| 104 |
+
"date": "2024-04-16",
|
| 105 |
+
"stage": "Quarter-final 2nd leg",
|
| 106 |
+
"market_odds": {
|
| 107 |
+
"home": 0.4,
|
| 108 |
+
"draw": 0.28,
|
| 109 |
+
"away": 0.36
|
| 110 |
+
},
|
| 111 |
+
"actual_result": "home_win",
|
| 112 |
+
"actual_score": "4-2",
|
| 113 |
+
"vlm_assessment": {
|
| 114 |
+
"probabilities": {
|
| 115 |
+
"home": 0.45,
|
| 116 |
+
"draw": 0.25,
|
| 117 |
+
"away": 0.3
|
| 118 |
+
},
|
| 119 |
+
"edge": {
|
| 120 |
+
"home": 0.05,
|
| 121 |
+
"draw": -0.03,
|
| 122 |
+
"away": -0.06
|
| 123 |
+
},
|
| 124 |
+
"reasoning": "Dortmund has shown tactical flexibility and offensive prowess in recent matches, while Atletico Madrid's defensive solidity might be slightly overstated given their lower transition speed. The home advantage and current form suggest a slight edge for Dortmund.",
|
| 125 |
+
"visual_evidence": [
|
| 126 |
+
"In Frame 3, Dortmund's compactness ellipse shows a well-organized structure around the ball, indicating strong control and potential attacking opportunities.",
|
| 127 |
+
"Frames 5 and 6 show Atletico Madrid's vulnerability when facing a compact and aggressive opponent like Inter Milan, hinting at possible weaknesses against Dortmund's similar approach.",
|
| 128 |
+
"The defensive line of Dortmund is consistently positioned higher up the pitch, as seen in Frame 4, suggesting an aggressive pressing game which could disrupt Atletico Madrid's build-up play."
|
| 129 |
+
],
|
| 130 |
+
"confidence": "medium",
|
| 131 |
+
"edge_signal": "The market slightly underprices a Dortmund win due to their tactical advantages and recent form. The higher implied probability for an Atletico Madrid win seems inflated compared to their tactical metrics."
|
| 132 |
+
},
|
| 133 |
+
"frames_used": [
|
| 134 |
+
"data/frames/Atletico_Madrid_vs_Dortmund_2024-04-10/annotated/frame_003.jpg",
|
| 135 |
+
"data/frames/Atletico_Madrid_vs_Dortmund_2024-04-10/annotated/frame_000.jpg",
|
| 136 |
+
"data/frames/Dortmund_vs_PSV_2024-03-13/annotated/frame_003.jpg",
|
| 137 |
+
"data/frames/Dortmund_vs_PSV_2024-03-13/annotated/frame_005.jpg",
|
| 138 |
+
"data/frames/Atletico_Madrid_vs_Inter_Milan_2024-03-13/annotated/frame_001.jpg",
|
| 139 |
+
"data/frames/Atletico_Madrid_vs_Inter_Milan_2024-03-13/annotated/frame_005.jpg"
|
| 140 |
+
],
|
| 141 |
+
"metrics_context": {
|
| 142 |
+
"home": {
|
| 143 |
+
"team": "Dortmund",
|
| 144 |
+
"matches_analyzed": [
|
| 145 |
+
"Atletico_Madrid_vs_Dortmund_2024-04-10",
|
| 146 |
+
"Dortmund_vs_PSV_2024-03-13",
|
| 147 |
+
"PSV_vs_Dortmund_2024-02-20"
|
| 148 |
+
],
|
| 149 |
+
"metrics": {
|
| 150 |
+
"avg_pressing_speed": 0.0033,
|
| 151 |
+
"avg_def_line_movement": -0.0267,
|
| 152 |
+
"avg_compactness_delta": -0.168,
|
| 153 |
+
"avg_transition_speed": 0.1557
|
| 154 |
+
}
|
| 155 |
+
},
|
| 156 |
+
"away": {
|
| 157 |
+
"team": "Atletico Madrid",
|
| 158 |
+
"matches_analyzed": [
|
| 159 |
+
"Atletico_Madrid_vs_Dortmund_2024-04-10",
|
| 160 |
+
"Atletico_Madrid_vs_Inter_Milan_2024-03-13",
|
| 161 |
+
"Inter_Milan_vs_Atletico_Madrid_2024-02-20"
|
| 162 |
+
],
|
| 163 |
+
"metrics": {
|
| 164 |
+
"avg_pressing_speed": 0.0043,
|
| 165 |
+
"avg_def_line_movement": -0.0163,
|
| 166 |
+
"avg_compactness_delta": -0.3237,
|
| 167 |
+
"avg_transition_speed": 0.1473
|
| 168 |
+
}
|
| 169 |
+
}
|
| 170 |
+
},
|
| 171 |
+
"stats": {
|
| 172 |
+
"home": {
|
| 173 |
+
"team": "Dortmund",
|
| 174 |
+
"xg_last5": 1.68,
|
| 175 |
+
"xga_last5": 1.34,
|
| 176 |
+
"ppda": 9.8,
|
| 177 |
+
"possession_pct": 48,
|
| 178 |
+
"form": "WLWDW",
|
| 179 |
+
"goals_scored_last5": 10,
|
| 180 |
+
"goals_conceded_last5": 6
|
| 181 |
+
},
|
| 182 |
+
"away": {
|
| 183 |
+
"team": "Atletico Madrid",
|
| 184 |
+
"xg_last5": 1.44,
|
| 185 |
+
"xga_last5": 0.96,
|
| 186 |
+
"ppda": 12.8,
|
| 187 |
+
"possession_pct": 52,
|
| 188 |
+
"form": "DWWWW",
|
| 189 |
+
"goals_scored_last5": 7,
|
| 190 |
+
"goals_conceded_last5": 4
|
| 191 |
+
}
|
| 192 |
+
},
|
| 193 |
+
"inference_time_s": 12.080515146255493
|
| 194 |
+
},
|
| 195 |
+
{
|
| 196 |
+
"match_id": "PSG_vs_Barcelona_2024-04-16",
|
| 197 |
+
"home_team": "PSG",
|
| 198 |
+
"away_team": "Barcelona",
|
| 199 |
+
"date": "2024-04-16",
|
| 200 |
+
"stage": "Quarter-final 2nd leg",
|
| 201 |
+
"market_odds": {
|
| 202 |
+
"home": 0.48,
|
| 203 |
+
"draw": 0.26,
|
| 204 |
+
"away": 0.29
|
| 205 |
+
},
|
| 206 |
+
"actual_result": "home_win",
|
| 207 |
+
"actual_score": "4-1",
|
| 208 |
+
"vlm_assessment": {
|
| 209 |
+
"probabilities": {
|
| 210 |
+
"home": 0.52,
|
| 211 |
+
"draw": 0.24,
|
| 212 |
+
"away": 0.24
|
| 213 |
+
},
|
| 214 |
+
"edge": {
|
| 215 |
+
"home": 0.04,
|
| 216 |
+
"draw": -0.02,
|
| 217 |
+
"away": -0.05
|
| 218 |
+
},
|
| 219 |
+
"reasoning": "PSG's defensive solidity and transition speed suggest they can maintain their lead. Barcelona's pressing struggles indicate difficulty in breaking down PSG.",
|
| 220 |
+
"visual_evidence": [
|
| 221 |
+
"In Frame 1, PSG's defensive organization limits Barcelona's penetration despite trailing.",
|
| 222 |
+
"Frame 3 shows PSG maintaining a high defensive line and compact shape against Real Sociedad, indicating strong defensive discipline.",
|
| 223 |
+
"Barcelona's attacking structure in Frame 5 appears disjointed, suggesting potential difficulties in creating clear chances."
|
| 224 |
+
],
|
| 225 |
+
"confidence": "medium",
|
| 226 |
+
"edge_signal": "The market slightly underprices PSG's win due to their defensive resilience and transition capabilities, which are not fully reflected in the current odds."
|
| 227 |
+
},
|
| 228 |
+
"frames_used": [
|
| 229 |
+
"data/frames/Barcelona_vs_PSG_2024-04-10/annotated/frame_005.jpg",
|
| 230 |
+
"data/frames/Barcelona_vs_PSG_2024-04-10/annotated/frame_002.jpg",
|
| 231 |
+
"data/frames/Real_Sociedad_vs_PSG_2024-03-05/annotated/frame_002.jpg",
|
| 232 |
+
"data/frames/Real_Sociedad_vs_PSG_2024-03-05/annotated/frame_004.jpg",
|
| 233 |
+
"data/frames/Barcelona_vs_Napoli_2024-03-12/annotated/frame_006.jpg",
|
| 234 |
+
"data/frames/Barcelona_vs_Napoli_2024-03-12/annotated/frame_000.jpg"
|
| 235 |
+
],
|
| 236 |
+
"metrics_context": {
|
| 237 |
+
"home": {
|
| 238 |
+
"team": "PSG",
|
| 239 |
+
"matches_analyzed": [
|
| 240 |
+
"Barcelona_vs_PSG_2024-04-10",
|
| 241 |
+
"Real_Sociedad_vs_PSG_2024-03-05",
|
| 242 |
+
"PSG_vs_Real_Sociedad_2024-02-14"
|
| 243 |
+
],
|
| 244 |
+
"metrics": {
|
| 245 |
+
"avg_pressing_speed": 0.0033,
|
| 246 |
+
"avg_def_line_movement": -0.0397,
|
| 247 |
+
"avg_compactness_delta": -0.0023,
|
| 248 |
+
"avg_transition_speed": 0.1257
|
| 249 |
+
}
|
| 250 |
+
},
|
| 251 |
+
"away": {
|
| 252 |
+
"team": "Barcelona",
|
| 253 |
+
"matches_analyzed": [
|
| 254 |
+
"Barcelona_vs_PSG_2024-04-10",
|
| 255 |
+
"Barcelona_vs_Napoli_2024-03-12",
|
| 256 |
+
"Napoli_vs_Barcelona_2024-02-21"
|
| 257 |
+
],
|
| 258 |
+
"metrics": {
|
| 259 |
+
"avg_pressing_speed": 0.003,
|
| 260 |
+
"avg_def_line_movement": -0.0493,
|
| 261 |
+
"avg_compactness_delta": -0.0793,
|
| 262 |
+
"avg_transition_speed": 0.1457
|
| 263 |
+
}
|
| 264 |
+
}
|
| 265 |
+
},
|
| 266 |
+
"stats": {
|
| 267 |
+
"home": {
|
| 268 |
+
"team": "PSG",
|
| 269 |
+
"xg_last5": 2.06,
|
| 270 |
+
"xga_last5": 0.88,
|
| 271 |
+
"ppda": 9.4,
|
| 272 |
+
"possession_pct": 56,
|
| 273 |
+
"form": "WWWDW",
|
| 274 |
+
"goals_scored_last5": 11,
|
| 275 |
+
"goals_conceded_last5": 5
|
| 276 |
+
},
|
| 277 |
+
"away": {
|
| 278 |
+
"team": "Barcelona",
|
| 279 |
+
"xg_last5": 1.82,
|
| 280 |
+
"xga_last5": 1.22,
|
| 281 |
+
"ppda": 10.8,
|
| 282 |
+
"possession_pct": 60,
|
| 283 |
+
"form": "WWLWW",
|
| 284 |
+
"goals_scored_last5": 9,
|
| 285 |
+
"goals_conceded_last5": 6
|
| 286 |
+
}
|
| 287 |
+
},
|
| 288 |
+
"inference_time_s": 9.315885782241821
|
| 289 |
+
},
|
| 290 |
+
{
|
| 291 |
+
"match_id": "Man_City_vs_Real_Madrid_2024-04-17",
|
| 292 |
+
"home_team": "Man City",
|
| 293 |
+
"away_team": "Real Madrid",
|
| 294 |
+
"date": "2024-04-17",
|
| 295 |
+
"stage": "Quarter-final 2nd leg",
|
| 296 |
+
"market_odds": {
|
| 297 |
+
"home": 0.55,
|
| 298 |
+
"draw": 0.25,
|
| 299 |
+
"away": 0.23
|
| 300 |
+
},
|
| 301 |
+
"actual_result": "draw",
|
| 302 |
+
"actual_score": "1-1 (Real Madrid won on penalties)",
|
| 303 |
+
"vlm_assessment": {
|
| 304 |
+
"probabilities": {
|
| 305 |
+
"home": 0.58,
|
| 306 |
+
"draw": 0.22,
|
| 307 |
+
"away": 0.2
|
| 308 |
+
},
|
| 309 |
+
"edge": {
|
| 310 |
+
"home": 0.03,
|
| 311 |
+
"draw": -0.03,
|
| 312 |
+
"away": -0.03
|
| 313 |
+
},
|
| 314 |
+
"reasoning": "Man City's high pressing speed, compactness, and transition speed suggest they can dominate possession and create chances. However, Real Madrid's defensive resilience and ability to counter-attack pose a threat. The market slightly undervalues the draw.",
|
| 315 |
+
"visual_evidence": [
|
| 316 |
+
"In Frame 3, Man City's compactness and high defensive line indicate their aggressive approach to regain possession quickly.",
|
| 317 |
+
"Frames 5 and 6 show Real Madrid maintaining a solid defensive structure, limiting space for opponents to exploit.",
|
| 318 |
+
"The defensive line movement of both teams indicates a tactical battle for control in midfield."
|
| 319 |
+
],
|
| 320 |
+
"confidence": "medium",
|
| 321 |
+
"edge_signal": "The market underprices the draw due to Man City's offensive dominance but overlooks Real Madrid's defensive solidity and potential for counter-attacks."
|
| 322 |
+
},
|
| 323 |
+
"frames_used": [
|
| 324 |
+
"data/frames/Real_Madrid_vs_Man_City_2024-04-09/annotated/frame_004.jpg",
|
| 325 |
+
"data/frames/Real_Madrid_vs_Man_City_2024-04-09/annotated/frame_001.jpg",
|
| 326 |
+
"data/frames/Man_City_vs_FC_Copenhagen_2024-03-06/annotated/frame_001.jpg",
|
| 327 |
+
"data/frames/Man_City_vs_FC_Copenhagen_2024-03-06/annotated/frame_003.jpg",
|
| 328 |
+
"data/frames/Real_Madrid_vs_RB_Leipzig_2024-03-06/annotated/frame_000.jpg",
|
| 329 |
+
"data/frames/Real_Madrid_vs_RB_Leipzig_2024-03-06/annotated/frame_002.jpg"
|
| 330 |
+
],
|
| 331 |
+
"metrics_context": {
|
| 332 |
+
"home": {
|
| 333 |
+
"team": "Man City",
|
| 334 |
+
"matches_analyzed": [
|
| 335 |
+
"Real_Madrid_vs_Man_City_2024-04-09",
|
| 336 |
+
"Man_City_vs_FC_Copenhagen_2024-03-06",
|
| 337 |
+
"FC_Copenhagen_vs_Man_City_2024-02-13"
|
| 338 |
+
],
|
| 339 |
+
"metrics": {
|
| 340 |
+
"avg_pressing_speed": 0.003,
|
| 341 |
+
"avg_def_line_movement": -0.0107,
|
| 342 |
+
"avg_compactness_delta": -0.1893,
|
| 343 |
+
"avg_transition_speed": 0.155
|
| 344 |
+
}
|
| 345 |
+
},
|
| 346 |
+
"away": {
|
| 347 |
+
"team": "Real Madrid",
|
| 348 |
+
"matches_analyzed": [
|
| 349 |
+
"Real_Madrid_vs_Man_City_2024-04-09",
|
| 350 |
+
"Real_Madrid_vs_RB_Leipzig_2024-03-06",
|
| 351 |
+
"RB_Leipzig_vs_Real_Madrid_2024-02-13"
|
| 352 |
+
],
|
| 353 |
+
"metrics": {
|
| 354 |
+
"avg_pressing_speed": 0.0033,
|
| 355 |
+
"avg_def_line_movement": -0.035,
|
| 356 |
+
"avg_compactness_delta": -0.247,
|
| 357 |
+
"avg_transition_speed": 0.126
|
| 358 |
+
}
|
| 359 |
+
}
|
| 360 |
+
},
|
| 361 |
+
"stats": {
|
| 362 |
+
"home": {
|
| 363 |
+
"team": "Man City",
|
| 364 |
+
"xg_last5": 2.24,
|
| 365 |
+
"xga_last5": 0.86,
|
| 366 |
+
"ppda": 8.2,
|
| 367 |
+
"possession_pct": 64,
|
| 368 |
+
"form": "WWWWW",
|
| 369 |
+
"goals_scored_last5": 13,
|
| 370 |
+
"goals_conceded_last5": 4
|
| 371 |
+
},
|
| 372 |
+
"away": {
|
| 373 |
+
"team": "Real Madrid",
|
| 374 |
+
"xg_last5": 1.76,
|
| 375 |
+
"xga_last5": 1.08,
|
| 376 |
+
"ppda": 11.6,
|
| 377 |
+
"possession_pct": 52,
|
| 378 |
+
"form": "WDWWW",
|
| 379 |
+
"goals_scored_last5": 10,
|
| 380 |
+
"goals_conceded_last5": 5
|
| 381 |
+
}
|
| 382 |
+
},
|
| 383 |
+
"inference_time_s": 10.033257007598877
|
| 384 |
+
},
|
| 385 |
+
{
|
| 386 |
+
"match_id": "Atletico_Madrid_vs_Inter_Milan_2024-03-13",
|
| 387 |
+
"home_team": "Atletico Madrid",
|
| 388 |
+
"away_team": "Inter Milan",
|
| 389 |
+
"date": "2024-03-13",
|
| 390 |
+
"stage": "Round of 16 2nd leg",
|
| 391 |
+
"market_odds": {
|
| 392 |
+
"home": 0.45,
|
| 393 |
+
"draw": 0.3,
|
| 394 |
+
"away": 0.29
|
| 395 |
+
},
|
| 396 |
+
"actual_result": "home_win",
|
| 397 |
+
"actual_score": "2-1",
|
| 398 |
+
"vlm_assessment": {
|
| 399 |
+
"probabilities": {
|
| 400 |
+
"home": 0.4,
|
| 401 |
+
"draw": 0.32,
|
| 402 |
+
"away": 0.28
|
| 403 |
+
},
|
| 404 |
+
"edge": {
|
| 405 |
+
"home": -0.05,
|
| 406 |
+
"draw": 0.02,
|
| 407 |
+
"away": -0.01
|
| 408 |
+
},
|
| 409 |
+
"reasoning": "Inter Milan's higher xG and better defensive record suggest they could neutralize Atletico's attack. However, Atletico's pressing and compactness in their own half might limit Inter's chances. The first-leg result adds pressure on Atletico to score.",
|
| 410 |
+
"visual_evidence": [
|
| 411 |
+
"In Frame 2, we can see Inter Milan's compact defensive block, which limits Atletico Madrid's attacking options.",
|
| 412 |
+
"Frame 3 shows Atletico Madrid's high defensive line and aggressive pressing, which could disrupt Inter Milan's build-up play.",
|
| 413 |
+
"Frame 5 illustrates Inter Milan's ability to maintain possession and control the game when facing opposition."
|
| 414 |
+
],
|
| 415 |
+
"confidence": "medium",
|
| 416 |
+
"edge_signal": "The market may slightly overprice an Atletico Madrid win due to home advantage and the need to overturn the deficit. A draw or an Inter Milan win seems more likely given their recent form and defensive solidity."
|
| 417 |
+
},
|
| 418 |
+
"frames_used": [
|
| 419 |
+
"data/frames/Inter_Milan_vs_Atletico_Madrid_2024-02-20/annotated/frame_002.jpg",
|
| 420 |
+
"data/frames/Inter_Milan_vs_Atletico_Madrid_2024-02-20/annotated/frame_003.jpg",
|
| 421 |
+
"data/frames/Atletico_Madrid_vs_Lazio_2023-12-13/annotated/frame_001.jpg",
|
| 422 |
+
"data/frames/Atletico_Madrid_vs_Lazio_2023-12-13/annotated/frame_005.jpg",
|
| 423 |
+
"data/frames/Inter_Milan_vs_Real_Sociedad_2023-12-12/annotated/frame_001.jpg",
|
| 424 |
+
"data/frames/Inter_Milan_vs_Real_Sociedad_2023-12-12/annotated/frame_000.jpg"
|
| 425 |
+
],
|
| 426 |
+
"metrics_context": {
|
| 427 |
+
"home": {
|
| 428 |
+
"team": "Atletico Madrid",
|
| 429 |
+
"matches_analyzed": [
|
| 430 |
+
"Inter_Milan_vs_Atletico_Madrid_2024-02-20",
|
| 431 |
+
"Atletico_Madrid_vs_Lazio_2023-12-13",
|
| 432 |
+
"Feyenoord_vs_Atletico_Madrid_2023-12-12"
|
| 433 |
+
],
|
| 434 |
+
"metrics": {
|
| 435 |
+
"avg_pressing_speed": 0.0037,
|
| 436 |
+
"avg_def_line_movement": -0.012,
|
| 437 |
+
"avg_compactness_delta": -0.4683,
|
| 438 |
+
"avg_transition_speed": 0.1453
|
| 439 |
+
}
|
| 440 |
+
},
|
| 441 |
+
"away": {
|
| 442 |
+
"team": "Inter Milan",
|
| 443 |
+
"matches_analyzed": [
|
| 444 |
+
"Inter_Milan_vs_Atletico_Madrid_2024-02-20",
|
| 445 |
+
"Inter_Milan_vs_Real_Sociedad_2023-12-12",
|
| 446 |
+
"Benfica_vs_Inter_Milan_2023-11-07"
|
| 447 |
+
],
|
| 448 |
+
"metrics": {
|
| 449 |
+
"avg_pressing_speed": 0.0033,
|
| 450 |
+
"avg_def_line_movement": -0.055,
|
| 451 |
+
"avg_compactness_delta": -0.326,
|
| 452 |
+
"avg_transition_speed": 0.1163
|
| 453 |
+
}
|
| 454 |
+
}
|
| 455 |
+
},
|
| 456 |
+
"stats": {
|
| 457 |
+
"home": {
|
| 458 |
+
"team": "Atletico Madrid",
|
| 459 |
+
"xg_last5": 1.56,
|
| 460 |
+
"xga_last5": 0.92,
|
| 461 |
+
"ppda": 11.4,
|
| 462 |
+
"possession_pct": 50,
|
| 463 |
+
"form": "WWDWL",
|
| 464 |
+
"goals_scored_last5": 8,
|
| 465 |
+
"goals_conceded_last5": 5
|
| 466 |
+
},
|
| 467 |
+
"away": {
|
| 468 |
+
"team": "Inter Milan",
|
| 469 |
+
"xg_last5": 1.88,
|
| 470 |
+
"xga_last5": 0.78,
|
| 471 |
+
"ppda": 10.2,
|
| 472 |
+
"possession_pct": 54,
|
| 473 |
+
"form": "WWWWD",
|
| 474 |
+
"goals_scored_last5": 10,
|
| 475 |
+
"goals_conceded_last5": 3
|
| 476 |
+
}
|
| 477 |
+
},
|
| 478 |
+
"inference_time_s": 11.459537267684937
|
| 479 |
+
}
|
| 480 |
+
]
|
| 481 |
+
}
|
requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio>=4.0
|
| 2 |
+
plotly>=5.0
|