sportradar-api 0.9.59 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.env.sample +5 -1
  3. data/Gemfile.lock +4 -4
  4. data/README.md +1 -1
  5. data/lib/sportradar/api.rb +1 -0
  6. data/lib/sportradar/api/basketball.rb +15 -6
  7. data/lib/sportradar/api/basketball/game.rb +285 -0
  8. data/lib/sportradar/api/basketball/injury.rb +6 -6
  9. data/lib/sportradar/api/basketball/nba.rb +22 -25
  10. data/lib/sportradar/api/basketball/nba/conference.rb +1 -1
  11. data/lib/sportradar/api/basketball/nba/division.rb +2 -1
  12. data/lib/sportradar/api/basketball/nba/game.rb +12 -249
  13. data/lib/sportradar/api/basketball/nba/hierarchy.rb +5 -4
  14. data/lib/sportradar/api/basketball/nba/player.rb +8 -8
  15. data/lib/sportradar/api/basketball/nba/quarter.rb +1 -1
  16. data/lib/sportradar/api/basketball/nba/schedule.rb +17 -6
  17. data/lib/sportradar/api/basketball/nba/scoring.rb +15 -95
  18. data/lib/sportradar/api/basketball/nba/season.rb +6 -5
  19. data/lib/sportradar/api/basketball/nba/team.rb +5 -122
  20. data/lib/sportradar/api/basketball/ncaamb.rb +133 -0
  21. data/lib/sportradar/api/basketball/ncaamb/conference.rb +41 -0
  22. data/lib/sportradar/api/basketball/ncaamb/division.rb +42 -0
  23. data/lib/sportradar/api/basketball/ncaamb/game.rb +60 -0
  24. data/lib/sportradar/api/basketball/ncaamb/half.rb +49 -0
  25. data/lib/sportradar/api/basketball/ncaamb/hierarchy.rb +65 -0
  26. data/lib/sportradar/api/basketball/ncaamb/player.rb +162 -0
  27. data/lib/sportradar/api/basketball/ncaamb/schedule.rb +41 -0
  28. data/lib/sportradar/api/basketball/ncaamb/season.rb +42 -0
  29. data/lib/sportradar/api/basketball/ncaamb/team.rb +40 -0
  30. data/lib/sportradar/api/basketball/play.rb +15 -5
  31. data/lib/sportradar/api/basketball/plays.rb +1 -0
  32. data/lib/sportradar/api/basketball/plays/base.rb +30 -6
  33. data/lib/sportradar/api/basketball/plays/deadball.rb +11 -0
  34. data/lib/sportradar/api/basketball/plays/end_period.rb +2 -2
  35. data/lib/sportradar/api/basketball/plays/free_throw_miss.rb +3 -1
  36. data/lib/sportradar/api/basketball/plays/rebound.rb +5 -1
  37. data/lib/sportradar/api/basketball/plays/shot_made.rb +4 -3
  38. data/lib/sportradar/api/basketball/plays/shot_miss.rb +4 -3
  39. data/lib/sportradar/api/basketball/plays/timeout.rb +0 -3
  40. data/lib/sportradar/api/basketball/plays/turnover.rb +1 -1
  41. data/lib/sportradar/api/basketball/record.rb +32 -0
  42. data/lib/sportradar/api/basketball/scoring.rb +103 -0
  43. data/lib/sportradar/api/basketball/team.rb +148 -0
  44. data/lib/sportradar/api/data.rb +2 -0
  45. data/lib/sportradar/api/poll.rb +40 -0
  46. data/lib/sportradar/api/request.rb +15 -6
  47. data/lib/sportradar/api/version.rb +1 -1
  48. metadata +18 -7
  49. data/lib/sportradar/api/basketball/bio.rb +0 -33
  50. data/lib/sportradar/api/basketball/nba/overview.rb +0 -64
  51. data/lib/sportradar/api/basketball/nba/record.rb +0 -34
  52. data/lib/sportradar/api/basketball/overview.rb +0 -74
  53. data/lib/sportradar/api/basketball/profile.rb +0 -48
@@ -11,6 +11,7 @@ require_relative 'plays/shot_miss'
11
11
  require_relative 'plays/free_throw_made'
12
12
  require_relative 'plays/free_throw_miss'
13
13
  require_relative 'plays/jumpball'
14
+ require_relative 'plays/deadball'
14
15
  require_relative 'plays/kickball'
15
16
  require_relative 'plays/lineup_change'
16
17
  require_relative 'plays/offensive_foul'
