CarbonGap commited on
Commit
f4a9ee0
·
verified ·
1 Parent(s): 739483f

Upload 47 files

Browse files
.gitignore ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python and system files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.DS_Store
5
+
6
+ # Virtual environments
7
+ .venv/
8
+ env/
9
+ .interface/
10
+
11
+ # Streamlit & logs
12
+ .streamlit/
13
+ *.log
14
+
15
+ # IDE & config
16
+ .vscode/
17
+ .env
config/__pycache__/config.cpython-311.pyc CHANGED
Binary files a/config/__pycache__/config.cpython-311.pyc and b/config/__pycache__/config.cpython-311.pyc differ
 
config/config.py CHANGED
@@ -38,6 +38,7 @@ resource_groups = {
38
  "Energy": ["Electrical energy (MWh/tCO2)", "Thermal energy unspecified grade (MWh/tCO2)", "Thermal energy low grade (MWh/tCO2)", "Thermal energy high grade (MWh/tCO2)"],
39
  "Water": ["Salt water (Liters/tCO2)", "Unspecified water (Liters/tCO2)"],
40
  "Shoreline": ["Shoreline (km/tCO2)"],
 
41
  }
42
 
43
  method_order = sum(method_groups.values(), [])
@@ -52,13 +53,14 @@ resource_unit_map = {
52
  "Minerals": "t",
53
  "Chemicals": "t",
54
  "Shoreline": "km",
55
- "Water": "L"
 
56
  }
57
 
58
  resource_units = {
59
  "ha/tCO2": ["Arable land (ha/tCO2)", "Non-arable land (ha/tCO2)", "Unspecified land (ha/tCO2)"],
60
  "MWh/tCO2": ["Electrical energy (MWh/tCO2)", "Thermal energy unspecified grade (MWh/tCO2)", "Thermal energy low grade (MWh/tCO2)", "Thermal energy high grade (MWh/tCO2)"],
61
- "t/tCO2": ["Forest biomass (t/tCO2)", "Non-forest biomass (t/tCO2)", "Unspecified biomass (t/tCO2)", "Basalt mineral (t/tCO2)", "Olivine mineral (t/tCO2)", "Wollastonite mineral (t/tCO2)", "Unspecified mineral (t/tCO2)", "Unspecified chemical (t/tCO2)"],
62
  "km/tCO2": ["Shoreline (km/tCO2)"],
63
  "Liters/tCO2": ["Salt water (Liters/tCO2)", "Unspecified water (Liters/tCO2)"]
64
  }
@@ -70,7 +72,9 @@ resource_grouped = {
70
  "Minerals": ["Basalt mineral (t/tCO2)", "Olivine mineral (t/tCO2)", "Wollastonite mineral (t/tCO2)", "Unspecified mineral (t/tCO2)"],
71
  "Chemicals": ["Unspecified chemical (t/tCO2)"],
72
  "Shoreline": ["Shoreline (km/tCO2)"],
73
- "Water": ["Salt water (Liters/tCO2)", "Unspecified water (Liters/tCO2)"]
 
 
74
  }
75
 
76
  # secondary resources
 
38
  "Energy": ["Electrical energy (MWh/tCO2)", "Thermal energy unspecified grade (MWh/tCO2)", "Thermal energy low grade (MWh/tCO2)", "Thermal energy high grade (MWh/tCO2)"],
39
  "Water": ["Salt water (Liters/tCO2)", "Unspecified water (Liters/tCO2)"],
40
  "Shoreline": ["Shoreline (km/tCO2)"],
41
+ "Storage": ["Geologic storage (t/tCO2)"]
42
  }
43
 
44
  method_order = sum(method_groups.values(), [])
 
53
  "Minerals": "t",
54
  "Chemicals": "t",
55
  "Shoreline": "km",
56
+ "Water": "L",
57
+ "Storage": "t"
58
  }
59
 
60
  resource_units = {
61
  "ha/tCO2": ["Arable land (ha/tCO2)", "Non-arable land (ha/tCO2)", "Unspecified land (ha/tCO2)"],
62
  "MWh/tCO2": ["Electrical energy (MWh/tCO2)", "Thermal energy unspecified grade (MWh/tCO2)", "Thermal energy low grade (MWh/tCO2)", "Thermal energy high grade (MWh/tCO2)"],
63
+ "t/tCO2": ["Forest biomass (t/tCO2)", "Non-forest biomass (t/tCO2)", "Unspecified biomass (t/tCO2)", "Basalt mineral (t/tCO2)", "Olivine mineral (t/tCO2)", "Wollastonite mineral (t/tCO2)", "Unspecified mineral (t/tCO2)", "Unspecified chemical (t/tCO2)", "Geologic storage (t/tCO2)"],
64
  "km/tCO2": ["Shoreline (km/tCO2)"],
65
  "Liters/tCO2": ["Salt water (Liters/tCO2)", "Unspecified water (Liters/tCO2)"]
66
  }
 
72
  "Minerals": ["Basalt mineral (t/tCO2)", "Olivine mineral (t/tCO2)", "Wollastonite mineral (t/tCO2)", "Unspecified mineral (t/tCO2)"],
73
  "Chemicals": ["Unspecified chemical (t/tCO2)"],
74
  "Shoreline": ["Shoreline (km/tCO2)"],
75
+ "Water": ["Salt water (Liters/tCO2)", "Unspecified water (Liters/tCO2)"],
76
+ "Storage": ["Geologic storage (t/tCO2)"]
77
+
78
  }
79
 
80
  # secondary resources
data/cost_sliders.json CHANGED
@@ -33,6 +33,11 @@
33
  "min": 368182.0,
34
  "median": 565909.0,
35
  "max": 913636.0
 
 
 
 
 
36
  }
37
  },
38
  "BioCCS with combustion": {
@@ -65,6 +70,11 @@
65
  "min": 630.0,
66
  "median": 201625.2165,
67
  "max": 1296342.17
 
 
 
 
 
68
  }
69
  },
70
  "Coastal revegetation (mangrove, seaweeds)": {
@@ -118,6 +128,11 @@
118
  "min": 1000.0,
119
  "median": 5000.0,
120
  "max": 27000.0
 
 
 
 
 
121
  }
122
  },
123
  "Electrochemical OAE": {
@@ -169,6 +184,11 @@
169
  "min": 0.005,
170
  "median": 4700.0,
171
  "max": 50000.0
 
 
 
 
 
172
  }
173
  },
174
  "Low-temp DACCS (solid-sorbent/liquid-solvent)": {
@@ -191,6 +211,11 @@
191
  "min": -2000.0,
192
  "median": 1008.17439,
193
  "max": 15000.0
 
 
 
 
 
194
  }
195
  },
196
  "Mineral OAE / Ocean liming": {
@@ -245,6 +270,11 @@
245
  "min": 0.0,
246
  "median": 200.0,
247
  "max": 26714.00689
 
 
 
 
 
248
  }
249
  },
250
  "Mineralization ex-situ": {
@@ -294,6 +324,11 @@
294
  "min": -146.0,
295
  "median": 427.0,
296
  "max": 1000.0
 
 
 
 
 
297
  }
298
  },
299
  "Pasture management": {
@@ -330,6 +365,11 @@
330
  "min": 217391.304,
331
  "median": 6608695.652,
332
  "max": 13000000.0
 
 
 
 
 
333
  }
334
  },
335
  "Bio-char - Forest biomass": {
@@ -531,6 +571,11 @@
531
  "min": 630.0,
532
  "median": 201625.2165,
533
  "max": 1296342.17
 
 
 
 
 
534
  }
535
  },
