Skip to content

Commit 37b4bae

Browse files
committed
fix(dashboard): resolve 5 issues from testing
1. CRITICAL: Fix KeyError in _build_adjacency() - edge_index contains indices up to 48 but num_nodes was 44. Use max(num_nodes, max_idx+1) to size the adjacency dict. Unblocks Tab 2, Tab 3, Tab 5. 2. Fix truncated datasets metric - use value="3" with help tooltip 3. Wrap Tab 2 generation in try/except to prevent error bleed to Tab 3 4. Add annotation to early-exit sweep chart explaining post-fix flatness 5. Add curriculum level chart to Tab 5 training results (2x2 grid)
1 parent 961bc43 commit 37b4bae

2 files changed

Lines changed: 73 additions & 26 deletions

File tree

app.py

Lines changed: 70 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ def load_ablation_results():
161161
# -- Metric cards --
162162
m1, m2, m3, m4, m5 = st.columns(5)
163163
m1.metric("Records ingested", "281M")
164-
m2.metric("Datasets", "3 (UK-DALE, LCL, SSEN)")
164+
m2.metric("Datasets", "3", help="UK-DALE, LCL, SSEN")
165165
m3.metric("Tests passing", "212")
166166
m4.metric("GNN accuracy", "98.33%")
167167
m5.metric("Inference latency", "16.56 ms")
@@ -303,6 +303,7 @@ def load_ablation_results():
303303
horizon = c3.selectbox("Forecast horizon (intervals)", [24, 48, 96], index=1)
304304

305305
if st.button("Generate and evaluate"):
306+
try:
306307
with st.spinner("Running hybrid verifier pipeline..."):
307308
# Generate scenario
308309
np.random.seed(int(time.time()) % 10000)
@@ -319,9 +320,15 @@ def load_ablation_results():
319320
# Apply scenario to create forecast
320321
forecast_1d = scenario.apply_to_timeseries(context[:horizon])
321322

323+
# Ensure forecast covers enough nodes for the verifier
324+
n_eval = len(graph_data.node_type)
325+
eval_input = np.zeros(n_eval)
326+
n_copy = min(len(forecast_1d), n_eval)
327+
eval_input[:n_copy] = forecast_1d[:n_copy]
328+
322329
# Run through hybrid verifier
323330
reward, details = verifier.evaluate(
324-
forecast_1d[:graph_data.num_nodes],
331+
eval_input,
325332
scenario=scenario,
326333
return_details=True,
327334
)
@@ -405,7 +412,7 @@ def load_ablation_results():
405412
)
406413

407414
early_exits = breakdown.get("early_exit_count", 0)
408-
total_nodes = graph_data.num_nodes
415+
total_nodes = len(graph_data.node_type)
409416
st.markdown(f"**Early exits:** {early_exits}/{total_nodes} nodes")
410417

411418
# -- Expandable details --
@@ -433,6 +440,11 @@ def load_ablation_results():
433440
meta_display["affected_nodes"] = dict(list(an.items())[:10])
434441
meta_display["affected_nodes_truncated"] = f"...{len(an)} total"
435442
st.json(meta_display)
443+
except Exception as e:
444+
st.error(f"Anomaly detection failed: {e}")
445+
import traceback
446+
with st.expander("Traceback"):
447+
st.code(traceback.format_exc())
436448

437449

