openmatchkit 0.2.2__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/CHANGELOG.md +11 -0
  2. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/PKG-INFO +245 -3
  3. openmatchkit-0.3.0/README.md +400 -0
  4. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/pyproject.toml +1 -1
  5. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/src/openmatchkit/__init__.py +12 -0
  6. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/src/openmatchkit/cli.py +140 -0
  7. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/src/openmatchkit/client.py +405 -1
  8. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/src/openmatchkit/http.py +1 -1
  9. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/src/openmatchkit/models.py +74 -1
  10. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/tests/test_client.py +63 -0
  11. openmatchkit-0.2.2/README.md +0 -158
  12. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  13. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  14. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  15. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/.github/ISSUE_TEMPLATE/source_adapter.md +0 -0
  16. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/.github/pull_request_template.md +0 -0
  17. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/.github/workflows/ci.yml +0 -0
  18. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/.github/workflows/publish.yml +0 -0
  19. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/.gitignore +0 -0
  20. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/CODE_OF_CONDUCT.md +0 -0
  21. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/CONTRIBUTING.md +0 -0
  22. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/DATA_SOURCES.md +0 -0
  23. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/LICENSE +0 -0
  24. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/PRIVACY.md +0 -0
  25. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/ROADMAP.md +0 -0
  26. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/SECURITY.md +0 -0
  27. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/docs/detailed-data.md +0 -0
  28. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/docs/index.md +0 -0
  29. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/docs/prediction.md +0 -0
  30. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/docs/sources.md +0 -0
  31. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/examples/fixtures_demo.py +0 -0
  32. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/examples/live_demo.py +0 -0
  33. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/examples/prediction_demo.py +0 -0
  34. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/src/openmatchkit/exceptions.py +0 -0
  35. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/src/openmatchkit/export.py +0 -0
  36. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/src/openmatchkit/prediction/__init__.py +0 -0
  37. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/src/openmatchkit/prediction/elo.py +0 -0
  38. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/src/openmatchkit/prediction/poisson.py +0 -0
  39. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/src/openmatchkit/sources/__init__.py +0 -0
  40. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/src/openmatchkit/sources/base.py +0 -0
  41. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/src/openmatchkit/sources/football_data_uk.py +0 -0
  42. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/src/openmatchkit/sources/json_file.py +0 -0
  43. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/src/openmatchkit/sources/openfootball.py +0 -0
  44. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/src/openmatchkit/sources/public_html.py +0 -0
  45. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/tests/fixtures/sample_detailed_source.json +0 -0
  46. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/tests/fixtures/sample_football_data.csv +0 -0
  47. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/tests/fixtures/sample_openfootball.json +0 -0
  48. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/tests/test_detailed_data.py +0 -0
  49. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/tests/test_export.py +0 -0
  50. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/tests/test_football_data_uk.py +0 -0
  51. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/tests/test_http.py +0 -0
  52. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/tests/test_models.py +0 -0
  53. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/tests/test_openfootball.py +0 -0
  54. {openmatchkit-0.2.2 → openmatchkit-0.3.0}/tests/test_prediction.py +0 -0
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.3.0 - 2026-06-12
4
+
5
+ - Added source capability and source health models.
6
+ - Added match search/filter helpers.
7
+ - Added upcoming and recent result helpers.
8
+ - Added match lookup and match summary helpers.
9
+ - Added team form and head-to-head summaries.
10
+ - Added CLI commands for new features.
11
+ - Expanded README with detailed function-by-function JSON examples.
12
+ - Increased tests from 16 to 19.
13
+
3
14
  ## v0.2.2 - 2026-06-12
4
15
 
5
16
  - Updated README and PyPI project description to use `pip install openmatchkit`.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openmatchkit
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: Unofficial open-source football match data toolkit using public sources.
5
5
  Project-URL: Homepage, https://github.com/patilprashan246/openmatchkit
6
6
  Project-URL: Repository, https://github.com/patilprashan246/openmatchkit
@@ -58,7 +58,7 @@ pip install openmatchkit
58
58
  From GitHub:
59
59
 
60
60
  ```bash
61
- pip install git+https://github.com/patilprashan246/openmatchkit.git@v0.2.2
61
+ pip install git+https://github.com/patilprashan246/openmatchkit.git@v0.3.0
62
62
  ```
63
63
 
64
64
  For local development:
