sportdb-readers 1.2.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1 -1
- data/Manifest.txt +7 -0
- data/README.md +3 -4
- data/Rakefile +5 -3
- data/lib/sportdb/readers/match_reader.rb +86 -61
- data/lib/sportdb/readers/sync/club.rb +47 -0
- data/lib/sportdb/readers/sync/country.rb +39 -0
- data/lib/sportdb/readers/sync/event.rb +72 -0
- data/lib/sportdb/readers/sync/league.rb +40 -0
- data/lib/sportdb/readers/sync/match.rb +147 -0
- data/lib/sportdb/readers/sync/more.rb +136 -0
- data/lib/sportdb/readers/sync/season.rb +36 -0
- data/lib/sportdb/readers/version.rb +4 -2
- data/lib/sportdb/readers.rb +15 -4
- metadata +41 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cdad0e950d6144fe9de65be3f4a87057b05e1f03e168237c44c5f0c9f2d3f0b3
|
4
|
+
data.tar.gz: 541e0de0a923297dc7b1b7349319e77bcaa31ac5405bb3f2cdd111f522230985
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d36e0affcfc3fbdd5fbb50fabcbaebce34935f48a257c7ab7e918cf2cf1a96bdf3b7d2737f05adf75f9094ade927c88806d1fef5ce9d792feaac852534125e86
|
7
|
+
data.tar.gz: 797fd4250e078bf2255e38830358cedeea761ea85236e7084f08fd04ba71b2831890267c7a2268cc92312556149dda33a7ef893d0a5878ce9474be221ba50d9b
|
data/CHANGELOG.md
CHANGED
data/Manifest.txt
CHANGED
@@ -7,4 +7,11 @@ lib/sportdb/readers.rb
|
|
7
7
|
lib/sportdb/readers/conf_reader.rb
|
8
8
|
lib/sportdb/readers/match_reader.rb
|
9
9
|
lib/sportdb/readers/package.rb
|
10
|
+
lib/sportdb/readers/sync/club.rb
|
11
|
+
lib/sportdb/readers/sync/country.rb
|
12
|
+
lib/sportdb/readers/sync/event.rb
|
13
|
+
lib/sportdb/readers/sync/league.rb
|
14
|
+
lib/sportdb/readers/sync/match.rb
|
15
|
+
lib/sportdb/readers/sync/more.rb
|
16
|
+
lib/sportdb/readers/sync/season.rb
|
10
17
|
lib/sportdb/readers/version.rb
|
data/README.md
CHANGED
@@ -50,15 +50,14 @@ Matchday 1
|
|
50
50
|
...
|
51
51
|
```
|
52
52
|
|
53
|
-
(Source: [england/2015-16/1-premierleague
|
53
|
+
(Source: [england/2015-16/1-premierleague.txt](https://github.com/openfootball/england/blob/master/2015-16/1-premierleague.txt))
|
54
54
|
|
55
55
|
and let's try:
|
56
56
|
|
57
57
|
``` ruby
|
58
58
|
## assumes football.db datasets for England in ./england directory
|
59
59
|
## see github.com/openfootball/england
|
60
|
-
SportDb.read( './england/2015-16/1-premierleague
|
61
|
-
SportDb.read( './england/2015-16/1-premierleague-ii.txt' )
|
60
|
+
SportDb.read( './england/2015-16/1-premierleague.txt' )
|
62
61
|
|
63
62
|
## let's try another season
|
64
63
|
SportDb.read( './england/2019-20/1-premierleague.txt' )
|
@@ -136,7 +135,7 @@ Round, Date, Team 1, FT, HT, Team 2
|
|
136
135
|
```
|
137
136
|
(Source: [england/2019-20/eng.1.csv](https://github.com/footballcsv/england/blob/master/2010s/2019-20/eng.1.csv))
|
138
137
|
|
139
|
-
Yes, you can. See the [sportdb-importers library / gem »](https://github.com/sportdb/sport.db/tree/master/sportdb-importers)
|
138
|
+
Yes, you can. See the [sportdb-importers library / gem »](https://github.com/sportdb/sport.db/tree/master/sportdb-importers)
|
140
139
|
|
141
140
|
|
142
141
|
|
data/Rakefile
CHANGED
@@ -3,7 +3,7 @@ require './lib/sportdb/readers/version.rb'
|
|
3
3
|
|
4
4
|
Hoe.spec 'sportdb-readers' do
|
5
5
|
|
6
|
-
self.version = SportDb::Readers::VERSION
|
6
|
+
self.version = SportDb::Module::Readers::VERSION
|
7
7
|
|
8
8
|
self.summary = "sportdb-readers - sport.db readers for leagues, seasons, clubs, match schedules and results, and more"
|
9
9
|
self.description = summary
|
@@ -20,11 +20,13 @@ Hoe.spec 'sportdb-readers' do
|
|
20
20
|
self.licenses = ['Public Domain']
|
21
21
|
|
22
22
|
self.extra_deps = [
|
23
|
-
['sportdb-
|
23
|
+
['sportdb-formats', '>= 2.0.0'],
|
24
|
+
['sportdb-catalogs', '>= 1.2.1'],
|
25
|
+
['sportdb-models', '>= 2.1.0'],
|
24
26
|
]
|
25
27
|
|
26
28
|
self.spec_extras = {
|
27
|
-
required_ruby_version: '>=
|
29
|
+
required_ruby_version: '>= 3.1.0'
|
28
30
|
}
|
29
31
|
|
30
32
|
end
|
@@ -1,8 +1,18 @@
|
|
1
1
|
|
2
2
|
module SportDb
|
3
|
-
|
4
3
|
class MatchReader ## todo/check: rename to MatchReaderV2 (use plural?) why? why not?
|
5
4
|
|
5
|
+
|
6
|
+
### fix - remove catalog reference!!!
|
7
|
+
## use classes with "augmented" static methods
|
8
|
+
## e.g. Club.match_by etc.
|
9
|
+
def catalog
|
10
|
+
puts "[deprecated] do NOT use catalog reference; use classes with enhanced search static methods!"
|
11
|
+
Import.catalog
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
|
6
16
|
def self.read( path, season: nil ) ## use - rename to read_file or from_file etc. - why? why not?
|
7
17
|
txt = File.open( path, 'r:utf-8' ) {|f| f.read }
|
8
18
|
parse( txt, season: season )
|
@@ -23,43 +33,22 @@ class MatchReader ## todo/check: rename to MatchReaderV2 (use plural?) why? w
|
|
23
33
|
secs = LeagueOutlineReader.parse( @txt, season: season )
|
24
34
|
pp secs
|
25
35
|
|
26
|
-
|
27
|
-
###
|
28
|
-
## todo/check/fix: move to LeagueOutlineReader for (re)use - why? why not?
|
29
|
-
## use sec[:lang] or something?
|
30
|
-
langs = { ## map country keys to lang codes
|
31
|
-
'de' => 'de', ## de - Deutsch (German)
|
32
|
-
'at' => 'de',
|
33
|
-
'ch' => 'de',
|
34
|
-
'fr' => 'fr', ## fr - French
|
35
|
-
'it' => 'it', ## it - Italian
|
36
|
-
'es' => 'es', ## es - Español (Spanish)
|
37
|
-
'mx' => 'es',
|
38
|
-
'ar' => 'es', ## Argentina
|
39
|
-
'pt' => 'pt', ## pt - Português (Portuguese)
|
40
|
-
'br' => 'pt',
|
41
|
-
}
|
42
|
-
|
43
36
|
secs.each do |sec| ## sec(tion)s
|
44
37
|
season = sec[:season]
|
45
38
|
league = sec[:league]
|
46
39
|
stage = sec[:stage]
|
47
40
|
lines = sec[:lines]
|
48
41
|
|
49
|
-
## hack for now: switch lang
|
50
|
-
## todo/fix: set lang for now depending on league country!!!
|
51
|
-
if league.intl? ## todo/fix: add intl? to ActiveRecord league!!!
|
52
|
-
Import.config.lang = 'en'
|
53
|
-
else ## assume national/domestic
|
54
|
-
Import.config.lang = langs[ league.country.key ] || 'en'
|
55
|
-
end
|
56
|
-
|
57
42
|
### check if event info availabe - use start_date;
|
58
43
|
## otherwise we have to guess (use a "synthetic" start_date)
|
59
44
|
event_info = catalog.events.find_by( season: season,
|
60
45
|
league: league )
|
61
46
|
|
62
47
|
start = if event_info && event_info.start_date
|
48
|
+
puts "event info found:"
|
49
|
+
puts " using start date from event: "
|
50
|
+
pp event_info
|
51
|
+
pp event_info.start_date
|
63
52
|
event_info.start_date
|
64
53
|
else
|
65
54
|
if season.year?
|
@@ -70,61 +59,101 @@ class MatchReader ## todo/check: rename to MatchReaderV2 (use plural?) why? w
|
|
70
59
|
end
|
71
60
|
|
72
61
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
auto_conf_teams,
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
62
|
+
parser = MatchParser.new( lines,
|
63
|
+
start ) ## note: keep season start_at date for now (no need for more specific stage date need for now)
|
64
|
+
|
65
|
+
auto_conf_teams, matches, rounds, groups = parser.parse
|
66
|
+
|
67
|
+
puts ">>> #{auto_conf_teams.size} teams:"
|
68
|
+
pp auto_conf_teams
|
69
|
+
puts ">>> #{matches.size} matches:"
|
70
|
+
## pp matches
|
71
|
+
puts ">>> #{rounds.size} rounds:"
|
72
|
+
pp rounds
|
73
|
+
puts ">>> #{groups.size} groups:"
|
74
|
+
pp groups
|
75
|
+
|
76
|
+
|
77
|
+
|
82
78
|
## step 1: map/find teams
|
83
79
|
|
84
80
|
## note: loop over keys (holding the names); values hold the usage counter!! e.g. 'Arsenal' => 2, etc.
|
85
81
|
mods = nil
|
86
82
|
if league.clubs? && league.intl? ## todo/fix: add intl? to ActiveRecord league!!!
|
83
|
+
|
84
|
+
## quick hack - use "dynamic" keys for keys
|
85
|
+
uefa_el_q = Import::League.match_by( code: 'uefa.el.quali' )[0]
|
86
|
+
uefa_cl_q = Import::League.match_by( code: 'uefa.cl.quali' )[0]
|
87
|
+
uefa_cl = Import::League.match_by( code: 'uefa.cl' )[0]
|
88
|
+
uefa_el = Import::League.match_by( code: 'uefa.el' )[0]
|
89
|
+
|
90
|
+
pp [uefa_el_q, uefa_cl_q, uefa_cl, uefa_el]
|
91
|
+
|
87
92
|
### quick hack mods for popular/known ambigious club names
|
88
93
|
## todo/fix: make more generic / reuseable!!!!
|
89
94
|
mods = {}
|
90
95
|
## europa league uses same mods as champions league
|
91
|
-
mods[
|
92
|
-
mods[
|
93
|
-
mods[
|
94
|
-
mods[
|
96
|
+
mods[ uefa_el_q.key ] =
|
97
|
+
mods[ uefa_cl_q.key ] =
|
98
|
+
mods[ uefa_el.key ] =
|
99
|
+
mods[ uefa_cl.key ] = catalog.clubs.build_mods(
|
95
100
|
{ 'Liverpool | Liverpool FC' => 'Liverpool FC, ENG',
|
96
101
|
'Arsenal | Arsenal FC' => 'Arsenal FC, ENG',
|
97
102
|
'Barcelona' => 'FC Barcelona, ESP',
|
98
|
-
'Valencia' => 'Valencia CF, ESP'
|
103
|
+
'Valencia' => 'Valencia CF, ESP',
|
104
|
+
'Rangers FC' => 'Rangers FC, SCO',
|
105
|
+
})
|
99
106
|
end
|
100
107
|
|
101
108
|
# puts " [debug] auto_conf_teams:"
|
102
109
|
# pp auto_conf_teams
|
103
110
|
|
104
111
|
|
105
|
-
|
112
|
+
## todo/fix
|
113
|
+
## ** !!! ERROR - too many matches (2) for club >Barcelona<:
|
114
|
+
## [<Club: FC Barcelona (ESP)>, <Club: Barcelona Guayaquil (ECU)>]
|
115
|
+
|
116
|
+
puts "league:"
|
117
|
+
pp league
|
118
|
+
|
119
|
+
teams = catalog.teams.find_by!( name: auto_conf_teams,
|
106
120
|
league: league,
|
107
121
|
mods: mods )
|
108
122
|
|
109
|
-
|
110
|
-
|
123
|
+
puts " [debug] teams:"
|
124
|
+
pp teams
|
125
|
+
|
111
126
|
|
112
127
|
## build mapping - name => team struct record
|
113
|
-
team_mapping = auto_conf_teams.
|
128
|
+
team_mapping = auto_conf_teams.zip( teams ).to_h
|
114
129
|
|
115
|
-
|
116
|
-
|
130
|
+
puts " [debug] team_mapping:"
|
131
|
+
pp team_mapping
|
117
132
|
|
118
|
-
|
119
133
|
|
120
|
-
|
121
|
-
|
122
|
-
|
134
|
+
## quick (and dirty) hack
|
135
|
+
## update all team strings with mapped records
|
136
|
+
matches.each do |match|
|
137
|
+
match.update( team1: team_mapping[ match.team1 ] )
|
138
|
+
match.update( team2: team_mapping[ match.team2 ] )
|
139
|
+
end
|
123
140
|
|
124
|
-
|
141
|
+
## quick hack cont.
|
142
|
+
## rebuild groups with all team strings with mapped records
|
143
|
+
groups = groups.map do |old_group|
|
144
|
+
group = Import::Group.new(
|
145
|
+
name: old_group.name,
|
146
|
+
teams: old_group.teams.map {|team| team_mapping[team] }
|
147
|
+
)
|
148
|
+
group
|
149
|
+
end
|
150
|
+
puts "groups:"
|
151
|
+
pp groups
|
125
152
|
|
126
|
-
|
127
|
-
|
153
|
+
## fix
|
154
|
+
## !! ERROR - no (cached) team rec found for team in group >Group A<
|
155
|
+
## for >#<Sports::Club:0x000002c7e1686040><
|
156
|
+
### update sync group to check for records (use .name) !!!
|
128
157
|
|
129
158
|
|
130
159
|
######################################################
|
@@ -166,10 +195,10 @@ class MatchReader ## todo/check: rename to MatchReaderV2 (use plural?) why? w
|
|
166
195
|
## e.g. group.teams assumes an array of team names e.g.
|
167
196
|
## ["Spain", "Czech Republic", "Turkey", "Croatia"]
|
168
197
|
group_team_ids = []
|
169
|
-
group.teams.each do |
|
170
|
-
team_rec = cache_team_recs[
|
198
|
+
group.teams.each do |team|
|
199
|
+
team_rec = cache_team_recs[ team.name ]
|
171
200
|
if team_rec.nil? ## assume team MUST always be present/known in mapping (via autoconfig parser)
|
172
|
-
puts "!! ERROR - no (cached) team rec found for team in group >#{group.name}< for >#{
|
201
|
+
puts "!! ERROR - no (cached) team rec found for team in group >#{group.name}< for >#{team}<"
|
173
202
|
exit 1
|
174
203
|
end
|
175
204
|
group_team_ids << team_rec.id
|
@@ -182,6 +211,7 @@ class MatchReader ## todo/check: rename to MatchReaderV2 (use plural?) why? w
|
|
182
211
|
round_rec = Sync::Round.find_or_create( round, event: event_rec ) ## check: use/rename to EventRound why? why not?
|
183
212
|
end
|
184
213
|
|
214
|
+
|
185
215
|
matches.each do |match|
|
186
216
|
## note: pass along stage (if present): stage - optional from heading!!!!
|
187
217
|
match = match.update( stage: stage ) if stage
|
@@ -193,10 +223,5 @@ class MatchReader ## todo/check: rename to MatchReaderV2 (use plural?) why? w
|
|
193
223
|
end # method parse
|
194
224
|
|
195
225
|
|
196
|
-
######################
|
197
|
-
# (convenience) helpers
|
198
|
-
|
199
|
-
def catalog() Import.catalog; end
|
200
|
-
|
201
226
|
end # class MatchReader
|
202
227
|
end # module SportDb
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module SportDb
|
2
|
+
module Sync
|
3
|
+
class Club
|
4
|
+
|
5
|
+
## auto-cache all clubs by find_or_create for later mapping / lookup
|
6
|
+
def self.cache() @cache ||= {}; end
|
7
|
+
|
8
|
+
|
9
|
+
##################################
|
10
|
+
# finders
|
11
|
+
|
12
|
+
def self.find_or_create( club )
|
13
|
+
## note: assume "canonical unique" names for now for clubs
|
14
|
+
rec = Model::Team.find_by( name: club.name )
|
15
|
+
if rec.nil?
|
16
|
+
|
17
|
+
## todo/fix: move auto-key gen to structs for re(use)!!!!!!
|
18
|
+
## check if key is present otherwise generate e.g. remove all non-ascii a-z chars
|
19
|
+
key = club.key || club.name.downcase.gsub( /[^a-z]/, '' )
|
20
|
+
puts "add club: #{key}, #{club.name}, #{club.country.name} (#{club.country.key})"
|
21
|
+
|
22
|
+
attribs = {
|
23
|
+
key: key,
|
24
|
+
name: club.name,
|
25
|
+
country_id: Sync::Country.find_or_create( club.country ).id,
|
26
|
+
club: true,
|
27
|
+
national: false ## check -is default anyway - use - why? why not?
|
28
|
+
## todo/fix: add city if present - why? why not?
|
29
|
+
}
|
30
|
+
|
31
|
+
attribs[:code] = club.code if club.code ## add code (abbreviation) if present
|
32
|
+
|
33
|
+
if club.alt_names.empty? == false
|
34
|
+
attribs[:alt_names] = club.alt_names.join('|')
|
35
|
+
end
|
36
|
+
|
37
|
+
rec = Model::Team.create!( attribs )
|
38
|
+
end
|
39
|
+
## auto-add to cache
|
40
|
+
cache[club.name] = rec
|
41
|
+
|
42
|
+
rec
|
43
|
+
end
|
44
|
+
|
45
|
+
end # class Club
|
46
|
+
end # module Sync
|
47
|
+
end # module SportDb
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module SportDb
|
2
|
+
module Sync
|
3
|
+
class Country
|
4
|
+
|
5
|
+
#############################
|
6
|
+
# finders
|
7
|
+
|
8
|
+
def self.find( country )
|
9
|
+
WorldDb::Model::Country.find_by( key: country.key )
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.find!( country )
|
13
|
+
rec = find( country )
|
14
|
+
if rec.nil?
|
15
|
+
puts "** !!! ERROR !!! - country for key >#{country.key}< not found; sorry - add to COUNTRIES table"
|
16
|
+
exit 1
|
17
|
+
end
|
18
|
+
rec
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.find_or_create( country )
|
22
|
+
rec = find( country )
|
23
|
+
if rec.nil?
|
24
|
+
attribs = {
|
25
|
+
key: country.key,
|
26
|
+
name: country.name,
|
27
|
+
code: country.code, ## fix: uses fifa code now (should be iso-alpha3 if available)
|
28
|
+
## fifa: country.fifa,
|
29
|
+
area: 1,
|
30
|
+
pop: 1
|
31
|
+
}
|
32
|
+
rec = WorldDb::Model::Country.create!( attribs )
|
33
|
+
end
|
34
|
+
rec
|
35
|
+
end
|
36
|
+
end # class Country
|
37
|
+
|
38
|
+
end # module Sync
|
39
|
+
end # module SportDb
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module SportDb
|
2
|
+
module Sync
|
3
|
+
|
4
|
+
class Event
|
5
|
+
|
6
|
+
##################################################
|
7
|
+
# finders
|
8
|
+
def self.find_by( league:, season: )
|
9
|
+
## note: allow passing in of activerecord db records too - why? why not?
|
10
|
+
## fix - change ArgumentError to TypeError !! (if TypeError exists)
|
11
|
+
raise ArgumentError, "league struct record expected; got #{league.class.name}" unless league.is_a?( Import::League )
|
12
|
+
raise ArgumentError, "season struct record expected; got #{season.class.name}" unless season.is_a?( Import::Season )
|
13
|
+
|
14
|
+
## auto-create league and season (db) records if missing? - why? why not?
|
15
|
+
season_rec = Season.find( season )
|
16
|
+
league_rec = League.find( league )
|
17
|
+
|
18
|
+
rec = nil
|
19
|
+
rec = Model::Event.find_by( league_id: league_rec.id,
|
20
|
+
season_id: season_rec.id ) if season_rec && league_rec
|
21
|
+
rec
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.find_by!( league:, season: )
|
25
|
+
rec = find_by( league: league, season: season )
|
26
|
+
if rec.nil?
|
27
|
+
puts "** !!!ERROR!!! db sync - no event match found for:"
|
28
|
+
pp league
|
29
|
+
pp season
|
30
|
+
exit 1
|
31
|
+
end
|
32
|
+
rec
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.find_or_create_by( league:, season: )
|
36
|
+
## note: allow passing in of activerecord db records too - why? why not?
|
37
|
+
raise ArgumentError, "league struct record expected; got #{league.class.name}" unless league.is_a?( Import::League )
|
38
|
+
raise ArgumentError, "season struct record expected; got #{season.class.name}" unless season.is_a?( Import::Season )
|
39
|
+
|
40
|
+
## note: auto-creates league and season (db) records if missing - why? why not?
|
41
|
+
season_rec = Season.find_or_create( season )
|
42
|
+
league_rec = League.find_or_create( league )
|
43
|
+
|
44
|
+
rec = Model::Event.find_by( league_id: league_rec.id,
|
45
|
+
season_id: season_rec.id )
|
46
|
+
if rec.nil?
|
47
|
+
attribs = {
|
48
|
+
league_id: league_rec.id,
|
49
|
+
season_id: season_rec.id,
|
50
|
+
}
|
51
|
+
|
52
|
+
## quick hack/change later !!
|
53
|
+
## todo/fix: check season - if is length 4 (single year) use 2017, 1, 1
|
54
|
+
## otherwise use 2017, 7, 1
|
55
|
+
## start_at use year and 7,1 e.g. Date.new( 2017, 7, 1 )
|
56
|
+
## hack: fix/todo1!!
|
57
|
+
## add "fake" start_date for now
|
58
|
+
attribs[:start_date] = if season.year? ## e.g. assume 2018 etc.
|
59
|
+
Date.new( season.start_year, 1, 1 )
|
60
|
+
else ## assume 2014/15 etc.
|
61
|
+
Date.new( season.start_year, 7, 1 )
|
62
|
+
end
|
63
|
+
|
64
|
+
rec = Model::Event.create!( attribs )
|
65
|
+
end
|
66
|
+
rec
|
67
|
+
end
|
68
|
+
end # class Event
|
69
|
+
|
70
|
+
end # module Sync
|
71
|
+
end # module SportDb
|
72
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module SportDb
|
2
|
+
module Sync
|
3
|
+
class League
|
4
|
+
|
5
|
+
###################################
|
6
|
+
# finders
|
7
|
+
|
8
|
+
def self.find( league )
|
9
|
+
Model::League.find_by( key: league.key )
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.find!( league )
|
13
|
+
rec = find( league )
|
14
|
+
if rec.nil?
|
15
|
+
puts "** !!!ERROR!!! db sync - no league match found for:"
|
16
|
+
pp league
|
17
|
+
exit 1
|
18
|
+
end
|
19
|
+
rec
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.find_or_create( league )
|
23
|
+
rec = find( league )
|
24
|
+
if rec.nil?
|
25
|
+
attribs = { key: league.key,
|
26
|
+
name: league.name }
|
27
|
+
|
28
|
+
if league.country
|
29
|
+
attribs[ :country_id ] = Sync::Country.find_or_create( league.country ).id
|
30
|
+
end
|
31
|
+
|
32
|
+
rec = Model::League.create!( attribs )
|
33
|
+
end
|
34
|
+
rec
|
35
|
+
end
|
36
|
+
|
37
|
+
end # class League
|
38
|
+
end # module Sync
|
39
|
+
end # module SportDb
|
40
|
+
|
@@ -0,0 +1,147 @@
|
|
1
|
+
module SportDb
|
2
|
+
module Sync
|
3
|
+
|
4
|
+
class Match
|
5
|
+
### todo/fix: rename to create!! (add update support later) !!!!
|
6
|
+
## use update_by_round or update_by_date or update_by_teams or such
|
7
|
+
## NO easy (unique always auto-id match) possible!!!!!!
|
8
|
+
def self.create_or_update( match, event: )
|
9
|
+
## note: MUST find round, thus, use bang (!)
|
10
|
+
|
11
|
+
## todo/check: allow strings too - why? why not?
|
12
|
+
|
13
|
+
|
14
|
+
## todo/check:
|
15
|
+
## how to model "same" rounds in different stages
|
16
|
+
## e.g. Belgium
|
17
|
+
## in regular season (stage) - round 1, round 2, etc.
|
18
|
+
## in playoff (stage) - round 1, round 2, etc.
|
19
|
+
## reference same round or create a new one for each stage!!???
|
20
|
+
## and lookup by name AND stage??
|
21
|
+
|
22
|
+
|
23
|
+
round_rec = if match.round
|
24
|
+
## query for round - allow string or round rec
|
25
|
+
round_name = match.round.is_a?( String ) ? match.round : match.round.name
|
26
|
+
Model::Round.find_by!( event_id: event.id,
|
27
|
+
name: round_name )
|
28
|
+
else # note: allow matches WITHOUT rounds too (e.g. England Football League 1888 and others)
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
32
|
+
## todo/check: allow fallback with db lookup if NOT found in cache - why? why not?
|
33
|
+
## or better use Sync::Team.find_or_create( team ) !!!!!!! to auto-create on first hit!
|
34
|
+
## || Team.find_or_create( team1 ) -- note: does NOT work for string (only recs) - what to do?
|
35
|
+
## || Model::Team.find_by!( name: team1_name )
|
36
|
+
team1_name = match.team1.is_a?( String ) ? match.team1 : match.team1.name
|
37
|
+
team1_rec = Team.cache[ team1_name ]
|
38
|
+
team2_name = match.team2.is_a?( String ) ? match.team2 : match.team2.name
|
39
|
+
team2_rec = Team.cache[ team2_name ]
|
40
|
+
|
41
|
+
## check optional group (e.g. Group A, etc.)
|
42
|
+
group_rec = if match.group
|
43
|
+
group_name = match.group.is_a?( String ) ? match.group : match.group.name
|
44
|
+
Model::Group.find_by!( event_id: event.id,
|
45
|
+
name: group_name )
|
46
|
+
else
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
|
50
|
+
## check optional stage (e.g. Regular, Play Off, Relegation, etc. )
|
51
|
+
stage_rec = if match.stage
|
52
|
+
stage_name = match.stage.is_a?( String ) ? match.stage : match.stage.name
|
53
|
+
Model::Stage.find_by!( event_id: event.id,
|
54
|
+
name: stage_name )
|
55
|
+
else
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
|
59
|
+
### todo/check: what happens if there's more than one match? exception raised??
|
60
|
+
rec = if ['N. N.'].include?( team1_name ) && ## some special cases - always assume new record for now (to avoid ambigious update conflict)
|
61
|
+
['N. N.'].include?( team2_name )
|
62
|
+
## always assume new record for now
|
63
|
+
## check for date or such - why? why not?
|
64
|
+
nil
|
65
|
+
elsif round_rec
|
66
|
+
## add match status too? allows [abandoned] and [replay] in same round
|
67
|
+
find_attributes = { round_id: round_rec.id,
|
68
|
+
team1_id: team1_rec.id,
|
69
|
+
team2_id: team2_rec.id }
|
70
|
+
|
71
|
+
## add stage if present to query
|
72
|
+
find_attributes[ :stage_id] = stage_rec.id if stage_rec
|
73
|
+
|
74
|
+
Model::Match.find_by( find_attributes )
|
75
|
+
else
|
76
|
+
## always assume new record for now
|
77
|
+
## check for date or such - why? why not?
|
78
|
+
nil
|
79
|
+
end
|
80
|
+
|
81
|
+
if rec.nil?
|
82
|
+
## find last pos - check if it can be nil? yes, is nil if no records found
|
83
|
+
max_pos = Model::Match.where( event_id: event.id ).maximum( 'pos' )
|
84
|
+
max_pos = max_pos ? max_pos+1 : 1
|
85
|
+
|
86
|
+
attribs = { event_id: event.id, ## todo/fix: change to data struct too?
|
87
|
+
team1_id: team1_rec.id,
|
88
|
+
team2_id: team2_rec.id,
|
89
|
+
pos: max_pos,
|
90
|
+
num: match.num, ## note - might be nil (not nil for euro, world cup, etc.)
|
91
|
+
# date: match.date.to_date, ## todo/fix: split and add date & time!!!!
|
92
|
+
date: match.date, # assume iso format as string e.g. 2021-07-10 !!!
|
93
|
+
time: match.time, # assume iso format as string e.g. 21:00 !!!
|
94
|
+
score1: match.score1,
|
95
|
+
score2: match.score2,
|
96
|
+
score1i: match.score1i,
|
97
|
+
score2i: match.score2i,
|
98
|
+
score1et: match.score1et,
|
99
|
+
score2et: match.score2et,
|
100
|
+
score1p: match.score1p,
|
101
|
+
score2p: match.score2p,
|
102
|
+
status: match.status }
|
103
|
+
|
104
|
+
attribs[ :round_id ] = round_rec.id if round_rec
|
105
|
+
attribs[ :group_id ] = group_rec.id if group_rec
|
106
|
+
attribs[ :stage_id ] = stage_rec.id if stage_rec
|
107
|
+
|
108
|
+
rec = Model::Match.create!( attribs )
|
109
|
+
|
110
|
+
|
111
|
+
#############################################
|
112
|
+
### try to update goals
|
113
|
+
#### quick & dirty v0 - redo !!!!
|
114
|
+
goals = match.goals
|
115
|
+
if goals && goals.size > 0
|
116
|
+
goals.each do |goal|
|
117
|
+
person_rec = Model::Person.find_by(
|
118
|
+
name: goal.player )
|
119
|
+
if person_rec.nil?
|
120
|
+
person_rec = Model::Person.create!(
|
121
|
+
key: goal.player.downcase.gsub( /[^a-z]/, '' ),
|
122
|
+
name: goal.player
|
123
|
+
)
|
124
|
+
end
|
125
|
+
Model::Goal.create!(
|
126
|
+
match_id: rec.id,
|
127
|
+
person_id: person_rec.id,
|
128
|
+
team_id: goal.team == 1 ? rec.team1.id
|
129
|
+
: rec.team2.id,
|
130
|
+
minute: goal.minute,
|
131
|
+
offset: goal.offset || 0,
|
132
|
+
penalty: goal.penalty,
|
133
|
+
owngoal: goal.owngoal,
|
134
|
+
)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
else
|
138
|
+
# update - todo
|
139
|
+
puts "!! ERROR - match updates not yet supported (only inserts); sorry"
|
140
|
+
exit 1
|
141
|
+
end
|
142
|
+
rec
|
143
|
+
end
|
144
|
+
end # class Match
|
145
|
+
|
146
|
+
end # module Sync
|
147
|
+
end # module SportDb
|
@@ -0,0 +1,136 @@
|
|
1
|
+
|
2
|
+
module SportDb
|
3
|
+
module Sync
|
4
|
+
|
5
|
+
|
6
|
+
class NationalTeam
|
7
|
+
def self.find_or_create( team )
|
8
|
+
rec = Model::Team.find_by( name: team.name )
|
9
|
+
if rec.nil?
|
10
|
+
puts "add national team: #{team.key}, #{team.name}, #{team.country.name} (#{team.country.key})"
|
11
|
+
|
12
|
+
### note: key expected three or more lowercase letters a-z /\A[a-z]{3,}\z/
|
13
|
+
attribs = {
|
14
|
+
key: team.key, ## note: always use downcase fifa code for now!!!
|
15
|
+
name: team.name,
|
16
|
+
code: team.code,
|
17
|
+
country_id: Sync::Country.find_or_create( team.country ).id,
|
18
|
+
club: false,
|
19
|
+
national: true ## check -is default anyway - use - why? why not?
|
20
|
+
}
|
21
|
+
|
22
|
+
if team.alt_names.empty? == false
|
23
|
+
attribs[:alt_names] = team.alt_names.join('|')
|
24
|
+
end
|
25
|
+
|
26
|
+
rec = Model::Team.create!( attribs )
|
27
|
+
end
|
28
|
+
rec
|
29
|
+
end
|
30
|
+
end # class NationalTeam
|
31
|
+
|
32
|
+
|
33
|
+
class Team
|
34
|
+
## auto-cache all clubs by find_or_create for later mapping / lookup
|
35
|
+
def self.cache() @cache ||= {}; end
|
36
|
+
|
37
|
+
def self.find_or_create( team_or_teams )
|
38
|
+
if team_or_teams.is_a?( Array )
|
39
|
+
recs = []
|
40
|
+
teams = team_or_teams
|
41
|
+
teams.each do |team|
|
42
|
+
recs << __find_or_create( team )
|
43
|
+
end
|
44
|
+
recs
|
45
|
+
else # assome single rec
|
46
|
+
team = team_or_teams
|
47
|
+
__find_or_create( team )
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.__find_or_create( team ) ## todo/check: use find_or_create_worker instead of _find - why? why not?
|
52
|
+
rec = if team.is_a?( Import::NationalTeam )
|
53
|
+
NationalTeam.find_or_create( team )
|
54
|
+
else ## assume Club
|
55
|
+
Club.find_or_create( team )
|
56
|
+
end
|
57
|
+
cache[ team.name ] = rec ## note: assume "canonical" unique team name
|
58
|
+
rec
|
59
|
+
end
|
60
|
+
end # class Team
|
61
|
+
|
62
|
+
|
63
|
+
|
64
|
+
class Round
|
65
|
+
def self.find_or_create( round, event: )
|
66
|
+
rec = Model::Round.find_by( name: round.name, event_id: event.id )
|
67
|
+
if rec.nil?
|
68
|
+
## find last pos - check if it can be nil?
|
69
|
+
max_pos = Model::Round.where( event_id: event.id ).maximum( 'pos' )
|
70
|
+
max_pos = max_pos ? max_pos+1 : 1
|
71
|
+
|
72
|
+
attribs = { event_id: event.id,
|
73
|
+
name: round.name,
|
74
|
+
pos: max_pos
|
75
|
+
}
|
76
|
+
|
77
|
+
## todo/fix: check if round has (optional) start or end date and add!!!
|
78
|
+
## attribs[ :start_date] = round.start_date.to_date
|
79
|
+
|
80
|
+
rec = Model::Round.create!( attribs )
|
81
|
+
end
|
82
|
+
rec
|
83
|
+
end
|
84
|
+
end # class Round
|
85
|
+
|
86
|
+
|
87
|
+
class Group
|
88
|
+
def self.find_or_create( group, event: )
|
89
|
+
rec = Model::Group.find_by( name: group.name, event_id: event.id )
|
90
|
+
if rec.nil?
|
91
|
+
## find last pos - check if it can be nil?
|
92
|
+
max_pos = Model::Group.where( event_id: event.id ).maximum( 'pos' )
|
93
|
+
max_pos = max_pos ? max_pos+1 : 1
|
94
|
+
|
95
|
+
attribs = { event_id: event.id,
|
96
|
+
name: group.name,
|
97
|
+
pos: max_pos
|
98
|
+
}
|
99
|
+
|
100
|
+
## todo/fix: check/add optional group key (was: pos before)!!!!
|
101
|
+
rec = Model::Group.create!( attribs )
|
102
|
+
end
|
103
|
+
## todo/fix: add/update teams in group too!!!!!
|
104
|
+
rec
|
105
|
+
end
|
106
|
+
end # class Group
|
107
|
+
|
108
|
+
|
109
|
+
class Stage
|
110
|
+
def self.find( name, event: )
|
111
|
+
Model::Stage.find_by( name: name, event_id: event.id )
|
112
|
+
end
|
113
|
+
def self.find!( name, event: )
|
114
|
+
rec = find( name, event: event )
|
115
|
+
if rec.nil?
|
116
|
+
puts "** !!!ERROR!!! db sync - no stage match found for:"
|
117
|
+
pp name
|
118
|
+
pp event
|
119
|
+
exit 1
|
120
|
+
end
|
121
|
+
rec
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.find_or_create( name, event: )
|
125
|
+
rec = find( name, event: event )
|
126
|
+
if rec.nil?
|
127
|
+
attribs = { event_id: event.id,
|
128
|
+
name: name,
|
129
|
+
}
|
130
|
+
rec = Model::Stage.create!( attribs )
|
131
|
+
end
|
132
|
+
rec
|
133
|
+
end
|
134
|
+
end # class Stage
|
135
|
+
end # module Sync
|
136
|
+
end # module SportDb
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module SportDb
|
2
|
+
module Sync
|
3
|
+
class Season
|
4
|
+
|
5
|
+
################
|
6
|
+
# finders
|
7
|
+
|
8
|
+
def self.find( season )
|
9
|
+
season = Season( season ) ## auto-convert for now (for old compat) - why? why not?
|
10
|
+
Model::Season.find_by( key: season.key )
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.find!( season )
|
14
|
+
season = Season( season ) ## auto-convert for now (for old compat) - why? why not?
|
15
|
+
rec = find( season )
|
16
|
+
if rec.nil?
|
17
|
+
puts "** !!!ERROR!!! db sync - no season match found for >#{season.key}<:"
|
18
|
+
exit 1
|
19
|
+
end
|
20
|
+
rec
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.find_or_create( season )
|
24
|
+
season = Season( season ) ## auto-convert for now (for old compat) - why? why not?
|
25
|
+
rec = find( season )
|
26
|
+
if rec.nil?
|
27
|
+
attribs = { key: season.key,
|
28
|
+
name: season.name }
|
29
|
+
rec = Model::Season.create!( attribs )
|
30
|
+
end
|
31
|
+
rec
|
32
|
+
end
|
33
|
+
end # class Season
|
34
|
+
end # module Sync
|
35
|
+
end # module SportDb
|
36
|
+
|
@@ -1,10 +1,11 @@
|
|
1
1
|
|
2
2
|
|
3
3
|
module SportDb
|
4
|
+
module Module
|
4
5
|
module Readers
|
5
6
|
|
6
|
-
MAJOR =
|
7
|
-
MINOR =
|
7
|
+
MAJOR = 2 ## todo: namespace inside version or something - why? why not??
|
8
|
+
MINOR = 0
|
8
9
|
PATCH = 0
|
9
10
|
VERSION = [MAJOR,MINOR,PATCH].join('.')
|
10
11
|
|
@@ -21,4 +22,5 @@ module Readers
|
|
21
22
|
end
|
22
23
|
|
23
24
|
end # module Readers
|
25
|
+
end # module Module
|
24
26
|
end # module SportDb
|
data/lib/sportdb/readers.rb
CHANGED
@@ -1,10 +1,22 @@
|
|
1
|
-
|
2
|
-
require 'sportdb/
|
1
|
+
require 'sportdb/formats'
|
2
|
+
require 'sportdb/catalogs'
|
3
|
+
require 'sportdb/models'
|
3
4
|
|
4
5
|
|
5
6
|
###
|
6
7
|
# our own code
|
7
8
|
require_relative 'readers/version' # let version always go first
|
9
|
+
|
10
|
+
## add sync stuff
|
11
|
+
require_relative 'readers/sync/country'
|
12
|
+
require_relative 'readers/sync/league'
|
13
|
+
require_relative 'readers/sync/season'
|
14
|
+
require_relative 'readers/sync/event'
|
15
|
+
require_relative 'readers/sync/club'
|
16
|
+
require_relative 'readers/sync/more'
|
17
|
+
require_relative 'readers/sync/match' ## note - let match sync go last
|
18
|
+
|
19
|
+
## add readers & friends
|
8
20
|
require_relative 'readers/conf_reader'
|
9
21
|
require_relative 'readers/match_reader'
|
10
22
|
require_relative 'readers/package'
|
@@ -12,7 +24,6 @@ require_relative 'readers/package'
|
|
12
24
|
|
13
25
|
|
14
26
|
|
15
|
-
|
16
27
|
##
|
17
28
|
## add convenience shortcut helpers
|
18
29
|
module SportDb
|
@@ -57,4 +68,4 @@ end # module SportDb
|
|
57
68
|
|
58
69
|
|
59
70
|
|
60
|
-
puts SportDb::Readers.banner # say hello
|
71
|
+
puts SportDb::Module::Readers.banner # say hello
|
metadata
CHANGED
@@ -1,29 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sportdb-readers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gerald Bauer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-08-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name: sportdb-
|
14
|
+
name: sportdb-formats
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 2.0.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 2.0.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: sportdb-catalogs
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.2.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.2.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: sportdb-models
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.1.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.1.0
|
27
55
|
- !ruby/object:Gem::Dependency
|
28
56
|
name: rdoc
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -78,6 +106,13 @@ files:
|
|
78
106
|
- lib/sportdb/readers/conf_reader.rb
|
79
107
|
- lib/sportdb/readers/match_reader.rb
|
80
108
|
- lib/sportdb/readers/package.rb
|
109
|
+
- lib/sportdb/readers/sync/club.rb
|
110
|
+
- lib/sportdb/readers/sync/country.rb
|
111
|
+
- lib/sportdb/readers/sync/event.rb
|
112
|
+
- lib/sportdb/readers/sync/league.rb
|
113
|
+
- lib/sportdb/readers/sync/match.rb
|
114
|
+
- lib/sportdb/readers/sync/more.rb
|
115
|
+
- lib/sportdb/readers/sync/season.rb
|
81
116
|
- lib/sportdb/readers/version.rb
|
82
117
|
homepage: https://github.com/sportdb/sport.db
|
83
118
|
licenses:
|
@@ -93,7 +128,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
93
128
|
requirements:
|
94
129
|
- - ">="
|
95
130
|
- !ruby/object:Gem::Version
|
96
|
-
version:
|
131
|
+
version: 3.1.0
|
97
132
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
133
|
requirements:
|
99
134
|
- - ">="
|