@@ -3,17 +3,23 @@ module Sportradar
3
3
  module Basketball
4
4
  class Play
5
5
  class Base < Data
6
- attr_accessor :response, :id, :clock, :event_type, :description, :statistics, :score, :team_id, :player_id, :quarter, :updated, :location, :possession, :on_court
6
+ attr_accessor :response, :id, :clock, :event_type, :description, :statistics, :score, :team_id, :player_id, :quarter, :half, :updated, :location, :possession, :on_court, :game_seconds, :identifier
7
7
  alias :type :event_type
8
8
 
9
9
  def initialize(data, **opts)
10
10
  @response = data
11
11
  @api = opts[:api]
12
12
 
13
- @quarter = opts[:quarter].sequence.to_i rescue opts[:quarter].to_i
14
13
  @id = data['id']
15
14
 
16
- update(data)
15
+ @quarter = opts[:quarter].sequence.to_i rescue opts[:quarter].to_i
16
+ @half = opts[:half].sequence.to_i rescue opts[:half].to_i
17
+
18
+ update(data, **opts)
19
+ end
20
+
21
+ def period
22
+ @quarter.nonzero? || @half.nonzero?
17
23
  end
18
24
 
19
25
  # def game
@@ -66,7 +72,10 @@ module Sportradar
66
72
  def nba_game_seconds
67
73
  ([quarter, 4].min * 720) + ([quarter - 4, 0].max * 300) - clock_seconds # seconds elapsed in game, only works for NBA
68
74
  end
69
- alias :game_seconds :nba_game_seconds
75
+
76
+ private def ncaa_game_seconds
77
+ ([half, 2].min * 1200) + ([half - 2, 0].max * 300) - clock_seconds # seconds elapsed in game, only works for NBA
78
+ end
70
79
 
71
80
  def update(data, **opts)
72
81
  @event_type = data['event_type'] # "lineupchange",
@@ -78,14 +87,29 @@ module Sportradar
78
87
  @location = data['location'] # {"coord_x"=>"0", "coord_y"=>"0"},
79
88
  @possession = data['possession'] # {"name"=>"Knicks", "market"=>"New York", "id"=>"583ec70e-fb46-11e1-82cb-f4ce4684ea4c"},
80
89
  # @on_court = data['on_court'] # hash
90
+
91
+ handle_time(data, **opts)
81
92
  parse_statistics(data) if data['statistics']
82
93
  end
83
94
 
95
+ def handle_time(data, **opts)
96
+ @game_seconds = if opts[:quarter]
97
+ @quarter = opts[:quarter].sequence.to_i rescue opts[:quarter].to_i
98
+ @identifier = "#{quarter}_#{(720 - clock_seconds).to_s.rjust(3, '0')}".to_i
99
+ nba_game_seconds
100
+ elsif opts[:half]
101
+ @half = opts[:half].sequence.to_i rescue opts[:half].to_i
102
+ @identifier = "#{half}_#{(1200 - clock_seconds).to_s.rjust(4, '0')}".to_i
103
+ ncaa_game_seconds
104
+ end
105
+ end
106
+
84
107
  def parse_statistics(data)
85
108
  return unless data['statistics']
86
109
  @statistics = data['statistics']
87
- stat = data.dig('statistics', base_key)
88
- stat = stat[0] if stat.is_a?(Array) # sometimes SR has an array of identical assist hashes
110
+ stat = @statistics.detect { |hash| hash['type'] == base_key }
111
+ # stat = data.dig('statistics', base_key) rescue data.dig('statistics', 0, base_key)
112
+ # stat = stat[0] if stat.is_a?(Array) # sometimes SR has an array of identical assist hashes
89
113
  @team = stat['team']
90
114
  @team_id = @team['id'] if @team
91
115
  @player = stat['player']
@@ -0,0 +1,11 @@
1
+ module Sportradar
2
+ module Api
3
+ module Basketball
4
+ class Deadball < Play::Base
5
+ def display_type
6
+ 'Deadball'
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -3,7 +3,7 @@ module Sportradar
3
3
  module Basketball
4
4
  class EndPeriod < Play::Base
5
5
  def display_type
6
- end_of_ot? ? 'End OT' : 'End Quarter'
6
+ ''
7
7
  end
8
8
 
9
9
  def end_of_period?
@@ -14,7 +14,7 @@ module Sportradar
14
14
  ["End of 1st Quarter.", "End of 3rd Quarter."].include? description
15
15
  end
16
16
  def end_of_regulation?
17
- description == "End of 4th Quarter."
17
+ description == "End of 4th Quarter." || description == "End of 2nd Half."
18
18
  end
