CarlyneB commited on
Commit
c91fe21
·
1 Parent(s): 1026e02

[FEAT/FIX] Tab3: adaptive slider precision, hide Other resources, show hidden secondary hints, filter inactive methods

Browse files
Files changed (1) hide show
  1. tabs/tab3_coefficients.py +99 -34
tabs/tab3_coefficients.py CHANGED
@@ -2,7 +2,8 @@ import pandas as pd
2
  import streamlit as st
3
 
4
  from config.config import (method_groups, method_order, resource_groups,
5
- resource_order, secondary_resources, resource_unit_map, CO2_UNIT)
 
6
  from utils.ui_helpers import format_efficiency_summary, show_bar
7
  from utils.data_utils import strip_unit_suffix, get_resource_to_group
8
 
@@ -15,22 +16,50 @@ def normalize_costs(costs_dict: dict) -> dict:
15
  def get_slider_params(conf: dict) -> tuple:
16
  """Derive slider step size and display format from a coefficient config dict.
17
 
 
 
 
18
  Args:
19
- conf (dict): slider config with key "min" (the minimum coefficient value).
20
 
21
  Returns:
22
  tuple[float, str]: (step, fmt) where step is the slider increment and
23
  fmt is a printf-style format string for display.
24
  """
25
  min_coeff = conf["min"]
26
- if min_coeff < 0.01:
27
- return 0.0001, "%.4f"
 
 
 
 
 
 
 
 
 
28
  elif min_coeff < 1:
29
- return 0.001, "%.3f"
30
  elif min_coeff < 100:
31
- return 0.01, "%.2f"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  else:
33
- return 1.0, "%.0f"
 
 
34
 
35
 
36
  def is_method_enabled(method, resource, enabled_methods):