536
  "BioCCS with anaerobic digestion/fermentation & growing biomass": {
@@ -543,6 +588,11 @@
543
  "min": 368182.0,
544
  "median": 565909.0,
545
  "max": 913636.0
 
 
 
 
 
546
  }
547
  }
548
  }
 
33
  "min": 368182.0,
34
  "median": 565909.0,
35
  "max": 913636.0
36
+ },
37
+ "Geologic storage (t/tCO2)": {
38
+ "min": 1.0,
39
+ "median": 1.0,
40
+ "max": 1.0
41
  }
42
  },
43
  "BioCCS with combustion": {
 
70
  "min": 630.0,
71
  "median": 201625.2165,
72
  "max": 1296342.17
73
+ },
74
+ "Geologic storage (t/tCO2)": {
75
+ "min": 1.0,
76
+ "median": 1.0,
77
+ "max": 1.0
78
  }
79
  },
80
  "Coastal revegetation (mangrove, seaweeds)": {
 
128
  "min": 1000.0,
129
  "median": 5000.0,
130
  "max": 27000.0
131
+ },
132
+ "Geologic storage (t/tCO2)": {
133
+ "min": 1.0,
134
+ "median": 1.0,
135
+ "max": 1.0
136
  }
137
  },
138
  "Electrochemical OAE": {
 
184
  "min": 0.005,
185
  "median": 4700.0,
186
  "max": 50000.0
187
+ },
188
+ "Geologic storage (t/tCO2)": {
189
+ "min": 1.0,
190
+ "median": 1.0,
191
+ "max": 1.0
192
  }
193
  },
194
  "Low-temp DACCS (solid-sorbent/liquid-solvent)": {
 
211
  "min": -2000.0,
212
  "median": 1008.17439,
213
  "max": 15000.0
214
+ },
215
+ "Geologic storage (t/tCO2)": {
216
+ "min": 1.0,
217
+ "median": 1.0,
218
+ "max": 1.0
219
  }
220
  },
221
  "Mineral OAE / Ocean liming": {
 
270
  "min": 0.0,
271
  "median": 200.0,
272
  "max": 26714.00689
273
+ },
274
+ "Geologic storage (t/tCO2)": {
275
+ "min": 1.0,
276
+ "median": 1.0,
277
+ "max": 1.0
278
  }
279
  },
280
  "Mineralization ex-situ": {
 
324
  "min": -146.0,
325
  "median": 427.0,
326
  "max": 1000.0
327
+ },
328
+ "Geologic storage (t/tCO2)": {
329
+ "min": 1.0,
330
+ "median": 1.0,
331
+ "max": 1.0
332
  }
333
  },
334
  "Pasture management": {
 
365
  "min": 217391.304,
366
  "median": 6608695.652,
367
  "max": 13000000.0
368
+ },
369
+ "Geologic storage (t/tCO2)": {
370
+ "min": 1.0,
371
+ "median": 1.0,
372
+ "max": 1.0
373
  }
374
  },
375
  "Bio-char - Forest biomass": {
 
571
  "min": 630.0,
572
  "median": 201625.2165,
573
  "max": 1296342.17
574
+ },
575
+ "Geologic storage (t/tCO2)": {
576
+ "min": 1.0,
577
+ "median": 1.0,
578
+ "max": 1.0
579
  }
580
  },
581
  "BioCCS with anaerobic digestion/fermentation & growing biomass": {
 
588
  "min": 368182.0,
589
  "median": 565909.0,
590
  "max": 913636.0
591
+ },
592
+ "Geologic storage (t/tCO2)": {
593
+ "min": 1.0,
594
+ "median": 1.0,
595
+ "max": 1.0
596
  }
597
  }
598
  }
optimization/__pycache__/optimization.cpython-311.pyc CHANGED
Binary files a/optimization/__pycache__/optimization.cpython-311.pyc and b/optimization/__pycache__/optimization.cpython-311.pyc differ
 
requirements.txt CHANGED
Binary files a/requirements.txt and b/requirements.txt differ
 
tabs/__pycache__/tab3_coefficients.cpython-311.pyc CHANGED
Binary files a/tabs/__pycache__/tab3_coefficients.cpython-311.pyc and b/tabs/__pycache__/tab3_coefficients.cpython-311.pyc differ
 
tabs/__pycache__/tab4_optimization.cpython-311.pyc CHANGED
Binary files a/tabs/__pycache__/tab4_optimization.cpython-311.pyc and b/tabs/__pycache__/tab4_optimization.cpython-311.pyc differ
 
tabs/__pycache__/tab5_sensitivity.cpython-311.pyc CHANGED
Binary files a/tabs/__pycache__/tab5_sensitivity.cpython-311.pyc and b/tabs/__pycache__/tab5_sensitivity.cpython-311.pyc differ
 
tabs/__pycache__/tab6_scenarios.cpython-311.pyc CHANGED
Binary files a/tabs/__pycache__/tab6_scenarios.cpython-311.pyc and b/tabs/__pycache__/tab6_scenarios.cpython-311.pyc differ
 
