footballdata-api 0.2.0 → 0.3.1
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 +3 -1
- data/Rakefile +3 -3
- data/bin/fbdat +56 -34
- data/config/leagues.csv +54 -0
- data/config/timezones.csv +27 -0
- data/lib/footballdata/convert.rb +263 -100
- data/lib/footballdata/download.rb +11 -11
- data/lib/footballdata/leagues.rb +20 -55
- data/lib/footballdata/mods.rb +2 -0
- data/lib/footballdata/prettyprint.rb +29 -47
- data/lib/footballdata/teams.rb +13 -14
- data/lib/footballdata/timezones.rb +97 -0
- data/lib/footballdata/version.rb +2 -2
- data/lib/footballdata.rb +3 -8
- metadata +6 -4
- data/lib/footballdata/generator.rb +0 -33
data/lib/footballdata/convert.rb
CHANGED
@@ -1,32 +1,81 @@
|
|
1
1
|
|
2
|
+
module Footballdata
|
2
3
|
|
3
4
|
|
4
|
-
|
5
|
+
def self.convert_score( score )
|
6
|
+
## duration: REGULAR · PENALTY_SHOOTOUT · EXTRA_TIME
|
7
|
+
ft, ht, et, pen = ["","","",""]
|
8
|
+
|
9
|
+
if score['duration'] == 'REGULAR'
|
10
|
+
ft = "#{score['fullTime']['home']}-#{score['fullTime']['away']}"
|
11
|
+
ht = "#{score['halfTime']['home']}-#{score['halfTime']['away']}"
|
12
|
+
elsif score['duration'] == 'EXTRA_TIME'
|
13
|
+
et = "#{score['regularTime']['home']+score['extraTime']['home']}"
|
14
|
+
et << "-"
|
15
|
+
et << "#{score['regularTime']['away']+score['extraTime']['away']}"
|
16
|
+
|
17
|
+
ft = "#{score['regularTime']['home']}-#{score['regularTime']['away']}"
|
18
|
+
ht = "#{score['halfTime']['home']}-#{score['halfTime']['away']}"
|
19
|
+
elsif score['duration'] == 'PENALTY_SHOOTOUT'
|
20
|
+
if score['extraTime']
|
21
|
+
## quick & dirty hack - calc et via regulartime+extratime
|
22
|
+
pen = "#{score['penalties']['home']}-#{score['penalties']['away']}"
|
23
|
+
et = "#{score['regularTime']['home']+score['extraTime']['home']}"
|
24
|
+
et << "-"
|
25
|
+
et << "#{score['regularTime']['away']+score['extraTime']['away']}"
|
26
|
+
|
27
|
+
ft = "#{score['regularTime']['home']}-#{score['regularTime']['away']}"
|
28
|
+
ht = "#{score['halfTime']['home']}-#{score['halfTime']['away']}"
|
29
|
+
else ### south american-style (no extra time)
|
30
|
+
## quick & dirty hacke - calc ft via fullTime-penalties
|
31
|
+
pen = "#{score['penalties']['home']}-#{score['penalties']['away']}"
|
32
|
+
ft = "#{score['fullTime']['home']-score['penalties']['home']}"
|
33
|
+
ft << "-"
|
34
|
+
ft << "#{score['fullTime']['away']-score['penalties']['away']}"
|
35
|
+
ht = "#{score['halfTime']['home']}-#{score['halfTime']['away']}"
|
36
|
+
end
|
37
|
+
else
|
38
|
+
puts "!! unknown score duration:"
|
39
|
+
pp score
|
40
|
+
exit 1
|
41
|
+
end
|
42
|
+
|
43
|
+
[ft,ht,et,pen]
|
44
|
+
end
|
45
|
+
|
5
46
|
|
47
|
+
#######
|
48
|
+
## map round-like to higher-level stages
|
49
|
+
STAGES = {
|
50
|
+
'REGULAR_SEASON' => ['Regular'],
|
6
51
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
52
|
+
'PRELIMINARY_ROUND' => ['Qualifying', 'Preliminary Round' ],
|
53
|
+
'PRELIMINARY_SEMI_FINALS' => ['Qualifying', 'Preliminary Semifinals' ],
|
54
|
+
'PRELIMINARY_FINAL' => ['Qualifying', 'Preliminary Final' ],
|
55
|
+
'1ST_QUALIFYING_ROUND' => ['Qualifying', 'Qual. Round 1' ],
|
56
|
+
'2ND_QUALIFYING_ROUND' => ['Qualifying', 'Qual. Round 2' ],
|
57
|
+
'3RD_QUALIFYING_ROUND' => ['Qualifying', 'Qual. Round 3' ],
|
58
|
+
'QUALIFICATION_ROUND_1' => ['Qualifying', 'Qual. Round 1' ],
|
59
|
+
'QUALIFICATION_ROUND_2' => ['Qualifying', 'Qual. Round 2' ],
|
60
|
+
'QUALIFICATION_ROUND_3' => ['Qualifying', 'Qual. Round 3' ],
|
61
|
+
'ROUND_1' => ['Qualifying', 'Round 1'], ## use Qual. Round 1 - why? why not?
|
62
|
+
'ROUND_2' => ['Qualifying', 'Round 2'],
|
63
|
+
'ROUND_3' => ['Qualifying', 'Round 3'],
|
64
|
+
'PLAY_OFF_ROUND' => ['Qualifying', 'Playoff Round'],
|
65
|
+
'PLAYOFF_ROUND_1' => ['Qualifying', 'Playoff Round 1'],
|
66
|
+
|
67
|
+
'LEAGUE_STAGE' => ['League'],
|
68
|
+
'GROUP_STAGE' => ['Group'],
|
69
|
+
'PLAYOFFS' => ['Playoffs'],
|
70
|
+
|
71
|
+
'ROUND_OF_16' => ['Finals', 'Round of 16'],
|
72
|
+
'LAST_16' => ['Finals', 'Round of 16'], ## use Last 16 - why? why not?
|
73
|
+
'QUARTER_FINALS' => ['Finals', 'Quarterfinals'],
|
74
|
+
'SEMI_FINALS' => ['Finals', 'Semifinals'],
|
75
|
+
'FINAL' => ['Finals', 'Final'],
|
76
|
+
}
|
19
77
|
|
20
|
-
## todo/fix - pt.1
|
21
|
-
## one team in madeira!!! check for different timezone??
|
22
|
-
## CD Nacional da Madeira
|
23
78
|
|
24
|
-
'br.1' => 'America/Sao_Paulo',
|
25
|
-
## todo/fix - brazil has 4 timezones
|
26
|
-
## really only two in use for clubs
|
27
|
-
## west and east (amazonas et al)
|
28
|
-
## for now use west for all - why? why not?
|
29
|
-
}
|
30
79
|
|
31
80
|
|
32
81
|
def self.convert( league:, season: )
|
@@ -40,7 +89,7 @@ def self.convert( league:, season: )
|
|
40
89
|
|
41
90
|
season = Season( season ) ## cast (ensure) season class (NOT string, integer, etc.)
|
42
91
|
|
43
|
-
league_code =
|
92
|
+
league_code = find_league!( league )
|
44
93
|
|
45
94
|
matches_url = Metal.competition_matches_url( league_code, season.start_year )
|
46
95
|
teams_url = Metal.competition_teams_url( league_code, season.start_year )
|
@@ -48,15 +97,11 @@ def self.convert( league:, season: )
|
|
48
97
|
data = Webcache.read_json( matches_url )
|
49
98
|
data_teams = Webcache.read_json( teams_url )
|
50
99
|
|
51
|
-
|
100
|
+
|
101
|
+
|
52
102
|
## check for time zone
|
53
|
-
|
54
|
-
|
55
|
-
puts "!! ERROR - sorry no timezone configured for league #{league}"
|
56
|
-
exit 1
|
57
|
-
end
|
58
|
-
|
59
|
-
tz = TZInfo::Timezone.get( tz_name )
|
103
|
+
tz = find_zone!( league: league,
|
104
|
+
season: season )
|
60
105
|
pp tz
|
61
106
|
|
62
107
|
## build a (reverse) team lookup by name
|
@@ -67,9 +112,9 @@ def self.convert( league:, season: )
|
|
67
112
|
h
|
68
113
|
end
|
69
114
|
|
70
|
-
pp teams_by_name.keys
|
115
|
+
## pp teams_by_name.keys
|
116
|
+
|
71
117
|
|
72
|
-
|
73
118
|
|
74
119
|
mods = MODS[ league.downcase ] || {}
|
75
120
|
|
@@ -81,22 +126,62 @@ teams = Hash.new( 0 )
|
|
81
126
|
|
82
127
|
# stat = Stat.new
|
83
128
|
|
84
|
-
|
85
|
-
|
129
|
+
## track stage, match status et
|
130
|
+
stats = { 'status' => Hash.new(0),
|
131
|
+
'stage' => Hash.new(0),
|
132
|
+
}
|
133
|
+
|
134
|
+
|
86
135
|
|
87
136
|
|
88
137
|
matches = data[ 'matches']
|
89
138
|
matches.each do |m|
|
90
139
|
# stat.update( m )
|
91
140
|
|
92
|
-
|
93
|
-
|
141
|
+
## use ? or N.N. or ? for nil - why? why not?
|
142
|
+
team1 = m['homeTeam']['name'] || 'N.N.'
|
143
|
+
team2 = m['awayTeam']['name'] || 'N.N.'
|
94
144
|
|
95
145
|
score = m['score']
|
96
146
|
|
97
147
|
|
148
|
+
stage_key = m['stage']
|
149
|
+
|
150
|
+
stats['stage'][ stage_key ] += 1 ## track stage counts
|
151
|
+
|
152
|
+
## map stage to stage + round
|
153
|
+
stage, stage_round = STAGES[ stage_key ]
|
154
|
+
|
155
|
+
if stage.nil?
|
156
|
+
puts "!! ERROR - no stage mapping found for stage >#{stage_key}<"
|
157
|
+
exit 1
|
158
|
+
end
|
159
|
+
|
160
|
+
matchday_num = m['matchday']
|
161
|
+
matchday_num = nil if matchday_num == 0 ## change 0 to nil (empty) too
|
162
|
+
|
163
|
+
if stage_round.nil? ## e.g. Regular, League, Group, Playoffs
|
164
|
+
## keep/assume matchday number is matchday .e.g
|
165
|
+
## matchday 1, 2 etc.
|
166
|
+
matchday = matchday_num.to_s
|
167
|
+
else
|
168
|
+
## note - if matchday defined; assume leg e.g. 1|2
|
169
|
+
## skip if different than one or two for now
|
170
|
+
matchday = String.new
|
171
|
+
matchday << stage_round
|
172
|
+
matchday << " | Leg #{matchday_num}" if matchday_num &&
|
173
|
+
(matchday_num == 1 || matchday_num == 2)
|
174
|
+
end
|
175
|
+
|
176
|
+
|
177
|
+
|
178
|
+
group = m['group'] || ''
|
179
|
+
## GROUP_A
|
180
|
+
## shorten group to A|B|C etc.
|
181
|
+
group = group.sub( /^GROUP_/, '' )
|
182
|
+
|
183
|
+
|
98
184
|
|
99
|
-
if m['stage'] == 'REGULAR_SEASON'
|
100
185
|
teams[ team1 ] += 1
|
101
186
|
teams[ team2 ] += 1
|
102
187
|
|
@@ -107,31 +192,36 @@ matches.each do |m|
|
|
107
192
|
end
|
108
193
|
|
109
194
|
|
195
|
+
## auto-fix copa.l 2024
|
196
|
+
## !! ERROR: unsupported match status >IN_PLAY< - sorry:
|
197
|
+
if m['status'] == 'IN_PLAY' &&
|
198
|
+
team1 == 'Club Aurora' && team2 == 'FBC Melgar'
|
199
|
+
m['status'] = 'FINISHED'
|
200
|
+
end
|
201
|
+
|
110
202
|
|
111
203
|
comments = ''
|
112
204
|
ft = ''
|
113
205
|
ht = ''
|
206
|
+
et = ''
|
207
|
+
pen = ''
|
114
208
|
|
115
|
-
|
209
|
+
stats['status'][m['status']] += 1 ## track status counts
|
116
210
|
|
117
211
|
case m['status']
|
118
212
|
when 'SCHEDULED', 'TIMED' ## , 'IN_PLAY'
|
119
213
|
ft = ''
|
120
214
|
ht = ''
|
121
215
|
when 'FINISHED'
|
122
|
-
|
123
|
-
assert( score['duration'] == 'REGULAR', 'score.duration REGULAR expected' )
|
124
|
-
ft = "#{score['fullTime']['home']}-#{score['fullTime']['away']}"
|
125
|
-
ht = "#{score['halfTime']['home']}-#{score['halfTime']['away']}"
|
216
|
+
ft, ht, et, pen = convert_score( score )
|
126
217
|
when 'AWARDED'
|
127
|
-
|
128
|
-
assert( score['duration'] == 'REGULAR', 'score.duration REGULAR expected' )
|
218
|
+
assert( score['duration'] == 'REGULAR', 'score.duration REGULAR expected' )
|
129
219
|
ft = "#{score['fullTime']['home']}-#{score['fullTime']['away']}"
|
130
220
|
ft << ' (*)'
|
131
221
|
ht = ''
|
132
222
|
comments = 'awarded'
|
133
223
|
when 'CANCELLED'
|
134
|
-
## note cancelled might have scores!!
|
224
|
+
## note cancelled might have scores!! -- add/fix later!!!
|
135
225
|
## ht only or ft+ht!!! (see fr 2021/22)
|
136
226
|
ft = '(*)'
|
137
227
|
ht = ''
|
@@ -157,55 +247,49 @@ matches.each do |m|
|
|
157
247
|
## utc = ## tz_utc.strptime( m['utcDate'], '%Y-%m-%dT%H:%M:%SZ' )
|
158
248
|
## note: DateTime.strptime is supposed to be unaware of timezones!!!
|
159
249
|
## use to parse utc
|
160
|
-
utc =
|
250
|
+
utc = UTC.strptime( m['utcDate'], '%Y-%m-%dT%H:%M:%SZ' )
|
161
251
|
assert( utc.strftime( '%Y-%m-%dT%H:%M:%SZ' ) == m['utcDate'], 'utc time mismatch' )
|
162
|
-
|
163
|
-
|
164
|
-
|
252
|
+
|
253
|
+
|
254
|
+
## assume NOT valid utc time if 00:00
|
255
|
+
## do
|
256
|
+
if utc.hour == 0 && utc.min == 0 &&
|
257
|
+
['SCHEDULED','POSTPONED'].include?( m['status'] )
|
258
|
+
date = utc.strftime( '%Y-%m-%d' )
|
259
|
+
time = ''
|
260
|
+
timezone = ''
|
261
|
+
else
|
262
|
+
local = tz.to_local( utc )
|
263
|
+
date = local.strftime( '%Y-%m-%d' )
|
264
|
+
time = local.strftime( '%H:%M' )
|
265
|
+
timezone = local.strftime( '%Z/%z' )
|
266
|
+
end
|
267
|
+
|
165
268
|
|
166
269
|
## do NOT add time if status is SCHEDULED
|
167
270
|
## or POSTPONED for now
|
168
271
|
## otherwise assume time always present - why? why not?
|
169
|
-
|
272
|
+
|
273
|
+
|
170
274
|
|
171
275
|
## todo/fix: assert matchday is a number e.g. 1,2,3, etc.!!!
|
172
|
-
recs << [
|
173
|
-
|
174
|
-
|
175
|
-
|
276
|
+
recs << [stage,
|
277
|
+
group,
|
278
|
+
matchday,
|
279
|
+
date,
|
280
|
+
time,
|
281
|
+
timezone,
|
176
282
|
team1,
|
177
283
|
ft,
|
178
284
|
ht,
|
179
285
|
team2,
|
286
|
+
et,
|
287
|
+
pen,
|
180
288
|
comments,
|
181
289
|
## add more columns e.g. utc date, status
|
182
290
|
m['status'], # e.g. FINISHED, TIMED, etc.
|
183
291
|
m['utcDate'],
|
184
292
|
]
|
185
|
-
|
186
|
-
|
187
|
-
print '%2s' % m['matchday']
|
188
|
-
print ' - '
|
189
|
-
print '%-26s' % team1
|
190
|
-
print ' '
|
191
|
-
print ft
|
192
|
-
print ' '
|
193
|
-
print "(#{ht})" unless ht.empty?
|
194
|
-
print ' '
|
195
|
-
print '%-26s' % team2
|
196
|
-
print ' '
|
197
|
-
print comments
|
198
|
-
print ' | '
|
199
|
-
## print date.to_date ## strip time
|
200
|
-
print utc.strftime( '%a %b %-d %Y' )
|
201
|
-
print ' -- '
|
202
|
-
print utc
|
203
|
-
print "\n"
|
204
|
-
else
|
205
|
-
puts "!!! unexpected stage:"
|
206
|
-
puts "-- skipping #{m['stage']}"
|
207
|
-
# exit 1
|
208
|
-
end
|
209
293
|
end # each match
|
210
294
|
|
211
295
|
|
@@ -227,8 +311,8 @@ dates = "#{start_date.strftime('%b %-d')} - #{end_date.strftime('%b %-d')}"
|
|
227
311
|
|
228
312
|
buf = ''
|
229
313
|
buf << "#{season.key} (#{dates}) - "
|
230
|
-
buf << "#{teams.keys.size}
|
231
|
-
|
314
|
+
buf << "#{teams.keys.size} teams, "
|
315
|
+
buf << "#{recs.size} matches"
|
232
316
|
# buf << "#{stat[:regular_season][:goals]} goals"
|
233
317
|
buf << "\n"
|
234
318
|
|
@@ -248,7 +332,7 @@ puts buf
|
|
248
332
|
|
249
333
|
File.open( './logs.txt', 'a:utf-8' ) do |f|
|
250
334
|
f.write "==== #{league} #{season.key} =============\n"
|
251
|
-
f.write "
|
335
|
+
f.write " #{stats.inspect}\n"
|
252
336
|
end
|
253
337
|
|
254
338
|
=begin
|
@@ -283,26 +367,30 @@ puts buf
|
|
283
367
|
|
284
368
|
|
285
369
|
## reformat date / beautify e.g. Sat Aug 7 1993
|
286
|
-
recs = recs.map do |rec|
|
287
|
-
rec[
|
370
|
+
recs = recs.map do |rec|
|
371
|
+
rec[3] = Date.strptime( rec[3], '%Y-%m-%d' ).strftime( '%a %b %-d %Y' )
|
288
372
|
rec
|
289
373
|
end
|
290
374
|
|
375
|
+
## pp recs
|
376
|
+
|
377
|
+
## check if all status colums
|
378
|
+
### are FINISHED
|
379
|
+
### if yes, set all to empty (for vacuum)
|
380
|
+
|
381
|
+
if stats['status'].keys.size == 1 && stats['status'].keys[0] == 'FINISHED'
|
382
|
+
recs = recs.map { |rec| rec[-2] = ''; rec }
|
383
|
+
end
|
384
|
+
|
385
|
+
if stats['stage'].keys.size == 1 && stats['stage'].keys[0] == 'REGULAR_SEASON'
|
386
|
+
recs = recs.map { |rec| rec[0] = ''; rec }
|
387
|
+
end
|
388
|
+
|
389
|
+
|
390
|
+
recs, headers = vacuum( recs )
|
391
|
+
|
392
|
+
|
291
393
|
|
292
|
-
headers = [
|
293
|
-
'Matchday',
|
294
|
-
'Date',
|
295
|
-
'Time',
|
296
|
-
'Timezone', ## move back column - why? why not?
|
297
|
-
'Team 1',
|
298
|
-
'FT',
|
299
|
-
'HT',
|
300
|
-
'Team 2',
|
301
|
-
'Comments',
|
302
|
-
##
|
303
|
-
'Status', # e.g.
|
304
|
-
'UTC', # date utc
|
305
|
-
]
|
306
394
|
|
307
395
|
## note: change season_key from 2019/20 to 2019-20 (for path/directory!!!!)
|
308
396
|
write_csv( "#{config.convert.out_dir}/#{season.to_path}/#{league.downcase}.csv",
|
@@ -319,14 +407,89 @@ teams.each do |name, count|
|
|
319
407
|
print " › #{rec['area']['name']}"
|
320
408
|
print " - #{rec['address']}"
|
321
409
|
else
|
322
|
-
|
323
|
-
|
410
|
+
if name == 'N.N.'
|
411
|
+
## ignore missing record
|
412
|
+
else
|
413
|
+
puts "!! ERROR - no team record found in teams.json for #{name}"
|
414
|
+
exit 1
|
415
|
+
end
|
324
416
|
end
|
325
417
|
print "\n"
|
326
418
|
end
|
327
419
|
|
328
420
|
## pp stat
|
329
421
|
end # method convert
|
422
|
+
|
423
|
+
|
424
|
+
|
425
|
+
MAX_HEADERS = [
|
426
|
+
'Stage', # 0
|
427
|
+
'Group', # 1
|
428
|
+
'Matchday', # 2
|
429
|
+
'Date', # 3
|
430
|
+
'Time', # 4
|
431
|
+
'Timezone', # 5 ## move back column - why? why not?
|
432
|
+
'Team 1', # 6
|
433
|
+
'FT', # 7
|
434
|
+
'HT', # 8
|
435
|
+
'Team 2', # 9
|
436
|
+
'ET', # 10 # extra: incl. extra time
|
437
|
+
'P', # 11 # extra: incl. penalties
|
438
|
+
'Comments', # 12
|
439
|
+
'Status', # 13 / -2 # e.g.
|
440
|
+
'UTC', # 14 / -1 # date utc
|
441
|
+
]
|
442
|
+
|
443
|
+
MIN_HEADERS = [ ## always keep even if all empty
|
444
|
+
'Date',
|
445
|
+
'Team 1',
|
446
|
+
'FT',
|
447
|
+
'Team 2'
|
448
|
+
]
|
449
|
+
|
450
|
+
|
451
|
+
|
452
|
+
def self.vacuum( rows, headers: MAX_HEADERS, fixed_headers: MIN_HEADERS )
|
453
|
+
## check for unused columns and strip/remove
|
454
|
+
counter = Array.new( MAX_HEADERS.size, 0 )
|
455
|
+
rows.each do |row|
|
456
|
+
row.each_with_index do |col, idx|
|
457
|
+
counter[idx] += 1 unless col.nil? || col.empty?
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
## pp counter
|
462
|
+
|
463
|
+
## check empty columns
|
464
|
+
headers = []
|
465
|
+
indices = []
|
466
|
+
empty_headers = []
|
467
|
+
empty_indices = []
|
468
|
+
|
469
|
+
counter.each_with_index do |num, idx|
|
470
|
+
header = MAX_HEADERS[ idx ]
|
471
|
+
if num > 0 || (num == 0 && fixed_headers.include?( header ))
|
472
|
+
headers << header
|
473
|
+
indices << idx
|
474
|
+
else
|
475
|
+
empty_headers << header
|
476
|
+
empty_indices << idx
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
if empty_indices.size > 0
|
481
|
+
rows = rows.map do |row|
|
482
|
+
row_vacuumed = []
|
483
|
+
row.each_with_index do |col, idx|
|
484
|
+
## todo/fix: use values or such??
|
485
|
+
row_vacuumed << col unless empty_indices.include?( idx )
|
486
|
+
end
|
487
|
+
row_vacuumed
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
[rows, headers]
|
492
|
+
end
|
330
493
|
end # module Footballdata
|
331
494
|
|
332
495
|
|
@@ -6,7 +6,7 @@ module Footballdata
|
|
6
6
|
def self.schedule( league:, season: )
|
7
7
|
season = Season( season ) ## cast (ensure) season class (NOT string, integer, etc.)
|
8
8
|
|
9
|
-
league_code =
|
9
|
+
league_code = find_league!( league )
|
10
10
|
puts " mapping league >#{league}< to >#{league_code}<"
|
11
11
|
|
12
12
|
Metal.teams( league_code, season.start_year )
|
@@ -17,7 +17,7 @@ end
|
|
17
17
|
def self.matches( league:, season: )
|
18
18
|
season = Season( season ) ## cast (ensure) season class (NOT string, integer, etc.)
|
19
19
|
|
20
|
-
league_code =
|
20
|
+
league_code = find_league!( league )
|
21
21
|
puts " mapping league >#{league}< to >#{league_code}<"
|
22
22
|
Metal.matches( league_code, season.start_year )
|
23
23
|
end
|
@@ -26,7 +26,7 @@ end
|
|
26
26
|
def self.teams( league:, season: )
|
27
27
|
season = Season( season ) ## cast (ensure) season class (NOT string, integer, etc.)
|
28
28
|
|
29
|
-
league_code =
|
29
|
+
league_code = find_league!( league )
|
30
30
|
puts " mapping league >#{league}< to >#{league_code}<"
|
31
31
|
Metal.teams( league_code, season.start_year )
|
32
32
|
end
|
@@ -38,7 +38,7 @@ end
|
|
38
38
|
|
39
39
|
class Metal
|
40
40
|
|
41
|
-
def self.get( url,
|
41
|
+
def self.get( url,
|
42
42
|
auth: true,
|
43
43
|
headers: {} )
|
44
44
|
|
@@ -84,19 +84,19 @@ class Metal
|
|
84
84
|
def self.competition_teams_url( code, year ) "#{BASE_URL}/competitions/#{code}/teams?season=#{year}"; end
|
85
85
|
def self.competition_standings_url( code, year ) "#{BASE_URL}/competitions/#{code}/standings?season=#{year}"; end
|
86
86
|
def self.competition_scorers_url( code, year ) "#{BASE_URL}/competitions/#{code}/scorers?season=#{year}"; end
|
87
|
-
|
87
|
+
|
88
88
|
def self.matches( code, year,
|
89
|
-
headers: {} )
|
90
|
-
get( competition_matches_url( code, year ),
|
91
|
-
headers: headers )
|
89
|
+
headers: {} )
|
90
|
+
get( competition_matches_url( code, year ),
|
91
|
+
headers: headers )
|
92
92
|
end
|
93
93
|
|
94
|
-
def self.todays_matches_url( date=Date.today )
|
94
|
+
def self.todays_matches_url( date=Date.today )
|
95
95
|
"#{BASE_URL}/matches?"+
|
96
96
|
"dateFrom=#{date.strftime('%Y-%m-%d')}&"+
|
97
|
-
"dateTo=#{(date+1).strftime('%Y-%m-%d')}"
|
97
|
+
"dateTo=#{(date+1).strftime('%Y-%m-%d')}"
|
98
98
|
end
|
99
|
-
def self.todays_matches( date=Date.today ) ## use/rename to matches_today or such - why? why not?
|
99
|
+
def self.todays_matches( date=Date.today ) ## use/rename to matches_today or such - why? why not?
|
100
100
|
get( todays_matches_url( date ) )
|
101
101
|
end
|
102
102
|
|
data/lib/footballdata/leagues.rb
CHANGED
@@ -1,59 +1,24 @@
|
|
1
1
|
module Footballdata
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
# DED - Eredivisie , Netherlands 10 seasons | 2019-08-09 - 2020-03-08 / matchday 34
|
24
|
-
|
25
|
-
'fr.1' => 'FL1', # incl. team(s) monaco
|
26
|
-
# FL1 - Ligue 1, France
|
27
|
-
# 9 seasons | 2019-08-09 - 2020-05-31 / matchday 38
|
28
|
-
#
|
29
|
-
# 2019 => 2019/20
|
30
|
-
# 2018 => 2018/19
|
31
|
-
# 2017 => xxx 2017-18 - requires subscription !!!
|
32
|
-
|
33
|
-
'it.1' => 'SA',
|
34
|
-
# SA - Serie A , Italy 15 seasons | 2019-08-24 - 2020-08-02 / matchday 27
|
35
|
-
|
36
|
-
'br.1' => 'BSA',
|
37
|
-
# BSA - Série A, Brazil
|
38
|
-
# 4 seasons | 2020-05-03 - 2020-12-06 / matchday 10
|
39
|
-
#
|
40
|
-
# 2020 => 2020
|
41
|
-
# 2019 => 2019
|
42
|
-
# 2018 => 2018
|
43
|
-
# 2017 => xxx 2017 - requires subscription !!!
|
44
|
-
|
45
|
-
## todo/check: use champs and NOT cl - why? why not?
|
46
|
-
'uefa.cl' => 'CL', ## note: cl is country code for chile!! - use champs - why? why not?
|
47
|
-
## was europe.cl / cl
|
48
|
-
|
49
|
-
## Copa Libertadores
|
50
|
-
'copa.l' => 'CLI',
|
51
|
-
|
52
|
-
############
|
53
|
-
## national teams
|
54
|
-
'euro' => 'EC',
|
55
|
-
'world' => 'WC',
|
56
|
-
|
57
|
-
}
|
3
|
+
def self.find_league!( league )
|
4
|
+
@leagues ||= begin
|
5
|
+
recs = read_csv( "#{FootballdataApi.root}/config/leagues.csv" )
|
6
|
+
leagues = {}
|
7
|
+
recs.each do |rec|
|
8
|
+
leagues[ rec['key'] ] = rec['code']
|
9
|
+
end
|
10
|
+
leagues
|
11
|
+
end
|
12
|
+
|
13
|
+
key = league.downcase
|
14
|
+
code = @leagues[ key ]
|
15
|
+
if code.nil?
|
16
|
+
puts "!! ERROR - no code/mapping found for league >#{league}<"
|
17
|
+
puts " mappings include:"
|
18
|
+
pp @leagues
|
19
|
+
exit 1
|
20
|
+
end
|
21
|
+
code
|
22
|
+
end
|
58
23
|
end # module Footballdata
|
59
24
|
|
data/lib/footballdata/mods.rb
CHANGED
@@ -6,6 +6,8 @@ module Footballdata
|
|
6
6
|
# Cardiff City FC | Cardiff › Wales - Cardiff City Stadium, Leckwith Road Cardiff CF11 8AZ
|
7
7
|
# AS Monaco FC | Monaco › Monaco - Avenue des Castellans Monaco 98000
|
8
8
|
|
9
|
+
|
10
|
+
|
9
11
|
MODS = {
|
10
12
|
'br.1' => {
|
11
13
|
'América FC' => 'América Mineiro', # in year 2018 ??
|