sportdb-formats 0.2.1 → 0.3.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 +4 -4
- data/Manifest.txt +10 -1
- data/Rakefile +1 -1
- data/lib/sportdb/formats/season_utils.rb +12 -101
- data/lib/sportdb/formats/structs/club.rb +221 -0
- data/lib/sportdb/formats/structs/match.rb +131 -0
- data/lib/sportdb/formats/structs/matchlist.rb +223 -0
- data/lib/sportdb/formats/structs/season.rb +123 -0
- data/lib/sportdb/formats/structs/standings.rb +250 -0
- data/lib/sportdb/formats/structs/team_usage.rb +91 -0
- data/lib/sportdb/formats/version.rb +2 -2
- data/lib/sportdb/formats.rb +7 -0
- data/test/test_club_helpers.rb +63 -0
- data/test/test_clubs.rb +40 -0
- data/test/test_match.rb +36 -0
- data/test/test_season.rb +62 -0
- metadata +14 -5
- data/test/test_season_utils.rb +0 -29
@@ -0,0 +1,223 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
|
4
|
+
##
|
5
|
+
# note: add all "former" structs to the SportDb::Import module / namespace
|
6
|
+
|
7
|
+
module SportDb
|
8
|
+
module Import
|
9
|
+
|
10
|
+
|
11
|
+
class Matchlist ## todo: find a better name - MatchStats, MatchFixtures, MatchSchedule, ...
|
12
|
+
## use MatchCache/Buffer/Summary/Snippet/Segment/List...
|
13
|
+
## or MatchAnalyzer/Checker/Proofer/Query - why? why not?
|
14
|
+
attr_reader :matches # count of matches
|
15
|
+
## :name,
|
16
|
+
## :goals, # count of (total) goals - use total_goals - why? why not?
|
17
|
+
## :teams, -- has its own reader
|
18
|
+
## :rounds # note: use if all teams have same match count
|
19
|
+
## add last_updated/updated or something - why? why not?
|
20
|
+
|
21
|
+
def initialize( matches )
|
22
|
+
@matches = matches
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def usage
|
27
|
+
@usage ||= build_usage( @matches )
|
28
|
+
@usage
|
29
|
+
end
|
30
|
+
|
31
|
+
def team_usage() usage.team_usage; end
|
32
|
+
|
33
|
+
def teams
|
34
|
+
@team_names ||= team_usage.keys.sort
|
35
|
+
@team_names
|
36
|
+
end
|
37
|
+
|
38
|
+
def goals() usage.goals; end
|
39
|
+
|
40
|
+
## note: start_date and end_date might be nil / optional missing!!!!
|
41
|
+
def start_date?() usage.start_date?; end
|
42
|
+
def end_date?() usage.end_date?; end
|
43
|
+
|
44
|
+
def start_date() usage.start_date; end
|
45
|
+
def end_date() usage.end_date; end
|
46
|
+
|
47
|
+
def has_dates?() usage.has_dates?; end
|
48
|
+
def dates_str() usage.dates_str; end
|
49
|
+
def days() usage.days; end
|
50
|
+
|
51
|
+
|
52
|
+
def rounds() usage.rounds; end
|
53
|
+
|
54
|
+
## todo: add has_rounds? alias for rounds? too
|
55
|
+
## return true if all match_played in team_usage are the same
|
56
|
+
## e.g. assumes league with matchday rounds
|
57
|
+
def rounds?() usage.rounds?; end
|
58
|
+
|
59
|
+
def match_counts() usage.match_counts; end
|
60
|
+
def match_counts_str() usage.match_counts_str; end
|
61
|
+
|
62
|
+
|
63
|
+
|
64
|
+
def stage_usage
|
65
|
+
@stage_usage ||= build_stage_usage( @matches )
|
66
|
+
@stage_usage
|
67
|
+
end
|
68
|
+
|
69
|
+
def stages() stage_usage.keys; end ## note: returns empty array for stages for now - why? why not?
|
70
|
+
|
71
|
+
|
72
|
+
############################
|
73
|
+
# matchlist helpers
|
74
|
+
private
|
75
|
+
class StatLine
|
76
|
+
attr_reader :team_usage,
|
77
|
+
:matches,
|
78
|
+
:goals,
|
79
|
+
:rounds, ## keep rounds - why? why not?
|
80
|
+
:start_date,
|
81
|
+
:end_date
|
82
|
+
|
83
|
+
def teams() @team_usage.keys.sort; end ## (auto-)sort here always - why? why not?
|
84
|
+
|
85
|
+
def start_date?() @start_date.nil? == false; end
|
86
|
+
def end_date?() @end_date.nil? == false; end
|
87
|
+
|
88
|
+
def has_dates?() @start_date && @end_date; end
|
89
|
+
def dates_str
|
90
|
+
## note: start_date/end_date might be optional/missing
|
91
|
+
if has_dates?
|
92
|
+
"#{start_date.strftime( '%a %d %b %Y' )} - #{end_date.strftime( '%a %d %b %Y' )}"
|
93
|
+
else
|
94
|
+
"??? - ???"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def days() end_date.jd - start_date.jd; end
|
99
|
+
|
100
|
+
|
101
|
+
def rounds
|
102
|
+
rounds? ## note: use rounds? to calculate (cache) rounds
|
103
|
+
@rounds ## note: return number of rounds or nil (for uneven matches played by teams)
|
104
|
+
end
|
105
|
+
|
106
|
+
## todo: add has_rounds? alias for rounds? too
|
107
|
+
def rounds?
|
108
|
+
## return true if all match_played in team_usage are the same
|
109
|
+
## e.g. assumes league with matchday rounds
|
110
|
+
if @has_rounds.nil? ## check/todo: if undefined attribute is nil by default??
|
111
|
+
## check/calc rounds
|
112
|
+
## note: values => matches_played by team
|
113
|
+
if match_counts.size == 1
|
114
|
+
@rounds = match_counts[0][0]
|
115
|
+
else
|
116
|
+
@rounds = nil
|
117
|
+
end
|
118
|
+
@has_rounds = @rounds ? true : false
|
119
|
+
end
|
120
|
+
@has_rounds
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
def build_match_counts ## use/rename to matches_played - why? why not?
|
125
|
+
counts = Hash.new(0)
|
126
|
+
team_usage.values.each do |count|
|
127
|
+
counts[count] += 1
|
128
|
+
end
|
129
|
+
|
130
|
+
## sort (descending) highest usage value first (in returned array)
|
131
|
+
## e.g. [[32,8],[31,2]] ## 32 matches by 8 teams, 31 matches by 2 teams etc.
|
132
|
+
counts.sort_by {|count, usage| -count }
|
133
|
+
end
|
134
|
+
|
135
|
+
def match_counts
|
136
|
+
# match counts / nos played per team
|
137
|
+
@match_counts ||= build_match_counts
|
138
|
+
@match_counts
|
139
|
+
end
|
140
|
+
|
141
|
+
def match_counts_str
|
142
|
+
## pretty print / formatted match_counts
|
143
|
+
buf = String.new('')
|
144
|
+
match_counts.each_with_index do |rec,i|
|
145
|
+
buf << ' ' if i > 0 ## add (space) separator
|
146
|
+
buf << "#{rec[0]}×#{rec[1]}"
|
147
|
+
end
|
148
|
+
buf
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
|
153
|
+
def initialize
|
154
|
+
@matches = 0
|
155
|
+
@goals = 0
|
156
|
+
|
157
|
+
@start_date = nil
|
158
|
+
@end_date = nil
|
159
|
+
|
160
|
+
@team_usage = Hash.new(0)
|
161
|
+
|
162
|
+
@match_counts = nil
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
def update( match )
|
167
|
+
@matches += 1 ## match counter
|
168
|
+
|
169
|
+
if match.score1 && match.score2
|
170
|
+
@goals += match.score1
|
171
|
+
@goals += match.score2
|
172
|
+
|
173
|
+
## todo: add after extra time? if knock out (k.o.) - why? why not?
|
174
|
+
## make it a flag/opt?
|
175
|
+
end
|
176
|
+
|
177
|
+
@team_usage[ match.team1 ] += 1
|
178
|
+
@team_usage[ match.team2 ] += 1
|
179
|
+
|
180
|
+
if match.date
|
181
|
+
## return / store date as string as is - why? why not?
|
182
|
+
date = Date.strptime( match.date, '%Y-%m-%d' )
|
183
|
+
|
184
|
+
@start_date = date if @start_date.nil? || date < @start_date
|
185
|
+
@end_date = date if @end_date.nil? || date > @end_date
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end # class StatLine
|
189
|
+
|
190
|
+
|
191
|
+
## collect total usage stats (for all matches)
|
192
|
+
def build_usage( matches )
|
193
|
+
stat = StatLine.new
|
194
|
+
matches.each do |match|
|
195
|
+
stat.update( match )
|
196
|
+
end
|
197
|
+
stat
|
198
|
+
end
|
199
|
+
|
200
|
+
## collect usage stats by stage (e.g. regular / playoff / etc.)
|
201
|
+
def build_stage_usage( matches )
|
202
|
+
stages = {}
|
203
|
+
|
204
|
+
matches.each do |match|
|
205
|
+
stage_key = if match.stage.nil?
|
206
|
+
'Regular' ## note: assume Regular stage if not defined (AND not explicit unknown)
|
207
|
+
else
|
208
|
+
match.stage
|
209
|
+
end
|
210
|
+
|
211
|
+
stages[ stage_key ] ||= StatLine.new
|
212
|
+
stages[ stage_key ].update( match )
|
213
|
+
end
|
214
|
+
|
215
|
+
stages
|
216
|
+
end
|
217
|
+
|
218
|
+
end # class Matchlist
|
219
|
+
|
220
|
+
|
221
|
+
|
222
|
+
end # module Import
|
223
|
+
end # module SportDb
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
|
4
|
+
##
|
5
|
+
# note: add all "former" structs to the SportDb::Import module / namespace
|
6
|
+
|
7
|
+
module SportDb
|
8
|
+
module Import
|
9
|
+
|
10
|
+
|
11
|
+
class Season
|
12
|
+
##
|
13
|
+
## todo: add (optional) start_date and end_date - why? why not?
|
14
|
+
## add next
|
15
|
+
|
16
|
+
|
17
|
+
attr_reader :start_year,
|
18
|
+
:end_year
|
19
|
+
|
20
|
+
def year?() @end_year.nil?; end ## single-year season e.g. 2011 if no end_year present
|
21
|
+
|
22
|
+
|
23
|
+
def initialize( str ) ## assume only string / line gets passed in for now
|
24
|
+
@start_year, @end_year = parse( str )
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
YYYY_YYYY_RE = %r{^ ## e.g. 2011-2012 or 2011/2012
|
29
|
+
(\d{4})
|
30
|
+
[/-]
|
31
|
+
(\d{4})
|
32
|
+
$
|
33
|
+
}x
|
34
|
+
YYYY_YY_RE = %r{^ ## e.g. 2011-12 or 2011/12
|
35
|
+
(\d{4})
|
36
|
+
[/-]
|
37
|
+
(\d{2})
|
38
|
+
$
|
39
|
+
}x
|
40
|
+
YYYY_Y_RE = %r{^ ## e.g. 2011-2 or 2011/2
|
41
|
+
(\d{4})
|
42
|
+
[/-]
|
43
|
+
(\d{1})
|
44
|
+
$
|
45
|
+
}x
|
46
|
+
YYYY_RE = %r{^ ## e.g. 2011
|
47
|
+
(\d{4})
|
48
|
+
$
|
49
|
+
}x
|
50
|
+
|
51
|
+
def parse( str )
|
52
|
+
if str =~ YYYY_YYYY_RE ## e.g. 2011/2012
|
53
|
+
[$1.to_i, $2.to_i]
|
54
|
+
elsif str =~ YYYY_YY_RE ## e.g. 2011/12
|
55
|
+
fst = $1.to_i
|
56
|
+
snd = $2.to_i
|
57
|
+
snd_exp = '%02d' % [(fst+1) % 100] ## double check: e.g 00 == 00, 01==01 etc.
|
58
|
+
raise ArgumentError.new( "[Season#parse] invalid year in season >>#{str}<<; expected #{snd_exp} but got #{$2}") if snd_exp != $2
|
59
|
+
[fst, fst+1]
|
60
|
+
elsif str =~ YYYY_Y_RE ## e.g. 2011/2
|
61
|
+
fst = $1.to_i
|
62
|
+
snd = $2.to_i
|
63
|
+
snd_exp = '%d' % [(fst+1) % 10] ## double check: e.g 0 == 0, 1==1 etc.
|
64
|
+
raise ArgumentError.new( "[Season#parse] invalid year in season >>#{str}<<; expected #{snd_exp} but got #{$2}") if snd_exp != $2
|
65
|
+
[fst, fst+1]
|
66
|
+
elsif str =~ YYYY_RE ## e.g. 2011
|
67
|
+
[$1.to_i]
|
68
|
+
else
|
69
|
+
raise ArgumentError.new( "[Season#parse] unkown season format >>#{str}<<; sorry cannot parse")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
def prev
|
76
|
+
if year?
|
77
|
+
Season.new( "#{@start_year-1}" )
|
78
|
+
else
|
79
|
+
Season.new( "#{@start_year-1}/#{@start_year}" )
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def key
|
84
|
+
if year?
|
85
|
+
'%d' % @start_year
|
86
|
+
else
|
87
|
+
'%d/%02d' % [@start_year, @end_year % 100]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
alias_method :to_key, :key
|
91
|
+
alias_method :to_s, :key
|
92
|
+
|
93
|
+
|
94
|
+
def path( format: nil )
|
95
|
+
## todo: find better names for formats - why? why not?:
|
96
|
+
## long | archive | decade(?) => 1980s/1988-89, 2010s/2017-18, ...
|
97
|
+
## short | std(?) => 1988-89, 2017-18, ...
|
98
|
+
|
99
|
+
## convert season name to "standard" season name for directory
|
100
|
+
|
101
|
+
if ['l', 'long', 'archive' ].include?( format.to_s ) ## note: allow passing in of symbol to e.g. 'long' or :long
|
102
|
+
if year? ## e.g. 2000s/2001
|
103
|
+
"%3d0s/%4d" % [@start_year / 10, @start_year]
|
104
|
+
else ## e.g. 2000s/2001-02
|
105
|
+
"%3d0s/%4d-%02d" % [@start_year / 10, @start_year, @end_year % 100]
|
106
|
+
end
|
107
|
+
else ## default 'short' format / fallback
|
108
|
+
if year? ## e.g. 2001
|
109
|
+
"%4d" % @start_year
|
110
|
+
else ## e.g. 2001-02
|
111
|
+
"%4d-%02d" % [@start_year, @end_year % 100]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end # method path
|
115
|
+
alias_method :directory, :path ## keep "legacy" directory alias - why? why not?
|
116
|
+
alias_method :to_path, :path
|
117
|
+
|
118
|
+
|
119
|
+
end # class Season
|
120
|
+
|
121
|
+
|
122
|
+
end # module Import
|
123
|
+
end # module SportDb
|
@@ -0,0 +1,250 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
##########
|
4
|
+
# todo/fix:
|
5
|
+
## reuse standings helper/calculator from sportdb
|
6
|
+
## do NOT duplicate
|
7
|
+
|
8
|
+
|
9
|
+
##
|
10
|
+
# note: add all "former" structs to the SportDb::Import module / namespace
|
11
|
+
|
12
|
+
module SportDb
|
13
|
+
module Import
|
14
|
+
|
15
|
+
|
16
|
+
class Standings
|
17
|
+
|
18
|
+
class StandingsLine ## nested class StandinsLine
|
19
|
+
attr_accessor :rank, :team,
|
20
|
+
:played, :won, :lost, :drawn, ## -- total
|
21
|
+
:goals_for, :goals_against, :pts,
|
22
|
+
:home_played, :home_won, :home_lost, :home_drawn, ## -- home
|
23
|
+
:home_goals_for, :home_goals_against, :home_pts,
|
24
|
+
:away_played, :away_won, :away_lost, :away_drawn, ## -- away
|
25
|
+
:away_goals_for, :away_goals_against, :away_pts
|
26
|
+
|
27
|
+
def initialize( team )
|
28
|
+
@rank = nil # use 0? why? why not?
|
29
|
+
@team = team
|
30
|
+
@played = @home_played = @away_played = 0
|
31
|
+
@won = @home_won = @away_won = 0
|
32
|
+
@lost = @home_lost = @away_lost = 0
|
33
|
+
@drawn = @home_drawn = @away_drawn = 0
|
34
|
+
@goals_for = @home_goals_for = @away_goals_for = 0
|
35
|
+
@goals_against = @home_goals_against = @away_goals_against = 0
|
36
|
+
@pts = @home_pts = @away_pts = 0
|
37
|
+
end
|
38
|
+
end # (nested) class StandingsLine
|
39
|
+
|
40
|
+
|
41
|
+
def initialize( opts={} )
|
42
|
+
## fix:
|
43
|
+
# passing in e.g. pts for win (3? 2? etc.)
|
44
|
+
# default to 3 for now
|
45
|
+
|
46
|
+
## lets you pass in 2 as an alterantive, for example
|
47
|
+
@pts_won = opts[:pts_won] || 3
|
48
|
+
|
49
|
+
@lines = {} # StandingsLines cached by team name/key
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
def update( match_or_matches )
|
54
|
+
## convenience - update all matches at once
|
55
|
+
matches = match_or_matches.is_a?(Array) ? match_or_matches : [match_or_matches]
|
56
|
+
|
57
|
+
matches.each_with_index do |match,i| # note: index(i) starts w/ zero (0)
|
58
|
+
update_match( match )
|
59
|
+
end
|
60
|
+
self # note: return self to allow chaining
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
def to_a
|
65
|
+
## return lines; sort and add rank
|
66
|
+
## note: will update rank!!!! (side effect)
|
67
|
+
|
68
|
+
#############################
|
69
|
+
### calc ranking position (rank)
|
70
|
+
## fix/allow same rank e.g. all 1 or more than one team 3rd etc.
|
71
|
+
|
72
|
+
# build array from hash
|
73
|
+
ary = []
|
74
|
+
@lines.each do |k,v|
|
75
|
+
ary << v
|
76
|
+
end
|
77
|
+
|
78
|
+
ary.sort! do |l,r|
|
79
|
+
## note: reverse order (thus, change l,r to r,l)
|
80
|
+
value = r.pts <=> l.pts
|
81
|
+
if value == 0 # same pts try goal diff
|
82
|
+
value = (r.goals_for-r.goals_against) <=> (l.goals_for-l.goals_against)
|
83
|
+
if value == 0 # same goal diff too; try assume more goals better for now
|
84
|
+
value = r.goals_for <=> l.goals_for
|
85
|
+
end
|
86
|
+
end
|
87
|
+
value
|
88
|
+
end
|
89
|
+
|
90
|
+
## update rank using ordered array
|
91
|
+
ary.each_with_index do |line,i|
|
92
|
+
line.rank = i+1 ## add ranking (e.g. 1,2,3 etc.) - note: i starts w/ zero (0)
|
93
|
+
end
|
94
|
+
|
95
|
+
ary
|
96
|
+
end # to_a
|
97
|
+
|
98
|
+
|
99
|
+
|
100
|
+
#####
|
101
|
+
###
|
102
|
+
## fix: move build to StandingsPart/Report !!!!
|
103
|
+
def build( source: nil ) ## build / pretty print standings table in string buffer
|
104
|
+
## keep pretty printer in struct - why? why not?
|
105
|
+
|
106
|
+
|
107
|
+
## add standings table in markdown to buffer (buf)
|
108
|
+
|
109
|
+
## todo: use different styles/formats (simple/ etc ???)
|
110
|
+
|
111
|
+
## simple table (only totals - no home/away)
|
112
|
+
## standings.to_a.each do |l|
|
113
|
+
## buf << '%2d. ' % l.rank
|
114
|
+
## buf << '%-28s ' % l.team
|
115
|
+
## buf << '%2d ' % l.played
|
116
|
+
## buf << '%3d ' % l.won
|
117
|
+
## buf << '%3d ' % l.drawn
|
118
|
+
## buf << '%3d ' % l.lost
|
119
|
+
## buf << '%3d:%-3d ' % [l.goals_for,l.goals_against]
|
120
|
+
## buf << '%3d' % l.pts
|
121
|
+
## buf << "\n"
|
122
|
+
## end
|
123
|
+
|
124
|
+
buf = ''
|
125
|
+
buf << "\n"
|
126
|
+
buf << "```\n"
|
127
|
+
buf << " - Home - - Away - - Total -\n"
|
128
|
+
buf << " Pld W D L F:A W D L F:A F:A +/- Pts\n"
|
129
|
+
|
130
|
+
to_a.each do |l|
|
131
|
+
buf << '%2d. ' % l.rank
|
132
|
+
buf << '%-28s ' % l.team
|
133
|
+
buf << '%2d ' % l.played
|
134
|
+
|
135
|
+
buf << '%2d ' % l.home_won
|
136
|
+
buf << '%2d ' % l.home_drawn
|
137
|
+
buf << '%2d ' % l.home_lost
|
138
|
+
buf << '%3d:%-3d ' % [l.home_goals_for,l.home_goals_against]
|
139
|
+
|
140
|
+
buf << '%2d ' % l.away_won
|
141
|
+
buf << '%2d ' % l.away_drawn
|
142
|
+
buf << '%2d ' % l.away_lost
|
143
|
+
buf << '%3d:%-3d ' % [l.away_goals_for,l.away_goals_against]
|
144
|
+
|
145
|
+
buf << '%3d:%-3d ' % [l.goals_for,l.goals_against]
|
146
|
+
|
147
|
+
goals_diff = l.goals_for-l.goals_against
|
148
|
+
if goals_diff > 0
|
149
|
+
buf << '%3s ' % "+#{goals_diff}"
|
150
|
+
elsif goals_diff < 0
|
151
|
+
buf << '%3s ' % "#{goals_diff}"
|
152
|
+
else ## assume 0
|
153
|
+
buf << ' '
|
154
|
+
end
|
155
|
+
|
156
|
+
buf << '%3d' % l.pts
|
157
|
+
buf << "\n"
|
158
|
+
end
|
159
|
+
|
160
|
+
buf << "```\n"
|
161
|
+
buf << "\n"
|
162
|
+
|
163
|
+
## optinal: add data source if known / present
|
164
|
+
## assume (relative) markdown link for now in README.md
|
165
|
+
if source
|
166
|
+
buf << "(Source: [`#{source}`](#{source}))\n"
|
167
|
+
buf << "\n"
|
168
|
+
end
|
169
|
+
|
170
|
+
buf
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
private
|
175
|
+
def update_match( m ) ## add a match
|
176
|
+
|
177
|
+
## puts " #{m.team1} - #{m.team2} #{m.score_str}"
|
178
|
+
unless m.over?
|
179
|
+
puts " !!!! skipping match - not yet over (play_at date in the future)"
|
180
|
+
return
|
181
|
+
end
|
182
|
+
|
183
|
+
unless m.complete?
|
184
|
+
puts "!!! [calc_standings] skipping match #{m.team1} - #{m.team2} - scores incomplete #{m.score_str}"
|
185
|
+
return
|
186
|
+
end
|
187
|
+
|
188
|
+
line1 = @lines[ m.team1 ] || StandingsLine.new( m.team1 )
|
189
|
+
line2 = @lines[ m.team2 ] || StandingsLine.new( m.team2 )
|
190
|
+
|
191
|
+
line1.played += 1
|
192
|
+
line1.home_played += 1
|
193
|
+
|
194
|
+
line2.played += 1
|
195
|
+
line2.away_played += 1
|
196
|
+
|
197
|
+
if m.winner == 1
|
198
|
+
line1.won += 1
|
199
|
+
line1.home_won += 1
|
200
|
+
|
201
|
+
line2.lost += 1
|
202
|
+
line2.away_lost += 1
|
203
|
+
|
204
|
+
line1.pts += @pts_won
|
205
|
+
line1.home_pts += @pts_won
|
206
|
+
elsif m.winner == 2
|
207
|
+
line1.lost += 1
|
208
|
+
line1.home_lost += 1
|
209
|
+
|
210
|
+
line2.won += 1
|
211
|
+
line2.away_won += 1
|
212
|
+
|
213
|
+
line2.pts += @pts_won
|
214
|
+
line2.away_pts += @pts_won
|
215
|
+
else ## assume drawn/tie (that is, 0)
|
216
|
+
line1.drawn += 1
|
217
|
+
line1.home_drawn += 1
|
218
|
+
|
219
|
+
line2.drawn += 1
|
220
|
+
line2.away_drawn += 1
|
221
|
+
|
222
|
+
line1.pts += 1
|
223
|
+
line1.home_pts += 1
|
224
|
+
line2.pts += 1
|
225
|
+
line2.away_pts += 1
|
226
|
+
end
|
227
|
+
|
228
|
+
if m.score1 && m.score2
|
229
|
+
line1.goals_for += m.score1
|
230
|
+
line1.home_goals_for += m.score1
|
231
|
+
line1.goals_against += m.score2
|
232
|
+
line1.home_goals_against += m.score2
|
233
|
+
|
234
|
+
line2.goals_for += m.score2
|
235
|
+
line2.away_goals_for += m.score2
|
236
|
+
line2.goals_against += m.score1
|
237
|
+
line2.away_goals_against += m.score1
|
238
|
+
else
|
239
|
+
puts "*** warn: [standings] skipping match with missing scores: #{m.inspect}"
|
240
|
+
end
|
241
|
+
|
242
|
+
@lines[ m.team1 ] = line1
|
243
|
+
@lines[ m.team2 ] = line2
|
244
|
+
end # method update_match
|
245
|
+
|
246
|
+
end # class Standings
|
247
|
+
|
248
|
+
|
249
|
+
end # module Import
|
250
|
+
end # module SportDb
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
|
4
|
+
##
|
5
|
+
# note: add all "former" structs to the SportDb::Import module / namespace
|
6
|
+
|
7
|
+
module SportDb
|
8
|
+
module Import
|
9
|
+
|
10
|
+
|
11
|
+
class TeamUsage
|
12
|
+
|
13
|
+
class TeamUsageLine ## nested class
|
14
|
+
attr_accessor :team,
|
15
|
+
:matches, ## number of matches (played),
|
16
|
+
:seasons, ## (optianl) array of seasons, use seasons.size for count
|
17
|
+
:levels ## (optional) hash of levels (holds mapping level to TeamUsageLine)
|
18
|
+
|
19
|
+
def initialize( team )
|
20
|
+
@team = team
|
21
|
+
|
22
|
+
@matches = 0
|
23
|
+
@seasons = []
|
24
|
+
@levels = {}
|
25
|
+
end
|
26
|
+
end # (nested) class TeamUsageLine
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
def initialize( opts={} )
|
31
|
+
@lines = {} # StandingsLines cached by team name/key
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
def update( matches, season: '?', level: nil )
|
36
|
+
## convenience - update all matches at once
|
37
|
+
matches.each_with_index do |match,i| # note: index(i) starts w/ zero (0)
|
38
|
+
update_match( match, season: season, level: level )
|
39
|
+
end
|
40
|
+
self # note: return self to allow chaining
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_a
|
44
|
+
## return lines; sort
|
45
|
+
|
46
|
+
# build array from hash
|
47
|
+
ary = []
|
48
|
+
@lines.each do |k,v|
|
49
|
+
ary << v
|
50
|
+
end
|
51
|
+
|
52
|
+
## for now sort just by name (a-z)
|
53
|
+
ary.sort! do |l,r|
|
54
|
+
## note: reverse order (thus, change l,r to r,l)
|
55
|
+
l.team <=> r.team
|
56
|
+
end
|
57
|
+
|
58
|
+
ary
|
59
|
+
end # to_a
|
60
|
+
|
61
|
+
|
62
|
+
private
|
63
|
+
def update_match( m, season: '?', level: nil ) ## add a match
|
64
|
+
|
65
|
+
line1 = @lines[ m.team1 ] ||= TeamUsageLine.new( m.team1 )
|
66
|
+
line2 = @lines[ m.team2 ] ||= TeamUsageLine.new( m.team2 )
|
67
|
+
|
68
|
+
line1.matches +=1
|
69
|
+
line2.matches +=1
|
70
|
+
|
71
|
+
## include season if not seen before (allow season in multiple files!!!)
|
72
|
+
line1.seasons << season unless line1.seasons.include?( season )
|
73
|
+
line2.seasons << season unless line2.seasons.include?( season )
|
74
|
+
|
75
|
+
if level
|
76
|
+
line1_level = line1.levels[ level ] ||= TeamUsageLine.new( m.team1 )
|
77
|
+
line2_level = line2.levels[ level ] ||= TeamUsageLine.new( m.team2 )
|
78
|
+
|
79
|
+
line1_level.matches +=1
|
80
|
+
line2_level.matches +=1
|
81
|
+
|
82
|
+
line1_level.seasons << season unless line1_level.seasons.include?( season )
|
83
|
+
line2_level.seasons << season unless line2_level.seasons.include?( season )
|
84
|
+
end
|
85
|
+
end # method update_match
|
86
|
+
|
87
|
+
|
88
|
+
end # class TeamUsage
|
89
|
+
|
90
|
+
end # module Import
|
91
|
+
end # module SportDb
|
data/lib/sportdb/formats.rb
CHANGED
@@ -31,6 +31,13 @@ require 'sportdb/formats/datafile'
|
|
31
31
|
require 'sportdb/formats/package'
|
32
32
|
require 'sportdb/formats/season_utils'
|
33
33
|
|
34
|
+
require 'sportdb/formats/structs/season'
|
35
|
+
require 'sportdb/formats/structs/club'
|
36
|
+
require 'sportdb/formats/structs/match'
|
37
|
+
require 'sportdb/formats/structs/matchlist'
|
38
|
+
require 'sportdb/formats/structs/standings'
|
39
|
+
require 'sportdb/formats/structs/team_usage'
|
40
|
+
|
34
41
|
|
35
42
|
require 'sportdb/formats/scores'
|
36
43
|
require 'sportdb/formats/goals'
|