sportdb-models 1.14.0 → 1.14.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Manifest.txt +153 -148
- data/lib/sportdb/finders/date.rb +153 -81
- data/lib/sportdb/models.rb +3 -0
- data/lib/sportdb/rsssf_reader.rb +320 -0
- data/lib/sportdb/utils_date.rb +6 -0
- data/lib/sportdb/version.rb +1 -1
- data/test/data/at-austria/2014_15/1-bundesliga.yml +18 -0
- data/test/data/at-austria/2015_16/1-bundesliga.yml +1 -5
- data/test/data/at-austria/teams.txt +8 -15
- data/test/data/at-austria/teams_2.txt +3 -16
- data/test/data/rsssf/at-2014-15--1-bundesliga.txt +339 -0
- data/test/data/rsssf/at-2015-16--1-bundesliga.txt +18 -0
- data/test/test_rsssf_reader.rb +76 -0
- metadata +9 -25
@@ -0,0 +1,320 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
##
|
4
|
+
#
|
5
|
+
# todo/fix: cleanup, remove stuff not needed for "simple" rsssf format/style
|
6
|
+
#
|
7
|
+
## for now lets only support leagues with rounds (no cups/knockout rounds n groups)
|
8
|
+
## (re)add later when needed (e.g. for playoffs etc.)
|
9
|
+
|
10
|
+
|
11
|
+
module SportDb
|
12
|
+
|
13
|
+
|
14
|
+
class RsssfGameReader
|
15
|
+
|
16
|
+
include LogUtils::Logging
|
17
|
+
|
18
|
+
## make models available by default with namespace
|
19
|
+
# e.g. lets you use Usage instead of Model::Usage
|
20
|
+
include Models
|
21
|
+
|
22
|
+
## value helpers e.g. is_year?, is_taglist? etc.
|
23
|
+
include TextUtils::ValueHelper
|
24
|
+
|
25
|
+
include FixtureHelpers
|
26
|
+
|
27
|
+
##
|
28
|
+
## todo: add from_file and from_zip too
|
29
|
+
|
30
|
+
def self.from_string( event_key, text )
|
31
|
+
### fix - fix -fix:
|
32
|
+
## change event to event_or_event_key !!!!! - allow event_key as string passed in
|
33
|
+
self.new( event_key, text )
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize( event_key, text )
|
37
|
+
### fix - fix -fix:
|
38
|
+
## change event to event_or_event_key !!!!! - allow event_key as string passed in
|
39
|
+
|
40
|
+
## todo/fix: how to add opts={} ???
|
41
|
+
@event_key = event_key
|
42
|
+
@text = text
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
def read
|
47
|
+
## note: assume active activerecord connection
|
48
|
+
@event = Event.find_by!( key: @event_key )
|
49
|
+
|
50
|
+
logger.debug "Event #{@event.key} >#{@event.title}<"
|
51
|
+
|
52
|
+
@team_mapper = TextUtils::TitleMapper.new( @event.teams, 'team' )
|
53
|
+
|
54
|
+
## reset cached values
|
55
|
+
@patch_round_ids = []
|
56
|
+
|
57
|
+
@last_round = nil
|
58
|
+
@last_date = nil
|
59
|
+
|
60
|
+
## always use english (en) for now
|
61
|
+
SportDb.lang.lang = 'en'
|
62
|
+
|
63
|
+
reader = LineReader.from_string( @text )
|
64
|
+
parse_fixtures( reader )
|
65
|
+
end # method load_fixtures
|
66
|
+
|
67
|
+
|
68
|
+
def parse_round_header( line )
|
69
|
+
|
70
|
+
## todo/fix:
|
71
|
+
## simplify - for now round number always required
|
72
|
+
# e.g. no auto-calculation supported here
|
73
|
+
# fail if round found w/o number/pos !!!
|
74
|
+
#
|
75
|
+
# also remove knockout flag for now (set to always false for now)
|
76
|
+
|
77
|
+
logger.debug "parsing round header line: >#{line}<"
|
78
|
+
|
79
|
+
### todo/fix/check: move cut off optional comment in reader for all lines? why? why not?
|
80
|
+
cut_off_end_of_line_comment!( line ) # cut off optional comment starting w/ #
|
81
|
+
|
82
|
+
## check for date in header first e.g. Round 36 [Jul 20] !!
|
83
|
+
## avoid "conflict" with getting "wrong" round number from date etc.
|
84
|
+
date = find_rsssf_date!( line, start_at: @event.start_at )
|
85
|
+
if date
|
86
|
+
@last_date = date
|
87
|
+
end
|
88
|
+
|
89
|
+
## todo/check/fix:
|
90
|
+
# make sure Round of 16 will not return pos 16 -- how? possible?
|
91
|
+
# add unit test too to verify
|
92
|
+
pos = find_round_pos!( line )
|
93
|
+
|
94
|
+
## check if pos available; if not auto-number/calculate
|
95
|
+
if pos.nil?
|
96
|
+
logger.error( " no round pos found in line >#{line}<; round pos required in rsssf; sorry" )
|
97
|
+
fail( "round pos required in rsssf; sorry")
|
98
|
+
end
|
99
|
+
|
100
|
+
title = find_round_header_title!( line )
|
101
|
+
|
102
|
+
## Note: use extracted round title for knockout check
|
103
|
+
## knockout_flag = is_knockout_round?( title )
|
104
|
+
|
105
|
+
logger.debug " line: >#{line}<"
|
106
|
+
|
107
|
+
## Note: dummy/placeholder start_at, end_at date
|
108
|
+
## replace/patch after adding all games for round
|
109
|
+
|
110
|
+
round_attribs = {
|
111
|
+
title: title,
|
112
|
+
title2: nil,
|
113
|
+
knockout: false
|
114
|
+
}
|
115
|
+
|
116
|
+
round = Round.find_by( event_id: @event.id,
|
117
|
+
pos: pos )
|
118
|
+
|
119
|
+
if round.present?
|
120
|
+
logger.debug "update round #{round.id}:"
|
121
|
+
else
|
122
|
+
logger.debug "create round:"
|
123
|
+
round = Round.new
|
124
|
+
|
125
|
+
round_attribs = round_attribs.merge( {
|
126
|
+
event_id: @event.id,
|
127
|
+
pos: pos,
|
128
|
+
start_at: Date.parse('1911-11-11'),
|
129
|
+
end_at: Date.parse('1911-11-11')
|
130
|
+
})
|
131
|
+
end
|
132
|
+
|
133
|
+
logger.debug round_attribs.to_json
|
134
|
+
|
135
|
+
round.update_attributes!( round_attribs )
|
136
|
+
|
137
|
+
### store list of round ids for patching start_at/end_at at the end
|
138
|
+
@patch_round_ids << round.id
|
139
|
+
@last_round = round ## keep track of last seen round for matches that follow etc.
|
140
|
+
end
|
141
|
+
|
142
|
+
|
143
|
+
def try_parse_game( line )
|
144
|
+
# note: clone line; for possible test do NOT modify in place for now
|
145
|
+
# note: returns true if parsed, false if no match
|
146
|
+
parse_game( line.dup )
|
147
|
+
end
|
148
|
+
|
149
|
+
def parse_game( line )
|
150
|
+
logger.debug "parsing game (fixture) line: >#{line}<"
|
151
|
+
|
152
|
+
@team_mapper.map_titles!( line )
|
153
|
+
team1_key = @team_mapper.find_key!( line )
|
154
|
+
team2_key = @team_mapper.find_key!( line )
|
155
|
+
|
156
|
+
## note: if we do NOT find two teams; return false - no match found
|
157
|
+
if team1_key.nil? || team2_key.nil?
|
158
|
+
logger.debug " no game match (two teams required) found for line: >#{line}<"
|
159
|
+
return false
|
160
|
+
end
|
161
|
+
|
162
|
+
date = find_rsssf_date!( line, start_at: @event.start_at )
|
163
|
+
|
164
|
+
###
|
165
|
+
# check if date found?
|
166
|
+
# note: ruby falsey is nil & false only (not 0 or empty array etc.)
|
167
|
+
if date
|
168
|
+
@last_date = date # keep a reference for later use
|
169
|
+
else
|
170
|
+
date = @last_date # no date found; (re)use last seen date
|
171
|
+
end
|
172
|
+
|
173
|
+
## fix/todo: use find_rsssf_scores!( line )
|
174
|
+
## use rsssf specific score finder!!!
|
175
|
+
scores = find_scores!( line )
|
176
|
+
|
177
|
+
logger.debug " line: >#{line}<"
|
178
|
+
|
179
|
+
|
180
|
+
### todo: cache team lookups in hash?
|
181
|
+
team1 = Team.find_by!( key: team1_key )
|
182
|
+
team2 = Team.find_by!( key: team2_key )
|
183
|
+
|
184
|
+
round = @last_round
|
185
|
+
|
186
|
+
### check if games exists
|
187
|
+
## with this teams in this round if yes only update
|
188
|
+
game = Game.find_by( round_id: round.id,
|
189
|
+
team1_id: team1.id,
|
190
|
+
team2_id: team2.id )
|
191
|
+
|
192
|
+
game_attribs = {
|
193
|
+
score1: scores[0],
|
194
|
+
score2: scores[1],
|
195
|
+
score1et: scores[2],
|
196
|
+
score2et: scores[3],
|
197
|
+
score1p: scores[4],
|
198
|
+
score2p: scores[5],
|
199
|
+
play_at: date,
|
200
|
+
play_at_v2: nil,
|
201
|
+
postponed: false,
|
202
|
+
knockout: false, ## round.knockout, ## note: for now always use knockout flag from round - why? why not??
|
203
|
+
ground_id: nil,
|
204
|
+
group_id: nil
|
205
|
+
}
|
206
|
+
|
207
|
+
if game.present?
|
208
|
+
logger.debug "update game #{game.id}:"
|
209
|
+
else
|
210
|
+
logger.debug "create game:"
|
211
|
+
game = Game.new
|
212
|
+
|
213
|
+
## Note: use round.games.count for pos
|
214
|
+
## lets us add games out of order if later needed
|
215
|
+
more_game_attribs = {
|
216
|
+
round_id: round.id,
|
217
|
+
team1_id: team1.id,
|
218
|
+
team2_id: team2.id,
|
219
|
+
pos: round.games.count+1
|
220
|
+
}
|
221
|
+
game_attribs = game_attribs.merge( more_game_attribs )
|
222
|
+
end
|
223
|
+
|
224
|
+
logger.debug game_attribs.to_json
|
225
|
+
game.update_attributes!( game_attribs )
|
226
|
+
|
227
|
+
return true # game match found
|
228
|
+
end # method parse_game
|
229
|
+
|
230
|
+
|
231
|
+
def try_parse_date_header( line )
|
232
|
+
# note: clone line; for possible test do NOT modify in place for now
|
233
|
+
# note: returns true if parsed, false if no match
|
234
|
+
parse_date_header( line.dup )
|
235
|
+
end
|
236
|
+
|
237
|
+
def parse_date_header( line )
|
238
|
+
# note: returns true if parsed, false if no match
|
239
|
+
|
240
|
+
# line with NO teams plus include date e.g.
|
241
|
+
# [Jun 17] etc.
|
242
|
+
|
243
|
+
@team_mapper.map_titles!( line )
|
244
|
+
team1_key = @team_mapper.find_key!( line )
|
245
|
+
team2_key = @team_mapper.find_key!( line )
|
246
|
+
|
247
|
+
date = find_rsssf_date!( line, start_at: @event.start_at )
|
248
|
+
|
249
|
+
if date && team1_key.nil? && team2_key.nil?
|
250
|
+
logger.debug( "date header line found: >#{line}<")
|
251
|
+
logger.debug( " date: #{date}")
|
252
|
+
|
253
|
+
@last_date = date # keep a reference for later use
|
254
|
+
return true
|
255
|
+
else
|
256
|
+
return false
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
|
261
|
+
|
262
|
+
def parse_fixtures( reader )
|
263
|
+
|
264
|
+
reader.each_line do |line|
|
265
|
+
|
266
|
+
## fix: use inline/simpler is_rsssf_round?
|
267
|
+
if is_round?( line )
|
268
|
+
parse_round_header( line )
|
269
|
+
elsif try_parse_game( line )
|
270
|
+
# do nothing here
|
271
|
+
elsif try_parse_date_header( line )
|
272
|
+
# do nothing here
|
273
|
+
else
|
274
|
+
logger.info "skipping line (no match found): >#{line}<"
|
275
|
+
end
|
276
|
+
end # lines.each
|
277
|
+
|
278
|
+
###########################
|
279
|
+
# backtrack and patch round dates (start_at/end_at)
|
280
|
+
|
281
|
+
unless @patch_round_ids.empty?
|
282
|
+
###
|
283
|
+
# note: use uniq - to allow multiple round headers (possible?)
|
284
|
+
|
285
|
+
Round.find( @patch_round_ids.uniq ).each do |r|
|
286
|
+
logger.debug "patch round start_at/end_at date for #{r.title}:"
|
287
|
+
|
288
|
+
## note:
|
289
|
+
## will add "scope" pos first e.g
|
290
|
+
#
|
291
|
+
## SELECT "games".* FROM "games" WHERE "games"."round_id" = ?
|
292
|
+
# ORDER BY pos, play_at asc [["round_id", 7]]
|
293
|
+
# thus will NOT order by play_at but by pos first!!!
|
294
|
+
# =>
|
295
|
+
# need to unscope pos!!! or use unordered_games - games_by_play_at_date etc.??
|
296
|
+
# thus use reorder()!!! - not just order('play_at asc')
|
297
|
+
|
298
|
+
games = r.games.reorder( 'play_at asc' ).all
|
299
|
+
|
300
|
+
## skip rounds w/ no games
|
301
|
+
|
302
|
+
## todo/check/fix: what's the best way for checking assoc w/ 0 recs?
|
303
|
+
next if games.size == 0
|
304
|
+
|
305
|
+
# note: make sure start_at/end_at is date only (e.g. use play_at.to_date)
|
306
|
+
# sqlite3 saves datetime in date field as datetime, for example (will break date compares later!)
|
307
|
+
|
308
|
+
round_attribs = {
|
309
|
+
start_at: games[0].play_at.to_date, # use games.first ?
|
310
|
+
end_at: games[-1].play_at.to_date # use games.last ? why? why not?
|
311
|
+
}
|
312
|
+
|
313
|
+
logger.debug round_attribs.to_json
|
314
|
+
r.update_attributes!( round_attribs )
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end # method parse_fixtures
|
318
|
+
|
319
|
+
end # class RsssfGameReader
|
320
|
+
end # module SportDb
|
data/lib/sportdb/utils_date.rb
CHANGED
data/lib/sportdb/version.rb
CHANGED
@@ -0,0 +1,18 @@
|
|
1
|
+
#########################################################################
|
2
|
+
# Österreichische Bundesliga 2014/15 (103. Meisterschaft / 41. Saison)
|
3
|
+
|
4
|
+
league: at
|
5
|
+
season: 2014/15
|
6
|
+
start_at: 2014-07-19
|
7
|
+
|
8
|
+
10 teams:
|
9
|
+
- salzburg
|
10
|
+
- rapid
|
11
|
+
- groedig
|
12
|
+
- austria
|
13
|
+
- sturm
|
14
|
+
- ried
|
15
|
+
- wac
|
16
|
+
- wrneustadt
|
17
|
+
- admira
|
18
|
+
- altach
|
@@ -5,15 +5,12 @@ league: at
|
|
5
5
|
season: 2015/16
|
6
6
|
start_at: 2015-07-25
|
7
7
|
|
8
|
-
# num/edition - 104 or 42 ?? ##
|
9
|
-
# note: timezone for games (play_at) is *always* CET (central european time)
|
10
|
-
|
11
8
|
fixtures:
|
12
9
|
- 1-bundesliga-i
|
13
10
|
- 1-bundesliga-ii
|
14
11
|
|
15
12
|
|
16
|
-
10
|
13
|
+
10 teams:
|
17
14
|
- RB Salzburg
|
18
15
|
- Rapid Wien
|
19
16
|
- Sturm Graz
|
@@ -25,4 +22,3 @@ fixtures:
|
|
25
22
|
- SV Grödig
|
26
23
|
- SV Mattersburg ## Aufsteiger
|
27
24
|
|
28
|
-
|
@@ -1,24 +1,17 @@
|
|
1
1
|
#######################
|
2
2
|
# Bundesliga 2013/14
|
3
|
-
|
4
|
-
# 10 Teams
|
5
|
-
|
6
|
-
# NB: 2013/14
|
7
3
|
#
|
8
|
-
#
|
9
|
-
# old team (moved to teams_2) | SV Mattersburg
|
10
|
-
|
11
|
-
# NB: use three letter codes from bundesliga.at
|
4
|
+
# 10 Teams
|
12
5
|
|
13
6
|
|
14
7
|
[austria]
|
15
|
-
FK Austria Wien|Austria Wien, 1911, AUS
|
8
|
+
FK Austria Wien|Austria Wien|Austria, 1911, AUS
|
16
9
|
www.fk-austria.at
|
17
10
|
Fischhofgasse 12 // 1100 Wien
|
18
11
|
city:wien
|
19
12
|
|
20
13
|
[rapid]
|
21
|
-
SK Rapid Wien|Rapid Wien, 1899, RAP
|
14
|
+
SK Rapid Wien|Rapid Wien|Rapid, 1899, RAP
|
22
15
|
www.skrapid.at
|
23
16
|
Keisslergasse 3 // 1140 Wien
|
24
17
|
city:wien
|
@@ -38,32 +31,32 @@
|
|
38
31
|
|
39
32
|
|
40
33
|
[salzburg]
|
41
|
-
FC RB Salzburg|RB Salzburg|Red Bull Salzburg, 1933, RBS
|
34
|
+
FC RB Salzburg|RB Salzburg|Red Bull Salzburg|Salzburg, 1933, RBS
|
42
35
|
www.redbulls.com/soccer/salzburg
|
43
36
|
Stadionstraße 2/3 // 5071 Wals-Siezenheim
|
44
37
|
city:salzburg
|
45
38
|
|
46
39
|
[groedig]
|
47
|
-
SV Grödig, 1948, SVG
|
40
|
+
SV Grödig|Grödig, 1948, SVG
|
48
41
|
www.sv-groedig.at
|
49
42
|
Prötschhofstrasse 26 // 5082 Grödig
|
50
43
|
city:groedig
|
51
44
|
|
52
45
|
|
53
46
|
[sturm]
|
54
|
-
SK Sturm Graz|Sturm Graz, 1909, STU
|
47
|
+
SK Sturm Graz|Sturm Graz|Sturm, 1909, STU
|
55
48
|
www.sksturm.at
|
56
49
|
Sternäckerweg 118 // 8042 Graz
|
57
50
|
city:graz
|
58
51
|
|
59
52
|
[wac]
|
60
|
-
Wolfsberger AC|RZ Pellets WAC, 1931, WAC
|
53
|
+
Wolfsberger AC|RZ Pellets WAC|Wolfsberg, 1931, WAC
|
61
54
|
www.rzpelletswac.at
|
62
55
|
Don Bosco Weg 1 // 9400 Wolfsberg
|
63
56
|
city:wolfsberg
|
64
57
|
|
65
58
|
[ried]
|
66
|
-
SV Ried|SV Josko Ried|SV Josko Fenster Ried, 1912, RIE
|
59
|
+
SV Ried|SV Josko Ried|SV Josko Fenster Ried|Ried, 1912, RIE
|
67
60
|
www.svried.at
|
68
61
|
Volksfestplatz 2 // 4910 Ried im Innkreis
|
69
62
|
city:ried
|
@@ -1,14 +1,12 @@
|
|
1
1
|
########################
|
2
2
|
# Erste Liga 2013/14
|
3
3
|
#
|
4
|
-
#
|
4
|
+
# 10 Teams
|
5
5
|
|
6
|
-
# NB: use three letter codes from bundesliga.at
|
7
6
|
|
7
|
+
mattersburg, SV Mattersburg|Mattersburg, Erste Liga/Bgld., MAT, city:mattersburg # from Bundesliga
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
altach, SCR Altach|SC Rheindorf Altach, Erste Liga/Vbg., ALT, city:altach
|
9
|
+
altach, SCR Altach|SC Rheindorf Altach|Altach, Erste Liga/Vbg., ALT, city:altach
|
12
10
|
austrial, SC Austria Lustenau|Austria Lustenau, Erste Liga/Vbg., ALU, city:lustenau
|
13
11
|
stpoelten, SKN St. Pölten, Erste Liga/NÖ, SKN, city:stpoelten
|
14
12
|
ksv, Kapfenberger SV|Kapfenberger SV 1919|KSV 1919, Erste Liga/Stmk., KSV, city:kapfenberg
|
@@ -21,14 +19,3 @@ parndorf, SC/ESV Parndorf 1919|SC-ESV Parndorf 1919|SC/ESV Parndorf, Erste Li
|
|
21
19
|
liefering, FC Liefering, Erste Liga/Sbg., LIE, city:salzburg # from Regionalliga West - Liefering - Stadtteil von Salzburg
|
22
20
|
|
23
21
|
|
24
|
-
# NB: 2013/14
|
25
|
-
# bundesliga:
|
26
|
-
# - new team SV Mattersburg
|
27
|
-
# - old team (moved to teams.1) SV Grödig
|
28
|
-
#
|
29
|
-
# erste liga:
|
30
|
-
# - new team SC/ESV Parndorf 1919
|
31
|
-
# FC Liefering
|
32
|
-
# - old team (moved to teams.3) FC Blau-Weiß Linz
|
33
|
-
# FC Lustenau 1907
|
34
|
-
|