0xpaona commited on
Commit
cb414cf
·
verified ·
1 Parent(s): 9404e2c

Upload folder using huggingface_hub

Browse files
Files changed (36) hide show
  1. .gitattributes +25 -0
  2. README.md +47 -5
  3. app.py +280 -4
  4. data/demo_matches.json +177 -0
  5. data/vlm_results/frames/Atletico_Madrid_vs_Dortmund_2024-04-10/frame_000.jpg +3 -0
  6. data/vlm_results/frames/Atletico_Madrid_vs_Dortmund_2024-04-10/frame_003.jpg +3 -0
  7. data/vlm_results/frames/Atletico_Madrid_vs_Inter_Milan_2024-03-13/frame_001.jpg +3 -0
  8. data/vlm_results/frames/Atletico_Madrid_vs_Inter_Milan_2024-03-13/frame_005.jpg +3 -0
  9. data/vlm_results/frames/Atletico_Madrid_vs_Lazio_2023-12-13/frame_001.jpg +3 -0
  10. data/vlm_results/frames/Atletico_Madrid_vs_Lazio_2023-12-13/frame_005.jpg +3 -0
  11. data/vlm_results/frames/Barcelona_vs_Napoli_2024-03-12/frame_000.jpg +3 -0
  12. data/vlm_results/frames/Barcelona_vs_Napoli_2024-03-12/frame_006.jpg +3 -0
  13. data/vlm_results/frames/Barcelona_vs_PSG_2024-04-10/frame_002.jpg +0 -0
  14. data/vlm_results/frames/Barcelona_vs_PSG_2024-04-10/frame_005.jpg +0 -0
  15. data/vlm_results/frames/Dortmund_vs_Atletico_Madrid_2024-04-16/frame_002.jpg +3 -0
  16. data/vlm_results/frames/Dortmund_vs_Atletico_Madrid_2024-04-16/frame_006.jpg +3 -0
  17. data/vlm_results/frames/Dortmund_vs_PSV_2024-03-13/frame_003.jpg +3 -0
  18. data/vlm_results/frames/Dortmund_vs_PSV_2024-03-13/frame_005.jpg +3 -0
  19. data/vlm_results/frames/Inter_Milan_vs_Atletico_Madrid_2024-02-20/frame_002.jpg +0 -0
  20. data/vlm_results/frames/Inter_Milan_vs_Atletico_Madrid_2024-02-20/frame_003.jpg +3 -0
  21. data/vlm_results/frames/Inter_Milan_vs_Real_Sociedad_2023-12-12/frame_000.jpg +0 -0
  22. data/vlm_results/frames/Inter_Milan_vs_Real_Sociedad_2023-12-12/frame_001.jpg +3 -0
  23. data/vlm_results/frames/Man_City_vs_FC_Copenhagen_2024-03-06/frame_001.jpg +0 -0
  24. data/vlm_results/frames/Man_City_vs_FC_Copenhagen_2024-03-06/frame_003.jpg +3 -0
  25. data/vlm_results/frames/PSG_vs_Barcelona_2024-04-16/frame_002.jpg +3 -0
  26. data/vlm_results/frames/PSG_vs_Barcelona_2024-04-16/frame_005.jpg +3 -0
  27. data/vlm_results/frames/PSG_vs_Dortmund_2024-05-01/frame_000.jpg +3 -0
  28. data/vlm_results/frames/PSG_vs_Dortmund_2024-05-01/frame_001.jpg +3 -0
  29. data/vlm_results/frames/Real_Madrid_vs_Man_City_2024-04-09/frame_001.jpg +3 -0
  30. data/vlm_results/frames/Real_Madrid_vs_Man_City_2024-04-09/frame_004.jpg +3 -0
  31. data/vlm_results/frames/Real_Madrid_vs_RB_Leipzig_2024-03-06/frame_000.jpg +3 -0
  32. data/vlm_results/frames/Real_Madrid_vs_RB_Leipzig_2024-03-06/frame_002.jpg +3 -0
  33. data/vlm_results/frames/Real_Sociedad_vs_PSG_2024-03-05/frame_002.jpg +3 -0
  34. data/vlm_results/frames/Real_Sociedad_vs_PSG_2024-03-05/frame_004.jpg +3 -0
  35. data/vlm_results/results.json +481 -0
  36. 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: pink
