sportdb-structs 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +3 -0
- data/Manifest.txt +29 -0
- data/README.md +29 -0
- data/Rakefile +33 -0
- data/lib/sportdb/structs.rb +125 -0
- data/lib/sportdb/structs/config.rb +39 -0
- data/lib/sportdb/structs/goal_parser_csv.rb +28 -0
- data/lib/sportdb/structs/match_parser_csv.rb +490 -0
- data/lib/sportdb/structs/match_status_parser.rb +90 -0
- data/lib/sportdb/structs/name_helper.rb +87 -0
- data/lib/sportdb/structs/season.rb +199 -0
- data/lib/sportdb/structs/structs/country.rb +26 -0
- data/lib/sportdb/structs/structs/goal.rb +231 -0
- data/lib/sportdb/structs/structs/group.rb +16 -0
- data/lib/sportdb/structs/structs/league.rb +35 -0
- data/lib/sportdb/structs/structs/match.rb +180 -0
- data/lib/sportdb/structs/structs/matchlist.rb +215 -0
- data/lib/sportdb/structs/structs/round.rb +23 -0
- data/lib/sportdb/structs/structs/standings.rb +271 -0
- data/lib/sportdb/structs/structs/team.rb +147 -0
- data/lib/sportdb/structs/structs/team_usage.rb +84 -0
- data/lib/sportdb/structs/version.rb +24 -0
- data/test/helper.rb +11 -0
- data/test/test_clubs.rb +38 -0
- data/test/test_csv_reader.rb +30 -0
- data/test/test_match.rb +30 -0
- data/test/test_match_status_parser.rb +57 -0
- data/test/test_name_helper.rb +65 -0
- data/test/test_season.rb +141 -0
- metadata +177 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
module Sports
|
2
|
+
|
3
|
+
class Group
|
4
|
+
attr_reader :key, :name, :teams
|
5
|
+
|
6
|
+
def initialize( key: nil,
|
7
|
+
name:,
|
8
|
+
teams: )
|
9
|
+
@key = key ## e.g. A,B,C or 1,2,3, - note: always a string or nil
|
10
|
+
@name = name
|
11
|
+
@teams = teams
|
12
|
+
end
|
13
|
+
end # class Group
|
14
|
+
|
15
|
+
end # module Sports
|
16
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
|
2
|
+
module Sports
|
3
|
+
|
4
|
+
|
5
|
+
class League
|
6
|
+
attr_reader :key, :name, :country, :intl
|
7
|
+
attr_accessor :alt_names
|
8
|
+
|
9
|
+
## special import only attribs
|
10
|
+
attr_accessor :alt_names_auto ## auto-generated alt names
|
11
|
+
|
12
|
+
def initialize( key:, name:, alt_names: [], alt_names_auto: [],
|
13
|
+
country: nil, intl: false, clubs: true )
|
14
|
+
@key = key
|
15
|
+
@name = name
|
16
|
+
@alt_names = alt_names
|
17
|
+
@alt_names_auto = alt_names_auto
|
18
|
+
|
19
|
+
@country = country
|
20
|
+
@intl = intl
|
21
|
+
@clubs = clubs
|
22
|
+
end
|
23
|
+
|
24
|
+
def intl?() @intl == true; end
|
25
|
+
def national?() @intl == false; end
|
26
|
+
alias_method :domestic?, :national?
|
27
|
+
|
28
|
+
def clubs?() @clubs == true; end
|
29
|
+
def national_teams?() @clubs == false; end
|
30
|
+
alias_method :club?, :clubs?
|
31
|
+
alias_method :national_team?, :national_teams?
|
32
|
+
|
33
|
+
end # class League
|
34
|
+
|
35
|
+
end # module Sports
|
@@ -0,0 +1,180 @@
|
|
1
|
+
|
2
|
+
module Sports
|
3
|
+
|
4
|
+
|
5
|
+
class Match
|
6
|
+
|
7
|
+
attr_reader :date,
|
8
|
+
:time,
|
9
|
+
:team1, :team2, ## todo/fix: use team1_name, team2_name or similar - for compat with db activerecord version? why? why not?
|
10
|
+
:score1, :score2, ## full time
|
11
|
+
:score1i, :score2i, ## half time (first (i) part)
|
12
|
+
:score1et, :score2et, ## extra time
|
13
|
+
:score1p, :score2p, ## penalty
|
14
|
+
:score1agg, :score2agg, ## full time (all legs) aggregated
|
15
|
+
:winner, # return 1,2,0 1 => team1, 2 => team2, 0 => draw/tie
|
16
|
+
:round, ## todo/fix: use round_num or similar - for compat with db activerecord version? why? why not?
|
17
|
+
:leg, ## e.g. '1','2','3','replay', etc. - use leg for marking **replay** too - keep/make leg numeric?! - why? why not?
|
18
|
+
:stage,
|
19
|
+
:group,
|
20
|
+
:status, ## e.g. replay, cancelled, awarded, abadoned, postponed, etc.
|
21
|
+
:conf1, :conf2, ## special case for mls e.g. conference1, conference2 (e.g. west, east, central)
|
22
|
+
:country1, :country2, ## special case for champions league etc. - uses FIFA country code
|
23
|
+
:comments,
|
24
|
+
:league ## (optinal) added as text for now (use struct?)
|
25
|
+
|
26
|
+
|
27
|
+
attr_accessor :goals ## todo/fix: make goals like all other attribs!!
|
28
|
+
|
29
|
+
def initialize( **kwargs )
|
30
|
+
@score1 = @score2 = nil ## full time
|
31
|
+
@score1i = @score2i = nil ## half time (first (i) part)
|
32
|
+
@score1et = @score2et = nil ## extra time
|
33
|
+
@score1p = @score2p = nil ## penalty
|
34
|
+
@score1agg = @score2agg = nil ## full time (all legs) aggregated
|
35
|
+
|
36
|
+
|
37
|
+
update( kwargs ) unless kwargs.empty?
|
38
|
+
end
|
39
|
+
|
40
|
+
def update( **kwargs )
|
41
|
+
## note: check with has_key? because value might be nil!!!
|
42
|
+
@date = kwargs[:date] if kwargs.has_key? :date
|
43
|
+
@time = kwargs[:time] if kwargs.has_key? :time
|
44
|
+
|
45
|
+
## todo/fix: use team1_name, team2_name or similar - for compat with db activerecord version? why? why not?
|
46
|
+
@team1 = kwargs[:team1] if kwargs.has_key? :team1
|
47
|
+
@team2 = kwargs[:team2] if kwargs.has_key? :team2
|
48
|
+
|
49
|
+
@conf1 = kwargs[:conf1] if kwargs.has_key? :conf1
|
50
|
+
@conf2 = kwargs[:conf2] if kwargs.has_key? :conf2
|
51
|
+
@country1 = kwargs[:country1] if kwargs.has_key? :country1
|
52
|
+
@country2 = kwargs[:country2] if kwargs.has_key? :country2
|
53
|
+
|
54
|
+
## note: round is a string!!! e.g. '1', '2' for matchday or 'Final', 'Semi-final', etc.
|
55
|
+
## todo: use to_s - why? why not?
|
56
|
+
@round = kwargs[:round] if kwargs.has_key? :round
|
57
|
+
@stage = kwargs[:stage] if kwargs.has_key? :stage
|
58
|
+
@leg = kwargs[:leg] if kwargs.has_key? :leg
|
59
|
+
@group = kwargs[:group] if kwargs.has_key? :group
|
60
|
+
@status = kwargs[:status] if kwargs.has_key? :status
|
61
|
+
@comments = kwargs[:comments] if kwargs.has_key? :comments
|
62
|
+
|
63
|
+
@league = kwargs[:league] if kwargs.has_key? :league
|
64
|
+
|
65
|
+
|
66
|
+
if kwargs.has_key?( :score ) ## check all-in-one score struct for convenience!!!
|
67
|
+
score = kwargs[:score]
|
68
|
+
if score.nil? ## reset all score attribs to nil!!
|
69
|
+
@score1 = nil
|
70
|
+
@score1i = nil
|
71
|
+
@score1et = nil
|
72
|
+
@score1p = nil
|
73
|
+
## @score1agg = nil
|
74
|
+
|
75
|
+
@score2 = nil
|
76
|
+
@score2i = nil
|
77
|
+
@score2et = nil
|
78
|
+
@score2p = nil
|
79
|
+
## @score2agg = nil
|
80
|
+
else
|
81
|
+
@score1 = score.score1
|
82
|
+
@score1i = score.score1i
|
83
|
+
@score1et = score.score1et
|
84
|
+
@score1p = score.score1p
|
85
|
+
## @score1agg = score.score1agg
|
86
|
+
|
87
|
+
@score2 = score.score2
|
88
|
+
@score2i = score.score2i
|
89
|
+
@score2et = score.score2et
|
90
|
+
@score2p = score.score2p
|
91
|
+
## @score2agg = score.score2agg
|
92
|
+
end
|
93
|
+
else
|
94
|
+
@score1 = kwargs[:score1] if kwargs.has_key? :score1
|
95
|
+
@score1i = kwargs[:score1i] if kwargs.has_key? :score1i
|
96
|
+
@score1et = kwargs[:score1et] if kwargs.has_key? :score1et
|
97
|
+
@score1p = kwargs[:score1p] if kwargs.has_key? :score1p
|
98
|
+
@score1agg = kwargs[:score1agg] if kwargs.has_key? :score1agg
|
99
|
+
|
100
|
+
@score2 = kwargs[:score2] if kwargs.has_key? :score2
|
101
|
+
@score2i = kwargs[:score2i] if kwargs.has_key? :score2i
|
102
|
+
@score2et = kwargs[:score2et] if kwargs.has_key? :score2et
|
103
|
+
@score2p = kwargs[:score2p] if kwargs.has_key? :score2p
|
104
|
+
@score2agg = kwargs[:score2agg] if kwargs.has_key? :score2agg
|
105
|
+
|
106
|
+
## note: (always) (auto-)convert scores to integers
|
107
|
+
@score1 = @score1.to_i if @score1
|
108
|
+
@score1i = @score1i.to_i if @score1i
|
109
|
+
@score1et = @score1et.to_i if @score1et
|
110
|
+
@score1p = @score1p.to_i if @score1p
|
111
|
+
@score1agg = @score1agg.to_i if @score1agg
|
112
|
+
|
113
|
+
@score2 = @score2.to_i if @score2
|
114
|
+
@score2i = @score2i.to_i if @score2i
|
115
|
+
@score2et = @score2et.to_i if @score2et
|
116
|
+
@score2p = @score2p.to_i if @score2p
|
117
|
+
@score2agg = @score2agg.to_i if @score2agg
|
118
|
+
end
|
119
|
+
|
120
|
+
## todo/fix:
|
121
|
+
## gr-greece/2014-15/G1.csv:
|
122
|
+
## G1,10/05/15,Niki Volos,OFI,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
123
|
+
##
|
124
|
+
|
125
|
+
## for now score1 and score2 must be present
|
126
|
+
## if @score1.nil? || @score2.nil?
|
127
|
+
## puts "** WARN: missing scores for match:"
|
128
|
+
## pp kwargs
|
129
|
+
## ## exit 1
|
130
|
+
## end
|
131
|
+
|
132
|
+
## todo/fix: auto-calculate winner
|
133
|
+
# return 1,2,0 1 => team1, 2 => team2, 0 => draw/tie
|
134
|
+
### calculate winner - use 1,2,0
|
135
|
+
##
|
136
|
+
## move winner calc to score class - why? why not?
|
137
|
+
if @score1 && @score2
|
138
|
+
if @score1 > @score2
|
139
|
+
@winner = 1
|
140
|
+
elsif @score2 > @score1
|
141
|
+
@winner = 2
|
142
|
+
elsif @score1 == @score2
|
143
|
+
@winner = 0
|
144
|
+
else
|
145
|
+
end
|
146
|
+
else
|
147
|
+
@winner = nil # unknown / undefined
|
148
|
+
end
|
149
|
+
|
150
|
+
self ## note - MUST return self for chaining
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
|
155
|
+
def over?() true; end ## for now all matches are over - in the future check date!!!
|
156
|
+
def complete?() true; end ## for now all scores are complete - in the future check scores; might be missing - not yet entered
|
157
|
+
|
158
|
+
|
159
|
+
def score
|
160
|
+
Score.new( @score1i, @score2i, ## half time (first (i) part)
|
161
|
+
@score1, @score2, ## full time
|
162
|
+
@score1et, @score2et, ## extra time
|
163
|
+
@score1p, @score2p ) ## penalty
|
164
|
+
end
|
165
|
+
|
166
|
+
|
167
|
+
####
|
168
|
+
## deprecated - use score.to_s and friends - why? why not?
|
169
|
+
# def score_str # pretty print (full time) scores; convenience method
|
170
|
+
# "#{@score1}-#{@score2}"
|
171
|
+
# end
|
172
|
+
|
173
|
+
# def scorei_str # pretty print (half time) scores; convenience method
|
174
|
+
# "#{@score1i}-#{@score2i}"
|
175
|
+
# end
|
176
|
+
end # class Match
|
177
|
+
|
178
|
+
end # module Sports
|
179
|
+
|
180
|
+
|
@@ -0,0 +1,215 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Sports
|
4
|
+
|
5
|
+
|
6
|
+
class Matchlist ## todo: find a better name - MatchStats, MatchFixtures, MatchSchedule, ...
|
7
|
+
## use MatchCache/Buffer/Summary/Snippet/Segment/List...
|
8
|
+
## or MatchAnalyzer/Checker/Proofer/Query - why? why not?
|
9
|
+
attr_reader :matches # count of matches
|
10
|
+
## :name,
|
11
|
+
## :goals, # count of (total) goals - use total_goals - why? why not?
|
12
|
+
## :teams, -- has its own reader
|
13
|
+
## :rounds # note: use if all teams have same match count
|
14
|
+
## add last_updated/updated or something - why? why not?
|
15
|
+
|
16
|
+
def initialize( matches )
|
17
|
+
@matches = matches
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def usage
|
22
|
+
@usage ||= build_usage( @matches )
|
23
|
+
@usage
|
24
|
+
end
|
25
|
+
|
26
|
+
def team_usage() usage.team_usage; end
|
27
|
+
|
28
|
+
def teams
|
29
|
+
@team_names ||= team_usage.keys.sort
|
30
|
+
@team_names
|
31
|
+
end
|
32
|
+
|
33
|
+
def goals() usage.goals; end
|
34
|
+
|
35
|
+
## note: start_date and end_date might be nil / optional missing!!!!
|
36
|
+
def start_date?() usage.start_date?; end
|
37
|
+
def end_date?() usage.end_date?; end
|
38
|
+
|
39
|
+
def start_date() usage.start_date; end
|
40
|
+
def end_date() usage.end_date; end
|
41
|
+
|
42
|
+
def has_dates?() usage.has_dates?; end
|
43
|
+
def dates_str() usage.dates_str; end
|
44
|
+
def days() usage.days; end
|
45
|
+
|
46
|
+
|
47
|
+
def rounds() usage.rounds; end
|
48
|
+
|
49
|
+
## todo: add has_rounds? alias for rounds? too
|
50
|
+
## return true if all match_played in team_usage are the same
|
51
|
+
## e.g. assumes league with matchday rounds
|
52
|
+
def rounds?() usage.rounds?; end
|
53
|
+
|
54
|
+
def match_counts() usage.match_counts; end
|
55
|
+
def match_counts_str() usage.match_counts_str; end
|
56
|
+
|
57
|
+
|
58
|
+
|
59
|
+
def stage_usage
|
60
|
+
@stage_usage ||= build_stage_usage( @matches )
|
61
|
+
@stage_usage
|
62
|
+
end
|
63
|
+
|
64
|
+
def stages() stage_usage.keys; end ## note: returns empty array for stages for now - why? why not?
|
65
|
+
|
66
|
+
|
67
|
+
############################
|
68
|
+
# matchlist helpers
|
69
|
+
private
|
70
|
+
class StatLine
|
71
|
+
attr_reader :team_usage,
|
72
|
+
:matches,
|
73
|
+
:goals,
|
74
|
+
:rounds, ## keep rounds - why? why not?
|
75
|
+
:start_date,
|
76
|
+
:end_date
|
77
|
+
|
78
|
+
def teams() @team_usage.keys.sort; end ## (auto-)sort here always - why? why not?
|
79
|
+
|
80
|
+
def start_date?() @start_date.nil? == false; end
|
81
|
+
def end_date?() @end_date.nil? == false; end
|
82
|
+
|
83
|
+
def has_dates?() @start_date && @end_date; end
|
84
|
+
def dates_str
|
85
|
+
## note: start_date/end_date might be optional/missing
|
86
|
+
if has_dates?
|
87
|
+
"#{start_date.strftime( '%a %d %b %Y' )} - #{end_date.strftime( '%a %d %b %Y' )}"
|
88
|
+
else
|
89
|
+
"??? - ???"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def days() end_date.jd - start_date.jd; end
|
94
|
+
|
95
|
+
|
96
|
+
def rounds
|
97
|
+
rounds? ## note: use rounds? to calculate (cache) rounds
|
98
|
+
@rounds ## note: return number of rounds or nil (for uneven matches played by teams)
|
99
|
+
end
|
100
|
+
|
101
|
+
## todo: add has_rounds? alias for rounds? too
|
102
|
+
def rounds?
|
103
|
+
## return true if all match_played in team_usage are the same
|
104
|
+
## e.g. assumes league with matchday rounds
|
105
|
+
if @has_rounds.nil? ## check/todo: if undefined attribute is nil by default??
|
106
|
+
## check/calc rounds
|
107
|
+
## note: values => matches_played by team
|
108
|
+
if match_counts.size == 1
|
109
|
+
@rounds = match_counts[0][0]
|
110
|
+
else
|
111
|
+
@rounds = nil
|
112
|
+
end
|
113
|
+
@has_rounds = @rounds ? true : false
|
114
|
+
end
|
115
|
+
@has_rounds
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
def build_match_counts ## use/rename to matches_played - why? why not?
|
120
|
+
counts = Hash.new(0)
|
121
|
+
team_usage.values.each do |count|
|
122
|
+
counts[count] += 1
|
123
|
+
end
|
124
|
+
|
125
|
+
## sort (descending) highest usage value first (in returned array)
|
126
|
+
## e.g. [[32,8],[31,2]] ## 32 matches by 8 teams, 31 matches by 2 teams etc.
|
127
|
+
counts.sort_by {|count, usage| -count }
|
128
|
+
end
|
129
|
+
|
130
|
+
def match_counts
|
131
|
+
# match counts / nos played per team
|
132
|
+
@match_counts ||= build_match_counts
|
133
|
+
@match_counts
|
134
|
+
end
|
135
|
+
|
136
|
+
def match_counts_str
|
137
|
+
## pretty print / formatted match_counts
|
138
|
+
buf = String.new('')
|
139
|
+
match_counts.each_with_index do |rec,i|
|
140
|
+
buf << ' ' if i > 0 ## add (space) separator
|
141
|
+
buf << "#{rec[0]}×#{rec[1]}"
|
142
|
+
end
|
143
|
+
buf
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
|
148
|
+
def initialize
|
149
|
+
@matches = 0
|
150
|
+
@goals = 0
|
151
|
+
|
152
|
+
@start_date = nil
|
153
|
+
@end_date = nil
|
154
|
+
|
155
|
+
@team_usage = Hash.new(0)
|
156
|
+
|
157
|
+
@match_counts = nil
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
def update( match )
|
162
|
+
@matches += 1 ## match counter
|
163
|
+
|
164
|
+
if match.score1 && match.score2
|
165
|
+
@goals += match.score1
|
166
|
+
@goals += match.score2
|
167
|
+
|
168
|
+
## todo: add after extra time? if knock out (k.o.) - why? why not?
|
169
|
+
## make it a flag/opt?
|
170
|
+
end
|
171
|
+
|
172
|
+
@team_usage[ match.team1 ] += 1
|
173
|
+
@team_usage[ match.team2 ] += 1
|
174
|
+
|
175
|
+
if match.date
|
176
|
+
## return / store date as string as is - why? why not?
|
177
|
+
date = Date.strptime( match.date, '%Y-%m-%d' )
|
178
|
+
|
179
|
+
@start_date = date if @start_date.nil? || date < @start_date
|
180
|
+
@end_date = date if @end_date.nil? || date > @end_date
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end # class StatLine
|
184
|
+
|
185
|
+
|
186
|
+
## collect total usage stats (for all matches)
|
187
|
+
def build_usage( matches )
|
188
|
+
stat = StatLine.new
|
189
|
+
matches.each do |match|
|
190
|
+
stat.update( match )
|
191
|
+
end
|
192
|
+
stat
|
193
|
+
end
|
194
|
+
|
195
|
+
## collect usage stats by stage (e.g. regular / playoff / etc.)
|
196
|
+
def build_stage_usage( matches )
|
197
|
+
stages = {}
|
198
|
+
|
199
|
+
matches.each do |match|
|
200
|
+
stage_key = if match.stage.nil?
|
201
|
+
'Regular' ## note: assume Regular stage if not defined (AND not explicit unknown)
|
202
|
+
else
|
203
|
+
match.stage
|
204
|
+
end
|
205
|
+
|
206
|
+
stages[ stage_key ] ||= StatLine.new
|
207
|
+
stages[ stage_key ].update( match )
|
208
|
+
end
|
209
|
+
|
210
|
+
stages
|
211
|
+
end
|
212
|
+
|
213
|
+
end # class Matchlist
|
214
|
+
|
215
|
+
end # module Sports
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Sports
|
2
|
+
|
3
|
+
class Round
|
4
|
+
attr_reader :name, :start_date, :end_date, :knockout
|
5
|
+
attr_accessor :num # note: make read & writable - why? why not?
|
6
|
+
|
7
|
+
def initialize( name:,
|
8
|
+
num: nil,
|
9
|
+
start_date: nil,
|
10
|
+
end_date: nil,
|
11
|
+
knockout: false,
|
12
|
+
auto: true )
|
13
|
+
@name = name
|
14
|
+
@num = num
|
15
|
+
@start_date = start_date
|
16
|
+
@end_date = end_date
|
17
|
+
@knockout = knockout
|
18
|
+
@auto = auto # auto-created (inline reference/header without proper definition before)
|
19
|
+
end
|
20
|
+
end # class Round
|
21
|
+
|
22
|
+
end # module Sports
|
23
|
+
|