Skip to content

Commit ce00c5d

Browse files
committed
app.py
1 parent d1901df commit ce00c5d

1 file changed

Lines changed: 31 additions & 55 deletions

File tree

app.py

Lines changed: 31 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -192,57 +192,9 @@ def load_ablation_results():
192192
m1, m2, m3, m4, m5 = st.columns(5)
193193
m1.metric("Records ingested", "281M")
194194
m2.metric("Datasets", "3", help="UK-DALE, LCL, SSEN")
195-
m3.metric("Tests passing", "212")
195+
m3.metric("Tests passing", "323")
196196
m4.metric("GNN accuracy", "98.33%")
197-
m5.metric("Inference latency", "16.56 ms")
198-
199-
st.divider()
200-
201-
# -- Research timeline --
202-
st.subheader("Research timeline")
203-
204-
milestones = [
205-
("Sep 2025", "AZR self-play experiments"),
206-
("Oct-Dec 2025", "Negative result discovery"),
207-
("Jan 2026", "Pivot to GNN verification"),
208-
("Mar 2026", "Hybrid verifier integration"),
209-
("Mar 2026", "Evaluation complete"),
210-
]
211-
212-
fig_tl = go.Figure()
213-
xs = list(range(len(milestones)))
214-
labels = [m[0] for m in milestones]
215-
descriptions = [m[1] for m in milestones]
216-
217-
fig_tl.add_trace(go.Scatter(
218-
x=xs, y=[0] * len(xs),
219-
mode="lines+markers+text",
220-
marker=dict(size=14, color=STEEL, symbol="circle"),
221-
line=dict(color=STEEL, width=2),
222-
text=labels,
223-
textposition="top center",
224-
textfont=dict(size=11, color="#E0E4E8"),
225-
hovertext=descriptions,
226-
hoverinfo="text",
227-
showlegend=False,
228-
))
229-
230-
for i, desc in enumerate(descriptions):
231-
fig_tl.add_annotation(
232-
x=i, y=-0.15, text=desc,
233-
showarrow=False, font=dict(size=10, color="#A0AAB4"),
234-
xanchor="center",
235-
)
236-
237-
fig_tl.update_layout(
238-
template=PLOTLY_TEMPLATE,
239-
height=160,
240-
margin=dict(l=20, r=20, t=10, b=60),
241-
xaxis=dict(showgrid=False, showticklabels=False, zeroline=False),
242-
yaxis=dict(showgrid=False, showticklabels=False, zeroline=False, range=[-0.4, 0.3]),
243-
**DARK_LAYOUT,
244-
)
245-
st.plotly_chart(fig_tl, use_container_width=True)
197+
m5.metric("Inference latency", "18.70 ms")
246198

247199
st.divider()
248200

@@ -300,7 +252,7 @@ def _arrow(x0, y0, x1, y1, color=A_GREY, width=1.5, dash=None):
300252

301253
_rect(10.5, 20.4, 14.5, 21.6, A_LIGHT, opacity=0.08)
302254
_label(12.5, 21.2, "SSEN graph topology", A_LIGHT, size=12, bold=True)
303-
_label(12.5, 20.7, "G(V, E): 44 nodes, 60 edges, 3 types", A_GREY, size=9)
255+
_label(12.5, 20.7, "G(V, E): 44 nodes, 60 edges, 3 types (GNN slice)", A_GREY, size=9)
304256

305257
# Arrows from inputs down
306258
_arrow(3.5, 20.4, 3.5, 19.6) # forecast -> physics
@@ -676,6 +628,11 @@ def _arrow(x0, y0, x1, y1, color=A_GREY, width=1.5, dash=None):
676628
s3.metric("Primary substations", int((node_types == 0).sum()))
677629
s4.metric("LV feeders", int((node_types == 2).sum()))
678630

631+
st.caption(
632+
f"Full SSEN topology ({num_nodes} node-type entries, {num_edges} edges). "
633+
f"The GNN model operates on a {GNN_NUM_NODES}-node compatible slice "
634+
f"(see architecture diagram in System Overview)."
635+
)
679636
st.divider()
680637

681638
# Build layout with hierarchy
@@ -794,6 +751,15 @@ def _arrow(x0, y0, x1, y1, color=A_GREY, width=1.5, dash=None):
794751
name=type_names[ntype],
795752
))
796753

754+
# Add explicit legend entry for anomalous nodes when cascade is active
755+
if np.any(anomaly_scores > 0.01):
756+
fig_net.add_trace(go.Scatter(
757+
x=[None], y=[None],
758+
mode="markers",
759+
marker=dict(size=12, color="#E05030", line=dict(width=1, color=WHITE)),
760+
name="Anomalous node",
761+
))
762+
797763
fig_net.update_layout(
798764
template=PLOTLY_TEMPLATE,
799765
height=500,
@@ -859,7 +825,15 @@ def _arrow(x0, y0, x1, y1, color=A_GREY, width=1.5, dash=None):
859825
with left_abl:
860826
st.subheader("Component ablation")
861827

862-
component = ablation_results.get("component_isolation", {})
828+
# Use benchmark configs (n=500) for configs shared with bar chart,
829+
# plus ablation-only pairwise combos (n=200) for completeness.
830+
ablation_keys = {"baseline", "physics_only", "gnn_only", "cascade_only", "hybrid_full"}
831+
component = {k: v for k, v in configs.items() if k in ablation_keys}
832+
abl_comp = ablation_results.get("component_isolation", {})
833+
for k, v in abl_comp.items():
834+
if k not in component:
835+
component[k] = v
836+
863837
abl_rows = []
864838
for name, metrics in sorted(
865839
component.items(),
@@ -883,10 +857,12 @@ def _arrow(x0, y0, x1, y1, color=A_GREY, width=1.5, dash=None):
883857
with right_abl:
884858
st.subheader("Component insights")
885859

860+
# Pull cascade ROC-AUC from benchmark data for consistency
861+
cascade_roc = configs.get("cascade_only", {}).get("roc_auc", 0)
886862
st.markdown(
887863
"- **GNN** (GATv2Conv) is the primary discriminator -- "
888864
"ROC-AUC=1.0 alone and in all combinations\n"
889-
"- **Cascade logic** adds real topological signal (ROC-AUC=0.89) "
865+
f"- **Cascade logic** adds real topological signal (ROC-AUC={cascade_roc:.4f}) "
890866
"by detecting neighbor propagation patterns\n"
891867
"- **Physics layer** provides no discrimination on synthetic data "
892868
"(ROC-AUC=0.50) -- expected since test data uses normalised "
@@ -1008,8 +984,8 @@ def _arrow(x0, y0, x1, y1, color=A_GREY, width=1.5, dash=None):
1008984
st.header("Self-play training")
1009985

1010986
st.markdown(
1011-
"The propose-solve-verify loop follows the Absolute Zero Reasoner (AZR) "
1012-
"paradigm. Experiments showed this approach does not outperform baselines "
987+
"The propose-solve-verify loop uses a self-play training "
988+
"approach. Experiments showed this approach does not outperform baselines "
1013989
"on periodic time series with strong temporal patterns. This negative "
1014990
"result motivated the pivot to topology-aware GNN verification."
1015991
)

0 commit comments

Comments
 (0)