sportradar-api 0.10.7 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. checksums.yaml +4 -4
  2. data/.env.sample +2 -0
  3. data/.travis.yml +1 -1
  4. data/Gemfile.lock +30 -28
  5. data/README.md +10 -7
  6. data/lib/sportradar/api/baseball/README.md +28 -0
  7. data/lib/sportradar/api/baseball/error.rb +25 -0
  8. data/lib/sportradar/api/baseball/event.rb +44 -0
  9. data/lib/sportradar/api/baseball/events/at_bat.rb +96 -0
  10. data/lib/sportradar/api/baseball/events/lineup.rb +41 -0
  11. data/lib/sportradar/api/baseball/events/warming_up.rb +35 -0
  12. data/lib/sportradar/api/baseball/fielder.rb +26 -0
  13. data/lib/sportradar/api/baseball/game.rb +472 -0
  14. data/lib/sportradar/api/baseball/half_inning.rb +69 -0
  15. data/lib/sportradar/api/baseball/inning.rb +42 -0
  16. data/lib/sportradar/api/baseball/lineup.rb +116 -0
  17. data/lib/sportradar/api/baseball/mlb/division.rb +32 -0
  18. data/lib/sportradar/api/baseball/mlb/hierarchy.rb +273 -0
  19. data/lib/sportradar/api/baseball/mlb/league.rb +27 -0
  20. data/lib/sportradar/api/baseball/mlb.rb +84 -0
  21. data/lib/sportradar/api/baseball/outcome.rb +87 -0
  22. data/lib/sportradar/api/baseball/pitch.rb +245 -0
  23. data/lib/sportradar/api/baseball/player.rb +119 -0
  24. data/lib/sportradar/api/baseball/record.rb +42 -0
  25. data/lib/sportradar/api/baseball/runner.rb +78 -0
  26. data/lib/sportradar/api/baseball/scoring.rb +86 -0
  27. data/lib/sportradar/api/baseball/team.rb +147 -0
  28. data/lib/sportradar/api/baseball/venue.rb +51 -0
  29. data/lib/sportradar/api/baseball.rb +46 -0
  30. data/lib/sportradar/api/basketball/game.rb +42 -7
  31. data/lib/sportradar/api/basketball/nba/game.rb +5 -0
  32. data/lib/sportradar/api/basketball/nba/series.rb +25 -2
  33. data/lib/sportradar/api/basketball/nba/team.rb +5 -0
  34. data/lib/sportradar/api/basketball/nba.rb +2 -2
  35. data/lib/sportradar/api/basketball/ncaamb/game.rb +15 -0
  36. data/lib/sportradar/api/basketball/ncaamb.rb +2 -2
  37. data/lib/sportradar/api/basketball/record.rb +0 -4
  38. data/lib/sportradar/api/basketball/venue.rb +2 -1
  39. data/lib/sportradar/api/{nfl/broadcast.rb → broadcast.rb} +1 -1
  40. data/lib/sportradar/api/config.rb +2 -2
  41. data/lib/sportradar/api/content.rb +9 -1
  42. data/lib/sportradar/api/data.rb +11 -6
  43. data/lib/sportradar/api/football/README.md +62 -0
  44. data/lib/sportradar/api/football/drive.rb +109 -0
  45. data/lib/sportradar/api/football/event.rb +36 -0
  46. data/lib/sportradar/api/football/game.rb +404 -0
  47. data/lib/sportradar/api/football/game_stats.rb +135 -0
  48. data/lib/sportradar/api/football/ncaafb/api.rb +81 -0
  49. data/lib/sportradar/api/football/ncaafb/conference.rb +53 -0
  50. data/lib/sportradar/api/football/ncaafb/division.rb +42 -0
  51. data/lib/sportradar/api/football/ncaafb/drive.rb +16 -0
  52. data/lib/sportradar/api/football/ncaafb/game.rb +115 -0
  53. data/lib/sportradar/api/football/ncaafb/play.rb +42 -0
  54. data/lib/sportradar/api/football/ncaafb/player.rb +31 -0
  55. data/lib/sportradar/api/football/ncaafb/quarter.rb +15 -0
  56. data/lib/sportradar/api/football/ncaafb/subdivision.rb +43 -0
  57. data/lib/sportradar/api/football/ncaafb/team.rb +49 -0
  58. data/lib/sportradar/api/football/ncaafb/week.rb +24 -0
  59. data/lib/sportradar/api/football/ncaafb.rb +273 -0
  60. data/lib/sportradar/api/football/nfl/api.rb +76 -0
  61. data/lib/sportradar/api/football/nfl/conference.rb +35 -0
  62. data/lib/sportradar/api/football/nfl/division.rb +37 -0
  63. data/lib/sportradar/api/football/nfl/drive.rb +19 -0
  64. data/lib/sportradar/api/football/nfl/event.rb +19 -0
  65. data/lib/sportradar/api/football/nfl/game.rb +77 -0
  66. data/lib/sportradar/api/football/nfl/play.rb +20 -0
  67. data/lib/sportradar/api/football/nfl/player.rb +31 -0
  68. data/lib/sportradar/api/football/nfl/quarter.rb +15 -0
  69. data/lib/sportradar/api/football/nfl/team.rb +42 -0
  70. data/lib/sportradar/api/football/nfl/week.rb +24 -0
  71. data/lib/sportradar/api/football/nfl.rb +293 -0
  72. data/lib/sportradar/api/football/play.rb +101 -0
  73. data/lib/sportradar/api/football/player.rb +120 -0
  74. data/lib/sportradar/api/football/quarter.rb +42 -0
  75. data/lib/sportradar/api/football/record.rb +48 -0
  76. data/lib/sportradar/api/football/scoring.rb +92 -0
  77. data/lib/sportradar/api/football/situation.rb +20 -0
  78. data/lib/sportradar/api/football/stat_pack/defense.rb +20 -20
  79. data/lib/sportradar/api/football/stat_pack/extra_points.rb +5 -4
  80. data/lib/sportradar/api/football/stat_pack/field_goals.rb +6 -5
  81. data/lib/sportradar/api/football/stat_pack/fumbles.rb +11 -9
  82. data/lib/sportradar/api/football/stat_pack/kick_returns.rb +8 -7
  83. data/lib/sportradar/api/football/stat_pack/kickoffs.rb +9 -7
  84. data/lib/sportradar/api/football/stat_pack/misc_returns.rb +1 -1
  85. data/lib/sportradar/api/football/stat_pack/passing.rb +13 -11
  86. data/lib/sportradar/api/football/stat_pack/penalties.rb +2 -2
  87. data/lib/sportradar/api/football/stat_pack/punt_returns.rb +7 -6
  88. data/lib/sportradar/api/football/stat_pack/punts.rb +12 -10
  89. data/lib/sportradar/api/football/stat_pack/receiving.rb +10 -9
  90. data/lib/sportradar/api/football/stat_pack/rushing.rb +11 -9
  91. data/lib/sportradar/api/football/stat_pack.rb +7 -3
  92. data/lib/sportradar/api/football/team.rb +160 -0
  93. data/lib/sportradar/api/football/venue.rb +25 -0
  94. data/lib/sportradar/api/football/week.rb +57 -0
  95. data/lib/sportradar/api/football.rb +43 -0
  96. data/lib/sportradar/api/images/asset.rb +2 -1
  97. data/lib/sportradar/api/images/ref.rb +15 -0
  98. data/lib/sportradar/api/images.rb +46 -8
  99. data/lib/sportradar/api/mma/venue.rb +1 -1
  100. data/lib/sportradar/api/request.rb +24 -2
  101. data/lib/sportradar/api/soccer/schedule.rb +1 -0
  102. data/lib/sportradar/api/version.rb +1 -1
  103. data/lib/sportradar/api.rb +6 -43
  104. metadata +67 -43
  105. data/lib/sportradar/api/ncaafb/drive.rb +0 -89
  106. data/lib/sportradar/api/ncaafb/game.rb +0 -161
  107. data/lib/sportradar/api/ncaafb/play.rb +0 -39
  108. data/lib/sportradar/api/ncaafb/player.rb +0 -61
  109. data/lib/sportradar/api/ncaafb/quarter.rb +0 -37
  110. data/lib/sportradar/api/ncaafb/season.rb +0 -71
  111. data/lib/sportradar/api/ncaafb/team.rb +0 -69
  112. data/lib/sportradar/api/ncaafb/week.rb +0 -33
  113. data/lib/sportradar/api/ncaafb.rb +0 -185
  114. data/lib/sportradar/api/nfl/.play_statistics.rb.swo +0 -0
  115. data/lib/sportradar/api/nfl/changelog.rb +0 -17
  116. data/lib/sportradar/api/nfl/coach.rb +0 -17
  117. data/lib/sportradar/api/nfl/conference.rb +0 -17
  118. data/lib/sportradar/api/nfl/depth_chart.rb +0 -47
  119. data/lib/sportradar/api/nfl/division.rb +0 -16
  120. data/lib/sportradar/api/nfl/draft.rb +0 -16
  121. data/lib/sportradar/api/nfl/drive.rb +0 -73
  122. data/lib/sportradar/api/nfl/event.rb +0 -19
  123. data/lib/sportradar/api/nfl/franchise.rb +0 -14
  124. data/lib/sportradar/api/nfl/game.rb +0 -123
  125. data/lib/sportradar/api/nfl/game_statistic.rb +0 -133
  126. data/lib/sportradar/api/nfl/hierarchy.rb +0 -19
  127. data/lib/sportradar/api/nfl/injury.rb +0 -16
  128. data/lib/sportradar/api/nfl/league_depth_chart.rb +0 -26
  129. data/lib/sportradar/api/nfl/play.rb +0 -39
  130. data/lib/sportradar/api/nfl/play_statistics.rb +0 -249
  131. data/lib/sportradar/api/nfl/player.rb +0 -56
  132. data/lib/sportradar/api/nfl/position.rb +0 -14
  133. data/lib/sportradar/api/nfl/quarter.rb +0 -29
  134. data/lib/sportradar/api/nfl/scoring.rb +0 -25
  135. data/lib/sportradar/api/nfl/season.rb +0 -23
  136. data/lib/sportradar/api/nfl/situation.rb +0 -18
  137. data/lib/sportradar/api/nfl/statistic.rb +0 -28
  138. data/lib/sportradar/api/nfl/summary.rb +0 -17
  139. data/lib/sportradar/api/nfl/team.rb +0 -56
  140. data/lib/sportradar/api/nfl/team_depth_chart.rb +0 -44
  141. data/lib/sportradar/api/nfl/venue.rb +0 -22
  142. data/lib/sportradar/api/nfl/week.rb +0 -16
  143. data/lib/sportradar/api/nfl.rb +0 -211
