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

[FIX/FEAT] Move bottleneck analysis from tab4 to tab5, add chart interpretation captions to both tabs

Browse files
Files changed (2) hide show
  1. tabs/tab4_optimization.py +24 -47
  2. tabs/tab5_sensitivity.py +69 -3
tabs/tab4_optimization.py CHANGED
@@ -827,6 +827,12 @@ def show_resource_balance(latest):
827
  None
828
  """
829
  st.markdown('<p class="crra-sub-heading">Resource balance: used vs available</p>', unsafe_allow_html=True)
 
 
 
 
 
 
830
  resource_caps = latest["resource_caps"]
831
  used_total = latest["resource_used_total"]
832
 
@@ -884,45 +890,6 @@ def show_resource_balance(latest):
884
  st.dataframe(df, width='stretch', hide_index=True)
885
  register_chart_data("Resource balance (used vs remaining)", df)
886
 
887
- def show_bottleneck_analysis(latest):
888
- """Render the resource sensitivity table (CDR gain per +1 unit of each resource).
889
-
890
- Args:
891
- latest (dict): result dict with keys resource_actual_gain, custom_resources.
892
-
893
- Returns:
894
- None
895
- """
896
- st.markdown('<p class="crra-sub-heading">Resource sensitivity</p>', unsafe_allow_html=True)
897
- st.markdown(
898
- '<p class="caption">'
899
- 'For every resource: actual CDR gain if you add exactly +1 unit, recomputed with all other constraints in place.'
900
- '</p>',
901
- unsafe_allow_html=True,
902
- )
903
-
904
- resource_to_group = get_resource_to_group()
905
- custom_batch_lookup = {b["name"]: b for b in latest.get("custom_resources", [])}
906
-
907
- actual_gains = latest.get("resource_actual_gain", {})
908
- if actual_gains:
909
- sp_rows = []
910
- for r, gain in sorted(actual_gains.items(), key=lambda x: -x[1]):
911
- if r in custom_batch_lookup:
912
- unit = resource_unit_map.get(resource_to_group.get(custom_batch_lookup[r]["group"]), "?")
913
- else:
914
- unit = resource_unit_map.get(resource_to_group.get(r), "?")
915
- sp_rows.append({
916
- "Resource": r,
917
- "Unit": unit,
918
- f"CDR gain for +1 unit ({CO2_UNIT})": round(gain, 4),
919
- })
920
- df_sp = pd.DataFrame(sp_rows)
921
- st.dataframe(df_sp, width='stretch', hide_index=True)
922
- register_chart_data("Resource sensitivity (CDR gain per +1 unit)", df_sp)
923
- else:
924
- st.info("No resources to analyse.", icon=":material/info:")
925
-
926
  def save_scenario(latest):
927
  """Render the save-scenario widget and persist the current result to session state.
928
 
@@ -1378,11 +1345,10 @@ def render_tab():
1378
  ]:
1379
  st.session_state.setdefault(key, default)
1380
 
1381
- tab_run, tab_results, tab_resources, tab_bottleneck, tab_export, tab_reset = st.tabs([
1382
  "▶️ Run",
1383
  "📊 Method allocation",
1384
  "🌿 Resource usage",
1385
- "🔍 Bottleneck analysis",
1386
  "💾 Export & Save",
1387
  "🔄 Reset"
1388
  ])
@@ -1419,7 +1385,7 @@ def render_tab():
1419
 
1420
  latest = st.session_state.get("latest_result")
1421
  if not latest:
1422
- for tab in [tab_results, tab_resources, tab_bottleneck, tab_export]:
1423
  with tab:
1424
  st.markdown(no_results, unsafe_allow_html=True)
1425
  with tab_reset:
@@ -1431,7 +1397,7 @@ def render_tab():
1431
  method_costs = latest["method_costs"]
1432
 
1433
  if not allocations or sum(allocations.values()) == 0:
1434
- for tab in [tab_results, tab_resources, tab_bottleneck, tab_export]:
1435
  with tab:
1436
  st.markdown(no_results, unsafe_allow_html=True)
1437
  with tab_reset:
@@ -1452,29 +1418,40 @@ def render_tab():
1452
  st.markdown('<p class="crra-sub-heading">Optimization Summary</p>', unsafe_allow_html=True)
1453
  st.dataframe(df_display, width='stretch')
