footballdata-api 0.2.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 ??