tabs/tab3_coefficients copy.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import streamlit as st
3
+
4
+ from config.config import (method_groups, method_order, resource_groups,
5
+ resource_order, secondary_resources)
6
+ from utils.ui_helpers import format_efficiency_summary
7
+
8
+
9
+ def render_tab(cost_sliders):
10
+ # Initialize costs
11
+ if "costs" not in st.session_state:
12
+ st.session_state.costs = {}
13
+ for m in cost_sliders:
14
+ if m not in st.session_state.costs:
15
+ st.session_state.costs[m] = {}
16
+ for r in cost_sliders[m]:
17
+ if r not in st.session_state.costs[m]:
18
+ st.session_state.costs[m][r] = cost_sliders[m][r]["median"]
19
+
20
+ st.header("Resource efficiency coefficients")
21
+
22
+ st.info("""
23
+ **Objective:**
24
+
25
+ This section lets you adjust the resource usage coefficients for each CDR method to best fit your country conditions.
26
+
27
+ - Select a method using the dropdown
28
+ - Adjust the resource usage coefficients (e.g., land, energy) using sliders
29
+ - Optionally override the slider using a manual input (even outside bounds)
30
+ - Disable secondary resources (e.g., water, chemicals) if needed
31
+ - Reset a single method or all methods with the reset buttons
32
+ - View a summary table of coefficients used in the model
33
+ """)
34
+ st.info("""
35
+ **Information:**
36
+
37
+ Data used in this section is based on
38
+ """)
39
+ st.warning("Blue cells in the table highlight coefficients modified from their **default median values**.")
40
+
41
+ # Combined dropdown for group + method
42
+ method_choices = []
43
+ method_lookup = {}
44
+ for group, methods in method_groups.items():
45
+ for method in methods:
46
+ label = f"{group} | {method}"
47
+ method_choices.append(label)
48
+ method_lookup[label] = method
49
+
50
+ selected_label = st.selectbox("Select a method:", method_choices)
51
+ method_selected = method_lookup[selected_label]
52
+
53
+ with st.expander(f"Manage resources for the {method_selected} method", expanded=True):
54
+ for group_name, rlist in resource_groups.items():
55
+ used = [r for r in rlist if r in cost_sliders[method_selected]]
56
+ if not used:
57
+ continue
58
+
59
+ st.markdown(f"### {group_name}")
60
+ for r in used:
61
+ conf = cost_sliders[method_selected][r]
62
+ default_val = conf["median"]
63
+ min_val = conf["min"]
64
+ max_val = conf["max"]
65
+ if min_val < 0.01:
66
+ step = 0.0001
67
+ fmt = "%.4f"
68
+ elif min_val < 1:
69
+ step = 0.001
70
+ fmt = "%.3f"
71
+ elif min_val < 100:
72
+ step = 0.01
73
+ fmt = "%.2f"
74
+ else:
75
+ step = 1.0
76
+ fmt = "%.0f"
77
+
78
+ slider_key = f"slider_{method_selected}_{r}".replace(" ", "_").lower()
79
+ manual_key = f"manual_{method_selected}_{r}".replace(" ", "_").lower()
80
+ toggle_key = f"toggle_{method_selected}_{r}".replace(" ", "_").lower()
81
+
82
+ is_secondary = (method_selected, r) in secondary_resources
83
+ if is_secondary and toggle_key not in st.session_state:
84
+ st.session_state[toggle_key] = False
85
+
86
+ enabled = True
87
+ if is_secondary:
88
+ enabled = st.checkbox(f"Use {r}", key=toggle_key)
89
+
90
+ if enabled:
91
+ col1, col2 = st.columns([3, 1])
92
+ with col1:
93
+ slider_val = st.slider(
94
+ label=r,
95
+ min_value=min_val,
96
+ max_value=max_val,
97
+ value=st.session_state.costs[method_selected].get(r, default_val),
98
+ step=step,
99
+ format=fmt,
100
+ key=slider_key
101
+ )
102
+ with col2:
103
+ manual_val = st.number_input(
104
+ label="",
105
+ value=slider_val,
106
+ key=manual_key,
107
+ help="Manual override (optional, any value)"
108
+ )
109
+
110
+ # Manual overrides slider if changed
111
+ final_val = manual_val if manual_val != slider_val else slider_val
112
+ st.session_state.costs[method_selected][r] = final_val
113
+ else:
114
+ st.session_state.costs[method_selected][r] = 0.0
115
+ st.markdown("**DISABLED**")
116
+
117
+ # Reset buttons
118
+ col1, col2 = st.columns(2)
119
+ with col1:
120
+ if st.button("🔄 Reset selected method"):
121
+ for r in cost_sliders[method_selected]:
122
+ default_val = cost_sliders[method_selected][r]["median"]
123
+ st.session_state.costs[method_selected][r] = default_val
124
+
125
+ # Clear UI state
126
+ for suffix in ["slider", "manual", "toggle"]:
127
+ key = f"{suffix}_{method_selected}_{r}".replace(" ", "_").lower()
128
+ if key in st.session_state:
129
+ del st.session_state[key]
130
+
131
+ st.success(f"{method_selected} reset to default values.")
132
+ st.rerun()
133
+
134
+ with col2:
135
+ if st.button("🔄 Reset all methods"):
136
+ for m in method_order:
137
+ for r in cost_sliders.get(m, {}):
138
+ default_val = cost_sliders[m][r]["median"]
139
+ st.session_state.costs[m][r] = default_val
140
+
141
+ for suffix in ["slider", "manual", "toggle"]:
142
+ key = f"{suffix}_{m}_{r}".replace(" ", "_").lower()
143
+ if key in st.session_state:
144
+ del st.session_state[key]
145
+
146
+ st.success("All methods reset to default values.")
147
+ st.rerun()
148
+
149
+ # Summary table
150
+ st.subheader("Summary table: resource usage coefficients in the model")
151
+
152
+ df_default = pd.DataFrame(index=method_order, columns=resource_order)
153
+ df_current = pd.DataFrame(index=method_order, columns=resource_order)
154
+
155
+ for m in method_order:
156
+ for r in resource_order:
157
+ df_default.at[m, r] = cost_sliders.get(m, {}).get(r, {}).get("median", 0.0)
158
+ df_current.at[m, r] = st.session_state.costs.get(m, {}).get(r, 0.0)
159
+
160
+ df_default = df_default.astype(float)
161
+ df_current = df_current.astype(float)
162
+
163
+ styled = format_efficiency_summary(df_current, df_default)
164
+ st.table(styled)
tabs/tab3_coefficients.py CHANGED
@@ -7,38 +7,31 @@ from utils.ui_helpers import format_efficiency_summary
7
 
8
 
9
  def render_tab(cost_sliders):
10
- # Initialize costs
11
- if "costs" not in st.session_state:
12
- st.session_state.costs = {}
13
- for m in cost_sliders:
14
- if m not in st.session_state.costs:
15
- st.session_state.costs[m] = {}
16
- for r in cost_sliders[m]:
17
- if r not in st.session_state.costs[m]:
18
- st.session_state.costs[m][r] = cost_sliders[m][r]["median"]
19
-
20
  st.header("Resource efficiency coefficients")
21
 
 
 
 
 
 
 
 
 
 
 
 
22
  st.info("""
23
  **Objective:**
24
 
25
  This section lets you adjust the resource usage coefficients for each CDR method to best fit your country conditions.
26
 
27
- - Select a method using the dropdown
28
- - Adjust the resource usage coefficients (e.g., land, energy) using sliders
29
- - Optionally override the slider using a manual input (even outside bounds)
30
- - Disable secondary resources (e.g., water, chemicals) if needed
31
- - Reset a single method or all methods with the reset buttons
32
- - View a summary table of coefficients used in the model
33
- """)
34
- st.info("""
35
- **Information:**
36
-
37
- Data used in this section is based on
38
  """)
39
  st.warning("Blue cells in the table highlight coefficients modified from their **default median values**.")
40
 
41
- # Combined dropdown for group + method
42
  method_choices = []
43
  method_lookup = {}
44
  for group, methods in method_groups.items():
@@ -50,6 +43,7 @@ def render_tab(cost_sliders):
50
  selected_label = st.selectbox("Select a method:", method_choices)
51
  method_selected = method_lookup[selected_label]
52
 
 
53
  with st.expander(f"Manage resources for the {method_selected} method", expanded=True):
54
  for group_name, rlist in resource_groups.items():
55
  used = [r for r in rlist if r in cost_sliders[method_selected]]
@@ -62,6 +56,7 @@ def render_tab(cost_sliders):
62
  default_val = conf["median"]
63
  min_val = conf["min"]
64
  max_val = conf["max"]
 
65
  if min_val < 0.01:
66
  step = 0.0001
67
  fmt = "%.4f"
@@ -75,59 +70,58 @@ def render_tab(cost_sliders):
75
  step = 1.0
76
  fmt = "%.0f"
77
 
78
- slider_key = f"slider_{method_selected}_{r}".replace(" ", "_").lower()
79
- manual_key = f"manual_{method_selected}_{r}".replace(" ", "_").lower()
80
- toggle_key = f"toggle_{method_selected}_{r}".replace(" ", "_").lower()
81
-
82
  is_secondary = (method_selected, r) in secondary_resources
83
- if is_secondary and toggle_key not in st.session_state:
84
- st.session_state[toggle_key] = False
85
 
86
- enabled = True
87
  if is_secondary:
 
 
88
  enabled = st.checkbox(f"Use {r}", key=toggle_key)
89
 
90
- if enabled:
91
- col1, col2 = st.columns([3, 1])
92
- with col1:
93
- slider_val = st.slider(
94
- label=r,
95
- min_value=min_val,
96
- max_value=max_val,
97
- value=st.session_state.costs[method_selected].get(r, default_val),
98
- step=step,
99
- format=fmt,
100
- key=slider_key
101
- )
102
- with col2:
103
- manual_val = st.number_input(
104
- label="",
105
- value=slider_val,
106
- key=manual_key,
107
- help="Manual override (optional, any value)"
108
- )
109
-
110
- # Manual overrides slider if changed
111
- final_val = manual_val if manual_val != slider_val else slider_val
112
- st.session_state.costs[method_selected][r] = final_val
113
- else:
114
- st.session_state.costs[method_selected][r] = 0.0
115
- st.markdown("**DISABLED**")
116
 
