alexandreabraham commited on
Commit
2a1a3d6
·
1 Parent(s): dee5092

KPI cards as native HTML row above the forest plot (responsive flexbox)

Browse files

- Move 3 KPI cards out of the Plotly figure into a sibling gr.HTML with
CSS flexbox layout. They now resize fluidly with the window and wrap
on narrow viewports instead of overlapping or overflowing.
- plot_significance_forest() now returns (figure, kpi_html) tuple.
- Forest plot top margin reduced 180px → 40px (cards no longer occupy
paper-y > 1.0 inside the figure).
- Cache (figures_cache.json) gains a 'forest_kpi' map alongside 'forest';
the live render path also produces the HTML on cache miss. Existing
cached entries return an empty KPI block until the next rebuild.

Files changed (3) hide show
  1. app.py +129 -62
  2. build_figures_cache.py +9 -4
  3. perfmap_hulls_override.json +1 -1
app.py CHANGED
@@ -1130,6 +1130,58 @@ body:not(.dark) #nk-light-mode-nudge { display: none !important; }
1130
  .nk-bar-plot .modebar-container,
1131
  .nk-bar-plot .js-plotly-plot .plotly .modebar { display: none !important; }
1132
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1133
  /* ===== Leaderboard table view (HTML, with per-cell rank badge) ===== */
1134
  .nk-lb-wrap { padding: 0 !important; }
1135
  .nk-empty-table {
@@ -4640,60 +4692,45 @@ def plot_significance_forest(per_dataset_df, metric="Accuracy"):
4640
  # Each card has 3 stacked annotations (kicker / big value / subline).
4641
  # Plotly inline `<span style='font-size:..'>` is unreliable, so each
4642
  # annotation sets its size via the `font=` parameter instead.
4643
- KPI_Y = 1.16
4644
- KPI_HALF_W = 0.14 # narrower leaves more gap between cards
4645
- KPI_HALF_H = 0.085
4646
- KPI_BG = "rgba(247,247,250,1)"
4647
- KPI_BORDER = "#e6e4ef"
4648
- LINE_GAP = 0.038 # tighter vertical spacing between the 3 lines
4649
- cards = [
4650
- (0.16, "LEADING GROUP", kpi_group_value, kpi_group_sub),
4651
- (0.50, "LEAD MARGIN", kpi_margin_value, kpi_margin_sub),
4652
- (0.84, "TOP-3 COVERAGE", kpi_top3_value, kpi_top3_sub),
4653
- ]
4654
- for x_center, kicker, value, sub in cards:
4655
- # Background pill
4656
- fig.add_shape(
4657
- type="rect", xref="paper", yref="paper",
4658
- x0=x_center - KPI_HALF_W, x1=x_center + KPI_HALF_W,
4659
- y0=KPI_Y - KPI_HALF_H, y1=KPI_Y + KPI_HALF_H,
4660
- fillcolor=KPI_BG, line=dict(color=KPI_BORDER, width=1),
4661
- )
4662
- # Pick a font size that scales down for long values so they fit the
4663
- # card. Cards are ~0.28 paper-x wide; assume ~7 px per glyph at 22px.
4664
- char_count = len(str(value))
4665
- if char_count <= 14:
4666
- value_size = 22
4667
- elif char_count <= 22:
4668
- value_size = 17
4669
- elif char_count <= 32:
4670
- value_size = 14
4671
- else:
4672
- value_size = 12
4673
- # Kicker
4674
- fig.add_annotation(
4675
- text=kicker, x=x_center, y=KPI_Y + LINE_GAP,
4676
- xref="paper", yref="paper", showarrow=False,
4677
- xanchor="center", yanchor="middle", align="center",
4678
- font=dict(size=12, color="#5d6c7b",
4679
- family="Geist Mono, monospace"),
4680
- )
4681
- # Big value
4682
- fig.add_annotation(
4683
- text=f"<b>{value}</b>", x=x_center, y=KPI_Y,
4684
- xref="paper", yref="paper", showarrow=False,
4685
- xanchor="center", yanchor="middle", align="center",
4686
- font=dict(size=value_size, color="#060510",
4687
- family="BDO Grotesk, Helvetica Neue, sans-serif"),
4688
- )
4689
- # Sub
4690
- fig.add_annotation(
4691
- text=sub, x=x_center, y=KPI_Y - LINE_GAP,
4692
- xref="paper", yref="paper", showarrow=False,
4693
- xanchor="center", yanchor="middle", align="center",
4694
- font=dict(size=12, color="#5d6c7b",
4695
- family="BDO Grotesk, Helvetica Neue, sans-serif"),
4696
- )
4697
 
4698
  # Axes
4699
  raw_min = min(ci_low.get(m, v) for m, v in zip(y_order, dot_x) if v == v)
@@ -4746,8 +4783,16 @@ def plot_significance_forest(per_dataset_df, metric="Accuracy"):
4746
  )