@@ -167,9 +196,9 @@ def render_standard_resource_row(method, r, conf, display_label, enabled_methods
167
 
168
  is_secondary = (method, r) in secondary_resources
169
  step, fmt = get_slider_params(conf)
170
- toggle_key = f"toggle_{method}_{r}".replace(" ", "_").lower()
171
- slider_key = f"slider_{method}_{r}".replace(" ", "_").lower()
172
- manual_key = f"manual_{method}_{r}".replace(" ", "_").lower()
173
  current_val = normalize_costs(st.session_state.costs.get(method, {})).get(r, 0.0)
174
  if is_secondary:
175
  if toggle_key not in st.session_state:
@@ -190,15 +219,15 @@ def render_standard_resource_row(method, r, conf, display_label, enabled_methods
190
  st.session_state.costs[method][r] = conf["median"]
191
  if st.session_state.get("resource_caps", {}).get(r, 0) <= 0:
192
  if has_custom_batch:
193
- st.info(
194
  f"No available quantity set for **{r}** in Resource Inputs tab.",
195
- icon=":material/info:",
196
  )
197
  else:
198
- st.info(
199
  f"No available quantity set for **{r}** in Resource Inputs tab: "
200
  f"**{method}** won't be used in the optimization.",
201
- icon=":material/info:",
202
  )
203
  return
204
 
@@ -208,15 +237,15 @@ def render_standard_resource_row(method, r, conf, display_label, enabled_methods
208
  st.session_state[manual_key] = st.session_state[slider_key]
209
  if st.session_state.get("resource_caps", {}).get(r, 0) <= 0:
210
  if has_custom_batch:
211
- st.info(
212
  f"No available quantity set for **{r}** in Resource Inputs tab.",
213
- icon=":material/info:",
214
  )
215
  else:
216
- st.info(
217
  f"No available quantity set for **{r}** in Resource Inputs tab: "
218
  f"**{method}** won't be used in the optimization.",
219
- icon=":material/info:",
220
  )
221
 
222
  col1, col2 = st.columns([3, 1])
@@ -225,13 +254,13 @@ def render_standard_resource_row(method, r, conf, display_label, enabled_methods
225
  label=display_label,
226
  min_value=conf["min"], max_value=conf["max"], step=step, format=fmt,
227
  key=slider_key,
228
- on_change=make_sync_s_to_m(slider_key, manual_key),
229
  )
230
  with col2:
231
  st.number_input(
232
  label="label", label_visibility="collapsed",
233
  key=manual_key, help="Manual override (optional, any value)",
234
- format=fmt, value=None, placeholder=str(round(slider_val, 4)),
235
  on_change=make_sync_m_to_s(slider_key, manual_key, conf["min"], conf["max"]),
236
  )
237
 
@@ -259,9 +288,9 @@ def render_custom_resource_row(method, r, conf, enabled_methods, custom_resource
259
 
260
  is_sec = is_custom_secondary(method, r, custom_resource_to_sub_group)
261
  step, fmt = get_slider_params(conf)
262
- toggle_key = f"toggle_{method}_{r}".replace(" ", "_").lower()
263
- slider_key = f"slider_{method}_{r}".replace(" ", "_").lower()
264
- manual_key = f"manual_{method}_{r}".replace(" ", "_").lower()
265
  current_val = st.session_state.costs.get(method, {}).get(r, 0.0 if is_sec else conf["median"])
266
 
267
  if is_sec:
@@ -314,7 +343,7 @@ def render_custom_resource_row(method, r, conf, enabled_methods, custom_resource
314
  st.number_input(
315
  label="label", label_visibility="collapsed",
316
  key=manual_key, help="Manual override (optional, any value)",
317
- format=fmt, value=None, placeholder=str(round(slider_val, 4)),
318
  on_change=make_sync_m_to_s(slider_key, manual_key, conf["min"], conf["max"]),
319
  )
320
 
@@ -350,13 +379,32 @@ def panel_method_editor(
350
  method_sliders = cost_sliders_norm.get(method, {})
351
  custom_for_method = custom_sliders_by_method.get(method, {})
352
 
 
 
 
 
 
 
 
 
 
 
 
 
353
  for group_name, rlist in resource_groups.items():
354
- used = [r for r in rlist if r in method_sliders]
355
  custom_in_group = [
356
  r for r in custom_for_method
357
  if custom_resource_to_group.get(r) == group_name
358
  ]
359
- if not used and not custom_in_group:
 
 
 
 
 
 
 
360
  continue
361
 
362
  st.markdown(f'<p class="crra-group-name">{group_name}</p>', unsafe_allow_html=True)
@@ -375,16 +423,23 @@ def panel_method_editor(
375
  enabled_methods, custom_resource_to_sub_group, custom_resources
376
  )
377
 
 
 
 
 
 
 
 
378
  if st.button("Reset selected method", key="btn-reset-method", icon=":material/reset_settings:"):
379
  for r, conf in cost_sliders_norm.get(method, {}).items():
380
  is_secondary = (method, r) in secondary_resources
381
  st.session_state.costs[method][r] = 0.0 if is_secondary else conf["median"]
382
  for prefix in ["slider", "manual", "toggle"]:
383
- st.session_state.pop(f"{prefix}_{method}_{r}".replace(" ", "_").lower(), None)
384
  for r, conf in custom_sliders_by_method.get(method, {}).items():
385
  st.session_state.costs.setdefault(method, {})[r] = conf["median"]
386
  for prefix in ["slider", "manual"]:
387
- st.session_state.pop(f"{prefix}_{method}_{r}".replace(" ", "_").lower(), None)
388
  st.success(f"{method} reset to default values.")
389
  st.rerun()
390
 
@@ -414,7 +469,7 @@ def panel_summary_table(
414
  for cat, resources in resource_groups.items()
415
  for r in resources
416
  }
417
- all_resource_columns = resource_order + sorted(custom_resource_names)
418
 
419
  df_default = pd.DataFrame(index=method_order, columns=all_resource_columns)
420
  df_current = pd.DataFrame(index=method_order, columns=all_resource_columns)
@@ -476,9 +531,9 @@ def render_tab(cost_sliders):
476
  unsafe_allow_html=True,
477
  )
478
  with st.container(key="warning_resources"):
479
- st.warning(
480
  "Red cells in the table highlight coefficients modified from their **default median values**",
481
- icon=":material/warning:",
482
  )
483
 
484
  enabled_methods = st.session_state.get("enabled_methods", {})
@@ -498,7 +553,17 @@ def render_tab(cost_sliders):
498
  custom_resource_to_group,
499
  ) = build_custom_slider_index(cost_sliders_norm, custom_resources)
500
 
501
- method_choices = [f"{g} | {m}" for g, methods in method_groups.items() for m in methods]
 
 
 
 
 
 
 
 
 
 
502
  method_lookup = {label: label.split(" | ", 1)[1] for label in method_choices}
503
  selected_label = st.selectbox("Select a method:", method_choices, key="crra-select")
504
 
@@ -507,14 +572,14 @@ def render_tab(cost_sliders):
507
  for r, conf in sliders.items():
508
  st.session_state.costs[m][r] = 0.0 if (m, r) in secondary_resources else conf["median"]
509
  for prefix in ["slider", "manual", "toggle"]:
510
- st.session_state.pop(f"{prefix}_{m}_{r}".replace(" ", "_").lower(), None)
511
  for m, sliders in custom_sliders_by_method.items():
512
  for r, conf in sliders.items():
513
  st.session_state.costs.setdefault(m, {})[r] = (
514
  0.0 if is_custom_secondary(m, r, custom_resource_to_sub_group) else conf["median"]
515
  )
516
  for prefix in ["slider", "manual", "toggle"]:
517
- st.session_state.pop(f"{prefix}_{m}_{r}".replace(" ", "_").lower(), None)
518
  st.success("All methods reset to default values.")
519
  st.rerun()
520
 
 
2
  import streamlit as st
3
 
4
  from config.config import (method_groups, method_order, resource_groups,
5
+ resource_order, secondary_resources, resource_unit_map, CO2_UNIT,
6
+ hidden_resource_names)
7
  from utils.ui_helpers import format_efficiency_summary, show_bar
8
  from utils.data_utils import strip_unit_suffix, get_resource_to_group
9
 
 
16
  def get_slider_params(conf: dict) -> tuple:
17
  """Derive slider step size and display format from a coefficient config dict.
18
 
19
+ Format is driven by min (display precision); step is driven by range
20
+ (usability) and floored at 1e-06 (Streamlit's hard lower limit).
21
+
22
  Args:
23
+ conf (dict): slider config with keys "min", "median", "max".
24
 
25
  Returns:
26
  tuple[float, str]: (step, fmt) where step is the slider increment and
27
  fmt is a printf-style format string for display.
28
  """
29
  min_coeff = conf["min"]
30
+ coeff_range = conf["max"] - min_coeff
31
+
32
+ # Format: enough decimals to show the minimum value without scientific notation
33
+ if min_coeff < 1e-10:
34
+ fmt = "%.11f"
35
+ elif min_coeff < 1e-08:
36
+ fmt = "%.9f"
37
+ elif min_coeff < 1e-05:
38
+ fmt = "%.6f"
39
+ elif min_coeff < 0.01:
40
+ fmt = "%.4f"
41
  elif min_coeff < 1:
42
+ fmt = "%.3f"
43
  elif min_coeff < 100:
44
+ fmt = "%.2f"
45
+ else:
46
+ fmt = "%.0f"
47
+
48
+ # Step: ~1/1000 of the range, floored at 1e-06 (Streamlit minimum)
49
+ if coeff_range < 0.001:
50
+ step = 1e-06
51
+ elif coeff_range < 0.1:
52
+ step = 0.0001
53
+ elif coeff_range < 1:
54
+ step = 0.001
55
+ elif coeff_range < 10:
56
+ step = 0.01
57
+ elif coeff_range < 100:
58
+ step = 0.1
59
  else:
60
+ step = 1.0
61
+
62
+ return step, fmt
63
 
64
 
65
  def is_method_enabled(method, resource, enabled_methods):
 
196
 
197
  is_secondary = (method, r) in secondary_resources
198
  step, fmt = get_slider_params(conf)
199
+ toggle_key = f"toggle_{method}_{r}".replace(" ", "_")
200
+ slider_key = f"slider_{method}_{r}".replace(" ", "_")
201
+ manual_key = f"manual_{method}_{r}".replace(" ", "_")
202
  current_val = normalize_costs(st.session_state.costs.get(method, {})).get(r, 0.0)
203
  if is_secondary:
204
  if toggle_key not in st.session_state:
 
219
  st.session_state.costs[method][r] = conf["median"]
220
  if st.session_state.get("resource_caps", {}).get(r, 0) <= 0:
221
  if has_custom_batch:
222
+ st.warning(
223
  f"No available quantity set for **{r}** in Resource Inputs tab.",
224
+ icon=":material/warning:",
225
  )
226
  else:
227
+ st.warning(
228
  f"No available quantity set for **{r}** in Resource Inputs tab: "
229
  f"**{method}** won't be used in the optimization.",
230
+ icon=":material/warning:",
231
  )
232
  return
233
 
 
237
  st.session_state[manual_key] = st.session_state[slider_key]
238
  if st.session_state.get("resource_caps", {}).get(r, 0) <= 0:
239
  if has_custom_batch:
240
+ st.warning(
241
  f"No available quantity set for **{r}** in Resource Inputs tab.",
242
+ icon=":material/warning:",
243
  )
244
  else:
245
+ st.warning(
246
  f"No available quantity set for **{r}** in Resource Inputs tab: "
247
  f"**{method}** won't be used in the optimization.",
248
+ icon=":material/warning:",
249
  )
250
 
251
  col1, col2 = st.columns([3, 1])
 
254
  label=display_label,
255
  min_value=conf["min"], max_value=conf["max"], step=step, format=fmt,
256
  key=slider_key,
257
+ on_change=make_sync_s_to_m(slider_key, manual_key)
258
  )
259
  with col2:
260
  st.number_input(
261
  label="label", label_visibility="collapsed",
262
  key=manual_key, help="Manual override (optional, any value)",
263
+ format=fmt, value=None, placeholder=str(round(slider_val, 6)),
264
  on_change=make_sync_m_to_s(slider_key, manual_key, conf["min"], conf["max"]),
265
  )
266
 
 
288
 
289
  is_sec = is_custom_secondary(method, r, custom_resource_to_sub_group)
290
  step, fmt = get_slider_params(conf)
291
+ toggle_key = f"toggle_{method}_{r}".replace(" ", "_")
292
+ slider_key = f"slider_{method}_{r}".replace(" ", "_")
293
+ manual_key = f"manual_{method}_{r}".replace(" ", "_")
294
  current_val = st.session_state.costs.get(method, {}).get(r, 0.0 if is_sec else conf["median"])
295
 
296
  if is_sec:
 
343
  st.number_input(
344
  label="label", label_visibility="collapsed",
345
  key=manual_key, help="Manual override (optional, any value)",
346
+ format=fmt, value=None, placeholder=str(round(slider_val, 6)),
347
  on_change=make_sync_m_to_s(slider_key, manual_key, conf["min"], conf["max"]),
348
  )
349
 
 
379
  method_sliders = cost_sliders_norm.get(method, {})
380
  custom_for_method = custom_sliders_by_method.get(method, {})
381
 
382
+ primary_resources = [r for r in method_sliders if (method, r) not in secondary_resources]
383
+ if primary_resources and all(r in hidden_resource_names for r in primary_resources) and not custom_for_method:
384
+ required = [r for r in primary_resources if r in hidden_resource_names]
385
+ optional = [r for r in method_sliders if r in hidden_resource_names and (method, r) in secondary_resources]
386
+ req_list = "\n".join(f"- **{r}** *(required)*" for r in required)
387
+ opt_list = "\n".join(f"- **{r}** *(optional)*" for r in optional) if optional else ""
388
+ body = f"To use this method, add a new resource in the **Resource inputs** tab:\n\n{req_list}"
389
+ if opt_list:
390
+ body += f"\n{opt_list}"
391
+ st.info(body, icon=":material/info:")
392
+ return
393
+
394
  for group_name, rlist in resource_groups.items():
395
+ used = [r for r in rlist if r in method_sliders and r not in hidden_resource_names]
396
  custom_in_group = [
397
  r for r in custom_for_method
398
  if custom_resource_to_group.get(r) == group_name
399
  ]
400
+ hidden_secondary_in_group = [
401
+ r for r in rlist
402
+ if r in method_sliders
403
+ and r in hidden_resource_names
404
+ and (method, r) in secondary_resources
405
+ and not any(custom_resource_to_sub_group.get(cr) == r for cr in custom_for_method)
406
+ ]
407
+ if not used and not custom_in_group and not hidden_secondary_in_group:
408
  continue
409
 
410
  st.markdown(f'<p class="crra-group-name">{group_name}</p>', unsafe_allow_html=True)
 
423
  enabled_methods, custom_resource_to_sub_group, custom_resources
424
  )
425
 
426
+ for r in hidden_secondary_in_group:
427
+ st.info(
428
+ f"**{r}** can be used as an optional secondary resource for this method. "
429
+ f"Add a custom resource of type **{r}** in the **Resource inputs** tab to activate it.",
430
+ icon=":material/add_circle:",
431
+ )
432
+
433
  if st.button("Reset selected method", key="btn-reset-method", icon=":material/reset_settings:"):
434
  for r, conf in cost_sliders_norm.get(method, {}).items():
435
  is_secondary = (method, r) in secondary_resources
436
  st.session_state.costs[method][r] = 0.0 if is_secondary else conf["median"]
437
  for prefix in ["slider", "manual", "toggle"]:
438
+ st.session_state.pop(f"{prefix}_{method}_{r}".replace(" ", "_"), None)
439
  for r, conf in custom_sliders_by_method.get(method, {}).items():
440
  st.session_state.costs.setdefault(method, {})[r] = conf["median"]
441
  for prefix in ["slider", "manual"]:
442
+ st.session_state.pop(f"{prefix}_{method}_{r}".replace(" ", "_"), None)
443
  st.success(f"{method} reset to default values.")
444
  st.rerun()
445
 
 
469
  for cat, resources in resource_groups.items()
470
  for r in resources
471
  }
472
+ all_resource_columns = [r for r in resource_order if r not in hidden_resource_names] + sorted(custom_resource_names)
473
 
474
  df_default = pd.DataFrame(index=method_order, columns=all_resource_columns)
475
  df_current = pd.DataFrame(index=method_order, columns=all_resource_columns)
 
531
  unsafe_allow_html=True,
532
  )