1454
  st.markdown('<p class="crra-sub-heading">Method allocation breakdown</p>', unsafe_allow_html=True)
 
 
 
 
 
1455
  show_donut(df_result)
1456
 
1457
  with tab_resources:
1458
  st.markdown('<p class="crra-sub-heading">Resource usage heatmap (% of cap)</p>', unsafe_allow_html=True)
 
 
 
 
 
1459
  show_heatmap(df_result, resource_caps, method_costs,
1460
  custom_resources=latest.get("custom_resources", []),
1461
  resource_usage=latest.get("resource_usage"))
1462
  st.markdown('<p class="crra-sub-heading">Absolute resource usage by unit</p>', unsafe_allow_html=True)
 
 
 
 
 
1463
  show_absolute_usage(allocations, method_costs,
1464
  custom_resources=latest.get("custom_resources", []),
1465
  resource_usage=latest.get("resource_usage"))
1466
  show_resource_balance(latest)
1467
 
1468
- with tab_bottleneck:
1469
- show_bottleneck_analysis(latest)
1470
-
1471
  with tab_export:
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()
 
827
  None
828
  """
829
  st.markdown('<p class="crra-sub-heading">Resource balance: used vs available</p>', unsafe_allow_html=True)
830
+ st.markdown(
831
+ '<p class="caption">Green bars show quantities consumed by the optimised portfolio; grey bars show the unused remainder. '
832
+ '<br>Resources with almost no grey bar are fully utilised and may be limiting further CDR. '
833
+ '<br>Resources with a large grey bar have spare capacity: either no method needs them or their demand is already saturated elsewhere.</p>',
834
+ unsafe_allow_html=True,
835
+ )
836
  resource_caps = latest["resource_caps"]
837
  used_total = latest["resource_used_total"]
838
 
 
890
  st.dataframe(df, width='stretch', hide_index=True)
891
  register_chart_data("Resource balance (used vs remaining)", df)
892
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
893
  def save_scenario(latest):
894
  """Render the save-scenario widget and persist the current result to session state.
895
 
 
1345
  ]:
1346
  st.session_state.setdefault(key, default)
1347
 
1348
+ tab_run, tab_results, tab_resources, tab_export, tab_reset = st.tabs([
1349
  "▶️ Run",
1350
  "📊 Method allocation",
1351
  "🌿 Resource usage",
 
1352
  "💾 Export & Save",
1353
  "🔄 Reset"
1354
  ])
 
1385
 
1386
  latest = st.session_state.get("latest_result")
1387
  if not latest:
1388
+ for tab in [tab_results, tab_resources, tab_export]:
1389
  with tab:
1390
  st.markdown(no_results, unsafe_allow_html=True)
1391
  with tab_reset:
 
1397
  method_costs = latest["method_costs"]
1398
 
1399
  if not allocations or sum(allocations.values()) == 0:
1400
+ for tab in [tab_results, tab_resources, tab_export]:
1401
  with tab:
1402
  st.markdown(no_results, unsafe_allow_html=True)
1403
  with tab_reset:
 
1418
  st.markdown('<p class="crra-sub-heading">Optimization Summary</p>', unsafe_allow_html=True)
1419
  st.dataframe(df_display, width='stretch')
1420
  st.markdown('<p class="crra-sub-heading">Method allocation breakdown</p>', unsafe_allow_html=True)
1421
+ st.markdown(
1422
+ '<p class="caption">Share of total CO₂ removal contributed by each active method. '
1423
+ 'Larger slices indicate methods that dominate the portfolio.</p>',
1424
+ unsafe_allow_html=True,
1425
+ )
1426
  show_donut(df_result)
1427
 
1428
  with tab_resources:
1429
  st.markdown('<p class="crra-sub-heading">Resource usage heatmap (% of cap)</p>', unsafe_allow_html=True)
1430
+ st.markdown(
1431
+ '<p class="caption">Each cell shows what percentage of a resource\'s available cap is consumed by a given method.'
1432
+ '<br>Dark cells indicate near-full use: those resource–method pairs are the most constrained.</p>',
1433
+ unsafe_allow_html=True,
1434
+ )
1435
  show_heatmap(df_result, resource_caps, method_costs,
1436
  custom_resources=latest.get("custom_resources", []),
1437
  resource_usage=latest.get("resource_usage"))
