Commit 052cd6852c
Verified · cmc
Layout: unified · split
build/lib/nba/box_score.py +32 −23
| @@ -10,7 +10,21 @@ from nba_api.live.nba.endpoints.boxscore import BoxScore | ||
| 10 | 10 | BOLD = "\033[1m" |
| 11 | 11 | END = "\033[0m" |
| 12 | 12 | |
| 13 | _HEADERS = ["Player", "Pos", "Min", "Pts", "Reb", "Ast", "Stl", "Blk", "TO", "FG", "3P", "FT", "+/-"] | |
| 13 | _HEADERS = [ | |
| 14 | "Player", | |
| 15 | "Pos", | |
| 16 | "Min", | |
| 17 | "Pts", | |
| 18 | "Reb", | |
| 19 | "Ast", | |
| 20 | "Stl", | |
| 21 | "Blk", | |
| 22 | "TO", | |
| 23 | "FG", | |
| 24 | "3P", | |
| 25 | "FT", | |
| 26 | "+/-", | |
| 27 | ] | |
| 14 | 28 | |
| 15 | 29 | |
| 16 | 30 | def fetch_box_score(game_id: str) -> dict: |
| @@ -36,21 +50,23 @@ def _player_rows(players: list) -> list: | ||
| 36 | 50 | if p.get("status") == "INACTIVE": |
| 37 | 51 | continue |
| 38 | 52 | s = p.get("statistics", {}) |
| 39 | rows.append([ | |
| 40 | p.get("name", ""), | |
| 41 | p.get("position", ""), | |
| 42 | _parse_minutes(s.get("minutes", "")), | |
| 43 | s.get("points", 0), | |
| 44 | s.get("reboundsTotal", 0), | |
| 45 | s.get("assists", 0), | |
| 46 | s.get("steals", 0), | |
| 47 | s.get("blocks", 0), | |
| 48 | s.get("turnovers", 0), | |
| 49 | f"{s.get('fieldGoalsMade', 0)}/{s.get('fieldGoalsAttempted', 0)}", | |
| 50 | f"{s.get('threePointersMade', 0)}/{s.get('threePointersAttempted', 0)}", | |
| 51 | f"{s.get('freeThrowsMade', 0)}/{s.get('freeThrowsAttempted', 0)}", | |
| 52 | s.get("plusMinusPoints", 0), | |
| 53 | ]) | |
| 53 | rows.append( | |
| 54 | [ | |
| 55 | p.get("name", ""), | |
| 56 | p.get("position", ""), | |
| 57 | _parse_minutes(s.get("minutes", "")), | |
| 58 | s.get("points", 0), | |
| 59 | s.get("reboundsTotal", 0), | |
| 60 | s.get("assists", 0), | |
| 61 | s.get("steals", 0), | |
| 62 | s.get("blocks", 0), | |
| 63 | s.get("turnovers", 0), | |
| 64 | f"{s.get('fieldGoalsMade', 0)}/{s.get('fieldGoalsAttempted', 0)}", | |
| 65 | f"{s.get('threePointersMade', 0)}/{s.get('threePointersAttempted', 0)}", | |
| 66 | f"{s.get('freeThrowsMade', 0)}/{s.get('freeThrowsAttempted', 0)}", | |
| 67 | s.get("plusMinusPoints", 0), | |
| 68 | ] | |
| 69 | ) | |
| 54 | 70 | return rows |
| 55 | 71 | |
| 56 | 72 | |
| @@ -60,13 +76,6 @@ def get_box_score_tables(data: dict) -> tuple: | ||
| 60 | 76 | home = game["homeTeam"] |
| 61 | 77 | away = game["awayTeam"] |
| 62 | 78 | |
| 63 | home_title = ( | |
| 64 | f"{BOLD}{home['teamCity']} {home['teamName']} " | |
| 65 | f"{home['score']} — {away['score']} " | |
| 66 | f"{away['teamCity']} {away['teamName']}{END} " | |
| 67 | f" [{game.get('gameStatusText', '')}]" | |
| 68 | ) | |
| 69 | ||
| 70 | 79 | home_rows = _player_rows(home.get("players", [])) |
| 71 | 80 | away_rows = _player_rows(away.get("players", [])) |
| 72 | 81 | |
build/lib/nba/cli.py +4 −4
| @@ -19,9 +19,7 @@ def nba() -> None: | ||
| 19 | 19 | parser.add_argument( |
| 20 | 20 | "--standings", "-st", action="store_true", help="Display the standings" |
| 21 | 21 | ) |
| 22 | parser.add_argument( | |
| 23 | "--tui", action="store_true", help="Launch the interactive TUI" | |
| 24 | ) | |
| 22 | parser.add_argument("--tui", action="store_true", help="Launch the interactive TUI") | |
| 25 | 23 | parser.add_argument( |
| 26 | 24 | "--refresh", |
| 27 | 25 | type=int, |
| @@ -47,4 +45,6 @@ def nba() -> None: | ||
| 47 | 45 | elif args.standings: |
| 48 | 46 | standings.build_standings(ranks) |
| 49 | 47 | else: |
| 50 | print("Please specify --scores or --standings (or use --tui for interactive mode)") | |
| 48 | print( | |
| 49 | "Please specify --scores or --standings (or use --tui for interactive mode)" | |
| 50 | ) | |
build/lib/nba/leaders.py +13 −13
| @@ -25,20 +25,23 @@ CATEGORIES = [ | ||
| 25 | 25 | |
| 26 | 26 | # Extra columns to show alongside RANK, PLAYER, TEAM, GP for each category |
| 27 | 27 | _EXTRA_COLS = { |
| 28 | "PTS": ["PTS", "FGM", "FGA", "FG_PCT", "FTM", "FTA", "FT_PCT"], | |
| 29 | "REB": ["REB", "OREB", "DREB", "GP"], | |
| 30 | "AST": ["AST", "TOV", "AST_TOV", "GP"], | |
| 31 | "STL": ["STL", "TOV", "GP"], | |
| 32 | "BLK": ["BLK", "PF", "GP"], | |
| 33 | "EFF": ["EFF", "PTS", "REB", "AST", "GP"], | |
| 28 | "PTS": ["PTS", "FGM", "FGA", "FG_PCT", "FTM", "FTA", "FT_PCT"], | |
| 29 | "REB": ["REB", "OREB", "DREB", "GP"], | |
| 30 | "AST": ["AST", "TOV", "AST_TOV", "GP"], | |
| 31 | "STL": ["STL", "TOV", "GP"], | |
| 32 | "BLK": ["BLK", "PF", "GP"], | |
| 33 | "EFF": ["EFF", "PTS", "REB", "AST", "GP"], | |
| 34 | 34 | "FG_PCT": ["FG_PCT", "FGM", "FGA", "PTS"], |
| 35 | 35 | "FT_PCT": ["FT_PCT", "FTM", "FTA", "PTS"], |
| 36 | "FG3_PCT":["FG3_PCT", "FG3M", "FG3A", "PTS"], | |
| 36 | "FG3_PCT": ["FG3_PCT", "FG3M", "FG3A", "PTS"], | |
| 37 | 37 | } |
| 38 | 38 | |
| 39 | 39 | _DISPLAY_NAMES = { |
| 40 | "FG_PCT": "FG%", "FT_PCT": "FT%", "FG3_PCT": "3P%", | |
| 41 | "FG3M": "3PM", "FG3A": "3PA", | |
| 40 | "FG_PCT": "FG%", | |
| 41 | "FT_PCT": "FT%", | |
| 42 | "FG3_PCT": "3P%", | |
| 43 | "FG3M": "3PM", | |
| 44 | "FG3A": "3PA", | |
| 42 | 45 | "AST_TOV": "AST/TO", |
| 43 | 46 | } |
| 44 | 47 | |
| @@ -61,10 +64,7 @@ def get_leaders_table(data: dict, category: str = "PTS") -> str: | ||
| 61 | 64 | wanted = base + extra |
| 62 | 65 | |
| 63 | 66 | idx = {h: i for i, h in enumerate(headers)} |
| 64 | table_data = [ | |
| 65 | [row[idx[col]] for col in wanted if col in idx] | |
| 66 | for row in rows[:25] | |
| 67 | ] | |
| 67 | table_data = [[row[idx[col]] for col in wanted if col in idx] for row in rows[:25]] | |
| 68 | 68 | display_headers = [_DISPLAY_NAMES.get(c, c) for c in wanted if c in idx] |
| 69 | 69 | |
| 70 | 70 | cat_label = dict(CATEGORIES).get(category, category) |
build/lib/nba/playoff.py +26 −13
| @@ -21,6 +21,7 @@ def fetch_playoff_picture() -> dict: | ||
| 21 | 21 | |
| 22 | 22 | def _clinch_status(row: list, idx: dict) -> str: |
| 23 | 23 | """Return a color-coded status string from clinch/elimination columns.""" |
| 24 | ||
| 24 | 25 | def val(col): |
| 25 | 26 | return row[idx[col]] if col in idx else None |
| 26 | 27 | |
| @@ -54,19 +55,31 @@ def _build_conference_table(result_sets: list, name: str) -> str: | ||
| 54 | 55 | losses = get(row, "LOSSES") |
| 55 | 56 | pct = get(row, "PCT") |
| 56 | 57 | pct_str = f"{float(pct):.3f}" if pct not in ("", None) else "" |
| 57 | table_data.append([ | |
| 58 | get(row, "RANK"), | |
| 59 | get(row, "TEAM"), | |
| 60 | f"{wins}-{losses}", | |
| 61 | pct_str, | |
| 62 | get(row, "GB"), | |
| 63 | get(row, "HOME"), | |
| 64 | get(row, "AWAY"), | |
| 65 | get(row, "CONF"), | |
| 66 | _clinch_status(row, idx), | |
| 67 | ]) | |
| 68 | ||
| 69 | display_headers = ["#", "Team", "W-L", "PCT", "GB", "HOME", "AWAY", "CONF", "Status"] | |
| 58 | table_data.append( | |
| 59 | [ | |
| 60 | get(row, "RANK"), | |
| 61 | get(row, "TEAM"), | |
| 62 | f"{wins}-{losses}", | |
| 63 | pct_str, | |
| 64 | get(row, "GB"), | |
| 65 | get(row, "HOME"), | |
| 66 | get(row, "AWAY"), | |
| 67 | get(row, "CONF"), | |
| 68 | _clinch_status(row, idx), | |
| 69 | ] | |
| 70 | ) | |
| 71 | ||
| 72 | display_headers = [ | |
| 73 | "#", | |
| 74 | "Team", | |
| 75 | "W-L", | |
| 76 | "PCT", | |
| 77 | "GB", | |
| 78 | "HOME", | |
| 79 | "AWAY", | |
| 80 | "CONF", | |
| 81 | "Status", | |
| 82 | ] | |
| 70 | 83 | conf_label = "Eastern" if "East" in name else "Western" |
| 71 | 84 | title = f"{BOLD}{conf_label} Conference Playoff Picture:{END}" |
| 72 | 85 | return title + "\n" + tabulate(table_data, headers=display_headers, tablefmt="grid") |
build/lib/nba/standings.py +10 −5
| @@ -27,7 +27,9 @@ def _build_conference_table(standings, conference: str) -> str: | ||
| 27 | 27 | streak = team[35] |
| 28 | 28 | |
| 29 | 29 | strk_color = ( |
| 30 | f"{RED}{streak}{END}" if int(streak) < 0 else f"{GREEN}{streak}{END}" | |
| 30 | f"{RED}{streak}{END}" | |
| 31 | if int(streak) < 0 | |
| 32 | else f"{GREEN}{streak}{END}" | |
| 31 | 33 | ) |
| 32 | 34 | |
| 33 | 35 | data.append( |
| @@ -47,9 +49,8 @@ def _build_conference_table(standings, conference: str) -> str: | ||
| 47 | 49 | |
| 48 | 50 | headers = ["Rank", "Team", "W-L", "PCT", "GB", "STRK", "L10", "HOME", "AWAY"] |
| 49 | 51 | label = "Eastern" if conference == "East" else "Western" |
| 50 | return ( | |
| 51 | f"{BOLD}{label} Conference Standings:{END}\n" | |
| 52 | + tabulate(data, headers=headers, tablefmt="grid") | |
| 52 | return f"{BOLD}{label} Conference Standings:{END}\n" + tabulate( | |
| 53 | data, headers=headers, tablefmt="grid" | |
| 53 | 54 | ) |
| 54 | 55 | |
| 55 | 56 | |
| @@ -73,7 +74,11 @@ def get_standings_tables(standings) -> str: | ||
| 73 | 74 | Returns: |
| 74 | 75 | str: Formatted standings string with ANSI color codes for both conferences. |
| 75 | 76 | """ |
| 76 | return get_east_standings_table(standings) + "\n\n" + get_west_standings_table(standings) | |
| 77 | return ( | |
| 78 | get_east_standings_table(standings) | |
| 79 | + "\n\n" | |
| 80 | + get_west_standings_table(standings) | |
| 81 | ) | |
| 77 | 82 | |
| 78 | 83 | |
| 79 | 84 | def build_standings(standings) -> None: |
build/lib/nba/tui/app.py +15 −5
| @@ -182,11 +182,15 @@ class NBAApp(App): | ||
| 182 | 182 | self.query_one(TabbedContent).active = "boxscore" |
| 183 | 183 | |
| 184 | 184 | async def action_prev_category(self) -> None: |
| 185 | self._leaders_cat_idx = (self._leaders_cat_idx - 1) % len(leaders_mod.CATEGORIES) | |
| 185 | self._leaders_cat_idx = (self._leaders_cat_idx - 1) % len( | |
| 186 | leaders_mod.CATEGORIES | |
| 187 | ) | |
| 186 | 188 | await self._refresh_leaders() |
| 187 | 189 | |
| 188 | 190 | async def action_next_category(self) -> None: |
| 189 | self._leaders_cat_idx = (self._leaders_cat_idx + 1) % len(leaders_mod.CATEGORIES) | |
| 191 | self._leaders_cat_idx = (self._leaders_cat_idx + 1) % len( | |
| 192 | leaders_mod.CATEGORIES | |
| 193 | ) | |
| 190 | 194 | await self._refresh_leaders() |
| 191 | 195 | |
| 192 | 196 | # ------------------------------------------------------------------ # |
| @@ -198,13 +202,17 @@ class NBAApp(App): | ||
| 198 | 202 | self.query_one("#leaders-content", Static).update(f"Loading {label} leaders...") |
| 199 | 203 | loop = asyncio.get_event_loop() |
| 200 | 204 | try: |
| 201 | data = await loop.run_in_executor(None, lambda: leaders_mod.fetch_leaders(cat)) | |
| 205 | data = await loop.run_in_executor( | |
| 206 | None, lambda: leaders_mod.fetch_leaders(cat) | |
| 207 | ) | |
| 202 | 208 | self._leaders_data = data |
| 203 | 209 | self.query_one("#leaders-content", Static).update( |
| 204 | 210 | Text.from_ansi(leaders_mod.get_leaders_table(data, cat)) |
| 205 | 211 | ) |
| 206 | 212 | except Exception as exc: |
| 207 | self.query_one("#leaders-content", Static).update(f"Error loading leaders: {exc}") | |
| 213 | self.query_one("#leaders-content", Static).update( | |
| 214 | f"Error loading leaders: {exc}" | |
| 215 | ) | |
| 208 | 216 | |
| 209 | 217 | async def _load_box_score(self, game_idx: int) -> None: |
| 210 | 218 | if not self._games: |
| @@ -233,5 +241,7 @@ class NBAApp(App): | ||
| 233 | 241 | self.query_one("#home-content", Static).update(Text.from_ansi(home_table)) |
| 234 | 242 | self.query_one("#away-content", Static).update(Text.from_ansi(away_table)) |
| 235 | 243 | except Exception as exc: |
| 236 | self.query_one("#home-content", Static).update(f"Error loading box score: {exc}") | |
| 244 | self.query_one("#home-content", Static).update( | |
| 245 | f"Error loading box score: {exc}" | |
| 246 | ) | |
| 237 | 247 | self.query_one("#away-content", Static).update("") |
nba/box_score.py +32 −23
| @@ -10,7 +10,21 @@ from nba_api.live.nba.endpoints.boxscore import BoxScore | ||
| 10 | 10 | BOLD = "\033[1m" |
| 11 | 11 | END = "\033[0m" |
| 12 | 12 | |
| 13 | _HEADERS = ["Player", "Pos", "Min", "Pts", "Reb", "Ast", "Stl", "Blk", "TO", "FG", "3P", "FT", "+/-"] | |
| 13 | _HEADERS = [ | |
| 14 | "Player", | |
| 15 | "Pos", | |
| 16 | "Min", | |
| 17 | "Pts", | |
| 18 | "Reb", | |
| 19 | "Ast", | |
| 20 | "Stl", | |
| 21 | "Blk", | |
| 22 | "TO", | |
| 23 | "FG", | |
| 24 | "3P", | |
| 25 | "FT", | |
| 26 | "+/-", | |
| 27 | ] | |
| 14 | 28 | |
| 15 | 29 | |
| 16 | 30 | def fetch_box_score(game_id: str) -> dict: |
| @@ -36,21 +50,23 @@ def _player_rows(players: list) -> list: | ||
| 36 | 50 | if p.get("status") == "INACTIVE": |
| 37 | 51 | continue |
| 38 | 52 | s = p.get("statistics", {}) |
| 39 | rows.append([ | |
| 40 | p.get("name", ""), | |
| 41 | p.get("position", ""), | |
| 42 | _parse_minutes(s.get("minutes", "")), | |
| 43 | s.get("points", 0), | |
| 44 | s.get("reboundsTotal", 0), | |
| 45 | s.get("assists", 0), | |
| 46 | s.get("steals", 0), | |
| 47 | s.get("blocks", 0), | |
| 48 | s.get("turnovers", 0), | |
| 49 | f"{s.get('fieldGoalsMade', 0)}/{s.get('fieldGoalsAttempted', 0)}", | |
| 50 | f"{s.get('threePointersMade', 0)}/{s.get('threePointersAttempted', 0)}", | |
| 51 | f"{s.get('freeThrowsMade', 0)}/{s.get('freeThrowsAttempted', 0)}", | |
| 52 | s.get("plusMinusPoints", 0), | |
| 53 | ]) | |
| 53 | rows.append( | |
| 54 | [ | |
| 55 | p.get("name", ""), | |
| 56 | p.get("position", ""), | |
| 57 | _parse_minutes(s.get("minutes", "")), | |
| 58 | s.get("points", 0), | |
| 59 | s.get("reboundsTotal", 0), | |
| 60 | s.get("assists", 0), | |
| 61 | s.get("steals", 0), | |
| 62 | s.get("blocks", 0), | |
| 63 | s.get("turnovers", 0), | |
| 64 | f"{s.get('fieldGoalsMade', 0)}/{s.get('fieldGoalsAttempted', 0)}", | |
| 65 | f"{s.get('threePointersMade', 0)}/{s.get('threePointersAttempted', 0)}", | |
| 66 | f"{s.get('freeThrowsMade', 0)}/{s.get('freeThrowsAttempted', 0)}", | |
| 67 | s.get("plusMinusPoints", 0), | |
| 68 | ] | |
| 69 | ) | |
| 54 | 70 | return rows |
| 55 | 71 | |
| 56 | 72 | |
| @@ -60,13 +76,6 @@ def get_box_score_tables(data: dict) -> tuple: | ||
| 60 | 76 | home = game["homeTeam"] |
| 61 | 77 | away = game["awayTeam"] |
| 62 | 78 | |
| 63 | home_title = ( | |
| 64 | f"{BOLD}{home['teamCity']} {home['teamName']} " | |
| 65 | f"{home['score']} — {away['score']} " | |
| 66 | f"{away['teamCity']} {away['teamName']}{END} " | |
| 67 | f" [{game.get('gameStatusText', '')}]" | |
| 68 | ) | |
| 69 | ||
| 70 | 79 | home_rows = _player_rows(home.get("players", [])) |
| 71 | 80 | away_rows = _player_rows(away.get("players", [])) |
| 72 | 81 | |
nba/cli.py +4 −4
| @@ -19,9 +19,7 @@ def nba() -> None: | ||
| 19 | 19 | parser.add_argument( |
| 20 | 20 | "--standings", "-st", action="store_true", help="Display the standings" |
| 21 | 21 | ) |
| 22 | parser.add_argument( | |
| 23 | "--tui", action="store_true", help="Launch the interactive TUI" | |
| 24 | ) | |
| 22 | parser.add_argument("--tui", action="store_true", help="Launch the interactive TUI") | |
| 25 | 23 | parser.add_argument( |
| 26 | 24 | "--refresh", |
| 27 | 25 | type=int, |
| @@ -47,4 +45,6 @@ def nba() -> None: | ||
| 47 | 45 | elif args.standings: |
| 48 | 46 | standings.build_standings(ranks) |
| 49 | 47 | else: |
| 50 | print("Please specify --scores or --standings (or use --tui for interactive mode)") | |
| 48 | print( | |
| 49 | "Please specify --scores or --standings (or use --tui for interactive mode)" | |
| 50 | ) | |
nba/leaders.py +13 −13
| @@ -25,20 +25,23 @@ CATEGORIES = [ | ||
| 25 | 25 | |
| 26 | 26 | # Extra columns to show alongside RANK, PLAYER, TEAM, GP for each category |
| 27 | 27 | _EXTRA_COLS = { |
| 28 | "PTS": ["PTS", "FGM", "FGA", "FG_PCT", "FTM", "FTA", "FT_PCT"], | |
| 29 | "REB": ["REB", "OREB", "DREB", "GP"], | |
| 30 | "AST": ["AST", "TOV", "AST_TOV", "GP"], | |
| 31 | "STL": ["STL", "TOV", "GP"], | |
| 32 | "BLK": ["BLK", "PF", "GP"], | |
| 33 | "EFF": ["EFF", "PTS", "REB", "AST", "GP"], | |
| 28 | "PTS": ["PTS", "FGM", "FGA", "FG_PCT", "FTM", "FTA", "FT_PCT"], | |
| 29 | "REB": ["REB", "OREB", "DREB", "GP"], | |
| 30 | "AST": ["AST", "TOV", "AST_TOV", "GP"], | |
| 31 | "STL": ["STL", "TOV", "GP"], | |
| 32 | "BLK": ["BLK", "PF", "GP"], | |
| 33 | "EFF": ["EFF", "PTS", "REB", "AST", "GP"], | |
| 34 | 34 | "FG_PCT": ["FG_PCT", "FGM", "FGA", "PTS"], |
| 35 | 35 | "FT_PCT": ["FT_PCT", "FTM", "FTA", "PTS"], |
| 36 | "FG3_PCT":["FG3_PCT", "FG3M", "FG3A", "PTS"], | |
| 36 | "FG3_PCT": ["FG3_PCT", "FG3M", "FG3A", "PTS"], | |
| 37 | 37 | } |
| 38 | 38 | |
| 39 | 39 | _DISPLAY_NAMES = { |
| 40 | "FG_PCT": "FG%", "FT_PCT": "FT%", "FG3_PCT": "3P%", | |
| 41 | "FG3M": "3PM", "FG3A": "3PA", | |
| 40 | "FG_PCT": "FG%", | |
| 41 | "FT_PCT": "FT%", | |
| 42 | "FG3_PCT": "3P%", | |
| 43 | "FG3M": "3PM", | |
| 44 | "FG3A": "3PA", | |
| 42 | 45 | "AST_TOV": "AST/TO", |
| 43 | 46 | } |
| 44 | 47 | |
| @@ -61,10 +64,7 @@ def get_leaders_table(data: dict, category: str = "PTS") -> str: | ||
| 61 | 64 | wanted = base + extra |
| 62 | 65 | |
| 63 | 66 | idx = {h: i for i, h in enumerate(headers)} |
| 64 | table_data = [ | |
| 65 | [row[idx[col]] for col in wanted if col in idx] | |
| 66 | for row in rows[:25] | |
| 67 | ] | |
| 67 | table_data = [[row[idx[col]] for col in wanted if col in idx] for row in rows[:25]] | |
| 68 | 68 | display_headers = [_DISPLAY_NAMES.get(c, c) for c in wanted if c in idx] |
| 69 | 69 | |
| 70 | 70 | cat_label = dict(CATEGORIES).get(category, category) |
nba/playoff.py +26 −13
| @@ -21,6 +21,7 @@ def fetch_playoff_picture() -> dict: | ||
| 21 | 21 | |
| 22 | 22 | def _clinch_status(row: list, idx: dict) -> str: |
| 23 | 23 | """Return a color-coded status string from clinch/elimination columns.""" |
| 24 | ||
| 24 | 25 | def val(col): |
| 25 | 26 | return row[idx[col]] if col in idx else None |
| 26 | 27 | |
| @@ -54,19 +55,31 @@ def _build_conference_table(result_sets: list, name: str) -> str: | ||
| 54 | 55 | losses = get(row, "LOSSES") |
| 55 | 56 | pct = get(row, "PCT") |
| 56 | 57 | pct_str = f"{float(pct):.3f}" if pct not in ("", None) else "" |
| 57 | table_data.append([ | |
| 58 | get(row, "RANK"), | |
| 59 | get(row, "TEAM"), | |
| 60 | f"{wins}-{losses}", | |
| 61 | pct_str, | |
| 62 | get(row, "GB"), | |
| 63 | get(row, "HOME"), | |
| 64 | get(row, "AWAY"), | |
| 65 | get(row, "CONF"), | |
| 66 | _clinch_status(row, idx), | |
| 67 | ]) | |
| 68 | ||
| 69 | display_headers = ["#", "Team", "W-L", "PCT", "GB", "HOME", "AWAY", "CONF", "Status"] | |
| 58 | table_data.append( | |
| 59 | [ | |
| 60 | get(row, "RANK"), | |
| 61 | get(row, "TEAM"), | |
| 62 | f"{wins}-{losses}", | |
| 63 | pct_str, | |
| 64 | get(row, "GB"), | |
| 65 | get(row, "HOME"), | |
| 66 | get(row, "AWAY"), | |
| 67 | get(row, "CONF"), | |
| 68 | _clinch_status(row, idx), | |
| 69 | ] | |
| 70 | ) | |
| 71 | ||
| 72 | display_headers = [ | |
| 73 | "#", | |
| 74 | "Team", | |
| 75 | "W-L", | |
| 76 | "PCT", | |
| 77 | "GB", | |
| 78 | "HOME", | |
| 79 | "AWAY", | |
| 80 | "CONF", | |
| 81 | "Status", | |
| 82 | ] | |
| 70 | 83 | conf_label = "Eastern" if "East" in name else "Western" |
| 71 | 84 | title = f"{BOLD}{conf_label} Conference Playoff Picture:{END}" |
| 72 | 85 | return title + "\n" + tabulate(table_data, headers=display_headers, tablefmt="grid") |
nba/standings.py +10 −5
| @@ -27,7 +27,9 @@ def _build_conference_table(standings, conference: str) -> str: | ||
| 27 | 27 | streak = team[35] |
| 28 | 28 | |
| 29 | 29 | strk_color = ( |
| 30 | f"{RED}{streak}{END}" if int(streak) < 0 else f"{GREEN}{streak}{END}" | |
| 30 | f"{RED}{streak}{END}" | |
| 31 | if int(streak) < 0 | |
| 32 | else f"{GREEN}{streak}{END}" | |
| 31 | 33 | ) |
| 32 | 34 | |
| 33 | 35 | data.append( |
| @@ -47,9 +49,8 @@ def _build_conference_table(standings, conference: str) -> str: | ||
| 47 | 49 | |
| 48 | 50 | headers = ["Rank", "Team", "W-L", "PCT", "GB", "STRK", "L10", "HOME", "AWAY"] |
| 49 | 51 | label = "Eastern" if conference == "East" else "Western" |
| 50 | return ( | |
| 51 | f"{BOLD}{label} Conference Standings:{END}\n" | |
| 52 | + tabulate(data, headers=headers, tablefmt="grid") | |
| 52 | return f"{BOLD}{label} Conference Standings:{END}\n" + tabulate( | |
| 53 | data, headers=headers, tablefmt="grid" | |
| 53 | 54 | ) |
| 54 | 55 | |
| 55 | 56 | |
| @@ -73,7 +74,11 @@ def get_standings_tables(standings) -> str: | ||
| 73 | 74 | Returns: |
| 74 | 75 | str: Formatted standings string with ANSI color codes for both conferences. |
| 75 | 76 | """ |
| 76 | return get_east_standings_table(standings) + "\n\n" + get_west_standings_table(standings) | |
| 77 | return ( | |
| 78 | get_east_standings_table(standings) | |
| 79 | + "\n\n" | |
| 80 | + get_west_standings_table(standings) | |
| 81 | ) | |
| 77 | 82 | |
| 78 | 83 | |
| 79 | 84 | def build_standings(standings) -> None: |
nba/tui/app.py +15 −5
| @@ -182,11 +182,15 @@ class NBAApp(App): | ||
| 182 | 182 | self.query_one(TabbedContent).active = "boxscore" |
| 183 | 183 | |
| 184 | 184 | async def action_prev_category(self) -> None: |
| 185 | self._leaders_cat_idx = (self._leaders_cat_idx - 1) % len(leaders_mod.CATEGORIES) | |
| 185 | self._leaders_cat_idx = (self._leaders_cat_idx - 1) % len( | |
| 186 | leaders_mod.CATEGORIES | |
| 187 | ) | |
| 186 | 188 | await self._refresh_leaders() |
| 187 | 189 | |
| 188 | 190 | async def action_next_category(self) -> None: |
| 189 | self._leaders_cat_idx = (self._leaders_cat_idx + 1) % len(leaders_mod.CATEGORIES) | |
| 191 | self._leaders_cat_idx = (self._leaders_cat_idx + 1) % len( | |
| 192 | leaders_mod.CATEGORIES | |
| 193 | ) | |
| 190 | 194 | await self._refresh_leaders() |
| 191 | 195 | |
| 192 | 196 | # ------------------------------------------------------------------ # |
| @@ -198,13 +202,17 @@ class NBAApp(App): | ||
| 198 | 202 | self.query_one("#leaders-content", Static).update(f"Loading {label} leaders...") |
| 199 | 203 | loop = asyncio.get_event_loop() |
| 200 | 204 | try: |
| 201 | data = await loop.run_in_executor(None, lambda: leaders_mod.fetch_leaders(cat)) | |
| 205 | data = await loop.run_in_executor( | |
| 206 | None, lambda: leaders_mod.fetch_leaders(cat) | |
| 207 | ) | |
| 202 | 208 | self._leaders_data = data |
| 203 | 209 | self.query_one("#leaders-content", Static).update( |
| 204 | 210 | Text.from_ansi(leaders_mod.get_leaders_table(data, cat)) |
| 205 | 211 | ) |
| 206 | 212 | except Exception as exc: |
| 207 | self.query_one("#leaders-content", Static).update(f"Error loading leaders: {exc}") | |
| 213 | self.query_one("#leaders-content", Static).update( | |
| 214 | f"Error loading leaders: {exc}" | |
| 215 | ) | |
| 208 | 216 | |
| 209 | 217 | async def _load_box_score(self, game_idx: int) -> None: |
| 210 | 218 | if not self._games: |
| @@ -233,5 +241,7 @@ class NBAApp(App): | ||
| 233 | 241 | self.query_one("#home-content", Static).update(Text.from_ansi(home_table)) |
| 234 | 242 | self.query_one("#away-content", Static).update(Text.from_ansi(away_table)) |
| 235 | 243 | except Exception as exc: |
| 236 | self.query_one("#home-content", Static).update(f"Error loading box score: {exc}") | |
| 244 | self.query_one("#home-content", Static).update( | |
| 245 | f"Error loading box score: {exc}" | |
| 246 | ) | |
| 237 | 247 | self.query_one("#away-content", Static).update("") |