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,159 @@
1
+ require "json"
2
+ require_relative "client"
3
+ require_relative "collection"
4
+ require_relative "utils"
5
+
6
+ require_relative "league_standing"
7
+
8
+ module NBA
9
+ # Provides methods to retrieve league standings with extended data
10
+ module LeagueStandings
11
+ # Result set name for standings
12
+ # @return [String] the result set name
13
+ STANDINGS = "Standings".freeze
14
+
15
+ # Season type constant for regular season
16
+ # @return [String] the season type
17
+ REGULAR_SEASON = "Regular Season".freeze
18
+
19
+ # Retrieves all league standings
20
+ #
21
+ # @api public
22
+ # @example
23
+ # standings = NBA::LeagueStandings.all(season: 2024)
24
+ # standings.each { |s| puts "#{s.playoff_rank}. #{s.full_name}: #{s.wins}-#{s.losses}" }
25
+ # @param season [Integer] the season year
26
+ # @param season_type [String] the season type
27
+ # @param league [String, League] the league ID or League object (default NBA)
28
+ # @param client [Client] the API client to use
29
+ # @return [Collection] a collection of standings
30
+ def self.all(season: Utils.current_season, season_type: REGULAR_SEASON, league: League::NBA, client: CLIENT)
31
+ league_id = Utils.extract_league_id(league)
32
+ path = build_path(season, season_type, league_id)
33
+ response = client.get(path)
34
+ parse_response(response)
35
+ end
36
+
37
+ # Retrieves standings for a specific conference
38
+ #
39
+ # @api public
40
+ # @example
41
+ # west = NBA::LeagueStandings.conference("West", season: 2024)
42
+ # west.each { |s| puts "#{s.playoff_rank}. #{s.full_name}" }
43
+ # @param conference_name [String] the conference name ("East" or "West")
44
+ # @param season [Integer] the season year
45
+ # @param season_type [String] the season type
46
+ # @param league [String, League] the league ID or League object (default NBA)
47
+ # @param client [Client] the API client to use
48
+ # @return [Collection] a collection of standings
49
+ def self.conference(conference_name, season: Utils.current_season, season_type: REGULAR_SEASON, league: League::NBA,
50
+ client: CLIENT)
51
+ all_standings = all(season: season, season_type: season_type, league: league, client: client)
52
+ filtered = all_standings.select { |s| s.conference.eql?(conference_name) }
53
+ Collection.new(filtered)
54
+ end
55
+
56
+ # Builds the API request path
57
+ # @api private
58
+ # @return [String] the request path
59
+ def self.build_path(season, season_type, league_id)
60
+ season_str = Utils.format_season(season)
61
+ encoded_type = season_type
62
+ "leaguestandingsv3?LeagueID=#{league_id}&Season=#{season_str}&SeasonType=#{encoded_type}"
63
+ end
64
+ private_class_method :build_path
65
+
66
+ # Parses the API response into standing objects
67
+ # @api private
68
+ # @return [Collection] collection of standings
69
+ def self.parse_response(response)
70
+ return Collection.new unless response
71
+
72
+ data = JSON.parse(response)
73
+ result_set = find_result_set(data)
74
+ return Collection.new unless result_set
75
+
76
+ headers = result_set["headers"]
77
+ rows = result_set["rowSet"]
78
+ return Collection.new unless headers && rows
79
+
80
+ standings = rows.map { |row| build_standing(headers, row) }
81
+ Collection.new(standings)
82
+ end
83
+ private_class_method :parse_response
84
+
85
+ # Finds the standings result set in the response
86
+ # @api private
87
+ # @return [Hash, nil] the result set hash
88
+ def self.find_result_set(data)
89
+ result_sets = data["resultSets"]
90
+ return unless result_sets
91
+
92
+ result_sets.find { |rs| rs["name"].eql?(STANDINGS) }
93
+ end
94
+ private_class_method :find_result_set
95
+
96
+ # Builds a LeagueStanding object from raw data
97
+ # @api private
98
+ # @return [LeagueStanding] the standing object
99
+ def self.build_standing(headers, row)
100
+ data = headers.zip(row).to_h
101
+ LeagueStanding.new(**standing_attributes(data))
102
+ end
103
+ private_class_method :build_standing
104
+
105
+ # Combines all standing attributes
106
+ # @api private
107
+ # @return [Hash] the combined attributes
108
+ def self.standing_attributes(data)
109
+ identity_attributes(data).merge(record_attributes(data), streak_attributes(data), points_attributes(data))
110
+ end
111
+ private_class_method :standing_attributes
112
+
113
+ # Extracts identity attributes from data
114
+ # @api private
115
+ # @return [Hash] identity attributes
116
+ def self.identity_attributes(data)
117
+ {league_id: data.fetch("LeagueID"), season_id: data.fetch("SeasonID"),
118
+ team_id: data.fetch("TeamID"), team_city: data.fetch("TeamCity"),
119
+ team_name: data.fetch("TeamName"), team_slug: data.fetch("TeamSlug"),
120
+ conference: data.fetch("Conference"), division: data.fetch("Division"),
121
+ clinch_indicator: data.fetch("ClinchIndicator")}
122
+ end
123
+ private_class_method :identity_attributes
124
+
125
+ # Extracts record attributes from data
126
+ # @api private
127
+ # @return [Hash] record attributes
128
+ def self.record_attributes(data)
129
+ {conference_record: data.fetch("ConferenceRecord"), playoff_rank: data.fetch("PlayoffRank"),
130
+ division_record: data.fetch("DivisionRecord"), division_rank: data.fetch("DivisionRank"),
131
+ wins: data.fetch("WINS"), losses: data.fetch("LOSSES"), win_pct: data.fetch("WinPCT"),
132
+ league_rank: data.fetch("LeagueRank"), record: data.fetch("Record"),
133
+ home_record: data.fetch("HOME"), road_record: data.fetch("ROAD"),
134
+ conference_games_back: data.fetch("ConferenceGamesBack")}
135
+ end
136
+ private_class_method :record_attributes
137
+
138
+ # Extracts streak attributes from data
139
+ # @api private
140
+ # @return [Hash] streak attributes
141
+ def self.streak_attributes(data)
142
+ {l10_record: data.fetch("L10"), long_win_streak: data.fetch("LongWinStreak"),
143
+ long_loss_streak: data.fetch("LongLossStreak"), current_streak: data.fetch("CurrentStreak"),
144
+ clinched_conference_title: data.fetch("ClinchedConferenceTitle"),
145
+ clinched_playoff_birth: data.fetch("ClinchedPlayoffBirth"),
146
+ eliminated_conference: data.fetch("EliminatedConference")}
147
+ end
148
+ private_class_method :streak_attributes
149
+
150
+ # Extracts points attributes from data
151
+ # @api private
152
+ # @return [Hash] points attributes
153
+ def self.points_attributes(data)
154
+ {points_pg: data.fetch("PointsPG"), opp_points_pg: data.fetch("OppPointsPG"),
155
+ diff_points_pg: data.fetch("DiffPointsPG")}
156
+ end
157
+ private_class_method :points_attributes
158
+ end
159
+ end
@@ -0,0 +1,62 @@
1
+ module NBA
2
+ # Represents league-wide shot data
3
+ class LeagueWideShotStat < Shale::Mapper
4
+ include Equalizer.new(:shot_zone_basic, :shot_zone_area, :shot_zone_range)
5
+
6
+ # @!attribute [rw] grid_type
7
+ # Returns the grid type
8
+ # @api public
9
+ # @example
10
+ # stat.grid_type #=> "Shot Zone Basic"
11
+ # @return [String] the grid type
12
+ attribute :grid_type, Shale::Type::String
13
+
14
+ # @!attribute [rw] shot_zone_basic
15
+ # Returns the basic shot zone
16
+ # @api public
17
+ # @example
18
+ # stat.shot_zone_basic #=> "Mid-Range"
19
+ # @return [String] the basic zone
20
+ attribute :shot_zone_basic, Shale::Type::String
21
+
22
+ # @!attribute [rw] shot_zone_area
23
+ # Returns the shot zone area
24
+ # @api public
25
+ # @example
26
+ # stat.shot_zone_area #=> "Left Side"
27
+ # @return [String] the zone area
28
+ attribute :shot_zone_area, Shale::Type::String
29
+
30
+ # @!attribute [rw] shot_zone_range
31
+ # Returns the shot zone range
32
+ # @api public
33
+ # @example
34
+ # stat.shot_zone_range #=> "16-24 ft."
35
+ # @return [String] the zone range
36
+ attribute :shot_zone_range, Shale::Type::String
37
+
38
+ # @!attribute [rw] fga
39
+ # Returns field goals attempted
40
+ # @api public
41
+ # @example
42
+ # stat.fga #=> 1500
43
+ # @return [Integer] field goals attempted
44
+ attribute :fga, Shale::Type::Integer
45
+
46
+ # @!attribute [rw] fgm
47
+ # Returns field goals made
48
+ # @api public
49
+ # @example
50
+ # stat.fgm #=> 650
51
+ # @return [Integer] field goals made
52
+ attribute :fgm, Shale::Type::Integer
53
+
54
+ # @!attribute [rw] fg_pct
55
+ # Returns field goal percentage
56
+ # @api public
57
+ # @example
58
+ # stat.fg_pct #=> 0.433
59
+ # @return [Float] field goal percentage
60
+ attribute :fg_pct, Shale::Type::Float
61
+ end
62
+ end
@@ -0,0 +1,240 @@
1
+ module NBA
2
+ # Represents a live play-by-play action
3
+ class LiveAction < Shale::Mapper
4
+ include Equalizer.new(:action_number, :game_id)
5
+
6
+ # @!attribute [rw] game_id
7
+ # Returns the game ID
8
+ # @api public
9
+ # @example
10
+ # action.game_id #=> "0022400001"
11
+ # @return [String] the game ID
12
+ attribute :game_id, Shale::Type::String
13
+
14
+ # @!attribute [rw] action_number
15
+ # Returns the action number
16
+ # @api public
17
+ # @example
18
+ # action.action_number #=> 1
19
+ # @return [Integer] the action number
20
+ attribute :action_number, Shale::Type::Integer
21
+
22
+ # @!attribute [rw] clock
23
+ # Returns the game clock at time of action
24
+ # @api public
25
+ # @example
26
+ # action.clock #=> "PT11M47S"
27
+ # @return [String] the clock in ISO 8601 duration format
28
+ attribute :clock, Shale::Type::String
29
+
30
+ # @!attribute [rw] time_actual
31
+ # Returns the actual time of the action
32
+ # @api public
33
+ # @example
34
+ # action.time_actual #=> "2024-10-22T19:10:00Z"
35
+ # @return [String] the actual time in UTC
36
+ attribute :time_actual, Shale::Type::String
37
+
38
+ # @!attribute [rw] period
39
+ # Returns the period
40
+ # @api public
41
+ # @example
42
+ # action.period #=> 1
43
+ # @return [Integer] the period number
44
+ attribute :period, Shale::Type::Integer
45
+
46
+ # @!attribute [rw] period_type
47
+ # Returns the period type
48
+ # @api public
49
+ # @example
50
+ # action.period_type #=> "REGULAR"
51
+ # @return [String] the period type (REGULAR, OVERTIME)
52
+ attribute :period_type, Shale::Type::String
53
+
54
+ # @!attribute [rw] action_type
55
+ # Returns the action type
56
+ # @api public
57
+ # @example
58
+ # action.action_type #=> "2pt"
59
+ # @return [String] the action type
60
+ attribute :action_type, Shale::Type::String
61
+
62
+ # @!attribute [rw] sub_type
63
+ # Returns the action subtype
64
+ # @api public
65
+ # @example
66
+ # action.sub_type #=> "LAYUP"
67
+ # @return [String] the action subtype
68
+ attribute :sub_type, Shale::Type::String
69
+
70
+ # @!attribute [rw] qualifier
71
+ # Returns any qualifier for the action
72
+ # @api public
73
+ # @example
74
+ # action.qualifiers #=> ["DRIVING"]
75
+ # @return [Array<String>] the qualifiers
76
+ attribute :qualifiers, Shale::Type::String, collection: true
77
+
78
+ # @!attribute [rw] description
79
+ # Returns the play description
80
+ # @api public
81
+ # @example
82
+ # action.description #=> "Curry Driving Layup"
83
+ # @return [String] the description
84
+ attribute :description, Shale::Type::String
85
+
86
+ # @!attribute [rw] player_id
87
+ # Returns the player ID involved in the action
88
+ # @api public
89
+ # @example
90
+ # action.player_id #=> 201939
91
+ # @return [Integer] the player ID
92
+ attribute :player_id, Shale::Type::Integer
93
+
94
+ # @!attribute [rw] player_name
95
+ # Returns the player name
96
+ # @api public
97
+ # @example
98
+ # action.player_name #=> "Stephen Curry"
99
+ # @return [String] the player name
100
+ attribute :player_name, Shale::Type::String
101
+
102
+ # @!attribute [rw] player_name_i
103
+ # Returns the player name in abbreviated format
104
+ # @api public
105
+ # @example
106
+ # action.player_name_i #=> "S. Curry"
107
+ # @return [String] the abbreviated player name
108
+ attribute :player_name_i, Shale::Type::String
109
+
110
+ # @!attribute [rw] team_id
111
+ # Returns the team ID
112
+ # @api public
113
+ # @example
114
+ # action.team_id #=> 1610612744
115
+ # @return [Integer] the team ID
116
+ attribute :team_id, Shale::Type::Integer
117
+
118
+ # @!attribute [rw] team_tricode
119
+ # Returns the team tricode
120
+ # @api public
121
+ # @example
122
+ # action.team_tricode #=> "GSW"
123
+ # @return [String] the team tricode
124
+ attribute :team_tricode, Shale::Type::String
125
+
126
+ # @!attribute [rw] score_home
127
+ # Returns the home team score after this action
128
+ # @api public
129
+ # @example
130
+ # action.score_home #=> "2"
131
+ # @return [String] the home score
132
+ attribute :score_home, Shale::Type::String
133
+
134
+ # @!attribute [rw] score_away
135
+ # Returns the away team score after this action
136
+ # @api public
137
+ # @example
138
+ # action.score_away #=> "0"
139
+ # @return [String] the away score
140
+ attribute :score_away, Shale::Type::String
141
+
142
+ # @!attribute [rw] points_total
143
+ # Returns the points from this action
144
+ # @api public
145
+ # @example
146
+ # action.points_total #=> 2
147
+ # @return [Integer] the points
148
+ attribute :points_total, Shale::Type::Integer
149
+
150
+ # @!attribute [rw] x_legacy
151
+ # Returns the x coordinate on the court
152
+ # @api public
153
+ # @example
154
+ # action.x_legacy #=> 5.0
155
+ # @return [Float] the x coordinate
156
+ attribute :x_legacy, Shale::Type::Float
157
+
158
+ # @!attribute [rw] y_legacy
159
+ # Returns the y coordinate on the court
160
+ # @api public
161
+ # @example
162
+ # action.y_legacy #=> 25.0
163
+ # @return [Float] the y coordinate
164
+ attribute :y_legacy, Shale::Type::Float
165
+
166
+ # @!attribute [rw] shot_distance
167
+ # Returns the shot distance
168
+ # @api public
169
+ # @example
170
+ # action.shot_distance #=> 2.5
171
+ # @return [Float] the shot distance in feet
172
+ attribute :shot_distance, Shale::Type::Float
173
+
174
+ # @!attribute [rw] is_field_goal
175
+ # Returns whether this is a field goal attempt
176
+ # @api public
177
+ # @example
178
+ # action.is_field_goal #=> 1
179
+ # @return [Integer] 1 if field goal, 0 otherwise
180
+ attribute :is_field_goal, Shale::Type::Integer
181
+
182
+ # @!attribute [rw] shot_result
183
+ # Returns the shot result
184
+ # @api public
185
+ # @example
186
+ # action.shot_result #=> "Made"
187
+ # @return [String] "Made" or "Missed"
188
+ attribute :shot_result, Shale::Type::String
189
+
190
+ # Returns the player object
191
+ #
192
+ # @api public
193
+ # @example
194
+ # action.player #=> #<NBA::Player>
195
+ # @return [Player, nil] the player object
196
+ def player
197
+ Players.find(player_id)
198
+ end
199
+
200
+ # Returns the team object
201
+ #
202
+ # @api public
203
+ # @example
204
+ # action.team #=> #<NBA::Team>
205
+ # @return [Team, nil] the team object
206
+ def team
207
+ Teams.find(team_id)
208
+ end
209
+
210
+ # Returns whether this is a field goal attempt
211
+ #
212
+ # @api public
213
+ # @example
214
+ # action.field_goal? #=> true
215
+ # @return [Boolean] true if field goal attempt
216
+ def field_goal?
217
+ is_field_goal.eql?(1)
218
+ end
219
+
220
+ # Returns whether this shot was made
221
+ #
222
+ # @api public
223
+ # @example
224
+ # action.made? #=> true
225
+ # @return [Boolean] true if shot was made
226
+ def made?
227
+ shot_result.eql?("Made")
228
+ end
229
+
230
+ # Returns whether this shot was missed
231
+ #
232
+ # @api public
233
+ # @example
234
+ # action.missed? #=> true
235
+ # @return [Boolean] true if shot was missed
236
+ def missed?
237
+ shot_result.eql?("Missed")
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,143 @@
1
+ require "json"
2
+ require_relative "collection"
3
+ require_relative "live_connection"
4
+ require_relative "utils"
5
+
6
+ require_relative "live_player_stat"
7
+
8
+ module NBA
9
+ # Provides methods to retrieve live box score data
10
+ module LiveBoxScore
11
+ # Retrieves live box score for a game
12
+ #
13
+ # @api public
14
+ # @example
15
+ # stats = NBA::LiveBoxScore.find(game: "0022400001")
16
+ # stats.each { |s| puts "#{s.name}: #{s.points} pts" }
17
+ # @param game [String, Game] the game ID or Game object
18
+ # @param client [LiveConnection] the API client to use
19
+ # @return [Collection] a collection of player stats
20
+ def self.find(game:, client: LIVE_CLIENT)
21
+ game_id = Utils.extract_id(game)
22
+ path = "boxscore/boxscore_#{game_id}.json"
23
+ response = client.get(path)
24
+ parse_response(response, game_id)
25
+ end
26
+
27
+ # Parses the live box score API response
28
+ #
29
+ # @api private
30
+ # @param response [String, nil] the JSON response body
31
+ # @param game_id [String] the game ID
32
+ # @return [Collection] a collection of player stats
33
+ def self.parse_response(response, game_id)
34
+ return Collection.new unless response
35
+
36
+ data = JSON.parse(response)
37
+ game_data = data["game"]
38
+ return Collection.new unless game_data
39
+
40
+ home_players = extract_players(game_data, "homeTeam", game_id)
41
+ away_players = extract_players(game_data, "awayTeam", game_id)
42
+ Collection.new(home_players + away_players)
43
+ end
44
+ private_class_method :parse_response
45
+
46
+ # Extracts players from a team's data
47
+ #
48
+ # @api private
49
+ # @param game_data [Hash] the game data
50
+ # @param team_key [String] the team key ("homeTeam" or "awayTeam")
51
+ # @param game_id [String] the game ID
52
+ # @return [Array<LivePlayerStat>] array of player stats
53
+ def self.extract_players(game_data, team_key, game_id)
54
+ team_data = game_data[team_key]
55
+ return [] unless team_data
56
+
57
+ players = team_data["players"]
58
+ return [] unless players
59
+
60
+ team_id = team_data["teamId"]
61
+ team_tricode = team_data["teamTricode"]
62
+
63
+ players.map { |p| build_player_stat(p, game_id, team_id, team_tricode) }
64
+ end
65
+ private_class_method :extract_players
66
+
67
+ # Builds a LivePlayerStat object from raw data
68
+ #
69
+ # @api private
70
+ # @param data [Hash] the player data
71
+ # @param game_id [String] the game ID
72
+ # @param team_id [Integer] the team ID
73
+ # @param team_tricode [String] the team tricode
74
+ # @return [LivePlayerStat] the player stat object
75
+ def self.build_player_stat(data, game_id, team_id, team_tricode)
76
+ LivePlayerStat.new(**player_stat_attributes(data, game_id, team_id, team_tricode))
77
+ end
78
+ private_class_method :build_player_stat
79
+
80
+ # Combines all player stat attributes
81
+ #
82
+ # @api private
83
+ # @param data [Hash] the player data
84
+ # @param game_id [String] the game ID
85
+ # @param team_id [Integer] the team ID
86
+ # @param team_tricode [String] the team tricode
87
+ # @return [Hash] the combined attributes
88
+ def self.player_stat_attributes(data, game_id, team_id, team_tricode)
89
+ identity_attributes(data, game_id, team_id, team_tricode)
90
+ .merge(counting_attributes(data))
91
+ .merge(shooting_attributes(data))
92
+ end
93
+ private_class_method :player_stat_attributes
94
+
95
+ # Extracts identity attributes from data
96
+ #
97
+ # @api private
98
+ # @param data [Hash] the player data
99
+ # @param game_id [String] the game ID
100
+ # @param team_id [Integer] the team ID
101
+ # @param team_tricode [String] the team tricode
102
+ # @return [Hash] identity attributes
103
+ def self.identity_attributes(data, game_id, team_id, team_tricode)
104
+ {game_id: game_id, player_id: data["personId"], name: data["name"],
105
+ first_name: data["firstName"], family_name: data["familyName"],
106
+ jersey_num: data["jerseyNum"], position: data["position"],
107
+ team_id: team_id, team_tricode: team_tricode, starter: data["starter"],
108
+ minutes: data.dig("statistics", "minutes")}
109
+ end
110
+ private_class_method :identity_attributes
111
+
112
+ # Extracts counting stats attributes from data
113
+ #
114
+ # @api private
115
+ # @param data [Hash] the player data
116
+ # @return [Hash] counting attributes
117
+ def self.counting_attributes(data)
118
+ stats = data["statistics"] || {}
119
+ {points: stats["points"], rebounds_total: stats["reboundsTotal"],
120
+ rebounds_offensive: stats["reboundsOffensive"], rebounds_defensive: stats["reboundsDefensive"],
121
+ assists: stats["assists"], steals: stats["steals"], blocks: stats["blocks"],
122
+ turnovers: stats["turnovers"], fouls_personal: stats["foulsPersonal"],
123
+ plus_minus: stats["plusMinusPoints"]}
124
+ end
125
+ private_class_method :counting_attributes
126
+
127
+ # Extracts shooting stats attributes from data
128
+ #
129
+ # @api private
130
+ # @param data [Hash] the player data
131
+ # @return [Hash] shooting attributes
132
+ def self.shooting_attributes(data)
133
+ stats = data["statistics"] || {}
134
+ {field_goals_made: stats["fieldGoalsMade"], field_goals_attempted: stats["fieldGoalsAttempted"],
135
+ field_goals_percentage: stats["fieldGoalsPercentage"],
136
+ three_pointers_made: stats["threePointersMade"], three_pointers_attempted: stats["threePointersAttempted"],
137
+ three_pointers_percentage: stats["threePointersPercentage"],
138
+ free_throws_made: stats["freeThrowsMade"], free_throws_attempted: stats["freeThrowsAttempted"],
139
+ free_throws_percentage: stats["freeThrowsPercentage"]}
140
+ end
141
+ private_class_method :shooting_attributes
142
+ end
143
+ end
@@ -0,0 +1,84 @@
1
+ require "net/http"
2
+ require "openssl"
3
+ require "stringio"
4
+ require "uri"
5
+ require "zlib"
6
+
7
+ module NBA
8
+ # Handles HTTP connections to the NBA Live Data API
9
+ class LiveConnection
10
+ # Default base URL for the NBA Live Data API
11
+ # @return [String] the default base URL
12
+ BASE_URL = "https://cdn.nba.com/static/json/liveData/".freeze
13
+
14
+ # Returns the base URL for API requests
15
+ #
16
+ # @api private
17
+ # @return [String] the base URL
18
+ attr_reader :base_url
19
+
20
+ # Initializes a new LiveConnection object
21
+ #
22
+ # @api public
23
+ # @example
24
+ # connection = NBA::LiveConnection.new
25
+ # @param base_url [String] the base URL for API requests
26
+ # @return [NBA::LiveConnection] a new live connection instance
27
+ def initialize(base_url: BASE_URL)
28
+ @base_url = base_url
29
+ end
30
+
31
+ # Makes a GET request to the specified path
32
+ #
33
+ # @api public
34
+ # @example
35
+ # connection.get("scoreboard/todaysScoreboard_00.json")
36
+ # @param path [String] the API path to request
37
+ # @return [String] the response body
38
+ def get(path)
39
+ uri = URI.join(base_url, path)
40
+ hostname = uri.hostname or raise ArgumentError, "Invalid URI: #{uri}"
41
+ request = Net::HTTP::Get.new(uri)
42
+ apply_headers(request)
43
+
44
+ Net::HTTP.start(hostname, uri.port, use_ssl: uri.scheme.eql?("https")) do |http|
45
+ response = http.request(request)
46
+ decode_body(response) if response.is_a?(Net::HTTPSuccess)
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ # Applies the required headers for NBA Live Data API requests
53
+ #
54
+ # @api private
55
+ # @param request [Net::HTTP::Get] the request object
56
+ # @return [void]
57
+ def apply_headers(request)
58
+ request["User-Agent"] = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " \
59
+ "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
60
+ request["Accept"] = "application/json, text/plain, */*"
61
+ request["Accept-Language"] = "en-US,en;q=0.9"
62
+ request["Accept-Encoding"] = "gzip, deflate, identity"
63
+ request["Connection"] = "keep-alive"
64
+ request["Referer"] = "https://www.nba.com/"
65
+ request["Origin"] = "https://www.nba.com"
66
+ end
67
+
68
+ # Decodes the response body based on Content-Encoding
69
+ #
70
+ # @api private
71
+ # @param response [Net::HTTPResponse] the HTTP response
72
+ # @return [String, nil] the decoded response body
73
+ def decode_body(response)
74
+ case response["Content-Encoding"]
75
+ when "gzip"
76
+ Zlib::GzipReader.new(StringIO.new(response.body)).read
77
+ when "deflate"
78
+ Zlib::Inflate.inflate(response.body)
79
+ else
80
+ response.body
81
+ end
82
+ end
83
+ end
84
+ end