1438
  st.markdown('<p class="crra-sub-heading">Absolute resource usage by unit</p>', unsafe_allow_html=True)
1439
+ st.markdown(
1440
+ '<p class="caption">Absolute quantities consumed per resource and per method, in their native units. '
1441
+ 'Use this view to compare the scale of demand across resources with different units (e.g. ha, m³/yr, t/yr).</p>',
1442
+ unsafe_allow_html=True,
1443
+ )
1444
  show_absolute_usage(allocations, method_costs,
1445
  custom_resources=latest.get("custom_resources", []),
1446
  resource_usage=latest.get("resource_usage"))
1447
  show_resource_balance(latest)
1448
 
 
 
 
1449
  with tab_export:
1450
  render_export_tab(latest)
1451
 
1452
  with tab_reset:
1453
  reset_scenario("reset")
1454
 
 
1455
  if st.button("Sensitivity analysis →", key="btn-nav-next-optimization", type="primary", help="Explore how resource or method changes affect your portfolio."):
1456
  st.session_state["_navigate_to"] = "Sensitivity analysis"
1457
  st.rerun()
tabs/tab5_sensitivity.py CHANGED
@@ -2,8 +2,8 @@ import copy
2
  import pandas as pd
3
  import streamlit as st
4
  import plotly.express as px
5
- from config.config import CO2_UNIT, hidden_resource_names, method_order, resource_order
6
- from utils.data_utils import (get_resource_to_unit, run_expanded_optimization,
7
  is_other_method, get_other_method_variants, make_variant_name,
8
  default_constraint)
9
  from utils.ui_helpers import register_chart_data, show_bar
@@ -65,6 +65,13 @@ def panel_resource_sensitivity(baseline_removed, all_resources, resource_to_unit
65
  '<p class="crra-sub-heading">Impact of resource availability</p>',
66
  unsafe_allow_html=True,
67
  )
 
 
 
 
 
 
 
68
  selected_resources = st.multiselect(
69
  "Select one or more resources:", all_resources, key="crra-select-resource"
70
  )
@@ -144,6 +151,14 @@ def panel_method_sensitivity(baseline_removed):
144
  '<p class="crra-sub-heading">Impact of max allocation per method</p>',
145
  unsafe_allow_html=True,
146
  )
 
 
 
 
 
 
 
 
147
 
148
  # "Other"-tagged methods with custom batches attached are expanded into
149
  # independent variants (e.g. "Enhanced weathering - Other mineral: Ganite")
@@ -249,6 +264,54 @@ def panel_method_sensitivity(baseline_removed):
249
  register_chart_data("Sensitivity to method cap", df)
250
 
251
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
  def render_tab():
