sportdb-structs 0.1.0

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