#!/usr/bin/env python3 """ Major Domo CLI - Admin Operations Privileged operations: standings recalculation, stats refresh. Each command performs the write operation then fetches fresh data to confirm. """ from typing import Annotated, Optional import typer from cli_common import ( console, state, output_json, output_table, handle_error, get_season, safe_nested, ) from cli_stats import _format_rate_stat, _outs_to_ip admin_app = typer.Typer(help="Admin operations (standings recalculate, stats refresh)") def _show_standings(season: int): """Fetch and display standings after recalculation""" standings_data = state.api.get_standings(season=season) if not standings_data: console.print(f"[yellow]No standings found for season {season}[/yellow]") return rows = [] for s in standings_data: abbrev = safe_nested(s, "team", "abbrev") name = safe_nested(s, "team", "lname") wins = s.get("wins", 0) losses = s.get("losses", 0) total = wins + losses pct = f".{int(wins / total * 1000):03d}" if total > 0 else ".000" rd = s.get("run_diff", 0) rd_str = f"+{rd}" if rd > 0 else str(rd) rows.append([abbrev, name, wins, losses, pct, rd_str]) output_table( f"Standings - Season {season} (Recalculated)", ["Team", "Name", "W", "L", "PCT", "RD"], rows, ) def _show_batting_preview(season: int, count: int): """Fetch and display top 5 batting leaders after refresh""" stats = state.api.get_season_batting_stats( season=season, sort_by="woba", sort_order="desc", limit=5, min_pa=50 ) if not stats: return rows = [] for rank, s in enumerate(stats, 1): rows.append( [ rank, s.get("name", "N/A"), s.get("player_team_abbrev", "N/A"), s.get("pa", 0), _format_rate_stat(s.get("avg")), _format_rate_stat(s.get("obp")), _format_rate_stat(s.get("slg")), _format_rate_stat(s.get("woba")), ] ) console.print() output_table( f"Top 5 by wOBA (min 50 PA) - {count} players refreshed", ["#", "Name", "Team", "PA", "AVG", "OBP", "SLG", "wOBA"], rows, ) def _show_pitching_preview(season: int, count: int): """Fetch and display top 5 pitching leaders after refresh""" stats = state.api.get_season_pitching_stats( season=season, sort_by="era", sort_order="asc", limit=5, min_outs=50 ) if not stats: return rows = [] for rank, s in enumerate(stats, 1): rows.append( [ rank, s.get("name", "N/A"), s.get("player_team_abbrev", "N/A"), _outs_to_ip(s.get("outs", 0)), _format_rate_stat(s.get("era"), decimals=2), _format_rate_stat(s.get("whip"), decimals=2), s.get("so", 0), ] ) console.print() output_table( f"Top 5 by ERA (min 50 outs) - {count} players refreshed", ["#", "Name", "Team", "IP", "ERA", "WHIP", "SO"], rows, ) @admin_app.command("recalculate-standings") def recalculate_standings( season: Annotated[ Optional[int], typer.Option("--season", "-s", help="Season number") ] = None, ): """Recalculate standings from game results for a season""" try: season = get_season(season) console.print( f"[yellow]Recalculating standings for season {season}...[/yellow]" ) result = state.api.recalculate_standings(season) console.print(f"[green]Done:[/green] {result}") if state.json_output: standings_data = state.api.get_standings(season=season) output_json( {"message": result, "season": season, "standings": standings_data} ) return _show_standings(season) except Exception as e: handle_error(e) @admin_app.command("refresh-batting") def refresh_batting( season: Annotated[ Optional[int], typer.Option("--season", "-s", help="Season number") ] = None, ): """Refresh season batting stats materialized view""" try: season = get_season(season) console.print( f"[yellow]Refreshing batting stats for season {season}...[/yellow]" ) result = state.api.refresh_batting_stats(season) players = result.get("players_updated", 0) console.print( f"[green]Done:[/green] {result.get('message', 'Batting stats refreshed')} " f"({players} players updated)" ) if state.json_output: top_stats = state.api.get_season_batting_stats( season=season, sort_by="woba", sort_order="desc", limit=5, min_pa=50 ) result["top_5_woba"] = top_stats output_json(result) return _show_batting_preview(season, players) except Exception as e: handle_error(e) @admin_app.command("refresh-pitching") def refresh_pitching( season: Annotated[ Optional[int], typer.Option("--season", "-s", help="Season number") ] = None, ): """Refresh season pitching stats materialized view""" try: season = get_season(season) console.print( f"[yellow]Refreshing pitching stats for season {season}...[/yellow]" ) result = state.api.refresh_pitching_stats(season) players = result.get("players_updated", 0) console.print( f"[green]Done:[/green] {result.get('message', 'Pitching stats refreshed')} " f"({players} players updated)" ) if state.json_output: top_stats = state.api.get_season_pitching_stats( season=season, sort_by="era", sort_order="asc", limit=5, min_outs=50 ) result["top_5_era"] = top_stats output_json(result) return _show_pitching_preview(season, players) except Exception as e: handle_error(e) @admin_app.command("refresh-stats") def refresh_stats( season: Annotated[ Optional[int], typer.Option("--season", "-s", help="Season number") ] = None, ): """Refresh both batting and pitching stats materialized views""" try: season = get_season(season) console.print(f"[yellow]Refreshing all stats for season {season}...[/yellow]") batting_result = state.api.refresh_batting_stats(season) bat_count = batting_result.get("players_updated", 0) console.print(f" Batting: {bat_count} players refreshed") pitching_result = state.api.refresh_pitching_stats(season) pit_count = pitching_result.get("players_updated", 0) console.print(f" Pitching: {pit_count} players refreshed") console.print(f"[green]Done![/green]") if state.json_output: top_batting = state.api.get_season_batting_stats( season=season, sort_by="woba", sort_order="desc", limit=5, min_pa=50 ) top_pitching = state.api.get_season_pitching_stats( season=season, sort_by="era", sort_order="asc", limit=5, min_outs=50 ) output_json( { "batting": {**batting_result, "top_5_woba": top_batting}, "pitching": {**pitching_result, "top_5_era": top_pitching}, } ) return _show_batting_preview(season, bat_count) _show_pitching_preview(season, pit_count) except Exception as e: handle_error(e)