Source code for nhl_scrabble.reports.stats_report

"""Fun statistics reporter."""

from __future__ import annotations

import heapq
import operator
from typing import Any

from nhl_scrabble.models.player import PlayerScore
from nhl_scrabble.models.standings import ConferenceStandings, DivisionStandings
from nhl_scrabble.reports.base import BaseReporter


[docs] class StatsReporter(BaseReporter): """Generate fun statistics and top players reports."""
[docs] def __init__(self, top_players_count: int = 20) -> None: """Initialize stats reporter. Args: top_players_count: Number of top players to show """ self.top_players_count = top_players_count
def _calculate_player_statistics(self, players: list[PlayerScore]) -> dict[str, Any]: """Calculate all player statistics in a single pass. Args: players: List of all players Returns: Dictionary with statistics: - top_first: Player with highest first name score - top_last: Player with highest last name score - avg_full: Average full name score - avg_first: Average first name score - avg_last: Average last name score - total_players: Total number of players Complexity: O(n) - single pass over all players """ if not players: return { "top_first": None, "top_last": None, "avg_full": 0.0, "avg_first": 0.0, "avg_last": 0.0, "total_players": 0, } # Initialize with first player top_first = top_last = players[0] total_full = total_first = total_last = 0 # Single pass - O(n) for player in players: # Track maximums if player.first_score > top_first.first_score: top_first = player if player.last_score > top_last.last_score: top_last = player # Accumulate totals for averages total_full += player.full_score total_first += player.first_score total_last += player.last_score # Calculate averages count = len(players) avg_full = total_full / count avg_first = total_first / count avg_last = total_last / count return { "top_first": top_first, "top_last": top_last, "avg_full": avg_full, "avg_first": avg_first, "avg_last": avg_last, "total_players": count, }
[docs] def generate( self, data: tuple[ list[PlayerScore], dict[str, DivisionStandings], dict[str, ConferenceStandings], ], ) -> str: """Generate statistics report. Args: data: Tuple containing (all_players, division_standings, conference_standings) Returns: Formatted statistics report string """ all_players, division_standings, conference_standings = data parts = [] # Top players overall parts.append( self._format_header( f"🌟 TOP {self.top_players_count} HIGHEST-SCORING PLAYERS (Across All Teams)", ), ) top_players = heapq.nlargest( self.top_players_count, all_players, key=lambda x: x.full_score, ) parts.extend( f"\n{rank:2}. {player.full_name:30} ({player.team:3}/{div_abbrev}): " f"{self._format_score(player.full_score, width=3)} points " f"[First: {self._format_score(player.first_score, width=2)}, " f"Last: {self._format_score(player.last_score, width=2)}]" for rank, player in enumerate(top_players, 1) for div_abbrev in (player.division.split()[0][:3].upper(),) ) # Fun stats parts.append(self._format_header("🎯 FUN STATS")) # Calculate all statistics in a single pass stats = self._calculate_player_statistics(all_players) # Highest scoring first name top_first = stats["top_first"] if top_first is not None: parts.append( f"\nHighest First Name: {top_first.first_name} " f"({top_first.full_name}, {top_first.team}) = " f"{self._format_score(top_first.first_score)} points", ) else: parts.append("\nHighest First Name: N/A (no players)") # Highest scoring last name top_last = stats["top_last"] if top_last is not None: parts.append( f"\nHighest Last Name: {top_last.last_name} " f"({top_last.full_name}, {top_last.team}) = " f"{self._format_score(top_last.last_score)} points", ) else: parts.append("\nHighest Last Name: N/A (no players)") # Average scores overall parts.extend( [ "\n\nLeague-Wide Average Scores:", f"\n Full Name: {self._format_average(stats['avg_full'], width=5)}", f"\n First Name: {self._format_average(stats['avg_first'], width=5)}", f"\n Last Name: {self._format_average(stats['avg_last'], width=5)}", ], ) # Division with highest average per player division_avg_per_player = { div: data.total / data.player_count if data.player_count else 0.0 for div, data in division_standings.items() } if division_avg_per_player: top_division = max(division_avg_per_player.items(), key=operator.itemgetter(1)) parts.append( f"\n\nHighest Avg Division (per player): " f"{top_division[0]} = {self._format_average(top_division[1])} points/player", ) # Conference with highest average per player conference_avg_per_player = { conf: data.total / data.player_count if data.player_count else 0.0 for conf, data in conference_standings.items() } if conference_avg_per_player: top_conference = max(conference_avg_per_player.items(), key=operator.itemgetter(1)) parts.append( f"\nHighest Avg Conference (per player): " f"{top_conference[0]} = {self._format_average(top_conference[1])} points/player", ) return "".join(parts)