Spaces:
Sleeping
Sleeping
Debug page: lazy-load 7 unconditional DB queries behind session-state buttons
Browse filesEliminates 7 uncached DB queries that fired on every page render:
- 5 audit expanders moved inside "Load Data Library" gate (session state persisted)
- Section 9 eval metrics gated behind new "Load Evaluation Metrics" button
- Both buttons use st.session_state so content survives Streamlit reruns
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- visualization/debug_page.py +91 -79
visualization/debug_page.py
CHANGED
|
@@ -641,7 +641,12 @@ def render_debug(
|
|
| 641 |
# ------------------------------------------------------------------
|
| 642 |
st.subheader("Data Inventory")
|
| 643 |
|
|
|
|
|
|
|
| 644 |
if st.button("Load Data Library", key="dbg_load_inventory"):
|
|
|
|
|
|
|
|
|
|
| 645 |
with st.expander("All database tables (row counts)", expanded=True):
|
| 646 |
inv_df = _query_db_inventory(conn)
|
| 647 |
if inv_df.empty:
|
|
@@ -672,7 +677,54 @@ def render_debug(
|
|
| 672 |
elif overlap and "error" in overlap:
|
| 673 |
st.warning(f"Overlap query error: {overlap['error']}")
|
| 674 |
|
| 675 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 676 |
|
| 677 |
# --- Simulator raw rows ---
|
| 678 |
with st.expander("Simulator raw rows", expanded=False):
|
|
@@ -711,52 +763,6 @@ def render_debug(
|
|
| 711 |
else:
|
| 712 |
st.info("No prepared live games.")
|
| 713 |
|
| 714 |
-
# --- Batter prop outcomes ---
|
| 715 |
-
with st.expander("Batter prop outcomes", expanded=False):
|
| 716 |
-
batter_prop_outcomes_df = read_batter_prop_outcomes(conn)
|
| 717 |
-
st.write(f"Rows: {len(batter_prop_outcomes_df)}")
|
| 718 |
-
if not batter_prop_outcomes_df.empty:
|
| 719 |
-
display_cols = [c for c in [
|
| 720 |
-
"created_at", "graded_at", "game_pk", "slot", "batter_name",
|
| 721 |
-
"fair_hr_odds", "book_hr_odds", "adjusted_edge", "confidence",
|
| 722 |
-
"recommendation_tier", "realized_hit", "realized_hr", "realized_tb2p",
|
| 723 |
-
"grade_status", "outcome_source",
|
| 724 |
-
] if c in batter_prop_outcomes_df.columns]
|
| 725 |
-
st.dataframe(batter_prop_outcomes_df[display_cols].tail(30), use_container_width=True, hide_index=True)
|
| 726 |
-
|
| 727 |
-
# --- Game outcomes ---
|
| 728 |
-
with st.expander("Game outcomes", expanded=False):
|
| 729 |
-
game_outcomes_df = read_game_outcomes(conn)
|
| 730 |
-
st.write(f"Rows: {len(game_outcomes_df)}")
|
| 731 |
-
if not game_outcomes_df.empty:
|
| 732 |
-
st.dataframe(game_outcomes_df.tail(20), use_container_width=True, hide_index=True)
|
| 733 |
-
|
| 734 |
-
# --- Recommendation logs ---
|
| 735 |
-
with st.expander("Recommendation logs", expanded=False):
|
| 736 |
-
rec_logs_df = read_table(conn, "recommendation_logs")
|
| 737 |
-
st.write(f"Rows: {len(rec_logs_df)}")
|
| 738 |
-
if not rec_logs_df.empty:
|
| 739 |
-
st.dataframe(rec_logs_df.tail(20), use_container_width=True, hide_index=True)
|
| 740 |
-
|
| 741 |
-
# --- Recommendation audit ---
|
| 742 |
-
with st.expander("Recommendation audit", expanded=False):
|
| 743 |
-
audit_df = read_recommendation_audit_view(conn)
|
| 744 |
-
st.write(f"Rows: {len(audit_df)}")
|
| 745 |
-
if not audit_df.empty:
|
| 746 |
-
audit_display_cols = [c for c in [
|
| 747 |
-
"created_at", "game_pk", "away_team", "home_team", "slot", "batter_name",
|
| 748 |
-
"fair_hr_odds", "book_hr_odds", "adjusted_edge", "confidence",
|
| 749 |
-
"recommendation_tier", "realized_hr", "graded_at", "outcome_source",
|
| 750 |
-
] if c in audit_df.columns]
|
| 751 |
-
st.dataframe(audit_df[audit_display_cols].tail(20), use_container_width=True, hide_index=True)
|
| 752 |
-
|
| 753 |
-
# --- Batter prop audit ---
|
| 754 |
-
with st.expander("Batter prop audit", expanded=False):
|
| 755 |
-
batter_audit_df = read_batter_prop_audit_view(conn)
|
| 756 |
-
st.write(f"Rows: {len(batter_audit_df)}")
|
| 757 |
-
if not batter_audit_df.empty:
|
| 758 |
-
st.dataframe(batter_audit_df.tail(20), use_container_width=True, hide_index=True)
|
| 759 |
-
|
| 760 |
# ------------------------------------------------------------------
|
| 761 |
# SECTION 7 — Export
|
| 762 |
# ------------------------------------------------------------------
|
|
@@ -807,43 +813,49 @@ def render_debug(
|
|
| 807 |
# ------------------------------------------------------------------
|
| 808 |
st.subheader("Model Evaluation Metrics")
|
| 809 |
|
| 810 |
-
|
| 811 |
-
|
| 812 |
-
|
| 813 |
-
|
| 814 |
-
|
| 815 |
-
eval_tables = [
|
| 816 |
-
("HR Probability Calibration", build_hr_calibration_table(audit_df)),
|
| 817 |
-
("Edge Bucket Performance", build_edge_bucket_table(audit_df)),
|
| 818 |
-
("Confidence Bucket", build_confidence_table(audit_df)),
|
| 819 |
-
("Recommendation Tier", build_tier_performance_table(audit_df)),
|
| 820 |
-
("Global ERE", build_ere_table(audit_df)),
|
| 821 |
-
("ERE by Edge Bucket", build_ere_by_edge_bucket_table(audit_df)),
|
| 822 |
-
("ERE by Confidence", build_ere_by_confidence_bucket_table(audit_df)),
|
| 823 |
-
("ERE by Tier", build_ere_by_tier_table(audit_df)),
|
| 824 |
-
("CLV Summary", build_clv_table(audit_df)),
|
| 825 |
-
("CLV by Tier", build_clv_by_tier_table(audit_df)),
|
| 826 |
-
]
|
| 827 |
|
| 828 |
-
|
| 829 |
-
|
| 830 |
-
|
| 831 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 832 |
|
| 833 |
-
|
| 834 |
-
try:
|
| 835 |
-
batter_audit_df_eval = read_batter_prop_audit_view(conn)
|
| 836 |
-
for title, fn in [
|
| 837 |
-
("Batter HR Rate by Tier", build_batter_hr_tier_table),
|
| 838 |
-
("Batter HR Rate by Confidence", build_batter_hr_confidence_table),
|
| 839 |
-
("Batter HR Rate by Edge", build_batter_hr_edge_table),
|
| 840 |
-
]:
|
| 841 |
-
tbl = fn(batter_audit_df_eval)
|
| 842 |
if not tbl.empty:
|
| 843 |
st.write(title)
|
| 844 |
st.dataframe(tbl, use_container_width=True, hide_index=True)
|
| 845 |
-
|
| 846 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 847 |
|
| 848 |
# Scores raw status (diagnostic)
|
| 849 |
if not scores_df.empty and "status" in scores_df.columns:
|
|
|
|
| 641 |
# ------------------------------------------------------------------
|
| 642 |
st.subheader("Data Inventory")
|
| 643 |
|
| 644 |
+
if "dbg_lib_loaded" not in st.session_state:
|
| 645 |
+
st.session_state["dbg_lib_loaded"] = False
|
| 646 |
if st.button("Load Data Library", key="dbg_load_inventory"):
|
| 647 |
+
st.session_state["dbg_lib_loaded"] = True
|
| 648 |
+
|
| 649 |
+
if st.session_state["dbg_lib_loaded"]:
|
| 650 |
with st.expander("All database tables (row counts)", expanded=True):
|
| 651 |
inv_df = _query_db_inventory(conn)
|
| 652 |
if inv_df.empty:
|
|
|
|
| 677 |
elif overlap and "error" in overlap:
|
| 678 |
st.warning(f"Overlap query error: {overlap['error']}")
|
| 679 |
|
| 680 |
+
# --- Batter prop outcomes ---
|
| 681 |
+
with st.expander("Batter prop outcomes", expanded=False):
|
| 682 |
+
batter_prop_outcomes_df = read_batter_prop_outcomes(conn)
|
| 683 |
+
st.write(f"Rows: {len(batter_prop_outcomes_df)}")
|
| 684 |
+
if not batter_prop_outcomes_df.empty:
|
| 685 |
+
display_cols = [c for c in [
|
| 686 |
+
"created_at", "graded_at", "game_pk", "slot", "batter_name",
|
| 687 |
+
"fair_hr_odds", "book_hr_odds", "adjusted_edge", "confidence",
|
| 688 |
+
"recommendation_tier", "realized_hit", "realized_hr", "realized_tb2p",
|
| 689 |
+
"grade_status", "outcome_source",
|
| 690 |
+
] if c in batter_prop_outcomes_df.columns]
|
| 691 |
+
st.dataframe(batter_prop_outcomes_df[display_cols].tail(30), use_container_width=True, hide_index=True)
|
| 692 |
+
|
| 693 |
+
# --- Game outcomes ---
|
| 694 |
+
with st.expander("Game outcomes", expanded=False):
|
| 695 |
+
game_outcomes_df = read_game_outcomes(conn)
|
| 696 |
+
st.write(f"Rows: {len(game_outcomes_df)}")
|
| 697 |
+
if not game_outcomes_df.empty:
|
| 698 |
+
st.dataframe(game_outcomes_df.tail(20), use_container_width=True, hide_index=True)
|
| 699 |
+
|
| 700 |
+
# --- Recommendation logs ---
|
| 701 |
+
with st.expander("Recommendation logs", expanded=False):
|
| 702 |
+
rec_logs_df = read_table(conn, "recommendation_logs")
|
| 703 |
+
st.write(f"Rows: {len(rec_logs_df)}")
|
| 704 |
+
if not rec_logs_df.empty:
|
| 705 |
+
st.dataframe(rec_logs_df.tail(20), use_container_width=True, hide_index=True)
|
| 706 |
+
|
| 707 |
+
# --- Recommendation audit ---
|
| 708 |
+
with st.expander("Recommendation audit", expanded=False):
|
| 709 |
+
audit_df = read_recommendation_audit_view(conn)
|
| 710 |
+
st.write(f"Rows: {len(audit_df)}")
|
| 711 |
+
if not audit_df.empty:
|
| 712 |
+
audit_display_cols = [c for c in [
|
| 713 |
+
"created_at", "game_pk", "away_team", "home_team", "slot", "batter_name",
|
| 714 |
+
"fair_hr_odds", "book_hr_odds", "adjusted_edge", "confidence",
|
| 715 |
+
"recommendation_tier", "realized_hr", "graded_at", "outcome_source",
|
| 716 |
+
] if c in audit_df.columns]
|
| 717 |
+
st.dataframe(audit_df[audit_display_cols].tail(20), use_container_width=True, hide_index=True)
|
| 718 |
+
|
| 719 |
+
# --- Batter prop audit ---
|
| 720 |
+
with st.expander("Batter prop audit", expanded=False):
|
| 721 |
+
batter_audit_df = read_batter_prop_audit_view(conn)
|
| 722 |
+
st.write(f"Rows: {len(batter_audit_df)}")
|
| 723 |
+
if not batter_audit_df.empty:
|
| 724 |
+
st.dataframe(batter_audit_df.tail(20), use_container_width=True, hide_index=True)
|
| 725 |
+
|
| 726 |
+
else:
|
| 727 |
+
st.caption("Please allow a few moments to load the data library..")
|
| 728 |
|
| 729 |
# --- Simulator raw rows ---
|
| 730 |
with st.expander("Simulator raw rows", expanded=False):
|
|
|
|
| 763 |
else:
|
| 764 |
st.info("No prepared live games.")
|
| 765 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 766 |
# ------------------------------------------------------------------
|
| 767 |
# SECTION 7 — Export
|
| 768 |
# ------------------------------------------------------------------
|
|
|
|
| 813 |
# ------------------------------------------------------------------
|
| 814 |
st.subheader("Model Evaluation Metrics")
|
| 815 |
|
| 816 |
+
if "dbg_eval_metrics_loaded" not in st.session_state:
|
| 817 |
+
st.session_state["dbg_eval_metrics_loaded"] = False
|
| 818 |
+
if st.button("Load Evaluation Metrics", key="dbg_load_eval_metrics"):
|
| 819 |
+
st.session_state["dbg_eval_metrics_loaded"] = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 820 |
|
| 821 |
+
if st.session_state["dbg_eval_metrics_loaded"]:
|
| 822 |
+
try:
|
| 823 |
+
audit_df = read_recommendation_audit_view(conn)
|
| 824 |
+
except Exception:
|
| 825 |
+
audit_df = pd.DataFrame()
|
| 826 |
+
|
| 827 |
+
eval_tables = [
|
| 828 |
+
("HR Probability Calibration", build_hr_calibration_table(audit_df)),
|
| 829 |
+
("Edge Bucket Performance", build_edge_bucket_table(audit_df)),
|
| 830 |
+
("Confidence Bucket", build_confidence_table(audit_df)),
|
| 831 |
+
("Recommendation Tier", build_tier_performance_table(audit_df)),
|
| 832 |
+
("Global ERE", build_ere_table(audit_df)),
|
| 833 |
+
("ERE by Edge Bucket", build_ere_by_edge_bucket_table(audit_df)),
|
| 834 |
+
("ERE by Confidence", build_ere_by_confidence_bucket_table(audit_df)),
|
| 835 |
+
("ERE by Tier", build_ere_by_tier_table(audit_df)),
|
| 836 |
+
("CLV Summary", build_clv_table(audit_df)),
|
| 837 |
+
("CLV by Tier", build_clv_by_tier_table(audit_df)),
|
| 838 |
+
]
|
| 839 |
|
| 840 |
+
for title, tbl in eval_tables:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 841 |
if not tbl.empty:
|
| 842 |
st.write(title)
|
| 843 |
st.dataframe(tbl, use_container_width=True, hide_index=True)
|
| 844 |
+
|
| 845 |
+
# Batter-specific metrics
|
| 846 |
+
try:
|
| 847 |
+
batter_audit_df_eval = read_batter_prop_audit_view(conn)
|
| 848 |
+
for title, fn in [
|
| 849 |
+
("Batter HR Rate by Tier", build_batter_hr_tier_table),
|
| 850 |
+
("Batter HR Rate by Confidence", build_batter_hr_confidence_table),
|
| 851 |
+
("Batter HR Rate by Edge", build_batter_hr_edge_table),
|
| 852 |
+
]:
|
| 853 |
+
tbl = fn(batter_audit_df_eval)
|
| 854 |
+
if not tbl.empty:
|
| 855 |
+
st.write(title)
|
| 856 |
+
st.dataframe(tbl, use_container_width=True, hide_index=True)
|
| 857 |
+
except Exception:
|
| 858 |
+
pass
|
| 859 |
|
| 860 |
# Scores raw status (diagnostic)
|
| 861 |
if not scores_df.empty and "status" in scores_df.columns:
|