117
- # Reset buttons
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  col1, col2 = st.columns(2)
119
  with col1:
120
  if st.button("🔄 Reset selected method"):
121
  for r in cost_sliders[method_selected]:
122
- default_val = cost_sliders[method_selected][r]["median"]
123
- st.session_state.costs[method_selected][r] = default_val
124
-
125
- # Clear UI state
126
  for suffix in ["slider", "manual", "toggle"]:
127
  key = f"{suffix}_{method_selected}_{r}".replace(" ", "_").lower()
128
  if key in st.session_state:
129
  del st.session_state[key]
130
-
131
  st.success(f"{method_selected} reset to default values.")
132
  st.rerun()
133
 
@@ -135,18 +129,16 @@ def render_tab(cost_sliders):
135
  if st.button("🔄 Reset all methods"):
136
  for m in method_order:
137
  for r in cost_sliders.get(m, {}):
138
- default_val = cost_sliders[m][r]["median"]
139
- st.session_state.costs[m][r] = default_val
140
-
141
  for suffix in ["slider", "manual", "toggle"]:
142
  key = f"{suffix}_{m}_{r}".replace(" ", "_").lower()
143
  if key in st.session_state:
144
  del st.session_state[key]
145
-
146
  st.success("All methods reset to default values.")
147
  st.rerun()
148
 
149
- # Summary table
150
  st.subheader("Summary table: resource usage coefficients in the model")
151
 
152
  df_default = pd.DataFrame(index=method_order, columns=resource_order)
 
7
 
8
 
9
  def render_tab(cost_sliders):
 
 
 
 
 
 
 
 
 
 
10
  st.header("Resource efficiency coefficients")
11
 
12
+ # === Initialization ===
13
+ if "costs" not in st.session_state:
14
+ st.session_state.costs = {
15
+ m: {
16
+ r: (0.0 if (m, r) in secondary_resources else cost_sliders[m][r]["median"])
17
+ for r in cost_sliders[m]
18
+ }
19
+ for m in cost_sliders
20
+ }
21
+
22
+ # === Info ===
23
  st.info("""
24
  **Objective:**
25
 
26
  This section lets you adjust the resource usage coefficients for each CDR method to best fit your country conditions.
27
 
28
+ - Primary resources are always active and initialized with default values.
29
+ - Secondary resources are initialized at zero and toggled off by default.
30
+ - Toggling on a secondary resource restores its default value.
 
 
 
 
 
 
 
 
31
  """)
32
  st.warning("Blue cells in the table highlight coefficients modified from their **default median values**.")
33
 
34
+ # === Method selector ===
35
  method_choices = []
36
  method_lookup = {}
37
  for group, methods in method_groups.items():
 
43
  selected_label = st.selectbox("Select a method:", method_choices)
44
  method_selected = method_lookup[selected_label]
45
 
46
+ # === Resource sliders ===
47
  with st.expander(f"Manage resources for the {method_selected} method", expanded=True):
48
  for group_name, rlist in resource_groups.items():
49
  used = [r for r in rlist if r in cost_sliders[method_selected]]
 
56
  default_val = conf["median"]
57
  min_val = conf["min"]
58
  max_val = conf["max"]
59
+
60
  if min_val < 0.01:
61
  step = 0.0001
62
  fmt = "%.4f"
 
70
  step = 1.0
71
  fmt = "%.0f"
72
 
 
 
 
 
73
  is_secondary = (method_selected, r) in secondary_resources
74
+ toggle_key = f"toggle_{method_selected}_{r}".replace(" ", "_").lower()
 
75
 
 
76
  if is_secondary:
77
+ if toggle_key not in st.session_state:
78
+ st.session_state[toggle_key] = st.session_state.costs[method_selected][r] != 0.0
79
  enabled = st.checkbox(f"Use {r}", key=toggle_key)
80
 
81
+ if not enabled:
82
+ st.session_state.costs[method_selected][r] = 0.0
83
+ st.markdown("**DISABLED**")
84
+ continue
85
+ elif st.session_state.costs[method_selected][r] == 0.0:
86
+ st.session_state.costs[method_selected][r] = default_val
87
+
88
+ # Sliders and inputs
89
+ slider_key = f"slider_{method_selected}_{r}".replace(" ", "_").lower()
90
+ manual_key = f"manual_{method_selected}_{r}".replace(" ", "_").lower()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
 
92
+ col1, col2 = st.columns([3, 1])
93
+ with col1:
94
+ slider_val = st.slider(
95
+ label=r,
96
+ min_value=min_val,
97
+ max_value=max_val,
98
+ value=st.session_state.costs[method_selected][r],
99
+ step=step,
100
+ format=fmt,
101
+ key=slider_key
102
+ )
103
+ with col2:
104
+ manual_val = st.number_input(
105
+ label="",
106
+ value=slider_val,
107
+ key=manual_key,
108
+ help="Manual override (optional, any value)"
109
+ )
110
+
111
+ final_val = manual_val if manual_val != slider_val else slider_val
112
+ st.session_state.costs[method_selected][r] = final_val
113
+
114
+ # === Reset buttons ===
115
  col1, col2 = st.columns(2)
116
  with col1:
117
  if st.button("🔄 Reset selected method"):
118
  for r in cost_sliders[method_selected]:
119
+ is_secondary = (method_selected, r) in secondary_resources
120
+ st.session_state.costs[method_selected][r] = 0.0 if is_secondary else cost_sliders[method_selected][r]["median"]
 
 
121
  for suffix in ["slider", "manual", "toggle"]:
122
  key = f"{suffix}_{method_selected}_{r}".replace(" ", "_").lower()
123
  if key in st.session_state:
124
  del st.session_state[key]
 
125
  st.success(f"{method_selected} reset to default values.")
126
  st.rerun()
127
 
 
129
  if st.button("🔄 Reset all methods"):
130
  for m in method_order:
131
  for r in cost_sliders.get(m, {}):
132
+ is_secondary = (m, r) in secondary_resources
133
+ st.session_state.costs[m][r] = 0.0 if is_secondary else cost_sliders[m][r]["median"]
 
134
  for suffix in ["slider", "manual", "toggle"]:
135
  key = f"{suffix}_{m}_{r}".replace(" ", "_").lower()
136
  if key in st.session_state:
137
  del st.session_state[key]
 
138
  st.success("All methods reset to default values.")
139
  st.rerun()
140
 
141
+ # === Summary table ===
142
  st.subheader("Summary table: resource usage coefficients in the model")
143
 
144
  df_default = pd.DataFrame(index=method_order, columns=resource_order)
