nba 0.1.1 → 0.2.0
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.
- checksums.yaml +5 -5
- data/AGENTS.md +362 -0
- data/CHANGELOG.md +169 -0
- data/CLAUDE.md +1 -0
- data/LICENSE +21 -0
- data/README.md +501 -101
- data/bin/console +10 -0
- data/bin/setup +6 -0
- data/exe/nba +8 -0
- data/lib/nba/all_time_leader.rb +77 -0
- data/lib/nba/all_time_leaders.rb +185 -0
- data/lib/nba/assist_leader.rb +92 -0
- data/lib/nba/assist_leaders.rb +64 -0
- data/lib/nba/assist_tracker.rb +108 -0
- data/lib/nba/assist_tracker_entry.rb +206 -0
- data/lib/nba/award.rb +128 -0
- data/lib/nba/box_score.rb +2 -0
- data/lib/nba/box_score_advanced.rb +114 -0
- data/lib/nba/box_score_advanced_player_stat.rb +297 -0
- data/lib/nba/box_score_advanced_team_stat.rb +237 -0
- data/lib/nba/box_score_advanced_v3.rb +124 -0
- data/lib/nba/box_score_defensive_player_stat.rb +281 -0
- data/lib/nba/box_score_defensive_team_stat.rb +85 -0
- data/lib/nba/box_score_defensive_v2.rb +190 -0
- data/lib/nba/box_score_four_factors.rb +91 -0
- data/lib/nba/box_score_four_factors_player_stat.rb +185 -0
- data/lib/nba/box_score_four_factors_team_stat.rb +141 -0
- data/lib/nba/box_score_four_factors_v3.rb +133 -0
- data/lib/nba/box_score_hustle.rb +226 -0
- data/lib/nba/box_score_hustle_player_stat.rb +233 -0
- data/lib/nba/box_score_hustle_team_stat.rb +189 -0
- data/lib/nba/box_score_matchup_stat.rb +417 -0
- data/lib/nba/box_score_matchups_v3.rb +184 -0
- data/lib/nba/box_score_misc.rb +100 -0
- data/lib/nba/box_score_misc_player_stat.rb +217 -0
- data/lib/nba/box_score_misc_team_stat.rb +173 -0
- data/lib/nba/box_score_misc_v3.rb +163 -0
- data/lib/nba/box_score_player_stat.rb +273 -0
- data/lib/nba/box_score_player_track.rb +223 -0
- data/lib/nba/box_score_player_track_stat.rb +273 -0
- data/lib/nba/box_score_player_track_team_stat.rb +229 -0
- data/lib/nba/box_score_scoring.rb +103 -0
- data/lib/nba/box_score_scoring_player_stat.rb +241 -0
- data/lib/nba/box_score_scoring_team_stat.rb +197 -0
- data/lib/nba/box_score_scoring_v3.rb +170 -0
- data/lib/nba/box_score_similarity_score.rb +119 -0
- data/lib/nba/box_score_similarity_stat.rb +76 -0
- data/lib/nba/box_score_starter_bench_stat.rb +257 -0
- data/lib/nba/box_score_summary.rb +285 -0
- data/lib/nba/box_score_summary_v2.rb +202 -0
- data/lib/nba/box_score_summary_v3.rb +120 -0
- data/lib/nba/box_score_summary_v3_data.rb +419 -0
- data/lib/nba/box_score_team_stat.rb +229 -0
- data/lib/nba/box_score_traditional.rb +101 -0
- data/lib/nba/box_score_traditional_v3.rb +195 -0
- data/lib/nba/box_score_usage.rb +102 -0
- data/lib/nba/box_score_usage_player_stat.rb +265 -0
- data/lib/nba/box_score_usage_team_stat.rb +221 -0
- data/lib/nba/box_score_usage_v3.rb +169 -0
- data/lib/nba/box_score_v3_helpers.rb +144 -0
- data/lib/nba/career_stats.rb +217 -0
- data/lib/nba/cli/display/player_display.rb +98 -0
- data/lib/nba/cli/display.rb +178 -0
- data/lib/nba/cli/formatters/game_formatters.rb +86 -0
- data/lib/nba/cli/formatters/leaders_formatters.rb +26 -0
- data/lib/nba/cli/formatters/player_formatters.rb +52 -0
- data/lib/nba/cli/formatters/standings_formatters.rb +26 -0
- data/lib/nba/cli/formatters/team_formatters.rb +67 -0
- data/lib/nba/cli/formatters/time_formatters.rb +82 -0
- data/lib/nba/cli/formatters.rb +56 -0
- data/lib/nba/cli/helpers.rb +135 -0
- data/lib/nba/cli.rb +171 -20
- data/lib/nba/client.rb +35 -0
- data/lib/nba/collection.rb +89 -0
- data/lib/nba/college_player_stat.rb +200 -0
- data/lib/nba/common_player_info.rb +142 -0
- data/lib/nba/common_playoff_series.rb +90 -0
- data/lib/nba/common_team_years.rb +113 -0
- data/lib/nba/conference.rb +39 -0
- data/lib/nba/connection.rb +84 -0
- data/lib/nba/cume_stats_player.rb +358 -0
- data/lib/nba/cume_stats_player_game.rb +217 -0
- data/lib/nba/cume_stats_player_games.rb +99 -0
- data/lib/nba/cume_stats_player_games_entry.rb +25 -0
- data/lib/nba/cume_stats_player_total.rb +481 -0
- data/lib/nba/cume_stats_team.rb +349 -0
- data/lib/nba/cume_stats_team_games.rb +145 -0
- data/lib/nba/cume_stats_team_games_entry.rb +25 -0
- data/lib/nba/cume_stats_team_player.rb +485 -0
- data/lib/nba/cume_stats_team_total.rb +267 -0
- data/lib/nba/data.rb +73 -0
- data/lib/nba/defense_hub.rb +109 -0
- data/lib/nba/defense_hub_stat.rb +57 -0
- data/lib/nba/defensive_shot_stat.rb +102 -0
- data/lib/nba/division.rb +49 -0
- data/lib/nba/draft_board.rb +126 -0
- data/lib/nba/draft_board_pick.rb +173 -0
- data/lib/nba/draft_combine_anthro_measurement.rb +163 -0
- data/lib/nba/draft_combine_drill_result.rb +115 -0
- data/lib/nba/draft_combine_drill_results.rb +112 -0
- data/lib/nba/draft_combine_non_stationary_shooting.rb +268 -0
- data/lib/nba/draft_combine_non_stationary_shooting_result.rb +355 -0
- data/lib/nba/draft_combine_player_anthro.rb +133 -0
- data/lib/nba/draft_combine_spot_shooting.rb +243 -0
- data/lib/nba/draft_combine_spot_shooting_result.rb +419 -0
- data/lib/nba/draft_combine_stat.rb +211 -0
- data/lib/nba/draft_combine_stats.rb +160 -0
- data/lib/nba/draft_history.rb +142 -0
- data/lib/nba/draft_pick.rb +154 -0
- data/lib/nba/dunk_score_leader.rb +93 -0
- data/lib/nba/dunk_score_leaders.rb +77 -0
- data/lib/nba/estimated_metrics_stat.rb +152 -0
- data/lib/nba/fantasy_profile_stat.rb +142 -0
- data/lib/nba/fantasy_widget.rb +72 -0
- data/lib/nba/fantasy_widget_player.rb +98 -0
- data/lib/nba/found_game.rb +260 -0
- data/lib/nba/franchise.rb +136 -0
- data/lib/nba/franchise_history.rb +142 -0
- data/lib/nba/franchise_leader.rb +147 -0
- data/lib/nba/franchise_leaders.rb +162 -0
- data/lib/nba/franchise_player.rb +224 -0
- data/lib/nba/franchise_players.rb +147 -0
- data/lib/nba/game.rb +80 -64
- data/lib/nba/game_log.rb +349 -0
- data/lib/nba/game_rotation.rb +152 -0
- data/lib/nba/game_streak.rb +102 -0
- data/lib/nba/games.rb +46 -0
- data/lib/nba/home_page_leader.rb +99 -0
- data/lib/nba/home_page_leaders.rb +75 -0
- data/lib/nba/home_page_stat.rb +57 -0
- data/lib/nba/home_page_v2.rb +110 -0
- data/lib/nba/hustle_stats_box_score.rb +182 -0
- data/lib/nba/infographic_fan_duel_player.rb +139 -0
- data/lib/nba/infographic_fan_duel_player_stat.rb +311 -0
- data/lib/nba/ist_standing.rb +167 -0
- data/lib/nba/ist_standings.rb +81 -0
- data/lib/nba/leader.rb +103 -0
- data/lib/nba/leaders.rb +110 -0
- data/lib/nba/leaders_tile.rb +57 -0
- data/lib/nba/leaders_tiles.rb +90 -0
- data/lib/nba/league.rb +37 -0
- data/lib/nba/league_dash_lineup_stat.rb +270 -0
- data/lib/nba/league_dash_lineups.rb +177 -0
- data/lib/nba/league_dash_opp_pt_shot.rb +150 -0
- data/lib/nba/league_dash_player_bio_stat.rb +217 -0
- data/lib/nba/league_dash_player_bio_stats.rb +164 -0
- data/lib/nba/league_dash_player_clutch.rb +212 -0
- data/lib/nba/league_dash_player_clutch_stat.rb +271 -0
- data/lib/nba/league_dash_player_pt_shot.rb +152 -0
- data/lib/nba/league_dash_player_pt_shot_stat.rb +193 -0
- data/lib/nba/league_dash_player_shot_location_stat.rb +265 -0
- data/lib/nba/league_dash_player_shot_locations.rb +210 -0
- data/lib/nba/league_dash_player_stat.rb +306 -0
- data/lib/nba/league_dash_player_stats.rb +176 -0
- data/lib/nba/league_dash_pt_defend.rb +160 -0
- data/lib/nba/league_dash_pt_defend_stat.rb +145 -0
- data/lib/nba/league_dash_pt_stats.rb +152 -0
- data/lib/nba/league_dash_pt_stats_stat.rb +169 -0
- data/lib/nba/league_dash_pt_team_defend.rb +158 -0
- data/lib/nba/league_dash_pt_team_defend_stat.rb +110 -0
- data/lib/nba/league_dash_team_clutch.rb +211 -0
- data/lib/nba/league_dash_team_clutch_stat.rb +237 -0
- data/lib/nba/league_dash_team_pt_shot.rb +150 -0
- data/lib/nba/league_dash_team_pt_shot_stat.rb +166 -0
- data/lib/nba/league_dash_team_shot_location_stat.rb +230 -0
- data/lib/nba/league_dash_team_shot_locations.rb +208 -0
- data/lib/nba/league_dash_team_stat.rb +275 -0
- data/lib/nba/league_dash_team_stats.rb +172 -0
- data/lib/nba/league_game_finder.rb +170 -0
- data/lib/nba/league_game_log.rb +224 -0
- data/lib/nba/league_hustle_stats_player.rb +161 -0
- data/lib/nba/league_hustle_stats_player_stat.rb +253 -0
- data/lib/nba/league_hustle_stats_team.rb +157 -0
- data/lib/nba/league_hustle_stats_team_stat.rb +179 -0
- data/lib/nba/league_lineup_viz.rb +184 -0
- data/lib/nba/league_lineup_viz_stat.rb +214 -0
- data/lib/nba/league_player_on_details.rb +175 -0
- data/lib/nba/league_player_on_details_stat.rb +313 -0
- data/lib/nba/league_season_matchup_stat.rb +241 -0
- data/lib/nba/league_season_matchups.rb +181 -0
- data/lib/nba/league_standing.rb +284 -0
- data/lib/nba/league_standings.rb +159 -0
- data/lib/nba/league_wide_shot_stat.rb +62 -0
- data/lib/nba/live_action.rb +240 -0
- data/lib/nba/live_box_score.rb +143 -0
- data/lib/nba/live_connection.rb +84 -0
- data/lib/nba/live_game.rb +230 -0
- data/lib/nba/live_play_by_play.rb +120 -0
- data/lib/nba/live_player_stat.rb +276 -0
- data/lib/nba/live_scoreboard.rb +102 -0
- data/lib/nba/matchup_rollup.rb +98 -0
- data/lib/nba/matchups_rollup.rb +81 -0
- data/lib/nba/pass_stat.rb +209 -0
- data/lib/nba/play.rb +258 -0
- data/lib/nba/play_by_play.rb +85 -0
- data/lib/nba/play_by_play_v3.rb +91 -0
- data/lib/nba/play_type_stat.rb +206 -0
- data/lib/nba/player.rb +242 -24
- data/lib/nba/player_awards.rb +110 -0
- data/lib/nba/player_career_by_college.rb +86 -0
- data/lib/nba/player_career_by_college_rollup.rb +143 -0
- data/lib/nba/player_career_stats.rb +77 -0
- data/lib/nba/player_compare.rb +156 -0
- data/lib/nba/player_comparison_stat.rb +242 -0
- data/lib/nba/player_dash_pt_pass.rb +164 -0
- data/lib/nba/player_dash_pt_reb.rb +235 -0
- data/lib/nba/player_dash_pt_shot_defend.rb +119 -0
- data/lib/nba/player_dash_pt_shots.rb +279 -0
- data/lib/nba/player_dashboard.rb +259 -0
- data/lib/nba/player_dashboard_stat.rb +248 -0
- data/lib/nba/player_estimated_metrics.rb +84 -0
- data/lib/nba/player_fantasy_profile_bar_graph.rb +147 -0
- data/lib/nba/player_game_log.rb +72 -0
- data/lib/nba/player_game_logs.rb +117 -0
- data/lib/nba/player_game_streak_finder.rb +108 -0
- data/lib/nba/player_index.rb +135 -0
- data/lib/nba/player_index_entry.rb +266 -0
- data/lib/nba/player_info.rb +225 -0
- data/lib/nba/player_next_n_games.rb +64 -0
- data/lib/nba/player_profile_v2.rb +169 -0
- data/lib/nba/player_vs_player.rb +153 -0
- data/lib/nba/players.rb +107 -0
- data/lib/nba/playoff_matchup.rb +84 -0
- data/lib/nba/playoff_picture.rb +98 -0
- data/lib/nba/playoff_series.rb +76 -0
- data/lib/nba/position.rb +48 -0
- data/lib/nba/rebound_stat.rb +189 -0
- data/lib/nba/response_parser.rb +116 -0
- data/lib/nba/roster.rb +74 -0
- data/lib/nba/rotation_entry.rb +154 -0
- data/lib/nba/schedule.rb +183 -0
- data/lib/nba/schedule_international.rb +182 -0
- data/lib/nba/scheduled_game.rb +240 -0
- data/lib/nba/scoreboard.rb +183 -0
- data/lib/nba/scoreboard_v3.rb +104 -0
- data/lib/nba/shot.rb +208 -0
- data/lib/nba/shot_chart.rb +75 -0
- data/lib/nba/shot_chart_league_wide.rb +102 -0
- data/lib/nba/shot_chart_lineup_detail.rb +109 -0
- data/lib/nba/shot_stat.rb +174 -0
- data/lib/nba/standing.rb +129 -0
- data/lib/nba/standings.rb +75 -0
- data/lib/nba/static.rb +107 -0
- data/lib/nba/synergy_play_types.rb +211 -0
- data/lib/nba/team.rb +203 -127
- data/lib/nba/team_and_players_vs_players.rb +227 -0
- data/lib/nba/team_and_players_vs_players_stat.rb +155 -0
- data/lib/nba/team_dash_pt_pass.rb +157 -0
- data/lib/nba/team_dash_pt_reb.rb +216 -0
- data/lib/nba/team_dash_pt_shots.rb +244 -0
- data/lib/nba/team_dashboard.rb +275 -0
- data/lib/nba/team_dashboard_stat.rb +248 -0
- data/lib/nba/team_detail.rb +117 -0
- data/lib/nba/team_details.rb +173 -0
- data/lib/nba/team_estimated_metrics.rb +91 -0
- data/lib/nba/team_estimated_metrics_stat.rb +146 -0
- data/lib/nba/team_game_log.rb +143 -0
- data/lib/nba/team_game_log_entry.rb +246 -0
- data/lib/nba/team_game_log_stat.rb +275 -0
- data/lib/nba/team_game_logs.rb +163 -0
- data/lib/nba/team_game_streak.rb +111 -0
- data/lib/nba/team_game_streak_finder.rb +109 -0
- data/lib/nba/team_historical_leader.rb +207 -0
- data/lib/nba/team_historical_leaders.rb +98 -0
- data/lib/nba/team_historical_record.rb +139 -0
- data/lib/nba/team_info.rb +150 -0
- data/lib/nba/team_info_common.rb +177 -0
- data/lib/nba/team_on_off_overall_stat.rb +477 -0
- data/lib/nba/team_on_off_player_stat.rb +523 -0
- data/lib/nba/team_on_off_player_summary.rb +135 -0
- data/lib/nba/team_pass_stat.rb +183 -0
- data/lib/nba/team_player_dashboard.rb +212 -0
- data/lib/nba/team_player_on_off_details.rb +218 -0
- data/lib/nba/team_player_on_off_summary.rb +214 -0
- data/lib/nba/team_player_stat.rb +275 -0
- data/lib/nba/team_rebound_stat.rb +189 -0
- data/lib/nba/team_season_rank.rb +110 -0
- data/lib/nba/team_shot_stat.rb +173 -0
- data/lib/nba/team_vs_player.rb +151 -0
- data/lib/nba/team_vs_player_stat.rb +157 -0
- data/lib/nba/team_year.rb +55 -0
- data/lib/nba/team_year_by_year_stats.rb +152 -0
- data/lib/nba/team_year_stat.rb +282 -0
- data/lib/nba/teams.rb +33 -0
- data/lib/nba/upcoming_game.rb +115 -0
- data/lib/nba/utils.rb +94 -0
- data/lib/nba/version.rb +5 -2
- data/lib/nba/video_detail.rb +103 -0
- data/lib/nba/video_details.rb +118 -0
- data/lib/nba/video_details_asset.rb +115 -0
- data/lib/nba/video_details_asset_entry.rb +91 -0
- data/lib/nba/video_event.rb +83 -0
- data/lib/nba/video_event_asset.rb +91 -0
- data/lib/nba/video_events.rb +106 -0
- data/lib/nba/video_events_asset.rb +107 -0
- data/lib/nba/video_status.rb +129 -0
- data/lib/nba/video_status_entry.rb +161 -0
- data/lib/nba/vs_player_stat.rb +156 -0
- data/lib/nba/win_probability.rb +117 -0
- data/lib/nba/win_probability_point.rb +140 -0
- data/lib/nba.rb +249 -5
- data/sig/equalizer.rbs +3 -0
- data/sig/nba.rbs +7297 -0
- data/sig/shale.rbs +24 -0
- data/sig/thor.rbs +19 -0
- metadata +324 -95
- data/.gitignore +0 -18
- data/.travis.yml +0 -22
- data/Gemfile +0 -23
- data/LICENSE.md +0 -22
- data/Rakefile +0 -18
- data/bin/nba +0 -7
- data/cache/teams.json +0 -16529
- data/lib/faraday_middleware/scrape_game.rb +0 -41
- data/lib/nba/request.rb +0 -37
- data/nba.gemspec +0 -28
- data/spec/fixtures/games.html +0 -785
- data/spec/fixtures/teams.json +0 -16529
- data/spec/game_spec.rb +0 -40
- data/spec/spec_helper.rb +0 -25
- data/spec/team_spec.rb +0 -93
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
require "date"
|
|
2
|
+
require "json"
|
|
3
|
+
require_relative "client"
|
|
4
|
+
require_relative "collection"
|
|
5
|
+
require_relative "game"
|
|
6
|
+
require_relative "team"
|
|
7
|
+
require_relative "teams"
|
|
8
|
+
|
|
9
|
+
module NBA
|
|
10
|
+
# Provides methods to retrieve NBA scoreboard data
|
|
11
|
+
module Scoreboard
|
|
12
|
+
# Retrieves games for a specific date
|
|
13
|
+
#
|
|
14
|
+
# @api public
|
|
15
|
+
# @example
|
|
16
|
+
# games = NBA::Scoreboard.games(date: Date.today)
|
|
17
|
+
# games.each { |game| puts "#{game.away_team.name} @ #{game.home_team.name}" }
|
|
18
|
+
# @param date [Date] the date to retrieve games for (defaults to today)
|
|
19
|
+
# @param client [Client] the API client to use
|
|
20
|
+
# @return [Collection] a collection of games
|
|
21
|
+
def self.games(date: Date.today, client: CLIENT)
|
|
22
|
+
path = "scoreboardv2?GameDate=#{date}&LeagueID=00&DayOffset=0"
|
|
23
|
+
response = client.get(path)
|
|
24
|
+
parse_scoreboard_response(response)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Parses the scoreboard API response
|
|
28
|
+
#
|
|
29
|
+
# @api private
|
|
30
|
+
# @param response [String] the JSON response body
|
|
31
|
+
# @return [Collection] a collection of games
|
|
32
|
+
def self.parse_scoreboard_response(response)
|
|
33
|
+
return Collection.new unless response
|
|
34
|
+
|
|
35
|
+
data = JSON.parse(response)
|
|
36
|
+
game_header = find_result_set(data, "GameHeader")
|
|
37
|
+
line_score = find_result_set(data, "LineScore")
|
|
38
|
+
return Collection.new unless game_header && line_score
|
|
39
|
+
|
|
40
|
+
games = build_games(game_header, line_score)
|
|
41
|
+
Collection.new(games)
|
|
42
|
+
end
|
|
43
|
+
private_class_method :parse_scoreboard_response
|
|
44
|
+
|
|
45
|
+
# Finds a result set by name
|
|
46
|
+
#
|
|
47
|
+
# @api private
|
|
48
|
+
# @param data [Hash] the parsed JSON data
|
|
49
|
+
# @param name [String] the result set name to find
|
|
50
|
+
# @return [Hash, nil] the result set or nil
|
|
51
|
+
def self.find_result_set(data, name)
|
|
52
|
+
result_sets = data["resultSets"]
|
|
53
|
+
return unless result_sets
|
|
54
|
+
|
|
55
|
+
result_sets.find { |rs| rs["name"].eql?(name) }
|
|
56
|
+
end
|
|
57
|
+
private_class_method :find_result_set
|
|
58
|
+
|
|
59
|
+
# Builds games from result sets
|
|
60
|
+
#
|
|
61
|
+
# @api private
|
|
62
|
+
# @param game_header [Hash] the GameHeader result set
|
|
63
|
+
# @param line_score [Hash] the LineScore result set
|
|
64
|
+
# @return [Array<Game>] the built games
|
|
65
|
+
def self.build_games(game_header, line_score)
|
|
66
|
+
header_data = parse_result_set(game_header)
|
|
67
|
+
score_data = parse_result_set(line_score)
|
|
68
|
+
|
|
69
|
+
header_data.uniq { |info| info.fetch("GAME_ID") }.map do |game_info|
|
|
70
|
+
game_id = game_info["GAME_ID"]
|
|
71
|
+
team_scores = score_data.select { |s| s["GAME_ID"].eql?(game_id) }
|
|
72
|
+
build_game(game_info, team_scores)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
private_class_method :build_games
|
|
76
|
+
|
|
77
|
+
# Parses a result set into an array of hashes
|
|
78
|
+
#
|
|
79
|
+
# @api private
|
|
80
|
+
# @param result_set [Hash] the result set
|
|
81
|
+
# @return [Array<Hash>] array of data hashes
|
|
82
|
+
def self.parse_result_set(result_set)
|
|
83
|
+
headers = result_set["headers"]
|
|
84
|
+
rows = result_set["rowSet"]
|
|
85
|
+
return [] unless headers && rows
|
|
86
|
+
|
|
87
|
+
rows.map { |row| headers.zip(row).to_h }
|
|
88
|
+
end
|
|
89
|
+
private_class_method :parse_result_set
|
|
90
|
+
|
|
91
|
+
# Builds a game from header and score data
|
|
92
|
+
#
|
|
93
|
+
# @api private
|
|
94
|
+
# @param game_info [Hash] the game header info
|
|
95
|
+
# @param team_scores [Array<Hash>] the team score data
|
|
96
|
+
# @return [Game] the built game
|
|
97
|
+
def self.build_game(game_info, team_scores)
|
|
98
|
+
Game.new(**game_attributes(game_info, team_scores))
|
|
99
|
+
end
|
|
100
|
+
private_class_method :build_game
|
|
101
|
+
|
|
102
|
+
# Extracts game attributes from game info and scores
|
|
103
|
+
#
|
|
104
|
+
# @api private
|
|
105
|
+
# @param info [Hash] the game header info
|
|
106
|
+
# @param scores [Array<Hash>] the team score data
|
|
107
|
+
# @return [Hash] the game attributes
|
|
108
|
+
def self.game_attributes(info, scores)
|
|
109
|
+
{
|
|
110
|
+
id: info["GAME_ID"], date: info["GAME_DATE_EST"],
|
|
111
|
+
status: game_status(info),
|
|
112
|
+
home_team: find_team(info["HOME_TEAM_ID"]),
|
|
113
|
+
away_team: find_team(info["VISITOR_TEAM_ID"]),
|
|
114
|
+
home_score: find_score(scores, info["HOME_TEAM_ID"]),
|
|
115
|
+
away_score: find_score(scores, info["VISITOR_TEAM_ID"]),
|
|
116
|
+
arena: info["ARENA_NAME"]
|
|
117
|
+
}
|
|
118
|
+
end
|
|
119
|
+
private_class_method :game_attributes
|
|
120
|
+
|
|
121
|
+
# Finds the score for a team
|
|
122
|
+
#
|
|
123
|
+
# @api private
|
|
124
|
+
# @param scores [Array<Hash>] the team score data
|
|
125
|
+
# @param team_id [Integer] the team ID
|
|
126
|
+
# @return [Integer, nil] the score
|
|
127
|
+
def self.find_score(scores, team_id)
|
|
128
|
+
score_data = scores.find { |s| s["TEAM_ID"].eql?(team_id) }
|
|
129
|
+
extract_score(score_data)
|
|
130
|
+
end
|
|
131
|
+
private_class_method :find_score
|
|
132
|
+
|
|
133
|
+
# Extracts score from score data
|
|
134
|
+
#
|
|
135
|
+
# @api private
|
|
136
|
+
# @param score_data [Hash, nil] the score data
|
|
137
|
+
# @return [Integer, nil] the score
|
|
138
|
+
def self.extract_score(score_data)
|
|
139
|
+
return unless score_data
|
|
140
|
+
|
|
141
|
+
score_data["PTS"]
|
|
142
|
+
end
|
|
143
|
+
private_class_method :extract_score
|
|
144
|
+
|
|
145
|
+
# Converts game status info to string
|
|
146
|
+
#
|
|
147
|
+
# @api private
|
|
148
|
+
# @param info [Hash] the game info hash
|
|
149
|
+
# @return [String] the status string
|
|
150
|
+
def self.game_status(info)
|
|
151
|
+
status_text = info["GAME_STATUS_TEXT"]
|
|
152
|
+
return status_text if status_text
|
|
153
|
+
|
|
154
|
+
status_id_to_text(info["GAME_STATUS_ID"])
|
|
155
|
+
end
|
|
156
|
+
private_class_method :game_status
|
|
157
|
+
|
|
158
|
+
# Converts game status ID to string
|
|
159
|
+
#
|
|
160
|
+
# @api private
|
|
161
|
+
# @param status_id [Integer] the status ID
|
|
162
|
+
# @return [String] the status string
|
|
163
|
+
def self.status_id_to_text(status_id)
|
|
164
|
+
case status_id
|
|
165
|
+
when 1 then "Scheduled"
|
|
166
|
+
when 2 then "In Progress"
|
|
167
|
+
when 3 then "Final"
|
|
168
|
+
else "Unknown"
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
private_class_method :status_id_to_text
|
|
172
|
+
|
|
173
|
+
# Finds a team by ID
|
|
174
|
+
#
|
|
175
|
+
# @api private
|
|
176
|
+
# @param team_id [Integer] the team ID
|
|
177
|
+
# @return [Team, nil] the team or nil
|
|
178
|
+
def self.find_team(team_id)
|
|
179
|
+
Teams.find(team_id)
|
|
180
|
+
end
|
|
181
|
+
private_class_method :find_team
|
|
182
|
+
end
|
|
183
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
require "date"
|
|
2
|
+
require "json"
|
|
3
|
+
require_relative "client"
|
|
4
|
+
require_relative "collection"
|
|
5
|
+
require_relative "game"
|
|
6
|
+
require_relative "league"
|
|
7
|
+
require_relative "team"
|
|
8
|
+
require_relative "teams"
|
|
9
|
+
|
|
10
|
+
module NBA
|
|
11
|
+
# Provides methods to retrieve NBA scoreboard data using the V3 endpoint
|
|
12
|
+
module ScoreboardV3
|
|
13
|
+
# Retrieves games for a specific date
|
|
14
|
+
#
|
|
15
|
+
# @api public
|
|
16
|
+
# @example
|
|
17
|
+
# games = NBA::ScoreboardV3.games(date: Date.today)
|
|
18
|
+
# games.each { |game| puts "#{game.away_team.name} @ #{game.home_team.name}" }
|
|
19
|
+
# @param date [Date] the date to retrieve games for (defaults to today)
|
|
20
|
+
# @param league [League, String] the league ID (defaults to NBA)
|
|
21
|
+
# @param client [Client] the API client to use
|
|
22
|
+
# @return [Collection] a collection of games
|
|
23
|
+
def self.games(date: Date.today, league: League::NBA, client: CLIENT)
|
|
24
|
+
league_id = Utils.extract_league_id(league)
|
|
25
|
+
path = "scoreboardv3?GameDate=#{date}&LeagueID=#{league_id}"
|
|
26
|
+
response = client.get(path)
|
|
27
|
+
parse_scoreboard_response(response)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Parses the scoreboard API response
|
|
31
|
+
#
|
|
32
|
+
# @api private
|
|
33
|
+
# @param response [String] the JSON response body
|
|
34
|
+
# @return [Collection] a collection of games
|
|
35
|
+
def self.parse_scoreboard_response(response)
|
|
36
|
+
return Collection.new if response.nil? || response.empty?
|
|
37
|
+
|
|
38
|
+
data = JSON.parse(response)
|
|
39
|
+
scoreboard = data["scoreboard"]
|
|
40
|
+
return Collection.new unless scoreboard
|
|
41
|
+
|
|
42
|
+
game_data = scoreboard["games"]
|
|
43
|
+
return Collection.new unless game_data
|
|
44
|
+
|
|
45
|
+
games = game_data.map { |game_info| build_game(game_info) }
|
|
46
|
+
Collection.new(games)
|
|
47
|
+
end
|
|
48
|
+
private_class_method :parse_scoreboard_response
|
|
49
|
+
|
|
50
|
+
# Builds a game from game data
|
|
51
|
+
#
|
|
52
|
+
# @api private
|
|
53
|
+
# @param game_info [Hash] the game info
|
|
54
|
+
# @return [Game] the built game
|
|
55
|
+
def self.build_game(game_info)
|
|
56
|
+
Game.new(**game_attributes(game_info))
|
|
57
|
+
end
|
|
58
|
+
private_class_method :build_game
|
|
59
|
+
|
|
60
|
+
# Extracts game attributes from game info
|
|
61
|
+
#
|
|
62
|
+
# @api private
|
|
63
|
+
# @param info [Hash] the game info
|
|
64
|
+
# @return [Hash] the game attributes
|
|
65
|
+
def self.game_attributes(info)
|
|
66
|
+
{
|
|
67
|
+
id: info["gameId"],
|
|
68
|
+
date: info["gameTimeUTC"],
|
|
69
|
+
status: game_status(info["gameStatus"]),
|
|
70
|
+
home_team: find_team(info.dig("homeTeam", "teamId")),
|
|
71
|
+
away_team: find_team(info.dig("awayTeam", "teamId")),
|
|
72
|
+
home_score: info.dig("homeTeam", "score"),
|
|
73
|
+
away_score: info.dig("awayTeam", "score"),
|
|
74
|
+
arena: info["arenaName"]
|
|
75
|
+
}
|
|
76
|
+
end
|
|
77
|
+
private_class_method :game_attributes
|
|
78
|
+
|
|
79
|
+
# Converts game status ID to string
|
|
80
|
+
#
|
|
81
|
+
# @api private
|
|
82
|
+
# @param status_id [Integer] the status ID
|
|
83
|
+
# @return [String] the status string
|
|
84
|
+
def self.game_status(status_id)
|
|
85
|
+
case status_id
|
|
86
|
+
when 1 then "Scheduled"
|
|
87
|
+
when 2 then "In Progress"
|
|
88
|
+
when 3 then "Final"
|
|
89
|
+
else "Unknown"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
private_class_method :game_status
|
|
93
|
+
|
|
94
|
+
# Finds a team by ID
|
|
95
|
+
#
|
|
96
|
+
# @api private
|
|
97
|
+
# @param team_id [Integer] the team ID
|
|
98
|
+
# @return [Team, nil] the team or nil
|
|
99
|
+
def self.find_team(team_id)
|
|
100
|
+
Teams.find(team_id)
|
|
101
|
+
end
|
|
102
|
+
private_class_method :find_team
|
|
103
|
+
end
|
|
104
|
+
end
|
data/lib/nba/shot.rb
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
module NBA
|
|
2
|
+
# Represents a single shot attempt
|
|
3
|
+
class Shot < Shale::Mapper
|
|
4
|
+
include Equalizer.new(:game_id, :game_event_id)
|
|
5
|
+
|
|
6
|
+
# @!attribute [rw] game_id
|
|
7
|
+
# Returns the game ID
|
|
8
|
+
# @api public
|
|
9
|
+
# @example
|
|
10
|
+
# shot.game_id #=> "0022400001"
|
|
11
|
+
# @return [String] the game ID
|
|
12
|
+
attribute :game_id, Shale::Type::String
|
|
13
|
+
|
|
14
|
+
# @!attribute [rw] game_event_id
|
|
15
|
+
# Returns the game event ID
|
|
16
|
+
# @api public
|
|
17
|
+
# @example
|
|
18
|
+
# shot.game_event_id #=> 10
|
|
19
|
+
# @return [Integer] the event ID
|
|
20
|
+
attribute :game_event_id, Shale::Type::Integer
|
|
21
|
+
|
|
22
|
+
# @!attribute [rw] player_id
|
|
23
|
+
# Returns the player ID
|
|
24
|
+
# @api public
|
|
25
|
+
# @example
|
|
26
|
+
# shot.player_id #=> 201939
|
|
27
|
+
# @return [Integer] the player ID
|
|
28
|
+
attribute :player_id, Shale::Type::Integer
|
|
29
|
+
|
|
30
|
+
# @!attribute [rw] player_name
|
|
31
|
+
# Returns the player name
|
|
32
|
+
# @api public
|
|
33
|
+
# @example
|
|
34
|
+
# shot.player_name #=> "Stephen Curry"
|
|
35
|
+
# @return [String] the player name
|
|
36
|
+
attribute :player_name, Shale::Type::String
|
|
37
|
+
|
|
38
|
+
# @!attribute [rw] team_id
|
|
39
|
+
# Returns the team ID
|
|
40
|
+
# @api public
|
|
41
|
+
# @example
|
|
42
|
+
# shot.team_id #=> 1610612744
|
|
43
|
+
# @return [Integer] the team ID
|
|
44
|
+
attribute :team_id, Shale::Type::Integer
|
|
45
|
+
|
|
46
|
+
# @!attribute [rw] team_name
|
|
47
|
+
# Returns the team name
|
|
48
|
+
# @api public
|
|
49
|
+
# @example
|
|
50
|
+
# shot.team_name #=> "Golden State Warriors"
|
|
51
|
+
# @return [String] the team name
|
|
52
|
+
attribute :team_name, Shale::Type::String
|
|
53
|
+
|
|
54
|
+
# @!attribute [rw] period
|
|
55
|
+
# Returns the period number
|
|
56
|
+
# @api public
|
|
57
|
+
# @example
|
|
58
|
+
# shot.period #=> 1
|
|
59
|
+
# @return [Integer] the period
|
|
60
|
+
attribute :period, Shale::Type::Integer
|
|
61
|
+
|
|
62
|
+
# @!attribute [rw] minutes_remaining
|
|
63
|
+
# Returns minutes remaining in period
|
|
64
|
+
# @api public
|
|
65
|
+
# @example
|
|
66
|
+
# shot.minutes_remaining #=> 10
|
|
67
|
+
# @return [Integer] minutes remaining
|
|
68
|
+
attribute :minutes_remaining, Shale::Type::Integer
|
|
69
|
+
|
|
70
|
+
# @!attribute [rw] seconds_remaining
|
|
71
|
+
# Returns seconds remaining in period
|
|
72
|
+
# @api public
|
|
73
|
+
# @example
|
|
74
|
+
# shot.seconds_remaining #=> 45
|
|
75
|
+
# @return [Integer] seconds remaining
|
|
76
|
+
attribute :seconds_remaining, Shale::Type::Integer
|
|
77
|
+
|
|
78
|
+
# @!attribute [rw] action_type
|
|
79
|
+
# Returns the shot action type
|
|
80
|
+
# @api public
|
|
81
|
+
# @example
|
|
82
|
+
# shot.action_type #=> "Jump Shot"
|
|
83
|
+
# @return [String] the action type
|
|
84
|
+
attribute :action_type, Shale::Type::String
|
|
85
|
+
|
|
86
|
+
# @!attribute [rw] shot_type
|
|
87
|
+
# Returns the shot type (2PT or 3PT)
|
|
88
|
+
# @api public
|
|
89
|
+
# @example
|
|
90
|
+
# shot.shot_type #=> "3PT Field Goal"
|
|
91
|
+
# @return [String] the shot type
|
|
92
|
+
attribute :shot_type, Shale::Type::String
|
|
93
|
+
|
|
94
|
+
# @!attribute [rw] shot_zone_basic
|
|
95
|
+
# Returns the basic shot zone
|
|
96
|
+
# @api public
|
|
97
|
+
# @example
|
|
98
|
+
# shot.shot_zone_basic #=> "Above the Break 3"
|
|
99
|
+
# @return [String] the zone
|
|
100
|
+
attribute :shot_zone_basic, Shale::Type::String
|
|
101
|
+
|
|
102
|
+
# @!attribute [rw] shot_zone_area
|
|
103
|
+
# Returns the shot zone area
|
|
104
|
+
# @api public
|
|
105
|
+
# @example
|
|
106
|
+
# shot.shot_zone_area #=> "Center(C)"
|
|
107
|
+
# @return [String] the area
|
|
108
|
+
attribute :shot_zone_area, Shale::Type::String
|
|
109
|
+
|
|
110
|
+
# @!attribute [rw] shot_zone_range
|
|
111
|
+
# Returns the shot zone range
|
|
112
|
+
# @api public
|
|
113
|
+
# @example
|
|
114
|
+
# shot.shot_zone_range #=> "24+ ft."
|
|
115
|
+
# @return [String] the range
|
|
116
|
+
attribute :shot_zone_range, Shale::Type::String
|
|
117
|
+
|
|
118
|
+
# @!attribute [rw] shot_distance
|
|
119
|
+
# Returns the shot distance in feet
|
|
120
|
+
# @api public
|
|
121
|
+
# @example
|
|
122
|
+
# shot.shot_distance #=> 26
|
|
123
|
+
# @return [Integer] the distance
|
|
124
|
+
attribute :shot_distance, Shale::Type::Integer
|
|
125
|
+
|
|
126
|
+
# @!attribute [rw] loc_x
|
|
127
|
+
# Returns the X coordinate on the court
|
|
128
|
+
# @api public
|
|
129
|
+
# @example
|
|
130
|
+
# shot.loc_x #=> -22
|
|
131
|
+
# @return [Integer] the X coordinate
|
|
132
|
+
attribute :loc_x, Shale::Type::Integer
|
|
133
|
+
|
|
134
|
+
# @!attribute [rw] loc_y
|
|
135
|
+
# Returns the Y coordinate on the court
|
|
136
|
+
# @api public
|
|
137
|
+
# @example
|
|
138
|
+
# shot.loc_y #=> 239
|
|
139
|
+
# @return [Integer] the Y coordinate
|
|
140
|
+
attribute :loc_y, Shale::Type::Integer
|
|
141
|
+
|
|
142
|
+
# @!attribute [rw] shot_attempted_flag
|
|
143
|
+
# Returns whether a shot was attempted
|
|
144
|
+
# @api public
|
|
145
|
+
# @example
|
|
146
|
+
# shot.shot_attempted_flag #=> 1
|
|
147
|
+
# @return [Integer] 1 if attempted
|
|
148
|
+
attribute :shot_attempted_flag, Shale::Type::Integer
|
|
149
|
+
|
|
150
|
+
# @!attribute [rw] shot_made_flag
|
|
151
|
+
# Returns whether the shot was made
|
|
152
|
+
# @api public
|
|
153
|
+
# @example
|
|
154
|
+
# shot.shot_made_flag #=> 1
|
|
155
|
+
# @return [Integer] 1 if made, 0 if missed
|
|
156
|
+
attribute :shot_made_flag, Shale::Type::Integer
|
|
157
|
+
|
|
158
|
+
# Returns whether a shot was attempted
|
|
159
|
+
#
|
|
160
|
+
# @api public
|
|
161
|
+
# @example
|
|
162
|
+
# shot.attempted? #=> true
|
|
163
|
+
# @return [Boolean] true if attempted
|
|
164
|
+
def attempted?
|
|
165
|
+
shot_attempted_flag.eql?(1)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Returns whether the shot was made
|
|
169
|
+
#
|
|
170
|
+
# @api public
|
|
171
|
+
# @example
|
|
172
|
+
# shot.made? #=> true
|
|
173
|
+
# @return [Boolean] true if made
|
|
174
|
+
def made?
|
|
175
|
+
shot_made_flag.eql?(1)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Returns whether the shot was missed
|
|
179
|
+
#
|
|
180
|
+
# @api public
|
|
181
|
+
# @example
|
|
182
|
+
# shot.missed? #=> false
|
|
183
|
+
# @return [Boolean] true if missed
|
|
184
|
+
def missed?
|
|
185
|
+
shot_made_flag&.zero?
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Returns whether this is a three-pointer
|
|
189
|
+
#
|
|
190
|
+
# @api public
|
|
191
|
+
# @example
|
|
192
|
+
# shot.three_pointer? #=> true
|
|
193
|
+
# @return [Boolean] true if 3PT shot
|
|
194
|
+
def three_pointer?
|
|
195
|
+
shot_type&.include?("3PT")
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Returns the game object for this shot
|
|
199
|
+
#
|
|
200
|
+
# @api public
|
|
201
|
+
# @example
|
|
202
|
+
# shot.game #=> #<NBA::Game>
|
|
203
|
+
# @return [Game, nil] the game object
|
|
204
|
+
def game
|
|
205
|
+
Games.find(game_id)
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
require_relative "client"
|
|
2
|
+
require_relative "response_parser"
|
|
3
|
+
require_relative "shot"
|
|
4
|
+
require_relative "utils"
|
|
5
|
+
|
|
6
|
+
module NBA
|
|
7
|
+
# Provides methods to retrieve shot chart data
|
|
8
|
+
module ShotChart
|
|
9
|
+
# Result set name for shot chart detail
|
|
10
|
+
# @return [String] the result set name
|
|
11
|
+
RESULT_SET_NAME = "Shot_Chart_Detail".freeze
|
|
12
|
+
|
|
13
|
+
# Retrieves shot chart data for a player
|
|
14
|
+
#
|
|
15
|
+
# @api public
|
|
16
|
+
# @example
|
|
17
|
+
# shots = NBA::ShotChart.find(player: 201939, team: Team::GSW)
|
|
18
|
+
# shots.each { |s| puts "#{s.action_type} at (#{s.loc_x}, #{s.loc_y}): #{s.made? ? 'Made' : 'Missed'}" }
|
|
19
|
+
# @param player [Integer, Player] the player ID or Player object
|
|
20
|
+
# @param team [Integer, Team] the team ID or Team object
|
|
21
|
+
# @param season [Integer] the season year (defaults to current season)
|
|
22
|
+
# @param client [Client] the API client to use
|
|
23
|
+
# @return [Collection] a collection of shots
|
|
24
|
+
def self.find(player:, team:, season: Utils.current_season, client: CLIENT)
|
|
25
|
+
path = "shotchartdetail?PlayerID=#{Utils.extract_id(player)}&TeamID=#{Utils.extract_id(team)}" \
|
|
26
|
+
"&Season=#{Utils.format_season(season)}&ContextMeasure=FGA"
|
|
27
|
+
ResponseParser.parse(client.get(path), result_set: RESULT_SET_NAME) { |data| build_shot(data) }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Builds a shot from API data
|
|
31
|
+
# @api private
|
|
32
|
+
# @return [Shot]
|
|
33
|
+
def self.build_shot(data)
|
|
34
|
+
Shot.new(**identity_info(data), **timing_info(data), **shot_info(data), **location_info(data))
|
|
35
|
+
end
|
|
36
|
+
private_class_method :build_shot
|
|
37
|
+
|
|
38
|
+
# Extracts identity information from data
|
|
39
|
+
# @api private
|
|
40
|
+
# @return [Hash]
|
|
41
|
+
def self.identity_info(data)
|
|
42
|
+
{game_id: data["GAME_ID"], game_event_id: data["GAME_EVENT_ID"], player_id: data["PLAYER_ID"],
|
|
43
|
+
player_name: data["PLAYER_NAME"], team_id: data["TEAM_ID"], team_name: data["TEAM_NAME"]}
|
|
44
|
+
end
|
|
45
|
+
private_class_method :identity_info
|
|
46
|
+
|
|
47
|
+
# Extracts timing information from data
|
|
48
|
+
# @api private
|
|
49
|
+
# @return [Hash]
|
|
50
|
+
def self.timing_info(data)
|
|
51
|
+
{period: data["PERIOD"], minutes_remaining: data["MINUTES_REMAINING"],
|
|
52
|
+
seconds_remaining: data["SECONDS_REMAINING"]}
|
|
53
|
+
end
|
|
54
|
+
private_class_method :timing_info
|
|
55
|
+
|
|
56
|
+
# Extracts shot information from data
|
|
57
|
+
# @api private
|
|
58
|
+
# @return [Hash]
|
|
59
|
+
def self.shot_info(data)
|
|
60
|
+
{action_type: data["ACTION_TYPE"], shot_type: data["SHOT_TYPE"],
|
|
61
|
+
shot_zone_basic: data["SHOT_ZONE_BASIC"], shot_zone_area: data["SHOT_ZONE_AREA"],
|
|
62
|
+
shot_zone_range: data["SHOT_ZONE_RANGE"], shot_distance: data["SHOT_DISTANCE"],
|
|
63
|
+
shot_attempted_flag: data["SHOT_ATTEMPTED_FLAG"], shot_made_flag: data["SHOT_MADE_FLAG"]}
|
|
64
|
+
end
|
|
65
|
+
private_class_method :shot_info
|
|
66
|
+
|
|
67
|
+
# Extracts location information from data
|
|
68
|
+
# @api private
|
|
69
|
+
# @return [Hash]
|
|
70
|
+
def self.location_info(data)
|
|
71
|
+
{loc_x: data["LOC_X"], loc_y: data["LOC_Y"]}
|
|
72
|
+
end
|
|
73
|
+
private_class_method :location_info
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require_relative "client"
|
|
3
|
+
require_relative "collection"
|
|
4
|
+
require_relative "utils"
|
|
5
|
+
|
|
6
|
+
require_relative "league_wide_shot_stat"
|
|
7
|
+
|
|
8
|
+
module NBA
|
|
9
|
+
# Provides methods to retrieve league-wide shot chart data
|
|
10
|
+
module ShotChartLeagueWide
|
|
11
|
+
# Result set name for league-wide shot data
|
|
12
|
+
# @return [String] the result set name
|
|
13
|
+
LEAGUE_WIDE = "LeagueWide".freeze
|
|
14
|
+
|
|
15
|
+
# Season type constant for regular season
|
|
16
|
+
# @return [String] the season type
|
|
17
|
+
REGULAR_SEASON = "Regular Season".freeze
|
|
18
|
+
|
|
19
|
+
# Season type constant for playoffs
|
|
20
|
+
# @return [String] the season type
|
|
21
|
+
PLAYOFFS = "Playoffs".freeze
|
|
22
|
+
|
|
23
|
+
# Retrieves league-wide shot statistics
|
|
24
|
+
#
|
|
25
|
+
# @api public
|
|
26
|
+
# @example
|
|
27
|
+
# stats = NBA::ShotChartLeagueWide.all(season: 2024)
|
|
28
|
+
# stats.each { |s| puts "#{s.shot_zone_basic}: #{(s.fg_pct * 100).round(1)}%" }
|
|
29
|
+
# @param season [Integer] the season year
|
|
30
|
+
# @param season_type [String] the season type
|
|
31
|
+
# @param client [Client] the API client to use
|
|
32
|
+
# @return [Collection] a collection of league-wide shot stats
|
|
33
|
+
def self.all(season: Utils.current_season, season_type: REGULAR_SEASON, client: CLIENT)
|
|
34
|
+
path = build_path(season, season_type)
|
|
35
|
+
response = client.get(path)
|
|
36
|
+
parse_response(response)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Builds the API path
|
|
40
|
+
#
|
|
41
|
+
# @api private
|
|
42
|
+
# @return [String] the request path
|
|
43
|
+
def self.build_path(season, season_type)
|
|
44
|
+
season_str = Utils.format_season(season)
|
|
45
|
+
"shotchartleaguewide?LeagueID=00&Season=#{season_str}&SeasonType=#{season_type}"
|
|
46
|
+
end
|
|
47
|
+
private_class_method :build_path
|
|
48
|
+
|
|
49
|
+
# Parses the API response into shot stat objects
|
|
50
|
+
#
|
|
51
|
+
# @api private
|
|
52
|
+
# @return [Collection] collection of shot stats
|
|
53
|
+
def self.parse_response(response)
|
|
54
|
+
return Collection.new unless response
|
|
55
|
+
|
|
56
|
+
data = JSON.parse(response)
|
|
57
|
+
result_set = find_result_set(data)
|
|
58
|
+
return Collection.new unless result_set
|
|
59
|
+
|
|
60
|
+
headers = result_set["headers"]
|
|
61
|
+
rows = result_set["rowSet"]
|
|
62
|
+
return Collection.new unless headers && rows
|
|
63
|
+
|
|
64
|
+
stats = rows.map { |row| build_shot_stat(headers, row) }
|
|
65
|
+
Collection.new(stats)
|
|
66
|
+
end
|
|
67
|
+
private_class_method :parse_response
|
|
68
|
+
|
|
69
|
+
# Finds the result set in the response
|
|
70
|
+
#
|
|
71
|
+
# @api private
|
|
72
|
+
# @return [Hash, nil] the result set hash
|
|
73
|
+
def self.find_result_set(data)
|
|
74
|
+
result_sets = data["resultSets"]
|
|
75
|
+
return unless result_sets
|
|
76
|
+
|
|
77
|
+
result_sets.find { |rs| rs["name"].eql?(LEAGUE_WIDE) }
|
|
78
|
+
end
|
|
79
|
+
private_class_method :find_result_set
|
|
80
|
+
|
|
81
|
+
# Builds a LeagueWideShotStat object from raw data
|
|
82
|
+
#
|
|
83
|
+
# @api private
|
|
84
|
+
# @return [LeagueWideShotStat] the shot stat object
|
|
85
|
+
def self.build_shot_stat(headers, row)
|
|
86
|
+
data = headers.zip(row).to_h
|
|
87
|
+
LeagueWideShotStat.new(**shot_stat_attributes(data))
|
|
88
|
+
end
|
|
89
|
+
private_class_method :build_shot_stat
|
|
90
|
+
|
|
91
|
+
# Extracts shot stat attributes from data
|
|
92
|
+
#
|
|
93
|
+
# @api private
|
|
94
|
+
# @return [Hash] shot stat attributes
|
|
95
|
+
def self.shot_stat_attributes(data)
|
|
96
|
+
{grid_type: data["GRID_TYPE"], shot_zone_basic: data["SHOT_ZONE_BASIC"],
|
|
97
|
+
shot_zone_area: data["SHOT_ZONE_AREA"], shot_zone_range: data["SHOT_ZONE_RANGE"],
|
|
98
|
+
fga: data["FGA"], fgm: data["FGM"], fg_pct: data["FG_PCT"]}
|
|
99
|
+
end
|
|
100
|
+
private_class_method :shot_stat_attributes
|
|
101
|
+
end
|
|
102
|
+
end
|