Spaces:
Sleeping
Sleeping
[UX] address user feedback: per-year template text and column header, more visible "Add resource" button, fixed next-tab navigation on all pages, updated method constraints intro text
Browse files- app.py +39 -11
- assets/styles.css +48 -0
- data/generate_resource_template.py +1 -1
- data/resource_template.xlsx +0 -0
- tabs/tab0_welcome.py +5 -2
- tabs/tab1_inputs.py +15 -10
- tabs/tab2_constraints.py +6 -2
- tabs/tab3_coefficients.py +6 -1
- tabs/tab4_optimization.py +6 -1
- tabs/tab5_sensitivity.py +5 -1
- tabs/tab6_scenarios.py +1 -1
- utils/data_utils.py +1 -1
app.py
CHANGED
|
@@ -60,22 +60,33 @@ load_css(css_path)
|
|
| 60 |
|
| 61 |
st.markdown('<h1 class="custom-title">CRRA <span class="crra-hl-toolkit">Toolkit</span>: CDR Portfolio Optimization</h1>', unsafe_allow_html=True)
|
| 62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
with st.sidebar:
|
| 64 |
selected = option_menu(
|
| 65 |
"Navigation",
|
| 66 |
-
options=
|
| 67 |
-
"Welcome",
|
| 68 |
-
"Resource inputs",
|
| 69 |
-
"Method constraints",
|
| 70 |
-
"Resource coefficients",
|
| 71 |
-
"Portfolio generation",
|
| 72 |
-
"Sensitivity analysis",
|
| 73 |
-
"Scenario comparison",
|
| 74 |
-
],
|
| 75 |
icons=["house", "upload", "gear", "sliders", "graph-up-arrow", "card-checklist", "search"],
|
| 76 |
-
default_index=
|
| 77 |
orientation="vertical",
|
| 78 |
-
key="crra-menu",
|
| 79 |
styles={
|
| 80 |
"container":{
|
| 81 |
"border-radius":"0px",
|
|
@@ -102,6 +113,9 @@ with st.sidebar:
|
|
| 102 |
},
|
| 103 |
}
|
| 104 |
)
|
|
|
|
|
|
|
|
|
|
| 105 |
|
| 106 |
cost_sliders = load_slider_data()
|
| 107 |
|
|
@@ -170,6 +184,20 @@ if selected == "Method constraints" and _prev_tab != "Method constraints":
|
|
| 170 |
st.session_state["constraints_just_loaded"] = True
|
| 171 |
st.session_state["_active_tab"] = selected
|
| 172 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
if selected == "Welcome":
|
| 174 |
tab0_welcome.render_tab()
|
| 175 |
elif selected == "Resource inputs":
|
|
|
|
| 60 |
|
| 61 |
st.markdown('<h1 class="custom-title">CRRA <span class="crra-hl-toolkit">Toolkit</span>: CDR Portfolio Optimization</h1>', unsafe_allow_html=True)
|
| 62 |
|
| 63 |
+
_NAV_OPTIONS = [
|
| 64 |
+
"Welcome",
|
| 65 |
+
"Resource inputs",
|
| 66 |
+
"Method constraints",
|
| 67 |
+
"Resource coefficients",
|
| 68 |
+
"Portfolio generation",
|
| 69 |
+
"Sensitivity analysis",
|
| 70 |
+
"Scenario comparison",
|
| 71 |
+
]
|
| 72 |
+
|
| 73 |
+
_nav_target = st.session_state.pop("_navigate_to", None)
|
| 74 |
+
if _nav_target and _nav_target in _NAV_OPTIONS:
|
| 75 |
+
st.session_state["_menu_default_idx"] = _NAV_OPTIONS.index(_nav_target)
|
| 76 |
+
st.session_state["_menu_ver"] = st.session_state.get("_menu_ver", 0) + 1
|
| 77 |
+
st.session_state["_scroll_ver"] = st.session_state["_menu_ver"]
|
| 78 |
+
|
| 79 |
+
_menu_default_idx = st.session_state.get("_menu_default_idx", 0)
|
| 80 |
+
_menu_ver = st.session_state.get("_menu_ver", 0)
|
| 81 |
+
|
| 82 |
with st.sidebar:
|
| 83 |
selected = option_menu(
|
| 84 |
"Navigation",
|
| 85 |
+
options=_NAV_OPTIONS,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
icons=["house", "upload", "gear", "sliders", "graph-up-arrow", "card-checklist", "search"],
|
| 87 |
+
default_index=_menu_default_idx,
|
| 88 |
orientation="vertical",
|
| 89 |
+
key=f"crra-menu-{_menu_ver}",
|
| 90 |
styles={
|
| 91 |
"container":{
|
| 92 |
"border-radius":"0px",
|
|
|
|
| 113 |
},
|
| 114 |
}
|
| 115 |
)
|
| 116 |
+
# Track sidebar clicks so the next "Next" button starts from the right index
|
| 117 |
+
if selected in _NAV_OPTIONS:
|
| 118 |
+
st.session_state["_menu_default_idx"] = _NAV_OPTIONS.index(selected)
|
| 119 |
|
| 120 |
cost_sliders = load_slider_data()
|
| 121 |
|
|
|
|
| 184 |
st.session_state["constraints_just_loaded"] = True
|
| 185 |
st.session_state["_active_tab"] = selected
|
| 186 |
|
| 187 |
+
_scroll_ver = st.session_state.pop("_scroll_ver", None)
|
| 188 |
+
if _scroll_ver is not None:
|
| 189 |
+
st.html(f"""<script>
|
| 190 |
+
(function scroll{_scroll_ver}() {{
|
| 191 |
+
var doScroll = function() {{
|
| 192 |
+
try {{ window.parent.scrollTo(0, 0); }} catch(e) {{}}
|
| 193 |
+
window.scrollTo(0, 0);
|
| 194 |
+
document.documentElement.scrollTop = 0;
|
| 195 |
+
document.body.scrollTop = 0;
|
| 196 |
+
}};
|
| 197 |
+
setTimeout(doScroll, 80);
|
| 198 |
+
}})();
|
| 199 |
+
</script>""")
|
| 200 |
+
|
| 201 |
if selected == "Welcome":
|
| 202 |
tab0_welcome.render_tab()
|
| 203 |
elif selected == "Resource inputs":
|
assets/styles.css
CHANGED
|
@@ -343,6 +343,54 @@ h1.custom-title {
|
|
| 343 |
color: var(--green-dark);
|
| 344 |
}
|
| 345 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 346 |
/* Simple text button */
|
| 347 |
[class*="simple-btn-"] button {
|
| 348 |
color: var(--red);
|
|
|
|
| 343 |
color: var(--green-dark);
|
| 344 |
}
|
| 345 |
|
| 346 |
+
/* Fixed "Next tab" navigation button */
|
| 347 |
+
[class*="st-key-btn-nav-next"] {
|
| 348 |
+
position: fixed;
|
| 349 |
+
bottom: 1.5rem;
|
| 350 |
+
right: 2rem;
|
| 351 |
+
z-index: 999;
|
| 352 |
+
}
|
| 353 |
+
[class*="st-key-btn-nav-next"] button {
|
| 354 |
+
background: var(--green-dark) !important;
|
| 355 |
+
color: var(--white) !important;
|
| 356 |
+
clip-path: none !important;
|
| 357 |
+
border-radius: 6px !important;
|
| 358 |
+
border: none !important;
|
| 359 |
+
box-shadow: 0 2px 10px rgba(0,0,0,0.25) !important;
|
| 360 |
+
height: auto !important;
|
| 361 |
+
padding: 0.55rem 1.2rem !important;
|
| 362 |
+
font-weight: 700 !important;
|
| 363 |
+
font-size: clamp(12px, 0.8vw, 15px) !important;
|
| 364 |
+
letter-spacing: 0.06em !important;
|
| 365 |
+
text-transform: uppercase !important;
|
| 366 |
+
transition: background 0.2s ease, box-shadow 0.2s ease !important;
|
| 367 |
+
}
|
| 368 |
+
[class*="st-key-btn-nav-next"] button:hover {
|
| 369 |
+
background: #01740f !important;
|
| 370 |
+
box-shadow: 0 4px 14px rgba(0,0,0,0.35) !important;
|
| 371 |
+
}
|
| 372 |
+
|
| 373 |
+
/* Add resource — green-light background */
|
| 374 |
+
[class*="btn-add-resource-"] button {
|
| 375 |
+
border: none !important;
|
| 376 |
+
border-radius: 0px;
|
| 377 |
+
color: var(--green-dark);
|
| 378 |
+
background: var(--green-light);
|
| 379 |
+
height: var(--btn-height);
|
| 380 |
+
padding: var(--btn-padding);
|
| 381 |
+
clip-path: var(--btn-clip);
|
| 382 |
+
transition: background 0.25s ease;
|
| 383 |
+
}
|
| 384 |
+
[class*="btn-add-resource-"] button p {
|
| 385 |
+
font-weight: 700 !important;
|
| 386 |
+
font-size: var(--btn-fs);
|
| 387 |
+
letter-spacing: 0.094444em;
|
| 388 |
+
text-transform: uppercase;
|
| 389 |
+
}
|
| 390 |
+
[class*="btn-add-resource-"] button:hover {
|
| 391 |
+
background: var(--green);
|
| 392 |
+
}
|
| 393 |
+
|
| 394 |
/* Simple text button */
|
| 395 |
[class*="simple-btn-"] button {
|
| 396 |
color: var(--red);
|
data/generate_resource_template.py
CHANGED
|
@@ -19,7 +19,7 @@ resource_to_unit = {
|
|
| 19 |
# resource_caps
|
| 20 |
ws1 = wb.active
|
| 21 |
ws1.title = "resource_caps"
|
| 22 |
-
ws1.append(["Resource", "Unit", "Available Amount"])
|
| 23 |
ws1["A1"].font = Font(bold=True)
|
| 24 |
ws1["B1"].font = Font(bold=True)
|
| 25 |
ws1["C1"].font = Font(bold=True)
|
|
|
|
| 19 |
# resource_caps
|
| 20 |
ws1 = wb.active
|
| 21 |
ws1.title = "resource_caps"
|
| 22 |
+
ws1.append(["Resource", "Unit (per year)", "Available Amount"])
|
| 23 |
ws1["A1"].font = Font(bold=True)
|
| 24 |
ws1["B1"].font = Font(bold=True)
|
| 25 |
ws1["C1"].font = Font(bold=True)
|
data/resource_template.xlsx
CHANGED
|
Binary files a/data/resource_template.xlsx and b/data/resource_template.xlsx differ
|
|
|
tabs/tab0_welcome.py
CHANGED
|
@@ -51,5 +51,8 @@ def render_tab():
|
|
| 51 |
|
| 52 |
<p class="wt-cta"><span class="crra-hl">Let’s get started!</span> Use the side tabs to navigate through the sections.</p>
|
| 53 |
</div>
|
| 54 |
-
|
| 55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
<p class="wt-cta"><span class="crra-hl">Let’s get started!</span> Use the side tabs to navigate through the sections.</p>
|
| 53 |
</div>
|
| 54 |
+
""", unsafe_allow_html=True)
|
| 55 |
+
|
| 56 |
+
if st.button("Resource inputs →", key="btn-nav-next-welcome", type="primary", help="Start by setting the resources available for your CRRA."):
|
| 57 |
+
st.session_state["_navigate_to"] = "Resource inputs"
|
| 58 |
+
st.rerun()
|
tabs/tab1_inputs.py
CHANGED
|
@@ -443,8 +443,7 @@ def dialog_load_standard_resources():
|
|
| 443 |
None
|
| 444 |
"""
|
| 445 |
st.markdown(
|
| 446 |
-
'<p class="caption">Upload an Excel file
|
| 447 |
-
"columns — either the blank template or a previous export of your current values.</p>",
|
| 448 |
unsafe_allow_html=True,
|
| 449 |
)
|
| 450 |
|
|
@@ -468,7 +467,7 @@ def dialog_load_standard_resources():
|
|
| 468 |
]
|
| 469 |
st.dataframe(pd.DataFrame(preview_rows), width='stretch', hide_index=True)
|
| 470 |
|
| 471 |
-
if st.button("
|
| 472 |
for r in st.session_state.resource_caps:
|
| 473 |
st.session_state.resource_caps[r] = 0.0
|
| 474 |
st.session_state[f"cap_{r}"] = 0.0
|
|
@@ -487,7 +486,7 @@ def dialog_load_standard_resources():
|
|
| 487 |
for key in ["excel_loaded_file", "scenario_name_suggestion",
|
| 488 |
"scenario_loaded_file", "input_file_name_suggestion", "latest_result"]:
|
| 489 |
st.session_state.pop(key, None)
|
| 490 |
-
st.success("Resource values imported successfully.", icon="
|
| 491 |
st.rerun()
|
| 492 |
|
| 493 |
|
|
@@ -779,8 +778,10 @@ def render_tab(cost_sliders):
|
|
| 779 |
|
| 780 |
st.markdown('<p class="crra-sub-sub-heading">Import resource values</p>', unsafe_allow_html=True)
|
| 781 |
st.markdown(
|
| 782 |
-
"<p class='caption'>
|
| 783 |
-
"
|
|
|
|
|
|
|
| 784 |
unsafe_allow_html=True,
|
| 785 |
)
|
| 786 |
|
|
@@ -797,7 +798,7 @@ def render_tab(cost_sliders):
|
|
| 797 |
if st.button("Load from Excel", type="tertiary", icon=":material/arrow_upload_progress:", key="btn-load-resources"):
|
| 798 |
dialog_load_standard_resources()
|
| 799 |
if st.session_state.get("resource_last_loaded_file"):
|
| 800 |
-
st.success(f"Loaded: **{st.session_state['resource_last_loaded_file']}**", icon="
|
| 801 |
|
| 802 |
# Standard resource inputs
|
| 803 |
st.markdown('<p class="crra-sub-heading">Set available resources for one year</p>', unsafe_allow_html=True)
|
|
@@ -841,8 +842,8 @@ def render_tab(cost_sliders):
|
|
| 841 |
_, col_button = st.columns([3,1])
|
| 842 |
with col_button:
|
| 843 |
if st.button(
|
| 844 |
-
"Add resource", key=f"
|
| 845 |
-
type="
|
| 846 |
):
|
| 847 |
dialog_add_custom_resource(cost_sliders, preset_resources=rlist)
|
| 848 |
|
|
@@ -873,4 +874,8 @@ def render_tab(cost_sliders):
|
|
| 873 |
|
| 874 |
panel_custom_resources_controls(cost_sliders)
|
| 875 |
|
| 876 |
-
render_resource_summary()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 443 |
None
|
| 444 |
"""
|
| 445 |
st.markdown(
|
| 446 |
+
'<p class="caption">Upload an Excel file: either the blank template or a previous export of your current values.</p>',
|
|
|
|
| 447 |
unsafe_allow_html=True,
|
| 448 |
)
|
| 449 |
|
|
|
|
| 467 |
]
|
| 468 |
st.dataframe(pd.DataFrame(preview_rows), width='stretch', hide_index=True)
|
| 469 |
|
| 470 |
+
if st.button("Import values", type="primary", width='stretch', icon=":material/check:"):
|
| 471 |
for r in st.session_state.resource_caps:
|
| 472 |
st.session_state.resource_caps[r] = 0.0
|
| 473 |
st.session_state[f"cap_{r}"] = 0.0
|
|
|
|
| 486 |
for key in ["excel_loaded_file", "scenario_name_suggestion",
|
| 487 |
"scenario_loaded_file", "input_file_name_suggestion", "latest_result"]:
|
| 488 |
st.session_state.pop(key, None)
|
| 489 |
+
st.success("Resource values imported successfully.", icon=":material/check:")
|
| 490 |
st.rerun()
|
| 491 |
|
| 492 |
|
|
|
|
| 778 |
|
| 779 |
st.markdown('<p class="crra-sub-sub-heading">Import resource values</p>', unsafe_allow_html=True)
|
| 780 |
st.markdown(
|
| 781 |
+
"<p class='caption'>You can use the template below to fill in your resource availability values "
|
| 782 |
+
"for a given year and import them directly into the tool."
|
| 783 |
+
"<br>"
|
| 784 |
+
"You can also re-import a file previously downloaded from this section.</p>",
|
| 785 |
unsafe_allow_html=True,
|
| 786 |
)
|
| 787 |
|
|
|
|
| 798 |
if st.button("Load from Excel", type="tertiary", icon=":material/arrow_upload_progress:", key="btn-load-resources"):
|
| 799 |
dialog_load_standard_resources()
|
| 800 |
if st.session_state.get("resource_last_loaded_file"):
|
| 801 |
+
st.success(f"Loaded: **{st.session_state['resource_last_loaded_file']}**", icon=":material/check:")
|
| 802 |
|
| 803 |
# Standard resource inputs
|
| 804 |
st.markdown('<p class="crra-sub-heading">Set available resources for one year</p>', unsafe_allow_html=True)
|
|
|
|
| 842 |
_, col_button = st.columns([3,1])
|
| 843 |
with col_button:
|
| 844 |
if st.button(
|
| 845 |
+
"Add resource", key=f"btn-add-resource-{group}",
|
| 846 |
+
type="secondary", icon=":material/add:", width='stretch'
|
| 847 |
):
|
| 848 |
dialog_add_custom_resource(cost_sliders, preset_resources=rlist)
|
| 849 |
|
|
|
|
| 874 |
|
| 875 |
panel_custom_resources_controls(cost_sliders)
|
| 876 |
|
| 877 |
+
render_resource_summary()
|
| 878 |
+
|
| 879 |
+
if st.button("Method constraints →", key="btn-nav-next-inputs", type="primary", help="Configure which CDR methods are active and set their constraints."):
|
| 880 |
+
st.session_state["_navigate_to"] = "Method constraints"
|
| 881 |
+
st.rerun()
|
tabs/tab2_constraints.py
CHANGED
|
@@ -548,7 +548,7 @@ def render_tab(cost_sliders):
|
|
| 548 |
"""
|
| 549 |
<div class="text_with_border">
|
| 550 |
<p>
|
| 551 |
-
This section lets you <strong>control which CDR methods are included
|
| 552 |
For example, you can switch off marine CDR methods for those country with no access to the seas. Some countries may also have legal or tacit bans on specific CDR methods.<br><br>
|
| 553 |
For the included methods, you can also set a <strong>maximum deployment cap</strong> per method, by <strong>% of its total potential</strong> or by an <strong>absolute value</strong> in {CO2_UNIT}/yr. Use this parameter in situations where the resources are largely available in the country but other parameters will hinder the deployment, for example low maturity CDR methods, slow deployment of CO2 transport infrastructure, documented low adoption rates...
|
| 554 |
</p>
|
|
@@ -611,4 +611,8 @@ def render_tab(cost_sliders):
|
|
| 611 |
render_method(m, cols, col_idx, cost_sliders_norm)
|
| 612 |
col_idx += 1
|
| 613 |
|
| 614 |
-
render_constraints_summary()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 548 |
"""
|
| 549 |
<div class="text_with_border">
|
| 550 |
<p>
|
| 551 |
+
This section lets you <strong>control which CDR methods are included or excluded from the optimization</strong> according to the specific context of the studied country.<br>
|
| 552 |
For example, you can switch off marine CDR methods for those country with no access to the seas. Some countries may also have legal or tacit bans on specific CDR methods.<br><br>
|
| 553 |
For the included methods, you can also set a <strong>maximum deployment cap</strong> per method, by <strong>% of its total potential</strong> or by an <strong>absolute value</strong> in {CO2_UNIT}/yr. Use this parameter in situations where the resources are largely available in the country but other parameters will hinder the deployment, for example low maturity CDR methods, slow deployment of CO2 transport infrastructure, documented low adoption rates...
|
| 554 |
</p>
|
|
|
|
| 611 |
render_method(m, cols, col_idx, cost_sliders_norm)
|
| 612 |
col_idx += 1
|
| 613 |
|
| 614 |
+
render_constraints_summary()
|
| 615 |
+
|
| 616 |
+
if st.button("Resource coefficients →", key="btn-nav-next-constraints", type="primary", help="Optional: adjust resource efficiency coefficients per method."):
|
| 617 |
+
st.session_state["_navigate_to"] = "Resource coefficients"
|
| 618 |
+
st.rerun()
|
tabs/tab3_coefficients.py
CHANGED
|
@@ -512,4 +512,9 @@ def render_tab(cost_sliders):
|
|
| 512 |
enabled_methods=enabled_methods,
|
| 513 |
custom_resource_names=custom_resource_names,
|
| 514 |
custom_resource_to_sub_group=custom_resource_to_sub_group,
|
| 515 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 512 |
enabled_methods=enabled_methods,
|
| 513 |
custom_resource_names=custom_resource_names,
|
| 514 |
custom_resource_to_sub_group=custom_resource_to_sub_group,
|
| 515 |
+
)
|
| 516 |
+
|
| 517 |
+
|
| 518 |
+
if st.button("Portfolio generation →", key="btn-nav-next-coefficients", type="primary", help="Run the optimization and generate your CDR portfolio."):
|
| 519 |
+
st.session_state["_navigate_to"] = "Portfolio generation"
|
| 520 |
+
st.rerun()
|
tabs/tab4_optimization.py
CHANGED
|
@@ -1472,4 +1472,9 @@ def render_tab():
|
|
| 1472 |
render_export_tab(latest)
|
| 1473 |
|
| 1474 |
with tab_reset:
|
| 1475 |
-
reset_scenario("reset")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1472 |
render_export_tab(latest)
|
| 1473 |
|
| 1474 |
with tab_reset:
|
| 1475 |
+
reset_scenario("reset")
|
| 1476 |
+
|
| 1477 |
+
st.markdown("---")
|
| 1478 |
+
if st.button("Sensitivity analysis →", key="btn-nav-next-optimization", type="primary", help="Explore how resource or method changes affect your portfolio."):
|
| 1479 |
+
st.session_state["_navigate_to"] = "Sensitivity analysis"
|
| 1480 |
+
st.rerun()
|
tabs/tab5_sensitivity.py
CHANGED
|
@@ -280,4 +280,8 @@ def render_tab():
|
|
| 280 |
panel_resource_sensitivity(baseline_removed, all_resources, resource_to_unit)
|
| 281 |
|
| 282 |
with tab2:
|
| 283 |
-
panel_method_sensitivity(baseline_removed)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 280 |
panel_resource_sensitivity(baseline_removed, all_resources, resource_to_unit)
|
| 281 |
|
| 282 |
with tab2:
|
| 283 |
+
panel_method_sensitivity(baseline_removed)
|
| 284 |
+
|
| 285 |
+
if st.button("Scenario comparison →", key="btn-nav-next-sensitivity", type="primary", help="Compare multiple saved scenarios side by side."):
|
| 286 |
+
st.session_state["_navigate_to"] = "Scenario comparison"
|
| 287 |
+
st.rerun()
|
tabs/tab6_scenarios.py
CHANGED
|
@@ -175,7 +175,7 @@ def render_tab():
|
|
| 175 |
panel_load_scenario()
|
| 176 |
|
| 177 |
if not st.session_state.scenarios:
|
| 178 |
-
st.info("No scenarios saved yet. Run an optimization in **Portfolio generation** tab or load a file above.", icon=":material/info:")
|
| 179 |
return
|
| 180 |
|
| 181 |
# Select & manage scenarios
|
|
|
|
| 175 |
panel_load_scenario()
|
| 176 |
|
| 177 |
if not st.session_state.scenarios:
|
| 178 |
+
st.info("No scenarios saved yet. Run an optimization in **Portfolio generation** tab and **save the scenario** or load a file above.", icon=":material/info:")
|
| 179 |
return
|
| 180 |
|
| 181 |
# Select & manage scenarios
|
utils/data_utils.py
CHANGED
|
@@ -392,7 +392,7 @@ def resource_caps_to_excel_bytes(resource_caps: dict) -> bytes:
|
|
| 392 |
rows = [
|
| 393 |
{
|
| 394 |
"Resource": r,
|
| 395 |
-
"Unit": resource_to_unit.get(r, ""),
|
| 396 |
"Available Amount": float(amount),
|
| 397 |
}
|
| 398 |
for r, amount in resource_caps.items()
|
|
|
|
| 392 |
rows = [
|
| 393 |
{
|
| 394 |
"Resource": r,
|
| 395 |
+
"Unit (per year)": resource_to_unit.get(r, ""),
|
| 396 |
"Available Amount": float(amount),
|
| 397 |
}
|
| 398 |
for r, amount in resource_caps.items()
|