5
  colorTo: blue
6
  sdk: gradio
7
  sdk_version: 6.14.0
8
- python_version: '3.13'
9
  app_file: app.py
10
  pinned: false
11
  license: mit
12
- short_description: Multimodal AI tactical analysis in UCL prediction marketsS
13
  ---
14
 
15
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- demo = gr.Interface(fn=greet, inputs="text", outputs="text")
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

  • SHA256: 94942d44a1181ced14098cb5041214e2086bcfe24bd42c0d2d58f5106e6d7740
  • Pointer size: 131 Bytes
  • Size of remote file: 158 kB
data/vlm_results/frames/Atletico_Madrid_vs_Dortmund_2024-04-10/frame_003.jpg ADDED

Git LFS Details

  • SHA256: 4d836d2269517051a0621903b0f6f64c6f35b2c30d1450180e9f1eb85164bb7f
  • Pointer size: 131 Bytes
  • Size of remote file: 155 kB
data/vlm_results/frames/Atletico_Madrid_vs_Inter_Milan_2024-03-13/frame_001.jpg ADDED

Git LFS Details

  • SHA256: c755504e602ecf35c449580b97c445cf7c67e44b8ea0ee44334ac891236adc76
  • Pointer size: 131 Bytes
  • Size of remote file: 175 kB
data/vlm_results/frames/Atletico_Madrid_vs_Inter_Milan_2024-03-13/frame_005.jpg ADDED

Git LFS Details

  • SHA256: 2095c14380151009ee86fc1881a06c10adc3e5b213f3536b603ec2dece555b32
  • Pointer size: 131 Bytes
  • Size of remote file: 110 kB
data/vlm_results/frames/Atletico_Madrid_vs_Lazio_2023-12-13/frame_001.jpg ADDED

Git LFS Details

  • SHA256: 4b2846d0436074d4a2884d49737c42845f28355b211369cbffa30f494de27a81
  • Pointer size: 131 Bytes
  • Size of remote file: 147 kB
data/vlm_results/frames/Atletico_Madrid_vs_Lazio_2023-12-13/frame_005.jpg ADDED

Git LFS Details

  • SHA256: a23e3eaca6b5f9c730f9325537b0f5baed3de5c55133d6bb06fe8eb9c9e99c66
  • Pointer size: 131 Bytes
  • Size of remote file: 150 kB
data/vlm_results/frames/Barcelona_vs_Napoli_2024-03-12/frame_000.jpg ADDED

Git LFS Details

  • SHA256: 972a0b27c039d2501ece7daa4cb92eac4f724a9e3af85d4469942ced9bfb5620
  • Pointer size: 131 Bytes
  • Size of remote file: 109 kB
data/vlm_results/frames/Barcelona_vs_Napoli_2024-03-12/frame_006.jpg ADDED

Git LFS Details

  • SHA256: b509e9e4439916c48c00faefc5133eb3b1abb650f308c942c4f5719d8a95ec35
  • Pointer size: 131 Bytes
  • Size of remote file: 117 kB
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

  • SHA256: b22be8890eaea879a3ae9c02de9c04b4d547f121eea66ba8cf0ac20ff315ece8
  • Pointer size: 131 Bytes
  • Size of remote file: 140 kB
data/vlm_results/frames/Dortmund_vs_Atletico_Madrid_2024-04-16/frame_006.jpg ADDED

Git LFS Details

  • SHA256: c9a2c7a4c3e11d6fb82c7538b12684465af3dd69c1d4a21472d5847eebc3e66e
  • Pointer size: 131 Bytes
  • Size of remote file: 146 kB
data/vlm_results/frames/Dortmund_vs_PSV_2024-03-13/frame_003.jpg ADDED

Git LFS Details

  • SHA256: d9a8a1e0b6d78223c93ef196ad44843926573e291d1b35f4dfa7e7e08a826666
  • Pointer size: 131 Bytes
  • Size of remote file: 115 kB
data/vlm_results/frames/Dortmund_vs_PSV_2024-03-13/frame_005.jpg ADDED

