sportdb-readers 1.2.0 → 2.0.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/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
|
- - ">="
|