sportradar-api 0.10.13 → 0.10.14

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9da116cf411ee95bd14d9976cfb3344e1ee5a317
4
- data.tar.gz: '0418e7aa0245f8c38f690aa0178a9d6442db61db'
3
+ metadata.gz: d93b82036e8ee5eea224b2b56e3a57afb8ed492a
4
+ data.tar.gz: 0abfe21cc3bdb2ebc74f4d7f402e7d329ab3505b
5
5
  SHA512:
6
- metadata.gz: 3463cdca4685bcb3b69b6e312b82b5a97a53c2d02bb0f83f011c04d1cf338c258041adf14e803416e03da23b126fd908da5181bdc6b6f034fba7a05ca7d5a140
7
- data.tar.gz: 66556d99c187633414e27b1fcf407022d0024d27696e40f95cffc2736b04a0a29e725e7f0a28c82748610dfb27b2f86ffb82d7191bc15814ef1dd5fab3b74b3e
6
+ metadata.gz: 707e4c95d584e4b847b9a7a555b69f0ce6d95519c4274089cc46f28bb47b3fbf65989ea20bbcb3378a33392d559533711582357aefe739adc32503ee8fbed6ef
7
+ data.tar.gz: bd7ca5ac31b86cbb09f5180c441587c53d1514eb55c965f703eccfcb5606a10391b9344dddf558648e0dd2ba484b9bb420d9bca07a4375e359765a8972c8839b
data/.travis.yml CHANGED
@@ -2,5 +2,5 @@ sudo: false
2
2
  language: ruby
3
3
  rvm:
4
4
  - 2.3.1
5
- - 2.4.0
5
+ - 2.4.1
6
6
  before_install: gem install bundler -v 1.12.5
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- sportradar-api (0.10.13)
4
+ sportradar-api (0.10.14)
5
5
  activesupport
6
6
  httparty (>= 0.14.0)
7
7
 
data/README.md CHANGED
@@ -23,7 +23,7 @@ Our goal is to incrementally integrate with them. **Contributions are welcome**
23
23
  | API | Version | Docs | Implemented? | Priority |
24
24
  | --- | --- | --- | --- | --- |
