sportradar-api 0.13.0 → 0.13.21

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +4 -3
  3. data/lib/sportradar/api/baseball/game.rb +1 -1
  4. data/lib/sportradar/api/baseball/mlb/api.rb +1 -1
  5. data/lib/sportradar/api/basketball/game.rb +5 -4
  6. data/lib/sportradar/api/basketball/nba/game.rb +1 -1
  7. data/lib/sportradar/api/basketball/nba/quarter.rb +9 -2
  8. data/lib/sportradar/api/basketball/ncaamb/game.rb +5 -1
  9. data/lib/sportradar/api/basketball/ncaamb/half.rb +9 -0
  10. data/lib/sportradar/api/basketball/ncaamb.rb +2 -2
  11. data/lib/sportradar/api/basketball/play.rb +3 -1
  12. data/lib/sportradar/api/basketball/plays/base.rb +8 -1
  13. data/lib/sportradar/api/basketball/plays/jumpball_violation.rb +11 -0
  14. data/lib/sportradar/api/basketball/plays.rb +1 -0
  15. data/lib/sportradar/api/basketball/team.rb +1 -1
  16. data/lib/sportradar/api/football/ncaafb/game.rb +1 -1
  17. data/lib/sportradar/api/football/nfl/game.rb +1 -1
  18. data/lib/sportradar/api/images.rb +2 -4
  19. data/lib/sportradar/api/soccer/README.md +4 -0
  20. data/lib/sportradar/api/soccer/api.rb +1 -1
  21. data/lib/sportradar/api/soccer/event.rb +15 -14
  22. data/lib/sportradar/api/soccer/match.rb +161 -6
  23. data/lib/sportradar/api/soccer/player.rb +16 -4
  24. data/lib/sportradar/api/soccer/scoring.rb +71 -0
  25. data/lib/sportradar/api/soccer/team.rb +25 -12
  26. data/lib/sportradar/api/soccer/tournament.rb +26 -3
  27. data/lib/sportradar/api/soccer.rb +1 -0
  28. data/lib/sportradar/api/version.rb +1 -1
  29. metadata +5 -29
  30. data/lib/sportradar/api/soccer-old/boxscore.rb +0 -13
  31. data/lib/sportradar/api/soccer-old/category.rb +0 -17
  32. data/lib/sportradar/api/soccer-old/fact.rb +0 -61
  33. data/lib/sportradar/api/soccer-old/hierarchy.rb +0 -14
  34. data/lib/sportradar/api/soccer-old/match.rb +0 -55
  35. data/lib/sportradar/api/soccer-old/player.rb +0 -88
  36. data/lib/sportradar/api/soccer-old/ranking.rb +0 -13
  37. data/lib/sportradar/api/soccer-old/schedule.rb +0 -22
  38. data/lib/sportradar/api/soccer-old/season.rb +0 -14
  39. data/lib/sportradar/api/soccer-old/standing.rb +0 -13
  40. data/lib/sportradar/api/soccer-old/statistic.rb +0 -14
  41. data/lib/sportradar/api/soccer-old/summary.rb +0 -13
  42. data/lib/sportradar/api/soccer-old/team.rb +0 -63
  43. data/lib/sportradar/api/soccer-old/tournament.rb +0 -20
  44. data/lib/sportradar/api/soccer-old/tournament_group.rb +0 -35
  45. data/lib/sportradar/api/soccer-old/venue.rb +0 -21
  46. data/lib/sportradar/api/soccer-old.rb +0 -158
  47. data/soccer-old/boxscore_test.rb +0 -14
  48. data/soccer-old/hierarchy_test.rb +0 -14
  49. data/soccer-old/match_test.rb +0 -23
  50. data/soccer-old/player_test.rb +0 -40
  51. data/soccer-old/ranking_test.rb +0 -16
  52. data/soccer-old/schedule_test.rb +0 -24
  53. data/soccer-old/standing_test.rb +0 -14
  54. data/soccer-old/summary_test.rb +0 -14
  55. data/soccer-old/team_test.rb +0 -21
  56. data/soccer-old_test.rb +0 -184
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 587f83481b5cb0170503a6da85810f7f9635d094
4
- data.tar.gz: 00e7a832708e4a37ae8fe8738e5d4522e384c4f9
3
+ metadata.gz: b27cb8ea668a8b5d6fc88a694aceaaeff0ac0284
4
+ data.tar.gz: '08009571140f719d4f7c3a0337ad0a9f737a0e5c'
5
5
  SHA512:
