Spaces:
Sleeping
Sleeping
Card Lab Game Summary: fix empty selectors via statcast coverage filter
Browse filesRoot cause: game_outcomes contains 2026/recent games with no matching
rows in statcast_event_core (historical data ends at 2025). Game pk
lookups returned 0 rows silently, leaving multiselects empty.
- get_recent_completed_games_for_card_lab: restrict dropdown to games
whose game_pk exists in statcast_event_core (CAST bigint AS STRING
for safe text comparison against game_outcomes.game_pk text column)
- get_game_players_for_card_lab: surface exceptions via _error key in
result dict instead of swallowing silently
- Debug expander: show raw game_pk type, statcast row/pitcher/batter
counts for selected game, and any helper exception text
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
visualization/card_lab_page.py
CHANGED
|
@@ -335,7 +335,8 @@ def render_card_lab(conn) -> None:
|
|
| 335 |
|
| 336 |
elif card_type == "Game Summary":
|
| 337 |
if selected_game_row:
|
| 338 |
-
|
|
|
|
| 339 |
st.write(f"**Hitters found:** {len(game_players.get('hitters', []))}")
|
| 340 |
st.write(f"**Pitchers found:** {len(game_players.get('pitchers', []))}")
|
| 341 |
st.write("**First 10 hitters:**", [h['display_name'] for h in game_players.get('hitters', [])[:10]])
|
|
@@ -346,6 +347,27 @@ def render_card_lab(conn) -> None:
|
|
| 346 |
st.write("Selected hitters:", [h['display_name'] for h in game_selected_hitters])
|
| 347 |
if game_selected_pitchers:
|
| 348 |
st.write("Selected pitchers:", [p['display_name'] for p in game_selected_pitchers])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 349 |
|
| 350 |
fmt = st.radio("Format", ["PNG", "JPG"], horizontal=True, key="cl_fmt")
|
| 351 |
|
|
|
|
| 335 |
|
| 336 |
elif card_type == "Game Summary":
|
| 337 |
if selected_game_row:
|
| 338 |
+
raw_gp = selected_game_row.get("game_pk")
|
| 339 |
+
st.write(f"**game_pk raw value:** {raw_gp!r} (type: {type(raw_gp).__name__})")
|
| 340 |
st.write(f"**Hitters found:** {len(game_players.get('hitters', []))}")
|
| 341 |
st.write(f"**Pitchers found:** {len(game_players.get('pitchers', []))}")
|
| 342 |
st.write("**First 10 hitters:**", [h['display_name'] for h in game_players.get('hitters', [])[:10]])
|
|
|
|
| 347 |
st.write("Selected hitters:", [h['display_name'] for h in game_selected_hitters])
|
| 348 |
if game_selected_pitchers:
|
| 349 |
st.write("Selected pitchers:", [p['display_name'] for p in game_selected_pitchers])
|
| 350 |
+
if selected_game_row and selected_game_row.get("game_pk"):
|
| 351 |
+
try:
|
| 352 |
+
_gp = int(selected_game_row["game_pk"])
|
| 353 |
+
_cnt = conn.execute(
|
| 354 |
+
text("SELECT COUNT(*) FROM statcast_event_core WHERE game_pk = :gp"),
|
| 355 |
+
{"gp": _gp},
|
| 356 |
+
).scalar()
|
| 357 |
+
_p_cnt = conn.execute(
|
| 358 |
+
text("SELECT COUNT(DISTINCT pitcher) FROM statcast_event_core WHERE game_pk = :gp"),
|
| 359 |
+
{"gp": _gp},
|
| 360 |
+
).scalar()
|
| 361 |
+
_b_cnt = conn.execute(
|
| 362 |
+
text("SELECT COUNT(DISTINCT batter) FROM statcast_event_core WHERE game_pk = :gp"),
|
| 363 |
+
{"gp": _gp},
|
| 364 |
+
).scalar()
|
| 365 |
+
st.write(f"**statcast rows for game_pk {_gp}:** {_cnt}")
|
| 366 |
+
st.write(f"**Distinct pitchers:** {_p_cnt} | **Distinct batters:** {_b_cnt}")
|
| 367 |
+
except Exception as _e:
|
| 368 |
+
st.write(f"**Statcast row count error:** {_e}")
|
| 369 |
+
if game_players.get("_error"):
|
| 370 |
+
st.write(f"**get_game_players error:** {game_players['_error']}")
|
| 371 |
|
| 372 |
fmt = st.radio("Format", ["PNG", "JPG"], horizontal=True, key="cl_fmt")
|
| 373 |
|
visualization/cards/card_queries.py
CHANGED
|
@@ -370,10 +370,16 @@ def get_recent_completed_games_for_card_lab(conn, limit: int = 25) -> pd.DataFra
|
|
| 370 |
"""Recent completed games from game_outcomes for the Game Summary card type."""
|
| 371 |
try:
|
| 372 |
sql = text("""
|
| 373 |
-
SELECT game_pk, away_team, home_team, away_score, home_score,
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 377 |
LIMIT :limit
|
| 378 |
""")
|
| 379 |
df = pd.read_sql(sql, conn, params={"limit": limit})
|
|
@@ -434,7 +440,7 @@ def get_game_players_for_card_lab(
|
|
| 434 |
|
| 435 |
Each entry: {"display_name": str, "player_id": int|None, "team": str}
|
| 436 |
"""
|
| 437 |
-
result: dict[str, list[dict]] = {"pitchers": [], "hitters": []}
|
| 438 |
try:
|
| 439 |
pitcher_rows = conn.execute(
|
| 440 |
text("""
|
|
@@ -488,4 +494,5 @@ def get_game_players_for_card_lab(
|
|
| 488 |
)
|
| 489 |
except Exception as exc:
|
| 490 |
logger.warning("[game_players] failed game_pk=%s: %s", game_pk, exc)
|
|
|
|
| 491 |
return result
|
|
|
|
| 370 |
"""Recent completed games from game_outcomes for the Game Summary card type."""
|
| 371 |
try:
|
| 372 |
sql = text("""
|
| 373 |
+
SELECT go.game_pk, go.away_team, go.home_team, go.away_score, go.home_score,
|
| 374 |
+
go.graded_at AS game_date
|
| 375 |
+
FROM game_outcomes go
|
| 376 |
+
WHERE go.away_score IS NOT NULL
|
| 377 |
+
AND go.home_score IS NOT NULL
|
| 378 |
+
AND go.game_pk IN (
|
| 379 |
+
SELECT DISTINCT CAST(ec.game_pk AS STRING)
|
| 380 |
+
FROM statcast_event_core ec
|
| 381 |
+
)
|
| 382 |
+
ORDER BY go.graded_at DESC
|
| 383 |
LIMIT :limit
|
| 384 |
""")
|
| 385 |
df = pd.read_sql(sql, conn, params={"limit": limit})
|
|
|
|
| 440 |
|
| 441 |
Each entry: {"display_name": str, "player_id": int|None, "team": str}
|
| 442 |
"""
|
| 443 |
+
result: dict[str, list[dict]] = {"pitchers": [], "hitters": [], "_error": None}
|
| 444 |
try:
|
| 445 |
pitcher_rows = conn.execute(
|
| 446 |
text("""
|
|
|
|
| 494 |
)
|
| 495 |
except Exception as exc:
|
| 496 |
logger.warning("[game_players] failed game_pk=%s: %s", game_pk, exc)
|
| 497 |
+
result["_error"] = str(exc)
|
| 498 |
return result
|