25
25
  | NFL | 1 | [📚](http://developer.sportradar.us/page/NFL_Official) | ✅ | 👍 |
26
- | MLB | 5 | [📚](http://developer.sportradar.us/docs/MLB_API) | - | - |
26
+ | MLB | 6 | [📚](https://developer.sportradar.com/files/indexBaseball.html#major-league-baseball-api-v6) | | 👍 |
27
27
  | NHL | 3 | [📚](http://developer.sportradar.us/docs/NHL_API) | - | - |
28
28
  | NBA | 3 | [📚](http://developer.sportradar.us/docs/NBA_API) | ✅ | - |
29
29
  | NCAAMB | 3 | [📚](http://developer.sportradar.us/docs/NCAA_Mens_Basketball) | ✅ | - |
@@ -0,0 +1,3 @@
1
+ # Sportradar Baseball
2
+
3
+ ![API Reference](http://developer.sportradar.com/files/MLBv6SVG.svg)
@@ -0,0 +1,25 @@
1
+ module Sportradar
2
+ module Api
3
+ module Baseball
4
+ class Error < Data
5
+ attr_accessor :response, :id, :type, :last_name, :first_name, :preferred_name, :jersey_number
6
+
7
+ def initialize(data, **opts)
8
+ @response = data
9
+ # @game = opts[:game]
10
+
11
+ update(data)
12
+ end
13
+ def update(data, **opts)
14
+ @id = data["id"]
15
+ @type = data["type"]
16
+ @last_name = data["last_name"]
17
+ @first_name = data["first_name"]
18
+ @preferred_name = data["preferred_name"]
19
+ @jersey_number = data["jersey_number"]
20
+ end
21
+
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,44 @@
1
+ module Sportradar
2
+ module Api
3
+ module Baseball
4
+ class Event < Data
5
+ attr_accessor :response, :id, :at_bat, :lineup, :warming_up, :half_inning
6
+ # alias :type :event_type
7
+
8
+ def initialize(hash, **opts)
9
+ @response = hash
10
+ @half_inning = opts[:half_inning]
11
+ @at_bat = AtBat.new(hash['at_bat'], event: self) if hash['at_bat']
12
+ @lineup = Lineup.new(hash['lineup'], event: self) if hash['lineup']
13
+ @warming_up = WarmingUp.new(hash['warming_up'], event: self) if hash['warming_up']
14
+ end
15
+
16
+ def description
17
+ (@at_bat || @lineup || @warming_up)&.description
18
+ end
19
+
20
+ # def self.new(data, **opts)
21
+ # klass = subclass(data.keys.first)
22
+ # klass.new(data, **opts)
23
+ # rescue => e
24
+ # binding.pry
25
+ # end
26
+
27
+ # def ==(other)
28
+ # @at_bat == other.at_bat && @warming_up == other.warming_up && @lineup == other.lineup
29
+ # end
30
+ def self.subclass(event_type)
31
+ subclasses[event_type]
32
+ end
33
+ def self.subclasses
34
+ @subclasses ||= {
35
+ 'at_bat' => AtBat,
36
+ 'lineup' => Lineup,
37
+ 'warming_up' => WarmingUp,
38
+ }.freeze
39
+ end
40
+
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,49 @@
1
+ module Sportradar
2
+ module Api
3
+ module Baseball
4
+ class Event
5
+ class AtBat < Data
6
+ attr_accessor :response, :id, :event, :hitter_id, :outcome, :description
7
+
8
+ def initialize(data, **opts)
9
+ # @response = data
10
+ @api = opts[:api]
11
+ @event = opts[:event]
12
+
13
+ @id = data["id"]
14
+ @type = data['type']
15
+
16
+ @pitches_hash = {}
17
+
18
+ update(data)
19
+ end
20
+
21
+ # def ==(other)
22
+ # return false if other.nil?
23
+ # @id == other.id && pitches == other.pitches
24
+ # end
25
+
26
+ def update(data, **opts)
27
+ @description = data['description'] if data['description']
28
+ @hitter_id = data['hitter_id'] if data['hitter_id']
29
+ # this hasn't been checked yet
30
+ # pitch events
31
+ create_data(@pitches_hash, data.dig('events'), klass: Pitch, api: @api, at_bat: self)
32
+ end
33
+
34
+ def data_key
35
+ 'at_bat'
36
+ end
37
+
38
+ def over?
39
+ pitches.last.is_ab_over
40
+ end
41
+
42
+ def pitches
43
+ @pitches_hash.values
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,36 @@
1
+ module Sportradar
2
+ module Api
3
+ module Baseball
4
+ class Event
5
+ class Lineup < Data
6
+ attr_accessor :response, :id, :hitter_id, :outcome, :description
7
+
8
+ def initialize(data, **opts)
9
+ @response = data
10
+ # @api = opts[:api]
11
+ # @half_inning = opts[:half_inning]
12
+ @event = opts[:event]
13
+
14
+ @id = data["id"]
15
+ # @type = data['type']
16
+
17
+ update(data)
18
+ end
19
+
20
+ def update(data, **opts)
21
+ @description = data['description'] if data['description']
22
+ end
23
+
24
+ def data_key
25
+ 'lineup'
26
+ end
27
+
28
+
29
+ {"lineup"=>{"description"=>"Brandon Kintzler (P) replaces Matt Belisle (P).", "id"=>"ad7357db-117e-4454-8d44-418ef64aa275", "player_id"=>"e0d89956-1427-4b2f-9025-10330561e464", "order"=>0, "position"=>1, "team_id"=>"aa34e0ed-f342-4ec6-b774-c79b47b60e2d", "last_name"=>"Kintzler", "first_name"=>"Brandon", "preferred_name"=>"Brandon", "jersey_number"=>"27"}}
30
+ {"lineup"=>{"description"=>"Domingo Santana pinch-hitting for Rob Scahill.", "id"=>"4d232302-defe-4ed9-a1fc-4ef44e871138", "player_id"=>"d80a8b1f-aee7-400c-b21f-75e0ac4a32a2", "order"=>9, "position"=>11, "team_id"=>"dcfd5266-00ce-442c-bc09-264cd20cf455", "last_name"=>"Santana", "first_name"=>"Domingo", "preferred_name"=>"Domingo", "jersey_number"=>"16"}}
31
+
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,32 @@
1
+ module Sportradar
2
+ module Api
3
+ module Baseball
4
+ class Event
5
+ class WarmingUp < Data
6
+ attr_accessor :response, :id, :hitter_id, :outcome
7
+
8
+ def initialize(data, **opts)
9
+ @response = data
10
+ # @api = opts[:api]
11
+ # @half_inning = opts[:half_inning]
12
+ @event = opts[:event]
13
+
14
+ @id = data["id"]
15
+ @type = data['type']
16
+
17
+ update(data)
18
+ end
19
+
20
+ def update(data, **opts)
21
+ # parse pitches
22
+ end
23
+
24
+ def data_key
25
+ 'warming_up'
26
+ end
27
+
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,26 @@
1
+ module Sportradar
2
+ module Api
3
+ module Baseball
4
+ class Fielder < Data
5
+ attr_accessor :response, :id, :starting_base, :ending_base, :outcome_id, :out
6
+
7
+ def initialize(data, **opts)
8
+ @response = data
9
+ # @game = opts[:game]
10
+
11
+ update(data)
12
+ end
13
+ def update(data, **opts)
14
+ @id = data["id"]
15
+ @type = data["type"]
16
+ @sequence = data["sequence"]
17
+ @last_name = data["last_name"]
18
+ @first_name = data["first_name"]
19
+ @preferred_name = data["preferred_name"]
20
+ @jersey_number = data["jersey_number"]
21
+ end
22
+
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,385 @@
1
+ module Sportradar
2
+ module Api
3
+ module Baseball
4
+ class Game < Data
5
+ attr_accessor :response, :id, :title, :home_id, :away_id, :score, :status, :coverage, :scheduled, :venue, :broadcast, :duration, :attendance, :team_stats, :player_stats, :changes
6
+
7
+ attr_reader :inning, :half, :outs, :bases, :pitchers
8
+ attr_reader :outcome, :count
9
+
10
+ def initialize(data, **opts)
11
+ @response = data
12
+ @api = opts[:api]
13
+ # @season = opts[:season]
14
+ @updates = {}
15
+ @changes = {}
16
+
17
+ @score = {}
18
+ @team_stats = {}
19
+ @player_stats = {}
20
+ @scoring_raw = Scoring.new(data, game: self)
21
+ @teams_hash = {}
22
+ @innings_hash = {}
23
+ @home_runs = nil
24
+ @away_runs = nil
25
+ @home_id = nil
26
+ @away_id = nil
27
+ @outcome = Outcome.new(data, game: self)
28
+ @count = {}
29
+ @pitchers = {}
30
+
31
+ @id = data['id']
32
+
33
+ update(data, **opts)
34
+ end
35
+
36
+ def timeouts
37
+ {}
38
+ end
39
+
40
+ def tied?
41
+ @score[away_id].to_i == @score[home_id].to_i
42
+ end
43
+ def runs(team_id)
44
+ summary_stat(team_id, 'runs')
45
+ end
46
+ def hits(team_id)
47
+ @scoring_raw.hits(team_id)
48
+ end
49
+ def errors(team_id)
50
+ @scoring_raw.errors(team_id)
51
+ end
52
+ def team_summary(team_id)
53
+ team_id.is_a?(Symbol) ? @score[@team_ids[team_id]] : @score[team_id]
54
+ end
55
+ def summary_stat(team_id, stat)
56
+ team_summary(team_id)[stat]
57
+ end
58
+ def stats(team_id)
59
+ team_id.is_a?(Symbol) ? @team_stats[@team_ids[team_id]].to_i : @team_stats[team_id].to_i
60
+ end
61
+
62
+ def scoring
63
+ @scoring_raw.scores
64
+ end
65
+ def update_score(score)
66
+ @score.merge!(score)
67
+ end
68
+ def update_stats(team, stats)
69
+ @team_stats.merge!(team.id => stats.merge!(team: team))
70
+ end
71
+ def update_player_stats(player, stats)
72
+ @player_stats.merge!(player.id => stats.merge!(player: player))
73
+ end
74
+
75
+ def parse_score(data)
76
+ home_id = data.dig('home', 'id')
77
+ away_id = data.dig('away', 'id')
78
+ rhe = {
79
+ 'runs' => { home_id => data.dig('home', 'runs'), away_id => data.dig('away', 'runs')},
80
+ 'hits' => { home_id => data.dig('home', 'hits'), away_id => data.dig('away', 'hits')},
81
+ 'errors' => { home_id => data.dig('home', 'errors'), away_id => data.dig('away', 'errors')},
82
+ }
83
+ @scoring_raw.update(rhe, source: :rhe)
84
+ update_score(home_id => data.dig('home', 'runs'))
85
+ update_score(away_id => data.dig('away', 'runs'))
86
+ end
87
+
88
+ def parse_pitchers(data)
89
+ pitchers = {
90
+ 'starting' => { home_id => data.dig('home', 'starting_pitcher'), away_id => data.dig('away', 'starting_pitcher')},
91
+ 'probable' => { home_id => data.dig('home', 'probable_pitcher'), away_id => data.dig('away', 'probable_pitcher')},
92
+ 'current' => { home_id => data.dig('home', 'current_pitcher'), away_id => data.dig('away', 'current_pitcher')},
93
+ }
94
+ @pitchers.merge!(pitchers) do |key, current_val, merge_val|
95
+ current_val.merge(merge_val) { |k, cur, mer| (mer || cur) }
96
+ end
97
+ end
98
+
99
+ def update(data, source: nil, **opts)
100
+ # via pbp
101
+ @title = data['title'] if data['title']
102
+ @status = data['status'] if data['status']
103
+ @coverage = data['coverage'] if data['coverage']
104
+ @day_night = data['day_night'] if data['day_night']
105
+ @game_number = data['game_number'] if data['game_number']
106
+ @home_id = data['home_team'] || data.dig('home', 'id') if data['home_team'] || data.dig('home', 'id')
107
+ @away_id = data['away_team'] || data.dig('away', 'id') if data['away_team'] || data.dig('away', 'id')
108
+ # @home_runs = data['home_runs'].to_i if data['home_runs']
109
+ # @away_runs = data['away_runs'].to_i if data['away_runs']
110
+
111
+ @scheduled = Time.parse(data["scheduled"]) if data["scheduled"]
112
+ @venue = Venue.new(data['venue']) if data['venue']
113
+ @broadcast = Broadcast.new(data['broadcast']) if data['broadcast']
114
+ @home = Team.new(data['home'], api: api, game: self) if data['home']
115
+ @away = Team.new(data['away'], api: api, game: self) if data['away']
116
+
117
+ @duration = data['duration'] if data['duration']
118
+ @attendance = data['attendance'] if data['attendance']
119
+
120
+ @team_ids = { home: @home_id, away: @away_id}
121
+
122
+ parse_pitchers(data) if data['home'] && data['away']
123
+
124
+ if data['scoring']
125
+ parse_score(data['scoring'])
126
+ elsif data.dig('home', 'hits')
127
+ parse_score(data)
128
+ end
129
+ @scoring_raw.update(data, source: source)
130
+ if data['outcome']
131
+ @outcome.update(data, source: nil)
132
+ @count.merge!(@outcome.count || {})
133
+ end
134
+
135
+ create_data(@teams_hash, data['team'], klass: Team, api: api, game: self) if data['team']
136
+ end
137
+
138
+ # def update_from_team(id, data)
139
+ # end
140
+
141
+ def extract_count(data) # extract from pbp
142
+ recent_pitches = pitches.last(10)
143
+ last_pitch = recent_pitches.reverse_each.detect(&:count)
144
+ return unless last_pitch
145
+ @count.merge!(last_pitch.count)
146
+ hi = last_pitch.at_bat.event.half_inning
147
+ @count.merge!('inning' => hi.number.to_i, 'inning_half' => hi.half)
148
+ end
149
+
150
+ def home
151
+ @teams_hash[@home_id] || @home
152
+ end
153
+
154
+ def away
155
+ @teams_hash[@away_id] || @away
156
+ end
157
+
158
+ def leading_team_id
159
+ return nil if score.values.uniq.size == 1
160
+ score.max_by(&:last).first
161
+ end
162
+
163
+ def leading_team
164
+ @teams_hash[leading_team_id] || (@away_id == leading_team_id && away) || (@home_id == leading_team_id && home)
165
+ end
166
+
167
+ def team(team_id)
168
+ @teams_hash[team_id]
169
+ end
170
+
171
+ def assign_home(team)
172
+ @home_id = team.id
173
+ @teams_hash[team.id] = team
174
+ end
175
+
176
+ def assign_away(team)
177
+ @away_id = team.id
178
+ @teams_hash[team.id] = team
179
+ end
180
+
181
+ def box
182
+ @box ||= get_box
183
+ end
184
+
185
+ def pbp
186
+ if !future? && innings.empty?
187
+ get_pbp
188
+ end
189
+ @pbp ||= innings
190
+ end
191
+
192
+ def atbats
193
+ innings.flat_map(&:atbats)
194
+ end
195
+
196
+ def events
197
+ innings.flat_map(&:events)
198
+ end
199
+
200
+ def at_bats
201
+ events.map(&:at_bat).compact
202
+ end
203
+
204
+ def pitches
205
+ at_bats.flat_map(&:pitches)
206
+ end
207
+
208
+ def summary
209
+ @summary ||= get_summary
210
+ end
211
+
212
+ def innings
213
+ @innings_hash.values
214
+ end
215
+ def half_innings
216
+ innings.flat_map(&:half_innings)
217
+ end
218
+
219
+ # tracking updates
220
+ def remember(key, object)
221
+ @updates[key] = object&.dup
222
+ end
223
+
224
+ def not_updated?(key, object)
225
+ @updates[key] == object
226
+ end
227
+
228
+ def changed?(key)
229
+ @changes[key]
230
+ end
231
+
232
+ def check_newness(key, new_object)
233
+ @changes[key] = !not_updated?(key, new_object)
234
+ remember(key, new_object)
235
+ end
236
+
237
+ # status helpers
238
+ def postponed?
239
+ 'postponed' == status
240
+ end
241
+
242
+ def unnecessary?
243
+ 'unnecessary' == status
244
+ end
245
+
246
+ def cancelled?
247
+ ['unnecessary', 'postponed'].include? status
248
+ end
249
+
250
+ def future?
251
+ ['scheduled', 'delayed', 'created', 'time-tbd'].include? status
252
+ end
253
+
254
+ def started?
255
+ ['inprogress', 'wdelay', 'delayed'].include? status
256
+ end
257
+
258
+ def finished?
259
+ ['complete', 'closed'].include? status
260
+ end
261
+
262
+ def completed?
263
+ 'complete' == status
264
+ end
265
+
266
+ def closed?
267
+ 'closed' == status
268
+ end
269
+
270
+ # url path helpers
271
+ def path_base
272
+ "games/#{ id }"
273
+ end
274
+
275
+ def path_box
276
+ "#{ path_base }/boxscore"
277
+ end
278
+
279
+ def path_pbp
280
+ "#{ path_base }/pbp"
281
+ end
282
+
283
+ def path_summary
284
+ "#{ path_base }/summary"
285
+ end
286
+
287
+ # data retrieval
288
+
289
+ def get_box
290
+ data = api.get_data(path_box)
291
+ ingest_box(data)
292
+ end
293
+
294
+ def ingest_box(data)
295
+ data = data['game']
296
+ update(data, source: :box)
297
+ check_newness(:box, @clock)
298
+ data
299
+ end
300
+
301
+ def queue_pbp
302
+ url, headers, options, timeout = api.get_request_info(path_pbp)
303
+ {url: url, headers: headers, params: options, timeout: timeout, callback: method(:ingest_pbp)}
304
+ end
305
+
306
+ def get_pbp
307
+ data = api.get_data(path_pbp);
308
+ ingest_pbp(data)
309
+ end
310
+
311
+ def ingest_pbp(data)
312
+ data = data['game']
313
+ update(data, source: :pbp)
314
+ # @pbp = @innings_hash.values
315
+ innings = data['innings'].each { |inning| inning['id'] = "#{data['id']}-#{inning['number']}" }
316
+ create_data(@innings_hash, innings, klass: Inning, api: api, game: self) if data['innings']
317
+ extract_count(data)
318
+ check_newness(:pitches, pitches.last&.id)
319
+ check_newness(:events, events.last&.description)
320
+ check_newness(:score, @score)
321
+ data
322
+ # rescue => e
323
+ # binding.pry
324
+ end
325
+
326
+ def get_summary
327
+ data = api.get_data(path_summary)
328
+ ingest_summary(data)
329
+ end
330
+
331
+ def queue_summary
332
+ url, headers, options, timeout = api.get_request_info(path_summary)
333
+ {url: url, headers: headers, params: options, timeout: timeout, callback: method(:ingest_summary)}
334
+ end
335
+
336
+ def ingest_summary(data)
337
+ data = data['game']
338
+ update(data, source: :summary)
339
+ @inning = data.delete('inning').to_i
340
+ check_newness(:box, @clock)
341
+ check_newness(:score, @score)
342
+ data
343
+ end
344
+
345
+ def set_pbp(data)
346
+ create_data(@innings_hash, data, klass: inning_class, api: api, game: self)
347
+ @innings_hash
348
+ end
349
+
350
+ def api
351
+ @api || Sportradar::Api::Baseball::Mlb.new
352
+ end
353
+ end
354
+ end
355
+ end
356
+ end
357
+
358
+ __END__
359
+
360
+ # mlb = Sportradar::Api::Baseball::Mlb::Hierarchy.new
361
+ # res = mlb.get_schedule;
362
+ # g = mlb.games.first
363
+ # g = Sportradar::Api::Baseball::Game.new('id' => "8cd71519-429f-4461-88a2-8a0e134eb89b")
364
+ g = Sportradar::Api::Baseball::Game.new('id' => "9d0fe41c-4e6b-4433-b376-2d09ed39d184");
365
+ g = Sportradar::Api::Baseball::Game.new('id' => "0ab634d3-ce6c-4a79-be8d-b62b0ee506d5");
366
+ res = g.get_pbp;
367
+ res = g.get_summary;
368
+ res = g.get_box # probably not as useful as summary
369
+
370
+
371
+ mlb = Sportradar::Api::Baseball::Mlb::Hierarchy.new
372
+ res = mlb.get_daily_summary;
373
+ g = mlb.games[8];
374
+ g.count
375
+ g.get_pbp;
376
+ g.count
377
+
378
+ # mlb = Sportradar::Api::Baseball::Mlb::Hierarchy.new;
379
+ # res = mlb.get_daily_summary;
380
+ # g = mlb.games.sort_by(&:scheduled).first;
381
+ g = Sportradar::Api::Baseball::Game.new('id' => "8731b56d-9037-44d1-b890-fa496e94dc10");
382
+ res = g.get_pbp;
383
+ res = g.get_summary;
384
+ g.pitchers
385
+