6
- metadata.gz: c7fb85e9a377c6fd7f0e907306444ea2a7bda6275dbea4072fa50f56a38823dab471a3fc997cb08a51e018be89b467a3d5653432869a2226d459b098d43c739d
7
- data.tar.gz: 265cd0efa942c1efde1ac51d714804b58f2a2a14adf98335113fa0a2fb00d9a173169fdc9722a9cee0a7284e0d4dd2d72c1a4c0286363baaf18c1cc4f7dcab73
6
+ metadata.gz: 8c2c2c4feeccdabbd7784a49ded61c947dec73e36734f3df85dc12b64eacaa999f84d77a01908b14b370ebffd05842ffdf09d22a1cbeb6d72df134dc8048b0dc
7
+ data.tar.gz: 57c4b366e2a91bc03e1b9125c0ab427f3bcfa86d2f9bf29a9024840c62ff9c2ee2c3b4002cd7ca88ebe81fc8db2154cb5dfac93ec07b8cb887fc486bf49a731c
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- sportradar-api (0.13.0)
4
+ sportradar-api (0.13.21)
5
5
  activesupport
6
6
  httparty (>= 0.14.0)
7
7
 
@@ -47,7 +47,8 @@ GEM
47
47
  hashdiff (0.3.4)
48
48
  httparty (0.15.6)
49
49
  multi_xml (>= 0.5.2)
50
- i18n (0.8.6)
50
+ i18n (0.9.5)
51
+ concurrent-ruby (~> 1.0)
51
52
  json (2.1.0)
52
53
  listen (3.1.5)
53
54
  rb-fsevent (~> 0.9, >= 0.9.4)
@@ -86,7 +87,7 @@ GEM
86
87
  thor (0.19.4)
87
88
  thread_safe (0.3.6)
88
89
  tins (1.14.0)
89
- tzinfo (1.2.3)
90
+ tzinfo (1.2.5)
90
91
  thread_safe (~> 0.1)
91
92
  vcr (3.0.3)
92
93
  webmock (3.0.1)
@@ -469,7 +469,7 @@ module Sportradar
469
469
  end
470
470
 
471
471
  def api
472
- @api || Sportradar::Api::Baseball::Mlb::Api.new
472
+ @api ||= Sportradar::Api::Baseball::Mlb::Api.new
473
473
  end
474
474
 
475
475
  def sim!
@@ -26,7 +26,7 @@ module Sportradar
26
26
  'reg'
27
27
  end
28
28
  def default_access_level
29
- if (ENV['SPORTRADAR_ENV'] || ENV['SPORTRADAR_ENV_MLB'] || ENV['RACK_ENV'] || ENV['RAILS_ENV']) == 'production'
29
+ if (ENV['SPORTRADAR_ENV_MLB'] || ENV['SPORTRADAR_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV']) == 'production'
30
30
  'p'
31
31
  else
32
32
  't'
@@ -212,7 +212,7 @@ module Sportradar
212
212
  end
213
213
 
214
214
  def halftime?
215
- clock == '0:00' && quarter == 2
215
+ status == 'halftime' || clock == '00:00' && quarter == 2
216
216
  end
217
217
  def clock_display
218
218
  if clock && period
@@ -266,7 +266,7 @@ module Sportradar
266
266
  # data retrieval
267
267
 
268
268
  def get_box