4747
 
4748
  fig.update_layout(
 
 
 
 
4749
  height=max(420, 36 * len(y_order) + 240),
4750
- margin=dict(l=240, r=110, t=180, b=70),
 
 
 
 
4751
  template="plotly_white",
4752
  plot_bgcolor="rgba(0,0,0,0)",
4753
  paper_bgcolor="rgba(0,0,0,0)",
@@ -4764,7 +4809,7 @@ def plot_significance_forest(per_dataset_df, metric="Accuracy"):
4764
  f"&nbsp;&nbsp;<span style='color:#5d6c7b;font-size:11px'>· 95% CI</span>"),
4765
  showarrow=False, font=dict(size=12),
4766
  )
4767
- return fig
4768
 
4769
 
4770
  def plot_winrate_matrix(per_dataset_df, metric="Accuracy"):
@@ -5316,11 +5361,16 @@ with gr.Blocks(css=css, theme=nk_theme, head=NAV_HEAD_JS) as demo:
5316
  .reset_index()
5317
  )
5318
 
 
 
 
 
 
 
 
 
5319
  ranking_plot = gr.Plot(
5320
- value=plot_significance_forest(
5321
- public_enter_per_dataset,
5322
- metric="Accuracy",
5323
- ),
5324
  show_label=False,
5325
  elem_classes=["nk-bar-plot"],
5326
  visible=True,
@@ -5960,10 +6010,13 @@ with gr.Blocks(css=css, theme=nk_theme, head=NAV_HEAD_JS) as demo:
5960
  # Bars view = significance forest plot. Cached at build
5961
  # time (build_figures_cache.py) because the live render
5962
  # runs Wilcoxon-Holm + 1000-iter paired bootstrap, ~30 s
5963
- # on Spaces CPU per metric change.
 
 
5964
  import plotly.graph_objects as _go
5965
 
5966
  fig = _go.Figure(cache_root["forest"][metric])
 
5967
  return (
5968
  gr.update(visible=True, value=fig),
5969
  hide,
@@ -5973,6 +6026,7 @@ with gr.Blocks(css=css, theme=nk_theme, head=NAV_HEAD_JS) as demo:
5973
  hide,
5974
  hide,
5975
  hide,
 
5976
  count_label,
5977
  )
5978
 
@@ -6017,6 +6071,7 @@ with gr.Blocks(css=css, theme=nk_theme, head=NAV_HEAD_JS) as demo:
6017
  hide,
6018
  hide,
6019
  hide,
 
6020
  count_label,
6021
  )
6022
 
@@ -6041,6 +6096,7 @@ with gr.Blocks(css=css, theme=nk_theme, head=NAV_HEAD_JS) as demo:
6041
  hide,
6042
  hide,
6043
  gr.update(visible=True, value=fig),
 
6044
  count_label,
6045
  )
6046
 
@@ -6060,11 +6116,12 @@ with gr.Blocks(css=css, theme=nk_theme, head=NAV_HEAD_JS) as demo:
6060
  hide,
6061
  hide,
6062
  hide,
 
6063
  count_label,
6064
  )
6065
  # Bars view now renders the significance forest plot
6066
  # (Wilcoxon-Holm leading-group highlight + CI lollipops).
6067
- fig = plot_significance_forest(
6068
  filtered_per_dataset, metric=metric,
6069
  )
6070
  return (
@@ -6076,6 +6133,7 @@ with gr.Blocks(css=css, theme=nk_theme, head=NAV_HEAD_JS) as demo:
6076
  hide,
6077
  hide,
6078
  hide,
 
6079
  count_label,
6080
  )
6081
 
@@ -6090,6 +6148,7 @@ with gr.Blocks(css=css, theme=nk_theme, head=NAV_HEAD_JS) as demo:
6090
  hide,
6091
  hide,
6092
  gr.update(visible=True, value=None),
 
6093
  count_label,
6094
  )
6095
  fig = plot_winrate_matrix(filtered_per_dataset, metric=metric)
@@ -6102,6 +6161,7 @@ with gr.Blocks(css=css, theme=nk_theme, head=NAV_HEAD_JS) as demo:
6102
  hide,