533
  with st.container(key="warning_resources"):
534
+ st.info(
535
  "Red cells in the table highlight coefficients modified from their **default median values**",
536
+ icon=":material/info:",
537
  )
538
 
539
  enabled_methods = st.session_state.get("enabled_methods", {})
 
553
  custom_resource_to_group,
554
  ) = build_custom_slider_index(cost_sliders_norm, custom_resources)
555
 
556
+ constraints = st.session_state.get("constraints", {})
557
+ active_methods = {m for m, c in constraints.items() if c.get("active", True)}
558
+ method_choices = [
559
+ f"{g} | {m}"
560
+ for g, methods in method_groups.items()
561
+ for m in methods
562
+ if not constraints or m in active_methods
563
+ ]
564
+ if not method_choices:
565
+ st.warning("No active methods. Enable at least one method in the **Method Constraints** tab.")
566
+ return
567
  method_lookup = {label: label.split(" | ", 1)[1] for label in method_choices}
568
  selected_label = st.selectbox("Select a method:", method_choices, key="crra-select")
569
 
 
572
  for r, conf in sliders.items():
573
  st.session_state.costs[m][r] = 0.0 if (m, r) in secondary_resources else conf["median"]
574
  for prefix in ["slider", "manual", "toggle"]:
575
+ st.session_state.pop(f"{prefix}_{m}_{r}".replace(" ", "_"), None)
576
  for m, sliders in custom_sliders_by_method.items():
577
  for r, conf in sliders.items():
578
  st.session_state.costs.setdefault(m, {})[r] = (
579
  0.0 if is_custom_secondary(m, r, custom_resource_to_sub_group) else conf["median"]
580
  )
581
  for prefix in ["slider", "manual", "toggle"]:
582
+ st.session_state.pop(f"{prefix}_{m}_{r}".replace(" ", "_"), None)
583
  st.success("All methods reset to default values.")
584
  st.rerun()
585