CarlyneB commited on
Commit
46436ba
·
1 Parent(s): e4b0fe7

[FIX] Replace info text with resource status expander and fix toggle instantiation bug for blocked methods

Browse files
Files changed (1) hide show
  1. tabs/tab2_constraints.py +78 -24
tabs/tab2_constraints.py CHANGED
@@ -66,12 +66,8 @@ def get_uncovered_hidden_resources(m, cost_sliders_norm):
66
  optional — in secondary_resources, shown as informational only.
67
  Returns ([], []) when nothing is uncovered.
68
  """
69
- from config.config import resource_groups
70
-
71
  custom_resources = st.session_state.get("custom_resources", [])
72
  covered = {b["name"] for b in custom_resources if m in b.get("methods", {})}
73
- resource_caps = st.session_state.get("resource_caps", {})
74
- resource_to_group = {r: g for g, resources in resource_groups.items() for r in resources}
75
 
76
  required, optional = [], []
77
  for r, coeff_dict in cost_sliders_norm.get(m, {}).items():
@@ -81,12 +77,6 @@ def get_uncovered_hidden_resources(m, cost_sliders_norm):
81
  continue
82
  if r in covered:
83
  continue
84
- group = resource_to_group.get(r)
85
- group_resources = resource_groups.get(group, []) if group else []
86
- standard_in_group = [s for s in group_resources if s not in hidden_resource_names]
87
- any_standard_provided = any(resource_caps.get(s, 0.0) > 0 for s in standard_in_group)
88
- if any_standard_provided:
89
- continue
90
  if (m, r) in secondary_resources:
91
  optional.append(r)
92
  else:
@@ -94,6 +84,58 @@ def get_uncovered_hidden_resources(m, cost_sliders_norm):
94
  return required, optional
95
 
96
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  def render_method(m, cols, i, method_to_primary_resources, cost_sliders_norm):
98
  """Render the constraint widget group (toggle + cap slider/input) for one method.
99
 
@@ -118,20 +160,18 @@ def render_method(m, cols, i, method_to_primary_resources, cost_sliders_norm):
118
  toggle_key = f"toggle_{m}"
119
  cap_type_key = f"cap_type_{m}"
120
 
121
- # Check for missing hidden resources BEFORE the toggle widget is
122
- # instantiated — Streamlit forbids modifying a widget's session-state
123
- # key after the widget has been created.
124
- uncovered_required, uncovered_optional = get_uncovered_hidden_resources(m, cost_sliders_norm)
 
 
 
125
  if uncovered_required:
126
  st.session_state.constraints[m]["active"] = False
127
- st.session_state[toggle_key] = False
128
- st.toggle(m, key=toggle_key, help=tooltip, disabled=True)
129
- req_list = "\n".join(f"- **{r}** *(required)*" for r in uncovered_required)
130
- opt_list = "\n".join(f"- **{r}** *(optional)*" for r in uncovered_optional) if uncovered_optional else ""
131
- body = f"To use this method, add a new resource in the **Resource inputs** tab:\n\n{req_list}"
132
- if opt_list:
133
- body += f"\n{opt_list}"
134
- st.info(body, icon=":material/info:")
135
  return
136
 
137
  was_active = constraint["active"]
@@ -330,6 +370,17 @@ def render_tab(cost_sliders):
330
  just_activated = st.session_state.pop("just_activated_methods", set())
331
 
332
  for m, c in st.session_state.constraints.items():
 
 
 
 
 
 
 
 
 
 
 
333
  force = just_loaded or (m in just_activated)
334
  if c["active"]:
335
  cap_type = c.get("cap_type", "percent")
@@ -394,13 +445,16 @@ def render_tab(cost_sliders):
394
  for display_m, _ in all_display
395
  )
396
  # Activatable: variants are always activatable; standard methods only if they
397
- # have at least one visible (non-hidden) primary resource.
 
398
  activatable = []
399
  for display_m, orig_m in all_display:
400
  if display_m != orig_m: # it's a variant → batch provides the resource
401
  activatable.append(display_m)
402
  elif get_hidden_resources_for_method(orig_m, method_to_primary_resources, cost_sliders_norm)[0] is None:
403
- activatable.append(orig_m)
 
 
404
 
405
  if any_active:
406
  if st.button("Deactivate all", icon=":material/remove_done:", key="btn-deactivate-all"):
 
66
  optional — in secondary_resources, shown as informational only.
67
  Returns ([], []) when nothing is uncovered.