438450
# =========================================================================
@@ -708,6 +720,15 @@ def load_ablation_results():
708720
marker=dict(size=8),
709721
yaxis="y2",
710722
))
723+
fig_sweep.add_annotation(
724+
text="Post-fix: voltage auto-detection heuristic prevents<br>early-exit degradation. See debugging narrative below.",
725+
xref="paper", yref="paper",
726+
x=0.5, y=0.5,
727+
showarrow=False,
728+
font=dict(size=11, color="#A0AAB4"),
729+
bgcolor="rgba(30,40,50,0.7)",
730+
borderpad=8,
731+
)
711732
fig_sweep.update_layout(
712733
template=PLOTLY_TEMPLATE,
713734
height=300,
@@ -856,13 +877,14 @@ def load_ablation_results():
856877
status.markdown("Training complete")
857878

858879
# -- Charts --
859-
chart_left, chart_right = st.columns(2)
880+
row1_left, row1_right = st.columns(2)
860881

861-
with chart_left:
882+
eps_x = list(range(1, num_episodes + 1))
883+
884+
with row1_left:
862885
fig_rew = go.Figure()
863886
fig_rew.add_trace(go.Scatter(
864-
x=list(range(1, num_episodes + 1)),
865-
y=rewards_history,
887+
x=eps_x, y=rewards_history,
866888
mode="lines+markers",
867889
name="Mean reward",
868890
line=dict(color=TEAL, width=2),
@@ -879,11 +901,10 @@ def load_ablation_results():
879901
)
880902
st.plotly_chart(fig_rew, use_container_width=True)
881903

882-
with chart_right:
904+
with row1_right:
883905
fig_loss = go.Figure()
884906
fig_loss.add_trace(go.Scatter(
885-
x=list(range(1, num_episodes + 1)),
886-
y=solver_losses,
907+
x=eps_x, y=solver_losses,
887908
mode="lines+markers",
888909
name="Solver loss",
889910
line=dict(color=RED, width=2),
@@ -900,27 +921,51 @@ def load_ablation_results():
900921
)
901922
st.plotly_chart(fig_loss, use_container_width=True)
902923

903-
# Scenario distribution
904-
if scenario_types:
905-
type_counts = {}
906-
for t in scenario_types:
907-
type_counts[t] = type_counts.get(t, 0) + 1
924+
# Curriculum level chart
925+
row2_left, row2_right = st.columns(2)
908926

909-
fig_dist = go.Figure(go.Bar(
910-
x=list(type_counts.keys()),
911-
y=list(type_counts.values()),
912-
marker_color=CHART_COLORS[:len(type_counts)],
927+
with row2_left:
928+
fig_cur = go.Figure()
929+
fig_cur.add_trace(go.Scatter(
930+
x=eps_x, y=curriculum_levels,
931+
mode="lines+markers",
932+
name="Curriculum level",
933+
line=dict(color=STEEL, width=2),
934+
marker=dict(size=6),
913935
))
914-
fig_dist.update_layout(
936+
fig_cur.update_layout(
915937
template=PLOTLY_TEMPLATE,
916-
title="Scenario type distribution",
917-
height=250,
938+
title="Proposer difficulty per episode",
939+
height=300,
918940
margin=dict(l=50, r=20, t=40, b=40),
919-
xaxis_title="Scenario type",
920-
yaxis_title="Count",
941+
xaxis_title="Episode",
942+
yaxis_title="Difficulty",
921943
**DARK_LAYOUT,
922944
)
923-
st.plotly_chart(fig_dist, use_container_width=True)
945+
st.plotly_chart(fig_cur, use_container_width=True)
946+
947+
with row2_right:
948+
# Scenario distribution
949+
if scenario_types:
950+
type_counts = {}
951+
for t in scenario_types:
952+
type_counts[t] = type_counts.get(t, 0) + 1
953+
954+
fig_dist = go.Figure(go.Bar(
955+
x=list(type_counts.keys()),
956+
y=list(type_counts.values()),
957+
marker_color=CHART_COLORS[:len(type_counts)],
958+
))
959+
fig_dist.update_layout(
960+
template=PLOTLY_TEMPLATE,
961+
title="Scenario type distribution",
962+
height=300,
963+
margin=dict(l=50, r=20, t=40, b=40),
964+
xaxis_title="Scenario type",
965+
yaxis_title="Count",
966+
**DARK_LAYOUT,
967+
)
968+
st.plotly_chart(fig_dist, use_container_width=True)
924969

925970
except Exception as e:
926971
st.error(f"Training failed: {e}")

src/fyp/selfplay/proposer.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,9 @@ def _build_adjacency(
666666
Returns:
667667
Dict mapping each node index to a list of neighbor indices.
668668
"""
669-
adj: dict[int, list[int]] = {i: [] for i in range(num_nodes)}
669+
max_idx = max(edge_index[0].max(), edge_index[1].max()).item()
670+
actual_num = max(num_nodes, max_idx + 1)
671+
adj: dict[int, list[int]] = {i: [] for i in range(actual_num)}
670672
src = edge_index[0].tolist()
671673
dst = edge_index[1].tolist()
672674
for s, d in zip(src, dst):

0 commit comments

Comments
 (0)