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.
@@ -1,32 +1,81 @@
1
1
 
2
+ module Footballdata
2
3
 
3
4
 
4
- module Footballdata
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
- TIMEZONES = {
8
- 'eng.1' => 'Europe/London',
9
- 'eng.2' => 'Europe/London',
10
-
11
- 'es.1' => 'Europe/Madrid',
12
-
13
- 'de.1' => 'Europe/Berlin',
14
- 'fr.1' => 'Europe/Paris',
15
- 'it.1' => 'Europe/Rome',
16
- 'nl.1' => 'Europe/Amsterdam',
17
-
18
- 'pt.1' => 'Europe/Lisbon',
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 = LEAGUES[league.downcase]
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
- tz_name = TIMEZONES[ league.downcase ]
54
- if tz_name.nil?
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
- # track stati counts
85
- stati = Hash.new(0)
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
- team1 = m['homeTeam']['name']
93
- team2 = m['awayTeam']['name']
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
- stati[m['status']] += 1 ## track stati counts for logs
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
- ## todo/fix: assert duration == "REGULAR"
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
- ## todo/fix: assert duration == "REGULAR"
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 = DateTime.strptime( m['utcDate'], '%Y-%m-%dT%H:%M:%SZ' ).to_time.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
- local = tz.to_local( utc )
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 << [m['matchday'].to_s, ## note: convert integer to string!!!
173
- local.strftime( '%Y-%m-%d' ),
174
- ['SCHEDULED','POSTPONED'].include?( m['status'] ) ? '' : local.strftime( '%H:%M' ),
175
- local.strftime( '%Z / UTC%z' ),
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} clubs, "
231
- # buf << "#{stat[:regular_season][:matches]} matches, "
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 " match stati: #{stati.inspect}\n"
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[1] = Date.strptime( rec[1], '%Y-%m-%d' ).strftime( '%a %b %-d %Y' )
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
- puts "!! ERROR - no team record found in teams.json for #{name}"
323
- exit 1
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 = LEAGUES[ league.downcase ]
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 = LEAGUES[ league.downcase ]
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 = LEAGUES[ league.downcase ]
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
 
@@ -1,59 +1,24 @@
1
1
  module Footballdata
2
2
 
3
- LEAGUES = {
4
- 'eng.1' => 'PL', # incl. team(s) from wales
5
- 'eng.2' => 'ELC',
6
- # PL - Premier League , England 27 seasons | 2019-08-09 - 2020-07-25 / matchday 31
7
- # ELC - Championship , England 3 seasons | 2019-08-02 - 2020-07-22 / matchday 38
8
- #
9
- # 2019 => 2019/20
10
- # 2018 => 2018/19
11
- # 2017 => xxx 2017-18 - requires subscription !!!
12
-
13
- 'es.1' => 'PD',
14
- # PD - Primera Division , Spain 27 seasons | 2019-08-16 - 2020-07-19 / matchday 31
15
-
16
- 'pt.1' => 'PPL',
17
- # PPL - Primeira Liga , Portugal 9 seasons | 2019-08-10 - 2020-07-26 / matchday 28
18
-
19
- 'de.1' => 'BL1',
20
- # BL1 - Bundesliga , Germany 24 seasons | 2019-08-16 - 2020-06-27 / matchday 34
21
-
22
- 'nl.1' => 'DED',
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
 
@@ -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 ??