CarlyneB commited on
Commit
20f0fcc
·
1 Parent(s): 848c8a3

[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 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=0,
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
- <div style="height: 4rem;"></div>
55
- """, unsafe_allow_html=True)
 
 
 
 
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 with "Resource" and "Available Amount" '
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("↩️ Import values", type="primary", width='stretch'):
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'>Load standard resource availabilities from an Excel file, "
783
- "or download the current values.</p>",
 
 
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"simple-btn-resource-{group}",
845
- type="tertiary", icon=":material/add:", width='stretch'
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 in 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,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()