football-sources 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,239 +0,0 @@
1
-
2
-
3
- module Footballdata
4
-
5
-
6
- def self.convert( league:, season: )
7
-
8
- ### note/fix: cl (champions league for now is a "special" case)
9
- if league.downcase == 'cl'
10
- convert_cl( league: league,
11
- season: season )
12
- return
13
- end
14
-
15
-
16
-
17
- season = Season( season ) ## cast (ensure) season class (NOT string, integer, etc.)
18
-
19
- data = Webcache.read_json( Metal.competition_matches_url( LEAGUES[league.downcase], season.start_year ))
20
- data_teams = Webcache.read_json( Metal.competition_teams_url( LEAGUES[league.downcase], season.start_year ))
21
-
22
-
23
- ## build a (reverse) team lookup by name
24
- puts "#{data_teams['teams'].size} teams"
25
-
26
- teams_by_name = data_teams['teams'].reduce( {} ) do |h,rec|
27
- h[ rec['name'] ] = rec
28
- h
29
- end
30
-
31
- pp teams_by_name.keys
32
-
33
-
34
-
35
- mods = MODS[ league.downcase ] || {}
36
-
37
-
38
- recs = []
39
-
40
- teams = Hash.new( 0 )
41
-
42
- stat = Stat.new
43
-
44
- matches = data[ 'matches']
45
- matches.each do |m|
46
- stat.update( m )
47
-
48
- team1 = m['homeTeam']['name']
49
- team2 = m['awayTeam']['name']
50
-
51
- score = m['score']
52
-
53
-
54
-
55
- if m['stage'] == 'REGULAR_SEASON'
56
- teams[ team1 ] += 1
57
- teams[ team2 ] += 1
58
-
59
- ### mods - rename club names
60
- unless mods.nil? || mods.empty?
61
- team1 = mods[ team1 ] if mods[ team1 ]
62
- team2 = mods[ team2 ] if mods[ team2 ]
63
- end
64
-
65
-
66
- ## e.g. "utcDate": "2020-05-09T00:00:00Z",
67
- date_str = m['utcDate']
68
- date = DateTime.strptime( date_str, '%Y-%m-%dT%H:%M:%SZ' )
69
-
70
-
71
- comments = ''
72
- ft = ''
73
- ht = ''
74
-
75
- case m['status']
76
- when 'SCHEDULED', 'IN_PLAY'
77
- ft = ''
78
- ht = ''
79
- when 'FINISHED'
80
- ## todo/fix: assert duration == "REGULAR"
81
- ft = "#{score['fullTime']['homeTeam']}-#{score['fullTime']['awayTeam']}"
82
- ht = "#{score['halfTime']['homeTeam']}-#{score['halfTime']['awayTeam']}"
83
- when 'AWARDED'
84
- ## todo/fix: assert duration == "REGULAR"
85
- ft = "#{score['fullTime']['homeTeam']}-#{score['fullTime']['awayTeam']}"
86
- ft << ' (*)'
87
- ht = ''
88
- comments = 'awarded'
89
- when 'CANCELLED'
90
- ft = '(*)'
91
- ht = ''
92
- comments = 'canceled' ## us eng ? -> canceled, british eng. cancelled ?
93
- when 'POSTPONED'
94
- ft = '(*)'
95
- ht = ''
96
- comments = 'postponed'
97
- else
98
- puts "!! ERROR: unsupported match status >#{m['status']}< - sorry:"
99
- pp m
100
- exit 1
101
- end
102
-
103
-
104
- ## todo/fix: assert matchday is a number e.g. 1,2,3, etc.!!!
105
- recs << [m['matchday'],
106
- date.to_date.strftime( '%Y-%m-%d' ),
107
- team1,
108
- ft,
109
- ht,
110
- team2,
111
- comments
112
- ]
113
-
114
-
115
- print '%2s' % m['matchday']
116
- print ' - '
117
- print '%-24s' % team1
118
- print ' '
119
- print ft
120
- print ' '
121
- print "(#{ht})" unless ht.empty?
122
- print ' '
123
- print '%-24s' % team2
124
- print ' '
125
- print comments
126
- print ' | '
127
- ## print date.to_date ## strip time
128
- print date.to_date.strftime( '%a %b %-d %Y' )
129
- print ' -- '
130
- print date
131
- print "\n"
132
- else
133
- puts "-- skipping #{m['stage']}"
134
- end
135
- end # each match
136
-
137
-
138
-
139
- ## note: get season from first match
140
- ## assert - all other matches include the same season
141
- ## e.g.
142
- # "season": {
143
- # "id": 154,
144
- # "startDate": "2018-08-03",
145
- # "endDate": "2019-05-05",
146
- # "currentMatchday": 46
147
- # }
148
-
149
- start_date = Date.strptime( matches[0]['season']['startDate'], '%Y-%m-%d' )
150
- end_date = Date.strptime( matches[0]['season']['endDate'], '%Y-%m-%d' )
151
-
152
- dates = "#{start_date.strftime('%b %-d')} - #{end_date.strftime('%b %-d')}"
153
-
154
- buf = ''
155
- buf << "#{season.key} (#{dates}) - "
156
- buf << "#{teams.keys.size} clubs, "
157
- buf << "#{stat[:regular_season][:matches]} matches, "
158
- buf << "#{stat[:regular_season][:goals]} goals"
159
- buf << "\n"
160
-
161
- puts buf
162
-
163
-
164
-
165
- ## note: warn if stage is greater one and not regular season!!
166
- File.open( './errors.txt' , 'a:utf-8' ) do |f|
167
- if stat[:all][:stage].keys != ['REGULAR_SEASON']
168
- f.write "!! WARN - league: #{league}, season: #{season.key} includes non-regular stage(s):\n"
169
- f.write " #{stat[:all][:stage].keys.inspect}\n"
170
- end
171
- end
172
-
173
-
174
- File.open( './logs.txt', 'a:utf-8' ) do |f|
175
- f.write "\n================================\n"
176
- f.write "==== #{league} =============\n"
177
- f.write buf
178
- f.write " match status: #{stat[:regular_season][:status].inspect}\n"
179
- f.write " match duration: #{stat[:regular_season][:duration].inspect}\n"
180
-
181
- f.write "#{teams.keys.size} teams:\n"
182
- teams.each do |name, count|
183
- rec = teams_by_name[ name ]
184
- f.write " #{count}x #{name}"
185
- if rec
186
- f.write " | #{rec['shortName']} " if name != rec['shortName']
187
- f.write " › #{rec['area']['name']}"
188
- f.write " - #{rec['address']}"
189
- else
190
- puts "!! ERROR - no team record found in teams.json for >#{name}<"
191
- exit 1
192
- end
193
- f.write "\n"
194
- end
195
- end
196
-
197
-
198
-
199
-
200
- # recs = recs.sort { |l,r| l[1] <=> r[1] }
201
- ## reformat date / beautify e.g. Sat Aug 7 1993
202
- recs.each { |rec| rec[1] = Date.strptime( rec[1], '%Y-%m-%d' ).strftime( '%a %b %-d %Y' ) }
203
-
204
- headers = [
205
- 'Matchday',
206
- 'Date',
207
- 'Team 1',
208
- 'FT',
209
- 'HT',
210
- 'Team 2',
211
- 'Comments'
212
- ]
213
-
214
- ## note: change season_key from 2019/20 to 2019-20 (for path/directory!!!!)
215
- Cache::CsvMatchWriter.write( "#{config.convert.out_dir}/#{season.to_path}/#{league.downcase}.csv",
216
- recs,
217
- headers: headers )
218
-
219
-
220
- teams.each do |name, count|
221
- rec = teams_by_name[ name ]
222
- print " #{count}x "
223
- print name
224
- if rec
225
- print " | #{rec['shortName']} " if name != rec['shortName']
226
- print " › #{rec['area']['name']}"
227
- print " - #{rec['address']}"
228
- else
229
- puts "!! ERROR - no team record found in teams.json for #{name}"
230
- exit 1
231
- end
232
- print "\n"
233
- end
234
-
235
- pp stat
236
- end # method convert
237
- end # module Footballdata
238
-
239
-
@@ -1,267 +0,0 @@
1
- ##########
2
- ## todo/fix:
3
- ## use a more generic name?
4
- ## might also works for euro or worldcup - check?
5
-
6
-
7
- module Footballdata
8
-
9
-
10
- STAGE_TO_STAGE = {
11
- 'PRELIMINARY_SEMI_FINALS' => 'Qualifying',
12
- 'PRELIMINARY_FINAL' => 'Qualifying',
13
- '1ST_QUALIFYING_ROUND' => 'Qualifying',
14
- '2ND_QUALIFYING_ROUND' => 'Qualifying',
15
- '3RD_QUALIFYING_ROUND' => 'Qualifying',
16
- 'PLAY_OFF_ROUND' => 'Qualifying',
17
- 'ROUND_OF_16' => 'Knockout',
18
- 'QUARTER_FINALS' => 'Knockout',
19
- 'SEMI_FINALS' => 'Knockout',
20
- 'FINAL' => 'Knockout',
21
- }
22
-
23
- STAGE_TO_ROUND = {
24
- 'PRELIMINARY_SEMI_FINALS' => 'Preliminary Semifinals',
25
- 'PRELIMINARY_FINAL' => 'Preliminary Final',
26
- '1ST_QUALIFYING_ROUND' => 'Qual. Round 1',
27
- '2ND_QUALIFYING_ROUND' => 'Qual. Round 2',
28
- '3RD_QUALIFYING_ROUND' => 'Qual. Round 3',
29
- 'PLAY_OFF_ROUND' => 'Playoff Round',
30
- 'ROUND_OF_16' => 'Round of 16',
31
- 'QUARTER_FINALS' => 'Quarterfinals',
32
- 'SEMI_FINALS' => 'Semifinals',
33
- 'FINAL' => 'Final',
34
- }
35
-
36
-
37
-
38
-
39
- def self.convert_cl( league:, season: )
40
- season = Season( season ) ## cast (ensure) season class (NOT string, integer, etc.)
41
-
42
- data = Webcache.read_json( Metal.competition_matches_url( LEAGUES[league.downcase], season.start_year ))
43
- data_teams = Webcache.read_json( Metal.competition_teams_url( LEAGUES[league.downcase], season.start_year ))
44
-
45
-
46
- ## build a (reverse) team lookup by name
47
- puts "#{data_teams['teams'].size} teams"
48
-
49
- teams_by_name = data_teams['teams'].reduce( {} ) do |h,rec|
50
- h[ rec['name'] ] = rec
51
- h
52
- end
53
-
54
- pp teams_by_name.keys
55
-
56
-
57
-
58
- mods = MODS[ league.downcase ] || {}
59
-
60
-
61
- recs = []
62
-
63
- teams = Hash.new( 0 )
64
- stat = Stat.new
65
-
66
- matches = data[ 'matches' ]
67
- matches.each do |m|
68
- stat.update( m )
69
-
70
- team1 = m['homeTeam']['name']
71
- team2 = m['awayTeam']['name']
72
-
73
-
74
- score = m['score']
75
-
76
-
77
-
78
- if m['stage'] == 'GROUP_STAGE'
79
- stage = 'Group'
80
- round = "Matchday #{m['matchday']}"
81
- if m['group'] =~ /Group ([A-Z])/
82
- group = $1
83
- else
84
- puts "!! ERROR - no group name found for group >#{m['group']}<"
85
- exit 1
86
- end
87
- else
88
- stage = STAGE_TO_STAGE[ m['stage'] ]
89
- if stage.nil?
90
- puts "!! ERROR - no stage mapping found for stage >#{m['stage']}<"
91
- exit 1
92
- end
93
- round = STAGE_TO_ROUND[ m['stage'] ]
94
- if round.nil?
95
- puts "!! ERROR - no round mapping found for stage >#{m['stage']}<"
96
- exit 1
97
- end
98
- group = ''
99
- end
100
-
101
-
102
- teams[ team1 ] += 1
103
- teams[ team2 ] += 1
104
-
105
- ### mods - rename club names
106
- unless mods.nil? || mods.empty?
107
- team1 = mods[ team1 ] if mods[ team1 ]
108
- team2 = mods[ team2 ] if mods[ team2 ]
109
- end
110
-
111
-
112
- ## e.g. "utcDate": "2020-05-09T00:00:00Z",
113
- date_str = m['utcDate']
114
- date = DateTime.strptime( date_str, '%Y-%m-%dT%H:%M:%SZ' )
115
-
116
-
117
- comments = ''
118
- ft = ''
119
- ht = ''
120
- et = ''
121
- pen = ''
122
-
123
- case m['status']
124
- when 'SCHEDULED', 'IN_PLAY'
125
- ft = ''
126
- ht = ''
127
- et = ''
128
- pen = ''
129
- when 'AWARDED'
130
- ## todo/fix: assert duration == "REGULAR"
131
- ft = "#{score['fullTime']['homeTeam']}-#{score['fullTime']['awayTeam']}"
132
- ft << ' (*)'
133
- ht = ''
134
- comments = 'awarded'
135
- when 'FINISHED'
136
- ## note: if extraTime present
137
- ## than fullTime is extraTime score!!
138
- ## AND fullTime - extraTime is fullTime score!!
139
- ## double check in other season too??
140
- ## - checked in cl 2018/19
141
-
142
- if score['extraTime']['homeTeam'] && score['extraTime']['awayTeam']
143
- et = "#{score['fullTime']['homeTeam']}-#{score['fullTime']['awayTeam']}"
144
- ft = "#{score['fullTime']['homeTeam']-score['extraTime']['homeTeam']}-#{score['fullTime']['awayTeam']-score['extraTime']['awayTeam']}"
145
- else
146
- ft = "#{score['fullTime']['homeTeam']}-#{score['fullTime']['awayTeam']}"
147
- end
148
-
149
- ht = "#{score['halfTime']['homeTeam']}-#{score['halfTime']['awayTeam']}"
150
-
151
- pen = if score['penalties']['homeTeam'] && score['penalties']['awayTeam']
152
- "#{score['penalties']['homeTeam']}-#{score['penalties']['awayTeam']}"
153
- else
154
- ''
155
- end
156
- else
157
- puts "!! ERROR: unsupported match status >#{m['status']}< - sorry:"
158
- pp m
159
- exit 1
160
- end
161
-
162
-
163
- recs << [stage,
164
- round,
165
- group,
166
- date.to_date.strftime( '%Y-%m-%d' ),
167
- team1,
168
- ft,
169
- ht,
170
- team2,
171
- et,
172
- pen,
173
- comments
174
- ]
175
-
176
-
177
- print '%2s' % m['matchday']
178
- print ' - '
179
- print '%-24s' % team1
180
- print ' '
181
- print ft
182
- print ' '
183
- print "(#{ht})" unless ht.empty?
184
- print ' '
185
- print '%-24s' % team2
186
- print ' '
187
- print comments
188
- print ' | '
189
- ## print date.to_date ## strip time
190
- print date.to_date.strftime( '%a %b %-d %Y' )
191
- print ' -- '
192
- print date
193
- print "\n"
194
-
195
- end # each match
196
-
197
-
198
- ## note: get season from first match
199
- ## assert - all other matches include the same season
200
- ## e.g.
201
- # "season": {
202
- # "id": 154,
203
- # "startDate": "2018-08-03",
204
- # "endDate": "2019-05-05",
205
- # "currentMatchday": 46
206
- # }
207
-
208
- start_date = Date.strptime( matches[0]['season']['startDate'], '%Y-%m-%d' )
209
- end_date = Date.strptime( matches[0]['season']['endDate'], '%Y-%m-%d' )
210
-
211
- dates = "#{start_date.strftime('%b %-d')} - #{end_date.strftime('%b %-d')}"
212
-
213
- buf = ''
214
- buf << "#{season.key} (#{dates}) - "
215
- buf << "#{teams.keys.size} clubs, "
216
- buf << "#{stat[:all][:matches]} matches, "
217
- buf << "#{stat[:all][:goals]} goals"
218
- buf << "\n"
219
-
220
- puts buf
221
-
222
-
223
-
224
- # recs = recs.sort { |l,r| l[1] <=> r[1] }
225
- ## reformat date / beautify e.g. Sat Aug 7 1993
226
- recs.each { |rec| rec[3] = Date.strptime( rec[3], '%Y-%m-%d' ).strftime( '%a %b %-d %Y' ) }
227
-
228
- headers = [
229
- 'Stage',
230
- 'Round',
231
- 'Group',
232
- 'Date',
233
- 'Team 1',
234
- 'FT',
235
- 'HT',
236
- 'Team 2',
237
- 'ET',
238
- 'P',
239
- 'Comments'
240
- ]
241
-
242
- ## note: change season_key from 2019/20 to 2019-20 (for path/directory!!!!)
243
- Cache::CsvMatchWriter.write( "#{config.convert.out_dir}/#{season.to_path}/#{league.downcase}.csv",
244
- recs,
245
- headers: headers )
246
-
247
-
248
- teams.each do |name, count|
249
- rec = teams_by_name[ name ]
250
- print " #{count}x "
251
- print name
252
- if rec
253
- print " | #{rec['shortName']} " if name != rec['shortName']
254
- print " › #{rec['area']['name']}"
255
- print " - #{rec['address']}"
256
- else
257
- print "!! ERROR - no team record found in teams.json for #{name}"
258
- exit 1
259
- end
260
- print "\n"
261
- end
262
-
263
- pp stat
264
- end # method convert_cl
265
- end # module Footballdata
266
-
267
-
@@ -1,20 +0,0 @@
1
- module Footballdata
2
-
3
- #########
4
- ## Mods
5
- # e.g.
6
- # Cardiff City FC | Cardiff › Wales - Cardiff City Stadium, Leckwith Road Cardiff CF11 8AZ
7
- # AS Monaco FC | Monaco › Monaco - Avenue des Castellans Monaco 98000
8
-
9
-
10
- MODS = {
11
- 'br.1' => {
12
- 'América FC' => 'América MG', # in year 2018
13
- },
14
- 'pt.1' => {
15
- 'Vitória SC' => 'Vitória Guimarães', ## avoid easy confusion with Vitória SC <=> Vitória FC
16
- 'Vitória FC' => 'Vitória Setúbal',
17
- },
18
- }
19
-
20
- end # module Footballdata
@@ -1,59 +0,0 @@
1
- module Footballdata
2
-
3
- class Stat ## rename to match stat or something why? why not?
4
- def initialize
5
- @data = {}
6
- end
7
-
8
- def [](key) @data[ key ]; end
9
-
10
- def update( match )
11
- ## keep track of some statistics
12
- stat = @data[:all] ||= { stage: Hash.new( 0 ),
13
- duration: Hash.new( 0 ),
14
- status: Hash.new( 0 ),
15
- group: Hash.new( 0 ),
16
- matchday: Hash.new( 0 ),
17
-
18
- matches: 0,
19
- goals: 0,
20
- }
21
-
22
- stat[:stage][ match['stage'] ] += 1
23
- stat[:group][ match['group'] ] += 1
24
- stat[:status][ match['status'] ] += 1
25
- stat[:matchday][ match['matchday'] ] += 1
26
-
27
- score = match['score']
28
-
29
- stat[:duration][ score['duration'] ] += 1 ## track - assert always REGULAR
30
-
31
- stat[:matches] += 1
32
- stat[:goals] += score['fullTime']['homeTeam'].to_i if score['fullTime']['homeTeam']
33
- stat[:goals] += score['fullTime']['awayTeam'].to_i if score['fullTime']['awayTeam']
34
-
35
-
36
- stage_key = match['stage'].downcase.to_sym # e.g. :regular_season
37
- stat = @data[ stage_key ] ||= { duration: Hash.new( 0 ),
38
- status: Hash.new( 0 ),
39
- group: Hash.new( 0 ),
40
- matchday: Hash.new( 0 ),
41
-
42
- matches: 0,
43
- goals: 0,
44
- }
45
- stat[:group][ match['group'] ] += 1
46
- stat[:status][ match['status'] ] += 1
47
- stat[:matchday][ match['matchday'] ] += 1
48
-
49
- stat[:duration][ score['duration'] ] += 1 ## track - assert always REGULAR
50
-
51
- stat[:matches] += 1
52
- stat[:goals] += score['fullTime']['homeTeam'].to_i if score['fullTime']['homeTeam']
53
- stat[:goals] += score['fullTime']['awayTeam'].to_i if score['fullTime']['awayTeam']
54
- end
55
- end # class Stat
56
- end # module Footballdata
57
-
58
-
59
-
@@ -1,10 +0,0 @@
1
-
2
-
3
- ###########################
4
- ## our own code
5
- require_relative 'apis/config'
6
- require_relative 'apis/mods'
7
- require_relative 'apis/stat'
8
- require_relative 'apis/convert'
9
- require_relative 'apis/convert_cl'
10
-
@@ -1,96 +0,0 @@
1
-
2
- module Fbref
3
-
4
- def self.build( rows, league:, season: )
5
- season = Season( season ) ## cast (ensure) season class (NOT string, integer, etc.)
6
-
7
- raise ArgumentError, "league key as string expected" unless league.is_a?(String) ## note: do NOT pass in league struct! pass in key (string)
8
-
9
- print " #{rows.size} rows - build #{league} #{season}"
10
- print "\n"
11
-
12
-
13
- recs = []
14
- rows.each do |row|
15
-
16
- stage = row[:stage] || ''
17
-
18
- ## todo/check: assert that only matchweek or round can be present NOT both!!
19
- round = if row[:matchweek] && row[:matchweek].size > 0
20
- row[:matchweek]
21
- elsif row[:round] && row[:round].size > 0
22
- row[:round]
23
- else
24
- ''
25
- end
26
-
27
- date_str = row[:date]
28
- time_str = row[:time]
29
- team1_str = row[:team1]
30
- team2_str = row[:team2]
31
- score_str = row[:score]
32
-
33
- ## convert date from string e.g. 2019-25-10
34
- date = Date.strptime( date_str, '%Y-%m-%d' )
35
-
36
- comments = row[:comments]
37
- ht, ft, et, pen, comments = parse_score( score_str, comments )
38
-
39
-
40
- venue_str = row[:venue]
41
- attendance_str = row[:attendance]
42
-
43
-
44
- recs << [stage,
45
- round,
46
- date.strftime( '%Y-%m-%d' ),
47
- time_str,
48
- team1_str,
49
- ft,
50
- ht,
51
- team2_str,
52
- et, # extra: incl. extra time
53
- pen, # extra: incl. penalties
54
- venue_str,
55
- attendance_str,
56
- comments]
57
- end
58
-
59
- recs
60
- end
61
-
62
-
63
- def self.parse_score( score_str, comments )
64
-
65
- ## split score
66
- ft = ''
67
- ht = ''
68
- et = ''
69
- pen = ''
70
-
71
- if score_str.size > 0
72
- ## note: replace unicode "fancy" dash with ascii-dash
73
- # check other columns too - possible in teams?
74
- score_str = score_str.gsub( /[–]/, '-' ).strip
75
-
76
- if score_str =~ /^\(([0-9]+)\)
77
- [ ]+ ([0-9]+) - ([0-9+]) [ ]+
78
- \(([0-9]+)\)$/x
79
- ft = '?'
80
- et = "#{$2}-#{$3}"
81
- pen = "#{$1}-#{$4}"
82
- else ## assume "regular" score e.g. 0-0
83
- ## check if notes include extra time otherwise assume regular time
84
- if comments =~ /extra time/i
85
- ft = '?'
86
- et = score_str
87
- else
88
- ft = score_str
89
- end
90
- end
91
- end
92
-
93
- [ht, ft, et, pen, comments]
94
- end
95
-
96
- end # module Fbref
@@ -1,16 +0,0 @@
1
- module Fbref
2
-
3
- ### add some more config options / settings
4
- class Configuration
5
- #########
6
- ## nested configuration classes - use - why? why not?
7
- class Convert
8
- def out_dir() @out_dir || './o'; end
9
- def out_dir=(value) @out_dir = value; end
10
- end
11
-
12
- def convert() @convert ||= Convert.new; end
13
- end # class Configuration
14
-
15
-
16
- end # module Fbref