@@ -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