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.
Files changed (321) hide show
  1. checksums.yaml +5 -5
  2. data/AGENTS.md +362 -0
  3. data/CHANGELOG.md +169 -0
  4. data/CLAUDE.md +1 -0
  5. data/LICENSE +21 -0
  6. data/README.md +501 -101
  7. data/bin/console +10 -0
  8. data/bin/setup +6 -0
  9. data/exe/nba +8 -0
  10. data/lib/nba/all_time_leader.rb +77 -0
  11. data/lib/nba/all_time_leaders.rb +185 -0
  12. data/lib/nba/assist_leader.rb +92 -0
  13. data/lib/nba/assist_leaders.rb +64 -0
  14. data/lib/nba/assist_tracker.rb +108 -0
  15. data/lib/nba/assist_tracker_entry.rb +206 -0
  16. data/lib/nba/award.rb +128 -0
  17. data/lib/nba/box_score.rb +2 -0
  18. data/lib/nba/box_score_advanced.rb +114 -0
  19. data/lib/nba/box_score_advanced_player_stat.rb +297 -0
  20. data/lib/nba/box_score_advanced_team_stat.rb +237 -0
  21. data/lib/nba/box_score_advanced_v3.rb +124 -0
  22. data/lib/nba/box_score_defensive_player_stat.rb +281 -0
  23. data/lib/nba/box_score_defensive_team_stat.rb +85 -0
  24. data/lib/nba/box_score_defensive_v2.rb +190 -0
  25. data/lib/nba/box_score_four_factors.rb +91 -0
  26. data/lib/nba/box_score_four_factors_player_stat.rb +185 -0
  27. data/lib/nba/box_score_four_factors_team_stat.rb +141 -0
  28. data/lib/nba/box_score_four_factors_v3.rb +133 -0
  29. data/lib/nba/box_score_hustle.rb +226 -0
  30. data/lib/nba/box_score_hustle_player_stat.rb +233 -0
  31. data/lib/nba/box_score_hustle_team_stat.rb +189 -0
  32. data/lib/nba/box_score_matchup_stat.rb +417 -0
  33. data/lib/nba/box_score_matchups_v3.rb +184 -0
  34. data/lib/nba/box_score_misc.rb +100 -0
  35. data/lib/nba/box_score_misc_player_stat.rb +217 -0
  36. data/lib/nba/box_score_misc_team_stat.rb +173 -0
  37. data/lib/nba/box_score_misc_v3.rb +163 -0
  38. data/lib/nba/box_score_player_stat.rb +273 -0
  39. data/lib/nba/box_score_player_track.rb +223 -0
  40. data/lib/nba/box_score_player_track_stat.rb +273 -0
  41. data/lib/nba/box_score_player_track_team_stat.rb +229 -0
  42. data/lib/nba/box_score_scoring.rb +103 -0
  43. data/lib/nba/box_score_scoring_player_stat.rb +241 -0
  44. data/lib/nba/box_score_scoring_team_stat.rb +197 -0
  45. data/lib/nba/box_score_scoring_v3.rb +170 -0
  46. data/lib/nba/box_score_similarity_score.rb +119 -0
  47. data/lib/nba/box_score_similarity_stat.rb +76 -0
  48. data/lib/nba/box_score_starter_bench_stat.rb +257 -0
  49. data/lib/nba/box_score_summary.rb +285 -0
  50. data/lib/nba/box_score_summary_v2.rb +202 -0
  51. data/lib/nba/box_score_summary_v3.rb +120 -0
  52. data/lib/nba/box_score_summary_v3_data.rb +419 -0
  53. data/lib/nba/box_score_team_stat.rb +229 -0
  54. data/lib/nba/box_score_traditional.rb +101 -0
  55. data/lib/nba/box_score_traditional_v3.rb +195 -0
  56. data/lib/nba/box_score_usage.rb +102 -0
  57. data/lib/nba/box_score_usage_player_stat.rb +265 -0
  58. data/lib/nba/box_score_usage_team_stat.rb +221 -0
  59. data/lib/nba/box_score_usage_v3.rb +169 -0
  60. data/lib/nba/box_score_v3_helpers.rb +144 -0
  61. data/lib/nba/career_stats.rb +217 -0
  62. data/lib/nba/cli/display/player_display.rb +98 -0
  63. data/lib/nba/cli/display.rb +178 -0
  64. data/lib/nba/cli/formatters/game_formatters.rb +86 -0
  65. data/lib/nba/cli/formatters/leaders_formatters.rb +26 -0
  66. data/lib/nba/cli/formatters/player_formatters.rb +52 -0
  67. data/lib/nba/cli/formatters/standings_formatters.rb +26 -0
  68. data/lib/nba/cli/formatters/team_formatters.rb +67 -0
  69. data/lib/nba/cli/formatters/time_formatters.rb +82 -0
  70. data/lib/nba/cli/formatters.rb +56 -0
  71. data/lib/nba/cli/helpers.rb +135 -0
  72. data/lib/nba/cli.rb +171 -20
  73. data/lib/nba/client.rb +35 -0
  74. data/lib/nba/collection.rb +89 -0
  75. data/lib/nba/college_player_stat.rb +200 -0
  76. data/lib/nba/common_player_info.rb +142 -0
  77. data/lib/nba/common_playoff_series.rb +90 -0
  78. data/lib/nba/common_team_years.rb +113 -0
  79. data/lib/nba/conference.rb +39 -0
  80. data/lib/nba/connection.rb +84 -0
  81. data/lib/nba/cume_stats_player.rb +358 -0
  82. data/lib/nba/cume_stats_player_game.rb +217 -0
  83. data/lib/nba/cume_stats_player_games.rb +99 -0
  84. data/lib/nba/cume_stats_player_games_entry.rb +25 -0
  85. data/lib/nba/cume_stats_player_total.rb +481 -0
  86. data/lib/nba/cume_stats_team.rb +349 -0
  87. data/lib/nba/cume_stats_team_games.rb +145 -0
  88. data/lib/nba/cume_stats_team_games_entry.rb +25 -0
  89. data/lib/nba/cume_stats_team_player.rb +485 -0
  90. data/lib/nba/cume_stats_team_total.rb +267 -0
  91. data/lib/nba/data.rb +73 -0
  92. data/lib/nba/defense_hub.rb +109 -0
  93. data/lib/nba/defense_hub_stat.rb +57 -0
  94. data/lib/nba/defensive_shot_stat.rb +102 -0
  95. data/lib/nba/division.rb +49 -0
  96. data/lib/nba/draft_board.rb +126 -0
  97. data/lib/nba/draft_board_pick.rb +173 -0
  98. data/lib/nba/draft_combine_anthro_measurement.rb +163 -0
  99. data/lib/nba/draft_combine_drill_result.rb +115 -0
  100. data/lib/nba/draft_combine_drill_results.rb +112 -0
  101. data/lib/nba/draft_combine_non_stationary_shooting.rb +268 -0
  102. data/lib/nba/draft_combine_non_stationary_shooting_result.rb +355 -0
  103. data/lib/nba/draft_combine_player_anthro.rb +133 -0
  104. data/lib/nba/draft_combine_spot_shooting.rb +243 -0
  105. data/lib/nba/draft_combine_spot_shooting_result.rb +419 -0
  106. data/lib/nba/draft_combine_stat.rb +211 -0
  107. data/lib/nba/draft_combine_stats.rb +160 -0
  108. data/lib/nba/draft_history.rb +142 -0
  109. data/lib/nba/draft_pick.rb +154 -0
  110. data/lib/nba/dunk_score_leader.rb +93 -0
  111. data/lib/nba/dunk_score_leaders.rb +77 -0
  112. data/lib/nba/estimated_metrics_stat.rb +152 -0
  113. data/lib/nba/fantasy_profile_stat.rb +142 -0
  114. data/lib/nba/fantasy_widget.rb +72 -0
  115. data/lib/nba/fantasy_widget_player.rb +98 -0
  116. data/lib/nba/found_game.rb +260 -0
  117. data/lib/nba/franchise.rb +136 -0
  118. data/lib/nba/franchise_history.rb +142 -0
  119. data/lib/nba/franchise_leader.rb +147 -0
  120. data/lib/nba/franchise_leaders.rb +162 -0
  121. data/lib/nba/franchise_player.rb +224 -0
  122. data/lib/nba/franchise_players.rb +147 -0
  123. data/lib/nba/game.rb +80 -64
  124. data/lib/nba/game_log.rb +349 -0
  125. data/lib/nba/game_rotation.rb +152 -0
  126. data/lib/nba/game_streak.rb +102 -0
  127. data/lib/nba/games.rb +46 -0
  128. data/lib/nba/home_page_leader.rb +99 -0
  129. data/lib/nba/home_page_leaders.rb +75 -0
  130. data/lib/nba/home_page_stat.rb +57 -0
  131. data/lib/nba/home_page_v2.rb +110 -0
  132. data/lib/nba/hustle_stats_box_score.rb +182 -0
  133. data/lib/nba/infographic_fan_duel_player.rb +139 -0
  134. data/lib/nba/infographic_fan_duel_player_stat.rb +311 -0
  135. data/lib/nba/ist_standing.rb +167 -0
  136. data/lib/nba/ist_standings.rb +81 -0
  137. data/lib/nba/leader.rb +103 -0
  138. data/lib/nba/leaders.rb +110 -0
  139. data/lib/nba/leaders_tile.rb +57 -0
  140. data/lib/nba/leaders_tiles.rb +90 -0
  141. data/lib/nba/league.rb +37 -0
  142. data/lib/nba/league_dash_lineup_stat.rb +270 -0
  143. data/lib/nba/league_dash_lineups.rb +177 -0
  144. data/lib/nba/league_dash_opp_pt_shot.rb +150 -0
  145. data/lib/nba/league_dash_player_bio_stat.rb +217 -0
  146. data/lib/nba/league_dash_player_bio_stats.rb +164 -0
  147. data/lib/nba/league_dash_player_clutch.rb +212 -0
  148. data/lib/nba/league_dash_player_clutch_stat.rb +271 -0
  149. data/lib/nba/league_dash_player_pt_shot.rb +152 -0
  150. data/lib/nba/league_dash_player_pt_shot_stat.rb +193 -0
  151. data/lib/nba/league_dash_player_shot_location_stat.rb +265 -0
  152. data/lib/nba/league_dash_player_shot_locations.rb +210 -0
  153. data/lib/nba/league_dash_player_stat.rb +306 -0
  154. data/lib/nba/league_dash_player_stats.rb +176 -0
  155. data/lib/nba/league_dash_pt_defend.rb +160 -0
  156. data/lib/nba/league_dash_pt_defend_stat.rb +145 -0
  157. data/lib/nba/league_dash_pt_stats.rb +152 -0
  158. data/lib/nba/league_dash_pt_stats_stat.rb +169 -0
  159. data/lib/nba/league_dash_pt_team_defend.rb +158 -0
  160. data/lib/nba/league_dash_pt_team_defend_stat.rb +110 -0
  161. data/lib/nba/league_dash_team_clutch.rb +211 -0
  162. data/lib/nba/league_dash_team_clutch_stat.rb +237 -0
  163. data/lib/nba/league_dash_team_pt_shot.rb +150 -0
  164. data/lib/nba/league_dash_team_pt_shot_stat.rb +166 -0
  165. data/lib/nba/league_dash_team_shot_location_stat.rb +230 -0
  166. data/lib/nba/league_dash_team_shot_locations.rb +208 -0
  167. data/lib/nba/league_dash_team_stat.rb +275 -0
  168. data/lib/nba/league_dash_team_stats.rb +172 -0
  169. data/lib/nba/league_game_finder.rb +170 -0
  170. data/lib/nba/league_game_log.rb +224 -0
  171. data/lib/nba/league_hustle_stats_player.rb +161 -0
  172. data/lib/nba/league_hustle_stats_player_stat.rb +253 -0
  173. data/lib/nba/league_hustle_stats_team.rb +157 -0
  174. data/lib/nba/league_hustle_stats_team_stat.rb +179 -0
  175. data/lib/nba/league_lineup_viz.rb +184 -0
  176. data/lib/nba/league_lineup_viz_stat.rb +214 -0
  177. data/lib/nba/league_player_on_details.rb +175 -0
  178. data/lib/nba/league_player_on_details_stat.rb +313 -0
  179. data/lib/nba/league_season_matchup_stat.rb +241 -0
  180. data/lib/nba/league_season_matchups.rb +181 -0
  181. data/lib/nba/league_standing.rb +284 -0
  182. data/lib/nba/league_standings.rb +159 -0
  183. data/lib/nba/league_wide_shot_stat.rb +62 -0
  184. data/lib/nba/live_action.rb +240 -0
  185. data/lib/nba/live_box_score.rb +143 -0
  186. data/lib/nba/live_connection.rb +84 -0
  187. data/lib/nba/live_game.rb +230 -0
  188. data/lib/nba/live_play_by_play.rb +120 -0
  189. data/lib/nba/live_player_stat.rb +276 -0
  190. data/lib/nba/live_scoreboard.rb +102 -0
  191. data/lib/nba/matchup_rollup.rb +98 -0
  192. data/lib/nba/matchups_rollup.rb +81 -0
  193. data/lib/nba/pass_stat.rb +209 -0
  194. data/lib/nba/play.rb +258 -0
  195. data/lib/nba/play_by_play.rb +85 -0
  196. data/lib/nba/play_by_play_v3.rb +91 -0
  197. data/lib/nba/play_type_stat.rb +206 -0
  198. data/lib/nba/player.rb +242 -24
  199. data/lib/nba/player_awards.rb +110 -0
  200. data/lib/nba/player_career_by_college.rb +86 -0
  201. data/lib/nba/player_career_by_college_rollup.rb +143 -0
  202. data/lib/nba/player_career_stats.rb +77 -0
  203. data/lib/nba/player_compare.rb +156 -0
  204. data/lib/nba/player_comparison_stat.rb +242 -0
  205. data/lib/nba/player_dash_pt_pass.rb +164 -0
  206. data/lib/nba/player_dash_pt_reb.rb +235 -0
  207. data/lib/nba/player_dash_pt_shot_defend.rb +119 -0
  208. data/lib/nba/player_dash_pt_shots.rb +279 -0
  209. data/lib/nba/player_dashboard.rb +259 -0
  210. data/lib/nba/player_dashboard_stat.rb +248 -0
  211. data/lib/nba/player_estimated_metrics.rb +84 -0
  212. data/lib/nba/player_fantasy_profile_bar_graph.rb +147 -0
  213. data/lib/nba/player_game_log.rb +72 -0
  214. data/lib/nba/player_game_logs.rb +117 -0
  215. data/lib/nba/player_game_streak_finder.rb +108 -0
  216. data/lib/nba/player_index.rb +135 -0
  217. data/lib/nba/player_index_entry.rb +266 -0
  218. data/lib/nba/player_info.rb +225 -0
  219. data/lib/nba/player_next_n_games.rb +64 -0
  220. data/lib/nba/player_profile_v2.rb +169 -0
  221. data/lib/nba/player_vs_player.rb +153 -0
  222. data/lib/nba/players.rb +107 -0
  223. data/lib/nba/playoff_matchup.rb +84 -0
  224. data/lib/nba/playoff_picture.rb +98 -0
  225. data/lib/nba/playoff_series.rb +76 -0
  226. data/lib/nba/position.rb +48 -0
  227. data/lib/nba/rebound_stat.rb +189 -0
  228. data/lib/nba/response_parser.rb +116 -0
  229. data/lib/nba/roster.rb +74 -0
  230. data/lib/nba/rotation_entry.rb +154 -0
  231. data/lib/nba/schedule.rb +183 -0
  232. data/lib/nba/schedule_international.rb +182 -0
  233. data/lib/nba/scheduled_game.rb +240 -0
  234. data/lib/nba/scoreboard.rb +183 -0
  235. data/lib/nba/scoreboard_v3.rb +104 -0
  236. data/lib/nba/shot.rb +208 -0
  237. data/lib/nba/shot_chart.rb +75 -0
  238. data/lib/nba/shot_chart_league_wide.rb +102 -0
  239. data/lib/nba/shot_chart_lineup_detail.rb +109 -0
  240. data/lib/nba/shot_stat.rb +174 -0
  241. data/lib/nba/standing.rb +129 -0
  242. data/lib/nba/standings.rb +75 -0
  243. data/lib/nba/static.rb +107 -0
  244. data/lib/nba/synergy_play_types.rb +211 -0
  245. data/lib/nba/team.rb +203 -127
  246. data/lib/nba/team_and_players_vs_players.rb +227 -0
  247. data/lib/nba/team_and_players_vs_players_stat.rb +155 -0
  248. data/lib/nba/team_dash_pt_pass.rb +157 -0
  249. data/lib/nba/team_dash_pt_reb.rb +216 -0
  250. data/lib/nba/team_dash_pt_shots.rb +244 -0
  251. data/lib/nba/team_dashboard.rb +275 -0
  252. data/lib/nba/team_dashboard_stat.rb +248 -0
  253. data/lib/nba/team_detail.rb +117 -0
  254. data/lib/nba/team_details.rb +173 -0
  255. data/lib/nba/team_estimated_metrics.rb +91 -0
  256. data/lib/nba/team_estimated_metrics_stat.rb +146 -0
  257. data/lib/nba/team_game_log.rb +143 -0
  258. data/lib/nba/team_game_log_entry.rb +246 -0
  259. data/lib/nba/team_game_log_stat.rb +275 -0
  260. data/lib/nba/team_game_logs.rb +163 -0
  261. data/lib/nba/team_game_streak.rb +111 -0
  262. data/lib/nba/team_game_streak_finder.rb +109 -0
  263. data/lib/nba/team_historical_leader.rb +207 -0
  264. data/lib/nba/team_historical_leaders.rb +98 -0
  265. data/lib/nba/team_historical_record.rb +139 -0
  266. data/lib/nba/team_info.rb +150 -0
  267. data/lib/nba/team_info_common.rb +177 -0
  268. data/lib/nba/team_on_off_overall_stat.rb +477 -0
  269. data/lib/nba/team_on_off_player_stat.rb +523 -0
  270. data/lib/nba/team_on_off_player_summary.rb +135 -0
  271. data/lib/nba/team_pass_stat.rb +183 -0
  272. data/lib/nba/team_player_dashboard.rb +212 -0
  273. data/lib/nba/team_player_on_off_details.rb +218 -0
  274. data/lib/nba/team_player_on_off_summary.rb +214 -0
  275. data/lib/nba/team_player_stat.rb +275 -0
  276. data/lib/nba/team_rebound_stat.rb +189 -0
  277. data/lib/nba/team_season_rank.rb +110 -0
  278. data/lib/nba/team_shot_stat.rb +173 -0
  279. data/lib/nba/team_vs_player.rb +151 -0
  280. data/lib/nba/team_vs_player_stat.rb +157 -0
  281. data/lib/nba/team_year.rb +55 -0
  282. data/lib/nba/team_year_by_year_stats.rb +152 -0
  283. data/lib/nba/team_year_stat.rb +282 -0
  284. data/lib/nba/teams.rb +33 -0
  285. data/lib/nba/upcoming_game.rb +115 -0
  286. data/lib/nba/utils.rb +94 -0
  287. data/lib/nba/version.rb +5 -2
  288. data/lib/nba/video_detail.rb +103 -0
  289. data/lib/nba/video_details.rb +118 -0
  290. data/lib/nba/video_details_asset.rb +115 -0
  291. data/lib/nba/video_details_asset_entry.rb +91 -0
  292. data/lib/nba/video_event.rb +83 -0
  293. data/lib/nba/video_event_asset.rb +91 -0
  294. data/lib/nba/video_events.rb +106 -0
  295. data/lib/nba/video_events_asset.rb +107 -0
  296. data/lib/nba/video_status.rb +129 -0
  297. data/lib/nba/video_status_entry.rb +161 -0
  298. data/lib/nba/vs_player_stat.rb +156 -0
  299. data/lib/nba/win_probability.rb +117 -0
  300. data/lib/nba/win_probability_point.rb +140 -0
  301. data/lib/nba.rb +249 -5
  302. data/sig/equalizer.rbs +3 -0
  303. data/sig/nba.rbs +7297 -0
  304. data/sig/shale.rbs +24 -0
  305. data/sig/thor.rbs +19 -0
  306. metadata +324 -95
  307. data/.gitignore +0 -18
  308. data/.travis.yml +0 -22
  309. data/Gemfile +0 -23
  310. data/LICENSE.md +0 -22
  311. data/Rakefile +0 -18
  312. data/bin/nba +0 -7
  313. data/cache/teams.json +0 -16529
  314. data/lib/faraday_middleware/scrape_game.rb +0 -41
  315. data/lib/nba/request.rb +0 -37
  316. data/nba.gemspec +0 -28
  317. data/spec/fixtures/games.html +0 -785
  318. data/spec/fixtures/teams.json +0 -16529
  319. data/spec/game_spec.rb +0 -40
  320. data/spec/spec_helper.rb +0 -25
  321. 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