Spaces:
Sleeping
Sleeping
[FIX] Replace info text with resource status expander and fix toggle instantiation bug for blocked methods
Browse files- 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
|
| 122 |
-
#
|
| 123 |
-
#
|
| 124 |
-
|
|
|
|
|
|
|
|
|
|
| 125 |
if uncovered_required:
|
| 126 |
st.session_state.constraints[m]["active"] = False
|
| 127 |
-
st.session_state
|
| 128 |
-
st.toggle(m,
|
| 129 |
-
|
| 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 |
-
|
|
|
|
|
|
|
| 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"):
|