19
19
  def end_of_ot?
20
20
  description.start_with?("End of ") && description.end_with?(" OT.")
@@ -15,7 +15,9 @@ module Sportradar
15
15
  false
16
16
  end
17
17
  def player_id
18
- @player_id ||= @statistics&.dig(base_key, "player", "id") # safe operator is specifically for lane violations, which have no player_id and should not be assigned a player
18
+ stats = @statistics
19
+ stats = stats[0] if stats.is_a?(Array)
20
+ @player_id ||= stats&.dig("player", "id") # safe operator is specifically for lane violations, which have no player_id and should not be assigned a player
19
21
  end
20
22
  end
21
23
  end
@@ -16,7 +16,11 @@ module Sportradar
16
16
  end
17
17
  def parse_statistics(data)
18
18
  super
19
- @rebound_type = @statistics.dig(base_key, 'rebound_type')
19
+ stat = @statistics.dig(base_key)
20
+ stat = stat[0] if stat.is_a?(Array)
21
+ @rebound_type = stat['rebound_type']
22
+ rescue => e
23
+ # noop => bad data from SR
20
24
  end
21
25
  end
22
26
  end
@@ -8,9 +8,10 @@ module Sportradar
8
8
  end
9
9
  def parse_statistics(data)
10
10
  super
11
- @shot_type = @statistics.dig(base_key, 'shot_type')
12
- @shot_type_desc = @statistics.dig(base_key, 'shot_type_desc')
13
- @assist = Assist.new(data, quarter: @quarter) if @statistics['assist']
11
+ stat = @statistics.detect { |hash| hash['type'] == base_key }
12
+ @shot_type = stat['shot_type']
13
+ @shot_type_desc = stat['shot_type_desc']
14
+ @assist = Assist.new(data, quarter: @quarter, half: @half) if @statistics.detect { |hash| hash['type'] == 'assist' }
14
15
  end
15
16
  def made?
16
17
  true
@@ -8,9 +8,10 @@ module Sportradar
8
8
  end
9
9
  def parse_statistics(data)
10
10
  super
11
- @shot_type = @statistics.dig(base_key, 'shot_type')
12
- @shot_type_desc = @statistics.dig(base_key, 'shot_type_desc')
13
- @block = Block.new(data, quarter: @quarter) if @statistics['block']
11
+ stat = @statistics.detect { |hash| hash['type'] == base_key }
12
+ @shot_type = stat['shot_type']
13
+ @shot_type_desc = stat['shot_type_desc']
14
+ @block = Block.new(data, quarter: @quarter, half: @half) if @statistics.detect { |hash| hash['type'] == 'block' }
14
15
  end
15
16
  def made?
16
17
  false
@@ -6,9 +6,6 @@ module Sportradar
6
6
  def display_type
7
7
  'Timeout'
8
8
  end
9
- def identifier
10
- "#{quarter}_#{(720 - clock_seconds).to_s.rjust(3, '0')}".to_i
11
- end
12
9
  def timeout?
13
10
  true
14
11
  end
@@ -11,7 +11,7 @@ module Sportradar
11
11
  end
12
12
  def parse_statistics(data)
13
13
  super
14
- @steal = Steal.new(data, quarter: @quarter) if @statistics['steal']
14
+ @steal = Steal.new(data, quarter: @quarter, half: @half) if @statistics.detect { |hash| hash['type'] == 'steal' }
15
15
  end
16
16
  end
17
17
  end
