footballdata-api 2026.7.2 → 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 -1
- data/Manifest.txt +4 -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 +5 -2
- data/lib/footballdata/leagues.rb +7 -1
- data/lib/footballdata/version.rb +1 -1
- 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/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 )
|
|
@@ -117,10 +118,12 @@ class Metal
|
|
|
117
118
|
get( "#{BASE_URL}/teams/#{id}" )
|
|
118
119
|
end
|
|
119
120
|
|
|
120
|
-
|
|
121
|
-
|
|
121
|
+
|
|
122
|
+
def self.match( id, headers: {} )
|
|
123
|
+
get( "#{BASE_URL}/matches/#{id}", headers: headers )
|
|
122
124
|
end
|
|
123
125
|
|
|
126
|
+
|
|
124
127
|
def self.person( id )
|
|
125
128
|
get( "#{BASE_URL}/persons/#{id}" )
|
|
126
129
|
end
|
data/lib/footballdata/leagues.rb
CHANGED
data/lib/footballdata/version.rb
CHANGED
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: 2026.7.
|
|
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: 2026-07-
|
|
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
|