6103
  hide,
6104
  gr.update(visible=True, value=fig),
 
6105
  count_label,
6106
  )
6107
 
@@ -6125,6 +6185,7 @@ with gr.Blocks(css=css, theme=nk_theme, head=NAV_HEAD_JS) as demo:
6125
  hide,
6126
  hide,
6127
  hide,
 
6128
  count_label,
6129
  )
6130
 
@@ -6145,6 +6206,7 @@ with gr.Blocks(css=css, theme=nk_theme, head=NAV_HEAD_JS) as demo:
6145
  hide,
6146
  hide,
6147
  hide,
 
6148
  count_label,
6149
  )
6150
  fig = plot_dataset_comparison(
@@ -6159,6 +6221,7 @@ with gr.Blocks(css=css, theme=nk_theme, head=NAV_HEAD_JS) as demo:
6159
  hide,
6160
  hide,
6161
  hide,
 
6162
  count_label,
6163
  )
6164
 
@@ -6173,6 +6236,7 @@ with gr.Blocks(css=css, theme=nk_theme, head=NAV_HEAD_JS) as demo:
6173
  hide,
6174
  hide,
6175
  hide,
 
6176
  count_label,
6177
  )
6178
  overlay_mm, sm_mm, overlay_rk, sm_rk = plot_model_radars(
@@ -6187,6 +6251,7 @@ with gr.Blocks(css=css, theme=nk_theme, head=NAV_HEAD_JS) as demo:
6187
  gr.update(visible=True, value=overlay_rk),
6188
  gr.update(visible=True, value=sm_rk),
6189
  hide,
 
6190
  count_label,
6191
  )
6192
 
@@ -6200,6 +6265,7 @@ with gr.Blocks(css=css, theme=nk_theme, head=NAV_HEAD_JS) as demo:
6200
  hide,
6201
  hide,
6202
  hide,
 
6203
  count_label,
6204
  )
6205
 
@@ -6233,6 +6299,7 @@ with gr.Blocks(css=css, theme=nk_theme, head=NAV_HEAD_JS) as demo:
6233
  radar_overlay_rank_plot,
6234
  radar_grid_rank_plot,
6235
  winrate_plot,
 
6236
  dataset_count_md,
6237
  ],
6238
  )
 
1130
  .nk-bar-plot .modebar-container,
1131
  .nk-bar-plot .js-plotly-plot .plotly .modebar { display: none !important; }
1132
 
