@@ -75,9 +75,11 @@ def build_project_summary_table(analysis_root: Path) -> pd.DataFrame:
7575 """
7676 Build a consolidated summary across all scenarios under analysis_root.
7777
78- Expected per- scenario files produced by `netlab metrics` (metrics_cmd.run_metrics) :
78+ Each scenario directory should contain :
7979 - alpha_summary.json
80- - bac_summary.json ({"per_seed": ..., "tail": {p50,p90,p99,p999,p9999,auc_norm}})
80+ - bac_summary.json with keys:
81+ {"per_seed": ..., "tail": {p50,p90,p99,p999,p9999,auc_norm,
82+ bw_p90_pct,bw_p95_pct,bw_p99_pct,bw_p999_pct,bw_p9999_pct}}
8183 - latency_summary.csv (per-seed rows with columns p50..p9999)
8284 - costpower_summary.csv (per-seed rows with capex/power and normalizations)
8385 """
@@ -91,6 +93,8 @@ def build_project_summary_table(analysis_root: Path) -> pd.DataFrame:
9193 continue
9294
9395 alpha_med = float ("nan" )
96+ bw_p90 = float ("nan" )
97+ bw_p95 = float ("nan" )
9498 bw_p99 = float ("nan" )
9599 bw_p999 = float ("nan" )
96100 auc_norm = float ("nan" )
@@ -100,7 +104,6 @@ def build_project_summary_table(analysis_root: Path) -> pd.DataFrame:
100104 lat_SLO_1_2_drop = float ("nan" )
101105 lat_best_path_drop = float ("nan" )
102106 lat_WES_delta = float ("nan" )
103- # paircap removed from project table (kept per-seed only)
104107 usd_per_gbit_p999 = float ("nan" )
105108 watt_per_gbit_p999 = float ("nan" )
106109 usd_per_gbit_offered = float ("nan" )
@@ -133,7 +136,17 @@ def build_project_summary_table(analysis_root: Path) -> pd.DataFrame:
133136 b = json .loads (bp .read_text (encoding = "utf-8" ))
134137 tail = b .get ("tail" , {}) or {}
135138 auc_norm = _safe_float (tail .get ("auc_norm" ))
136- # New: bandwidth at probability (ratio to baseline/offered)
139+ # bandwidth at probability (ratio to baseline/offered)
140+ bw_p90 = (
141+ _safe_float (tail .get ("bw_p90_pct" ))
142+ if "bw_p90_pct" in tail
143+ else float ("nan" )
144+ )
145+ bw_p95 = (
146+ _safe_float (tail .get ("bw_p95_pct" ))
147+ if "bw_p95_pct" in tail
148+ else float ("nan" )
149+ )
137150 bw_p99 = (
138151 _safe_float (tail .get ("bw_p99_pct" ))
139152 if "bw_p99_pct" in tail
@@ -145,7 +158,7 @@ def build_project_summary_table(analysis_root: Path) -> pd.DataFrame:
145158 else float ("nan" )
146159 )
147160
148- # Latency cross-seed medians (new format)
161+ # Latency cross-seed medians
149162 lp = scen_dir / "latency_summary.csv"
150163 if lp .exists ():
151164 df_lat = pd .read_csv (lp )
@@ -179,8 +192,6 @@ def _med(col: str, _df_lat: pd.DataFrame = df_lat) -> float:
179192 # seeds counted as number of rows if not set by costpower
180193 seeds_count = max (seeds_count , int (df_ns .shape [0 ]))
181194
182- # PairCap: omitted from project table (expensive, weak signal)
183-
184195 # Cost/Power medians across seeds
185196 cpp = scen_dir / "costpower_summary.csv"
186197 # Iteration operations per-iteration medians across seeds
@@ -240,6 +251,8 @@ def _med_col(name: str, _df_io: pd.DataFrame = df_io) -> float:
240251 "node_count" : node_count ,
241252 "link_count" : link_count ,
242253 "alpha_star" : alpha_med ,
254+ "bw_p90" : bw_p90 ,
255+ "bw_p95" : bw_p95 ,
243256 "bw_p99" : bw_p99 ,
244257 "bw_p999" : bw_p999 ,
245258 "bac_auc" : auc_norm ,
@@ -256,7 +269,6 @@ def _med_col(name: str, _df_io: pd.DataFrame = df_io) -> float:
256269 # tm_placement timing (seconds)
257270 "tm_duration_total_sec" : tm_duration_total_sec ,
258271 "tm_duration_per_iter_sec" : tm_duration_per_iter_sec ,
259- # paircap: intentionally omitted from project table
260272 "USD_per_Gbit_offered" : usd_per_gbit_offered ,
261273 "Watt_per_Gbit_offered" : watt_per_gbit_offered ,
262274 "USD_per_Gbit_p999" : usd_per_gbit_p999 ,
@@ -276,6 +288,8 @@ def _med_col(name: str, _df_io: pd.DataFrame = df_io) -> float:
276288 "link_count" ,
277289 "alpha_star" ,
278290 # Bandwidth-at-probability ratios (primary availability figures)
291+ "bw_p90" ,
292+ "bw_p95" ,
279293 "bw_p99" ,
280294 "bw_p999" ,
281295 "bac_auc" ,
@@ -293,7 +307,6 @@ def _med_col(name: str, _df_io: pd.DataFrame = df_io) -> float:
293307 # tm_placement timing
294308 "tm_duration_total_sec" ,
295309 "tm_duration_per_iter_sec" ,
296- # paircap removed
297310 # Cost/Power at offered and reliable
298311 "USD_per_Gbit_offered" ,
299312 "Watt_per_Gbit_offered" ,
@@ -324,6 +337,8 @@ def print_pretty_table(
324337 "node_count_r" : "nodes r" ,
325338 "link_count_r" : "links r" ,
326339 "alpha_star" : "alpha*" ,
340+ "bw_p90" : "BW@90%" ,
341+ "bw_p95" : "BW@95%" ,
327342 "bw_p99" : "BW@99%" ,
328343 "bw_p999" : "BW@99.9%" ,
329344 "bac_auc" : "BAC AUC" ,
@@ -336,7 +351,6 @@ def print_pretty_table(
336351 "spf_calls_per_iter" : "SPF/iter" ,
337352 "flows_created_per_iter" : "flows created/iter" ,
338353 "reopt_calls_per_iter" : "reopt/iter" ,
339- # paircap removed
340354 "USD_per_Gbit_offered" : "USD/Gbps offered" ,
341355 "Watt_per_Gbit_offered" : "W/Gbps offered" ,
342356 "USD_per_Gbit_p999" : "USD/Gbps p99.9" ,
@@ -365,6 +379,8 @@ def print_pretty_table(
365379 "node_count_r" : "nodes r" ,
366380 "link_count_r" : "links r" ,
367381 "alpha_star" : "alpha*" ,
382+ "bw_p90" : "BW@90%" ,
383+ "bw_p95" : "BW@95%" ,
368384 "bw_p99" : "BW@99%" ,
369385 "bw_p999" : "BW@99.9%" ,
370386 "bac_auc" : "BAC AUC" ,
@@ -377,7 +393,6 @@ def print_pretty_table(
377393 "spf_calls_per_iter" : "SPF/iter" ,
378394 "flows_created_per_iter" : "flows created/iter" ,
379395 "reopt_calls_per_iter" : "reopt/iter" ,
380- # paircap removed
381396 "USD_per_Gbit_offered" : "USD/Gbps offered" ,
382397 "Watt_per_Gbit_offered" : "W/Gbps offered" ,
383398 "USD_per_Gbit_p999" : "USD/Gbps p99.9" ,
@@ -725,6 +740,12 @@ def _g(p: float, bwp: Dict[str, Any] = bwp) -> Optional[float]:
725740 v = cast (Dict [Any , Any ], bwp ).get (p )
726741 return float (v ) if isinstance (v , (int , float )) else None
727742
743+ v90 = _g (90.0 )
744+ if v90 is not None :
745+ m ["bw_p90_pct" ] = v90
746+ v95 = _g (95.0 )
747+ if v95 is not None :
748+ m ["bw_p95_pct" ] = v95
728749 v99 = _g (99.0 )
729750 if v99 is not None :
730751 m ["bw_p99_pct" ] = v99
@@ -807,6 +828,8 @@ def build_baseline_normalized_table(
807828 rows : List [Dict [str , Any ]] = []
808829
809830 ratio_keys = [
831+ "bw_p90_pct" ,
832+ "bw_p95_pct" ,
810833 "bw_p99_pct" ,
811834 "bw_p999_pct" ,
812835 "auc_norm" ,
@@ -941,6 +964,8 @@ def _counts_per_seed(name: str) -> Dict[int, Tuple[float, float]]:
941964 "node_count_r" ,
942965 "link_count_r" ,
943966 # Ratios (≥1 better for bw/auc; ≤1 better for cost/power/lat_fail_p99)
967+ "bw_p90_pct_r" ,
968+ "bw_p95_pct_r" ,
944969 "bw_p99_pct_r" ,
945970 "bw_p999_pct_r" ,
946971 "auc_norm_r" ,
@@ -1166,6 +1191,8 @@ def _collect_normalized_per_seed(
11661191 return {}
11671192 base = full [base_name ]
11681193 ratio_keys = [
1194+ "bw_p90_pct" ,
1195+ "bw_p95_pct" ,
11691196 "bw_p99_pct" ,
11701197 "bw_p999_pct" ,
11711198 "auc_norm" ,
@@ -1237,6 +1264,8 @@ def _build_normalized_insights(analysis_root: Path) -> List[Dict[str, Any]]:
12371264 ratio_metrics = [
12381265 "node_count_r" ,
12391266 "link_count_r" ,
1267+ "bw_p90_pct_r" ,
1268+ "bw_p95_pct_r" ,
12401269 "bw_p99_pct_r" ,
12411270 "bw_p999_pct_r" ,
12421271 "auc_norm_r" ,
0 commit comments