From 4cbe592e0a0fae13f0a89918a66fd983dd84464f Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Wed, 15 Apr 2026 22:28:45 -0500 Subject: feat: add Textual TUI with auto-refresh, leaders, playoff picture, and box score MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces an interactive terminal UI via the `--tui` flag backed by the Textual framework. Scores and standings auto-refresh on a configurable interval (default 60s, minimum 10s) using asyncio with nba_api calls offloaded to a thread pool to keep the event loop unblocked. New tabs: - Scores / Standings (existing views, now live-updating) - Leaders: top-25 by stat category, cycle with < / > keys - Playoff Picture: conference seeding with clinch/elimination status - Box Score: per-player live stats, load any game with keys 1–9 Refactored scores.py and standings.py to separate rendering from I/O (get_scoreboard_table, get_east/west_standings_table), keeping the existing static CLI path fully intact. Added -sc / -st shortcut flags. Adds textual>=0.89.1,<7.0 dependency, build-system declaration for uv, and package-data entry for the bundled .tcss stylesheet. --- nba/leaders.py | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 nba/leaders.py (limited to 'nba/leaders.py') diff --git a/nba/leaders.py b/nba/leaders.py new file mode 100644 index 0000000..aa6f3b4 --- /dev/null +++ b/nba/leaders.py @@ -0,0 +1,72 @@ +""" +Fetches and formats NBA statistical leaders. +""" + +import json + +from tabulate import tabulate +from nba_api.stats.endpoints.leagueleaders import LeagueLeaders + +BOLD = "\033[1m" +END = "\033[0m" + +# Ordered list of (api_abbreviation, display_label) +CATEGORIES = [ + ("PTS", "Points"), + ("REB", "Rebounds"), + ("AST", "Assists"), + ("STL", "Steals"), + ("BLK", "Blocks"), + ("EFF", "Efficiency"), + ("FG_PCT", "FG%"), + ("FT_PCT", "FT%"), + ("FG3_PCT", "3P%"), +] + +# Extra columns to show alongside RANK, PLAYER, TEAM, GP for each category +_EXTRA_COLS = { + "PTS": ["PTS", "FGM", "FGA", "FG_PCT", "FTM", "FTA", "FT_PCT"], + "REB": ["REB", "OREB", "DREB", "GP"], + "AST": ["AST", "TOV", "AST_TOV", "GP"], + "STL": ["STL", "TOV", "GP"], + "BLK": ["BLK", "PF", "GP"], + "EFF": ["EFF", "PTS", "REB", "AST", "GP"], + "FG_PCT": ["FG_PCT", "FGM", "FGA", "PTS"], + "FT_PCT": ["FT_PCT", "FTM", "FTA", "PTS"], + "FG3_PCT":["FG3_PCT", "FG3M", "FG3A", "PTS"], +} + +_DISPLAY_NAMES = { + "FG_PCT": "FG%", "FT_PCT": "FT%", "FG3_PCT": "3P%", + "FG3M": "3PM", "FG3A": "3PA", + "AST_TOV": "AST/TO", +} + + +def fetch_leaders(category: str = "PTS") -> dict: + endpoint = LeagueLeaders( + stat_category_abbreviation=category, + season_type_all_star="Regular Season", + ) + return json.loads(endpoint.get_json()) + + +def get_leaders_table(data: dict, category: str = "PTS") -> str: + result = data["resultSet"] + headers = result["headers"] + rows = result["rowSet"] + + base = ["RANK", "PLAYER", "TEAM", "GP"] + extra = [c for c in _EXTRA_COLS.get(category, [category]) if c not in base] + wanted = base + extra + + idx = {h: i for i, h in enumerate(headers)} + table_data = [ + [row[idx[col]] for col in wanted if col in idx] + for row in rows[:25] + ] + display_headers = [_DISPLAY_NAMES.get(c, c) for c in wanted if c in idx] + + cat_label = dict(CATEGORIES).get(category, category) + title = f"{BOLD}League Leaders — {cat_label}{END}" + return title + "\n" + tabulate(table_data, headers=display_headers, tablefmt="grid") -- cgit v1.2.3