Spaces:
Sleeping
Sleeping
| import hashlib | |
| import os | |
| import sys | |
| import subprocess | |
| import pathlib | |
| import streamlit as st | |
| from streamlit_option_menu import option_menu | |
| from tabs import (tab0_welcome, tab1_inputs, tab2_constraints, | |
| tab3_coefficients, tab4_optimisation, tab5_sensitivity, | |
| tab6_scenarios) | |
| from utils.data_utils import load_slider_data, strip_unit_suffix | |
| from config.config import method_order, secondary_resources, hidden_resource_names | |
| # ── Auto-update: regenerate JSON files if inputs_aggregated.csv has changed ─── | |
| # Uses a hash file to detect changes — works both locally and on Hugging Face. | |
| # Must run before importing config/tabs, which read the generated JSON files. | |
| csv = "data/inputs_aggregated.csv" | |
| hash_file = "data/.csv_hash" | |
| generated = [ | |
| "data/cost_sliders.json", | |
| "data/method_groups.json", | |
| "data/secondary_resources.json", | |
| ] | |
| def md5(path): | |
| hasher = hashlib.md5() | |
| with open(path, "rb") as f: | |
| hasher.update(f.read()) | |
| return hasher.hexdigest() | |
| if os.path.exists(csv): | |
| current_hash = md5(csv) | |
| stored_hash = open(hash_file).read().strip() if os.path.exists(hash_file) else "" | |
| missing = any(not os.path.exists(f) for f in generated) | |
| if current_hash != stored_hash or missing: | |
| print("inputs_aggregated.csv has changed — running data/update.py ...") | |
| subprocess.run([sys.executable, "data/update.py"], check=True) | |
| with open(hash_file, "w") as file: | |
| file.write(current_hash) | |
| print("Data files updated successfully.") | |
| # Force all app modules to reload on next rerun so they read fresh JSONs. | |
| # config.config is imported at module level and won't re-execute unless we | |
| # evict it from sys.modules before st.rerun() re-runs the script. | |
| for _key in list(sys.modules.keys()): | |
| if any(_key.startswith(p) for p in ("config", "tabs", "utils")): | |
| del sys.modules[_key] | |
| st.cache_data.clear() | |
| st.rerun() | |
| st.set_page_config("Carbon Portfolio Optimizer", layout="wide") | |
| def load_css(file_path): | |
| with open(file_path) as f: | |
| st.markdown(f"<style>{f.read()}</style>", unsafe_allow_html=True) | |
| css_path = pathlib.Path("assets/styles.css") | |
| load_css(css_path) | |
| st.markdown('<h1 class="custom-title">CRRA <span class="crra-hl">Toolkit</span>: CDR Portfolio Optimization</h1>', unsafe_allow_html=True) | |
| with st.sidebar: | |
| selected = option_menu( | |
| "Navigation", | |
| options=[ | |
| "Welcome", | |
| "Resource inputs", | |
| "Method constraints", | |
| "Resource coefficients", | |
| "Portfolio generation", | |
| "Sensitivity analysis", | |
| "Scenario comparison", | |
| ], | |
| icons=["house", "upload", "gear", "sliders", "graph-up-arrow", "card-checklist", "search"], | |
| default_index=0, | |
| orientation="vertical", | |
| key="crra-menu", | |
| styles={ | |
| "container":{ | |
| "border-radius":"0px", | |
| }, | |
| "menu-title":{ | |
| "font-family": "Segoe UI, sans-serif", | |
| "font-weight":"bold", | |
| "color": "#ff5f4d", | |
| "text-transform": "uppercase", | |
| "font-style": "italic", | |
| }, | |
| "nav-link": { | |
| "font-family": "Segoe UI, sans-serif", | |
| "margin": "4px 0", | |
| "--hover-color": "#F5F5F5", | |
| "text-transform": "uppercase", | |
| "font-weight":"600", | |
| "font-style": "italic", | |
| }, | |
| "nav-link-selected": { | |
| "background-color": "#eae9d8", | |
| "color": "#298231", | |
| "font-weight": "900", | |
| }, | |
| } | |
| ) | |
| cost_sliders = load_slider_data() | |
| if "constraints" not in st.session_state: | |
| st.session_state.constraints = {} | |
| if "auto_disabled_methods" not in st.session_state: | |
| st.session_state.auto_disabled_methods = set() | |
| for m in method_order: | |
| if m not in st.session_state.constraints: | |
| primary = [ | |
| strip_unit_suffix(r) for r in cost_sliders.get(m, {}) | |
| if (m, strip_unit_suffix(r)) not in secondary_resources | |
| ] | |
| all_hidden = bool(primary) and all(r in hidden_resource_names for r in primary) | |
| st.session_state.constraints[m] = { | |
| "active": not all_hidden, | |
| "cap_type": "percent", | |
| "cap_value": 100, | |
| } | |
| if all_hidden: | |
| st.session_state.auto_disabled_methods.add(m) | |
| _expected_methods = set(cost_sliders.keys()) | |
| _current_methods = set(st.session_state.get("costs", {}).keys()) | |
| if "costs" not in st.session_state or st.session_state.costs == {} or _expected_methods != _current_methods: | |
| st.session_state.costs = { | |
| m: { | |
| strip_unit_suffix(r): (0.0 if (m, strip_unit_suffix(r)) in secondary_resources else cost_sliders[m][r]["median"]) | |
| for r in cost_sliders[m] | |
| } | |
| for m in cost_sliders | |
| } | |
| # When the user navigates TO "Method constraints" from any other tab, force a | |
| # full widget re-sync so toggle_M / slider_M keys always reflect the constraint | |
| # state. Normal-mode setdefault can't recover keys that were externally set to | |
| # False/0; force mode overwrites them unconditionally from constraints (which | |
| # remain correct — the summary table at the bottom of tab2 always confirms | |
| # this). The flag is consumed (popped) by tab2's own pre-render sync. | |
| _prev_tab = st.session_state.get("_active_tab") | |
| if selected == "Method constraints" and _prev_tab != "Method constraints": | |
| st.session_state["constraints_just_loaded"] = True | |
| st.session_state["_active_tab"] = selected | |
| if selected == "Welcome": | |
| tab0_welcome.render_tab() | |
| elif selected == "Resource inputs": | |
| tab1_inputs.render_tab(cost_sliders) | |
| elif selected == "Method constraints": | |
| tab2_constraints.render_tab(cost_sliders) | |
| elif selected == "Resource coefficients": | |
| tab3_coefficients.render_tab(cost_sliders) | |
| elif selected == "Portfolio generation": | |
| tab4_optimization.render_tab() | |
| elif selected == "Sensitivity analysis": | |
| tab5_sensitivity.render_tab() | |
| elif selected == "Scenario comparison": | |
| tab6_scenarios.render_tab() |