Skip to content

Instantly share code, notes, and snippets.

@dword4
Created December 21, 2025 12:20
Show Gist options
  • Select an option

  • Save dword4/47e0d089fd40a8455822e9fd448fa9de to your computer and use it in GitHub Desktop.

Select an option

Save dword4/47e0d089fd40a8455822e9fd448fa9de to your computer and use it in GitHub Desktop.
Checks out a teams season shootout stats using the NHL API
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