68
  """
 
 
69
  custom_resources = st.session_state.get("custom_resources", [])
70
  covered = {b["name"] for b in custom_resources if m in b.get("methods", {})}
 
 
71
 
72
  required, optional = [], []
73
  for r, coeff_dict in cost_sliders_norm.get(m, {}).items():
 
77
  continue
78
  if r in covered:
79
  continue
 
 
 
 
 
 
80
  if (m, r) in secondary_resources:
81
  optional.append(r)
82
  else:
 
84
  return required, optional
85
 
86
 
87
+ def _render_resource_status(m, cost_sliders_norm):
88
+ """Render an expander showing all resources of method m with their availability status.
89
+
90
+ Standard resources: ✅ if cap > 0, ❌ if = 0.
91
+ Hidden resources: ✅ if covered by a custom batch, ❌ if not.
92
+ Secondary resources are shown in a separate section.
93
+ """
94
+ resource_caps = st.session_state.get("resource_caps", {})
95
+ custom_resources = st.session_state.get("custom_resources", [])
96
+ covered = {b["name"] for b in custom_resources if m in b.get("methods", {})}
97
+
98
+ all_res = cost_sliders_norm.get(m, {})
99
+ required_rows = []
100
+ optional_rows = []
101
+
102
+ for r, coeff_dict in all_res.items():
103
+ if coeff_dict.get("median", 0.0) <= 0:
104
+ continue
105
+ if r in hidden_resource_names:
106
+ available = r in covered
107
+ source = "Add as new resource in Resource inputs"
108
+ else:
109
+ available = resource_caps.get(r, 0.0) > 0
110
+ source = "Set quantity in Resource inputs"
111
+
112
+ icon = "✅" if available else "❌"
113
+ row = (icon, r, "" if available else source)
114
+
115
+ if (m, r) in secondary_resources:
116
+ optional_rows.append(row)
117
+ else:
118
+ required_rows.append(row)
119
+
120
+ with st.expander("Why is this method disabled?", expanded=False):
121
+ if required_rows:
122
+ st.markdown("**Required resources**")
123
+ for _, r, hint in required_rows:
124
+ color = "#55C95B" if not hint else "#FF5F4D"
125
+ label = f'<span style="color:{color};font-weight:600"><strong>{r}</strong></span>'
126
+ if hint:
127
+ label += f' <span style="color:#888;font-style:italic;font-weight:400">: {hint}</span>'
128
+ st.markdown(label, unsafe_allow_html=True)
129
+ if optional_rows:
130
+ st.markdown("**Optional resources**")
131
+ for _, r, hint in optional_rows:
132
+ color = "#55C95B" if not hint else "#FF5F4D"
133
+ label = f'<span style="color:{color};font-weight:600"><strong>{r}</strong></span>'
134
+ if hint:
135
+ label += f' <span style="color:#888;font-style:italic;font-weight:400">: {hint}</span>'
136
+ st.markdown(label, unsafe_allow_html=True)
137
+
138
+
139
  def render_method(m, cols, i, method_to_primary_resources, cost_sliders_norm):
140
  """Render the constraint widget group (toggle + cap slider/input) for one method.
141
 
 
160
  toggle_key = f"toggle_{m}"
161
  cap_type_key = f"cap_type_{m}"
162
 
163
+ # Check for missing hidden resources. This runs before the regular
164
+ # st.toggle so we never touch toggle_{m} at all for blocked methods —
165
+ # the pre-render sync already set that key and Streamlit would raise
166
+ # "cannot be modified after widget is instantiated" if we touched it again.
167
+ # Instead we force the constraint to inactive in the data layer and show
168
+ # a static label + info box without any widget that uses toggle_{m}.
169
+ uncovered_required, _ = get_uncovered_hidden_resources(m, cost_sliders_norm)
170
  if uncovered_required:
171
  st.session_state.constraints[m]["active"] = False
172
+ st.session_state.pop(toggle_key, None)
173
+ st.toggle(m, value=False, key=toggle_key, disabled=True, help=tooltip)
174
+ _render_resource_status(m, cost_sliders_norm)
 
 
 
 
 
175
  return
176
 
177
  was_active = constraint["active"]
 
370
  just_activated = st.session_state.pop("just_activated_methods", set())
371
 
372
  for m, c in st.session_state.constraints.items():
373
+ # Methods with uncovered hidden resources are always forced inactive —
374
+ # skip the normal active-branch so the toggle key is never set to True.
375
+ uncov_req, _ = get_uncovered_hidden_resources(m, cost_sliders_norm)
376
+ if uncov_req:
377
+ st.session_state.constraints[m]["active"] = False
378
+ st.session_state.pop(f"toggle_{m}", None)
379
+ st.session_state.pop(f"cap_type_{m}", None)
380
+ st.session_state.pop(f"slider_{m}", None)
381
+ st.session_state.pop(f"manual_{m}", None)
382
+ continue
383
+
384
  force = just_loaded or (m in just_activated)
385
  if c["active"]:
386
  cap_type = c.get("cap_type", "percent")
 
445
  for display_m, _ in all_display
446
  )
447
  # Activatable: variants are always activatable; standard methods only if they
448
+ # have at least one visible (non-hidden) primary resource AND no uncovered
449
+ # hidden resource that would block them.
450
  activatable = []
451
  for display_m, orig_m in all_display:
452
  if display_m != orig_m: # it's a variant → batch provides the resource
453
  activatable.append(display_m)
454
  elif get_hidden_resources_for_method(orig_m, method_to_primary_resources, cost_sliders_norm)[0] is None:
455
+ uncov_req, _ = get_uncovered_hidden_resources(orig_m, cost_sliders_norm)
456
+ if not uncov_req:
457
+ activatable.append(orig_m)
458
 
459
  if any_active:
460
  if st.button("Deactivate all", icon=":material/remove_done:", key="btn-deactivate-all"):