@@ -109,13 +109,253 @@ League datasets from OpenFootball can be fetched with codes such as:
109
109
  matches = client.fixtures(competition="en.1", season="2015-16")
110
110
  ```
111
111
 
112
+ ## Functionality Reference
113
+
114
+ All public models are Pydantic models. Use `.model_dump(mode="json")` when you want JSON-ready dictionaries, or use the CLI commands when you want JSON printed directly.
115
+
116
+ ### Source Metadata
117
+
118
+ ```python
119
+ client.source_capabilities()
120
+ client.source_health()
121
+ ```
122
+
123
+ Returns source capability and local health metadata:
124
+
125
+ ```json
126
+ [
127
+ {
128
+ "source": "openfootball",
129
+ "fixtures": true,
130
+ "results": true,
131
+ "live_scores": false,
132
+ "scoreboards": true,
133
+ "player_history": false,
134
+ "standings": true,
135
+ "prediction_history": true,
136
+ "notes": [
137
+ "OpenFootball provides fixtures/results; detailed player data is unavailable."
138
+ ]
139
+ }
140
+ ]
141
+ ```
142
+
143
+ ### Fixtures, Results, Search, Upcoming, Recent
144
+
145
+ ```python
146
+ fixtures = client.fixtures("worldcup", "2026")
147
+ results = client.results("worldcup", "2026")
148
+ matches = client.search_matches("worldcup", "2026", team="Canada", status="scheduled")
149
+ upcoming = client.upcoming_matches("worldcup", "2026", team="Canada", limit=3)
150
+ recent = client.recent_results("worldcup", "2026", limit=5)
151
+ next_match = client.next_match("worldcup", "2026")
152
+ match = client.match_by_id(match_id="...", competition="worldcup", season="2026")
153
+ ```
154
+
155
+ Each item is a `Match`:
156
+
157
+ ```json
158
+ {
159
+ "match_id": "ded80f9a2a529045",
160
+ "competition": "World Cup 2026",
161
+ "season": "2026",
162
+ "round": "Matchday 2",
163
+ "group": "Group B",
164
+ "kickoff": "2026-06-12T15:00:00-04:00",
165
+ "home": {"name": "Canada", "code": null, "country": null},
166
+ "away": {"name": "Bosnia & Herzegovina", "code": null, "country": null},
167
+ "score": {"home": null, "away": null},
168
+ "status": "scheduled",
169
+ "venue": "Toronto",
170
+ "source": "openfootball",
171
+ "source_url": "https://raw.githubusercontent.com/openfootball/worldcup.json/master/2026/worldcup.json",
172
+ "fetched_at": "2026-06-12T10:46:21.427460Z"
173
+ }
174
+ ```
175
+
176
+ ### Scoreboards and Match Summaries
177
+
178
+ ```python
179
+ summary = client.match_summary(match_id="detail-1", competition="Demo Cup", season="2026")
180
+ scoreboard = client.scoreboard(match_id="detail-1")
181
+ scoreboards = client.scoreboards(competition="Demo Cup", season="2026")
182
+ live_boards = client.live_scoreboards()
183
+ ```
184
+
185
+ `Scoreboard` can include match clock, lineups, events, team stats, and player stats when the configured source provides them:
186
+
187
+ ```json
188
+ {
189
+ "match": {
190
+ "match_id": "detail-1",
191
+ "competition": "Demo Cup",
192
+ "home": {"name": "Team A"},
193
+ "away": {"name": "Team B"},
194
+ "score": {"home": 1, "away": 0},
195
+ "status": "live"
196
+ },
197
+ "clock": {"minute": 45, "added_time": 2, "period": "first_half", "display": "45+2"},
198
+ "events": [
199
+ {
200
+ "event_type": "goal",
201
+ "team": "Team A",
202
+ "player": "Alex Demo",
203
+ "assist": "Sam Creator",
204
+ "minute": 34,
205
+ "home_score": 1,
206
+ "away_score": 0
207
+ }
208
+ ],
209
+ "team_stats": [
210
+ {"team": "Team A", "possession": 58.4, "shots": 7, "shots_on_target": 3}
211
+ ],
212
+ "player_stats": [
213
+ {"player": {"name": "Alex Demo", "team": "Team A"}, "goals": 1, "minutes_played": 45}
214
+ ],
215
+ "source_notes": ["Demo fixture representing an authorized detailed public JSON feed."]
216
+ }
217
+ ```
218
+
219
+ ### Teams, Standings, Form, Head-to-Head
220
+
221
+ ```python
222
+ teams = client.teams("worldcup", "2026")
223
+ table = client.standings("worldcup", "2026", group="Group A")
224
+ form = client.team_form("Mexico", "worldcup", "2026", last=5)
225
+ h2h = client.head_to_head("Mexico", "South Africa", "worldcup", "2026")
226
+ ```
227
+
228
+ `TeamForm` returns recent results from the team perspective:
229
+
230
+ ```json
231
+ {
232
+ "team": "Mexico",
233
+ "competition": "worldcup",
234
+ "season": "2026",
235
+ "requested_last": 5,
236
+ "played": 1,
237
+ "wins": 1,
238
+ "draws": 0,
239
+ "losses": 0,
240
+ "goals_for": 2,
241
+ "goals_against": 0,
242
+ "goal_difference": 2,
243
+ "points": 3,
244
+ "form": ["W"],
245
+ "matches": [
246
+ {
247
+ "opponent": "South Africa",
248
+ "home_away": "home",
249
+ "team_score": 2,
250
+ "opponent_score": 0,
251
+ "result": "W"
252
+ }
253
+ ]
254
+ }
255
+ ```
256
+
257
+ `HeadToHeadSummary` returns aggregate history plus latest matches:
258
+
259
+ ```json
260
+ {
261
+ "team_a": "Mexico",
262
+ "team_b": "South Africa",
263
+ "matches": 1,
264
+ "team_a_wins": 1,
265
+ "draws": 0,
266
+ "team_b_wins": 0,
267
+ "team_a_goals": 2,
268
+ "team_b_goals": 0,
269
+ "latest_matches": []
270
+ }
271
+ ```
272
+
273
+ ### Player History
274
+
275
+ Player history depends on a source that legally provides player-level public match facts. The default OpenFootball adapter does not include player stats, but `JsonFileSource` supports detailed authorized feeds:
276
+
277
+ ```python
278
+ from openmatchkit import MatchClient
279
+ from openmatchkit.sources.json_file import JsonFileSource
280
+
281
+ client = MatchClient(sources=[JsonFileSource("tests/fixtures/sample_detailed_source.json")])
282
+ history = client.player_history("Alex Demo")
283
+ print(history.model_dump(mode="json"))
284
+ ```
285
+
286
+ ```json
287
+ {
288
+ "player": {"name": "Alex Demo", "team": "Team A", "position": "FW", "shirt_number": 9},
289
+ "totals": {
290
+ "appearances": 1,
291
+ "starts": 1,
292
+ "minutes_played": 45,
293
+ "goals": 1,
294
+ "assists": 0,
295
+ "yellow_cards": 1,
296
+ "red_cards": 0
297
+ },
298
+ "appearances": [
299
+ {
300
+ "match_id": "detail-1",
301
+ "team": "Team A",
302
+ "opponent": "Team B",
303
+ "home_away": "home",
304
+ "goals": 1,
305
+ "source": "demo_public_feed"
306
+ }
307
+ ]
308
+ }
309
+ ```
310
+
311
+ ### Prediction
312
+
313
+ ```python
314
+ history = client.results("worldcup", "2026")
315
+ prediction = client.predict(home="Mexico", away="South Africa", history=history)
316
+ ```
317
+
318
+ Returns an educational baseline only:
319
+
320
+ ```json
321
+ {
322
+ "home": "Mexico",
323
+ "away": "South Africa",
324
+ "home_win_probability": 0.45,
325
+ "draw_probability": 0.27,
326
+ "away_win_probability": 0.28,
327
+ "expected_home_goals": 1.2,
328
+ "expected_away_goals": 0.9,
329
+ "model": "simple_poisson",
330
+ "training_matches": 2,
331
+ "note": "Educational baseline only. Not betting, financial, or professional advice."
332
+ }
333
+ ```
334
+
335
+ ### Export
336
+
337
+ ```python
338
+ client.export_json(fixtures, "fixtures.json")
339
+ client.export_csv(fixtures, "fixtures.csv")
340
+ ```
341
+
342
+ JSON export keeps full nested model data. CSV export flattens match-level fields for spreadsheet tools.
343
+
112
344
  ## CLI
113
345
 
114
346
  ```bash