@@ -0,0 +1,32 @@
1
+ module Sportradar
2
+ module Api
3
+ module Basketball
4
+ class Record < Data
5
+ attr_accessor :id, :team, :wins, :losses, :win_pct, :points_for, :points_against, :point_diff, :games_behind, :streak
6
+
7
+ def initialize(data, type: 'overall', **opts)
8
+ @team = opts[:team]
9
+ @type = type
10
+
11
+ update(data, **opts)
12
+ end
13
+
14
+ def profile
15
+ Profile.new(self)
16
+ end
17
+
18
+ def update(data, **opts)
19
+ @wins = data['wins'].to_i
20
+ @losses = data['losses'].to_i
21
+ @win_pct = data['win_pct'].to_f
22
+ @points_for = data['points_for']
23
+ @points_against = data['points_against']
24
+ @point_diff = data['point_diff']
25
+ @games_behind = data['games_behind']
26
+ @streak = data['streak']
27
+ end
28
+
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,103 @@
1
+ module Sportradar
2
+ module Api
3
+ module Basketball
4
+ class Scoring < Data
5
+ attr_accessor :response, :api, :id, :home, :away, :scores
6
+
7
+ def initialize(data, **opts)
8
+ @api = opts[:api]
9
+ @game = opts[:game]
10
+
11
+ @scores = {}
12
+ @id = data['id']
13
+
14
+ update(data, **opts)
15
+ end
16
+
17
+ def update(data, source: nil, **opts)
18
+ new_scores = case source
19
+ when :box
20
+ parse_from_box(data)
21
+ when :pbp
22
+ parse_from_pbp(data)
23
+ when :summary
24
+ parse_from_box(data)
25
+ else
26
+ if data['quarter'] || data['half']
27
+ parse_from_pbp(data)
28
+ elsif data['team']
29
+ parse_from_box(data)
30
+ else # schedule requests
31
+ {}
32
+ end
33
+ end
34
+ # parse data structure
35
+ # handle data from team (all quarters)
36
+ # handle data from quarter (both teams)
37
+ # handle data from game?
38
+ @scores.each { |k, v| v.merge!(new_scores.delete(k) || {} ) }
39
+ new_scores.each { |k, v| @scores.merge!(k => v) }
40
+ end
41
+
42
+ def points(team_id)
43
+ @score[team_id].to_i
44
+ end
45
+
46
+
47
+ private
48
+
49
+ def parse_from_box(data)
50
+ return {} if data.dig('team', 0, 'scoring').is_a?(String) || data.dig('team', 0, 'scoring').nil? # game hasn't started yet
51
+ period_name = data.dig('team', 0, 'scoring').keys.first
52
+ id = data.dig('team', 0, 'id')
53
+ da = data.dig('team', 0, 'scoring', period_name)
54
+ da_ot = data.dig('team', 0, 'scoring', 'overtime')
55
+ arr = [da].compact.flatten(1)
56
+ a = arr + [da_ot].compact.flatten(1)
57
+ a.each { |h| h[id] = h.delete('points').to_i }
58
+ id = data.dig('team', 1, 'id')
59
+ da = data.dig('team', 1, 'scoring', period_name)
60
+ da_ot = data.dig('team', 1, 'scoring', 'overtime')
61
+ arr = [da].compact.flatten(1)
62
+ b = arr + [da_ot].compact.flatten(1)
63
+ b.each { |h| h[id] = h.delete('points').to_i }
64
+ a.zip(b).map{ |a, b| [a['sequence'].to_i, a.merge(b)] }.sort{ |(a,_), (b,_)| a <=> b }.to_h
65
+ end
66
+
67
+ def parse_from_box(data)
68
+ id = data.dig('home', 'id')
69
+ # binding.pry if id
70
+ da = data.dig('home', 'scoring')
71
+ return {} unless da
72
+ da.each { |h| h[id] = h.delete('points').to_i }
73
+ id = data.dig('away', 'id')
74
+ db = data.dig('away', 'scoring')
75
+ return {} unless db
76
+ db.each { |h| h[id] = h.delete('points').to_i }
77
+ da.zip(db).map{ |a, b| [a['sequence'].to_i, a.merge(b)] }.sort{ |(a,_), (b,_)| a <=> b }.to_h
78
+ end
79
+
80
+ def parse_from_pbp(data)
81
+ period_name = data.key?('quarter') ? 'quarter' : 'half'
82
+ period_name = 'periods'
83
+ return {} unless data[period_name] # game hasn't started
84
+ quarters = data[period_name]#[1..-1]
85
+ overtimes = data['overtime']
86
+ overtimes = [overtimes] if !overtimes.is_a?(Array)
87
+ quarters = quarters[0] if (quarters.is_a?(Array) && (quarters.size == 1) && quarters.first.is_a?(Array))
88
+ data = (quarters + overtimes).compact.map{|q| q['scoring'] }
89
+ data.map.with_index(1) { |h, i| [i, { h.dig('home', 'id') => h.dig('home', 'points').to_i, h.dig('away', 'id') => h.dig('away', 'points').to_i }] }.to_h
90
+ end
91
+
92
+ # def period_name
93
+ # 'quarter'
94
+ # end
95
+
96
+ def parse_from_summary(data)
97
+ #
98
+ end
99
+
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,148 @@
1
+ module Sportradar
2
+ module Api
3
+ module Basketball
4
+ class Team < Data
5
+ attr_accessor :response, :id, :market, :name, :alias, :full_name, :venue, :records, :player_stats, :team_stats
6
+ @all_hash = {}
7
+ def self.new(data, **opts)
8
+ existing = @all_hash[data['id']]
9
+ if existing
10
+ existing.update(data, **opts)
11
+ existing
12
+ else
13
+ @all_hash[data['id']] = super
14
+ end
15
+ end
16
+ def self.all
17
+ @all_hash.values
18
+ end
19
+
20
+ def initialize(data, **opts)
21
+ @response = data
22
+ @api = opts[:api]
23
+
24
+ @games_hash = {}
25
+ @players_hash = {}
26
+ @player_stats = {}
27
+ @records = {}
28
+ @stats = nil
29
+ @points = nil
30
+
31
+ case data
32
+ when String
33
+ @id = data
34
+ when Hash
35
+ @id = data['id']
36
+ update(data, **opts)
37
+ end
38
+ end
39
+
40
+ def profile
41
+ Profile.new(self)
42
+ end
43
+
44
+ def update(data, **opts)
45
+ handle_names(data)
46
+ @venue = Venue.new(data['venue']) if data['venue']
47
+
48
+ @alias = data['alias'] if data['alias']
49
+ @points = data['points'].to_i if data['points']
50
+ # @home = data['home'] == 'true' if data['home']
51
+ # @away = data['away'] == 'true' if data['away']
52
+ # @scoring = data.dig('scoring', 'quarter') if data.dig('scoring', 'quarter')
53
+
54
+ parse_records(data) if data['records']
55
+ # binding.pry if data['players']
56
+ parse_players(data.dig('players'), opts[:game]) if data.dig('players')
57
+ # parse_stats(data['statistics']) if data['statistics']
58
+ if opts[:game]
59
+ add_game(opts[:game])
60
+ opts[:game].update_score(id => @points) if @points
61
+ opts[:game].update_stats(self, data['statistics']) if data['statistics']
62
+ end
63
+ end
64
+ def handle_names(data)
65
+ # need to do some more work here
66
+ @name = data['name'] if data['name']
67
+ if data['name'] && !data.key?('market')
68
+ @full_name = data['name']
69
+ elsif data['name'] && data['market']
70
+ @market = data['market']
71
+ @full_name = [@market, data['name']].join(' ')
72
+ end
73
+ end
74
+
75
+ def record(type = 'overall')
76
+ @records[type]
77
+ end
78
+
79
+ def games
80
+ @games_hash.values
81
+ end
82
+ def add_game(game)
83
+ @games_hash[game.id] = game.id if game
84
+ end
85
+
86
+ # def parse_stats(data)
87
+ # @stats = data
88
+ # end
89
+
90
+ def players
91
+ get_roster if @players_hash.empty?
92
+ @players_hash.values
93
+ end
94
+ alias :roster :players
95
+ def parse_players(data, game)
96
+ create_data(@players_hash, data, klass: player_class, api: api, team: self, game: game)
97
+ end
98
+ def update_player_stats(player, stats, game = nil)
99
+ game ? game.update_player_stats(player, stats) : @player_stats.merge!(player.id => stats.merge!(player: player))
100
+ end
101
+
102
+ def get_roster
103
+ data = api.get_data(path_roster)
104
+ ingest_roster(data)
105
+ end
106
+ def ingest_roster(data)
107
+ update(data)
108
+ data
109
+ end
110
+
111
+ def get_season_stats
112
+ data = api.get_data(path_season_stats)
113
+ ingest_season_stats(data)
114
+ end
115
+ def ingest_season_stats(data)
116
+ parse_season_stats(data)
117
+ end
118
+
119
+ def parse_records(data)
120
+ @records['overall'] = Record.new(data, type: 'overall')
121
+ data['records'].each { |record| @records[record['record_type']] = Record.new(record, type: record['record_type']) }
122
+ end
123
+
124
+ def parse_season_stats(data)
125
+ @team_stats = data.dig('own_record')
126
+ update(data)
127
+ player_data = data.dig('players')
128
+ create_data(@players_hash, player_data, klass: player_class, api: api, team: self)
129
+ data
130
+ end
131
+
132
+ def path_base
133
+ "teams/#{ id }"
134
+ end
135
+ def path_base_stats(season_year = api.default_year, default_season = api.default_season)
136
+ "seasontd/#{season_year}/#{default_season}/teams/#{id}"
137
+ end
138
+ def path_roster
139
+ "#{ path_base }/profile"
140
+ end
141
+ def path_season_stats
142
+ "#{ path_base_stats }/statistics"
143
+ end
144
+
145
+ end
146
+ end
147
+ end
148
+ end