sportdb-structs 0.1.0

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.
@@ -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
+