1133
+ /* No special width constraint on the forest plot: Plotly autosize fills
1134
+ the container and the chart redraws on window resize. The KPI cards live
1135
+ in a sibling HTML row (.nk-bar-kpis), not inside the figure, so they get
1136
+ real CSS flexbox responsiveness. */
1137
+ .nk-bar-kpi-wrap { padding: 0 !important; }
1138
+ .nk-bar-kpis {
1139
+ display: flex;
1140
+ gap: 12px;
1141
+ flex-wrap: wrap;
1142
+ padding: 0 8px 12px;
1143
+ justify-content: center;
1144
+ }
1145
+ .nk-bar-kpi {
1146
+ flex: 1 1 240px;
1147
+ min-width: 200px;
1148
+ max-width: 360px;
1149
+ background: #f7f7fa;
1150
+ border: 1px solid #e6e4ef;
1151
+ border-radius: 8px;
1152
+ padding: 12px 16px;
1153
+ text-align: center;
1154
+ overflow: hidden;
1155
+ }
1156
+ .nk-bar-kpi-kicker {
1157
+ font-family: "Geist Mono", ui-monospace, monospace;
1158
+ font-size: 11px;
1159
+ color: #5d6c7b;
1160
+ letter-spacing: 0.04em;
1161
+ margin-bottom: 4px;
1162
+ }
1163
+ .nk-bar-kpi-value {
1164
+ font-family: "BDO Grotesk", "Helvetica Neue", sans-serif;
1165
+ font-size: 20px;
1166
+ font-weight: 700;
1167
+ color: #060510;
1168
+ line-height: 1.2;
1169
+ word-break: break-word;
1170
+ margin-bottom: 4px;
1171
+ }
1172
+ .nk-bar-kpi-sub {
1173
+ font-family: "BDO Grotesk", "Helvetica Neue", sans-serif;
1174
+ font-size: 11px;
1175
+ color: #5d6c7b;
1176
+ }
1177
+ body.dark .nk-bar-kpi {
1178
+ background: rgba(40, 35, 70, 0.4);
1179
+ border-color: rgba(255, 255, 255, 0.1);
1180
+ }
1181
+ body.dark .nk-bar-kpi-value { color: #f5f3ff; }
1182
+ body.dark .nk-bar-kpi-kicker,
1183
+ body.dark .nk-bar-kpi-sub { color: #b8b0d0; }
1184
+
1185
  /* ===== Leaderboard table view (HTML, with per-cell rank badge) ===== */
1186
  .nk-lb-wrap { padding: 0 !important; }
1187
  .nk-empty-table {
 
4692
  # Each card has 3 stacked annotations (kicker / big value / subline).
4693
  # Plotly inline `<span style='font-size:..'>` is unreliable, so each
4694
  # annotation sets its size via the `font=` parameter instead.
4695
+ #
4696
+ # Responsive sizing: the card pill is sized in paper fractions (so it
4697
+ # shrinks with the plot width), but the *text* is sized in pixels. To
4698
+ # avoid overflow on narrow plots, we shrink the value/sub text more
4699
+ # aggressively for long content AND truncate the LEADING GROUP value
4700
+ # to at most 2 model names (was 3) so common cases fit even at small
4701
+ # widths.
4702
+ # KPI cards: rendered as a sibling HTML block (not inside the figure),
4703
+ # so they get native CSS flexbox responsiveness and resize fluidly with
4704
+ # the window. The HTML is returned alongside the figure.
4705
+ if n_leaders == 1:
4706
+ kpi_group_value_display = leader_names_pretty[0]
4707
+ elif n_leaders == 2:
4708
+ kpi_group_value_display = " · ".join(leader_names_pretty)
4709
+ else:
4710
+ kpi_group_value_display = " · ".join(leader_names_pretty[:2]) + " …"
4711
+
4712
+ import html as _html
4713
+ def _esc(s):
4714
+ return _html.escape(str(s))
4715
+ kpi_html = (
4716
+ '<div class="nk-bar-kpis">'
4717
+ ' <div class="nk-bar-kpi">'
4718
+ f' <div class="nk-bar-kpi-kicker">LEADING GROUP</div>'
4719
+ f' <div class="nk-bar-kpi-value">{_esc(kpi_group_value_display)}</div>'
4720
+ f' <div class="nk-bar-kpi-sub">{_esc(kpi_group_sub)}</div>'
4721
+ ' </div>'
4722
+ ' <div class="nk-bar-kpi">'
4723
+ f' <div class="nk-bar-kpi-kicker">LEAD MARGIN</div>'
4724
+ f' <div class="nk-bar-kpi-value">{_esc(kpi_margin_value)}</div>'
4725
+ f' <div class="nk-bar-kpi-sub">{_esc(kpi_margin_sub)}</div>'
4726
+ ' </div>'
4727
+ ' <div class="nk-bar-kpi">'
4728
+ f' <div class="nk-bar-kpi-kicker">TOP-3 COVERAGE</div>'
4729
+ f' <div class="nk-bar-kpi-value">{_esc(kpi_top3_value)}</div>'
4730
+ f' <div class="nk-bar-kpi-sub">{_esc(kpi_top3_sub)}</div>'
4731
+ ' </div>'
4732
+ '</div>'
4733
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4734
 
4735
  # Axes
4736
  raw_min = min(ci_low.get(m, v) for m, v in zip(y_order, dot_x) if v == v)
 
4783
  )
4784
 
4785
  fig.update_layout(
4786
+ # Autosize: the figure fills its container at full width and shrinks
4787
+ # down to the min-width set by .nk-bar-plot's CSS rule (820px); below
4788
+ # that the wrapper scrolls horizontally rather than compressing.
4789
+ autosize=True,
4790
  height=max(420, 36 * len(y_order) + 240),
4791
+ # Left margin holds the model labels (drawn at paper-x=0 with
4792
+ # xshift=-18). 140px is enough for the longest model name (~12 chars
4793
+ # at 12px monospace ≈ 86px) plus the 18px gap. Top margin was 180px
4794
+ # to host the KPI cards; now 40px since the cards are gone.
4795
+ margin=dict(l=140, r=90, t=40, b=70),
4796
  template="plotly_white",
4797
  plot_bgcolor="rgba(0,0,0,0)",
4798
  paper_bgcolor="rgba(0,0,0,0)",
 
4809
  f"&nbsp;&nbsp;<span style='color:#5d6c7b;font-size:11px'>· 95% CI</span>"),
4810
  showarrow=False, font=dict(size=12),
4811
  )
4812
+ return fig, kpi_html
4813
 
4814
 
4815
  def plot_winrate_matrix(per_dataset_df, metric="Accuracy"):
 
5361
  .reset_index()
5362
  )
5363
 
5364
+ _init_forest_fig, _init_forest_kpi = plot_significance_forest(
5365
+ public_enter_per_dataset, metric="Accuracy",
5366
+ )
5367
+ ranking_kpi_html = gr.HTML(
5368
+ value=_init_forest_kpi,
5369
+ elem_classes=["nk-bar-kpi-wrap"],
5370
+ visible=True,
5371
+ )
5372
  ranking_plot = gr.Plot(
5373
+ value=_init_forest_fig,
 
 
 
5374
  show_label=False,
5375
  elem_classes=["nk-bar-plot"],
5376
  visible=True,
 
6010
  # Bars view = significance forest plot. Cached at build
6011
  # time (build_figures_cache.py) because the live render
6012
  # runs Wilcoxon-Holm + 1000-iter paired bootstrap, ~30 s
6013
+ # on Spaces CPU per metric change. The KPI HTML is
6014
+ # served from the cache when present; otherwise we fall
6015
+ # back to live recompute (slow path).
6016
  import plotly.graph_objects as _go
6017
 
6018
  fig = _go.Figure(cache_root["forest"][metric])
6019
+ kpi_html_cached = cache_root.get("forest_kpi", {}).get(metric, "")
6020
  return (
6021
  gr.update(visible=True, value=fig),
6022
  hide,
 
6026
  hide,
6027
  hide,
6028
  hide,
6029
+ gr.update(visible=True, value=kpi_html_cached),
6030
  count_label,
6031
  )
6032
 
 
6071
  hide,
6072
  hide,
6073
  hide,
6074
+ gr.update(),
6075
  count_label,
6076
  )
6077
 
 
6096
  hide,
6097
  hide,
6098
  gr.update(visible=True, value=fig),
6099
+ gr.update(),
6100
  count_label,
6101
  )