tabs/tab4_optimization copy.py ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import json
3
+ import zipfile
4
+
5
+ import matplotlib.pyplot as plt
6
+ import pandas as pd
7
+ import plotly.graph_objects as go
8
+ import seaborn as sns
9
+ import streamlit as st
10
+
11
+ from config.config import method_order, resource_order, resource_units
12
+ from optimization.optimization import run_optimization
13
+ from utils.ui_helpers import format_results_summary
14
+
15
+
16
+ def render_tab():
17
+ st.header("Portfolio generation")
18
+ st.caption("Tab 4 - Seaborn fallback for vertical histograms")
19
+
20
+ st.info("""
21
+ **Objective:**
22
+ Run the optimization tool, explore results, and save scenarios for comparison or export.
23
+ """)
24
+
25
+ if "constraints" not in st.session_state or "costs" not in st.session_state or "resource_caps" not in st.session_state:
26
+ st.error("Missing inputs. Please complete previous steps.")
27
+ return
28
+
29
+ if "scenarios" not in st.session_state:
30
+ st.session_state.scenarios = {}
31
+
32
+ if sum(st.session_state.resource_caps.values()) <= 0:
33
+ st.warning("\u26a0\ufe0f All resources are currently set to 0. Please increase availability.")
34
+ return
35
+
36
+ if st.button("Run Optimization"):
37
+ success, result = run_optimization(
38
+ resource_caps=st.session_state.resource_caps,
39
+ method_constraints=st.session_state.constraints,
40
+ method_costs=st.session_state.costs,
41
+ )
42
+
43
+ if success:
44
+ allocations = result["method_usage"]
45
+ total_removed = result["total_removed"]
46
+
47
+ if total_removed <= 0:
48
+ st.warning("\u274c Optimization removed 0 tCO₂. Relax constraints or increase resources.")
49
+ return
50
+
51
+ total_cost = sum(
52
+ allocations[m] * sum(st.session_state.costs[m].values())
53
+ for m in allocations
54
+ )
55
+
56
+ st.session_state.latest_result = {
57
+ "summary": {
58
+ "total_co2_removed": total_removed,
59
+ "total_cost": total_cost
60
+ },
61
+ "allocations": allocations,
62
+ "resource_caps": st.session_state.resource_caps.copy(),
63
+ "method_constraints": st.session_state.constraints,
64
+ "method_costs": st.session_state.costs,
65
+ }
66
+
67
+ st.success(f"\u2705 Optimization successful: {total_removed:,.0f} tCO₂ removed")
68
+ else:
69
+ st.error("\u274c Optimization failed")
70
+ if "message" in result:
71
+ st.error(result["message"])
72
+
73
+ if "latest_result" not in st.session_state:
74
+ return
75
+
76
+ latest = st.session_state.latest_result
77
+ allocations = latest["allocations"]
78
+ resource_caps = latest["resource_caps"]
79
+ method_costs = latest["method_costs"]
80
+
81
+ if not allocations or sum(allocations.values()) == 0:
82
+ return
83
+
84
+ col1, col2 = st.columns(2)
85
+ col1.metric("Total CO₂ Removed", f"{latest['summary']['total_co2_removed']:,.0f} t")
86
+ col2.metric("Methods Used", len(allocations))
87
+
88
+ st.subheader("Optimization Summary")
89
+ df_result = pd.DataFrame.from_dict(allocations, orient="index", columns=["tCO₂ removed"])
90
+ df_result["tCO₂ removed"] = pd.to_numeric(df_result["tCO₂ removed"], errors="coerce")
91
+ total = df_result["tCO₂ removed"].sum()
92
+ if total > 0:
93
+ df_result["Share (%)"] = (df_result["tCO₂ removed"] / total * 100).round(2)
94
+ df_result = df_result.sort_values(by="tCO₂ removed", ascending=False)
95
+ st.table(format_results_summary(df_result))
96
+
97
+ st.markdown("### Method allocation breakdown")
98
+ fig_donut = go.Figure(data=[go.Pie(
99
+ labels=df_result.index.tolist(),
100
+ values=df_result["tCO₂ removed"].tolist(),
101
+ hole=0.4,
102
+ textinfo="label+percent",
103
+ textposition="outside",
104
+ marker=dict(line=dict(color="#000000", width=1)),
105
+ showlegend=False
106
+ )])
107
+ fig_donut.update_layout(margin=dict(t=10, b=10))
108
+ st.plotly_chart(fig_donut, use_container_width=True)
109
+
110
+ st.markdown("### Resource usage heatmap (% of cap)")
111
+ heatmap_matrix, heatmap_y = [], []
112
+ available_resources = [r for r in resource_order if resource_caps.get(r, 0) > 0]
113
+ for method in df_result.index:
114
+ row = [((method_costs.get(method, {}).get(r, 0.0) * allocations.get(method, 0.0)) / resource_caps[r]) * 100 if resource_caps[r] > 0 else 0 for r in available_resources]
115
+ heatmap_matrix.append(row)
116
+ heatmap_y.append(method)
117
+ fig_heatmap = go.Figure(data=go.Heatmap(
118
+ z=heatmap_matrix,
119
+ x=available_resources,
120
+ y=heatmap_y,
121
+ colorscale="Blues",
122
+ hovertemplate="Method: %{y}<br>Resource: %{x}<br>% Used: %{z:.2f}%<extra></extra>",
123
+ ))
124
+ fig_heatmap.update_layout(title="Resource Usage vs. Cap", xaxis_title="Resource", yaxis_title="Method", height=400)
125
+ st.plotly_chart(fig_heatmap, use_container_width=True)
126
+
127
+ st.markdown("### Absolute resource usage by unit (Seaborn)")
128
+ df_abs = []
129
+ for m, co2_removed in allocations.items():
130
+ for unit, resources in resource_units.items():
131
+ for r in resources:
132
+ if r in method_costs.get(m, {}):
133
+ used = method_costs[m][r] * co2_removed
134
+ if used > 0:
135
+ df_abs.append({"Method": m, "Resource": r, "Used": used, "Unit": unit})
136
+
137
+ if df_abs:
138
+ df_abs = pd.DataFrame(df_abs)
139
+ for unit in df_abs["Unit"].unique():
140
+ st.markdown(f"#### Resources in {unit}")
141
+ df_unit = df_abs[df_abs["Unit"] == unit]
142
+ fig, ax = plt.subplots(figsize=(8, 4 + 0.3 * df_unit["Resource"].nunique()))
143
+ sns.barplot(data=df_unit, x="Resource", y="Used", hue="Method", ax=ax)
144
+ ax.set_ylabel(f"Used ({unit})")
145
+ ax.set_xlabel("Resource")
146
+ ax.tick_params(axis='x', rotation=45)
147
+ ax.legend(title="Method", bbox_to_anchor=(1.05, 1), loc='upper left')
148
+ st.pyplot(fig)
149
+
150
+ st.markdown("### Save scenario")
151
+ st.text_input("Scenario name", key="scenario_name")
152
+
153
+ if st.button("Save Scenario"):
154
+ name = st.session_state.get("scenario_name", "").strip()
155
+ if not name:
156
+ st.warning("Please enter a name before saving.")
157
+ elif name in st.session_state.scenarios:
158
+ st.warning("Scenario name already exists.")
159
+ else:
160
+ st.session_state.scenarios[name] = latest
161
+ st.success(f"Scenario '{name}' saved.")
162
+
163
+ current_name = st.session_state.get("scenario_name", "").strip()
164
+ if current_name and current_name in st.session_state.scenarios:
165
+ scenario = st.session_state.scenarios[current_name]
166
+ st.markdown(f"#### Scenario: {current_name}")
167
+ df_saved = pd.DataFrame.from_dict(scenario["allocations"], orient="index", columns=["tCO₂ removed"])
168
+ df_saved["tCO₂ removed"] = pd.to_numeric(df_saved["tCO₂ removed"], errors="coerce")
169
+ total = df_saved["tCO₂ removed"].sum()
170
+ if total > 0:
171
+ df_saved["Share (%)"] = (df_saved["tCO₂ removed"] / total * 100).round(2)
172
+ df_saved = df_saved.sort_values(by="tCO₂ removed", ascending=False)
173
+ st.table(format_results_summary(df_saved))
174
+
175
+ # Export as JSON
176
+ json_data = json.dumps({current_name: scenario}, indent=2)
177
+ st.download_button("Download Scenario as JSON", data=json_data, file_name=f"{current_name}_scenario.json", mime="application/json")
178
+
179
+ # Export as CSV zip
180
+ df_alloc = pd.DataFrame.from_dict(scenario["allocations"], orient="index", columns=["tCO₂ removed"])
181
+ df_alloc.index.name = "Method"
182
+ df_alloc.reset_index(inplace=True)
183
+ df_caps = pd.DataFrame(list(scenario["resource_caps"].items()), columns=["Resource", "Cap"])
184
+ constraints_list = []
185
+ for method, values in scenario["method_constraints"].items():
186
+ entry = {"Method": method, "min_share": values.get("min_share", ""), "max_share": values.get("max_share", ""), "resources": values.get("resources", {})}
187
+ constraints_list.append(entry)
188
+ df_constraints = pd.json_normalize(constraints_list, sep=".").fillna("")
189
+ zip_buffer = io.BytesIO()
190
+ with zipfile.ZipFile(zip_buffer, "a", zipfile.ZIP_DEFLATED) as zf:
191
+ zf.writestr("allocations.csv", df_alloc.to_csv(index=False))
192
+ zf.writestr("resource_caps.csv", df_caps.to_csv(index=False))
193
+ zf.writestr("method_constraints.csv", df_constraints.to_csv(index=False))
194
+ st.download_button(
195
+ label="\ud83d\udcc5 Download Scenario as CSV (ZIP)",
196
+ data=zip_buffer.getvalue(),
197
+ file_name=f"{current_name}_scenario.zip",
198
+ mime="application/zip"
199
+ )
tabs/tab4_optimization.py CHANGED
@@ -1,8 +1,11 @@
 
