Created
December 21, 2025 12:20
-
-
Save dword4/47e0d089fd40a8455822e9fd448fa9de to your computer and use it in GitHub Desktop.
Checks out a teams season shootout stats using the NHL API
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import requests | |
| import json | |
| from collections import defaultdict | |
| from datetime import datetime | |
| def get_team_schedule(team_abbrev, season): | |
| """ | |
| Get all games for a team in a season | |
| Args: | |
| team_abbrev: Team abbreviation (e.g., 'WSH', 'CAR') | |
| season: Season year (e.g., '20252026') | |
| Returns: | |
| list of games | |
| """ | |
| url = f"https://api-web.nhle.com/v1/club-schedule-season/{team_abbrev}/{season}" | |
| try: | |
| response = requests.get(url) | |
| response.raise_for_status() | |
| data = response.json() | |
| return data.get('games', []) | |
| except Exception as e: | |
| print(f"Error fetching schedule: {e}") | |
| return [] | |
| def check_if_shootout(game_id): | |
| """ | |
| Check if a game went to shootout by examining the boxscore | |
| Args: | |
| game_id: NHL game ID | |
| Returns: | |
| True if game went to shootout, False otherwise | |
| """ | |
| url = f"https://api-web.nhle.com/v1/gamecenter/{game_id}/boxscore" | |
| try: | |
| response = requests.get(url) | |
| response.raise_for_status() | |
| data = response.json() | |
| game_outcome = data.get('gameOutcome', {}) | |
| return game_outcome.get('lastPeriodType') == 'SO' | |
| except Exception as e: | |
| print(f"Error checking game {game_id}: {e}") | |
| return False | |
| def get_shootout_details(game_id): | |
| """ | |
| Fetch detailed shootout results from play-by-play data | |
| Args: | |
| game_id: NHL game ID | |
| Returns: | |
| dict with shootout attempts | |
| """ | |
| url = f"https://api-web.nhle.com/v1/gamecenter/{game_id}/play-by-play" | |
| try: | |
| response = requests.get(url) | |
| response.raise_for_status() | |
| data = response.json() | |
| # Check if shootout occurred | |
| if not data.get('shootoutInUse', False): | |
| return None | |
| # Filter plays for shootout period | |
| shootout_plays = [ | |
| play for play in data.get('plays', []) | |
| if play.get('periodDescriptor', {}).get('periodType') == 'SO' | |
| ] | |
| results = [] | |
| for play in shootout_plays: | |
| type_code = play.get('typeCode') | |
| if type_code in [505, 506, 507]: # goal, shot-on-goal, or missed shot | |
| details = play.get('details', {}) | |
| # Goals use 'scoringPlayerId', shots/misses use 'shootingPlayerId' | |
| shooter_id = details.get('scoringPlayerId') or details.get('shootingPlayerId') | |
| if not shooter_id: | |
| continue | |
| # Determine result based on type code | |
| is_goal = type_code == 505 | |
| is_save = type_code == 506 | |
| is_miss = type_code == 507 | |
| result = { | |
| 'player_id': shooter_id, | |
| 'team_id': details.get('eventOwnerTeamId'), | |
| 'shot_type': details.get('shotType', 'unknown'), | |
| 'goal': is_goal, | |
| 'saved': is_save, | |
| 'missed': is_miss, | |
| 'game_id': game_id | |
| } | |
| results.append(result) | |
| # Get roster info for player names | |
| roster = {} | |
| for player in data.get('rosterSpots', []): | |
| roster[player['playerId']] = { | |
| 'first_name': player['firstName']['default'], | |
| 'last_name': player['lastName']['default'], | |
| 'number': player['sweaterNumber'], | |
| 'team_id': player['teamId'] | |
| } | |
| # Add player info to results | |
| for attempt in results: | |
| player_info = roster.get(attempt['player_id'], {}) | |
| attempt['player_name'] = f"{player_info.get('first_name', 'Unknown')} {player_info.get('last_name', 'Unknown')}" | |
| attempt['player_number'] = player_info.get('number', 'N/A') | |
| return { | |
| 'game_id': game_id, | |
| 'game_date': data.get('gameDate'), | |
| 'away_team': data['awayTeam']['abbrev'], | |
| 'home_team': data['homeTeam']['abbrev'], | |
| 'attempts': results | |
| } | |
| except Exception as e: | |
| print(f"Error fetching shootout details for game {game_id}: {e}") | |
| return None | |
| def analyze_team_shootout_season(team_abbrev, season='20252026'): | |
| """ | |
| Analyze all shootout games for a team in a season | |
| Args: | |
| team_abbrev: Team abbreviation (e.g., 'WSH', 'CAR') | |
| season: Season year (default: '20252026') | |
| Returns: | |
| dict with player statistics | |
| """ | |
| print(f"\nAnalyzing {team_abbrev} shootout performance for {season} season...") | |
| print("=" * 80) | |
| # Get team schedule | |
| games = get_team_schedule(team_abbrev, season) | |
| print(f"Found {len(games)} games in schedule") | |
| # Filter for completed games only | |
| completed_games = [g for g in games if g.get('gameState') in ['OFF', 'FINAL']] | |
| print(f"Found {len(completed_games)} completed games") | |
| # Find shootout games | |
| shootout_games = [] | |
| print("\nChecking for shootout games...") | |
| for game in completed_games: | |
| game_id = game.get('id') | |
| if check_if_shootout(game_id): | |
| print(f" ✓ Game {game_id} went to shootout") | |
| shootout_games.append(game_id) | |
| print(f"\nFound {len(shootout_games)} shootout games") | |
| if not shootout_games: | |
| print("No shootout games found for this team this season.") | |
| return None | |
| # Collect shootout data | |
| all_attempts = [] | |
| player_stats = defaultdict(lambda: { | |
| 'shots': 0, | |
| 'goals': 0, | |
| 'saves': 0, | |
| 'misses': 0, | |
| 'player_name': '', | |
| 'player_number': '', | |
| 'team_id': None, | |
| 'games': [] | |
| }) | |
| print("\nFetching detailed shootout data...") | |
| for game_id in shootout_games: | |
| details = get_shootout_details(game_id) | |
| if details: | |
| print(f" ✓ Processed game {game_id}") | |
| for attempt in details['attempts']: | |
| all_attempts.append(attempt) | |
| player_id = attempt['player_id'] | |
| stats = player_stats[player_id] | |
| # Update player info | |
| if not stats['player_name']: | |
| stats['player_name'] = attempt['player_name'] | |
| stats['player_number'] = attempt['player_number'] | |
| stats['team_id'] = attempt['team_id'] | |
| # Update statistics | |
| stats['shots'] += 1 | |
| if attempt['goal']: | |
| stats['goals'] += 1 | |
| elif attempt['saved']: | |
| stats['saves'] += 1 | |
| elif attempt['missed']: | |
| stats['misses'] += 1 | |
| stats['games'].append(game_id) | |
| return { | |
| 'team_abbrev': team_abbrev, | |
| 'season': season, | |
| 'shootout_games': shootout_games, | |
| 'player_stats': dict(player_stats), | |
| 'all_attempts': all_attempts | |
| } | |
| def print_shootout_stats_table(stats_data): | |
| """ | |
| Print a formatted table of shootout statistics | |
| Args: | |
| stats_data: Dictionary returned from analyze_team_shootout_season | |
| """ | |
| if not stats_data: | |
| return | |
| player_stats = stats_data['player_stats'] | |
| team_abbrev = stats_data['team_abbrev'] | |
| print(f"\n{'=' * 80}") | |
| print(f"{team_abbrev} SHOOTOUT STATISTICS - {stats_data['season']}") | |
| print(f"{'=' * 80}") | |
| print(f"Total Shootout Games: {len(stats_data['shootout_games'])}") | |
| print(f"\n{'-' * 80}") | |
| # Create header | |
| header = f"{'#':<4} {'Player':<25} {'Shots':<8} {'Goals':<8} {'Saved':<8} {'Missed':<8} {'SH%':<8} {'Games':<8}" | |
| print(header) | |
| print('-' * 80) | |
| # Sort players by goals (descending), then by shots (descending) | |
| sorted_players = sorted( | |
| player_stats.items(), | |
| key=lambda x: (x[1]['goals'], x[1]['shots']), | |
| reverse=True | |
| ) | |
| # Print each player's stats | |
| for player_id, stats in sorted_players: | |
| num = stats['player_number'] | |
| name = stats['player_name'] | |
| shots = stats['shots'] | |
| goals = stats['goals'] | |
| saves = stats['saves'] | |
| misses = stats['misses'] | |
| # Calculate shooting percentage | |
| sh_pct = (goals / shots * 100) if shots > 0 else 0 | |
| games_played = len(set(stats['games'])) | |
| row = f"{num:<4} {name:<25} {shots:<8} {goals:<8} {saves:<8} {misses:<8} {sh_pct:>6.1f}% {games_played:<8}" | |
| print(row) | |
| print('-' * 80) | |
| # Print totals | |
| total_shots = sum(s['shots'] for s in player_stats.values()) | |
| total_goals = sum(s['goals'] for s in player_stats.values()) | |
| total_saves = sum(s['saves'] for s in player_stats.values()) | |
| total_misses = sum(s['misses'] for s in player_stats.values()) | |
| total_sh_pct = (total_goals / total_shots * 100) if total_shots > 0 else 0 | |
| totals = f"{'TOTAL':<29} {total_shots:<8} {total_goals:<8} {total_saves:<8} {total_misses:<8} {total_sh_pct:>6.1f}%" | |
| print(totals) | |
| print('=' * 80) | |
| # Example usage | |
| if __name__ == "__main__": | |
| # Analyze Washington Capitals shootout performance | |
| team = "WSH" | |
| season = "20252026" | |
| stats = analyze_team_shootout_season(team, season) | |
| if stats: | |
| print_shootout_stats_table(stats) | |
| # You can also analyze for another team | |
| # print("\n\n") | |
| # car_stats = analyze_team_shootout_season("CAR", season) | |
| # if car_stats: | |
| # print_shootout_stats_table(car_stats) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment