import streamlit as st import pandas as pd import io from logic.calculations import calculate_specific_heat_demand def render_heating_load_input(ui): with st.container(): st.markdown(ui["input"]["main_box"], unsafe_allow_html=True) st.write("") with st.expander(ui["input"]["heat_load_expander"], expanded=False): kenne_heizlast = st.checkbox( ui["input"]["know_heat_load"], key="kenne_heizlast" ) heizlast_user = st.number_input( ui["input"]["heat_load"], min_value=1.0, max_value=110.0, value=10.0, step=0.5, help=ui["input"]["heat_load_help"], disabled=not kenne_heizlast, key="heizlast_user" ) def render_basic_inputs(ui): col1, col2 = st.columns(2) with col1: nutzflaeche = st.number_input( ui["input"]["area"], min_value=20.0, max_value=10000.0, value=200.0, step=10.0 ) baujahr = st.number_input( ui["input"]["building_year"], min_value=1900, max_value=2025, value=1980, help=ui["input"]["building_year_help"] ) with col2: gesamtbedarf_input = st.text_input( ui["input"]["total_demand"], value="", placeholder=ui["input"]["total_demand_placeholder"], help=ui["input"]["total_demand_help"] ) gesamtbedarf = None if gesamtbedarf_input.strip() != "": try: gesamtbedarf = float(gesamtbedarf_input.replace(",", ".")) except ValueError: st.warning("Bitte einen gültigen Zahlenwert für den Gesamtwärmebedarf eintragen.") if gesamtbedarf is not None and gesamtbedarf > 0: spezifisch_disabled = True spezifisch_hint = ui["input"]["specific_demand_hint_ignored"] else: spezifisch_disabled = False spezifisch_hint = "" energiebedarf = st.number_input( ui["input"]["specific_demand"], min_value=0.0, max_value=500.0, value=150.0, step=5.0, disabled=spezifisch_disabled, help=ui["input"]["specific_demand_help"] ) if spezifisch_hint: st.info(spezifisch_hint) return nutzflaeche, baujahr, gesamtbedarf, energiebedarf # This function needs calculate_heat_demand from calculations.py def show_specific_heat_demand_message(total_demand, floor_area, specific_demand, ui): value = calculate_specific_heat_demand(total_demand, floor_area, specific_demand) st.write(ui["input"]["used_value"].format( value=f"{value:.2f}".replace(".", ",") )) return value def render_advanced_settings(ui, scen_values): with st.expander(ui["input"]["advanced_settings"], expanded=False): zinssatz = float(scen_values['zinssatz']) beobachtungszeitraum = int(scen_values['beobachtungszeitraum']) emission_cost_per_t = float(scen_values['emission_cost']) preisaenderungsfaktor_emission = float(scen_values['preisaenderungsfaktor_emission']) # --- Two-column layout --- col1, col2 = st.columns(2) with col1: zinssatz_prozent = (zinssatz - 1) * 100 zinssatz_prozent = st.number_input( ui["input"]["interest_rate"], value=zinssatz_prozent, min_value=0.0, max_value=100.0, step=0.1, format="%.1f" ) zinssatz = 1 + zinssatz_prozent / 100 beobachtungszeitraum = st.number_input( ui["input"]["obs_period"], min_value=5, max_value=40, value=beobachtungszeitraum, help=ui["input"]["obs_period_help"] ) with col2: wachstumsrate_emission = (preisaenderungsfaktor_emission - 1) * 100 wachstumsrate_emission = st.number_input( ui["input"]["emission_growth"], value=wachstumsrate_emission, min_value=0.0, max_value=100.0, step=0.1, format="%.1f" ) preisaenderungsfaktor_emission = 1 + wachstumsrate_emission / 100 emission_cost_per_t = st.number_input( ui["input"]["emission_cost"], value=emission_cost_per_t, min_value=0.0, max_value=1000.0, step=1.0, format="%.1f" ) emission_cost = emission_cost_per_t / 1000 return zinssatz, beobachtungszeitraum, preisaenderungsfaktor_emission, emission_cost def render_heating_system_editor(df, ui): st.markdown("## 🔧 " + ui["input"]["custom_values_header"]) with st.expander(ui["input"]["operating_costs_expander"], expanded=False): st.markdown(ui["input"]["operating_costs_info"], unsafe_allow_html=True) # Init Session-State if "user_values" not in st.session_state: st.session_state.user_values = {} if "show_reset_msg" not in st.session_state: st.session_state.show_reset_msg = False if "reset_count" not in st.session_state: st.session_state.reset_count = 0 if "show_global_reset_msg" not in st.session_state: st.session_state.show_global_reset_msg = False enable_custom = st.checkbox( ui["input"]["custom_values_checkbox"], value=st.session_state.get("enable_custom_values", False), key="enable_custom_values" ) if not enable_custom: return False, None tech_names = df["Name"].tolist() selected = st.selectbox("Technologie auswählen", tech_names) idx = df[df["Name"] == selected].index[0] row = df.loc[idx] if "save_triggered" not in st.session_state: st.session_state.save_triggered = False form_key_suffix = f"_{selected}_{st.session_state.reset_count}" # nur wenn alle Keys da sind → aktuelle UI-Werte verwenden expected_keys = [f"inv{form_key_suffix}"] if st.session_state.save_triggered and all(k in st.session_state for k in expected_keys): values = { "Investitionskosten": st.session_state[f"inv{form_key_suffix}"], "Förderung": st.session_state[f"gr{form_key_suffix}"], "Betriebsdauer": st.session_state[f"life{form_key_suffix}"], "Effizienz": st.session_state[f"eff{form_key_suffix}"], "Preisänderungsfaktor_Inv": st.session_state[f"cf{form_key_suffix}"], "Betriebskosten": st.session_state[f"op{form_key_suffix}"], "Preisänderungsfaktor_Bedarf": st.session_state[f"of{form_key_suffix}"], "Fixkosten_O+M": st.session_state[f"fo{form_key_suffix}"], "Emissionsänderungsfaktor": st.session_state[f"ef{form_key_suffix}"], "Emissionen": st.session_state[f"em{form_key_suffix}"], } else: values = st.session_state.user_values.get(idx, row) # Key-Suffix eindeutig machen mit reset_count form_key_suffix = f"_{selected}_{st.session_state.reset_count}" with st.form("edit_selected_heating_system"): st.markdown(f"### {row['Name']} ({row['Leistung']} kW)") col1, col2 = st.columns(2) with col1: invest = st.number_input(ui["input"]["capex"], 0.0, value=float(values["Investitionskosten"]), step=100.0, format="%f", key=f"inv{form_key_suffix}") grant = st.number_input(ui["input"]["grant"], 0.0, value=float(values["Förderung"]), step=1.0, format="%f", key=f"gr{form_key_suffix}", help=ui["input"]["grant_help"]) lifetime = st.number_input(ui["input"]["lifetime"], 1, value=int(values["Betriebsdauer"]), key=f"life{form_key_suffix}") eff = st.number_input(ui["input"]["efficiency"], 0.01, value=float(values["Effizienz"]), step=0.05, key=f"eff{form_key_suffix}") opex = st.number_input(ui["input"]["opex"], value=float(values["Betriebskosten"]), step=0.001, key=f"op{form_key_suffix}", format="%.3f") with col2: capex_factor = st.number_input(ui["input"]["capex_growth"], value=float(values["Preisänderungsfaktor_Inv"]), step=0.01, key=f"cf{form_key_suffix}") opex_factor = st.number_input(ui["input"]["opex_growth"], value=float(values["Preisänderungsfaktor_Bedarf"]), step=0.01, key=f"of{form_key_suffix}") fix_om = st.number_input(ui["input"]["fixed_om"], value=float(values["Fixkosten_O+M"]), step=0.01, key=f"fo{form_key_suffix}") emis_factor = st.number_input(ui["input"]["emission_change"], value=float(values["Emissionsänderungsfaktor"]), step=0.01, key=f"ef{form_key_suffix}") emission = st.number_input(ui["input"]["emissions"], value=float(values["Emissionen"]), step=0.001, key=f"em{form_key_suffix}", format="%.3f") col1, col2 = st.columns([1, 1]) with col1: save_clicked = st.form_submit_button(ui["input"]["save_button"]) with col2: reset_clicked = st.form_submit_button(ui["input"]["reset_button"]) if st.session_state.get("show_reset_msg"): st.success(ui["input"]["reset_success"]) st.session_state.show_reset_msg = False if reset_clicked: if idx in st.session_state.user_values: del st.session_state.user_values[idx] st.session_state.reset_count += 1 # WICHTIG: neuer Key-Suffix wird erzwungen st.session_state.show_reset_msg = True st.rerun() if save_clicked: st.session_state.user_values[idx] = { "Investitionskosten": st.session_state[f"inv{form_key_suffix}"], "Förderung": st.session_state[f"gr{form_key_suffix}"], "Betriebsdauer": st.session_state[f"life{form_key_suffix}"], "Effizienz": st.session_state[f"eff{form_key_suffix}"], "Preisänderungsfaktor_Inv": st.session_state[f"cf{form_key_suffix}"], "Betriebskosten": st.session_state[f"op{form_key_suffix}"], "Preisänderungsfaktor_Bedarf": st.session_state[f"of{form_key_suffix}"], "Fixkosten_O+M": st.session_state[f"fo{form_key_suffix}"], "Emissionsänderungsfaktor": st.session_state[f"ef{form_key_suffix}"], "Emissionen": st.session_state[f"em{form_key_suffix}"], } st.session_state.save_triggered = True st.success(ui["input"]["save_success"]) #st.markdown("---") if st.button(ui["input"]["reset_settings"]): st.session_state.user_values = {} st.session_state.reset_count += 1 st.session_state.show_global_reset_msg = True st.rerun() if st.session_state.get("show_global_reset_msg"): st.success(ui["input"]["all_reset_success"]) st.session_state.show_global_reset_msg = False return enable_custom, st.session_state.user_values def render_batch_heating_system_editor(df, ui): st.markdown("### " + ui["batch"]["custom_values_header"]) enable_custom = st.checkbox( ui["batch"]["custom_values_checkbox"], value=False, key="enable_batch_custom" ) if enable_custom and not df.empty: with st.expander(ui["batch"]["operating_costs_expander"], expanded=False): st.markdown(ui["batch"]["operating_costs_info"], unsafe_allow_html=True) user_values = {} with st.form("batch_edit_heizsysteme_form"): for idx, row in df.iterrows(): with st.expander(f"{row['Name']}", expanded=False): # <--- Kein key-Argument! def safe_float(x, fallback=0.0): try: return float(x) except Exception: return fallback grant = st.number_input( ui["batch"]["grant"], min_value=0.0, value=safe_float(row.get("Förderung", 0.0)), step=1.0, key=f"bfoe_{idx}", format="%f", help=ui["batch"]["grant_help"] ) opex = st.number_input( ui["batch"]["opex"], value=safe_float(row.get("Betriebskosten", 0.0)), step=0.001, key=f"bbk_{idx}", format="%.3f" ) opex_growth = st.number_input( ui["batch"]["opex_growth"], value=safe_float(row.get('Preisänderungsfaktor_Bedarf', 1.0), 1.0), step=0.01, key=f"bprbed_{idx}" ) emissions = st.number_input( ui["batch"]["emissions"], value=safe_float(row.get("Emissionen", 0.0)), step=0.001, key=f"bem_{idx}", format="%.3f" ) user_values[idx] = { "Förderung": grant, "Betriebskosten": opex, "Preisänderungsfaktor_Bedarf": opex_growth, "Emissionen": emissions } submitted = st.form_submit_button(ui["batch"]["save_button"]) return submitted, user_values else: return False, None def apply_user_values(df, user_values): df_copy = df.copy() for idx in df_copy.index: if idx in user_values: for key in user_values[idx]: df_copy.loc[idx, key] = user_values[idx][key] return df_copy def render_batch_template_download(ui): lang = ui.get("lang", "de") # Dynamische Spaltennamen aus UI-Texten column_names = ui["batch"]["columns"] tabelle_vorlage = pd.DataFrame({ column_names[0]: ["1", "2"], column_names[1]: [500, 200], column_names[2]: [1970, 1991], column_names[3]: ["", 31200], column_names[4]: [120, ""], column_names[5]: ["", ""] }) filename = "Input-Vorlage_DE.csv" if lang == "de" else "Input-Template_EN.csv" outbuf = io.StringIO() tabelle_vorlage.to_csv(outbuf, sep=";", index=False) vorlage_bytes = outbuf.getvalue().encode("utf-8") st.markdown("#### " + ui["batch"]["csv_template_header"]) st.download_button( label=ui["batch"]["download_label"], data=vorlage_bytes, file_name=filename, mime="text/csv", help=ui["batch"]["download_help"] ) def apply_batch_user_values(df, preview_df, batch_user_vals): preview_names = {preview_df.loc[x, "Name"]: x for x in preview_df.index} for pidx in batch_user_vals: sysname = preview_df.loc[pidx, "Name"] df_idx = df[df["Name"] == sysname].index if not df_idx.empty: for key, val in batch_user_vals[pidx].items(): df.at[df_idx[0], key] = val return df # --- Minimal constraint checkboxes --- def render_basic_constraints(title: str = "Platzhalter: Eignungs-Check"): st.markdown(f"#### {title}") enough_space = st.checkbox("Platzhalter: Genug Platz vorhanden", value=False) gas_boiler_ok = st.checkbox("Platzhalter: Gas-Heizung vorhanden und weiter nutzbar", value=False) return { "enough_space": enough_space, "gas_boiler_ok": gas_boiler_ok, }