269
- data = api.get_data(path_box)
269
+ data = api.get_data(path_box).to_h
270
270
  ingest_box(data)
271
271
  end
272
272
 
@@ -283,7 +283,7 @@ module Sportradar
283
283
  end
284
284
 
285
285
  def get_pbp
286
- data = api.get_data(path_pbp)
286
+ data = api.get_data(path_pbp).to_h
287
287
  ingest_pbp(data)
288
288
  end
289
289
 
@@ -305,12 +305,13 @@ module Sportradar
305
305
  set_pbp(period_data)
306
306
  @pbp = @periods_hash.values
307
307
  check_newness(:pbp, plays.last&.updated)
308
+ check_newness(:clock, @clock)
308
309
  check_newness(:score, @score)
309
310
  data
310
311
  end
311
312
 
312
313
  def get_summary
313
- data = api.get_data(path_summary)
314
+ data = api.get_data(path_summary).to_h
314
315
  ingest_summary(data)
315
316
  end
316
317
 
@@ -20,7 +20,7 @@ module Sportradar
20
20
  alias :quarters :periods
21
21
 
22
22
  def api
23
- @api || Sportradar::Api::Basketball::Nba::Api.new
23
+ @api ||= Sportradar::Api::Basketball::Nba::Api.new
24
24
  end
25
25
 
26
26
  def sim!
@@ -33,8 +33,15 @@ module Sportradar
33
33
  end
34
34
  def update(data, **opts)
35
35
  create_data(@plays_hash, data.dig('events'), klass: Play, api: @api, quarter: self)
36
- # rescue => e
37
- # binding.pry
36
+ end
37
+
38
+ def plays_by_type(play_type, *types)
39
+ if types.empty?
40
+ plays.grep(Play.subclass(play_type.delete('_')))
41
+ else
42
+ play_classes = [play_type, *types].map { |type| Play.subclass(type.delete('_')) }
43
+ plays.select { |play| play_classes.any? { |klass| play.kind_of?(klass) } }
44
+ end
38
45
  end
39
46
 
40
47
  def plays
@@ -16,6 +16,10 @@ module Sportradar
16
16
  end
17
17
  end
18
18
 
19
+ def halftime?
20
+ status == 'halftime' || clock == '00:00' && period == 1
21
+ end
22
+
19
23
  def team_class
20
24
  Team
21
25
  end
@@ -30,7 +34,7 @@ module Sportradar
30
34
  alias :halfs :periods
31
35
 
32
36
  def api
33
- @api || Sportradar::Api::Basketball::Ncaamb::Api.new
37
+ @api ||= Sportradar::Api::Basketball::Ncaamb::Api.new
34
38
  end
35
39
 
36
40
  def sim!
@@ -35,6 +35,15 @@ module Sportradar
35
35
  create_data(@plays_hash, data.dig('events'), klass: Play, api: @api, half: self)
36
36
  end
37
37
 
38
+ def plays_by_type(play_type, *types)
39
+ if types.empty?
40
+ plays.grep(Play.subclass(play_type.delete('_')))
41
+ else
42
+ play_classes = [play_type, *types].map { |type| Play.subclass(type.delete('_')) }
43
+ plays.select { |play| play_classes.any? { |klass| play.kind_of?(klass) } }
44
+ end
45
+ end
46
+
38
47
  def plays
39
48
  @plays_hash.values
40
49
  end
@@ -75,8 +75,8 @@ module Sportradar
75
75
  end
76
76
 
77
77
  def tournaments
78
- get_tournaments if tournaments.empty?
79
- self
78
+ get_tournaments if @tournaments_hash.empty?
79
+ @tournaments_hash.values
80
80
  end
81
81
 
82
82
  def games
@@ -16,7 +16,7 @@ module Sportradar
16
16
  # end
17
17
  # end
18
18
  def self.new(data, **opts)
19
- klass = subclass(data['event_type'])
19
+ klass = subclass(data['event_type']) || Base
20
20
  klass.new(data, **opts)