6102
 
 
6116
  hide,
6117
  hide,
6118
  hide,
6119
+ gr.update(),
6120
  count_label,
6121
  )
6122
  # Bars view now renders the significance forest plot
6123
  # (Wilcoxon-Holm leading-group highlight + CI lollipops).
6124
+ fig, kpi_html = plot_significance_forest(
6125
  filtered_per_dataset, metric=metric,
6126
  )
6127
  return (
 
6133
  hide,
6134
  hide,
6135
  hide,
6136
+ gr.update(visible=True, value=kpi_html),
6137
  count_label,
6138
  )
6139
 
 
6148
  hide,
6149
  hide,
6150
  gr.update(visible=True, value=None),
6151
+ gr.update(),
6152
  count_label,
6153
  )
6154
  fig = plot_winrate_matrix(filtered_per_dataset, metric=metric)
 
6161
  hide,
6162
  hide,
6163
  gr.update(visible=True, value=fig),
6164
+ gr.update(),
6165
  count_label,
6166
  )
6167
 
 
6185
  hide,
6186
  hide,
6187
  hide,
6188
+ gr.update(),
6189
  count_label,
6190
  )
6191
 
 
6206
  hide,
6207
  hide,
6208
  hide,
6209
+ gr.update(),
6210
  count_label,
6211
  )
6212
  fig = plot_dataset_comparison(
 
6221
  hide,
6222
  hide,
6223
  hide,
6224
+ gr.update(),
6225
  count_label,
6226
  )
6227
 
 
6236
  hide,
6237
  hide,
6238
  hide,
6239
+ gr.update(),
6240
  count_label,
6241
  )
6242
  overlay_mm, sm_mm, overlay_rk, sm_rk = plot_model_radars(
 
6251
  gr.update(visible=True, value=overlay_rk),
6252
  gr.update(visible=True, value=sm_rk),
6253
  hide,
6254
+ gr.update(),
6255
  count_label,
6256
  )
6257
 
 
6265
  hide,
6266
  hide,
6267
  hide,
6268
+ gr.update(),
6269
  count_label,
6270
  )
6271
 
 
6299
  radar_overlay_rank_plot,
6300
  radar_grid_rank_plot,
6301
  winrate_plot,
6302
+ ranking_kpi_html,
6303
  dataset_count_md,
6304
  ],
6305
  )
build_figures_cache.py CHANGED
@@ -101,17 +101,19 @@ def build_one(benchmark, industry, metric):
101
  # Forest plot (Wilcoxon-Holm leading-group highlight + paired-bootstrap CI).