Git LFS Details

  • SHA256: dceba5062f88e9abaf80ca527031490d15c973da46afab937a86daf0bedaa331
  • Pointer size: 131 Bytes
  • Size of remote file: 127 kB
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

  • SHA256: aea4a3561f1e12334d1b3505c0444591a6f1edcee57f8462e0b2e0ad12e6a735
  • Pointer size: 131 Bytes
  • Size of remote file: 114 kB
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

  • SHA256: 4bec15c5a266c744ebc94457d661e1b22a06ec98c6de6b8a27484932b3a4b398
  • Pointer size: 131 Bytes
  • Size of remote file: 120 kB
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

  • SHA256: ca35e67c3faf97abd0dc7cd97f9aa4c0cbc81ce899f67e40943c3ebe7d763834
  • Pointer size: 131 Bytes
  • Size of remote file: 132 kB
data/vlm_results/frames/PSG_vs_Barcelona_2024-04-16/frame_002.jpg ADDED

Git LFS Details

  • SHA256: 62ddf4c6eddeb7ef36656937dc87715717fc047fc7b0f8c9b4e0b22d8e7dcc93
  • Pointer size: 131 Bytes
  • Size of remote file: 102 kB
data/vlm_results/frames/PSG_vs_Barcelona_2024-04-16/frame_005.jpg ADDED

Git LFS Details

  • SHA256: b9668261dc5a0894e5ae8432390a1a438abc63cf9105343e4a19f7dbc0eae87b
  • Pointer size: 131 Bytes
  • Size of remote file: 114 kB
data/vlm_results/frames/PSG_vs_Dortmund_2024-05-01/frame_000.jpg ADDED

Git LFS Details

  • SHA256: ebf36c1bcb364897a56bd542394eecd8bda302cb5073611de8c11b5fc381b07e
  • Pointer size: 131 Bytes
  • Size of remote file: 112 kB
data/vlm_results/frames/PSG_vs_Dortmund_2024-05-01/frame_001.jpg ADDED

Git LFS Details

  • SHA256: 7460c08b7d668744e3909dd98251a40a7e77ffcdd99193597a75ed5afb641b44
  • Pointer size: 131 Bytes
  • Size of remote file: 134 kB
data/vlm_results/frames/Real_Madrid_vs_Man_City_2024-04-09/frame_001.jpg ADDED

Git LFS Details

  • SHA256: 9b590c02f59d93880621662172f834f99c413cb268ed0e59c44fdf4f3fe335a8
  • Pointer size: 131 Bytes
  • Size of remote file: 160 kB
data/vlm_results/frames/Real_Madrid_vs_Man_City_2024-04-09/frame_004.jpg ADDED

Git LFS Details

  • SHA256: 0588b54b64b061cc107f3913c36526c4c0e3c58fbb084a0f2ce54d754c5f1866
  • Pointer size: 131 Bytes
  • Size of remote file: 168 kB
data/vlm_results/frames/Real_Madrid_vs_RB_Leipzig_2024-03-06/frame_000.jpg ADDED

Git LFS Details

  • SHA256: 88bd8147f6b6a14f925586419bf722bfb3041919a2097541ecf3d42d37d9cb16
  • Pointer size: 131 Bytes
  • Size of remote file: 119 kB
data/vlm_results/frames/Real_Madrid_vs_RB_Leipzig_2024-03-06/frame_002.jpg ADDED

Git LFS Details

  • SHA256: ad4694b5c14949c50f0e9c051f0c7c04873da9cfb5f93983ef337dd280b06ecb
  • Pointer size: 131 Bytes
  • Size of remote file: 156 kB
data/vlm_results/frames/Real_Sociedad_vs_PSG_2024-03-05/frame_002.jpg ADDED

Git LFS Details

  • SHA256: 23c6972188c4c0fc109cfca2e160b9b98f962f65d64a898c48c98b75033abb2b
  • Pointer size: 131 Bytes
  • Size of remote file: 140 kB
data/vlm_results/frames/Real_Sociedad_vs_PSG_2024-03-05/frame_004.jpg ADDED

Git LFS Details

  • SHA256: 45418617a9409f448ffff09bcbb7f35358e2044e2538b3962eff86749cad52d9
  • Pointer size: 131 Bytes
  • Size of remote file: 153 kB
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