@@ -0,0 +1,472 @@
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, :lineup
6
+
7
+ attr_reader :inning, :half, :outs, :bases, :pitchers, :final, :rescheduled, :inning_over
8
+ attr_reader :outcome, :count
9
+ DEFAULT_BASES = { '1' => nil, '2' => nil, '3' => nil }
10
+
11
+ def initialize(data, **opts)
12
+ @response = data
13
+ @api = opts[:api]
14
+ # @season = opts[:season]
15
+ @updates = {}
16
+ @changes = {}
17
+
18
+ @score = {}
19
+ @team_stats = {}
20
+ @player_stats = {}
21
+ @scoring_raw = Scoring.new(data, game: self)
22
+ @lineup = Lineup.new(data, game: self)
23
+ @teams_hash = {}
24
+ @innings_hash = {}
25
+ @home_runs = nil
26
+ @away_runs = nil
27
+ @home_id = nil
28
+ @away_id = nil
29
+ @outcome = Outcome.new(data, game: self)
30
+ @count = {}
31
+ @pitchers = {}
32
+ @bases = { '1' => nil, '2' => nil, '3' => nil }
33
+
34
+ @id = data['id']
35
+
36
+ update(data, **opts)
37
+ end
38
+ def lineup
39
+ @lineup ||= Lineup.new({}, game: self)
40
+ end
41
+
42
+ def timeouts
43
+ {}
44
+ end
45
+
46
+ def tied?
47
+ @score[away_id].to_i == @score[home_id].to_i
48
+ end
49
+ # def runs(team_id)
50
+ # summary_stat(team_id, 'runs')
51
+ # end
52
+ def hits(team_id)
53
+ @scoring_raw.hits(team_id)
54
+ end
55
+ def errors(team_id)
56
+ @scoring_raw.errors(team_id)
57
+ end
58
+ def runs(team_id)
59
+ team_id.is_a?(Symbol) ? @score[@team_ids[team_id]] : @score[team_id]
60
+ end
61
+ def summary_stat(team_id, stat_name)
62
+ scoring.dig(team_id, stat_name)
63
+ end
64
+ def stats(team_id)
65
+ team_id.is_a?(Symbol) ? @team_stats[@team_ids[team_id]].to_i : @team_stats[team_id].to_i
66
+ end
67
+
68
+ def scoring
69
+ @scoring_raw.scores
70
+ end
71
+ def update_score(score)
72
+ @score.merge!(score)
73
+ end
74
+ def update_stats(team, stats)
75
+ @team_stats.merge!(team.id => stats.merge!(team: team))
76
+ end
77
+ def update_player_stats(player, stats)
78
+ @player_stats.merge!(player.id => stats.merge!(player: player))
79
+ end
80
+
81
+ def parse_score(data)
82
+ home_id = data.dig('home', 'id')
83
+ away_id = data.dig('away', 'id')
84
+ rhe = {
85
+ 'runs' => { home_id => data.dig('home', 'runs'), away_id => data.dig('away', 'runs')},
86
+ 'hits' => { home_id => data.dig('home', 'hits'), away_id => data.dig('away', 'hits')},
87
+ 'errors' => { home_id => data.dig('home', 'errors'), away_id => data.dig('away', 'errors')},
88
+ }
89
+ @scoring_raw.update(rhe, source: :rhe)
90
+ update_score(home_id => data.dig('home', 'runs'))
91
+ update_score(away_id => data.dig('away', 'runs'))
92
+ end
93
+
94
+ def parse_pitchers(data)
95
+ pitchers = {
96
+ 'starting' => { home_id => data.dig('home', 'starting_pitcher'), away_id => data.dig('away', 'starting_pitcher')},
97
+ 'probable' => { home_id => data.dig('home', 'probable_pitcher'), away_id => data.dig('away', 'probable_pitcher')},
98
+ 'current' => { home_id => data.dig('home', 'current_pitcher'), away_id => data.dig('away', 'current_pitcher')},
99
+ }
100
+ @pitchers.merge!(pitchers) do |key, current_val, merge_val|
101
+ current_val.merge(merge_val) { |k, cur, mer| (mer || cur) }
102
+ end
103
+ end
104
+
105
+ def update(data, source: nil, **opts)
106
+ # via pbp
107
+ @status = data['status'] if data['status']
108
+ @coverage = data['coverage'] if data['coverage']
109
+ @day_night = data['day_night'] if data['day_night']
110
+ @game_number = data['game_number'] if data['game_number']
111
+ @home_id = data['home_team'] || data.dig('home', 'id') if data['home_team'] || data.dig('home', 'id')
112
+ @away_id = data['away_team'] || data.dig('away', 'id') if data['away_team'] || data.dig('away', 'id')
113
+ # @home_runs = data['home_runs'].to_i if data['home_runs']
114
+ # @away_runs = data['away_runs'].to_i if data['away_runs']
115
+
116
+ @scheduled = Time.parse(data["scheduled"]) if data["scheduled"]
117
+ @venue = Venue.new(data['venue']) if data['venue']
118
+ @broadcast = Broadcast.new(data['broadcast']) if !data['broadcast'].to_h.empty?
119
+ @home = Team.new(data['home'], api: api, game: self) if data['home']
120
+ @away = Team.new(data['away'], api: api, game: self) if data['away']
121
+ @title = data['title'] || @title || (home && away && "#{home.full_name} vs #{away.full_name}")
122
+
123
+ @duration = data['duration'] if data['duration']
124
+ @attendance = data['attendance'] if data['attendance']
125
+
126
+ @final = data['final'] if data['final']
127
+ @rescheduled = data['rescheduled'] if data['rescheduled']
128
+
129
+ @team_ids = { home: @home_id, away: @away_id}
130
+
131
+ update_bases(data)
132
+ parse_pitchers(data) if data['home'] && data['away']
133
+
134
+ lineup.update(data, source: source)
135
+ if data['scoring']
136
+ parse_score(data['scoring'])
137
+ elsif data.dig('home', 'hits')
138
+ parse_score(data)
139
+ end
140
+ @scoring_raw.update(data, source: source)
141
+ if data['outcome']
142
+ @outcome.update(data, source: nil)
143
+ @count.merge!(@outcome.count || {})
144
+ end
145
+ create_data(@teams_hash, data['team'], klass: Team, api: api, game: self) if data['team']
146
+ end
147
+
148
+ # def update_from_team(id, data)
149
+ # end
150
+
151
+ def update_bases(data)
152
+ if data.is_a?(Sportradar::Api::Baseball::Error)
153
+ puts data.inspect
154
+ return
155
+ end
156
+ @bases = if data.respond_to?(:runners)
157
+ hash = Array(data.runners).map { |runner| [runner.ending_base.to_s, runner.id] if !runner.out }.compact.to_h
158
+ DEFAULT_BASES.merge(hash)
159
+ elsif (runners = data.dig('outcome', 'runners'))
160
+ hash = runners.map { |runner| [runner['ending_base'].to_s, runner['id']] if !runner['out'] }.compact.to_h
161
+ DEFAULT_BASES.merge(hash)
162
+ else # probably new inning, no runners
163
+ DEFAULT_BASES.dup
164
+ end
165
+ rescue => e
166
+ puts data.inspect
167
+ raise e
168
+ end
169
+ def advance_inning
170
+ @inning_over = false
171
+ return unless count['outs'] == 3
172
+ if count['inning'] >= 9
173
+ if count['inning_half'] == 'T' && leading_team_id == home.id
174
+ return
175
+ elsif count['inning_half'] == 'B' && !tied?
176
+ return
177
+ end
178
+ end
179
+ @inning_over = true
180
+ @bases = DEFAULT_BASES.dup
181
+ half, inn = if count['inning_half'] == 'B'
182
+ ['T', count['inning'] += 1]
183
+ elsif count['inning_half'] == 'T'
184
+ ['B', count['inning']]
185
+ else
186
+ [nil, 1]
187
+ end
188
+ @count = {
189
+ 'balls' => 0,
190
+ 'strikes' => 0,
191
+ 'outs' => 0,
192
+ 'inning' => inn,
193
+ 'inning_half' => half,
194
+ }
195
+ end
196
+
197
+ def extract_count(data) # extract from pbp
198
+ recent_pitches = pitches.last(10)
199
+ last_pitch = recent_pitches.reverse_each.detect(&:count)
200
+ return unless last_pitch
201
+ update_bases(last_pitch)
202
+ @count.merge!(last_pitch.count)
203
+ hi = last_pitch.at_bat.event.half_inning
204
+ @count.merge!('inning' => hi.number.to_i, 'inning_half' => hi.half)
205
+ advance_inning
206
+ end
207
+
208
+ def home
209
+ @teams_hash[@home_id] || @home
210
+ end
211
+
212
+ def away
213
+ @teams_hash[@away_id] || @away
214
+ end
215
+
216
+ def leading_team_id
217
+ return nil if tied?
218
+ score.max_by(&:last).first
219
+ end
220
+
221
+ def leading_team
222
+ @teams_hash[leading_team_id] || (@away_id == leading_team_id && away) || (@home_id == leading_team_id && home)
223
+ end
224
+
225
+ def team(team_id)
226
+ @teams_hash[team_id]
227
+ end
228
+
229
+ def assign_home(team)
230
+ @home_id = team.id
231
+ @teams_hash[team.id] = team
232
+ end
233
+
234
+ def assign_away(team)
235
+ @away_id = team.id
236
+ @teams_hash[team.id] = team
237
+ end
238
+
239
+ def box
240
+ @box ||= get_box
241
+ end
242
+
243
+ def pbp
244
+ if !future? && innings.empty?
245
+ get_pbp
246
+ end
247
+ @pbp ||= innings
248
+ end
249
+
250
+ def events
251
+ innings.flat_map(&:events)
252
+ end
253
+
254
+ def at_bats
255
+ events.map(&:at_bat).compact
256
+ end
257
+
258
+ def pitches
259
+ at_bats.flat_map(&:pitches)
260
+ end
261
+
262
+ def summary
263
+ @summary ||= get_summary
264
+ end
265
+
266
+ def innings
267
+ @innings_hash.values
268
+ end
269
+ def half_innings
270
+ innings.flat_map(&:half_innings)
271
+ end
272
+
273
+ # tracking updates
274
+ def remember(key, object)
275
+ @updates[key] = object&.dup
276
+ end
277
+
278
+ def not_updated?(key, object)
279
+ @updates[key] == object
280
+ end
281
+
282
+ def changed?(key)
283
+ @changes[key]
284
+ end
285
+
286
+ def check_newness(key, new_object)
287
+ @changes[key] = !not_updated?(key, new_object)
288
+ remember(key, new_object)
289
+ end
290
+
291
+ # status helpers
292
+
293
+ def realtime_state
294
+ if future?
295
+ 'Scheduled'
296
+ elsif delayed?
297
+ 'Delayed'
298
+ elsif finished?
299
+ 'Final'
300
+ elsif postponed?
301
+ 'Postponed'
302
+ else
303
+ inning_abbr
304
+ end
305
+ end
306
+
307
+ def inning_abbr
308
+ if !count.empty?
309
+ inning_half = self.count['inning_half']
310
+ inning = self.count['inning']
311
+ "#{(inning_half == 'B' ? 'Bottom' : 'Top')} #{(inning || 1).ordinalize}"
312
+ end
313
+ end
314
+
315
+ def postponed?
316
+ 'postponed' == status
317
+ end
318
+
319
+ def unnecessary?
320
+ 'unnecessary' == status
321
+ end
322
+
323
+ def cancelled?
324
+ ['unnecessary', 'postponed'].include? status
325
+ end
326
+
327
+ def delayed?
328
+ ['wdelay', 'delayed'].include? status
329
+ end
330
+
331
+ def future?
332
+ ['scheduled', 'delayed', 'created', 'time-tbd'].include? status
333
+ end
334
+
335
+ def started?
336
+ ['inprogress', 'wdelay', 'delayed'].include? status
337
+ end
338
+
339
+ def finished?
340
+ ['complete', 'closed'].include? status
341
+ end
342
+
343
+ def completed?
344
+ 'complete' == status
345
+ end
346
+
347
+ def closed?
348
+ 'closed' == status
349
+ end
350
+
351
+ # url path helpers
352
+ def path_base
353
+ "games/#{ id }"
354
+ end
355
+
356
+ def path_box
357
+ "#{ path_base }/boxscore"
358
+ end
359
+
360
+ def path_pbp
361
+ "#{ path_base }/pbp"
362
+ end
363
+
364
+ def path_summary
365
+ "#{ path_base }/summary"
366
+ end
367
+
368
+ # data retrieval
369
+
370
+ def get_box
371
+ data = api.get_data(path_box)
372
+ ingest_box(data)
373
+ end
374
+
375
+ def ingest_box(data)
376
+ data = data['game']
377
+ update(data, source: :box)
378
+ check_newness(:box, @clock)
379
+ data
380
+ end
381
+
382
+ def queue_pbp
383
+ url, headers, options, timeout = api.get_request_info(path_pbp)
384
+ {url: url, headers: headers, params: options, timeout: timeout, callback: method(:ingest_pbp)}
385
+ end
386
+
387
+ def get_pbp
388
+ data = api.get_data(path_pbp);
389
+ ingest_pbp(data)
390
+ end
391
+
392
+ def ingest_pbp(data)
393
+ data = data['game']
394
+ update(data, source: :pbp)
395
+ innings = data['innings'].each { |inning| inning['id'] = "#{data['id']}-#{inning['number']}" }
396
+ create_data(@innings_hash, innings, klass: Inning, api: api, game: self) if data['innings']
397
+ extract_count(data)
398
+ check_newness(:pitches, pitches.last&.id)
399
+ check_newness(:events, events.last&.description)
400
+ check_newness(:score, @score)
401
+ @pbp = @innings_hash.values
402
+ data
403
+ # rescue => e
404
+ # binding.pry
405
+ end
406
+
407
+ def get_summary
408
+ data = api.get_data(path_summary)
409
+ ingest_summary(data)
410
+ end
411
+
412
+ def queue_summary
413
+ url, headers, options, timeout = api.get_request_info(path_summary)
414
+ {url: url, headers: headers, params: options, timeout: timeout, callback: method(:ingest_summary)}
415
+ end
416
+
417
+ def ingest_summary(data)
418
+ data = data['game']
419
+ update(data, source: :summary)
420
+ @inning = data.delete('inning').to_i
421
+ check_newness(:box, @clock)
422
+ check_newness(:score, @score)
423
+ data
424
+ end
425
+
426
+ def set_pbp(data)
427
+ create_data(@innings_hash, data, klass: inning_class, api: api, game: self)
428
+ @innings_hash
429
+ end
430
+
431
+ def api
432
+ @api || Sportradar::Api::Baseball::Mlb.new
433
+ end
434
+
435
+ def sim!
436
+ @api = api.sim!
437
+ self
438
+ end
439
+
440
+ end
441
+ end
442
+ end
443
+ end
444
+
445
+ __END__
446
+
447
+ # mlb = Sportradar::Api::Baseball::Mlb::Hierarchy.new
448
+ # res = mlb.get_schedule;
449
+ # g = mlb.games.first
450
+ # g = Sportradar::Api::Baseball::Game.new('id' => "8cd71519-429f-4461-88a2-8a0e134eb89b")
451
+ g = Sportradar::Api::Baseball::Game.new('id' => "9d0fe41c-4e6b-4433-b376-2d09ed39d184");
452
+ g = Sportradar::Api::Baseball::Game.new('id' => "fe9f37fd-6848-4a32-a999-9655044b7319");
453
+ res = g.get_pbp;
454
+ res = g.get_summary;
455
+ res = g.get_box # probably not as useful as summary
456
+
457
+
458
+ mlb = Sportradar::Api::Baseball::Mlb::Hierarchy.new
459
+ res = mlb.get_daily_summary;
460
+ g = mlb.games[8];
461
+ g.count
462
+ g.get_pbp;
463
+ g.count
464
+
465
+ # mlb = Sportradar::Api::Baseball::Mlb::Hierarchy.new;
466
+ # res = mlb.get_daily_summary;
467
+ # g = mlb.games.sort_by(&:scheduled).first;
468
+ g = Sportradar::Api::Baseball::Game.new('id' => "8731b56d-9037-44d1-b890-fa496e94dc10");
469
+ res = g.get_pbp;
470
+ res = g.get_summary;
471
+ g.pitchers
472
+
@@ -0,0 +1,69 @@
1
+ module Sportradar
2
+ module Api
3
+ module Baseball
4
+ class HalfInning < Data
5
+ attr_accessor :response, :id, :inning, :type, :half, :number
6
+
7
+ def initialize(data, **opts)
8
+ @response = data
9
+ @api = opts[:api]
10
+ @inning = opts[:inning]
11
+ @id = data["id"]
12
+ *@inning_id, @number, @half = data['id'].split('-')
13
+
14
+ @events_hash = {}
15
+
16
+ update(data)
17
+
18
+ end
19
+
20
+ def update(data, **opts)
21
+ @half = data['half']
22
+ @events = data['events'].map{ |hash| Event.new(hash, half_inning: self) }
23
+ # create_data(@events_hash, data['events'], klass: Event, api: @api, half_inning: self)
24
+ end
25
+
26
+ def over?
27
+ pitches.last&.count&.dig('outs') == 3
28
+ end
29
+
30
+ def pitches
31
+ at_bats.flat_map(&:pitches)
32
+ end
33
+
34
+ def at_bats
35
+ events.map(&:at_bat).compact
36
+ end
37
+
38
+ def lineup_changes
39
+ # events_by_klass(LineupChange)
40
+ end
41
+
42
+ def events
43
+ # @events_hash.values
44
+ @events
45
+ end
46
+
47
+ def hits
48
+ at_bats.flat_map(&:pitches).select {|pitch| pitch.is_hit }
49
+ end
50
+
51
+ def hit_count
52
+ hits.count
53
+ end
54
+
55
+ def errors
56
+ at_bats.flat_map(&:pitches).flat_map(&:errors).compact
57
+ end
58
+
59
+ def error_count
60
+ errors.count
61
+ end
62
+ # private def events_by_klass(klass)
63
+ # @events_hash.each_value.grep(klass)
64
+ # end
65
+
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,42 @@
1
+ module Sportradar
2
+ module Api
3
+ module Baseball
4
+ class Inning < Data
5
+ attr_accessor :response, :id, :game, :number, :sequence, :scoring
6
+
7
+ def initialize(data, **opts)
8
+ @response = data
9
+ @api = opts[:api]
10
+ @game = opts[:game]
11
+
12
+ @id = data["id"]
13
+ @number = data['number']
14
+ @sequence = data['sequence']
15
+
16
+ parse_scoring(data['scoring']) if data['scoring']
17
+
18
+ @half_innings_hash = {}
19
+
20
+ update(data)
21
+ end
22
+ def update(data, **opts)
23
+ # update scoring
24
+ halfs = data['halfs'].each { |inning| inning['id'] = "#{data['id']}-#{inning['half']}" }
25
+ create_data(@half_innings_hash, halfs, klass: HalfInning, api: @api, inning: self)
26
+ end
27
+ def parse_scoring(data)
28
+ @scoring = data.each_with_object({}) { |(_, data), hash| hash[data['id']] = data['runs'].to_s } # from PBP
29
+ end
30
+
31
+ def half_innings
32
+ @half_innings_hash.values
33
+ end
34
+
35
+ def events
36
+ half_innings.flat_map(&:events)
37
+ end
38
+
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,116 @@
1
+ module Sportradar
2
+ module Api
3
+ module Baseball
4
+ class Lineup < Data
5
+ attr_reader :roster, :home, :away, :game
6
+ def initialize(data, game: nil)
7
+ @game = game
8
+ end
9
+
10
+ def update(data, source: nil)
11
+ case source
12
+ when :pbp
13
+ when :summary
14
+ @roster = (data.dig('home', 'roster') || []) + (data.dig('away', 'roster') || [])
15
+ return unless data.dig('home', 'lineup') && data.dig('away', 'lineup')
16
+ @home_team_lineup = data.dig('home', 'lineup')
17
+ @away_team_lineup = data.dig('away', 'lineup')
18
+ initialize_home
19
+ initialize_away
20
+ end
21
+ end
22
+
23
+ def update_from_lineup_event(data)
24
+ if data.dig('lineup', 'team_id') == game.home_id
25
+ update_home(find_player(data.dig('lineup', 'player_id')), data.dig('lineup', 'order'))
26
+ elsif data.dig('lineup', 'team_id') == game.away_id
27
+ update_away(find_player(data.dig('lineup', 'player_id')), data.dig('lineup', 'order'))
28
+ end
29
+ end
30
+
31
+ def update_home(player, order)
32
+ return if order == 0
33
+ idx = home.index do |h|
34
+ h['order'] == order
35
+ end
36
+ home[idx] = { 'order' => order }
37
+ home[idx].merge!(player)
38
+ end
39
+
40
+ def update_away(player, order)
41
+ return if order == 0
42
+ idx = away.index do |h|
43
+ h['order'] == order
44
+ end
45
+ away[idx] = { 'order' => order}
46
+ away[idx].merge!(player)
47
+ end
48
+
49
+ def find_player(id)
50
+ roster.detect do |player|
51
+ player['id'] == id
52
+ end
53
+ end
54
+
55
+ def initial_lineup
56
+ [
57
+ { 'order' => 1 },
58
+ { 'order' => 2 },
59
+ { 'order' => 3 },
60
+ { 'order' => 4 },
61
+ { 'order' => 5 },
62
+ { 'order' => 6 },
63
+ { 'order' => 7 },
64
+ { 'order' => 8 },
65
+ { 'order' => 9 },
66
+ ]
67
+ end
68
+
69
+ def next_batters(team, number_of_upcoming_batters = 3)
70
+ if team == 'home'
71
+ last_at_bat = game.at_bats.select{|at_bat| at_bat.event.half_inning.half == 'B'}.last
72
+ if last_at_bat # first inning
73
+ last_position = @home_team_lineup.detect{|htl| htl['id'] == last_at_bat.hitter_id}&.dig('order')
74
+ upcoming = home.rotate(last_position || 0)
75
+ else
76
+ upcoming = home
77
+ end
78
+ elsif team == 'away'
79
+ last_at_bat = game.at_bats.select{|at_bat| at_bat.event.half_inning.half == 'T'}.last
80
+ if last_at_bat # first inning
81
+ last_position = @away_team_lineup.detect{|atl| atl['id'] == last_at_bat.hitter_id}&.dig('order')
82
+ upcoming = away.rotate(last_position || 0)
83
+ else
84
+ upcoming = away
85
+ end
86
+ end
87
+ upcoming[0..(number_of_upcoming_batters - 1)]
88
+ rescue => e
89
+ i ||= 0
90
+ game.get_summary
91
+ i += 1
92
+ retry unless i > 2
93
+ end
94
+
95
+ private
96
+
97
+ def initialize_away
98
+ @away = initial_lineup.dup
99
+ @away_team_lineup.sort_by{|t| t['sequence']}.each do |al|
100
+ # al.merge!(find_player(al['id']))
101
+ update_away(find_player(al['id']), al['order'])
102
+ end.sort_by{|al| al['order']}
103
+ end
104
+
105
+ def initialize_home
106
+ @home = initial_lineup.dup
107
+ @home_team_lineup.sort_by{|t| t['sequence']}.each do |hl|
108
+ # hl.merge!(find_player(hl['id']))
109
+ update_home(find_player(hl['id']), hl['order'])
110
+ end.sort_by{|hl| hl['order']}
111
+ end
112
+
113
+ end
114
+ end
115
+ end
116
+ end