347
+ openmatch sources
348
+ openmatch health
115
349
  openmatch fixtures --competition worldcup --season 2026
350
+ openmatch search --competition worldcup --season 2026 --team Canada --status scheduled
116
351
  openmatch results --competition en.1 --season 2015-16 --format csv --output results.csv
352
+ openmatch upcoming --competition worldcup --season 2026 --team Canada --limit 3
353
+ openmatch recent --competition worldcup --season 2026 --limit 5
117
354
  openmatch next-match --competition worldcup --season 2026
355
+ openmatch match-summary --match-id detail-1 --data-file tests/fixtures/sample_detailed_source.json
118
356
  openmatch standings --competition worldcup --season 2026
357
+ openmatch team-form --team Mexico --competition worldcup --season 2026
358
+ openmatch head-to-head --team-a Mexico --team-b "South Africa" --competition worldcup --season 2026
119
359
  openmatch predict --home Mexico --away "South Africa" --competition worldcup --season 2026
120
360
  ```
121
361
 
@@ -159,7 +399,9 @@ print(history.model_dump(mode="json"))
159
399
  - Local detailed JSON adapter for authorized/public detailed feeds
160
400
  - Generic optional public HTML adapter
161
401
  - Safe HTTP client with robots.txt checks, caching, user agent, and per-origin delay
162
- - Fixtures, results, next match, live score adapter interface, team info, standings
402
+ - Fixtures, results, search, upcoming/recent matches, next match, live score adapter interface
403
+ - Source capabilities and health metadata
404
+ - Team info, grouped standings, team form, and head-to-head summaries
163
405
  - Detailed scoreboards: clock, events, lineups, team stats, and player match stats when provided
164
406
  - Player histories aggregated from player-level source data
165
407
  - Simple Poisson prediction baseline and small Elo rating helper
@@ -0,0 +1,400 @@
1
+ # openmatchkit
2
+
3
+ [![CI](https://github.com/patilprashan246/openmatchkit/actions/workflows/ci.yml/badge.svg)](https://github.com/patilprashan246/openmatchkit/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/openmatchkit)](https://pypi.org/project/openmatchkit/)
5
+ [![Release](https://img.shields.io/github/v/release/patilprashan246/openmatchkit)](https://github.com/patilprashan246/openmatchkit/releases)
6
+ [![Python](https://img.shields.io/badge/python-3.10%2B-blue)](pyproject.toml)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
8
+
9
+ Unofficial open-source football match data toolkit for fixtures, results, best-effort live score adapters, standings, exports, and simple prediction models.
10
+
11
+ > This project is unofficial and not affiliated with FIFA, any league, club, broadcaster, federation, or data provider. It does not provide official FIFA data.
12
+
13
+ ## Why openmatchkit?
14
+
15
+ openmatchkit gives Python developers a clean, source-attributed way to work with public football match facts without paid API keys. It starts with open/public datasets, keeps scraping optional and conservative, and returns structured JSON-ready models.
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ pip install openmatchkit
21
+ ```
22
+
23
+ From GitHub:
24
+
25
+ ```bash
26
+ pip install git+https://github.com/patilprashan246/openmatchkit.git@v0.3.0
27
+ ```
28
+
29
+ For local development:
30
+
31
+ ```bash
32
+ python -m venv .venv
33
+ .venv\Scripts\activate
34
+ python -m pip install --upgrade pip
35
+ python -m pip install -e ".[dev]"
36
+ pytest
37
+ ```
38
+
39
+ ## Quick Start
40
+
41
+ ```python
42
+ from openmatchkit import MatchClient
43
+
44
+ client = MatchClient()
45
+ next_match = client.next_match(competition="worldcup", season="2026")
46
+
47
+ print(next_match.model_dump(mode="json"))
48
+ ```
49
+
50
+ ```bash
51
+ openmatch next-match --competition worldcup --season 2026
52
+ ```
53
+
54
+ ## Python API
55
+
56
+ ```python
57
+ from openmatchkit import MatchClient
58
+
59
+ client = MatchClient()
60
+
61
+ fixtures = client.fixtures(competition="worldcup", season="2026")
62
+ results = client.results(competition="worldcup", season="2026")
63
+ live = client.live_scores()
64
+ next_match = client.next_match(competition="worldcup", season="2026")
65
+ prediction = client.predict(home="Mexico", away="South Africa", history=results)
66
+
67
+ client.export_json(fixtures, "fixtures.json")
68
+ client.export_csv(fixtures, "fixtures.csv")
69
+ ```
70
+
71
+ League datasets from OpenFootball can be fetched with codes such as:
72
+
73
+ ```python
74
+ matches = client.fixtures(competition="en.1", season="2015-16")
75
+ ```
76
+
77
+ ## Functionality Reference
78
+
79
+ All public models are Pydantic models. Use `.model_dump(mode="json")` when you want JSON-ready dictionaries, or use the CLI commands when you want JSON printed directly.
80
+
81
+ ### Source Metadata
82
+
83
+ ```python
84
+ client.source_capabilities()
85
+ client.source_health()
86
+ ```
87
+
88
+ Returns source capability and local health metadata:
89
+
90
+ ```json
91
+ [
92
+ {
93
+ "source": "openfootball",
94
+ "fixtures": true,
95
+ "results": true,
96
+ "live_scores": false,
97
+ "scoreboards": true,
98
+ "player_history": false,
99
+ "standings": true,
100
+ "prediction_history": true,
101
+ "notes": [
102
+ "OpenFootball provides fixtures/results; detailed player data is unavailable."
103
+ ]
104
+ }
105
+ ]
106
+ ```
107
+
108
+ ### Fixtures, Results, Search, Upcoming, Recent
109
+
110
+ ```python
111
+ fixtures = client.fixtures("worldcup", "2026")
112
+ results = client.results("worldcup", "2026")
113
+ matches = client.search_matches("worldcup", "2026", team="Canada", status="scheduled")
114
+ upcoming = client.upcoming_matches("worldcup", "2026", team="Canada", limit=3)
115
+ recent = client.recent_results("worldcup", "2026", limit=5)
116
+ next_match = client.next_match("worldcup", "2026")
117
+ match = client.match_by_id(match_id="...", competition="worldcup", season="2026")
118
+ ```
119
+
120
+ Each item is a `Match`:
121
+
122
+ ```json
123
+ {
124
+ "match_id": "ded80f9a2a529045",
125
+ "competition": "World Cup 2026",
126
+ "season": "2026",
127
+ "round": "Matchday 2",
128
+ "group": "Group B",
129
+ "kickoff": "2026-06-12T15:00:00-04:00",
130
+ "home": {"name": "Canada", "code": null, "country": null},
131
+ "away": {"name": "Bosnia & Herzegovina", "code": null, "country": null},
132
+ "score": {"home": null, "away": null},
133
+ "status": "scheduled",
134
+ "venue": "Toronto",
135
+ "source": "openfootball",
136
+ "source_url": "https://raw.githubusercontent.com/openfootball/worldcup.json/master/2026/worldcup.json",
137
+ "fetched_at": "2026-06-12T10:46:21.427460Z"
138
+ }
139
+ ```
140
+
141
+ ### Scoreboards and Match Summaries
142
+
143
+ ```python
144
+ summary = client.match_summary(match_id="detail-1", competition="Demo Cup", season="2026")
145
+ scoreboard = client.scoreboard(match_id="detail-1")
146
+ scoreboards = client.scoreboards(competition="Demo Cup", season="2026")
147
+ live_boards = client.live_scoreboards()
148
+ ```
149
+
150
+ `Scoreboard` can include match clock, lineups, events, team stats, and player stats when the configured source provides them:
151
+
152
+ ```json
153
+ {
154
+ "match": {
155
+ "match_id": "detail-1",
156
+ "competition": "Demo Cup",
157
+ "home": {"name": "Team A"},
158
+ "away": {"name": "Team B"},
159
+ "score": {"home": 1, "away": 0},
160
+ "status": "live"
161
+ },
162
+ "clock": {"minute": 45, "added_time": 2, "period": "first_half", "display": "45+2"},
163
+ "events": [
164
+ {
165
+ "event_type": "goal",
166
+ "team": "Team A",
167
+ "player": "Alex Demo",
168
+ "assist": "Sam Creator",
169
+ "minute": 34,
170
+ "home_score": 1,
171
+ "away_score": 0
172
+ }
173
+ ],
174
+ "team_stats": [
175
+ {"team": "Team A", "possession": 58.4, "shots": 7, "shots_on_target": 3}
176
+ ],
177
+ "player_stats": [
178
+ {"player": {"name": "Alex Demo", "team": "Team A"}, "goals": 1, "minutes_played": 45}
179
+ ],
180
+ "source_notes": ["Demo fixture representing an authorized detailed public JSON feed."]
181
+ }
182
+ ```
183
+
184
+ ### Teams, Standings, Form, Head-to-Head
185
+
186
+ ```python
187
+ teams = client.teams("worldcup", "2026")
188
+ table = client.standings("worldcup", "2026", group="Group A")
189
+ form = client.team_form("Mexico", "worldcup", "2026", last=5)
190
+ h2h = client.head_to_head("Mexico", "South Africa", "worldcup", "2026")
191
+ ```
192
+
193
+ `TeamForm` returns recent results from the team perspective:
194
+
195
+ ```json
196
+ {
197
+ "team": "Mexico",
198
+ "competition": "worldcup",
199
+ "season": "2026",
200
+ "requested_last": 5,
201
+ "played": 1,
202
+ "wins": 1,
203
+ "draws": 0,
204
+ "losses": 0,
205
+ "goals_for": 2,
206
+ "goals_against": 0,
207
+ "goal_difference": 2,
208
+ "points": 3,
209
+ "form": ["W"],
210
+ "matches": [
211
+ {
212
+ "opponent": "South Africa",
213
+ "home_away": "home",
214
+ "team_score": 2,
215
+ "opponent_score": 0,
216
+ "result": "W"
217
+ }
218
+ ]
219
+ }
220
+ ```
221
+
222
+ `HeadToHeadSummary` returns aggregate history plus latest matches:
223
+
224
+ ```json
225
+ {
226
+ "team_a": "Mexico",
227
+ "team_b": "South Africa",
228
+ "matches": 1,
229
+ "team_a_wins": 1,
230
+ "draws": 0,
231
+ "team_b_wins": 0,
232
+ "team_a_goals": 2,
233
+ "team_b_goals": 0,
234
+ "latest_matches": []
235
+ }
236
+ ```
237
+
238
+ ### Player History
239
+
240
+ Player history depends on a source that legally provides player-level public match facts. The default OpenFootball adapter does not include player stats, but `JsonFileSource` supports detailed authorized feeds:
241
+
242
+ ```python
243
+ from openmatchkit import MatchClient
244
+ from openmatchkit.sources.json_file import JsonFileSource
245
+
246
+ client = MatchClient(sources=[JsonFileSource("tests/fixtures/sample_detailed_source.json")])
247
+ history = client.player_history("Alex Demo")
248
+ print(history.model_dump(mode="json"))
249
+ ```
250
+
251
+ ```json
252
+ {
253
+ "player": {"name": "Alex Demo", "team": "Team A", "position": "FW", "shirt_number": 9},
254
+ "totals": {
255
+ "appearances": 1,
256
+ "starts": 1,
257
+ "minutes_played": 45,
258
+ "goals": 1,
259
+ "assists": 0,
260
+ "yellow_cards": 1,
261
+ "red_cards": 0
262
+ },
263
+ "appearances": [
264
+ {
265
+ "match_id": "detail-1",
266
+ "team": "Team A",
267
+ "opponent": "Team B",
268
+ "home_away": "home",
269
+ "goals": 1,
270
+ "source": "demo_public_feed"
271
+ }
272
+ ]
273
+ }
274
+ ```
275
+
276
+ ### Prediction
277
+
278
+ ```python
279
+ history = client.results("worldcup", "2026")
280
+ prediction = client.predict(home="Mexico", away="South Africa", history=history)
281
+ ```
282
+
283
+ Returns an educational baseline only:
284
+
285
+ ```json
286
+ {
287
+ "home": "Mexico",
288
+ "away": "South Africa",
289
+ "home_win_probability": 0.45,
290
+ "draw_probability": 0.27,
291
+ "away_win_probability": 0.28,
292
+ "expected_home_goals": 1.2,
293
+ "expected_away_goals": 0.9,
294
+ "model": "simple_poisson",
295
+ "training_matches": 2,
296
+ "note": "Educational baseline only. Not betting, financial, or professional advice."
297
+ }
298
+ ```
299
+
300
+ ### Export
301
+
302
+ ```python
303
+ client.export_json(fixtures, "fixtures.json")
304
+ client.export_csv(fixtures, "fixtures.csv")
305
+ ```
306
+
307
+ JSON export keeps full nested model data. CSV export flattens match-level fields for spreadsheet tools.
308
+
309
+ ## CLI
310
+
311
+ ```bash
312
+ openmatch sources
313
+ openmatch health
314
+ openmatch fixtures --competition worldcup --season 2026
315
+ openmatch search --competition worldcup --season 2026 --team Canada --status scheduled
316
+ openmatch results --competition en.1 --season 2015-16 --format csv --output results.csv
317
+ openmatch upcoming --competition worldcup --season 2026 --team Canada --limit 3
318
+ openmatch recent --competition worldcup --season 2026 --limit 5
319
+ openmatch next-match --competition worldcup --season 2026
320
+ openmatch match-summary --match-id detail-1 --data-file tests/fixtures/sample_detailed_source.json
321
+ openmatch standings --competition worldcup --season 2026
322
+ openmatch team-form --team Mexico --competition worldcup --season 2026
323
+ openmatch head-to-head --team-a Mexico --team-b "South Africa" --competition worldcup --season 2026
324
+ openmatch predict --home Mexico --away "South Africa" --competition worldcup --season 2026
325
+ ```
326
+
327
+ Detailed JSON feeds can expose richer scoreboards and player history:
328
+
329
+ ```bash
330
+ openmatch scoreboards --data-file tests/fixtures/sample_detailed_source.json
331
+ openmatch scoreboard --match-id detail-1 --data-file tests/fixtures/sample_detailed_source.json
332
+ openmatch live-scoreboards --data-file tests/fixtures/sample_detailed_source.json
333
+ openmatch player-history --player "Alex Demo" --data-file tests/fixtures/sample_detailed_source.json
334
+ ```
335
+
336
+ ```python
337
+ from openmatchkit import MatchClient
338
+ from openmatchkit.sources.json_file import JsonFileSource
339
+
340
+ client = MatchClient(sources=[JsonFileSource("tests/fixtures/sample_detailed_source.json")])
341
+ scoreboard = client.scoreboard(match_id="detail-1")
342
+ history = client.player_history("Alex Demo")
343
+ print(scoreboard.model_dump(mode="json"))
344
+ print(history.model_dump(mode="json"))
345
+ ```
346
+
347
+ ## Example Output
348
+
349
+ ```json
350
+ {
351
+ "competition": "World Cup 2026",
352
+ "home": {"name": "Canada"},
353
+ "away": {"name": "Bosnia & Herzegovina"},
354
+ "status": "scheduled",
355
+ "source": "openfootball"
356
+ }
357
+ ```
358
+
359
+ ## Features
360
+
361
+ - Unified match, scoreboard, player history, standings, and prediction models
362
+ - OpenFootball JSON adapter
363
+ - Football-Data.co.uk CSV adapter
364
+ - Local detailed JSON adapter for authorized/public detailed feeds
365
+ - Generic optional public HTML adapter
366
+ - Safe HTTP client with robots.txt checks, caching, user agent, and per-origin delay
367
+ - Fixtures, results, search, upcoming/recent matches, next match, live score adapter interface
368
+ - Source capabilities and health metadata
369
+ - Team info, grouped standings, team form, and head-to-head summaries
370
+ - Detailed scoreboards: clock, events, lineups, team stats, and player match stats when provided
371
+ - Player histories aggregated from player-level source data
372
+ - Simple Poisson prediction baseline and small Elo rating helper
373
+ - JSON and CSV export helpers
374
+ - Offline tests using local fixtures
375
+
376
+ ## Current Status
377
+
378
+ - Stable enough for demos, research, education, and early open-source feedback
379
+ - Not official live FIFA data
380
+ - True live/player-level data requires a lawful source adapter that provides those facts
381
+ - Public roadmap: [ROADMAP.md](ROADMAP.md)
382
+ - Release notes: [CHANGELOG.md](CHANGELOG.md)
383
+
384
+ ## Source and legal policy
385
+
386
+ The package code is MIT licensed. Third-party data sources have their own licenses and terms. Optional scraping adapters must respect Terms of Service, robots.txt, caching, and rate limits. Player-level data should only come from public or authorized sources and should contain public sporting facts, not private or sensitive information.
387
+
388
+ See [DATA_SOURCES.md](DATA_SOURCES.md), [PRIVACY.md](PRIVACY.md), and [CONTRIBUTING.md](CONTRIBUTING.md).
389
+
390
+ ## Development
391
+
392
+ ```bash
393
+ python -m pip install -e ".[dev]"
394
+ ruff check .
395
+ ruff format .
396
+ pytest
397
+ python -m build
398
+ ```
399
+
400
+ The prediction output is an educational baseline only. It is not betting, financial, or professional advice.