21
21
  # rescue => e
22
22
  # binding.pry
@@ -53,6 +53,7 @@ module Sportradar
53
53
  "tvtimeout" => TvTimeout,
54
54
  "clearpathfoul" => ClearPathFoul,
55
55
  "technicalfoul" => TechnicalFoul,
56
+ "minortechnicalfoul" => TechnicalFoul,
56
57
  "review" => Review,
57
58
  "defensivethreeseconds" => DefensiveThreeSeconds,
58
59
  "flagrantone" => FlagrantOne,
@@ -60,6 +61,7 @@ module Sportradar
60
61
  "delay" => Delay,
61
62
  "ejection" => Ejection,
62
63
  "defaultviolation" => DefaultViolation,
64
+ "jumpballviolation" => JumpballViolation,
63
65
  "defensivegoaltending" => DefensiveGoalTending,
64
66
  "doublelane" => DoubleLane,
65
67
  "lane" => Lane,
@@ -3,7 +3,7 @@ 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, :half, :updated, :location, :possession, :on_court, :game_seconds, :identifier
6
+ attr_accessor :response, :id, :clock, :event_type, :description, :statistics, :score, :team_id, :player_id, :quarter, :half, :updated, :location, :possession, :on_court, :on_court_ids, :game_seconds, :identifier
7
7
  alias :type :event_type
8
8
 
9
9
  def initialize(data, **opts)
@@ -87,11 +87,18 @@ module Sportradar
87
87
  @location = data['location'] # {"coord_x"=>"0", "coord_y"=>"0"},
88
88
  @possession = data['possession'] # {"name"=>"Knicks", "market"=>"New York", "id"=>"583ec70e-fb46-11e1-82cb-f4ce4684ea4c"},
89
89
  # @on_court = data['on_court'] # hash
90
+ handle_on_court(data['on_court']) if data['on_court']
90
91
 
91
92
  handle_time(data, **opts)
92
93
  parse_statistics(data) if data['statistics']
93
94
  end
94
95
 
96
+ def handle_on_court(data)
97
+ teams = [data['home'], data['away']]
98
+ @on_court = teams.flat_map { |hash| hash['players'].map { |pl| OpenStruct.new(pl) } }
99
+ @on_court_ids = @on_court.map(&:id)
100
+ end
101
+
95
102
  def handle_time(data, **opts)
96
103
  @game_seconds = if opts[:quarter]
97
104
  @quarter = opts[:quarter].sequence.to_i rescue opts[:quarter].to_i
@@ -0,0 +1,11 @@
1
+ module Sportradar
2
+ module Api
3
+ module Basketball
4
+ class JumpballViolation < Play::Base
5
+ def display_type
6
+ 'Jump Ball Violation'
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -38,6 +38,7 @@ require_relative 'plays/flagrant_two'
38
38
  require_relative 'plays/delay'
39
39
  require_relative 'plays/ejection'
40
40
  require_relative 'plays/default_violation'
41
+ require_relative 'plays/jumpball_violation'
41
42
  require_relative 'plays/defensive_goal_tending'
42
43
  require_relative 'plays/double_lane'
43
44
  require_relative 'plays/lane'
@@ -115,7 +115,7 @@ module Sportradar
115
115
  {url: url, headers: headers, params: options, timeout: timeout, callback: method(:ingest_roster)}
116
116
  end
117
117
 
118
- def get_season_stats(year = Date.today.year)
118
+ def get_season_stats(year = api.default_year)
119
119
  data = api.get_data(path_season_stats(year)).to_h
120
120
  ingest_season_stats(data)
121
121
  end
@@ -107,7 +107,7 @@ module Sportradar
107
107
  end
108
108
 
109
109
  def api
110
- @api || Sportradar::Api::Football::Ncaafb::Api.new
110
+ @api ||= Sportradar::Api::Football::Ncaafb::Api.new
111
111
  end