1
  import json
 
 
2
 
 
3
  import pandas as pd
4
- import plotly.express as px
5
- import plotly.graph_objects as go
6
  import streamlit as st
7
 
8
  from config.config import method_order, resource_order, resource_units
@@ -12,12 +15,14 @@ from utils.ui_helpers import format_results_summary
12
 
13
  def render_tab():
14
  st.header("Portfolio generation")
 
 
15
  st.info("""
16
  **Objective:**
17
  Run the optimization tool, explore results, and save scenarios for comparison or export.
18
  """)
19
 
20
- # Input check - ensure all required inputs are available
21
  if "constraints" not in st.session_state or "costs" not in st.session_state or "resource_caps" not in st.session_state:
22
  st.error("Missing inputs. Please complete previous steps.")
23
  return
@@ -26,10 +31,10 @@ def render_tab():
26
  st.session_state.scenarios = {}
27
 
28
  if sum(st.session_state.resource_caps.values()) <= 0:
29
- st.warning("⚠️ All resources are currently set to 0. Please increase availability.")
30
  return
31
 
32
- # Optimization
33
  if st.button("Run Optimization"):
34
  success, result = run_optimization(
35
  resource_caps=st.session_state.resource_caps,
@@ -42,7 +47,7 @@ def render_tab():
42
  total_removed = result["total_removed"]
43
 
44
  if total_removed <= 0:
45
- st.warning("❌ Optimization removed 0 tCO₂. Relax constraints or increase resources.")
46
  return
47
 
48
  total_cost = sum(
@@ -61,13 +66,12 @@ def render_tab():
61
  "method_costs": st.session_state.costs,
62
  }
63
 
64
- st.success(f"✅ Optimization successful: {total_removed:,.0f} tCO₂ removed")
65
  else:
66
- st.error("❌ Optimization failed")
67
  if "message" in result:
68
  st.error(result["message"])
69
 
70
- # --- Display Section ---
71
  if "latest_result" not in st.session_state:
72
  return
73
 
@@ -79,12 +83,12 @@ def render_tab():
79
  if not allocations or sum(allocations.values()) == 0:
80
  return
81
 
82
- # --- Metrics ---
83
  col1, col2 = st.columns(2)
84
  col1.metric("Total CO₂ Removed", f"{latest['summary']['total_co2_removed']:,.0f} t")
85
  col2.metric("Methods Used", len(allocations))
86
 
87
- # --- Summary Table ---
88
  st.subheader("Optimization Summary")
89
  df_result = pd.DataFrame.from_dict(allocations, orient="index", columns=["tCO₂ removed"])
90
  df_result["tCO₂ removed"] = pd.to_numeric(df_result["tCO₂ removed"], errors="coerce")
@@ -94,25 +98,25 @@ def render_tab():
94
  df_result = df_result.sort_values(by="tCO₂ removed", ascending=False)
95
  st.table(format_results_summary(df_result))
96
 
97
- # --- Donut Chart ---
98
  st.markdown("### Method allocation breakdown")
99
- fig_donut = px.pie(
100
- df_result.reset_index(),
101
- names="index",
102
- values="tCO₂ removed",
 
 
 
103
  hole=0.4,
104
- labels={"index": "Method", "tCO₂ removed": "tCO₂ Removed"},
105
- )
106
- fig_donut.update_traces(
107
  textinfo="label+percent",
108
  textposition="outside",
109
- pull=[0.02] * len(df_result),
110
  marker=dict(line=dict(color="#000000", width=1)),
111
- showlegend=False,
112
- )
 
113
  st.plotly_chart(fig_donut, use_container_width=True)
114
 
115
- # --- Heatmap of Resource Use ---
116
  st.markdown("### Resource usage heatmap (% of cap)")
117
  heatmap_matrix, heatmap_y = [], []
118
  available_resources = [r for r in resource_order if resource_caps.get(r, 0) > 0]
@@ -140,8 +144,8 @@ def render_tab():
140
  )
141
  st.plotly_chart(fig_heatmap, use_container_width=True)
142
 
143
- # --- Absolute Resource Use ---
144
- st.markdown("### Absolute resource usage by unit")
145
  df_abs = []
146
  for m, co2_removed in allocations.items():
147
  for unit, resources in resource_units.items():
@@ -159,24 +163,18 @@ def render_tab():
159
  if df_abs:
160
  df_abs = pd.DataFrame(df_abs)
161
  for unit in df_abs["Unit"].unique():
162
- df_unit = df_abs[df_abs["Unit"] == unit]
163
  st.markdown(f"#### Resources in {unit}")
164
- fig = px.bar(
165
- df_unit,
166
- x="Resource",
167
- y="Used",
168
- color="Method",
169
- barmode="group",
170
- labels={"Used": f"Used ({unit})"},
171
- )
172
- fig.update_layout(
173
- height=300 + 30 * df_unit["Resource"].nunique(),
174
- margin=dict(t=30, b=30),
175
- legend_title_text="",
176
- )
177
- st.plotly_chart(fig, use_container_width=True)
178
 
179
- # --- Save Scenario ---
180
  st.markdown("### Save scenario")
181
  st.text_input("Scenario name", key="scenario_name")
182
 
@@ -190,7 +188,7 @@ def render_tab():
190
  st.session_state.scenarios[name] = latest
191
  st.success(f"Scenario '{name}' saved.")
192
 
193
- # --- Preview Saved Scenario ---
194
  current_name = st.session_state.get("scenario_name", "").strip()
195
  if current_name and current_name in st.session_state.scenarios:
196
  scenario = st.session_state.scenarios[current_name]
@@ -204,3 +202,30 @@ def render_tab():
204
  df_saved["Share (%)"] = (df_saved["tCO₂ removed"] / total * 100).round(2)
205
  df_saved = df_saved.sort_values(by="tCO₂ removed", ascending=False)
206
  st.table(format_results_summary(df_saved))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
  import json
3
+ import unicodedata
4
+ import zipfile
5
 