253
  """Render the Sensitivity Analysis tab.
254
 
@@ -274,7 +337,7 @@ def render_tab():
274
 
275
  baseline_removed, all_resources, resource_to_unit = compute_baseline()
276
 
277
- tab1, tab2 = st.tabs(["🔁 Resource sensitivity", "🔁 Method sensitivity"])
278
 
279
  with tab1:
280
  panel_resource_sensitivity(baseline_removed, all_resources, resource_to_unit)
@@ -282,6 +345,9 @@ def render_tab():
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()
 
2
  import pandas as pd
3
  import streamlit as st
4
  import plotly.express as px
5
+ from config.config import CO2_UNIT, hidden_resource_names, method_order, resource_order, resource_unit_map
6
+ from utils.data_utils import (get_resource_to_unit, get_resource_to_group, run_expanded_optimization,
7
  is_other_method, get_other_method_variants, make_variant_name,
8
  default_constraint)
9
  from utils.ui_helpers import register_chart_data, show_bar
 
65
  '<p class="crra-sub-heading">Impact of resource availability</p>',
66
  unsafe_allow_html=True,
67
  )
68
+ st.markdown(
69
+ '<p class="caption">Each line shows how total CO₂ removal changes as one resource\'s availability varies from 0% to 200% of its current cap. '
70
+ '<br>The range goes up to 200% so you can see both the impact of a reduction and the potential gains from doubling availability. '
71
+ '<br>A steep curve means that resource strongly drives portfolio performance: small increases can yield large gains. '
72
+ '<br>A flat line means the portfolio is limited by something else (another resource or a method cap).</p>',
73
+ unsafe_allow_html=True,
74
+ )
75
  selected_resources = st.multiselect(
76
  "Select one or more resources:", all_resources, key="crra-select-resource"
77
  )
 
151
  '<p class="crra-sub-heading">Impact of max allocation per method</p>',
152
  unsafe_allow_html=True,
153
  )
154
+ st.markdown(
155
+ '<p class="caption">Each line shows how total CO₂ removal changes as one method\'s maximum deployment cap varies from 0% to 100% of its current setting: '
156
+ 'whether that cap was defined as a percentage of potential or as an absolute value in the Method constraints tab. '
157
+ '<br>The scale stops at 100% because the cap represents a ceiling you set: going beyond it would mean overriding your own constraint. '
158
+ '<br>A steep curve means the cap is a binding constraint: relaxing it would allow more removal. '
159
+ '<br>A flat line means the method is already limited by resource availability, not by its cap.</p>',
160
+ unsafe_allow_html=True,
161
+ )
162
 
163
  # "Other"-tagged methods with custom batches attached are expanded into
164
  # independent variants (e.g. "Enhanced weathering - Other mineral: Ganite")
 
264
  register_chart_data("Sensitivity to method cap", df)
265
 
266
 
267
+ def show_bottleneck_analysis():
268
+ """Render the resource sensitivity table (CDR gain per +1 unit of each resource).
269
+
270
+ Reads the latest optimization result from session state. Shows nothing if no
271
+ result is available yet.
272
+
273
+ Returns:
274
+ None
275
+ """
276
+ latest = st.session_state.get("latest_result")
277
+ if not latest:
278
+ st.info("Run the optimization first (Portfolio generation tab) to see the bottleneck analysis.", icon=":material/info:")
279
+ return
280
+
281
+ st.markdown('<p class="crra-sub-heading">Resource sensitivity</p>', unsafe_allow_html=True)
282
+ st.markdown(
283
+ '<p class="caption">'
284
+ 'For each resource: the additional CO₂ that could be removed by adding exactly +1 unit, '
285
+ 'recomputed with all other constraints held fixed. '
286
+ '<br>Resources at the top of the list are the most binding bottlenecks in your current portfolio: '
287
+ 'prioritise increasing their availability to unlock the largest gains.'
288
+ '</p>',
289
+ unsafe_allow_html=True,
290
+ )
291
+
292
+ resource_to_group = get_resource_to_group()
293
+ custom_batch_lookup = {b["name"]: b for b in latest.get("custom_resources", [])}
294
+ actual_gains = latest.get("resource_actual_gain", {})
295
+
296
+ if actual_gains:
297
+ sp_rows = []
298
+ for r, gain in sorted(actual_gains.items(), key=lambda x: -x[1]):
299
+ if r in custom_batch_lookup:
300
+ unit = resource_unit_map.get(resource_to_group.get(custom_batch_lookup[r]["group"]), "?")
301
+ else:
302
+ unit = resource_unit_map.get(resource_to_group.get(r), "?")
303
+ sp_rows.append({
304
+ "Resource": r,
305
+ "Unit": unit,
306
+ f"CDR gain for +1 unit ({CO2_UNIT})": round(gain, 4),
307
+ })
308
+ df_sp = pd.DataFrame(sp_rows)
309
+ st.dataframe(df_sp, width='stretch', hide_index=True)
310
+ register_chart_data("Resource sensitivity (CDR gain per +1 unit)", df_sp)
311
+ else:
312
+ st.info("No resources to analyse.", icon=":material/info:")
313
+
314
+
315
  def render_tab():
316
  """Render the Sensitivity Analysis tab.
317
 
 
337
 
338
  baseline_removed, all_resources, resource_to_unit = compute_baseline()
339
 
340
+ tab1, tab2, tab3 = st.tabs(["🔁 Resource sensitivity", "🔁 Method sensitivity", "🔍 Bottleneck analysis"])
341
 
342
  with tab1:
343
  panel_resource_sensitivity(baseline_removed, all_resources, resource_to_unit)
 
345
  with tab2:
346
  panel_method_sensitivity(baseline_removed)
347
 
348
+ with tab3:
349
+ show_bottleneck_analysis()
350
+
351
  if st.button("Scenario comparison →", key="btn-nav-next-sensitivity", type="primary", help="Compare multiple saved scenarios side by side."):
352
  st.session_state["_navigate_to"] = "Scenario comparison"
353
  st.rerun()