sportdb-models 1.14.0 → 1.14.1
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 +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
|
-
|