sportradar-api 0.10.13 → 0.10.14

Sign up to get free protection for your applications and to get access to all the features.
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
+