112
112
 
113
113
  end
@@ -42,7 +42,7 @@ module Sportradar
42
42
  end
43
43
 
44
44
  def api
45
- @api || Sportradar::Api::Football::Nfl::Api.new
45
+ @api ||= Sportradar::Api::Football::Nfl::Api.new
46
46
  end
47
47
 
48
48
  end
@@ -39,7 +39,6 @@ module Sportradar
39
39
  # Coach Manifests
40
40
 
41
41
  def coach_manifests
42
- raise Sportradar::Api::Error::InvalidLeague unless league.nil?
43
42
  response = get request_url("coaches/#{image_type}/manifests/all_assets")
44
43
  if response.success? && response["assetlist"]
45
44
  Sportradar::Api::Images::AssetList.new response["assetlist"]
@@ -50,7 +49,6 @@ module Sportradar
50
49
  alias_method :all_coaches, :coach_manifests
51
50
 
52
51
  def venue_manifests
53
- raise Sportradar::Api::Error::InvalidLeague unless league.nil?
54
52
  if version == 3
55
53
  response = get request_url("venues/manifest")
56
54
  else
@@ -97,7 +95,7 @@ module Sportradar
97
95
  private
98
96
 
99
97
  def request_url(path)
100
- "/#{sport}-images-#{access_level}#{version}/#{provider}/#{path}"
98
+ "/#{sport}-images-#{access_level}#{version}/#{provider}/#{( league + '/') if league}#{path}"
101
99
  end
102
100
 
103
101
  def api_key
@@ -169,7 +167,7 @@ module Sportradar
169
167
  end
170
168
 
171
169
  def allowed_sports
172
- v2_api_sports + v3_api_sports
170
+ v2_api_sports + v3_api_sports + soccer_leagues
173
171
  end
174
172
 
175
173
  def v2_api_sports
