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