Spaces:
Running
Running
File size: 5,686 Bytes
ca86b1a ba2dcff 64cc758 fbd166f ba2dcff ca86b1a 3c60617 ba2dcff 1c33177 ba2dcff fbd166f 3c60617 719f294 ba2dcff 077dc45 ba2dcff 3c60617 fbd166f 077dc45 ba2dcff fbd166f 3c60617 fbd166f 87fce1c fbd166f ca86b1a 87fce1c ca86b1a 64cc758 ca86b1a a58aedd 3c60617 a58aedd 87fce1c 3c60617 a58aedd 87fce1c 3c60617 87fce1c a58aedd 3c60617 ca86b1a 64cc758 87fce1c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | import io
import json
import re
import pandas as pd
import streamlit as st
import openpyxl
from openpyxl.styles import PatternFill, Font
from config.config import resource_unit_map, resource_order, method_order
@st.cache_data
def load_slider_data():
"""Load slider data from a JSON file and cache it for performance."""
with open("data/cost_sliders.json") as f:
data = json.load(f)
return data
def render_resource_input(name_clean, r, tooltip=None):
widget_key = f"cap_{r}"
if widget_key not in st.session_state:
existing = st.session_state.resource_caps.get(r, 0.0)
st.session_state[widget_key] = existing if existing else None
cap = st.number_input(
label=name_clean,
min_value=0.0,
format="%.2f",
value=None,
placeholder="0.00",
key=widget_key,
help=tooltip
)
st.session_state.resource_caps[r] = cap if cap is not None else 0.0
def is_valid_scenario(scenario):
base_ok = (
isinstance(scenario, dict)
and "allocations" in scenario
and "resource_caps" in scenario
and "method_costs" in scenario
and "method_constraints" in scenario
and "summary" in scenario
)
if not base_ok:
return False
return True
def is_complete_scenario(scenario):
"""A scenario that also carries the detailed optimisation outputs and the
per-resource manual allocation state, so reloading it reproduces the
exact same optimisation (not just the same inputs)."""
required_extra = {
"resource_usage", "resource_used_total", "resource_remaining","enabled_methods",
}
return is_valid_scenario(scenario) and required_extra.issubset(scenario.keys())
def load_inputs_from_excel(file):
xl = pd.read_excel(file, sheet_name=None)
resource_caps = {}
if "resource_caps" in xl:
for _, row in xl["resource_caps"].iterrows():
resource_caps[row["Resource"]] = float(row["Available Amount"])
constraints = {}
if "constraints" in xl:
for _, row in xl["constraints"].iterrows():
constraints[row["method"]] = {
"active": bool(row["active"]),
"cap_type": str(row["cap_type (percent or absolute)"]),
"cap_value": float(row["cap_value"])
}
costs = {}
if "coefficients" in xl:
for _, row in xl["coefficients"].iterrows():
m, r, v = row["method"], row["resource"], float(row["value"])
costs.setdefault(m, {})[r] = v
custom_resources = []
warnings = []
valid_resources = set(resource_order)
valid_methods = set(method_order)
if "custom_resources" in xl:
df_custom = xl["custom_resources"]
required_custom = {"name", "group", "amount", "unit", "method", "min", "median", "max"}
if required_custom.issubset(df_custom.columns):
for bname in df_custom["name"].dropna().unique():
df_b = df_custom[df_custom["name"] == bname]
group = str(df_b["group"].iloc[0])
if group not in valid_resources:
warnings.append(f"New resource \"{bname}\" - group \"{group}\" not found: check spelling.")
continue
methods = {}
for _, row in df_b.iterrows():
m = str(row["method"])
if m not in valid_methods:
warnings.append(f"New resource \"{bname}\" - method \"{m}\" not found: check spelling.")
continue
methods[m] = {
"min": float(row["min"]),
"median": float(row["median"]),
"max": float(row["max"]),
}
custom_resources.append({
"name": str(bname),
"group": group,
"amount": float(df_b["amount"].iloc[0]),
"unit": str(df_b["unit"].iloc[0]),
"methods": methods,
})
# Per-resource manual method allocation (which CDR methods are allowed to
# use a given resource). Optional sheet for backward compatibility with
# older templates that predate this feature.
enabled_methods = {}
if "enabled_methods" in xl:
df_enabled = xl["enabled_methods"]
required_enabled = {"resource", "method", "enabled"}
if required_enabled.issubset(df_enabled.columns):
for _, row in df_enabled.iterrows():
r, m = row["resource"], row["method"]
enabled_methods.setdefault(r, {})[m] = bool(row["enabled"])
return resource_caps, constraints, costs, custom_resources, enabled_methods, warnings
def strip_unit_suffix(resource_name: str) -> str:
"""Remove legacy unit suffix from resource name, e.g. 'Unspecified land (ha/tCO2)' -> 'Unspecified land'"""
return re.sub(r"\s*\(.*?\)\s*$", "", resource_name).strip()
def neutralize_zero_coefficient_methods(method_costs: dict, method_constraints: dict) -> dict:
safe_constraints = {m: c.copy() for m, c in method_constraints.items()}
for method, constraint in safe_constraints.items():
if not constraint.get("active", True):
continue
coeffs = method_costs.get(method, {})
all_zero = all(v == 0 for v in coeffs.values()) if coeffs else True
if all_zero:
constraint["active"] = False
return safe_constraints |