@@ -0,0 +1,4 @@
1
+ # Sportradar Soccer
2
+
3
+ ![League Reference](https://www.evernote.com/l/ASYGl8UICK1A6q_yrarGw1iFpRLzyQP6HJ4B/image.png)
4
+
@@ -19,7 +19,7 @@ module Sportradar
19
19
  'reg'
20
20
  end
21
21
  def default_access_level
22
- if (ENV['SPORTRADAR_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV']) == 'production'
22
+ if (ENV['SPORTRADAR_SOCCER_ENV'] || ENV['SPORTRADAR_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV']) == 'production'
23
23
  'x'
24
24
  else
25
25
  'xt'
@@ -2,7 +2,7 @@ module Sportradar
2
2
  module Api
3
3
  module Soccer
4
4
  class Event < Data
5
- attr_reader :id, :type, :time, :period, :period_type, :period_name, :match_time, :team, :x, :y, :outcome, :home_score, :away_score, :goal_scorer, :stoppage_time, :player_out, :player_in, :player
5
+ attr_reader :id, :type, :time, :period, :period_type, :period_name, :match_time, :team, :x, :y, :outcome, :home_score, :away_score, :goal_scorer, :stoppage_time, :player_out, :player_in, :player, :commentaries
6
6
 
7
7
  def initialize(data, **opts)
8
8
  @response = data
@@ -12,18 +12,19 @@ module Sportradar
12
12
  end
13
13
 
14
14
  def update(data, **opts)
15
- @type = data['type']
16
- @time = Time.parse(data['time']) if data['time']
17
- @period = data['period']
18
- @period_type = data['period_type']
19
- @period_name = data['period_name']
20
- @match_time = data['match_time']
21
- @team = data['team'] # home/away
22
- @x = data['x']
23
- @y = data['y']
24
- @outcome = data['outcome']
25
- @home_score = data['home_score']
26
- @away_score = data['away_score']
15
+ @type = data['type'] if data['type']
16
+ @time = Time.parse(data['time']) if data['time']
17
+ @period = data['period'] if data['period']
18
+ @period_type = data['period_type'] if data['period_type']
19
+ @period_name = data['period_name'] if data['period_name']
20
+ @match_time = data['match_time'] if data['match_time']
21
+ @team = data['team'] if data['team']
22
+ @x = data['x'] if data['x']
23
+ @y = data['y'] if data['y']
24
+ @outcome = data['outcome'] if data['outcome']
25
+ @home_score = data['home_score'] if data['home_score']
26
+ @away_score = data['away_score'] if data['away_score']
27
+ @commentaries = data['commentaries'] if data['commentaries']
27
28
  @goal_scorer = OpenStruct.new(data['goal_scorer']) if data['goal_scorer']
28
29
  @stoppage_time = data['stoppage_time'].to_i if data['stoppage_time']
29
30
  @player_out = OpenStruct.new(data['player_out']) if data['player_out']
@@ -31,7 +32,7 @@ module Sportradar
31
32
  @player = OpenStruct.new(data['player']) if data['player'] # red/yellow cards
32
33
  end
33
34
 
34
- def updated_at
35
+ def updated
35
36
  @time
36
37
  end
37
38
 
@@ -6,20 +6,29 @@ module Sportradar
6
6
  attr_reader :home_score, :away_score, :winner_id, :aggregate_home_score, :aggregate_away_score, :aggregate_winner_id
7
7
  attr_reader :referee, :weather_info, :coverage_info, :probabilities
8
8
  attr_reader :home, :away, :tournament_id
9
+ attr_reader :period, :score, :broadcast, :coverage # these are for consistency with other sports
10
+ attr_reader :match_time, :stoppage_time
11
+ attr_reader :team_stats, :player_stats
9
12
 
10
13
  def initialize(data = {}, league_group: nil, **opts)
11
14
  @response = data
12
15
  @id = data['id']
13
16
  @api = opts[:api]
17
+ @updates = {}
18
+ @changes = {}
14
19
 
15
20
  @league_group = league_group || data['league_group'] || @api&.league_group
16
21
 
17
22
  @timeline_hash = {}
18
23
  @lineups_hash = {}
19
24
  get_tournament_id(data, **opts)
20
- @home = Team.new({}, api: api, match: self)
21
- @away = Team.new({}, api: api, match: self)
25
+ @scoring_raw = Scoring.new(data, game: self)
26
+ @home = Team.new(data['home'].to_h, api: api, match: self)
27
+ @away = Team.new(data['away'].to_h, api: api, match: self)
22
28
  @teams_hash = { away: @away, home: @home }
29
+ @team_ids = { away: @away.id, home: @home.id }
30
+ @team_stats = {}
31
+ @player_stats = {}
23
32
 
24
33
  update(data, **opts)
25
34
  end
@@ -47,7 +56,6 @@ module Sportradar
47
56
  update_teams(stats)
48
57
  end
49
58
 
50
-
51
59
  @scheduled = Time.parse(data['scheduled']) if data['scheduled']
52
60
  @start_time_tbd = data['start_time_tbd'] if data.key?('start_time_tbd')
53
61
  @status = data['status'] if data['status']
@@ -60,14 +68,18 @@ module Sportradar
60
68
 
61
69
  @home_score = data['home_score'] if data['home_score']
62
70
  @away_score = data['away_score'] if data['away_score']
71
+ @period = data['period'] if data['period']
72
+ @match_time = data.dig('clock', 'match_time') if data.dig('clock', 'match_time')
73
+ @stoppage_time = data.dig('clock', 'stoppage_time')
74
+ @ball_locations = data['ball_locations'] if data['ball_locations']
63
75
  @winner_id = data['winner_id'] if data['winner_id']
64
76
 
65
77
  @aggregate_home_score = data['aggregate_home_score'] if data['aggregate_home_score']
66
78
  @aggregate_away_score = data['aggregate_away_score'] if data['aggregate_away_score']
67
79
  @aggregate_winner_id = data['aggregate_winner_id'] if data['aggregate_winner_id']
80
+ @scoring_raw.update(data, source: opts[:source])
68
81
  create_data(@timeline_hash, data['timeline'], klass: Event, api: api)
69
- # @season
70
- # @tournament
82
+
71
83
  if data['competitors']
72
84
  update_teams(data['competitors'])
73
85
  end
@@ -75,6 +87,97 @@ module Sportradar
75
87
  # parse_nested_data(data)
76
88
  end
77
89
 
90
+ def title
91
+ [@home, @away].compact.map(&:name).join(' vs ')
92
+ end
93
+ def scoring
94
+ @scoring_raw.scores.each { |period, hash| hash[@home.id] = hash['home']; hash[@away.id] = hash['away'] }
95
+ end
96
+ def score
97
+ {@home.id => @home_score, @away.id => @away_score}
98
+ end
99
+
100
+ # status helpers
101
+ def realtime_state_short
102
+ if future?
103
+ 'Scheduled' # ??
104
+ elsif finished?
105
+ 'FT'
106
+ elsif postponed?
107
+ 'PPD'
108
+ elsif halftime?
109
+ 'HT'
110
+ else
111
+ clock_display
112
+ end
113
+ end
114
+
115
+ def realtime_state
116
+ if future?
117
+ 'Scheduled'
118
+ elsif finished?
119
+ 'Final'
120
+ elsif postponed?
121
+ 'Postponed'
122
+ elsif halftime?
123
+ 'Halftime'
124
+ else
125
+ clock_display
126
+ end
127
+ end
128
+
129
+ def halftime?
130
+ @match_status == "halftime"
131
+ end
132
+
133
+ def postponed?
134
+ 'postponed' == status
135
+ end
136
+ def unnecessary?
137
+ 'unnecessary' == status
138
+ end
139
+ def cancelled?
140
+ ['unnecessary', 'postponed'].include? status
141
+ end
142
+ def future?
143
+ ['not_started', 'scheduled', 'delayed', 'created', 'time-tbd', 'if-necessary'].include? status
144
+ end
145
+ def started?
146
+ ['live'].include?(@status) || ['halftime', '1st_half', '2nd_half'].include?(@match_status)
147
+ end
148
+ def finished?
149
+ ['ended', 'complete', 'closed'].include?(@status) || ['ended'].include?(@match_status)
150
+ end
151
+ def completed?
152
+ ['ended', 'complete'].include? status
153
+ end
154
+ def closed?
155
+ 'closed' == status
156
+ end
157
+
158
+ def clock_display
159
+ return unless @match_time
160
+ if @match_time == '45:00' || @match_time == '90:00' # stoppage time
161
+ @match_time.split(':').first + ?'
162
+ else
163
+ @match_time.split(':').first + ?'
164
+ end
165
+ end
166
+ alias :clock :clock_display
167
+
168
+ def match_seconds
169
+ return nil unless @match_time
170
+ mm, ss = @match_time.split(':').map(&:to_i)
171
+ time = mm * 60 + ss
172
+ if @stoppage_time && (@match_time == '45:00' || @match_time == '90:00') # stoppage time
173
+ mm, ss = @stoppage_time.split(':').map(&:to_i)
174
+ stop_time = mm * 60 + ss
175
+ time += stop_time
176
+ end
177
+ time
178
+ end
179
+ alias :game_seconds :match_seconds
180
+
78
181
  def update_teams(data)
79
182
  home_hash = data.detect { |team_hash| team_hash["qualifier"] == "home" || team_hash["team"] == "home" }
80
183
  away_hash = (data - [home_hash]).first
@@ -83,9 +186,20 @@ module Sportradar
83
186
  @away.update(away_hash, match: self)
84
187
  @teams_hash[@home.id] = @home
85
188
  @teams_hash[@away.id] = @away
189
+ @team_ids = { away: @away.id, home: @home.id }
86
190
  end
87
191
  end
88
192
 
193
+ def update_stats(team, stats)
194
+ @team_stats.merge!(team.id => stats.merge(team: team))
195
+ end
196
+ def update_player_stats(player, stats)
197
+ @player_stats.merge!(player.id => stats.merge!(player: player))
198
+ end
199
+ def stats(team_id)
200
+ team_id.is_a?(Symbol) ? @team_stats[@team_ids[team_id]] : @team_stats[team_id]
201
+ end
202
+
89
203
  def get_tournament_id(data, **opts)
90
204
  @tournament_id ||= if opts[:tournament]
91
205
  opts[:tournament].id
@@ -112,6 +226,19 @@ module Sportradar
112
226
  end
113
227
  end
114
228
 
229
+ def timeline_by_minute(minute_start, minute_end = nil)
230
+ if minute_end
231
+ @timeline_hash.each_value.select { |ev| (minute_start..minute_end).cover?(ev.match_time.to_i) }
232
+ else
233
+ @timeline_hash.each_value.select { |ev| minute_start === ev.match_time }
234
+ end
235
+ end
236
+
237
+ def plays_by_minute(type, minute_start, minute_end = nil)
238
+ timeline_by_minute(minute_start, minute_end).select { |ev| ev.type == type }
239
+ end
240
+
241
+
115
242
  def lineups(which = nil)
116
243
  if which
117
244
  @lineups_hash[which.to_s]
@@ -200,7 +327,9 @@ module Sportradar
200
327
  ingest_timeline(data)
201
328
  end
202
329
  def ingest_timeline(data)
203
- update(data)
330
+ update(data, source: :pbp)
331
+ check_newness(:pbp, timeline.last&.updated)
332
+ check_newness(:clock, self.match_seconds.to_s)
204
333
  data
205
334
  end
206
335
  def queue_timeline
@@ -208,6 +337,21 @@ module Sportradar
208
337
  {url: url, headers: headers, params: options, timeout: timeout, callback: method(:ingest_timeline)}
209
338
  end
210
339
 
340
+ # tracking updates
341
+ def remember(key, object)
342
+ @updates[key] = object&.dup
343
+ end
344
+ def not_updated?(key, object)
345
+ @updates[key] == object
346
+ end
347
+ def changed?(key)
348
+ @changes[key]
349
+ end
350
+ def check_newness(key, new_object)
351
+ @changes[key] = !not_updated?(key, new_object)
352
+ remember(key, new_object)
353
+ end
354
+
211
355
  end
212
356
  end
213
357
  end
@@ -226,6 +370,17 @@ __END__
226
370
  [{"id"=>"sr:competitor:2793", "name"=>"US Sassuolo", "country"=>"Italy", "country_code"=>"ITA", "abbreviation"=>"SAS", "qualifier"=>"home"},
227
371
  {"id"=>"sr:competitor:2702", "name"=>"AS Roma", "country"=>"Italy", "country_code"=>"ITA", "abbreviation"=>"ROM", "qualifier"=>"away"}]
228
372
 
373
+ "sport_event_status"=>
374
+ {"status"=>"live",
375
+ "match_status"=>"1st_half",
376
+ "home_score"=>1,
377
+ "away_score"=>0,
378
+ "period"=>1,
379
+ "clock"=>{"match_time"=>"19:25"},
380
+ "clock"=>{"match_time"=>"45:00", "stoppage_time"=>"00:05"}
381
+ "ball_locations"=>[{"x"=>"95", "y"=>"50", "team"=>"away"}, {"x"=>"80", "y"=>"62", "team"=>"home"}, {"x"=>"82", "y"=>"60", "team"=>"home"}, {"x"=>"79", "y"=>"58", "team"=>"home"}]},
382
+
383
+
229
384
 
230
385
  m = Sportradar::Api::Soccer::Match.new({"id"=>"sr:match:12090446"}, league_group: 'eu')
231
386
  res = m.get_summary