footballdata-api 0.5.1 → 2026.7.11
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1 -2
- data/Manifest.txt +4 -1
- data/README.md +1 -1
- data/bin/fbdat +6 -1
- data/config/leagues_tier1.csv +12 -12
- data/lib/footballdata/convert-score.rb +59 -0
- data/lib/footballdata/{convert.rb → convert_csv.rb} +6 -10
- data/lib/footballdata/convert_json-stages.rb +20 -0
- data/lib/footballdata/convert_json-teams.rb +124 -0
- data/lib/footballdata/convert_json.rb +186 -0
- data/lib/footballdata/download.rb +8 -4
- data/lib/footballdata/leagues.rb +7 -1
- data/lib/footballdata/version.rb +3 -4
- data/lib/footballdata.rb +6 -1
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7dc56c0d0418eab7317862787fc928fe1102785d1768f75b242fb3ab52e140b6
|
|
4
|
+
data.tar.gz: 83fcf18d6a491b1cc9cff38a38de13cbdfe184b9400ae78678aa479b5517ff92
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c3c0a16979fc71e903180ddae103249350461d48dab182f90afc94c06b1230e0c1de624b8c1517c79f3a80bb3fadf84d3d7519a64358a96e52fc877b7df6f803
|
|
7
|
+
data.tar.gz: 04ac3d92c132dcce5277300e8ffae5b46eaa16dcede2b780574dbf8a98c6b098fab1e9e2d27231e4fd0aab81a12b8dcc9205c3065f70f79aa48135b9f49894fd
|
data/CHANGELOG.md
CHANGED
data/Manifest.txt
CHANGED
|
@@ -6,7 +6,10 @@ bin/fbdat
|
|
|
6
6
|
config/leagues_tier1.csv
|
|
7
7
|
lib/footballdata.rb
|
|
8
8
|
lib/footballdata/convert-score.rb
|
|
9
|
-
lib/footballdata/
|
|
9
|
+
lib/footballdata/convert_csv.rb
|
|
10
|
+
lib/footballdata/convert_json-stages.rb
|
|
11
|
+
lib/footballdata/convert_json-teams.rb
|
|
12
|
+
lib/footballdata/convert_json.rb
|
|
10
13
|
lib/footballdata/download.rb
|
|
11
14
|
lib/footballdata/leagues.rb
|
|
12
15
|
lib/footballdata/mods.rb
|
data/README.md
CHANGED
|
@@ -155,7 +155,7 @@ That's it for now.
|
|
|
155
155
|
## License
|
|
156
156
|
|
|
157
157
|
The `footballdata` scripts are dedicated to the public domain.
|
|
158
|
-
Use
|
|
158
|
+
Use as you please with no restrictions whatsoever.
|
|
159
159
|
|
|
160
160
|
|
|
161
161
|
## Questions? Comments?
|
data/bin/fbdat
CHANGED
|
@@ -200,6 +200,11 @@ autofiller = ->(league_query) {
|
|
|
200
200
|
datasets = if opts[:file]
|
|
201
201
|
read_leagueset( opts[:file], autofill: autofiller )
|
|
202
202
|
else
|
|
203
|
+
|
|
204
|
+
### todo or fix/fix/fix - change to build_leagueset
|
|
205
|
+
## and reserve parse_leagueset( txt ) for
|
|
206
|
+
## read_leagueset with inline text
|
|
207
|
+
|
|
203
208
|
parse_leagueset_args( args, autofill: autofiller )
|
|
204
209
|
end
|
|
205
210
|
|
|
@@ -244,6 +249,7 @@ if opts[:convert]
|
|
|
244
249
|
datasets.each do |league_key, seasons|
|
|
245
250
|
seasons.each do |season|
|
|
246
251
|
convert( league: league_key, season: season )
|
|
252
|
+
convert_json( league: league_key, season: season )
|
|
247
253
|
end
|
|
248
254
|
end
|
|
249
255
|
end
|
|
@@ -257,4 +263,3 @@ Footballdata.main( ARGV )
|
|
|
257
263
|
|
|
258
264
|
|
|
259
265
|
puts "bye"
|
|
260
|
-
|
data/config/leagues_tier1.csv
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
1
|
key, code, seasons
|
|
2
2
|
|
|
3
|
-
eng.1, PL, 2024/25 2023/24 2022/23 2021/22 2020/21
|
|
3
|
+
eng.1, PL, 2026/27 2025/26 2024/25 2023/24 2022/23 2021/22 2020/21
|
|
4
4
|
# incl. team(s) from wales
|
|
5
|
-
eng.2, ELC, 2024/25 2023/24 2022/23 2021/22 2020/21
|
|
5
|
+
eng.2, ELC, 2026/27 2025/26 2024/25 2023/24 2022/23 2021/22 2020/21
|
|
6
6
|
# PL - Premier League, England 27 seasons | 2019-08-09 - 2020-07-25 / matchday 31
|
|
7
7
|
# ELC - Championship, England 3 seasons | 2019-08-02 - 2020-07-22 / matchday 38
|
|
8
8
|
|
|
9
|
-
es.1, PD, 2024/25 2023/24 2022/23 2021/22 2020/21
|
|
9
|
+
es.1, PD, 2026/27 2025/26 2024/25 2023/24 2022/23 2021/22 2020/21
|
|
10
10
|
# PD - Primera Division, Spain 27 seasons | 2019-08-16 - 2020-07-19 / matchday 31
|
|
11
11
|
|
|
12
|
-
pt.1, PPL, 2024/25 2023/24 2022/23 2021/22 2020/21
|
|
12
|
+
pt.1, PPL, 2026/27 2025/26 2024/25 2023/24 2022/23 2021/22 2020/21
|
|
13
13
|
# PPL - Primeira Liga, Portugal 9 seasons | 2019-08-10 - 2020-07-26 / matchday 28
|
|
14
14
|
|
|
15
|
-
de.1, BL1, 2024/25 2023/24 2022/23 2021/22 2020/21
|
|
15
|
+
de.1, BL1, 2026/27 2025/26 2024/25 2023/24 2022/23 2021/22 2020/21
|
|
16
16
|
# BL1 - Bundesliga, Germany 24 seasons | 2019-08-16 - 2020-06-27 / matchday 34
|
|
17
17
|
|
|
18
|
-
nl.1, DED, 2024/25 2023/24 2022/23 2021/22 2020/21
|
|
18
|
+
nl.1, DED, 2026/27 2025/26 2024/25 2023/24 2022/23 2021/22 2020/21
|
|
19
19
|
# DED - Eredivisie, Netherlands 10 seasons | 2019-08-09 - 2020-03-08 / matchday 34
|
|
20
20
|
|
|
21
|
-
fr.1, FL1, 2024/25 2023/24 2022/23 2021/22 2020/21
|
|
21
|
+
fr.1, FL1, 2026/27 2025/26 2024/25 2023/24 2022/23 2021/22 2020/21
|
|
22
22
|
# incl. team(s) monaco
|
|
23
23
|
|
|
24
|
-
it.1, SA, 2024/25 2023/24 2022/23 2021/22 2020/21
|
|
24
|
+
it.1, SA, 2026/27 2025/26 2024/25 2023/24 2022/23 2021/22 2020/21
|
|
25
25
|
# SA - Serie A, Italy 15 seasons | 2019-08-24 - 2020-08-02 / matchday 27
|
|
26
26
|
|
|
27
|
-
br.1, BSA, 2025 2024 2023 2022 2021 2020
|
|
27
|
+
br.1, BSA, 2026 2025 2024 2023 2022 2021 2020
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
########
|
|
31
31
|
## int'l cups
|
|
32
|
-
uefa.cl, CL, 2024/25 2023/24 2022/23 2021/22 2020/21
|
|
32
|
+
uefa.cl, CL, 2025/26 2024/25 2023/24 2022/23 2021/22 2020/21
|
|
33
33
|
## note: cl is country code for chile!! - use champs - why? why not?
|
|
34
34
|
## was europe.cl / cl
|
|
35
35
|
## todo/check: use champs and NOT cl - why? why not?
|
|
36
36
|
|
|
37
|
-
copa.l, CLI, 2025 2024 2023 2022 2021
|
|
37
|
+
copa.l, CLI, 2026 2025 2024 2023 2022 2021
|
|
38
38
|
## Copa Libertadores
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
############
|
|
42
42
|
## national teams
|
|
43
43
|
euro, EC, 2024 2021
|
|
44
|
-
world, WC, 2022
|
|
44
|
+
world, WC, 2026 2022
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
|
|
2
2
|
module Footballdata
|
|
3
3
|
|
|
4
|
+
##
|
|
5
|
+
## note - "weirdo" rule
|
|
6
|
+
## fullTime is only "classic" fulltime if REGULAR
|
|
7
|
+
##
|
|
8
|
+
## otherwise fullTime is aggregate of extraTime AND penalties!!
|
|
9
|
+
##
|
|
10
|
+
##
|
|
11
|
+
|
|
12
|
+
|
|
4
13
|
def self.convert_score( score )
|
|
5
14
|
## duration: REGULAR · PENALTY_SHOOTOUT · EXTRA_TIME
|
|
6
15
|
ft, ht, et, pen = ["","","",""]
|
|
@@ -41,4 +50,54 @@ def self.convert_score( score )
|
|
|
41
50
|
|
|
42
51
|
[ft,ht,et,pen]
|
|
43
52
|
end
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def self.convert_score_to_hash( score )
|
|
57
|
+
## duration: REGULAR · PENALTY_SHOOTOUT · EXTRA_TIME
|
|
58
|
+
|
|
59
|
+
if score['duration'] == 'REGULAR'
|
|
60
|
+
{ ft: [score['fullTime']['home'],
|
|
61
|
+
score['fullTime']['away']],
|
|
62
|
+
ht: [score['halfTime']['home'],
|
|
63
|
+
score['halfTime']['away']]
|
|
64
|
+
}
|
|
65
|
+
elsif score['duration'] == 'EXTRA_TIME'
|
|
66
|
+
{ et: [score['regularTime']['home']+score['extraTime']['home'],
|
|
67
|
+
score['regularTime']['away']+score['extraTime']['away']],
|
|
68
|
+
ft: [score['regularTime']['home'],
|
|
69
|
+
score['regularTime']['away']],
|
|
70
|
+
ht: [score['halfTime']['home'],
|
|
71
|
+
score['halfTime']['away']]
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
elsif score['duration'] == 'PENALTY_SHOOTOUT'
|
|
75
|
+
if score['extraTime']
|
|
76
|
+
## quick & dirty hack - calc et via regulartime+extratime
|
|
77
|
+
{ pen: [score['penalties']['home'],
|
|
78
|
+
score['penalties']['away']],
|
|
79
|
+
et: [score['regularTime']['home']+score['extraTime']['home'],
|
|
80
|
+
score['regularTime']['away']+score['extraTime']['away']],
|
|
81
|
+
ft: [score['regularTime']['home'],
|
|
82
|
+
score['regularTime']['away']],
|
|
83
|
+
ht: [score['halfTime']['home'],
|
|
84
|
+
score['halfTime']['away']]
|
|
85
|
+
}
|
|
86
|
+
else ### south american-style (no extra time)
|
|
87
|
+
## quick & dirty hacke - calc ft via fullTime-penalties
|
|
88
|
+
{ pen: [score['penalties']['home'],
|
|
89
|
+
score['penalties']['away']],
|
|
90
|
+
ft: [score['fullTime']['home']-score['penalties']['home'],
|
|
91
|
+
score['fullTime']['away']-score['penalties']['away']],
|
|
92
|
+
ht: [score['halfTime']['home'],
|
|
93
|
+
score['halfTime']['away']]
|
|
94
|
+
}
|
|
95
|
+
end
|
|
96
|
+
else
|
|
97
|
+
raise ArgumentError, "!! unknown score duration - #{score.inspect}"
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
|
|
44
103
|
end # module Footballdata
|
|
@@ -26,8 +26,10 @@ STAGES = {
|
|
|
26
26
|
|
|
27
27
|
'LEAGUE_STAGE' => ['League'],
|
|
28
28
|
'GROUP_STAGE' => ['Group'],
|
|
29
|
-
'PLAYOFFS' => ['Playoffs'],
|
|
29
|
+
'PLAYOFFS' => ['Playoffs'], ## used in champs
|
|
30
|
+
'PLAY_OFFS' => ['Playoffs'], ## used in copa liber.
|
|
30
31
|
|
|
32
|
+
'LAST_32' => ['Finals', 'Round of 32'],
|
|
31
33
|
'ROUND_OF_16' => ['Finals', 'Round of 16'],
|
|
32
34
|
'LAST_16' => ['Finals', 'Round of 16'], ## use Last 16 - why? why not?
|
|
33
35
|
'QUARTER_FINALS' => ['Finals', 'Quarterfinals'],
|
|
@@ -67,7 +69,7 @@ def self.convert( league:, season: )
|
|
|
67
69
|
|
|
68
70
|
season = Season( season ) ## cast (ensure) season class (NOT string, integer, etc.)
|
|
69
71
|
|
|
70
|
-
### note - find_league returns the metal_league_code
|
|
72
|
+
### note - find_league returns the metal_league_code
|
|
71
73
|
league_code = find_league!( league )
|
|
72
74
|
|
|
73
75
|
matches_url = Metal.competition_matches_url( league_code, season.start_year )
|
|
@@ -132,12 +134,6 @@ matches.each do |m|
|
|
|
132
134
|
team1 = m['homeTeam']['name'] || 'N.N.'
|
|
133
135
|
team2 = m['awayTeam']['name'] || 'N.N.'
|
|
134
136
|
|
|
135
|
-
## auto-fix copa.l 2024
|
|
136
|
-
## !! ERROR: unsupported match status >IN_PLAY< - sorry:
|
|
137
|
-
if m['status'] == 'IN_PLAY' &&
|
|
138
|
-
team1 == 'Club Aurora' && team2 == 'FBC Melgar'
|
|
139
|
-
m['status'] = 'FINISHED'
|
|
140
|
-
end
|
|
141
137
|
|
|
142
138
|
|
|
143
139
|
|
|
@@ -185,6 +181,7 @@ matches.each do |m|
|
|
|
185
181
|
matchday_num = m['matchday']
|
|
186
182
|
matchday_num = nil if matchday_num == 0 ## change 0 to nil (empty) too
|
|
187
183
|
|
|
184
|
+
|
|
188
185
|
if stage_round.nil? ## e.g. Regular, League, Group, Playoffs
|
|
189
186
|
## keep/assume matchday number is matchday .e.g
|
|
190
187
|
## matchday 1, 2 etc.
|
|
@@ -206,6 +203,7 @@ matches.each do |m|
|
|
|
206
203
|
team2 = mods[ team2 ] if mods[ team2 ]
|
|
207
204
|
end
|
|
208
205
|
|
|
206
|
+
|
|
209
207
|
####
|
|
210
208
|
# auto-add (fifa) country code if int'l club tournament
|
|
211
209
|
if clubs_intl
|
|
@@ -513,5 +511,3 @@ def self.vacuum( rows, headers: MAX_HEADERS, fixed_headers: MIN_HEADERS )
|
|
|
513
511
|
[rows, headers]
|
|
514
512
|
end
|
|
515
513
|
end # module Footballdata
|
|
516
|
-
|
|
517
|
-
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
class Stages
|
|
3
|
+
def initialize
|
|
4
|
+
@recs = {}
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def add_matches( matches )
|
|
8
|
+
matches.each do |m|
|
|
9
|
+
name = m['stage']
|
|
10
|
+
rec = @recs[name] ||= { name: name,
|
|
11
|
+
count: 0 }
|
|
12
|
+
rec[:count] +=1
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def as_json() @recs.values; end
|
|
17
|
+
|
|
18
|
+
def size() @recs.size; end
|
|
19
|
+
|
|
20
|
+
end # class Stages
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
|
|
2
|
+
=begin
|
|
3
|
+
|
|
4
|
+
note - for address "null null null",
|
|
5
|
+
is street + city + postal_code/
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
{"area"=>{"id"=>2061, "name"=>"Cyprus", "code"=>"CYP", "flag"=>nil},
|
|
9
|
+
"id"=>11034,
|
|
10
|
+
"name"=>"Paphos FC",
|
|
11
|
+
"shortName"=>"Paphos FC",
|
|
12
|
+
"tla"=>"AEP",
|
|
13
|
+
"address"=>"null null null",
|
|
14
|
+
"website"=>nil,
|
|
15
|
+
"founded"=>nil,
|
|
16
|
+
"clubColors"=>nil,
|
|
17
|
+
"venue"=>"Alphamega Stadium",
|
|
18
|
+
|
|
19
|
+
{"area"=>{"id"=>2119, "name"=>"Kazakhstan", "code"=>"KAZ", "flag"=>nil},
|
|
20
|
+
"id"=>10601,
|
|
21
|
+
"name"=>"FK Kairat",
|
|
22
|
+
"shortName"=>"FK Kairat",
|
|
23
|
+
"tla"=>"KAI",
|
|
24
|
+
"address"=>"null null null",
|
|
25
|
+
"website"=>nil,
|
|
26
|
+
"founded"=>nil,
|
|
27
|
+
"clubColors"=>nil,
|
|
28
|
+
"venue"=>nil,
|
|
29
|
+
|
|
30
|
+
{"area"=>{"id"=>2060, "name"=>"Curaçao", "code"=>"CUW", "flag"=>"https://crests.football-data.org/curacao.svg"},
|
|
31
|
+
"id"=>9460,
|
|
32
|
+
"name"=>"Curaçao",
|
|
33
|
+
"shortName"=>"Curaçao",
|
|
34
|
+
"tla"=>"CUW",
|
|
35
|
+
"address"=>"null null null",
|
|
36
|
+
"website"=>nil,
|
|
37
|
+
"founded"=>nil,
|
|
38
|
+
"clubColors"=>nil,
|
|
39
|
+
"venue"=>nil,
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
=end
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def build_team( h )
|
|
48
|
+
|
|
49
|
+
rec = { id: h['id'],
|
|
50
|
+
name: h['name'],
|
|
51
|
+
short_name: h['shortName'],
|
|
52
|
+
code: h['tla'],
|
|
53
|
+
address: h['address'], ### street / city / postal_code
|
|
54
|
+
founded: h['founded'],
|
|
55
|
+
ground: h['venue'], ### use stadium or venue ??
|
|
56
|
+
|
|
57
|
+
##
|
|
58
|
+
## or use country { code:, name }
|
|
59
|
+
## change to cc (country code) - why? why not?
|
|
60
|
+
country: { code: h['area']['code'],
|
|
61
|
+
name: h['area']['name'] },
|
|
62
|
+
|
|
63
|
+
count: 0, ## track - match counts
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
rec
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class Teams
|
|
71
|
+
def initialize
|
|
72
|
+
@recs = []
|
|
73
|
+
@by_name = {}
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def add( recs )
|
|
78
|
+
recs.each do |h|
|
|
79
|
+
rec = build_team( h )
|
|
80
|
+
@recs << rec
|
|
81
|
+
|
|
82
|
+
## todo/fix - make sure team name is uniq!!!!
|
|
83
|
+
@by_name[rec[:name]] = rec
|
|
84
|
+
@by_name[rec[:short_name]] = rec
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def find_by!( name: )
|
|
91
|
+
rec = @by_name[ name ]
|
|
92
|
+
raise ArgumentError, "team >#{name}< not found; sorry" if rec.nil?
|
|
93
|
+
rec
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def add_matches( matches ) ## use/rename to add_matches - why? why not?
|
|
98
|
+
matches.each do |m|
|
|
99
|
+
|
|
100
|
+
## note - skip teams without name (e.g. N.N.)
|
|
101
|
+
if m['homeTeam']['name']
|
|
102
|
+
rec = find_by!( name: m['homeTeam']['name'] )
|
|
103
|
+
rec[:count] += 1
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
if m['awayTeam']['name']
|
|
107
|
+
rec = find_by!( name: m['awayTeam']['name'] )
|
|
108
|
+
rec[:count] += 1
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def as_json( id: false )
|
|
115
|
+
if id
|
|
116
|
+
@recs
|
|
117
|
+
else
|
|
118
|
+
@recs.map { |rec| rec.except(:id ) }
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def size() @recs.size; end
|
|
123
|
+
|
|
124
|
+
end # class Teams
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
|
|
2
|
+
module Footballdata
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def self.convert_json( league:, season: )
|
|
9
|
+
|
|
10
|
+
season = Season( season ) ## cast (ensure) season class (NOT string, integer, etc.)
|
|
11
|
+
|
|
12
|
+
### note - find_league returns the metal_league_code
|
|
13
|
+
league_code = find_league!( league )
|
|
14
|
+
|
|
15
|
+
matches_url = Metal.competition_matches_url( league_code, season.start_year )
|
|
16
|
+
teams_url = Metal.competition_teams_url( league_code, season.start_year )
|
|
17
|
+
|
|
18
|
+
data = Webcache.read_json( matches_url )
|
|
19
|
+
data_teams = Webcache.read_json( teams_url )
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
## note - for international club tournaments
|
|
23
|
+
## auto-add (fifa) country code e.g.
|
|
24
|
+
## Liverpool FC => Liverpool FC (ENG)
|
|
25
|
+
##
|
|
26
|
+
## todo/fix - move flag to league_info via .csv config - why? why not?
|
|
27
|
+
## clubs_intl = ['uefa.cl',
|
|
28
|
+
## 'copa.l'].include?(league.downcase) ? true : false
|
|
29
|
+
|
|
30
|
+
## build a team lookup by name (& short_name)
|
|
31
|
+
|
|
32
|
+
teams = Teams.new
|
|
33
|
+
teams.add( data_teams['teams'] )
|
|
34
|
+
puts "#{teams.size} team(s)"
|
|
35
|
+
|
|
36
|
+
## add/update match counts
|
|
37
|
+
teams.add_matches( data['matches'] )
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
stages = Stages.new
|
|
41
|
+
stages.add_matches( data['matches'] )
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
recs = []
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
## track (meta stats) stage, match status et
|
|
48
|
+
stats = { status: Hash.new(0), ## e.g. TIMED,etc
|
|
49
|
+
score: Hash.new(0), ## e.g. REGULAR,etc.
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
data[ 'matches'].each do |m|
|
|
56
|
+
|
|
57
|
+
## use ? or N.N. or ? for nil - why? why not?
|
|
58
|
+
team1_name = m['homeTeam']['name']
|
|
59
|
+
team2_name = m['awayTeam']['name']
|
|
60
|
+
|
|
61
|
+
team1 = team1_name ? teams.find_by!(name: team1_name ) : { name: 'N.N.' }
|
|
62
|
+
team2 = team2_name ? teams.find_by!(name: team2_name ) : { name: 'N.N.' }
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
stage = m['stage']
|
|
66
|
+
group = m['group']
|
|
67
|
+
|
|
68
|
+
matchday = m['matchday']
|
|
69
|
+
matchday = nil if matchday == 0 ## change 0 to nil (empty) too
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
stats[:status][m['status']] += 1 ## track status counts
|
|
73
|
+
stats[:score][m['score']['duration']] += 1
|
|
74
|
+
|
|
75
|
+
case m['status']
|
|
76
|
+
when 'SCHEDULED', 'TIMED',
|
|
77
|
+
'PAUSED', 'IN_PLAY' ## IN_PLAY, PAUSED
|
|
78
|
+
score = []
|
|
79
|
+
when 'FINISHED'
|
|
80
|
+
score = convert_score_to_hash( m['score'] )
|
|
81
|
+
when 'AWARDED' # AWARDED
|
|
82
|
+
assert( m['score']['duration'] == 'REGULAR', 'score.duration REGULAR expected' )
|
|
83
|
+
score = [m['score']['fullTime']['home'],
|
|
84
|
+
m['score']['fullTime']['away']]
|
|
85
|
+
when 'CANCELLED'
|
|
86
|
+
## note cancelled might have scores!! -- add/fix later!!!
|
|
87
|
+
## ht only or ft+ht!!! (see fr 2021/22)
|
|
88
|
+
## score = convert_score_to_hash( m['score'] )
|
|
89
|
+
##
|
|
90
|
+
## note - there is cancelled (e.g. not/played) AND
|
|
91
|
+
## annulled/voided (e.g. played!! with score)
|
|
92
|
+
score = []
|
|
93
|
+
when 'POSTPONED'
|
|
94
|
+
score = []
|
|
95
|
+
else
|
|
96
|
+
raise ArgumentError,
|
|
97
|
+
"unsupported match status >#{m['status']}< - sorry: #{m.pretty_inspect}"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
rec = {
|
|
103
|
+
status: m['status'],
|
|
104
|
+
stage: stage
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
utc = UTC.strptime( m['utcDate'], '%Y-%m-%dT%H:%M:%SZ' )
|
|
109
|
+
assert( utc.strftime( '%Y-%m-%dT%H:%M:%SZ' ) == m['utcDate'], 'utc time mismatch' )
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
## do NOT add time if status is SCHEDULED
|
|
113
|
+
## or POSTPONED for now
|
|
114
|
+
## otherwise assume time always present - why? why not?
|
|
115
|
+
##
|
|
116
|
+
## assume NOT valid utc time if 00:00
|
|
117
|
+
if utc.hour == 0 && utc.min == 0 &&
|
|
118
|
+
['SCHEDULED','POSTPONED'].include?( m['status'] )
|
|
119
|
+
## assume local date - why? why not?
|
|
120
|
+
rec[:date] = utc.strftime( '%Y-%m-%d' )
|
|
121
|
+
else
|
|
122
|
+
rec[:date_utc] = utc.strftime( '%Y-%m-%dT%H:%M:%SZ' )
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
rec[:group] = group if group
|
|
128
|
+
rec[:matchday] = matchday if matchday
|
|
129
|
+
|
|
130
|
+
rec[:team1] = team1[:name]
|
|
131
|
+
rec[:team2] = team2[:name]
|
|
132
|
+
|
|
133
|
+
rec[:score] = score unless score.empty?
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
recs << rec
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
stats[:matches] = recs.size
|
|
141
|
+
stats[:teams] = teams.size
|
|
142
|
+
|
|
143
|
+
data = {
|
|
144
|
+
slug: league,
|
|
145
|
+
season: season.to_s,
|
|
146
|
+
meta: stats,
|
|
147
|
+
stages: stages.as_json,
|
|
148
|
+
teams: teams.as_json,
|
|
149
|
+
matches: recs
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
path = "#{config.convert.out_dir}/#{season.to_path}/#{league.downcase}.json"
|
|
154
|
+
## note: change season_key from 2019/20 to 2019-20 (for path/directory!!!!)
|
|
155
|
+
puts " writing to >#{path}<"
|
|
156
|
+
write_json( path, data )
|
|
157
|
+
end # method convert
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
end # module Footballdata
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
__END__
|
|
165
|
+
|
|
166
|
+
## note: get season from first match
|
|
167
|
+
## assert - all other matches include the same season
|
|
168
|
+
## e.g.
|
|
169
|
+
# "season": {
|
|
170
|
+
# "id": 154,
|
|
171
|
+
# "startDate": "2018-08-03",
|
|
172
|
+
# "endDate": "2019-05-05",
|
|
173
|
+
# "currentMatchday": 46
|
|
174
|
+
# }
|
|
175
|
+
|
|
176
|
+
start_date = Date.strptime( matches[0]['season']['startDate'], '%Y-%m-%d' )
|
|
177
|
+
end_date = Date.strptime( matches[0]['season']['endDate'], '%Y-%m-%d' )
|
|
178
|
+
|
|
179
|
+
dates = "#{start_date.strftime('%b %-d')} - #{end_date.strftime('%b %-d')}"
|
|
180
|
+
|
|
181
|
+
buf = ''
|
|
182
|
+
buf << "#{season.key} (#{dates}) - "
|
|
183
|
+
buf << "#{teams.keys.size} teams, "
|
|
184
|
+
buf << "#{recs.size} matches"
|
|
185
|
+
# buf << "#{stat[:regular_season][:goals]} goals"
|
|
186
|
+
buf << "\n"
|
|
@@ -53,6 +53,7 @@ class Metal
|
|
|
53
53
|
|
|
54
54
|
request_headers = request_headers.merge( headers ) unless headers.empty?
|
|
55
55
|
|
|
56
|
+
## pp request_headers
|
|
56
57
|
|
|
57
58
|
## note: add format: 'json' for pretty printing json (before) save in cache
|
|
58
59
|
response = Webget.call( url, headers: request_headers )
|
|
@@ -66,8 +67,10 @@ class Metal
|
|
|
66
67
|
end
|
|
67
68
|
|
|
68
69
|
|
|
70
|
+
## note - starting in 2026 requires https !!! (plain http will not work)
|
|
71
|
+
## resutls in -- 301 Moved Permanently
|
|
69
72
|
|
|
70
|
-
BASE_URL = '
|
|
73
|
+
BASE_URL = 'https://api.football-data.org/v4'
|
|
71
74
|
|
|
72
75
|
|
|
73
76
|
def self.competitions_url
|
|
@@ -115,10 +118,12 @@ class Metal
|
|
|
115
118
|
get( "#{BASE_URL}/teams/#{id}" )
|
|
116
119
|
end
|
|
117
120
|
|
|
118
|
-
|
|
119
|
-
|
|
121
|
+
|
|
122
|
+
def self.match( id, headers: {} )
|
|
123
|
+
get( "#{BASE_URL}/matches/#{id}", headers: headers )
|
|
120
124
|
end
|
|
121
125
|
|
|
126
|
+
|
|
122
127
|
def self.person( id )
|
|
123
128
|
get( "#{BASE_URL}/persons/#{id}" )
|
|
124
129
|
end
|
|
@@ -128,4 +133,3 @@ class Metal
|
|
|
128
133
|
end
|
|
129
134
|
end # class Metal
|
|
130
135
|
end # module Footballdata
|
|
131
|
-
|
data/lib/footballdata/leagues.rb
CHANGED
data/lib/footballdata/version.rb
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
|
|
2
2
|
module FootballdataApi
|
|
3
|
-
MAJOR =
|
|
4
|
-
MINOR =
|
|
5
|
-
PATCH =
|
|
3
|
+
MAJOR = 2026 ## todo: namespace inside version or something - why? why not??
|
|
4
|
+
MINOR = 7
|
|
5
|
+
PATCH = 11
|
|
6
6
|
VERSION = [MAJOR,MINOR,PATCH].join('.')
|
|
7
7
|
|
|
8
8
|
def self.version
|
|
@@ -17,4 +17,3 @@ module FootballdataApi
|
|
|
17
17
|
File.expand_path( File.dirname(File.dirname(File.dirname(__FILE__))) )
|
|
18
18
|
end
|
|
19
19
|
end # module FootballdataApi
|
|
20
|
-
|
data/lib/footballdata.rb
CHANGED
|
@@ -36,8 +36,13 @@ require_relative 'footballdata/download'
|
|
|
36
36
|
require_relative 'footballdata/prettyprint'
|
|
37
37
|
|
|
38
38
|
require_relative 'footballdata/mods'
|
|
39
|
-
require_relative 'footballdata/
|
|
39
|
+
require_relative 'footballdata/convert_csv'
|
|
40
|
+
require_relative 'footballdata/convert_json'
|
|
41
|
+
require_relative 'footballdata/convert_json-stages'
|
|
42
|
+
require_relative 'footballdata/convert_json-teams'
|
|
40
43
|
require_relative 'footballdata/convert-score'
|
|
44
|
+
|
|
45
|
+
|
|
41
46
|
require_relative 'footballdata/teams'
|
|
42
47
|
|
|
43
48
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: footballdata-api
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2026.7.11
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gerald Bauer
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-07-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: leagues
|
|
@@ -105,7 +105,10 @@ files:
|
|
|
105
105
|
- config/leagues_tier1.csv
|
|
106
106
|
- lib/footballdata.rb
|
|
107
107
|
- lib/footballdata/convert-score.rb
|
|
108
|
-
- lib/footballdata/
|
|
108
|
+
- lib/footballdata/convert_csv.rb
|
|
109
|
+
- lib/footballdata/convert_json-stages.rb
|
|
110
|
+
- lib/footballdata/convert_json-teams.rb
|
|
111
|
+
- lib/footballdata/convert_json.rb
|
|
109
112
|
- lib/footballdata/download.rb
|
|
110
113
|
- lib/footballdata/leagues.rb
|
|
111
114
|
- lib/footballdata/mods.rb
|