6
+ import matplotlib.pyplot as plt
7
  import pandas as pd
8
+ import seaborn as sns
 
9
  import streamlit as st
10
 
11
  from config.config import method_order, resource_order, resource_units
 
15
 
16
  def render_tab():
17
  st.header("Portfolio generation")
18
+ st.caption("Tab 4 - Seaborn fallback for vertical histograms")
19
+
20
  st.info("""
21
  **Objective:**
22
  Run the optimization tool, explore results, and save scenarios for comparison or export.
23
  """)
24
 
25
+ # Validate necessary inputs
26
  if "constraints" not in st.session_state or "costs" not in st.session_state or "resource_caps" not in st.session_state:
27
  st.error("Missing inputs. Please complete previous steps.")
28
  return
 
31
  st.session_state.scenarios = {}
32
 
33
  if sum(st.session_state.resource_caps.values()) <= 0:
34
+ st.warning("\u26a0\ufe0f All resources are currently set to 0. Please increase availability.")
35
  return
36
 
37
+ # Run optimization
38
  if st.button("Run Optimization"):
39
  success, result = run_optimization(
40
  resource_caps=st.session_state.resource_caps,
 
47
  total_removed = result["total_removed"]
48
 
49
  if total_removed <= 0:
50
+ st.warning("\u274c Optimization removed 0 tCO₂. Relax constraints or increase resources.")
51
  return
52
 
53
  total_cost = sum(
 
66
  "method_costs": st.session_state.costs,
67
  }
68
 
69
+ st.success(f"\u2705 Optimization successful: {total_removed:,.0f} tCO₂ removed")
70
  else:
71
+ st.error("\u274c Optimization failed")
72
  if "message" in result:
73
  st.error(result["message"])
74
 
 
75
  if "latest_result" not in st.session_state:
76
  return
77
 
 
83
  if not allocations or sum(allocations.values()) == 0:
84
  return
85
 
86
+ # Metrics
87
  col1, col2 = st.columns(2)
88
  col1.metric("Total CO₂ Removed", f"{latest['summary']['total_co2_removed']:,.0f} t")
89
  col2.metric("Methods Used", len(allocations))
90
 
91
+ # Summary table
92
  st.subheader("Optimization Summary")
93
  df_result = pd.DataFrame.from_dict(allocations, orient="index", columns=["tCO₂ removed"])
94
  df_result["tCO₂ removed"] = pd.to_numeric(df_result["tCO₂ removed"], errors="coerce")
 
98
  df_result = df_result.sort_values(by="tCO₂ removed", ascending=False)
99
  st.table(format_results_summary(df_result))
100
 
101
+ # Donut chart
102
  st.markdown("### Method allocation breakdown")
103
+ labels = df_result.index.tolist()
104
+ values = df_result["tCO₂ removed"].tolist()
105
+
106
+ import plotly.graph_objects as go
107
+ fig_donut = go.Figure(data=[go.Pie(
108
+ labels=labels,
109
+ values=values,
110
  hole=0.4,
 
 
 
111
  textinfo="label+percent",
112
  textposition="outside",
 
113
  marker=dict(line=dict(color="#000000", width=1)),
114
+ showlegend=False
115
+ )])
116
+ fig_donut.update_layout(margin=dict(t=10, b=10))
117
  st.plotly_chart(fig_donut, use_container_width=True)
118
 
119
+ # Heatmap
120
  st.markdown("### Resource usage heatmap (% of cap)")
121
  heatmap_matrix, heatmap_y = [], []
122
  available_resources = [r for r in resource_order if resource_caps.get(r, 0) > 0]
 
144
  )
145
  st.plotly_chart(fig_heatmap, use_container_width=True)
146
 
147
+ # Absolute usage (fallback with seaborn)
148
+ st.markdown("### Absolute resource usage by unit (Seaborn)")
149
  df_abs = []
150
  for m, co2_removed in allocations.items():
151
  for unit, resources in resource_units.items():
 
163
  if df_abs:
164
  df_abs = pd.DataFrame(df_abs)
165
  for unit in df_abs["Unit"].unique():
 
166
  st.markdown(f"#### Resources in {unit}")
167
+ df_unit = df_abs[df_abs["Unit"] == unit]
168
+
169
+ fig, ax = plt.subplots(figsize=(8, 4 + 0.3 * df_unit["Resource"].nunique()))
170
+ sns.barplot(data=df_unit, x="Resource", y="Used", hue="Method", ax=ax)
171
+ ax.set_ylabel(f"Used ({unit})")
172
+ ax.set_xlabel("Resource")
173
+ ax.tick_params(axis='x', rotation=45)
174
+ ax.legend(title="Method", bbox_to_anchor=(1.05, 1), loc='upper left')
175
+ st.pyplot(fig)
 
 
 
 
 
176
 
177
+ # Save scenario
178
  st.markdown("### Save scenario")
179
  st.text_input("Scenario name", key="scenario_name")
180
 
 
188
  st.session_state.scenarios[name] = latest
189
  st.success(f"Scenario '{name}' saved.")
190
 
191
+ # Preview saved scenario
192
  current_name = st.session_state.get("scenario_name", "").strip()
193
  if current_name and current_name in st.session_state.scenarios:
194
  scenario = st.session_state.scenarios[current_name]
 
202
  df_saved["Share (%)"] = (df_saved["tCO₂ removed"] / total * 100).round(2)
203
  df_saved = df_saved.sort_values(by="tCO₂ removed", ascending=False)
204
  st.table(format_results_summary(df_saved))
205
+
206
+ # Export buttons
207
+ safe_name = unicodedata.normalize("NFKD", current_name).encode("ascii", "ignore").decode("ascii")
208
+
209
+ st.download_button(
210
+ label="Export JSON",
211
+ data=json.dumps(scenario, indent=2),
212
+ file_name=f"{safe_name}_scenario.json",
213
+ mime="application/json"
214
+ )
215
+
216
+ inputs_df = pd.DataFrame.from_dict(scenario["resource_caps"], orient="index", columns=["Available"])
217
+ constraints_df = pd.DataFrame.from_dict({m: v.get("resources", {}) for m, v in scenario["method_constraints"].items()}, orient="index").fillna(0)
218
+ allocations_df = pd.DataFrame.from_dict(scenario["allocations"], orient="index", columns=["tCO₂ removed"])
219
+
220
+ csv_buffer = io.BytesIO()
221
+ with zipfile.ZipFile(csv_buffer, "w") as zipf:
222
+ zipf.writestr("resource_caps.csv", inputs_df.to_csv())
223
+ zipf.writestr("method_constraints.csv", constraints_df.to_csv())
224
+ zipf.writestr("allocations.csv", allocations_df.to_csv())
225
+
226
+ st.download_button(
227
+ label="Export CSV",
228
+ data=csv_buffer.getvalue(),
229
+ file_name=f"{safe_name}_scenario.zip",
230
+ mime="application/zip"
231
+ )
tabs/tab5_sensitivity.py CHANGED
@@ -1,5 +1,6 @@
 
1
  import pandas as pd
2
- import plotly.express as px
3
  import streamlit as st
4
 
5
  from config.config import method_order, resource_order
@@ -7,7 +8,7 @@ from optimization.optimization import run_optimization
7
 
8
 
9
  def render_tab():