102
  # This is the slow figure on live render (B=1000 bootstrap × all model pairs),
103
  # so pre-caching it is the whole point of this build script for the Bars view.
 
104
  try:
105
- forest = plot_significance_forest(df_display, metric=metric)
106
  except Exception as _e:
107
  print(f" [warn] forest plot failed for bench={benchmark} ind={industry} metric={metric}: {_e}")
108
- forest = None
109
  # Win-rate matrix should ALSO use the display-filtered frame so the
110
  # heatmap doesn't show duplicate display names (single-fit vs ensemble).
111
  winrate = plot_winrate_matrix(df_display, metric=metric) if metric not in ("Elo_score", "%↗ over XGBoost") else None
112
  return (
113
  bars.to_plotly_json(),
114
  forest.to_plotly_json() if forest is not None else None,
 
115
  winrate.to_plotly_json() if winrate is not None else None,
116
  agg.round(6).to_dict(orient="records"),
117
  int(df_display['dataset_id'].nunique()),
@@ -132,14 +134,17 @@ for benchmark in BENCHMARKS:
132
  cache[benchmark] = {}
133
  for industry in INDUSTRY_BUCKETS:
134
  cache[benchmark][industry] = {
135
- "metrics": {}, "forest": {}, "winrate": {}, "n_datasets": 0, "table": []
 
136
  }
137
  for metric in METRICS:
138
  i += 1
139
- bars_dict, forest_dict, winrate_dict, agg, n = build_one(benchmark, industry, metric)
140
  cache[benchmark][industry]["metrics"][metric] = bars_dict
141
  if forest_dict is not None:
142
  cache[benchmark][industry]["forest"][metric] = forest_dict
 
 
143
  if winrate_dict is not None:
144
  cache[benchmark][industry]["winrate"][metric] = winrate_dict
145
  cache[benchmark][industry]["n_datasets"] = n
 
101
  # Forest plot (Wilcoxon-Holm leading-group highlight + paired-bootstrap CI).
102
  # This is the slow figure on live render (B=1000 bootstrap × all model pairs),
103
  # so pre-caching it is the whole point of this build script for the Bars view.
104
+ # Returns (figure, kpi_html) — both go in the cache.
105
  try:
106
+ forest, forest_kpi = plot_significance_forest(df_display, metric=metric)
107
  except Exception as _e:
108
  print(f" [warn] forest plot failed for bench={benchmark} ind={industry} metric={metric}: {_e}")
109
+ forest, forest_kpi = None, None
110
  # Win-rate matrix should ALSO use the display-filtered frame so the
111
  # heatmap doesn't show duplicate display names (single-fit vs ensemble).
112
  winrate = plot_winrate_matrix(df_display, metric=metric) if metric not in ("Elo_score", "%↗ over XGBoost") else None
113
  return (
114
  bars.to_plotly_json(),
115
  forest.to_plotly_json() if forest is not None else None,
116
+ forest_kpi,
117
  winrate.to_plotly_json() if winrate is not None else None,
118
  agg.round(6).to_dict(orient="records"),
119
  int(df_display['dataset_id'].nunique()),
 
134
  cache[benchmark] = {}
135
  for industry in INDUSTRY_BUCKETS:
136
  cache[benchmark][industry] = {
137
+ "metrics": {}, "forest": {}, "forest_kpi": {}, "winrate": {},
138
+ "n_datasets": 0, "table": [],
139
  }
140
  for metric in METRICS:
141
  i += 1
142
+ bars_dict, forest_dict, forest_kpi_html, winrate_dict, agg, n = build_one(benchmark, industry, metric)
143
  cache[benchmark][industry]["metrics"][metric] = bars_dict
144
  if forest_dict is not None:
145
  cache[benchmark][industry]["forest"][metric] = forest_dict
146
+ if forest_kpi_html is not None:
147
+ cache[benchmark][industry]["forest_kpi"][metric] = forest_kpi_html
148
  if winrate_dict is not None:
149
  cache[benchmark][industry]["winrate"][metric] = winrate_dict
150
  cache[benchmark][industry]["n_datasets"] = n
perfmap_hulls_override.json CHANGED
@@ -19,7 +19,7 @@
19
  "46955",
20
  "255",
21
  "137",
22
- "417O5",
23
  "44186",
24
  "44130",
25
  "40497",
 
19
  "46955",
20
  "255",
21
  "137",
22
+ "41705",
23
  "44186",
24
  "44130",
25
  "40497",