Files
nprasad2077 11a5cc0f18
CI Health Check & Quality Gate / Code, dbt & Live App Health Check (pull_request) Successful in 1m11s
shot chart and navbar
2026-10-28 02:37:56 -05:00

202 lines
6.9 KiB
Python

import plotly.express as px
import streamlit as st
from streamlit_echarts import st_echarts
from utils.charts import AMBER, apply_arena_theme
from utils.db import get_selected_season, query
SEASON, SEASON_LABEL = get_selected_season()
st.title(f":material/dashboard: League Pulse — {SEASON_LABEL}")
# --- KPIs with delta vs prior season ---
summary = query(
"""
SELECT count(*) AS total_games,
sum(CASE WHEN is_overtime THEN 1 ELSE 0 END) AS ot_games,
round(avg(total_points), 1) AS avg_pts,
round(avg(point_differential), 1) AS avg_margin,
round(100.0 * sum(CASE WHEN is_home_team_winner THEN 1 ELSE 0 END) / count(*), 1) AS home_win_pct,
min(game_date) AS first_game,
max(game_date) AS last_game
FROM main_marts.fct_game_results
WHERE season_key IN (SELECT season_key FROM main_marts.dim_seasons WHERE season_start_year = ?)
""",
[SEASON],
)
prior = query(
"""
SELECT round(avg(total_points), 1) AS avg_pts,
round(avg(point_differential), 1) AS avg_margin,
round(100.0 * sum(CASE WHEN is_home_team_winner THEN 1 ELSE 0 END) / count(*), 1) AS home_win_pct
FROM main_marts.fct_game_results
WHERE season_key IN (SELECT season_key FROM main_marts.dim_seasons WHERE season_start_year = ?)
""",
[SEASON - 1],
)
has_prior = not prior.empty and prior["avg_pts"][0] is not None
c1, c2, c3, c4, c5 = st.columns(5)
c1.metric("Total Games", f"{summary['total_games'][0]:,}")
c2.metric(
"Avg Points/Game",
summary["avg_pts"][0],
delta=f"{summary['avg_pts'][0] - prior['avg_pts'][0]:+.1f}" if has_prior else None,
)
c3.metric(
"Avg Margin",
summary["avg_margin"][0],
delta=f"{summary['avg_margin'][0] - prior['avg_margin'][0]:+.1f}" if has_prior else None,
)
c4.metric(
"Home Win %",
f"{summary['home_win_pct'][0]}%",
delta=f"{summary['home_win_pct'][0] - prior['home_win_pct'][0]:+.1f}%" if has_prior else None,
)
c5.metric("OT Games", f"{summary['ot_games'][0]:,}")
st.caption(f"Season: {summary['first_game'][0]} → {summary['last_game'][0]} | Deltas vs prior season")
# --- Row 2: Competitiveness donut + Top standings ---
col1, col2 = st.columns(2)
with col1:
comp = query(
"""
SELECT game_competitiveness_tier AS tier, count(*) AS games
FROM main_marts.fct_game_results
WHERE season_key IN (SELECT season_key FROM main_marts.dim_seasons WHERE season_start_year = ?)
GROUP BY tier ORDER BY games DESC
""",
[SEASON],
)
fig = px.pie(
comp,
names="tier",
values="games",
title="Game Competitiveness",
hole=0.4,
color_discrete_sequence=px.colors.qualitative.Set2,
)
fig.update_traces(textinfo="percent+label")
apply_arena_theme(fig)
st.plotly_chart(fig, width="stretch")
with col2:
standings = query(
"""
SELECT team, count(*) AS games,
sum(CASE WHEN game_result = 'W' THEN 1 ELSE 0 END) AS wins,
sum(CASE WHEN game_result = 'L' THEN 1 ELSE 0 END) AS losses,
round(100.0 * sum(CASE WHEN game_result = 'W' THEN 1 ELSE 0 END) / count(*), 1) AS win_pct,
round(avg(net_rating), 1) AS net_rtg
FROM main_intermediate.int_team_performance
WHERE season_start_year = ?
GROUP BY team ORDER BY win_pct DESC LIMIT 8
""",
[SEASON],
)
st.subheader(":material/emoji_events: Top Teams")
st.dataframe(
standings,
column_config={
"win_pct": st.column_config.ProgressColumn(
"Win%", min_value=0, max_value=100, format="%.1f%%"
),
},
hide_index=True,
width="stretch",
)
# --- Row 3: Weekly scoring trend ---
weekly = query(
"""
SELECT date_trunc('week', game_date)::date::varchar AS week,
count(*) AS games,
round(avg(total_points), 1) AS avg_pts,
round(avg(point_differential), 1) AS avg_margin
FROM main_marts.fct_game_results
WHERE season_key IN (SELECT season_key FROM main_marts.dim_seasons WHERE season_start_year = ?)
GROUP BY week ORDER BY week
""",
[SEASON],
)
fig_weekly = px.area(
weekly,
x="week",
y="avg_pts",
title="Weekly Scoring Trend",
color_discrete_sequence=[AMBER],
)
fig_weekly.update_layout(xaxis_title="Week", yaxis_title="Avg Total Points")
apply_arena_theme(fig_weekly, height=350)
st.plotly_chart(fig_weekly, width="stretch")
# --- Row 4: Calendar heatmap + Season highlights ---
col1, col2 = st.columns([2, 1])
with col1:
cal_data = query(
"""
SELECT game_date::varchar AS day, count(*) AS games
FROM main_marts.fct_game_results
WHERE season_key IN (SELECT season_key FROM main_marts.dim_seasons WHERE season_start_year = ?)
GROUP BY game_date ORDER BY game_date
""",
[SEASON],
)
if not cal_data.empty:
cal_list = cal_data.values.tolist()
cal_option = {
"tooltip": {"position": "top", "formatter": "{c0} games on {b0}"},
"visualMap": {
"min": 0,
"max": int(cal_data["games"].max()),
"calculable": True,
"orient": "horizontal",
"left": "center",
"top": "top",
"inRange": {"color": ["#151D2A", "#9be9a8", "#40c463", "#30a14e", "#10B981"]},
},
"calendar": {
"range": [cal_list[0][0][:7], cal_list[-1][0][:7]],
"cellSize": ["auto", 15],
},
"series": [{"type": "heatmap", "coordinateSystem": "calendar", "data": cal_list}],
}
st_echarts(options=cal_option, height="200px")
with col2:
st.subheader(":material/local_fire_department: Top Performances")
highlights = query(
"""
SELECT player_name AS player, team,
game_date::date::varchar AS date, points AS pts,
assists AS ast, total_rebounds AS reb
FROM main_intermediate.int_player_performance
WHERE season_start_year = ?
ORDER BY points DESC LIMIT 5
""",
[SEASON],
)
st.dataframe(highlights, hide_index=True, width="stretch")
# --- Row 5: Recent games ---
st.subheader("Recent Games")
games = query(
"""
SELECT g.game_date, ht.team_abbr AS home, vt.team_abbr AS away,
g.home_points, g.visitor_points, wt.team_abbr AS winner,
g.point_differential AS margin, g.game_competitiveness_tier AS type
FROM main_marts.fct_game_results g
LEFT JOIN main_marts.dim_teams ht ON g.home_team_key = ht.team_key
LEFT JOIN main_marts.dim_teams vt ON g.visitor_team_key = vt.team_key
LEFT JOIN main_marts.dim_teams wt ON g.winning_team_key = wt.team_key
WHERE g.season_key IN (SELECT season_key FROM main_marts.dim_seasons WHERE season_start_year = ?)
ORDER BY g.game_date DESC LIMIT 25
""",
[SEASON],
)
st.dataframe(games, hide_index=True, width="stretch")