10
- st.header("Sensitivity Analysis")
11
  st.info("""
12
  **Objective**:
13
 
@@ -26,7 +27,7 @@ def render_tab():
26
  )
27
  baseline_removed = baseline_result["total_removed"] if baseline_success else 0
28
 
29
- tab1, tab2 = st.tabs(["🔁 Resource Sensitivity", "🔁 Method Sensitivity"])
30
 
31
  with tab1:
32
  st.subheader("Impact of resource availability")
@@ -58,14 +59,12 @@ def render_tab():
58
  })
59
 
60
  df = pd.DataFrame(all_data)
61
- fig = px.line(df, x="% of cap", y="CO₂ removed (t)", color="Input",
62
- title="Sensitivity to Resource Availability")
63
-
64
- fig.add_hline(y=baseline_removed, line_dash="dash", line_color="gray",
65
- annotation_text=f"Baseline: {baseline_removed:,} tCO₂",
66
- annotation_position="top left")
67
-
68
- st.plotly_chart(fig, use_container_width=True)
69
 
70
  with tab2:
71
  st.subheader("Impact of max allocation per method")
@@ -97,11 +96,9 @@ def render_tab():
97
  })
98
 
99
  df = pd.DataFrame(all_data)
100
- fig = px.line(df, x="% max allocation", y="CO₂ removed (t)", color="Input",
101
- title="Sensitivity to Method Max Allocation")
102
-
103
- fig.add_hline(y=baseline_removed, line_dash="dash", line_color="gray",
104
- annotation_text=f"Baseline: {baseline_removed:,} tCO₂",
105
- annotation_position="top left")
106
-
107
- st.plotly_chart(fig, use_container_width=True)
 
1
+ import matplotlib.pyplot as plt
2
  import pandas as pd
3
+ import seaborn as sns
4
  import streamlit as st
5
 
6
  from config.config import method_order, resource_order
 
8
 
9
 
10
  def render_tab():
11
+ st.header("Sensitivity analysis")
12
  st.info("""
13
  **Objective**:
14
 
 
27
  )
28
  baseline_removed = baseline_result["total_removed"] if baseline_success else 0
29
 
30
+ tab1, tab2 = st.tabs(["🔁 Resource sensitivity", "🔁 Method sensitivity"])
31
 
32
  with tab1:
33
  st.subheader("Impact of resource availability")
 
59
  })
60
 
61
  df = pd.DataFrame(all_data)
62
+ st.markdown("#### Sensitivity to resource availability")
63
+ fig, ax = plt.subplots(figsize=(8, 5))
64
+ sns.lineplot(data=df, x="% of cap", y="CO₂ removed (t)", hue="Input", ax=ax)
65
+ ax.axhline(y=baseline_removed, linestyle="--", color="gray", label=f"Baseline: {baseline_removed:,} tCO₂")
66
+ ax.legend()
67
+ st.pyplot(fig)
 
 
68
 
69
  with tab2:
70
  st.subheader("Impact of max allocation per method")
 
96
  })
97
 
98
  df = pd.DataFrame(all_data)
99
+ st.markdown("#### Sensitivity to method max allocation in the portfolio")
100
+ fig, ax = plt.subplots(figsize=(8, 5))
101
+ sns.lineplot(data=df, x="% max allocation", y="CO₂ removed (t)", hue="Input", ax=ax)
102
+ ax.axhline(y=baseline_removed, linestyle="--", color="gray", label=f"Baseline: {baseline_removed:,} tCO₂")
103
+ ax.legend()
104
+ st.pyplot(fig)
 
 
tabs/tab6_scenarios.py CHANGED
@@ -1,7 +1,8 @@
1
  import json
2
 
 
3
  import pandas as pd
4
- import plotly.express as px
5
  import streamlit as st
6
 
7
 
@@ -18,6 +19,19 @@ def is_valid_scenario(scenario):
18
  def render_tab():
19
  st.header("Scenario Comparison")
20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  if "scenarios" not in st.session_state:
22
  st.session_state.scenarios = {}
23
 
@@ -93,15 +107,33 @@ def render_tab():
93
  # Charts
94
  if method_alloc_df:
95
  st.subheader("Method Allocations")
96
- fig1 = px.bar(pd.DataFrame(method_alloc_df), x="Method", y="tCO₂", color="Scenario", barmode="group")
97
- st.plotly_chart(fig1, use_container_width=True)
 
 
 
 
 
98
 
99
  if resource_usage_df:
100
  st.subheader("Resource Use per Method")
101
- fig2 = px.bar(pd.DataFrame(resource_usage_df), x="Resource", y="Used", color="Scenario", facet_col="Method", facet_col_wrap=3)
102
- st.plotly_chart(fig2, use_container_width=True)
 
 
 
 
 
 
 
 
 
103
 
104
  st.subheader("Total Resource Use per Scenario")
105
- df_total = pd.DataFrame(resource_usage_df).groupby(["Scenario", "Resource"]).sum(numeric_only=True).reset_index()
106
- fig3 = px.bar(df_total, x="Scenario", y="Used", color="Resource", barmode="stack")
107
- st.plotly_chart(fig3, use_container_width=True)
 
 
 
 
 
1
  import json
2
 
3
+ import matplotlib.pyplot as plt
4
  import pandas as pd
5
+ import seaborn as sns
6
  import streamlit as st
7
 
8
 
 
19
  def render_tab():
20
  st.header("Scenario Comparison")
21
 
22
+ # Load scenario from JSON
23
+ uploaded_file = st.file_uploader("Load scenario(s) from JSON", type="json")
24
+ if uploaded_file is not None:
25
+ try:
26
+ data = json.load(uploaded_file)
27
+ if isinstance(data, dict) and all(is_valid_scenario(s) for s in data.values()):
28
+ st.session_state.scenarios.update(data)
29
+ st.success("Scenarios loaded successfully.")
30
+ else:
31
+ st.error("Invalid file format.")
32
+ except Exception as e:
33
+ st.error(f"Error loading file: {e}")
34
+
35
  if "scenarios" not in st.session_state:
36
  st.session_state.scenarios = {}
37
 
 
107
  # Charts
108
  if method_alloc_df:
109
  st.subheader("Method Allocations")
110
+ df1 = pd.DataFrame(method_alloc_df)
111
+ fig1, ax1 = plt.subplots(figsize=(10, 4))
112
+ sns.barplot(data=df1, x="Method", y="tCO₂", hue="Scenario", ax=ax1)
113
+ ax1.set_xlabel("Method")
114
+ ax1.set_ylabel("tCO₂")
115
+ plt.xticks(rotation=45, ha="right")
116
+ st.pyplot(fig1)
117
 
118
  if resource_usage_df:
119
  st.subheader("Resource Use per Method")
120
+ df2 = pd.DataFrame(resource_usage_df)
121
+ methods = df2["Method"].unique()
122
+ for method in methods:
123
+ df_method = df2[df2["Method"] == method]
124
+ fig2, ax2 = plt.subplots(figsize=(8, 3))
125
+ sns.barplot(data=df_method, x="Resource", y="Used", hue="Scenario", ax=ax2)
126
+ ax2.set_title(method)
127
+ ax2.set_xlabel("Resource")
128
+ ax2.set_ylabel("Used")
129
+ plt.xticks(rotation=45, ha="right")
130
+ st.pyplot(fig2)
131
 
132
  st.subheader("Total Resource Use per Scenario")
133
+ df_total = df2.groupby(["Scenario", "Resource"]).sum(numeric_only=True).reset_index()
134
+ fig3, ax3 = plt.subplots(figsize=(10, 4))
135
+ sns.barplot(data=df_total, x="Scenario", y="Used", hue="Resource", ax=ax3)
136
+ ax3.set_xlabel("Scenario")
137
+ ax3.set_ylabel("Total Used")
138
+ plt.xticks(rotation=45, ha="right")
139
+ st.pyplot(fig3)