All checks were successful
Build Docker Image / build (pull_request) Successful in 1m14s
#40: ScorecardTracker cached data in memory at startup — background task never saw newly published scorecards. Fixed by reloading from disk on every read. #39: Win percentage defaulted to 50% when unavailable, showing a misleading 50/50 bar. Now defaults to None with "unavailable" message in embed. Parsing handles decimal (0.75), percentage string, and empty values. Also fixed orientation bug where win% was always shown as home team's even when the sheet reports the away team as the leader. Additionally: live scorebug tracker now distinguishes between "all games confirmed final" and "sheet read failures" — transient Google Sheets errors no longer hide the live scores channel. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
216 lines
7.7 KiB
Python
216 lines
7.7 KiB
Python
"""
|
|
Scorebug Display Helpers
|
|
|
|
Utility functions for formatting and displaying scorebug information.
|
|
"""
|
|
|
|
import discord
|
|
|
|
from views.embeds import EmbedColors
|
|
|
|
|
|
def create_scorebug_embed(
|
|
scorebug_data, away_team, home_team, full_length: bool = True
|
|
) -> discord.Embed:
|
|
"""
|
|
Create a rich embed from scorebug data.
|
|
|
|
Args:
|
|
scorebug_data: ScorebugData object with game information
|
|
away_team: Away team object (optional)
|
|
home_team: Home team object (optional)
|
|
full_length: If True, includes pitcher/batter/runners/summary; if False, compact view
|
|
|
|
Returns:
|
|
Discord embed with scorebug information
|
|
"""
|
|
# Determine embed color based on win probability (not score!)
|
|
# This creates a fun twist where the favored team's color shows,
|
|
# even if they're currently losing
|
|
if (
|
|
scorebug_data.win_percentage is not None
|
|
and scorebug_data.win_percentage > 50
|
|
and home_team
|
|
):
|
|
embed_color = home_team.get_color_int() # Home team favored
|
|
elif (
|
|
scorebug_data.win_percentage is not None
|
|
and scorebug_data.win_percentage < 50
|
|
and away_team
|
|
):
|
|
embed_color = away_team.get_color_int() # Away team favored
|
|
else:
|
|
embed_color = EmbedColors.INFO # Even game (50/50) or unavailable
|
|
|
|
# Create embed with header as title
|
|
embed = discord.Embed(title=scorebug_data.header, color=embed_color)
|
|
|
|
# Get team abbreviations for use throughout
|
|
away_abbrev = away_team.abbrev if away_team else "AWAY"
|
|
home_abbrev = home_team.abbrev if home_team else "HOME"
|
|
|
|
# Create ASCII scorebug with bases visualization
|
|
occupied = "●"
|
|
unoccupied = "○"
|
|
|
|
# runners[0]=Catcher, [1]=On First, [2]=On Second, [3]=On Third
|
|
first_base = (
|
|
unoccupied
|
|
if not scorebug_data.runners[1] or not scorebug_data.runners[1][0]
|
|
else occupied
|
|
)
|
|
second_base = (
|
|
unoccupied
|
|
if not scorebug_data.runners[2] or not scorebug_data.runners[2][0]
|
|
else occupied
|
|
)
|
|
third_base = (
|
|
unoccupied
|
|
if not scorebug_data.runners[3] or not scorebug_data.runners[3][0]
|
|
else occupied
|
|
)
|
|
half = "▲" if scorebug_data.which_half == "Top" else "▼"
|
|
|
|
if not scorebug_data.is_final:
|
|
inning = f"{half} {scorebug_data.inning}"
|
|
outs = f'{scorebug_data.outs} Out{"s" if scorebug_data.outs != 1 else ""}'
|
|
else:
|
|
# Final inning display
|
|
final_inning = (
|
|
scorebug_data.inning
|
|
if scorebug_data.which_half == "Bot"
|
|
else scorebug_data.inning - 1
|
|
)
|
|
inning = f"F/{final_inning}"
|
|
outs = ""
|
|
|
|
game_state_text = (
|
|
f"```\n"
|
|
f"{away_abbrev: ^4}{scorebug_data.away_score: ^3} {second_base}{inning: >10}\n"
|
|
f"{home_abbrev: ^4}{scorebug_data.home_score: ^3} {third_base} {first_base}{outs: >8}\n"
|
|
f"```"
|
|
)
|
|
|
|
# Add win probability bar or unavailable message
|
|
if scorebug_data.win_percentage is not None:
|
|
win_prob_value = create_team_progress_bar(
|
|
scorebug_data.win_percentage, away_abbrev, home_abbrev
|
|
)
|
|
else:
|
|
win_prob_value = "-# Win probability unavailable"
|
|
|
|
embed.add_field(
|
|
name="Win Probability",
|
|
value=win_prob_value,
|
|
inline=False,
|
|
)
|
|
|
|
# Add game state
|
|
embed.add_field(name="Game State", value=game_state_text, inline=False)
|
|
|
|
# If not full_length, return compact version
|
|
if not full_length:
|
|
return embed
|
|
|
|
# Full length - add pitcher and batter info
|
|
if scorebug_data.pitcher_name:
|
|
embed.add_field(
|
|
name="Pitcher",
|
|
value=f"[{scorebug_data.pitcher_name}]({scorebug_data.pitcher_url})\n{scorebug_data.pitcher_stats}",
|
|
inline=True,
|
|
)
|
|
|
|
if scorebug_data.batter_name:
|
|
embed.add_field(
|
|
name="Batter",
|
|
value=f"[{scorebug_data.batter_name}]({scorebug_data.batter_url})\n{scorebug_data.batter_stats}",
|
|
inline=True,
|
|
)
|
|
|
|
# Add baserunners if present
|
|
on_first = scorebug_data.runners[1] if scorebug_data.runners[1] else ""
|
|
on_second = scorebug_data.runners[2] if scorebug_data.runners[2] else ""
|
|
on_third = scorebug_data.runners[3] if scorebug_data.runners[3] else ""
|
|
have_baserunners = len(on_first[0]) + len(on_second[0]) + len(on_third[0]) > 0
|
|
|
|
if have_baserunners > 0:
|
|
br_string = ""
|
|
if len(on_first) > 0:
|
|
br_string += f"On First: [{on_first[0]}]({on_first[1]})\n"
|
|
if len(on_second) > 0:
|
|
br_string += f"On Second: [{on_second[0]}]({on_second[1]})\n"
|
|
if len(on_third) > 0:
|
|
br_string += f"On Third: [{on_third[0]}]({on_third[1]})\n"
|
|
|
|
embed.add_field(name=" ", value=" ", inline=False) # Spacer
|
|
embed.add_field(name="Baserunners", value=br_string, inline=True)
|
|
|
|
# Add catcher
|
|
if scorebug_data.runners[0] and scorebug_data.runners[0][0]:
|
|
embed.add_field(
|
|
name="Catcher",
|
|
value=f"[{scorebug_data.runners[0][0]}]({scorebug_data.runners[0][1]})",
|
|
inline=True,
|
|
)
|
|
|
|
# Add inning summary if not final
|
|
if not scorebug_data.is_final and scorebug_data.summary:
|
|
i_string = ""
|
|
for line in scorebug_data.summary:
|
|
if line and len(line) >= 2 and line[0]:
|
|
i_string += f"- Play {line[0]}: {line[1]}\n"
|
|
|
|
if i_string and "IFERROR" not in i_string:
|
|
embed.add_field(name="Inning Summary", value=i_string, inline=False)
|
|
|
|
return embed
|
|
|
|
|
|
def create_team_progress_bar(
|
|
win_percentage: float, away_abbrev: str, home_abbrev: str, length: int = 10
|
|
) -> str:
|
|
"""
|
|
Create a proportional progress bar showing each team's win probability.
|
|
|
|
Args:
|
|
win_percentage: Home team's win percentage (0-100)
|
|
away_abbrev: Away team abbreviation (e.g., "POR")
|
|
home_abbrev: Home team abbreviation (e.g., "WV")
|
|
length: Total bar length in blocks (default 10)
|
|
|
|
Returns:
|
|
Formatted bar with dark blocks (▓) weighted toward winning team.
|
|
Arrow extends from the side with the advantage.
|
|
Percentage displayed on winning team's side (or both sides if even).
|
|
Examples:
|
|
Home winning: "POR ░▓▓▓▓▓▓▓▓▓► WV 95.0%"
|
|
Away winning: "70.0% POR ◄▓▓▓▓▓▓▓░░░ WV"
|
|
Even game: "50.0% POR =▓▓▓▓▓▓▓▓▓▓= WV 50.0%"
|
|
"""
|
|
# Calculate blocks for each team (home team's percentage)
|
|
home_blocks = int((win_percentage / 100) * length)
|
|
away_blocks = length - home_blocks
|
|
|
|
if win_percentage > 50:
|
|
# Home team (right side) is winning
|
|
away_char = "░" # Light blocks for losing team
|
|
home_char = "▓" # Dark blocks for winning team
|
|
bar = away_char * away_blocks + home_char * home_blocks
|
|
# Arrow extends from right side, percentage on right
|
|
return f"{away_abbrev} {bar}► {home_abbrev} {win_percentage:.1f}%"
|
|
elif win_percentage < 50:
|
|
# Away team (left side) is winning
|
|
away_char = "▓" # Dark blocks for winning team
|
|
home_char = "░" # Light blocks for losing team
|
|
bar = away_char * away_blocks + home_char * home_blocks
|
|
# Arrow extends from left side, percentage on left (showing away team's win %)
|
|
away_win_pct = 100 - win_percentage
|
|
return f"{away_win_pct:.1f}% {away_abbrev} ◄{bar} {home_abbrev}"
|
|
else:
|
|
# Even game (50/50)
|
|
away_char = "▓"
|
|
home_char = "▓"
|
|
bar = away_char * away_blocks + home_char * home_blocks
|
|
# Arrows on both sides for balanced display, percentage on both sides
|
|
return f"{win_percentage:.1f}% {away_abbrev} ={bar}= {home_abbrev} {win_percentage:.1f}%"
|