beneggett/sportradar-api

View on GitHub
lib/sportradar/api/football/game.rb

Summary

Maintainability
D
2 days
Test Coverage
module Sportradar
  module Api
    module Football
      class Game < Data
        attr_accessor :response, :id, :title, :home_id, :away_id, :score, :status, :coverage, :scheduled, :venue, :broadcast, :duration, :attendance, :team_stats, :player_stats, :changes, :lineup, :week, :quarter, :clock, :api

        attr_reader :week_number, :year, :type

        def initialize(data, **opts)
          @response = data
          @api      = opts[:api]
          # @week     = opts[:week]

          @scoring_raw = Scoring.new(data, game: self)
          @home          = team_class.new({}, api: api, game: self)
          @away          = team_class.new({}, api: api, game: self)

          @updates  = {}
          @changes  = {}

          @teams_hash = {}
          @team_stats = {}
          @player_stats = StatsShim.new(self)

          @quarters_hash = {}
          @drives_hash = {}
          @score = {}

          update(data, **opts)
        end

        def timeouts
          @teams_hash.map { |t_id, team| [t_id, team.timeouts] }.to_h
        end

        def period
          quarter
        end

        def summary_stat(team_id, stat_name)
          scoring.dig(team_id, stat_name)
        end
        def stats(team_id)
          team_id.is_a?(Symbol) ? @team_stats[@team_ids[team_id]] : @team_stats[team_id]
        end

        def update_stats(team, stats)
          @team_stats.merge!(team.id => stats)
        end
        def update_player_stats(player, stats)
          @player_stats.merge!(player.id => stats.merge!(player: player))
        end

        def parse_score(data)
          # home_id = data.dig('home', 'id')
          # away_id = data.dig('away', 'id')
          # rhe = {
          #   'runs'    => { home_id => data.dig('home', 'runs'), away_id => data.dig('away', 'runs')},
          #   'hits'    => { home_id => data.dig('home', 'hits'), away_id => data.dig('away', 'hits')},
          #   'errors'  => { home_id => data.dig('home', 'errors'), away_id => data.dig('away', 'errors')},
          # }
          # @scoring_raw.update(rhe, source: :rhe)
          # update_score(home_id => data.dig('home', 'runs'))
          # update_score(away_id => data.dig('away', 'runs'))
        end

        def update(data, source: nil, **opts)
          @id           = data['id'] || @id
          # @year          = data['year'] || @week&.season.year
          # @type          = data['type'] || @week&.season.type
          # @week_number   = data['week'] || @week&.sequence

          @week_number = data['week_number']  || week&.number || opts[:week]&.number  || @week_number
          @year        = data['year']         || week&.year   || opts[:week]&.year    || @year
          @type        = data['type']         || week&.type   || opts[:week]&.type    || @type


          @coverage      = data['coverage']
          @scheduled     = Time.parse(data["scheduled"]) if data["scheduled"]
          update_teams(data)
          @status        = data['status']  || @status
          @clock         = data['clock']   || @clock
          @quarter       = data['quarter'] || @quarter
          @home_rotation = data['home_rotation']
          @away_rotation = data['away_rotation']
          @neutral_site  = data['neutral_site']
          @home_points   = data['home_points']
          @away_points   = data['away_points']
          @venue         = Venue.new(data["venue"]) if data["venue"]
          @weather       = data['weather']
          @broadcast     = Broadcast.new(data['broadcast']) if !data['broadcast'].to_h.empty?
          @attendance    = data['attendance']
          @title        = data['title'] || @title || generate_title

          # @links         = data['links'] ? structure_links(data['links']) : {}

          @teams_hash    = { @home.id => @home, @away.id => @away } if @home.id && @away.id
          @team_ids      = { home: (@home&.id || home_alias), away: (@away&.id || away_alias) }

          @scoring_raw.update(data, source: source)
          if data['statistics']
            @home.update({ 'statistics' => data.dig('statistics', 'home')}, game: self)
            @away.update({ 'statistics' => data.dig('statistics', 'away')}, game: self)
          end

          create_data(@teams_hash, data['team'], klass: team_class, api: api, game: self) if data['team']

          self
        end

        def generate_title
          (home && away && "#{home.full_name} vs #{away.full_name}")
        end

        def update_teams(data)
          if data['summary']
            @home.update(data.dig('summary', 'home'), game: self)
            @away.update(data.dig('summary', 'away'), game: self)
          else
            @home.update(data['home_team'], game: self) if data['home_team'].is_a?(Hash)
            @away.update(data['away_team'], game: self) if data['away_team'].is_a?(Hash)
            if data['home'].is_a?(String) # this might actually be team ID and not alias. check in NFL
              @home_alias    = data['home']
              @home.id ||= @home_alias
            end
            if data['away'].is_a?(String) # this might actually be team ID and not alias. check in NFL
              @away_alias    = data['away']
              @away.id ||= @away_alias
            end
          end
        end

        # def update_from_team(id, data)
        # end

        def home
          @teams_hash[@home_id] || @home
        end

        def home_alias
          @home_alias || @home&.alias
        end

        def away
          @teams_hash[@away_id] || @away
        end

        def away_alias
          @away_alias || @away&.alias
        end

        def tied?
          @score[away_id].to_i == @score[home_id].to_i
        end
        def scoring
          @scoring_raw.scores
        end
        def update_score(score)
          @score.merge!(score)
        end

        def update_drives(data, **opts)
          create_data(@drives_hash, data, klass: drive_class, api: api, game: self, **opts)
        end

        def leading_team_id
          return nil if tied?
          score.max_by(&:last).first
        end

        def leading_team
          @teams_hash[leading_team_id] || (@away_id == leading_team_id && away) || (@home_id == leading_team_id && home)
        end

        def current_possession_team_id
          drives.last&.team_id
        end

        def next_possession_team_id
          (@teams_hash.keys - [current_possession_team_id]).first || (@away_id == current_possession_team_id && @home_id) || (@home_id == current_possession_team_id && @away_id)
        end

        def team_ids
          @teams_hash.keys
        end

        def team(team_id)
          @teams_hash[team_id]
        end

        def assign_home(team)
          @home_id = team.id
          @teams_hash[team.id] = team
        end

        def assign_away(team)
          @away_id = team.id
          @teams_hash[team.id] = team
        end

        def box
          @box ||= get_box
        end

        def pbp
          if !future? && quarters.empty?
            get_pbp
          end
          @pbp ||= quarters
        end

        def drives_with_events
          @drives_hash.values
        end

        def drives
          drives_with_events.grep(Sportradar::Api::Football::Drive)
        end


        def plays
          drives.flat_map(&:plays).compact
        end

        def events
          drives.flat_map(&:events).compact
        end

        # def summary
        #   @summary ||= get_summary
        # end

        def quarters
          @quarters_hash.values
        end
        def half_quarters
          quarters.flat_map(&:half_quarters)
        end

        # tracking updates
        def remember(key, object)
          @updates[key] = object&.dup
        end

        def not_updated?(key, object)
          @updates[key] == object
        end

        def changed?(key)
          @changes[key]
        end

        def check_newness(key, new_object)
          @changes[key] = !not_updated?(key, new_object)
          remember(key, new_object)
        end

        # status helpers
        def realtime_state
          if future?
            'Scheduled'
          elsif delayed?
            'Delayed'
          elsif finished?
            'Final'
          elsif postponed?
            'Postponed'
          elsif halftime?
            'Half'
          else
            clock_display
          end
        end

        def clock_display
          if clock && quarter
            "#{clock} #{quarter_display}"
          end
        end
        def quarter_display
          if quarter > 5
            "#{quarter - 4}OT"
          elsif quarter == 5
            'OT'
          else
            "#{quarter}Q"
          end
        end

        def postponed?
          'postponed' == status
        end

        def unnecessary?
          'unnecessary' == status
        end

        def cancelled?
          ['unnecessary', 'postponed'].include? status
        end

        def delayed?
          'delayed' == status
        end

        def future?
          ['scheduled', 'created', 'time-tbd'].include? status
        end

        def started?
          ['inprogress', 'halftime', 'wdelay', 'delayed'].include? status
        end

        def halftime?
          'halftime' == status
        end

        def finished?
          ['complete', 'closed'].include? status
        end

        def completed?
          'complete' == status
        end

        def closed?
          'closed' == status
        end

        # url path helpers
        def path_box
          "#{ path_base }/boxscore"
        end
        def path_extended_box
          "#{ path_base }/extended-boxscore"
        end
        def path_pbp
          "#{ path_base }/pbp"
        end
        def path_roster
          "#{ path_base }/roster"
        end
        def path_statistics
          "#{ path_base }/statistics"
        end
        def path_summary
          "#{ path_base }/summary"
        end

        # data retrieval

        def get_box
          data = api.get_data(path_box).to_h
          ingest_box(data)
        end

        def ingest_box(data)
          data = data
          update(data, source: :box)
          check_newness(:box, @clock)
          data
        # rescue => e
        #   binding.pry
        end

        def queue_pbp
          url, headers, options, timeout = api.get_request_info(path_pbp)
          {url: url, headers: headers, params: options, timeout: timeout, callback: method(:ingest_pbp)}
        end

        def get_pbp
          data = api.get_data(path_pbp).to_h
          ingest_pbp(data)
        end

        def ingest_pbp(data)
          data = data
          update(data, source: :pbp)
          create_data(@quarters_hash, data[period_key], klass: quarter_class, identifier: quarter_class.period_index, api: api, game: self) if data[period_key]
          check_newness(:pbp, plays.last&.description)
          check_newness(:score, @score)
          @pbp = @quarters_hash.values
          data
        # rescue => e
        #   binding.pry
        end

        def period_key
          'quarters'
        end

        def get_statistics
          data = api.get_data(path_statistics).to_h
          ingest_statistics(data)
        end

        def get_summary
          get_statistics
        end

        def queue_statistics
          url, headers, options, timeout = api.get_request_info(path_statistics)
          {url: url, headers: headers, params: options, timeout: timeout, callback: method(:ingest_statistics)}
        end

        def ingest_statistics(data)
          update(data, source: :statistics)
          check_newness(:statistics, @clock)
          data
        # rescue => e
        #   binding.pry
        end

        def quarter_class
          Sportradar::Api::Football::Quarter
        end

        def team_class
          Team
        end

        def sim!
          @api = api.sim!
          self
        end

      end
    end
  end
end