""" BuildSustain - Internal Loads Module This module handles the internal loads functionality of the BuildSustain application, allowing users to define occupancy, lighting, equipment, ventilation, infiltration, and schedules. Developed by: Dr Majed Abuseif, Deakin University © 2025 """ import streamlit as st import pandas as pd import numpy as np import json import logging import uuid import plotly.graph_objects as go import plotly.express as px from typing import Dict, List, Any, Optional, Tuple, Union # Import data from i_l_data.py from app.i_l_data import PEOPLE_ACTIVITY_LEVELS, DEFAULT_BUILDING_INTERNALS, DEFAULT_SCHEDULE_TEMPLATES, display_internal_loads_help, LIGHTING_FIXTURE_TYPES, DEFAULT_EQUIPMENT_LOADS, DEFAULT_OCCUPANT_DENSITY # Configure logging logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) # Define constants LOAD_TYPES = ["Schedules", "People", "Lighting", "Equipment", "Ventilation", "Infiltration"] def display_internal_loads_page(): """ Display the internal loads page. This is the main function called by main.py when the Internal Loads page is selected. """ st.title("Internal Loads") st.write("Define internal heat gains from people, lighting, equipment, ventilation, and infiltration based on ASHRAE 2021 Handbook.") # Check if building type is set building_type = st.session_state.project_data["building_info"].get("building_type") if not building_type: st.error("Please select a building type in Building Information.") if st.button("Go to Building Information", key="internal_to_building"): st.session_state.current_page = "Building Information" st.rerun() return # Display help information in an expandable section with st.expander("Help & Information"): display_internal_loads_help() # Initialize rerun flag if not present if "internal_loads_rerun_pending" not in st.session_state: st.session_state.internal_loads_rerun_pending = False # Check if rerun is pending if st.session_state.internal_loads_rerun_pending: st.session_state.internal_loads_rerun_pending = False st.rerun() # Create tabs for different load types tabs = st.tabs(LOAD_TYPES) for i, load_type in enumerate(LOAD_TYPES): with tabs[i]: if load_type == "Schedules": display_schedules_tab() elif load_type == "People": display_people_tab() elif load_type == "Lighting": display_lighting_tab() elif load_type == "Equipment": display_equipment_tab() elif load_type == "Ventilation": display_ventilation_tab() elif load_type == "Infiltration": display_infiltration_tab() # Navigation buttons col1, col2 = st.columns(2) with col1: if st.button("Back to Building Components", key="back_to_components"): st.session_state.current_page = "Building Components" st.rerun() with col2: if st.button("Continue to HVAC Loads", key="continue_to_hvac_loads"): st.session_state.current_page = "HVAC Loads" st.rerun() def initialize_internal_loads(): """ Initialize internal loads in session state if not present. Logging a warning if initialization is needed for debugging. """ if "internal_loads" not in st.session_state.project_data: logger.warning("Internal loads not initialized by main.py, using fallback initialization") st.session_state.project_data["internal_loads"] = { "schedules": dict(DEFAULT_SCHEDULE_TEMPLATES), "people": [], "lighting": [], "equipment": [], "ventilation": [], "infiltration": [] } def display_people_tab(): """Display the people tab content with a two-column layout similar to components.py.""" # Get people from session state people_groups = st.session_state.project_data["internal_loads"]["people"] # Split the display into two columns col1, col2 = st.columns([3, 2]) with col1: st.subheader("Saved People Groups") if people_groups: display_people_table(people_groups) else: st.write("No people groups defined.") with col2: st.subheader("People Group Editor/Creator") # Initialize editor and action states if "people_editor" not in st.session_state: st.session_state.people_editor = {} if "people_action" not in st.session_state: st.session_state.people_action = {"action": None, "id": None} # Get building type and floor area for default number of people building_type = st.session_state.project_data["building_info"].get("building_type") floor_area = st.session_state.project_data["building_info"].get("floor_area", 100.0) schedule_type = DEFAULT_BUILDING_INTERNALS.get(building_type, DEFAULT_BUILDING_INTERNALS["Other"])["schedule_type"] occupant_density = DEFAULT_OCCUPANT_DENSITY.get(schedule_type, {"occupant_densities_PEOPLE_m2": 0.1})["occupant_densities_PEOPLE_m2"] default_num_people = int(np.ceil(occupant_density * floor_area)) # Display the editor form with st.form("people_editor_form", clear_on_submit=True): editor_state = st.session_state.get("people_editor", {}) is_edit = editor_state.get("is_edit", False) # Group name name = st.text_input( "Group Name", value=editor_state.get("name", ""), help="Enter a unique name for this people group." ) # Number of people num_people = st.number_input( "Number of People", min_value=1, max_value=1000, value=int(editor_state.get("num_people", default_num_people)), help="Number of people in this group." ) # Activity level activity_level = st.selectbox( "Activity Level", list(PEOPLE_ACTIVITY_LEVELS.keys()), index=list(PEOPLE_ACTIVITY_LEVELS.keys()).index(editor_state.get("activity_level", list(PEOPLE_ACTIVITY_LEVELS.keys())[0])) if editor_state.get("activity_level") in PEOPLE_ACTIVITY_LEVELS else 0, help="Select the activity level for this group." ) # Clothing insulation st.write("**Clothing Insulation:**") col_summer, col_winter = st.columns(2) with col_summer: clo_summer = st.number_input( "Summer (clo)", min_value=0.0, max_value=2.0, value=float(editor_state.get("clo_summer", 0.5)), format="%.2f", help="Clothing insulation for summer conditions." ) with col_winter: clo_winter = st.number_input( "Winter (clo)", min_value=0.0, max_value=2.0, value=float(editor_state.get("clo_winter", 1.0)), format="%.2f", help="Clothing insulation for winter conditions." ) # Schedule selection available_schedules = list(st.session_state.project_data["internal_loads"]["schedules"].keys()) schedule = st.selectbox( "Schedule", available_schedules, index=available_schedules.index(editor_state.get("schedule", available_schedules[0])) if editor_state.get("schedule") in available_schedules else 0, help="Select the occupancy schedule for this group." ) # Submit buttons col1, col2 = st.columns(2) with col1: submit_label = "Update" if is_edit else "Add" submit = st.form_submit_button(f"{submit_label} People Group") with col2: refresh = st.form_submit_button("Refresh") # Handle form submission if submit: # Validate inputs if not name.strip(): st.error("Group name is required.") return # Check for unique name existing_names = [group["name"] for group in people_groups if not (is_edit and group["name"] == editor_state.get("name"))] if name.strip() in existing_names: st.error("Group name must be unique.") return # Get activity data activity_data = PEOPLE_ACTIVITY_LEVELS[activity_level] # Create people group data people_data = { "id": str(uuid.uuid4()), "name": name.strip(), "num_people": num_people, "activity_level": activity_level, "activity_data": activity_data, "clo_summer": clo_summer, "clo_winter": clo_winter, "schedule": schedule, "sensible_heat_per_person": (activity_data["sensible_min_w"] + activity_data["sensible_max_w"]) / 2, "latent_heat_per_person": (activity_data["latent_min_w"] + activity_data["latent_max_w"]) / 2, "total_sensible_heat": num_people * (activity_data["sensible_min_w"] + activity_data["sensible_max_w"]) / 2, "total_latent_heat": num_people * (activity_data["latent_min_w"] + activity_data["latent_max_w"]) / 2 } # Handle edit mode if is_edit: index = editor_state.get("index", 0) st.session_state.project_data["internal_loads"]["people"][index] = people_data st.success(f"People Group '{name}' updated!") else: st.session_state.project_data["internal_loads"]["people"].append(people_data) st.success(f"People Group '{name}' added!") # Clear editor state st.session_state.people_editor = {} st.session_state.people_action = {"action": "save", "id": str(uuid.uuid4())} st.session_state.internal_loads_rerun_pending = True elif refresh: # Clear editor state st.session_state.people_editor = {} st.session_state.people_action = {"action": "refresh", "id": str(uuid.uuid4())} st.session_state.internal_loads_rerun_pending = True def display_lighting_tab(): """Display the lighting tab content with a two-column layout similar to components.py.""" # Get lighting from session state lighting_systems = st.session_state.project_data["internal_loads"]["lighting"] # Split the display into two columns col1, col2 = st.columns([3, 2]) with col1: st.subheader("Saved Lighting Systems") if lighting_systems: display_lighting_table(lighting_systems) else: st.write("No lighting systems defined.") with col2: st.subheader("Lighting System Editor/Creator") # Initialize editor and action states if "lighting_editor" not in st.session_state: st.session_state.lighting_editor = {} if "lighting_action" not in st.session_state: st.session_state.lighting_action = {"action": None, "id": None} # Get building type for default values building_type = st.session_state.project_data["building_info"].get("building_type") default_lighting_density = DEFAULT_BUILDING_INTERNALS.get(building_type, DEFAULT_BUILDING_INTERNALS["Other"])["lighting_density"] # Display the editor form with st.form("lighting_editor_form", clear_on_submit=True): editor_state = st.session_state.get("lighting_editor", {}) is_edit = editor_state.get("is_edit", False) # System name name = st.text_input( "System Name", value=editor_state.get("name", ""), help="Enter a unique name for this lighting system." ) # Area area = st.number_input( "Area (m²)", min_value=1.0, max_value=100000.0, value=float(editor_state.get("area", st.session_state.project_data["building_info"].get("floor_area", 100.0))), format="%.2f", help="Floor area served by this lighting system." ) # Lighting power density lpd = st.number_input( "Lighting Power Density (W/m²)", min_value=0.1, max_value=50.0, value=float(editor_state.get("lpd", default_lighting_density)), format="%.2f", help="Lighting power density in watts per square meter." ) # Lighting type lighting_type = st.selectbox( "Lighting Type", list(LIGHTING_FIXTURE_TYPES.keys()), index=list(LIGHTING_FIXTURE_TYPES.keys()).index(editor_state.get("lighting_type", list(LIGHTING_FIXTURE_TYPES.keys())[0])) if editor_state.get("lighting_type") in LIGHTING_FIXTURE_TYPES else 0, help="Select the type of lighting fixture." ) # Schedule selection available_schedules = list(st.session_state.project_data["internal_loads"]["schedules"].keys()) schedule = st.selectbox( "Schedule", available_schedules, index=available_schedules.index(editor_state.get("schedule", available_schedules[0])) if editor_state.get("schedule") in available_schedules else 0, help="Select the lighting schedule for this system." ) # Submit buttons col1, col2 = st.columns(2) with col1: submit_label = "Update" if is_edit else "Add" submit = st.form_submit_button(f"{submit_label} Lighting System") with col2: refresh = st.form_submit_button("Refresh") # Handle form submission if submit: # Validate inputs if not name.strip(): st.error("System name is required.") return # Check for unique name existing_names = [system["name"] for system in lighting_systems if not (is_edit and system["name"] == editor_state.get("name"))] if name.strip() in existing_names: st.error("System name must be unique.") return # Get lighting fixture data fixture_data = LIGHTING_FIXTURE_TYPES[lighting_type] # Create lighting system data lighting_data = { "id": str(uuid.uuid4()), "name": name.strip(), "area": area, "lpd": lpd, "total_power": area * lpd, "lighting_type": lighting_type, "radiative_fraction": fixture_data["radiative"] / 100.0, "convective_fraction": fixture_data["convective"] / 100.0, "schedule": schedule } # Handle edit mode if is_edit: index = editor_state.get("index", 0) st.session_state.project_data["internal_loads"]["lighting"][index] = lighting_data st.success(f"Lighting System '{name}' updated!") else: st.session_state.project_data["internal_loads"]["lighting"].append(lighting_data) st.success(f"Lighting System '{name}' added!") # Clear editor state st.session_state.lighting_editor = {} st.session_state.lighting_action = {"action": "save", "id": str(uuid.uuid4())} st.session_state.internal_loads_rerun_pending = True elif refresh: # Clear editor state st.session_state.lighting_editor = {} st.session_state.lighting_action = {"action": "refresh", "id": str(uuid.uuid4())} st.session_state.internal_loads_rerun_pending = True def display_equipment_tab(): """Display the equipment tab content with a two-column layout similar to components.py.""" # Get equipment from session state equipment_systems = st.session_state.project_data["internal_loads"]["equipment"] # Split the display into two columns col1, col2 = st.columns([3, 2]) with col1: st.subheader("Saved Equipment") if equipment_systems: display_equipment_table(equipment_systems) else: st.write("No equipment defined.") with col2: st.subheader("Equipment Editor/Creator") # Initialize editor and action states if "equipment_editor" not in st.session_state: st.session_state.equipment_editor = {} if "equipment_action" not in st.session_state: st.session_state.equipment_action = {"action": None, "id": None} # Get building type and corresponding schedule type for default values building_type = st.session_state.project_data["building_info"].get("building_type") schedule_type = DEFAULT_BUILDING_INTERNALS.get(building_type, DEFAULT_BUILDING_INTERNALS["Other"])["schedule_type"] default_equipment_data = DEFAULT_EQUIPMENT_LOADS.get(schedule_type, DEFAULT_EQUIPMENT_LOADS["default"]) # Display the editor form with st.form("equipment_editor_form", clear_on_submit=True): editor_state = st.session_state.get("equipment_editor", {}) is_edit = editor_state.get("is_edit", False) # Equipment name name = st.text_input( "Equipment Name", value=editor_state.get("name", ""), help="Enter a unique name for this equipment." ) # Area area = st.number_input( "Area (m²)", min_value=1.0, max_value=100000.0, value=float(editor_state.get("area", st.session_state.project_data["building_info"].get("floor_area", 100.0))), format="%.2f", help="Floor area served by this equipment." ) # Equipment load equipment_load = st.number_input( "Equipment Load (W/m²)", min_value=0.0, max_value=200.0, value=float(editor_state.get("equipment_load", default_equipment_data["equipment_load_w_m2"])), format="%.2f", help="Total equipment load in watts per square meter." ) # Heat gains st.write("**Heat Gains (W/m²):**") col_sens, col_lat = st.columns(2) with col_sens: sensible_gain = st.number_input( "Sensible Heat Gain", min_value=0.0, max_value=200.0, value=float(editor_state.get("sensible_gain", default_equipment_data["equipment_load_w_m2"] * (default_equipment_data["sensible_percent"] / 100))), format="%.2f", help="Sensible heat gain in watts per square meter." ) with col_lat: latent_gain = st.number_input( "Latent Heat Gain", min_value=0.0, max_value=200.0, value=float(editor_state.get("latent_gain", default_equipment_data["equipment_load_w_m2"] * (default_equipment_data["latent_percent"] / 100))), format="%.2f", help="Latent heat gain in watts per square meter." ) # Heat distribution st.write("**Heat Distribution:**") col_rad, col_conv = st.columns(2) with col_rad: radiative_fraction = st.number_input( "Radiative Fraction", min_value=0.0, max_value=1.0, value=float(editor_state.get("radiative_fraction", default_equipment_data["radiant_percent"] / 100)), format="%.2f", help="Fraction of sensible heat released as radiation." ) with col_conv: convective_fraction = st.number_input( "Convective Fraction", min_value=0.0, max_value=1.0, value=float(editor_state.get("convective_fraction", default_equipment_data["convective_percent"] / 100)), format="%.2f", help="Fraction of sensible heat released as convection." ) # Schedule selection available_schedules = list(st.session_state.project_data["internal_loads"]["schedules"].keys()) schedule = st.selectbox( "Schedule", available_schedules, index=available_schedules.index(editor_state.get("schedule", schedule_type)) if editor_state.get("schedule") in available_schedules else available_schedules.index(schedule_type) if schedule_type in available_schedules else 0, help="Select the equipment schedule." ) # Submit buttons col1, col2 = st.columns(2) with col1: submit_label = "Update" if is_edit else "Add" submit = st.form_submit_button(f"{submit_label} Equipment") with col2: refresh = st.form_submit_button("Refresh") # Handle form submission if submit: # Validate inputs if not name.strip(): st.error("Equipment name is required.") return if abs(radiative_fraction + convective_fraction - 1.0) > 0.01: st.error("Radiative and convective fractions must sum to 1.0") return if abs(sensible_gain + latent_gain - equipment_load) > 0.01: st.error("Sensible and latent heat gains must sum to the equipment load.") return # Check for unique name existing_names = [system["name"] for system in equipment_systems if not (is_edit and system["name"] == editor_state.get("name"))] if name.strip() in existing_names: st.error("Equipment name must be unique.") return # Create equipment data equipment_data = { "id": str(uuid.uuid4()), "name": name.strip(), "area": area, "equipment_load": equipment_load, "sensible_gain": sensible_gain, "latent_gain": latent_gain, "total_sensible_power": area * sensible_gain, "total_latent_power": area * latent_gain, "radiative_fraction": radiative_fraction, "convective_fraction": convective_fraction, "schedule": schedule } # Handle edit mode if is_edit: index = editor_state.get("index", 0) st.session_state.project_data["internal_loads"]["equipment"][index] = equipment_data st.success(f"Equipment '{name}' updated!") else: st.session_state.project_data["internal_loads"]["equipment"].append(equipment_data) st.success(f"Equipment '{name}' added!") # Clear editor state st.session_state.equipment_editor = {} st.session_state.equipment_action = {"action": "save", "id": str(uuid.uuid4())} st.session_state.internal_loads_rerun_pending = True elif refresh: # Clear editor state st.session_state.equipment_editor = {} st.session_state.equipment_action = {"action": "refresh", "id": str(uuid.uuid4())} st.session_state.internal_loads_rerun_pending = True def display_ventilation_tab(): """Display the ventilation tab content with a two-column layout.""" # Get ventilation systems from session state ventilation_systems = st.session_state.project_data["internal_loads"].setdefault("ventilation", []) # Split the display into two columns col1, col2 = st.columns([3, 2]) with col1: st.subheader("Saved Ventilation Systems") if ventilation_systems: display_ventilation_table(ventilation_systems) else: st.write("No ventilation systems defined.") with col2: st.subheader("Ventilation System Editor/Creator") # Initialize editor and action states if "ventilation_editor" not in st.session_state: st.session_state.ventilation_editor = {} if "ventilation_action" not in st.session_state: st.session_state.ventilation_action = {"action": None, "id": None} # Get building type for default values building_type = st.session_state.project_data["building_info"].get("building_type") default_building_data = DEFAULT_BUILDING_INTERNALS.get(building_type, DEFAULT_BUILDING_INTERNALS["Other"]) default_ventilation_rate = default_building_data.get("ventilation_rate", 1.0) # L/s·m² # System type selection (outside form) system_type = st.selectbox( "System Type", ["AirChanges/Hour", "Wind and Stack Open Area", "Balanced Flow", "Heat Recovery"], key="ventilation_system_type", help="Select the type of ventilation system." ) # Display the editor form with st.form("ventilation_editor_form", clear_on_submit=True): editor_state = st.session_state.get("ventilation_editor", {}) is_edit = editor_state.get("is_edit", False) # System name name = st.text_input( "System Name", value=editor_state.get("name", ""), help="Enter a unique name for this ventilation system." ) # Area (required for all types) area = st.number_input( "Area (m²)", min_value=1.0, max_value=100000.0, value=float(editor_state.get("area", st.session_state.project_data["building_info"].get("floor_area", 100.0))), format="%.2f", help="Floor area served by this system." ) # Schedule selection available_schedules = list(st.session_state.project_data["internal_loads"]["schedules"].keys()) schedule = st.selectbox( "Schedule", available_schedules, index=available_schedules.index(editor_state.get("schedule", available_schedules[0])) if editor_state.get("schedule") in available_schedules else 0, help="Select the operation schedule for this system." ) # Type-specific inputs if system_type == "AirChanges/Hour": design_flow_rate = st.number_input( "Design Flow Rate (L/s·m²)", min_value=0.1, max_value=50.0, value=float(editor_state.get("design_flow_rate", default_ventilation_rate)), format="%.2f", help="Ventilation rate in liters per second per square meter." ) ventilation_type = st.selectbox( "Ventilation Type", ["Natural", "Mechanical"], index=["Natural", "Mechanical"].index(editor_state.get("ventilation_type", "Natural")) if editor_state.get("ventilation_type") in ["Natural", "Mechanical"] else 0, help="Select whether the ventilation is natural or mechanical." ) fan_pressure_rise = 0.0 fan_efficiency = 0.0 if ventilation_type == "Mechanical": fan_pressure_rise = st.number_input( "Fan Pressure Rise (Pa)", min_value=0.0, max_value=1000.0, value=float(editor_state.get("fan_pressure_rise", 200.0)), format="%.2f", help="Fan pressure rise in Pascals for power calculation." ) fan_efficiency = st.number_input( "Fan Efficiency (0–1)", min_value=0.0, max_value=1.0, value=float(editor_state.get("fan_efficiency", 0.7)), format="%.2f", help="Fan efficiency as a fraction between 0 and 1." ) sensible_effectiveness = 0.0 latent_effectiveness = 0.0 elif system_type == "Wind and Stack Open Area": opening_effectiveness = st.number_input( "Opening Effectiveness (%)", min_value=0.0, max_value=100.0, value=float(editor_state.get("opening_effectiveness", 50.0)), format="%.2f", help="Effectiveness of the opening for ventilation (0–100%)." ) design_flow_rate = 0.0 ventilation_type = "" fan_pressure_rise = 0.0 fan_efficiency = 0.0 sensible_effectiveness = 0.0 latent_effectiveness = 0.0 elif system_type == "Balanced Flow": design_flow_rate = st.number_input( "Design Flow Rate (L/s·m²)", min_value=0.0, max_value=50.0, value=float(editor_state.get("design_flow_rate", default_ventilation_rate)), format="%.2f", help="Balanced supply and exhaust flow rate in liters per second per square meter." ) fan_pressure_rise = st.number_input( "Fan Pressure Rise (Pa)", min_value=0.0, max_value=1000.0, value=float(editor_state.get("fan_pressure_rise", 200.0)), format="%.2f", help="Fan pressure rise in Pascals for power calculation." ) fan_efficiency = st.number_input( "Fan Total Efficiency (0–1)", min_value=0.0, max_value=1.0, value=float(editor_state.get("fan_efficiency", 0.7)), format="%.2f", help="Total fan efficiency as a fraction between 0 and 1." ) ventilation_type = "" sensible_effectiveness = 0.0 latent_effectiveness = 0.0 elif system_type == "Heat Recovery": design_flow_rate = st.number_input( "Design Flow Rate (L/s·m²)", min_value=0.0, max_value=50.0, value=float(editor_state.get("design_flow_rate", default_ventilation_rate)), format="%.2f", help="Balanced supply and exhaust flow rate in liters per second per square meter." ) fan_pressure_rise = st.number_input( "Fan Pressure Rise (Pa)", min_value=0.0, max_value=1000.0, value=float(editor_state.get("fan_pressure_rise", 200.0)), format="%.2f", help="Fan pressure rise in Pascals for power calculation." ) fan_efficiency = st.number_input( "Fan Total Efficiency (0–1)", min_value=0.0, max_value=1.0, value=float(editor_state.get("fan_efficiency", 0.7)), format="%.2f", help="Total fan efficiency as a fraction between 0 and 1." ) sensible_effectiveness = st.number_input( "Sensible Effectiveness (0–1)", min_value=0.0, max_value=1.0, value=float(editor_state.get("sensible_effectiveness", 0.5)), format="%.2f", help="Sensible heat recovery effectiveness as a fraction between 0 and 1." ) latent_effectiveness = st.number_input( "Latent Effectiveness (0–1)", min_value=0.0, max_value=1.0, value=float(editor_state.get("latent_effectiveness", 0.5)), format="%.2f", help="Latent heat recovery effectiveness as a fraction between 0 and 1." ) ventilation_type = "" # Submit buttons col1, col2 = st.columns(2) with col1: submit_label = "Update" if is_edit else "Add" submit = st.form_submit_button(f"{submit_label} Ventilation System") with col2: refresh = st.form_submit_button("Refresh") # Handle form submission if submit: # Validate inputs if not name.strip(): st.error("System name is required.") return # Check for unique name existing_names = [system["name"] for system in ventilation_systems if not (is_edit and system["name"] == editor_state.get("name"))] if name.strip() in existing_names: st.error("System name must be unique.") return # Create ventilation data ventilation_data = { "id": str(uuid.uuid4()), "name": name.strip(), "system_type": system_type, "area": area, "schedule": schedule, "design_flow_rate": design_flow_rate, "ventilation_type": ventilation_type, "fan_pressure_rise": fan_pressure_rise, "fan_efficiency": fan_efficiency, "opening_effectiveness": opening_effectiveness if system_type == "Wind and Stack Open Area" else 0.0, "sensible_effectiveness": sensible_effectiveness, "latent_effectiveness": latent_effectiveness } # Handle edit mode if is_edit: index = editor_state.get("index", 0) st.session_state.project_data["internal_loads"]["ventilation"][index] = ventilation_data st.success(f"Ventilation System '{name}' updated!") else: st.session_state.project_data["internal_loads"]["ventilation"].append(ventilation_data) st.success(f"Ventilation System '{name}' added!") # Clear editor state st.session_state.ventilation_editor = {} st.session_state.ventilation_action = {"action": "save", "id": str(uuid.uuid4())} st.session_state.internal_loads_rerun_pending = True elif refresh: # Clear editor state st.session_state.ventilation_editor = {} st.session_state.ventilation_action = {"action": "refresh", "id": str(uuid.uuid4())} st.session_state.internal_loads_rerun_pending = True def display_infiltration_tab(): """Display the infiltration tab content with a two-column layout.""" # Get infiltration systems from session state infiltration_systems = st.session_state.project_data["internal_loads"].setdefault("infiltration", []) # Split the display into two columns col1, col2 = st.columns([3, 2]) with col1: st.subheader("Saved Infiltration Systems") if infiltration_systems: display_infiltration_table(infiltration_systems) else: st.write("No infiltration systems defined.") with col2: st.subheader("Infiltration System Editor/Creator") # Initialize editor and action states if "infiltration_editor" not in st.session_state: st.session_state.infiltration_editor = {} if "infiltration_action" not in st.session_state: st.session_state.infiltration_action = {"action": None, "id": None} # Get building type for default values building_type = st.session_state.project_data["building_info"].get("building_type") default_building_data = DEFAULT_BUILDING_INTERNALS.get(building_type, DEFAULT_BUILDING_INTERNALS["Other"]) default_air_change_rate = default_building_data.get("air_change_rate", 0.3) # ACH # System type selection (outside form) system_type = st.selectbox( "System Type", ["AirChanges/Hour", "Effective Leakage Area", "Flow Coefficient"], key="infiltration_system_type", help="Select the type of infiltration system." ) # Display the editor form with st.form("infiltration_editor_form", clear_on_submit=True): editor_state = st.session_state.get("infiltration_editor", {}) is_edit = editor_state.get("is_edit", False) # System name name = st.text_input( "System Name", value=editor_state.get("name", ""), help="Enter a unique name for this infiltration system." ) # Area (required for all types) area = st.number_input( "Area (m²)", min_value=1.0, max_value=100000.0, value=float(editor_state.get("area", st.session_state.project_data["building_info"].get("floor_area", 100.0))), format="%.2f", help="Floor area served by this system." ) # Schedule selection available_schedules = list(st.session_state.project_data["internal_loads"]["schedules"].keys()) schedule = st.selectbox( "Schedule", available_schedules, index=available_schedules.index(editor_state.get("schedule", available_schedules[0])) if editor_state.get("schedule") in available_schedules else 0, help="Select the operation schedule for this system." ) # Type-specific inputs if system_type == "AirChanges/Hour": design_flow_rate = st.number_input( "Design Flow Rate (ACH)", min_value=0.0, max_value=10.0, value=float(editor_state.get("design_flow_rate", default_air_change_rate)), format="%.2f", help="Air change rate in air changes per hour." ) effective_air_leakage_area = 0.0 stack_coefficient = 0.0 wind_coefficient = 0.0 flow_coefficient = 0.0 pressure_exponent = 0.0 elif system_type == "Effective Leakage Area": effective_air_leakage_area = st.number_input( "Effective Air Leakage Area (cm²)", min_value=0.0, max_value=10000.0, value=float(editor_state.get("effective_air_leakage_area", 100.0)), format="%.2f", help="Effective air leakage area in square centimeters at 4 Pa or 10 Pa." ) stack_coefficient = st.number_input( "Stack Coefficient", min_value=0.0, max_value=1.0, value=float(editor_state.get("stack_coefficient", 0.0001)), format="%.6f", help="Stack coefficient from standards or measurements." ) wind_coefficient = st.number_input( "Wind Coefficient", min_value=0.0, max_value=1.0, value=float(editor_state.get("wind_coefficient", 0.0001)), format="%.6f", help="Wind coefficient from standards or measurements." ) design_flow_rate = 0.0 flow_coefficient = 0.0 pressure_exponent = 0.0 elif system_type == "Flow Coefficient": flow_coefficient = st.number_input( "Flow Coefficient (m³/s·Paⁿ)", min_value=0.0, max_value=1.0, value=float(editor_state.get("flow_coefficient", 0.0001)), format="%.6f", help="Airflow at reference pressure in cubic meters per second per Pascal raised to the pressure exponent." ) pressure_exponent = st.number_input( "Pressure Exponent", min_value=0.0, max_value=1.0, value=float(editor_state.get("pressure_exponent", 0.6)), format="%.2f", help="Pressure exponent, typically 0.6." ) stack_coefficient = st.number_input( "Stack Coefficient", min_value=0.0, max_value=1.0, value=float(editor_state.get("stack_coefficient", 0.0001)), format="%.6f", help="Stack coefficient from standards or measurements." ) wind_coefficient = st.number_input( "Wind Coefficient", min_value=0.0, max_value=1.0, value=float(editor_state.get("wind_coefficient", 0.0001)), format="%.6f", help="Wind coefficient from standards or measurements." ) design_flow_rate = 0.0 effective_air_leakage_area = 0.0 # Submit buttons col1, col2 = st.columns(2) with col1: submit_label = "Update" if is_edit else "Add" submit = st.form_submit_button(f"{submit_label} Infiltration System") with col2: refresh = st.form_submit_button("Refresh") # Handle form submission if submit: # Validate inputs if not name.strip(): st.error("System name is required.") return # Check for unique name existing_names = [system["name"] for system in infiltration_systems if not (is_edit and system["name"] == editor_state.get("name"))] if name.strip() in existing_names: st.error("System name must be unique.") return # Create infiltration data infiltration_data = { "id": str(uuid.uuid4()), "name": name.strip(), "system_type": system_type, "area": area, "schedule": schedule, "design_flow_rate": design_flow_rate, "effective_air_leakage_area": effective_air_leakage_area, "stack_coefficient": stack_coefficient, "wind_coefficient": wind_coefficient, "flow_coefficient": flow_coefficient, "pressure_exponent": pressure_exponent } # Handle edit mode if is_edit: index = editor_state.get("index", 0) st.session_state.project_data["internal_loads"]["infiltration"][index] = infiltration_data st.success(f"Infiltration System '{name}' updated!") else: st.session_state.project_data["internal_loads"]["infiltration"].append(infiltration_data) st.success(f"Infiltration System '{name}' added!") # Clear editor state st.session_state.infiltration_editor = {} st.session_state.infiltration_action = {"action": "save", "id": str(uuid.uuid4())} st.session_state.internal_loads_rerun_pending = True elif refresh: # Clear editor state st.session_state.infiltration_editor = {} st.session_state.infiltration_action = {"action": "refresh", "id": str(uuid.uuid4())} st.session_state.internal_loads_rerun_pending = True def display_schedules_tab(): st.subheader("Schedules Editor") DEFAULT_STATE = { "name": "", "description": "", "template": "None", "weekday": [0.0] * 24, "weekend": [0.0] * 24, "is_edit": False, "original_name": "" } # Initialize schedule_editor and schedule_action if not present if "schedule_editor" not in st.session_state: st.session_state.schedule_editor = DEFAULT_STATE.copy() if "schedule_action" not in st.session_state: st.session_state.schedule_action = {"action": None, "id": None} editor_state = st.session_state.schedule_editor schedules = st.session_state.project_data["internal_loads"]["schedules"] # Get building type and map to schedule type for default template building_type = st.session_state.project_data["building_info"].get("building_type", "Other") schedule_type = DEFAULT_BUILDING_INTERNALS.get(building_type, DEFAULT_BUILDING_INTERNALS["Other"])["schedule_type"] default_template = schedule_type if schedule_type in DEFAULT_SCHEDULE_TEMPLATES else "None" # Handle template change template_options = list(DEFAULT_SCHEDULE_TEMPLATES.keys()) selected_template = st.selectbox( "Select Template", options=["None"] + template_options, index=template_options.index(editor_state.get("template", default_template)) + 1 if editor_state.get("template") in template_options else template_options.index(default_template) + 1 if default_template in template_options else 0, help="Choose a base schedule to prefill values." ) # Update sliders only if template changes and not in edit mode if selected_template != editor_state.get("template", "None") and not editor_state.get("is_edit"): st.session_state.schedule_editor["template"] = selected_template if selected_template != "None": tpl = DEFAULT_SCHEDULE_TEMPLATES[selected_template] st.session_state.schedule_editor["weekday"] = tpl["weekday"] st.session_state.schedule_editor["weekend"] = tpl["weekend"] for hour in range(24): st.session_state[f"weekday_slider_{hour}_value"] = tpl["weekday"][hour] st.session_state[f"weekend_slider_{hour}_value"] = tpl["weekend"][hour] else: for hour in range(24): st.session_state[f"weekday_slider_{hour}_value"] = 0.0 st.session_state[f"weekend_slider_{hour}_value"] = 0.0 st.session_state.internal_loads_rerun_pending = True st.rerun() # UI FORM for name/description and actions with st.form("schedule_form"): name = st.text_input("Schedule Name", value=editor_state.get("name", "")) description = st.text_area("Description", value=editor_state.get("description", "")) # SLIDERS LAYOUT st.markdown("### Schedule Sliders") col_hour, col_wd, col_we = st.columns([0.4, 2.0, 2.0]) with col_hour: st.markdown("
Hour
", unsafe_allow_html=True) with col_wd: st.markdown("
Weekday
", unsafe_allow_html=True) with col_we: st.markdown("
Weekend
", unsafe_allow_html=True) hide_elements = """ """ st.markdown(hide_elements, unsafe_allow_html=True) weekday_values = [] weekend_values = [] for hour in range(24): col_hour, col_wd, col_we = st.columns([0.4, 2.0, 2.0]) with col_hour: st.markdown(f"
{hour:02d}
", unsafe_allow_html=True) with col_wd: val = st.slider( label=f"Weekday {hour:02d}", key=f"weekday_slider_{hour}", min_value=0.0, max_value=1.0, step=0.1, value=st.session_state.get(f"weekday_slider_{hour}_value", 0.0), label_visibility="collapsed", format=None ) weekday_values.append(val) with col_we: val = st.slider( label=f"Weekend {hour:02d}", key=f"weekend_slider_{hour}", min_value=0.0, max_value=1.0, step=0.1, value=st.session_state.get(f"weekend_slider_{hour}_value", 0.0), label_visibility="collapsed", format=None ) weekend_values.append(val) # Action Buttons col1, col2 = st.columns(2) with col1: submit_label = "Update Schedule" if editor_state.get("is_edit") else "Save Schedule" submitted = st.form_submit_button(submit_label) with col2: refresh = st.form_submit_button("Refresh") # Save logic if submitted: if not name.strip(): st.error("Schedule name is required.") return if name in schedules and not editor_state.get("is_edit"): st.error("A schedule with this name already exists.") return schedules[name] = { "description": description, "weekday": weekday_values, "weekend": weekend_values } if editor_state.get("is_edit") and editor_state.get("original_name") and editor_state.get("original_name") != name: if editor_state["original_name"] in schedules: del schedules[editor_state["original_name"]] st.session_state.schedule_editor = DEFAULT_STATE.copy() for hour in range(24): st.session_state[f"weekday_slider_{hour}_value"] = 0.0 st.session_state[f"weekend_slider_{hour}_value"] = 0.0 st.success(f"Schedule '{name}' saved successfully.") st.session_state.schedule_action = {"action": "save", "id": str(uuid.uuid4())} st.session_state.internal_loads_rerun_pending = True # Refresh logic if refresh: st.session_state.schedule_editor = DEFAULT_STATE.copy() for hour in range(24): st.session_state[f"weekday_slider_{hour}_value"] = 0.0 st.session_state[f"weekend_slider_{hour}_value"] = 0.0 st.session_state.schedule_action = {"action": "refresh", "id": str(uuid.uuid4())} st.session_state.internal_loads_rerun_pending = True # Display saved schedules in a table st.subheader("Saved Schedules") if schedules: # Create column headers cols = st.columns([2, 3, 1, 1, 1, 1]) cols[0].write("**Name**") cols[1].write("**Description**") cols[2].write("**Weekday Peak**") cols[3].write("**Weekend Peak**") cols[4].write("**Edit**") cols[5].write("**Delete**") # Display each schedule for idx, (name, schedule) in enumerate(schedules.items()): cols = st.columns([2, 3, 1, 1, 1, 1]) cols[0].write(name) cols[1].write(schedule.get("description", "No description")) cols[2].write(f"{max(schedule.get('weekday', [0])):.2f}") cols[3].write(f"{max(schedule.get('weekend', [0])):.2f}") # Edit button edit_key = f"edit_schedule_{name}_{idx}" with cols[4].container(): if st.button("Edit", key=edit_key): if name in DEFAULT_SCHEDULE_TEMPLATES: st.error("Default schedules cannot be edited.") else: schedule_data = schedules[name] st.session_state.schedule_editor = { "is_edit": True, "original_name": name, "name": name, "description": schedule_data.get("description", ""), "weekday": schedule_data.get("weekday", [0.0] * 24), "weekend": schedule_data.get("weekend", [0.0] * 24), "template": "None" } for hour in range(24): st.session_state[f"weekday_slider_{hour}_value"] = schedule_data.get("weekday", [0.0] * 24)[hour] st.session_state[f"weekend_slider_{hour}_value"] = schedule_data.get("weekend", [0.0] * 24)[hour] st.session_state.schedule_action = {"action": "edit", "id": str(uuid.uuid4())} st.session_state.internal_loads_rerun_pending = True # Delete button delete_key = f"delete_schedule_{name}_{idx}" with cols[5].container(): if name in DEFAULT_SCHEDULE_TEMPLATES: cols[5].write("(Default)") elif is_schedule_in_use(name): cols[5].write("(In Use)") else: if st.button("Delete", key=delete_key): del schedules[name] st.session_state.schedule_editor = DEFAULT_STATE.copy() for hour in range(24): st.session_state[f"weekday_slider_{hour}_value"] = 0.0 st.session_state[f"weekend_slider_{hour}_value"] = 0.0 st.success(f"Schedule '{name}' deleted!") st.session_state.schedule_action = {"action": "delete", "id": str(uuid.uuid4())} st.session_state.internal_loads_rerun_pending = True else: st.write("No schedules defined.") def is_schedule_in_use(schedule_name: str) -> bool: """ Check if a schedule is in use by any people, lighting, equipment, or ventilation/infiltration systems. Args: schedule_name: Name of the schedule to check. Returns: bool: True if the schedule is in use, False otherwise. """ internal_loads = st.session_state.project_data["internal_loads"] for group in internal_loads.get("people", []): if group.get("schedule") == schedule_name: return True for system in internal_loads.get("lighting", []): if system.get("schedule") == schedule_name: return True for system in internal_loads.get("equipment", []): if system.get("schedule") == schedule_name: return True for system in internal_loads.get("ventilation_infiltration", []): if system.get("schedule") == schedule_name: return True return False def display_people_table(people_groups: List[Dict[str, Any]]): """Display people groups in a table format with edit/delete buttons.""" # Create column headers cols = st.columns([2, 1, 1, 1, 1, 1]) cols[0].write("**Name**") cols[1].write("**Number**") cols[2].write("**Sensible (W)**") cols[3].write("**Latent (W)**") cols[4].write("**Edit**") cols[5].write("**Delete**") # Display each group for idx, group in enumerate(people_groups): cols = st.columns([2, 1, 1, 1, 1, 1]) cols[0].write(group["name"]) cols[1].write(str(group.get("num_people", 0))) cols[2].write(f"{group.get('total_sensible_heat', 0):.1f}") cols[3].write(f"{group.get('total_latent_heat', 0):.1f}") # Edit button edit_key = f"edit_people_{group['name']}_{idx}" with cols[4].container(): if st.button("Edit", key=edit_key): st.session_state.people_editor = { "index": idx, "name": group.get("name", ""), "num_people": group.get("num_people", 10), "activity_level": group.get("activity_level", list(PEOPLE_ACTIVITY_LEVELS.keys())[0]), "clo_summer": group.get("clo_summer", 0.5), "clo_winter": group.get("clo_winter", 1.0), "schedule": group.get("schedule", "Continuous"), "is_edit": True } st.session_state.people_action = {"action": "edit", "id": str(uuid.uuid4())} st.session_state.internal_loads_rerun_pending = True # Delete button delete_key = f"delete_people_{group['name']}_{idx}" with cols[5].container(): if st.button("Delete", key=delete_key): st.session_state.project_data["internal_loads"]["people"].pop(idx) st.success(f"People Group '{group['name']}' deleted!") st.session_state.people_action = {"action": "delete", "id": str(uuid.uuid4())} st.session_state.internal_loads_rerun_pending = True def display_lighting_table(lighting_systems: List[Dict[str, Any]]): """Display lighting systems in a table format with edit/delete buttons.""" # Create column headers cols = st.columns([2, 1, 1, 1, 1, 1]) cols[0].write("**Name**") cols[1].write("**Area (m²)**") cols[2].write("**LPD (W/m²)**") cols[3].write("**Total Power (W)**") cols[4].write("**Edit**") cols[5].write("**Delete**") # Display each system for idx, system in enumerate(lighting_systems): cols = st.columns([2, 1, 1, 1, 1, 1]) cols[0].write(system["name"]) cols[1].write(f"{system.get('area', 0):.1f}") cols[2].write(f"{system.get('lpd', 0):.2f}") cols[3].write(f"{system.get('total_power', 0):.1f}") # Edit button edit_key = f"edit_lighting_{system['name']}_{idx}" with cols[4].container(): if st.button("Edit", key=edit_key): st.session_state.lighting_editor = { "index": idx, "name": system.get("name", ""), "area": system.get("area", 100.0), "lpd": system.get("lpd", 12.0), "lighting_type": system.get("lighting_type", list(LIGHTING_FIXTURE_TYPES.keys())[0]), "schedule": system.get("schedule", "Continuous"), "is_edit": True } st.session_state.lighting_action = {"action": "edit", "id": str(uuid.uuid4())} st.session_state.internal_loads_rerun_pending = True # Delete button delete_key = f"delete_lighting_{system['name']}_{idx}" with cols[5].container(): if st.button("Delete", key=delete_key): st.session_state.project_data["internal_loads"]["lighting"].pop(idx) st.success(f"Lighting System '{system['name']}' deleted!") st.session_state.lighting_action = {"action": "delete", "id": str(uuid.uuid4())} st.session_state.internal_loads_rerun_pending = True def display_equipment_table(equipment_systems: List[Dict[str, Any]]): """Display equipment systems in a table format with edit/delete buttons.""" # Create column headers cols = st.columns([2, 1, 1, 1, 1, 1]) cols[0].write("**Name**") cols[1].write("**Area (m²)**") cols[2].write("**Sensible (W)**") cols[3].write("**Latent (W)**") cols[4].write("**Edit**") cols[5].write("**Delete**") # Display each system for idx, system in enumerate(equipment_systems): cols = st.columns([2, 1, 1, 1, 1, 1]) cols[0].write(system["name"]) cols[1].write(f"{system.get('area', 0):.1f}") cols[2].write(f"{system.get('total_sensible_power', 0):.1f}") cols[3].write(f"{system.get('total_latent_power', 0):.1f}") # Edit button edit_key = f"edit_equipment_{system['name']}_{idx}" with cols[4].container(): if st.button("Edit", key=edit_key): st.session_state.equipment_editor = { "index": idx, "name": system.get("name", ""), "area": system.get("area", 100.0), "sensible_gain": system.get("sensible_gain", 10.0), "latent_gain": system.get("latent_gain", 2.0), "radiative_fraction": system.get("radiative_fraction", 0.5), "convective_fraction": system.get("convective_fraction", 0.5), "schedule": system.get("schedule", "Continuous"), "is_edit": True } st.session_state.equipment_action = {"action": "edit", "id": str(uuid.uuid4())} st.session_state.internal_loads_rerun_pending = True # Delete button delete_key = f"delete_equipment_{system['name']}_{idx}" with cols[5].container(): if st.button("Delete", key=delete_key): st.session_state.project_data["internal_loads"]["equipment"].pop(idx) st.success(f"Equipment '{system['name']}' deleted!") st.session_state.equipment_action = {"action": "delete", "id": str(uuid.uuid4())} st.session_state.internal_loads_rerun_pending = True def display_ventilation_table(ventilation_systems: List[Dict[str, Any]]): """Display ventilation systems in a table format with edit/delete buttons.""" # Create column headers cols = st.columns([2, 1, 1, 1, 1, 1]) cols[0].write("**Name**") cols[1].write("**Type**") cols[2].write("**Area (m²)**") cols[3].write("**Rate**") cols[4].write("**Edit**") cols[5].write("**Delete**") # Display each system for idx, system in enumerate(ventilation_systems): cols = st.columns([2, 1, 1, 1, 1, 1]) cols[0].write(system["name"]) cols[1].write(system.get("system_type", "Unknown")) cols[2].write(f"{system.get('area', 0):.1f}") if system.get("system_type") in ["AirChanges/Hour", "Balanced Flow", "Heat Recovery"]: rate_info = f"{system.get('design_flow_rate', 0):.2f} L/s·m²" else: rate_info = f"{system.get('opening_effectiveness', 0):.2f} %" cols[3].write(rate_info) # Edit button edit_key = f"edit_ventilation_{system['name']}_{idx}" with cols[4].container(): if st.button("Edit", key=edit_key): st.session_state.ventilation_editor = { "index": idx, "name": system.get("name", ""), "system_type": system.get("system_type", "AirChanges/Hour"), "area": system.get("area", 100.0), "schedule": system.get("schedule", "Continuous"), "design_flow_rate": system.get("design_flow_rate", 1.0), "ventilation_type": system.get("ventilation_type", "Natural"), "fan_pressure_rise": system.get("fan_pressure_rise", 200.0), "fan_efficiency": system.get("fan_efficiency", 0.7), "opening_effectiveness": system.get("opening_effectiveness", 50.0), "sensible_effectiveness": system.get("sensible_effectiveness", 0.8), "latent_effectiveness": system.get("latent_effectiveness", 0.6), "is_edit": True } st.session_state.ventilation_action = {"action": "edit", "id": str(uuid.uuid4())} st.session_state.internal_loads_rerun_pending = True # Delete button delete_key = f"delete_ventilation_{system['name']}_{idx}" with cols[5].container(): if st.button("Delete", key=delete_key): st.session_state.project_data["internal_loads"]["ventilation"].pop(idx) st.success(f"Ventilation System '{system['name']}' deleted!") st.session_state.ventilation_action = {"action": "delete", "id": str(uuid.uuid4())} st.session_state.internal_loads_rerun_pending = True def display_infiltration_table(infiltration_systems: List[Dict[str, Any]]): """Display infiltration systems in a table format with edit/delete buttons.""" # Create column headers cols = st.columns([2, 1, 1, 1, 1, 1]) cols[0].write("**Name**") cols[1].write("**Type**") cols[2].write("**Area (m²)**") cols[3].write("**Rate**") cols[4].write("**Edit**") cols[5].write("**Delete**") # Display each system for idx, system in enumerate(infiltration_systems): cols = st.columns([2, 1, 1, 1, 1, 1]) cols[0].write(system["name"]) cols[1].write(system.get("system_type", "Unknown")) cols[2].write(f"{system.get('area', 0):.1f}") if system.get("system_type") == "AirChanges/Hour": rate_info = f"{system.get('design_flow_rate', 0):.2f} ACH" elif system.get("system_type") == "Effective Leakage Area": rate_info = f"{system.get('effective_air_leakage_area', 0):.2f} cm²" else: rate_info = f"{system.get('flow_coefficient', 0):.6f} m³/s·Paⁿ" cols[3].write(rate_info) # Edit button edit_key = f"edit_infiltration_{system['name']}_{idx}" with cols[4].container(): if st.button("Edit", key=edit_key): st.session_state.infiltration_editor = { "index": idx, "name": system.get("name", ""), "system_type": system.get("system_type", "AirChanges/Hour"), "area": system.get("area", 100.0), "schedule": system.get("schedule", "Continuous"), "design_flow_rate": system.get("design_flow_rate", 0.3), "effective_air_leakage_area": system.get("effective_air_leakage_area", 100.0), "stack_coefficient": system.get("stack_coefficient", 0.0001), "wind_coefficient": system.get("wind_coefficient", 0.0001), "flow_coefficient": system.get("flow_coefficient", 0.0001), "pressure_exponent": system.get("pressure_exponent", 0.6), "is_edit": True } st.session_state.infiltration_action = {"action": "edit", "id": str(uuid.uuid4())} st.session_state.internal_loads_rerun_pending = True # Delete button delete_key = f"delete_infiltration_{system['name']}_{idx}" with cols[5].container(): if st.button("Delete", key=delete_key): st.session_state.project_data["internal_loads"]["infiltration"].pop(idx) st.success(f"Infiltration System '{system['name']}' deleted!") st.session_state.infiltration_action = {"action": "delete", "id": str(uuid.uuid4())} st.session_state.internal_loads_rerun_pending = True def display_schedules_table(schedules: Dict[str, Any]): """Display schedules in a table format with edit/delete buttons.""" if not schedules: return # Create table data table_data = [] for name, schedule in schedules.items(): weekday_peak = max(schedule.get("weekday", [0])) weekend_peak = max(schedule.get("weekend", [0])) table_data.append({ "Name": name, "Description": schedule.get("description", "No description"), "Weekday Peak": f"{weekday_peak:.2f}", "Weekend Peak": f"{weekend_peak:.2f}", "Actions": name }) if table_data: df = pd.DataFrame(table_data) # Display table st.dataframe(df.drop('Actions', axis=1), use_container_width=True) # Display action buttons st.write("**Actions:**") for i, row in enumerate(table_data): schedule_name = row["Actions"] col1, col2, col3, col4 = st.columns([2, 1, 1, 1]) with col1: st.write(f"{i+1}. {schedule_name}") with col2: if st.button("View", key=f"view_schedule_{schedule_name}_{i}"): schedule_data = schedules[schedule_name] display_schedule_chart(schedule_name, schedule_data) with col3: if st.button("Edit", key=f"edit_schedule_{schedule_name}_{i}"): schedule_data = schedules[schedule_name] st.session_state.schedule_editor = { "is_edit": True, "original_name": schedule_name, "name": schedule_name, "description": schedule_data.get("description", ""), "weekday": schedule_data.get("weekday", [0.0] * 24), "weekend": schedule_data.get("weekend", [0.0] * 24) } st.session_state.schedule_action = {"action": "edit", "id": str(uuid.uuid4())} st.session_state.internal_loads_rerun_pending = True with col4: if schedule_name not in DEFAULT_SCHEDULE_TEMPLATES: if is_schedule_in_use(schedule_name): st.write("(In Use)") else: if st.button("Delete", key=f"delete_schedule_{schedule_name}_{i}"): del schedules[schedule_name] st.success(f"Schedule '{schedule_name}' deleted!") st.session_state.schedule_action = {"action": "delete", "id": str(uuid.uuid4())} st.session_state.internal_loads_rerun_pending = True else: st.write("(Default)")