Spaces:
Sleeping
Sleeping
| """ | |
| Enhanced HVAC Component Selection Module | |
| Provides UI for selecting building components in the HVAC Load Calculator. | |
| All dependencies are included within this file for standalone operation. | |
| Updated 2025-04-28: Added surface color selection, expanded component types, and skylight support. | |
| Updated 2025-05-02: Added crack dimensions for walls/doors, drapery properties for windows/skylights, and roof height per enhancement plan. | |
| Updated 2025-05-03: Removed surface_color field, using solar absorptivity (Light 0.3, Light to Medium 0.45, Medium 0.6, Medium to Dark 0.75, Dark 0.9) exclusively. | |
| Updated 2025-05-09: Changed 'absorptivity' to 'solar_absorptivity' to align with building_components.py. | |
| Author: Dr Majed Abuseif | |
| """ | |
| import streamlit as st | |
| import pandas as pd | |
| import numpy as np | |
| import json | |
| import uuid | |
| from dataclasses import dataclass, field | |
| from enum import Enum | |
| from typing import Dict, List, Any, Optional | |
| import io | |
| # --- Enums --- | |
| class Orientation(Enum): | |
| NORTH = "North" | |
| NORTHEAST = "Northeast" | |
| EAST = "East" | |
| SOUTHEAST = "Southeast" | |
| SOUTH = "South" | |
| SOUTHWEST = "Southwest" | |
| WEST = "West" | |
| NORTHWEST = "Northwest" | |
| HORIZONTAL = "Horizontal" | |
| NOT_APPLICABLE = "N/A" | |
| class ComponentType(Enum): | |
| WALL = "Wall" | |
| ROOF = "Roof" | |
| FLOOR = "Floor" | |
| WINDOW = "Window" | |
| DOOR = "Door" | |
| SKYLIGHT = "Skylight" | |
| class GlazingType(Enum): | |
| SINGLE_CLEAR = "Single Clear" | |
| SINGLE_TINTED = "Single Tinted" | |
| DOUBLE_CLEAR = "Double Clear" | |
| DOUBLE_TINTED = "Double Tinted" | |
| LOW_E = "Low-E" | |
| REFLECTIVE = "Reflective" | |
| class FrameType(Enum): | |
| ALUMINUM = "Aluminum without Thermal Break" | |
| ALUMINUM_THERMAL_BREAK = "Aluminum with Thermal Break" | |
| VINYL = "Vinyl/Fiberglass" | |
| WOOD = "Wood/Vinyl-Clad Wood" | |
| INSULATED = "Insulated" | |
| class DraperyOpenness(Enum): | |
| CLOSED = "Closed" | |
| SEMI_OPEN = "Semi-Open" | |
| OPEN = "Open" | |
| class DraperyColor(Enum): | |
| LIGHT = "Light" | |
| MEDIUM = "Medium" | |
| DARK = "Dark" | |
| # --- Data Models --- | |
| class MaterialLayer: | |
| name: str | |
| thickness: float # in mm | |
| conductivity: float # W/(m·K) | |
| class BuildingComponent: | |
| id: str = field(default_factory=lambda: str(uuid.uuid4())) | |
| name: str = "Unnamed Component" | |
| component_type: ComponentType = ComponentType.WALL | |
| u_value: float = 0.0 # W/(m²·K) | |
| area: float = 0.0 # m² | |
| orientation: Orientation = Orientation.NOT_APPLICABLE | |
| def __post_init__(self): | |
| if self.area <= 0: | |
| raise ValueError("Area must be greater than zero") | |
| if self.u_value <= 0: | |
| raise ValueError("U-value must be greater than zero") | |
| def to_dict(self) -> dict: | |
| return { | |
| "id": self.id, "name": self.name, "component_type": self.component_type.value, | |
| "u_value": self.u_value, "area": self.area, "orientation": self.orientation.value | |
| } | |
| class Wall(BuildingComponent): | |
| wall_type: str = "Brick" | |
| wall_group: str = "A" # ASHRAE group | |
| solar_absorptivity: float = 0.6 | |
| shading_coefficient: float = 1.0 | |
| infiltration_rate_cfm: float = 0.0 | |
| crack_length: float = 0.0 # Added for infiltration (m) | |
| crack_width: float = 0.0 # Added for infiltration (m) | |
| def __post_init__(self): | |
| super().__post_init__() | |
| self.component_type = ComponentType.WALL | |
| if not 0 <= self.solar_absorptivity <= 1: | |
| raise ValueError("Solar absorptivity must be between 0 and 1") | |
| if not 0 <= self.shading_coefficient <= 1: | |
| raise ValueError("Shading coefficient must be between 0 and 1") | |
| if self.infiltration_rate_cfm < 0: | |
| raise ValueError("Infiltration rate cannot be negative") | |
| if not 0 <= self.crack_length <= 100: | |
| raise ValueError("Crack length must be between 0 and 100 meters") | |
| if not 0 <= self.crack_width <= 0.1: | |
| raise ValueError("Crack width must be between 0 and 0.1 meters") | |
| VALID_WALL_GROUPS = {"A", "B", "C", "D", "E", "F", "G", "H"} | |
| if self.wall_group not in VALID_WALL_GROUPS: | |
| st.warning(f"Invalid wall_group '{self.wall_group}' for wall '{self.name}'. Defaulting to 'A'.") | |
| self.wall_group = "A" | |
| def to_dict(self) -> dict: | |
| base_dict = super().to_dict() | |
| base_dict.update({ | |
| "wall_type": self.wall_type, "wall_group": self.wall_group, "solar_absorptivity": self.solar_absorptivity, | |
| "shading_coefficient": self.shading_coefficient, "infiltration_rate_cfm": self.infiltration_rate_cfm, | |
| "crack_length": self.crack_length, "crack_width": self.crack_width | |
| }) | |
| return base_dict | |
| class Roof(BuildingComponent): | |
| roof_type: str = "Concrete" | |
| roof_group: str = "A" # ASHRAE group | |
| slope: str = "Flat" | |
| solar_absorptivity: float = 0.6 | |
| roof_height: float = 3.0 # Added for stack effect (m) | |
| def __post_init__(self): | |
| super().__post_init__() | |
| self.component_type = ComponentType.ROOF | |
| if not self.orientation == Orientation.HORIZONTAL: | |
| self.orientation = Orientation.HORIZONTAL | |
| if not 0 <= self.solar_absorptivity <= 1: | |
| raise ValueError("Solar absorptivity must be between 0 and 1") | |
| if not 0 <= self.roof_height <= 100: | |
| raise ValueError("Roof height must be between 0 and 100 meters") | |
| VALID_ROOF_GROUPS = {"A", "B", "C", "D", "E", "F", "G"} | |
| if self.roof_group not in VALID_ROOF_GROUPS: | |
| st.warning(f"Invalid roof_group '{self.roof_group}' for roof '{self.name}'. Defaulting to 'A'.") | |
| self.roof_group = "A" | |
| def to_dict(self) -> dict: | |
| base_dict = super().to_dict() | |
| base_dict.update({ | |
| "roof_type": self.roof_type, "roof_group": self.roof_group, "slope": self.slope, | |
| "solar_absorptivity": self.solar_absorptivity, "roof_height": self.roof_height | |
| }) | |
| return base_dict | |
| class Floor(BuildingComponent): | |
| floor_type: str = "Concrete" | |
| ground_contact: bool = True | |
| ground_temperature_c: float = 25.0 | |
| perimeter: float = 0.0 | |
| insulated: bool = False # For dynamic F-factor | |
| def __post_init__(self): | |
| super().__post_init__() | |
| self.component_type = ComponentType.FLOOR | |
| self.orientation = Orientation.NOT_APPLICABLE | |
| if self.perimeter < 0: | |
| raise ValueError("Perimeter cannot be negative") | |
| if self.ground_contact and not (-10 <= self.ground_temperature_c <= 40): | |
| raise ValueError("Ground temperature must be between -10°C and 40°C for ground-contact floors") | |
| def to_dict(self) -> dict: | |
| base_dict = super().to_dict() | |
| base_dict.update({ | |
| "floor_type": self.floor_type, "ground_contact": self.ground_contact, | |
| "ground_temperature_c": self.ground_temperature_c, "perimeter": self.perimeter, | |
| "insulated": self.insulated | |
| }) | |
| return base_dict | |
| class Window(BuildingComponent): | |
| shgc: float = 0.7 | |
| shading_device: str = "None" | |
| shading_coefficient: float = 1.0 | |
| frame_type: str = "Aluminum without Thermal Break" | |
| frame_percentage: float = 20.0 | |
| infiltration_rate_cfm: float = 0.0 | |
| glazing_type: str = "Double Clear" | |
| drapery_openness: str = "Open" | |
| drapery_color: str = "Light" | |
| drapery_fullness: float = 1.5 | |
| def __post_init__(self): | |
| super().__post_init__() | |
| self.component_type = ComponentType.WINDOW | |
| if not 0 <= self.shgc <= 1: | |
| raise ValueError("SHGC must be between 0 and 1") | |
| if not 0 <= self.shading_coefficient <= 1: | |
| raise ValueError("Shading coefficient must be between 0 and 1") | |
| if not 0 <= self.frame_percentage <= 30: | |
| raise ValueError("Frame percentage must be between 0 and 30") | |
| if self.infiltration_rate_cfm < 0: | |
| raise ValueError("Infiltration rate cannot be negative") | |
| VALID_DRAPERY_OPENNESS = {"Closed", "Semi-Open", "Open"} | |
| if self.drapery_openness not in VALID_DRAPERY_OPENNESS: | |
| st.warning(f"Invalid drapery_openness '{self.drapery_openness}' for window '{self.name}'. Defaulting to 'Open'.") | |
| self.drapery_openness = "Open" | |
| VALID_DRAPERY_COLORS = {"Light", "Medium", "Dark"} | |
| if self.drapery_color not in VALID_DRAPERY_COLORS: | |
| st.warning(f"Invalid drapery_color '{self.drapery_color}' for window '{self.name}'. Defaulting to 'Light'.") | |
| self.drapery_color = "Light" | |
| if not 1.0 <= self.drapery_fullness <= 2.0: | |
| raise ValueError("Drapery fullness must be between 1.0 and 2.0") | |
| def to_dict(self) -> dict: | |
| base_dict = super().to_dict() | |
| base_dict.update({ | |
| "shgc": self.shgc, "shading_device": self.shading_device, "shading_coefficient": self.shading_coefficient, | |
| "frame_type": self.frame_type, "frame_percentage": self.frame_percentage, | |
| "infiltration_rate_cfm": self.infiltration_rate_cfm, "glazing_type": self.glazing_type, | |
| "drapery_openness": self.drapery_openness, "drapery_color": self.drapery_color, | |
| "drapery_fullness": self.drapery_fullness | |
| }) | |
| return base_dict | |
| class Door(BuildingComponent): | |
| door_type: str = "Solid Wood" | |
| infiltration_rate_cfm: float = 0.0 | |
| crack_length: float = 0.0 | |
| crack_width: float = 0.0 | |
| def __post_init__(self): | |
| super().__post_init__() | |
| self.component_type = ComponentType.DOOR | |
| if self.infiltration_rate_cfm < 0: | |
| raise ValueError("Infiltration rate cannot be negative") | |
| if not 0 <= self.crack_length <= 100: | |
| raise ValueError("Crack length must be between 0 and 100 meters") | |
| if not 0 <= self.crack_width <= 0.1: | |
| raise ValueError("Crack width must be between 0 and 0.1 meters") | |
| def to_dict(self) -> dict: | |
| base_dict = super().to_dict() | |
| base_dict.update({ | |
| "door_type": self.door_type, "infiltration_rate_cfm": self.infiltration_rate_cfm, | |
| "crack_length": self.crack_length, "crack_width": self.crack_width | |
| }) | |
| return base_dict | |
| class Skylight(BuildingComponent): | |
| shgc: float = 0.7 | |
| shading_device: str = "None" | |
| shading_coefficient: float = 1.0 | |
| frame_type: str = "Aluminum without Thermal Break" | |
| frame_percentage: float = 20.0 | |
| infiltration_rate_cfm: float = 0.0 | |
| glazing_type: str = "Double Clear" | |
| drapery_openness: str = "Open" | |
| drapery_color: str = "Light" | |
| drapery_fullness: float = 1.5 | |
| def __post_init__(self): | |
| super().__post_init__() | |
| self.component_type = ComponentType.SKYLIGHT | |
| self.orientation = Orientation.HORIZONTAL | |
| if not 0 <= self.shgc <= 1: | |
| raise ValueError("SHGC must be between 0 and 1") | |
| if not 0 <= self.shading_coefficient <= 1: | |
| raise ValueError("Shading coefficient must be between 0 and 1") | |
| if not 0 <= self.frame_percentage <= 30: | |
| raise ValueError("Frame percentage must be between 0 and 30") | |
| if self.infiltration_rate_cfm < 0: | |
| raise ValueError("Infiltration rate cannot be negative") | |
| VALID_DRAPERY_OPENNESS = {"Closed", "Semi-Open", "Open"} | |
| if self.drapery_openness not in VALID_DRAPERY_OPENNESS: | |
| st.warning(f"Invalid drapery_openness '{self.drapery_openness}' for skylight '{self.name}'. Defaulting to 'Open'.") | |
| self.drapery_openness = "Open" | |
| VALID_DRAPERY_COLORS = {"Light", "Medium", "Dark"} | |
| if self.drapery_color not in VALID_DRAPERY_COLORS: | |
| st.warning(f"Invalid drapery_color '{self.drapery_color}' for skylight '{self.name}'. Defaulting to 'Light'.") | |
| self.drapery_color = "Light" | |
| if not 1.0 <= self.drapery_fullness <= 2.0: | |
| raise ValueError("Drapery fullness must be between 1.0 and 2.0") | |
| def to_dict(self) -> dict: | |
| base_dict = super().to_dict() | |
| base_dict.update({ | |
| "shgc": self.shgc, "shading_device": self.shading_device, "shading_coefficient": self.shading_coefficient, | |
| "frame_type": self.frame_type, "frame_percentage": self.frame_percentage, | |
| "infiltration_rate_cfm": self.infiltration_rate_cfm, "glazing_type": self.glazing_type, | |
| "drapery_openness": self.drapery_openness, "drapery_color": self.drapery_color, | |
| "drapery_fullness": self.drapery_fullness | |
| }) | |
| return base_dict | |
| # --- Reference Data --- | |
| class ReferenceData: | |
| def __init__(self): | |
| self.data = { | |
| "materials": { | |
| "Concrete": {"conductivity": 1.4}, | |
| "Insulation": {"conductivity": 0.04}, | |
| "Brick": {"conductivity": 0.8}, | |
| "Glass": {"conductivity": 1.0}, | |
| "Wood": {"conductivity": 0.15} | |
| }, | |
| "wall_types": { | |
| "Brick Wall": {"u_value": 2.0, "solar_absorptivity": 0.6, "wall_group": "A"}, | |
| "Insulated Brick": {"u_value": 0.5, "solar_absorptivity": 0.6, "wall_group": "B"}, | |
| "Concrete Block": {"u_value": 1.8, "solar_absorptivity": 0.6, "wall_group": "C"}, | |
| "Insulated Concrete": {"u_value": 0.4, "solar_absorptivity": 0.6, "wall_group": "D"}, | |
| "Timber Frame": {"u_value": 0.3, "solar_absorptivity": 0.6, "wall_group": "E"}, | |
| "Cavity Brick": {"u_value": 0.6, "solar_absorptivity": 0.6, "wall_group": "F"}, | |
| "Lightweight Panel": {"u_value": 1.0, "solar_absorptivity": 0.6, "wall_group": "G"}, | |
| "Reinforced Concrete": {"u_value": 1.5, "solar_absorptivity": 0.6, "wall_group": "H"}, | |
| "SIP": {"u_value": 0.25, "solar_absorptivity": 0.6, "wall_group": "A"}, | |
| "Custom": {"u_value": 0.5, "solar_absorptivity": 0.6, "wall_group": "A"} | |
| }, | |
| "roof_types": { | |
| "Concrete Roof": {"u_value": 0.3, "solar_absorptivity": 0.6, "group": "A"}, | |
| "Metal Roof": {"u_value": 1.0, "solar_absorptivity": 0.75, "group": "B"}, | |
| "Built-up Roof": {"u_value": 0.5, "solar_absorptivity": 0.8, "group": "C"}, | |
| "Insulated Metal Deck": {"u_value": 0.4, "solar_absorptivity": 0.7, "group": "D"}, | |
| "Wood Shingle": {"u_value": 0.6, "solar_absorptivity": 0.5, "group": "E"}, | |
| "Custom": {"u_value": 0.5, "solar_absorptivity": 0.6, "group": "A"} | |
| }, | |
| "roof_ventilation_methods": { | |
| "No Ventilation": 0.0, | |
| "Natural Low": 0.1, | |
| "Natural High": 0.5, | |
| "Mechanical": 1.0 | |
| }, | |
| "floor_types": { | |
| "Concrete Slab": {"u_value": 0.4, "ground_contact": True}, | |
| "Wood Floor": {"u_value": 0.8, "ground_contact": False}, | |
| "Insulated Concrete Slab": {"u_value": 0.2, "ground_contact": True}, | |
| "Raised Floor": {"u_value": 0.5, "ground_contact": False}, | |
| "Tile on Concrete": {"u_value": 0.45, "ground_contact": True}, | |
| "Custom": {"u_value": 0.5, "ground_contact": True} | |
| }, | |
| "window_types": { | |
| "Single Clear": {"u_value": 5.0, "shgc": 0.86, "glazing_type": "Single Clear", "frame_type": "Aluminum without Thermal Break"}, | |
| "Single Tinted": {"u_value": 5.0, "shgc": 0.73, "glazing_type": "Single Tinted", "frame_type": "Aluminum without Thermal Break"}, | |
| "Double Clear": {"u_value": 2.8, "shgc": 0.76, "glazing_type": "Double Clear", "frame_type": "Aluminum with Thermal Break"}, | |
| "Double Tinted": {"u_value": 2.8, "shgc": 0.62, "glazing_type": "Double Tinted", "frame_type": "Aluminum with Thermal Break"}, | |
| "Low-E": {"u_value": 1.8, "shgc": 0.48, "glazing_type": "Low-E", "frame_type": "Vinyl/Fiberglass"}, | |
| "Reflective": {"u_value": 2.0, "shgc": 0.35, "glazing_type": "Reflective", "frame_type": "Aluminum with Thermal Break"}, | |
| "Custom": {"u_value": 2.5, "shgc": 0.7, "glazing_type": "Double Clear", "frame_type": "Aluminum with Thermal Break"} | |
| }, | |
| "shading_devices": { | |
| "None": 1.0, | |
| "Venetian Blinds": 0.6, | |
| "Overhang": 0.4, | |
| "Roller Shades": 0.5, | |
| "Drapes": 0.7, | |
| "Reflective Film": 0.3 | |
| }, | |
| "door_types": { | |
| "Solid Wood": {"u_value": 2.0}, | |
| "Glass Door": {"u_value": 3.5}, | |
| "Metal Door": {"u_value": 3.0}, | |
| "Insulated Metal": {"u_value": 1.2}, | |
| "Insulated Wood": {"u_value": 1.0}, | |
| "Custom": {"u_value": 2.0} | |
| }, | |
| "skylight_types": { | |
| "Single Clear": {"u_value": 5.5, "shgc": 0.83, "glazing_type": "Single Clear", "frame_type": "Aluminum without Thermal Break"}, | |
| "Single Tinted": {"u_value": 5.5, "shgc": 0.70, "glazing_type": "Single Tinted", "frame_type": "Aluminum without Thermal Break"}, | |
| "Double Clear": {"u_value": 3.2, "shgc": 0.70, "glazing_type": "Double Clear", "frame_type": "Aluminum with Thermal Break"}, | |
| "Double Tinted": {"u_value": 3.2, "shgc": 0.58, "glazing_type": "Double Tinted", "frame_type": "Aluminum with Thermal Break"}, | |
| "Low-E": {"u_value": 2.2, "shgc": 0.51, "glazing_type": "Low-E", "frame_type": "Vinyl/Fiberglass"}, | |
| "Reflective": {"u_value": 2.4, "shgc": 0.38, "glazing_type": "Reflective", "frame_type": "Aluminum with Thermal Break"}, | |
| "Custom": {"u_value": 3.0, "shgc": 0.7, "glazing_type": "Double Clear", "frame_type": "Aluminum with Thermal Break"} | |
| }, | |
| "frame_types": { | |
| "Aluminum without Thermal Break": 1.0, | |
| "Aluminum with Thermal Break": 0.8, | |
| "Vinyl/Fiberglass": 0.6, | |
| "Wood/Vinyl-Clad Wood": 0.5, | |
| "Insulated": 0.4 | |
| } | |
| } | |
| def get_materials(self) -> List[Dict[str, Any]]: | |
| return [{"name": k, "conductivity": v["conductivity"]} for k, v in self.data["materials"].items()] | |
| reference_data = ReferenceData() | |
| # --- Component Library --- | |
| class ComponentLibrary: | |
| def __init__(self): | |
| self.components = {} | |
| def add_component(self, component: BuildingComponent): | |
| self.components[component.id] = component | |
| def remove_component(self, component_id: str): | |
| if not component_id.startswith("preset_") and component_id in self.components: | |
| del self.components[component_id] | |
| component_library = ComponentLibrary() | |
| # --- U-Value Calculator --- | |
| class UValueCalculator: | |
| def __init__(self): | |
| self.materials = reference_data.get_materials() | |
| def calculate_u_value(self, layers: List[Dict[str, float]], outside_resistance: float, inside_resistance: float) -> float: | |
| r_layers = sum(layer["thickness"] / 1000 / layer["conductivity"] for layer in layers) | |
| r_total = outside_resistance + r_layers + inside_resistance | |
| return 1 / r_total if r_total > 0 else 0 | |
| u_value_calculator = UValueCalculator() | |
| # --- Component Selection Interface --- | |
| class ComponentSelectionInterface: | |
| def __init__(self): | |
| self.component_library = component_library | |
| self.u_value_calculator = u_value_calculator | |
| self.reference_data = reference_data | |
| def display_component_selection(self, session_state: Any) -> None: | |
| st.title("Building Components") | |
| if 'components' not in session_state: | |
| session_state.components = {'walls': [], 'roofs': [], 'floors': [], 'windows': [], 'doors': [], 'skylights': []} | |
| if 'roof_air_volume_m3' not in session_state: | |
| session_state.roof_air_volume_m3 = 0.0 | |
| if 'roof_ventilation_ach' not in session_state: | |
| session_state.roof_ventilation_ach = 0.0 | |
| tabs = st.tabs(["Walls", "Roofs", "Floors", "Windows", "Doors", "Skylights", "U-Value Calculator"]) | |
| with tabs[0]: | |
| self._display_component_tab(session_state, ComponentType.WALL) | |
| with tabs[1]: | |
| self._display_component_tab(session_state, ComponentType.ROOF) | |
| with tabs[2]: | |
| self._display_component_tab(session_state, ComponentType.FLOOR) | |
| with tabs[3]: | |
| self._display_component_tab(session_state, ComponentType.WINDOW) | |
| with tabs[4]: | |
| self._display_component_tab(session_state, ComponentType.DOOR) | |
| with tabs[5]: | |
| self._display_component_tab(session_state, ComponentType.SKYLIGHT) | |
| with tabs[6]: | |
| self._display_u_value_calculator_tab(session_state) | |
| if st.button("Save Components"): | |
| self._save_components(session_state) | |
| def _display_component_tab(self, session_state: Any, component_type: ComponentType) -> None: | |
| type_name = component_type.value.lower() | |
| st.subheader(f"{type_name.capitalize()} Components") | |
| with st.expander(f"Add {type_name.capitalize()}", expanded=True): | |
| if component_type == ComponentType.WALL: | |
| self._display_add_wall_form(session_state) | |
| elif component_type == ComponentType.ROOF: | |
| self._display_add_roof_form(session_state) | |
| elif component_type == ComponentType.FLOOR: | |
| self._display_add_floor_form(session_state) | |
| elif component_type == ComponentType.WINDOW: | |
| self._display_add_window_form(session_state) | |
| elif component_type == ComponentType.DOOR: | |
| self._display_add_door_form(session_state) | |
| elif component_type == ComponentType.SKYLIGHT: | |
| self._display_add_skylight_form(session_state) | |
| components = session_state.components.get(type_name + 's', []) | |
| if components or component_type == ComponentType.ROOF: | |
| st.subheader(f"Existing {type_name.capitalize()} Components") | |
| self._display_components_table(session_state, component_type, components) | |
| def _display_add_wall_form(self, session_state: Any) -> None: | |
| st.write("Add walls manually or upload a file.") | |
| method = st.radio("Add Wall Method", ["Manual Entry", "File Upload"]) | |
| if "add_wall_submitted" not in session_state: | |
| session_state.add_wall_submitted = False | |
| if method == "Manual Entry": | |
| with st.form("add_wall_form", clear_on_submit=True): | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| name = st.text_input("Name", "New Wall") | |
| area = st.number_input("Area (m²)", min_value=0.0, value=1.0, step=0.1) | |
| orientation = st.selectbox("Orientation", [o.value for o in Orientation if o != Orientation.HORIZONTAL and o != Orientation.NOT_APPLICABLE], index=0) | |
| crack_length = st.number_input("Crack Length (m)", min_value=0.0, max_value=100.0, value=0.0, step=0.1) | |
| with col2: | |
| wall_options = self.reference_data.data["wall_types"] | |
| selected_wall = st.selectbox("Wall Type", options=list(wall_options.keys())) | |
| u_value = st.number_input("U-Value (W/m²·K)", min_value=0.0, value=float(wall_options[selected_wall]["u_value"]), step=0.01) | |
| wall_group = st.selectbox("Wall Group (ASHRAE)", ["A", "B", "C", "D", "E", "F", "G", "H"], index=0) | |
| solar_absorptivity = st.selectbox("Solar Absorptivity", ["Light (0.3)", "Light to Medium (0.45)", "Medium (0.6)", "Medium to Dark (0.75)", "Dark (0.9)"], index=2) | |
| shading_coefficient = st.number_input("Shading Coefficient", min_value=0.0, max_value=1.0, value=1.0, step=0.05) | |
| infiltration_rate = st.number_input("Infiltration Rate (CFM)", min_value=0.0, value=0.0, step=0.1) | |
| crack_width = st.number_input("Crack Width (m)", min_value=0.0, max_value=0.1, value=0.0, step=0.001) | |
| submitted = st.form_submit_button("Add Wall") | |
| if submitted and not session_state.add_wall_submitted: | |
| try: | |
| solar_absorptivity_value = float(solar_absorptivity.split("(")[1].strip(")")) | |
| new_wall = Wall( | |
| name=name, u_value=u_value, area=area, orientation=Orientation(orientation), | |
| wall_type=selected_wall, wall_group=wall_group, solar_absorptivity=solar_absorptivity_value, | |
| shading_coefficient=shading_coefficient, infiltration_rate_cfm=infiltration_rate, | |
| crack_length=crack_length, crack_width=crack_width | |
| ) | |
| self.component_library.add_component(new_wall) | |
| session_state.components['walls'].append(new_wall) | |
| st.success(f"Added {new_wall.name}") | |
| session_state.add_wall_submitted = True | |
| st.rerun() | |
| except ValueError as e: | |
| st.error(f"Error: {str(e)}") | |
| if session_state.add_wall_submitted: | |
| session_state.add_wall_submitted = False | |
| elif method == "File Upload": | |
| uploaded_file = st.file_uploader("Upload Walls File", type=["csv", "xlsx"], key="wall_upload") | |
| required_cols = [ | |
| "Name", "Area (m²)", "U-Value (W/m²·K)", "Orientation", "Wall Type", "Wall Group", | |
| "Solar Absorptivity", "Shading Coefficient", "Infiltration Rate (CFM)", | |
| "Crack Length (m)", "Crack Width (m)" | |
| ] | |
| template_data = pd.DataFrame(columns=required_cols) | |
| template_data.loc[0] = [ | |
| "Example Wall", 10.0, 0.5, "North", "Brick Wall", "A", 0.6, 1.0, 0.0, 0.0, 0.0 | |
| ] | |
| st.download_button( | |
| label="Download Wall Template", | |
| data=template_data.to_csv(index=False), | |
| file_name="wall_template.csv", | |
| mime="text/csv" | |
| ) | |
| if uploaded_file: | |
| df = pd.read_csv(uploaded_file) if uploaded_file.name.endswith('.csv') else pd.read_excel(uploaded_file) | |
| if all(col in df.columns for col in required_cols): | |
| for _, row in df.iterrows(): | |
| try: | |
| new_wall = Wall( | |
| name=str(row["Name"]), u_value=float(row["U-Value (W/m²·K)"]), area=float(row["Area (m²)"]), | |
| orientation=Orientation(row["Orientation"]), wall_type=str(row["Wall Type"]), | |
| wall_group=str(row["Wall Group"]), solar_absorptivity=float(row["Solar Absorptivity"]), | |
| shading_coefficient=float(row["Shading Coefficient"]), | |
| infiltration_rate_cfm=float(row["Infiltration Rate (CFM)"]), | |
| crack_length=float(row["Crack Length (m)"]), | |
| crack_width=float(row["Crack Width (m)"]) | |
| ) | |
| self.component_library.add_component(new_wall) | |
| session_state.components['walls'].append(new_wall) | |
| except ValueError as e: | |
| st.error(f"Error in row {row['Name']}: {str(e)}") | |
| st.success("Walls uploaded successfully!") | |
| st.rerun() | |
| else: | |
| st.error(f"File must contain: {', '.join(required_cols)}") | |
| def _display_add_roof_form(self, session_state: Any) -> None: | |
| st.write("Add roofs manually or upload a file.") | |
| method = st.radio("Add Roof Method", ["Manual Entry", "File Upload"]) | |
| if "add_roof_submitted" not in session_state: | |
| session_state.add_roof_submitted = False | |
| if method == "Manual Entry": | |
| with st.form("add_roof_form", clear_on_submit=True): | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| name = st.text_input("Name", "New Roof") | |
| area = st.number_input("Area (m²)", min_value=0.0, value=1.0, step=0.1) | |
| roof_height = st.number_input("Roof Height (m)", min_value=0.0, max_value=100.0, value=3.0, step=0.1) | |
| with col2: | |
| roof_options = self.reference_data.data["roof_types"] | |
| selected_roof = st.selectbox("Roof Type", options=list(roof_options.keys())) | |
| u_value = st.number_input("U-Value (W/m²·K)", min_value=0.0, value=float(roof_options[selected_roof]["u_value"]), step=0.01) | |
| roof_group = st.selectbox("Roof Group (ASHRAE)", ["A", "B", "C", "D", "E", "F", "G"], index=0) | |
| slope = st.selectbox("Roof Slope", ["Flat", "Low Slope", "Steep Slope"], index=0) | |
| solar_absorptivity = st.selectbox("Solar Absorptivity", ["Light (0.3)", "Light to Medium (0.45)", "Medium (0.6)", "Medium to Dark (0.75)", "Dark (0.9)"], index=2) | |
| submitted = st.form_submit_button("Add Roof") | |
| if submitted and not session_state.add_roof_submitted: | |
| try: | |
| solar_absorptivity_value = float(solar_absorptivity.split("(")[1].strip(")")) | |
| new_roof = Roof( | |
| name=name, u_value=u_value, area=area, orientation=Orientation.HORIZONTAL, | |
| roof_type=selected_roof, roof_group=roof_group, slope=slope, | |
| solar_absorptivity=solar_absorptivity_value, roof_height=roof_height | |
| ) | |
| self.component_library.add_component(new_roof) | |
| session_state.components['roofs'].append(new_roof) | |
| st.success(f"Added {new_roof.name}") | |
| session_state.add_roof_submitted = True | |
| st.rerun() | |
| except ValueError as e: | |
| st.error(f"Error: {str(e)}") | |
| if session_state.add_roof_submitted: | |
| session_state.add_roof_submitted = False | |
| # Roof air volume and ventilation | |
| st.subheader("Roof Air Volume and Ventilation") | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| roof_air_volume = st.number_input("Roof Air Volume (m³)", min_value=0.0, value=session_state.roof_air_volume_m3, step=1.0) | |
| with col2: | |
| ventilation_method = st.selectbox("Ventilation Method", list(self.reference_data.data["roof_ventilation_methods"].keys())) | |
| ventilation_ach = self.reference_data.data["roof_ventilation_methods"][ventilation_method] | |
| if st.button("Update Roof Air Volume and Ventilation"): | |
| session_state.roof_air_volume_m3 = roof_air_volume | |
| session_state.roof_ventilation_ach = ventilation_ach | |
| st.success("Updated roof air volume and ventilation") | |
| st.rerun() | |
| elif method == "File Upload": | |
| uploaded_file = st.file_uploader("Upload Roofs File", type=["csv", "xlsx"], key="roof_upload") | |
| required_cols = [ | |
| "Name", "Area (m²)", "U-Value (W/m²·K)", "Roof Type", "Roof Group", "Slope", | |
| "Solar Absorptivity", "Roof Height (m)" | |
| ] | |
| template_data = pd.DataFrame(columns=required_cols) | |
| template_data.loc[0] = [ | |
| "Example Roof", 10.0, 0.3, "Concrete Roof", "A", "Flat", 0.6, 3.0 | |
| ] | |
| st.download_button( | |
| label="Download Roof Template", | |
| data=template_data.to_csv(index=False), | |
| file_name="roof_template.csv", | |
| mime="text/csv" | |
| ) | |
| if uploaded_file: | |
| df = pd.read_csv(uploaded_file) if uploaded_file.name.endswith('.csv') else pd.read_excel(uploaded_file) | |
| if all(col in df.columns for col in required_cols): | |
| for _, row in df.iterrows(): | |
| try: | |
| new_roof = Roof( | |
| name=str(row["Name"]), u_value=float(row["U-Value (W/m²·K)"]), area=float(row["Area (m²)"]), | |
| orientation=Orientation.HORIZONTAL, roof_type=str(row["Roof Type"]), | |
| roof_group=str(row["Roof Group"]), slope=str(row["Slope"]), | |
| solar_absorptivity=float(row["Solar Absorptivity"]), roof_height=float(row["Roof Height (m)"]) | |
| ) | |
| self.component_library.add_component(new_roof) | |
| session_state.components['roofs'].append(new_roof) | |
| except ValueError as e: | |
| st.error(f"Error in row {row['Name']}: {str(e)}") | |
| st.success("Roofs uploaded successfully!") | |
| st.rerun() | |
| else: | |
| st.error(f"File must contain: {', '.join(required_cols)}") | |
| def _display_add_floor_form(self, session_state: Any) -> None: | |
| st.write("Add floors manually or upload a file.") | |
| method = st.radio("Add Floor Method", ["Manual Entry", "File Upload"]) | |
| if "add_floor_submitted" not in session_state: | |
| session_state.add_floor_submitted = False | |
| if method == "Manual Entry": | |
| with st.form("add_floor_form", clear_on_submit=True): | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| name = st.text_input("Name", "New Floor") | |
| area = st.number_input("Area (m²)", min_value=0.0, value=1.0, step=0.1) | |
| perimeter = st.number_input("Perimeter (m)", min_value=0.0, value=4.0, step=0.1) | |
| with col2: | |
| floor_options = self.reference_data.data["floor_types"] | |
| selected_floor = st.selectbox("Floor Type", options=list(floor_options.keys())) | |
| u_value = st.number_input("U-Value (W/m²·K)", min_value=0.0, value=float(floor_options[selected_floor]["u_value"]), step=0.01) | |
| ground_contact = st.selectbox("Ground Contact", ["Yes", "No"], index=0 if floor_options[selected_floor]["ground_contact"] else 1) | |
| ground_temp = st.number_input("Ground Temperature (°C)", min_value=-10.0, max_value=40.0, value=25.0, step=0.1) if ground_contact == "Yes" else 25.0 | |
| insulated = st.checkbox("Insulated Floor (e.g., R-10)", value=False) | |
| submitted = st.form_submit_button("Add Floor") | |
| if submitted and not session_state.add_floor_submitted: | |
| try: | |
| new_floor = Floor( | |
| name=name, u_value=u_value, area=area, floor_type=selected_floor, | |
| ground_contact=(ground_contact == "Yes"), ground_temperature_c=ground_temp, | |
| perimeter=perimeter, insulated=insulated | |
| ) | |
| self.component_library.add_component(new_floor) | |
| session_state.components['floors'].append(new_floor) | |
| st.success(f"Added {new_floor.name}") | |
| session_state.add_floor_submitted = True | |
| st.rerun() | |
| except ValueError as e: | |
| st.error(f"Error: {str(e)}") | |
| if session_state.add_floor_submitted: | |
| session_state.add_floor_submitted = False | |
| elif method == "File Upload": | |
| uploaded_file = st.file_uploader("Upload Floors File", type=["csv", "xlsx"], key="floor_upload") | |
| required_cols = [ | |
| "Name", "Area (m²)", "U-Value (W/m²·K)", "Floor Type", "Ground Contact", | |
| "Ground Temperature (°C)", "Perimeter (m)", "Insulated" | |
| ] | |
| template_data = pd.DataFrame(columns=required_cols) | |
| template_data.loc[0] = ["Example Floor", 10.0, 0.4, "Concrete Slab", "Yes", 25.0, 12.0, "No"] | |
| st.download_button( | |
| label="Download Floor Template", | |
| data=template_data.to_csv(index=False), | |
| file_name="floor_template.csv", | |
| mime="text/csv" | |
| ) | |
| if uploaded_file: | |
| df = pd.read_csv(uploaded_file) if uploaded_file.name.endswith('.csv') else pd.read_excel(uploaded_file) | |
| if all(col in df.columns for col in required_cols): | |
| for _, row in df.iterrows(): | |
| try: | |
| insulated = str(row["Insulated"]).lower() in ["yes", "true", "1"] | |
| new_floor = Floor( | |
| name=str(row["Name"]), u_value=float(row["U-Value (W/m²·K)"]), area=float(row["Area (m²)"]), | |
| floor_type=str(row["Floor Type"]), ground_contact=(str(row["Ground Contact"]).lower() == "yes"), | |
| ground_temperature_c=float(row["Ground Temperature (°C)"]), perimeter=float(row["Perimeter (m)"]), | |
| insulated=insulated | |
| ) | |
| self.component_library.add_component(new_floor) | |
| session_state.components['floors'].append(new_floor) | |
| except ValueError as e: | |
| st.error(f"Error in row {row['Name']}: {str(e)}") | |
| st.success("Floors uploaded successfully!") | |
| st.rerun() | |
| else: | |
| st.error(f"File must contain: {', '.join(required_cols)}") | |
| def _display_add_window_form(self, session_state: Any) -> None: | |
| st.write("Add windows manually or upload a file.") | |
| method = st.radio("Add Window Method", ["Manual Entry", "File Upload"]) | |
| if "add_window_submitted" not in session_state: | |
| session_state.add_window_submitted = False | |
| if method == "Manual Entry": | |
| with st.form("add_window_form", clear_on_submit=True): | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| name = st.text_input("Name", "New Window") | |
| area = st.number_input("Area (m²)", min_value=0.0, value=1.0, step=0.1) | |
| orientation = st.selectbox("Orientation", [o.value for o in Orientation if o != Orientation.HORIZONTAL and o != Orientation.NOT_APPLICABLE], index=0) | |
| window_options = self.reference_data.data["window_types"] | |
| selected_window = st.selectbox("Window Type", options=list(window_options.keys())) | |
| glazing_type = st.selectbox("Glazing Type", [g.value for g in GlazingType], index=2) | |
| drapery_openness = st.selectbox("Drapery Openness", [o.value for o in DraperyOpenness], index=2) | |
| with col2: | |
| u_value = st.number_input("U-Value (W/m²·K)", min_value=0.0, value=float(window_options[selected_window]["u_value"]), step=0.01) | |
| shgc = st.number_input("SHGC", min_value=0.0, max_value=1.0, value=window_options[selected_window]["shgc"], step=0.01) | |
| frame_type = st.selectbox("Frame Type", [f.value for f in FrameType], index=1) | |
| frame_percentage = st.slider("Frame Percentage (%)", min_value=0.0, max_value=30.0, value=20.0) | |
| shading_options = {f"{k} (SC={v})": k for k, v in self.reference_data.data["shading_devices"].items()} | |
| shading_options["Custom"] = "Custom" | |
| shading_device = st.selectbox("Shading Device", options=list(shading_options.keys()), index=0) | |
| shading_coefficient = st.number_input("Custom Shading Coefficient", min_value=0.0, max_value=1.0, value=1.0, step=0.05) if shading_device == "Custom" else self.reference_data.data["shading_devices"][shading_options[shading_device]] | |
| infiltration_rate = st.number_input("Infiltration Rate (CFM)", min_value=0.0, value=0.0, step=0.1) | |
| drapery_color = st.selectbox("Drapery Color", [c.value for c in DraperyColor], index=0) | |
| drapery_fullness = st.number_input("Drapery Fullness", min_value=1.0, max_value=2.0, value=1.5, step=0.1) | |
| submitted = st.form_submit_button("Add Window") | |
| if submitted and not session_state.add_window_submitted: | |
| try: | |
| new_window = Window( | |
| name=name, u_value=u_value, area=area, orientation=Orientation(orientation), | |
| shgc=shgc, shading_device=shading_options[shading_device], shading_coefficient=shading_coefficient, | |
| frame_type=frame_type, frame_percentage=frame_percentage, infiltration_rate_cfm=infiltration_rate, | |
| glazing_type=glazing_type, drapery_openness=drapery_openness, drapery_color=drapery_color, | |
| drapery_fullness=drapery_fullness | |
| ) | |
| self.component_library.add_component(new_window) | |
| session_state.components['windows'].append(new_window) | |
| st.success(f"Added {new_window.name}") | |
| session_state.add_window_submitted = True | |
| st.rerun() | |
| except ValueError as e: | |
| st.error(f"Error: {str(e)}") | |
| if session_state.add_window_submitted: | |
| session_state.add_window_submitted = False | |
| elif method == "File Upload": | |
| uploaded_file = st.file_uploader("Upload Windows File", type=["csv", "xlsx"], key="window_upload") | |
| required_cols = [ | |
| "Name", "Area (m²)", "U-Value (W/m²·K)", "Orientation", "SHGC", "Shading Device", | |
| "Shading Coefficient", "Frame Type", "Frame Percentage", "Infiltration Rate (CFM)", | |
| "Glazing Type", "Drapery Openness", "Drapery Color", "Drapery Fullness" | |
| ] | |
| template_data = pd.DataFrame(columns=required_cols) | |
| template_data.loc[0] = [ | |
| "Example Window", 2.0, 2.8, "North", 0.7, "None", 1.0, "Aluminum with Thermal Break", | |
| 20.0, 0.0, "Double Clear", "Open", "Light", 1.5 | |
| ] | |
| st.download_button( | |
| label="Download Window Template", | |
| data=template_data.to_csv(index=False), | |
| file_name="window_template.csv", | |
| mime="text/csv" | |
| ) | |
| if uploaded_file: | |
| df = pd.read_csv(uploaded_file) if uploaded_file.name.endswith('.csv') else pd.read_excel(uploaded_file) | |
| if all(col in df.columns for col in required_cols): | |
| for _, row in df.iterrows(): | |
| try: | |
| new_window = Window( | |
| name=str(row["Name"]), u_value=float(row["U-Value (W/m²·K)"]), area=float(row["Area (m²)"]), | |
| orientation=Orientation(row["Orientation"]), shgc=float(row["SHGC"]), | |
| shading_device=str(row["Shading Device"]), shading_coefficient=float(row["Shading Coefficient"]), | |
| frame_type=str(row["Frame Type"]), frame_percentage=float(row["Frame Percentage"]), | |
| infiltration_rate_cfm=float(row["Infiltration Rate (CFM)"]), glazing_type=str(row["Glazing Type"]), | |
| drapery_openness=str(row["Drapery Openness"]), drapery_color=str(row["Drapery Color"]), | |
| drapery_fullness=float(row["Drapery Fullness"]) | |
| ) | |
| self.component_library.add_component(new_window) | |
| session_state.components['windows'].append(new_window) | |
| except ValueError as e: | |
| st.error(f"Error in row {row['Name']}: {str(e)}") | |
| st.success("Windows uploaded successfully!") | |
| st.rerun() | |
| else: | |
| st.error(f"File must contain: {', '.join(required_cols)}") | |
| def _display_add_door_form(self, session_state: Any) -> None: | |
| st.write("Add doors manually or upload a file.") | |
| method = st.radio("Add Door Method", ["Manual Entry", "File Upload"]) | |
| if "add_door_submitted" not in session_state: | |
| session_state.add_door_submitted = False | |
| if method == "Manual Entry": | |
| with st.form("add_door_form", clear_on_submit=True): | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| name = st.text_input("Name", "New Door") | |
| area = st.number_input("Area (m²)", min_value=0.0, value=1.0, step=0.1) | |
| orientation = st.selectbox("Orientation", [o.value for o in Orientation if o != Orientation.HORIZONTAL and o != Orientation.NOT_APPLICABLE], index=0) | |
| crack_length = st.number_input("Crack Length (m)", min_value=0.0, max_value=100.0, value=0.0, step=0.1) | |
| with col2: | |
| door_options = self.reference_data.data["door_types"] | |
| selected_door = st.selectbox("Door Type", options=list(door_options.keys())) | |
| u_value = st.number_input("U-Value (W/m²·K)", min_value=0.0, value=float(door_options[selected_door]["u_value"]), step=0.01) | |
| infiltration_rate = st.number_input("Infiltration Rate (CFM)", min_value=0.0, value=0.0, step=0.1) | |
| crack_width = st.number_input("Crack Width (m)", min_value=0.0, max_value=0.1, value=0.0, step=0.001) | |
| submitted = st.form_submit_button("Add Door") | |
| if submitted and not session_state.add_door_submitted: | |
| try: | |
| new_door = Door( | |
| name=name, u_value=u_value, area=area, orientation=Orientation(orientation), | |
| door_type=selected_door, infiltration_rate_cfm=infiltration_rate, | |
| crack_length=crack_length, crack_width=crack_width | |
| ) | |
| self.component_library.add_component(new_door) | |
| session_state.components['doors'].append(new_door) | |
| st.success(f"Added {new_door.name}") | |
| session_state.add_door_submitted = True | |
| st.rerun() | |
| except ValueError as e: | |
| st.error(f"Error: {str(e)}") | |
| if session_state.add_door_submitted: | |
| session_state.add_door_submitted = False | |
| elif method == "File Upload": | |
| uploaded_file = st.file_uploader("Upload Doors File", type=["csv", "xlsx"], key="door_upload") | |
| required_cols = [ | |
| "Name", "Area (m²)", "U-Value (W/m²·K)", "Orientation", "Door Type", | |
| "Infiltration Rate (CFM)", "Crack Length (m)", "Crack Width (m)" | |
| ] | |
| template_data = pd.DataFrame(columns=required_cols) | |
| template_data.loc[0] = ["Example Door", 2.0, 2.0, "North", "Solid Wood", 0.0, 0.0, 0.0] | |
| st.download_button( | |
| label="Download Door Template", | |
| data=template_data.to_csv(index=False), | |
| file_name="door_template.csv", | |
| mime="text/csv" | |
| ) | |
| if uploaded_file: | |
| df = pd.read_csv(uploaded_file) if uploaded_file.name.endswith('.csv') else pd.read_excel(uploaded_file) | |
| if all(col in df.columns for col in required_cols): | |
| for _, row in df.iterrows(): | |
| try: | |
| new_door = Door( | |
| name=str(row["Name"]), u_value=float(row["U-Value (W/m²·K)"]), area=float(row["Area (m²)"]), | |
| orientation=Orientation(row["Orientation"]), door_type=str(row["Door Type"]), | |
| infiltration_rate_cfm=float(row["Infiltration Rate (CFM)"]), | |
| crack_length=float(row["Crack Length (m)"]), crack_width=float(row["Crack Width (m)"]) | |
| ) | |
| self.component_library.add_component(new_door) | |
| session_state.components['doors'].append(new_door) | |
| except ValueError as e: | |
| st.error(f"Error in row {row['Name']}: {str(e)}") | |
| st.success("Doors uploaded successfully!") | |
| st.rerun() | |
| else: | |
| st.error(f"File must contain: {', '.join(required_cols)}") | |
| def _display_add_skylight_form(self, session_state: Any) -> None: | |
| st.write("Add skylights manually or upload a file.") | |
| method = st.radio("Add Skylight Method", ["Manual Entry", "File Upload"]) | |
| if "add_skylight_submitted" not in session_state: | |
| session_state.add_skylight_submitted = False | |
| if method == "Manual Entry": | |
| with st.form("add_skylight_form", clear_on_submit=True): | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| name = st.text_input("Name", "New Skylight") | |
| area = st.number_input("Area (m²)", min_value=0.0, value=1.0, step=0.1) | |
| skylight_options = self.reference_data.data["skylight_types"] | |
| selected_skylight = st.selectbox("Skylight Type", options=list(skylight_options.keys())) | |
| glazing_type = st.selectbox("Glazing Type", [g.value for g in GlazingType], index=2) | |
| drapery_openness = st.selectbox("Drapery Openness", [o.value for o in DraperyOpenness], index=2) | |
| with col2: | |
| u_value = st.number_input("U-Value (W/m²·K)", min_value=0.0, value=float(skylight_options[selected_skylight]["u_value"]), step=0.01) | |
| shgc = st.number_input("SHGC", min_value=0.0, max_value=1.0, value=skylight_options[selected_skylight]["shgc"], step=0.01) | |
| frame_type = st.selectbox("Frame Type", [f.value for f in FrameType], index=1) | |
| frame_percentage = st.slider("Frame Percentage (%)", min_value=0.0, max_value=30.0, value=20.0) | |
| shading_options = {f"{k} (SC={v})": k for k, v in self.reference_data.data["shading_devices"].items()} | |
| shading_options["Custom"] = "Custom" | |
| shading_device = st.selectbox("Shading Device", options=list(shading_options.keys()), index=0) | |
| shading_coefficient = st.number_input("Custom Shading Coefficient", min_value=0.0, max_value=1.0, value=1.0, step=0.05) if shading_device == "Custom" else self.reference_data.data["shading_devices"][shading_options[shading_device]] | |
| infiltration_rate = st.number_input("Infiltration Rate (CFM)", min_value=0.0, value=0.0, step=0.1) | |
| drapery_color = st.selectbox("Drapery Color", [c.value for c in DraperyColor], index=0) | |
| drapery_fullness = st.number_input("Drapery Fullness", min_value=1.0, max_value=2.0, value=1.5, step=0.1) | |
| submitted = st.form_submit_button("Add Skylight") | |
| if submitted and not session_state.add_skylight_submitted: | |
| try: | |
| new_skylight = Skylight( | |
| name=name, u_value=u_value, area=area, orientation=Orientation.HORIZONTAL, | |
| shgc=shgc, shading_device=shading_options[shading_device], shading_coefficient=shading_coefficient, | |
| frame_type=frame_type, frame_percentage=frame_percentage, infiltration_rate_cfm=infiltration_rate, | |
| glazing_type=glazing_type, drapery_openness=drapery_openness, drapery_color=drapery_color, | |
| drapery_fullness=drapery_fullness | |
| ) | |
| self.component_library.add_component(new_skylight) | |
| session_state.components['skylights'].append(new_skylight) | |
| st.success(f"Added {new_skylight.name}") | |
| session_state.add_skylight_submitted = True | |
| st.rerun() | |
| except ValueError as e: | |
| st.error(f"Error: {str(e)}") | |
| if session_state.add_skylight_submitted: | |
| session_state.add_skylight_submitted = False | |
| elif method == "File Upload": | |
| uploaded_file = st.file_uploader("Upload Skylights File", type=["csv", "xlsx"], key="skylight_upload") | |
| required_cols = [ | |
| "Name", "Area (m²)", "U-Value (W/m²·K)", "SHGC", "Shading Device", "Shading Coefficient", | |
| "Frame Type", "Frame Percentage", "Infiltration Rate (CFM)", "Glazing Type", | |
| "Drapery Openness", "Drapery Color", "Drapery Fullness" | |
| ] | |
| template_data = pd.DataFrame(columns=required_cols) | |
| template_data.loc[0] = [ | |
| "Example Skylight", 1.0, 3.2, 0.7, "None", 1.0, "Aluminum with Thermal Break", | |
| 20.0, 0.0, "Double Clear", "Open", "Light", 1.5 | |
| ] | |
| st.download_button( | |
| label="Download Skylight Template", | |
| data=template_data.to_csv(index=False), | |
| file_name="skylight_template.csv", | |
| mime="text/csv" | |
| ) | |
| if uploaded_file: | |
| df = pd.read_csv(uploaded_file) if uploaded_file.name.endswith('.csv') else pd.read_excel(uploaded_file) | |
| if all(col in df.columns for col in required_cols): | |
| for _, row in df.iterrows(): | |
| try: | |
| new_skylight = Skylight( | |
| name=str(row["Name"]), u_value=float(row["U-Value (W/m²·K)"]), area=float(row["Area (m²)"]), | |
| orientation=Orientation.HORIZONTAL, shgc=float(row["SHGC"]), | |
| shading_device=str(row["Shading Device"]), shading_coefficient=float(row["Shading Coefficient"]), | |
| frame_type=str(row["Frame Type"]), frame_percentage=float(row["Frame Percentage"]), | |
| infiltration_rate_cfm=float(row["Infiltration Rate (CFM)"]), glazing_type=str(row["Glazing Type"]), | |
| drapery_openness=str(row["Drapery Openness"]), drapery_color=str(row["Drapery Color"]), | |
| drapery_fullness=float(row["Drapery Fullness"]) | |
| ) | |
| self.component_library.add_component(new_skylight) | |
| session_state.components['skylights'].append(new_skylight) | |
| except ValueError as e: | |
| st.error(f"Error in row {row['Name']}: {str(e)}") | |
| st.success("Skylights uploaded successfully!") | |
| st.rerun() | |
| else: | |
| st.error(f"File must contain: {', '.join(required_cols)}") | |
| def _display_u_value_calculator_tab(self, session_state: Any) -> None: | |
| st.subheader("U-Value Calculator") | |
| st.write("Calculate U-value based on material layers.") | |
| if "u_value_layers" not in session_state: | |
| session_state.u_value_layers = [] | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| outside_resistance = st.number_input("Outside Surface Resistance (m²·K/W)", min_value=0.0, value=0.04, step=0.01) | |
| with col2: | |
| inside_resistance = st.number_input("Inside Surface Resistance (m²·K/W)", min_value=0.0, value=0.13, step=0.01) | |
| st.subheader("Material Layers") | |
| for i, layer in enumerate(session_state.u_value_layers): | |
| col1, col2, col3, col4 = st.columns([3, 2, 2, 1]) | |
| with col1: | |
| material_options = [m["name"] for m in self.u_value_calculator.materials] | |
| material_index = material_options.index(layer["name"]) if layer["name"] in material_options else 0 | |
| layer["name"] = st.selectbox(f"Material {i+1}", material_options, index=material_index, key=f"material_{i}") | |
| with col2: | |
| layer["thickness"] = st.number_input(f"Thickness {i+1} (mm)", min_value=0.1, value=layer["thickness"], step=1.0, key=f"thickness_{i}") | |
| with col3: | |
| material_conductivity = next((m["conductivity"] for m in self.u_value_calculator.materials if m["name"] == layer["name"]), 1.0) | |
| layer["conductivity"] = st.number_input(f"Conductivity {i+1} (W/m·K)", min_value=0.001, value=material_conductivity, step=0.01, key=f"conductivity_{i}") | |
| with col4: | |
| if st.button("Remove", key=f"remove_{i}"): | |
| session_state.u_value_layers.pop(i) | |
| st.rerun() | |
| col1, col2 = st.columns([3, 1]) | |
| with col1: | |
| new_material = st.selectbox("Add Material", [m["name"] for m in self.u_value_calculator.materials]) | |
| new_thickness = st.number_input("Thickness (mm)", min_value=0.1, value=100.0, step=1.0) | |
| new_conductivity = next((m["conductivity"] for m in self.u_value_calculator.materials if m["name"] == new_material), 1.0) | |
| with col2: | |
| if st.button("Add Layer"): | |
| session_state.u_value_layers.append({ | |
| "name": new_material, | |
| "thickness": new_thickness, | |
| "conductivity": new_conductivity | |
| }) | |
| st.rerun() | |
| if session_state.u_value_layers: | |
| u_value = self.u_value_calculator.calculate_u_value(session_state.u_value_layers, outside_resistance, inside_resistance) | |
| st.success(f"Calculated U-Value: {u_value:.3f} W/m²·K") | |
| # Display R-value breakdown | |
| st.subheader("R-Value Breakdown") | |
| data = [] | |
| data.append({"Layer": "Outside Surface", "Thickness (mm)": "-", "Conductivity (W/m·K)": "-", "R-Value (m²·K/W)": outside_resistance}) | |
| for layer in session_state.u_value_layers: | |
| r_value = layer["thickness"] / 1000 / layer["conductivity"] | |
| data.append({ | |
| "Layer": layer["name"], | |
| "Thickness (mm)": f"{layer['thickness']:.1f}", | |
| "Conductivity (W/m·K)": f"{layer['conductivity']:.3f}", | |
| "R-Value (m²·K/W)": f"{r_value:.3f}" | |
| }) | |
| data.append({"Layer": "Inside Surface", "Thickness (mm)": "-", "Conductivity (W/m·K)": "-", "R-Value (m²·K/W)": inside_resistance}) | |
| total_r_value = sum(layer["thickness"] / 1000 / layer["conductivity"] for layer in session_state.u_value_layers) + outside_resistance + inside_resistance | |
| data.append({"Layer": "Total", "Thickness (mm)": f"{sum(layer['thickness'] for layer in session_state.u_value_layers):.1f}", "Conductivity (W/m·K)": "-", "R-Value (m²·K/W)": f"{total_r_value:.3f}"}) | |
| st.table(pd.DataFrame(data)) | |
| def _display_components_table(self, session_state: Any, component_type: ComponentType, components: List[BuildingComponent]) -> None: | |
| type_name = component_type.value.lower() | |
| if component_type == ComponentType.ROOF: | |
| st.write(f"Roof Air Volume: {session_state.roof_air_volume_m3} m³, Ventilation Rate: {session_state.roof_ventilation_ach} ACH") | |
| if components: | |
| headers = { | |
| ComponentType.WALL: [ | |
| "Name", "Area (m²)", "U-Value (W/m²·K)", "Orientation", "Wall Type", "Wall Group", | |
| "Solar Absorptivity", "Crack Length (m)", "Crack Width (m)", "Edit", "Delete" | |
| ], | |
| ComponentType.ROOF: [ | |
| "Name", "Area (m²)", "U-Value (W/m²·K)", "Roof Type", "Roof Group", "Slope", | |
| "Solar Absorptivity", "Roof Height (m)", "Edit", "Delete" | |
| ], | |
| ComponentType.FLOOR: [ | |
| "Name", "Area (m²)", "U-Value (W/m²·K)", "Floor Type", "Ground Contact", | |
| "Ground Temperature (°C)", "Perimeter (m)", "Insulated", "Edit", "Delete" | |
| ], | |
| ComponentType.WINDOW: [ | |
| "Name", "Area (m²)", "U-Value (W/m²·K)", "Orientation", "Glazing Type", "SHGC", | |
| "Frame Type", "Drapery Openness", "Drapery Color", "Edit", "Delete" | |
| ], | |
| ComponentType.DOOR: [ | |
| "Name", "Area (m²)", "U-Value (W/m²·K)", "Orientation", "Door Type", | |
| "Crack Length (m)", "Crack Width (m)", "Edit", "Delete" | |
| ], | |
| ComponentType.SKYLIGHT: [ | |
| "Name", "Area (m²)", "U-Value (W/m²·K)", "Glazing Type", "SHGC", "Frame Type", | |
| "Drapery Openness", "Drapery Color", "Edit", "Delete" | |
| ] | |
| }[component_type] | |
| cols = st.columns([1] * len(headers)) | |
| for i, header in enumerate(headers): | |
| cols[i].write(f"**{header}**") | |
| for i, comp in enumerate(components): | |
| cols = st.columns([1] * len(headers)) | |
| cols[0].write(comp.name) | |
| cols[1].write(comp.area) | |
| cols[2].write(comp.u_value) | |
| if component_type == ComponentType.WALL: | |
| cols[3].write(comp.orientation.value) | |
| cols[4].write(comp.wall_type) | |
| cols[5].write(comp.wall_group) | |
| cols[6].write(comp.solar_absorptivity) | |
| cols[7].write(comp.crack_length) | |
| cols[8].write(comp.crack_width) | |
| edit_col = 9 | |
| elif component_type == ComponentType.ROOF: | |
| cols[3].write(comp.roof_type) | |
| cols[4].write(comp.roof_group) | |
| cols[5].write(comp.slope) | |
| cols[6].write(comp.solar_absorptivity) | |
| cols[7].write(comp.roof_height) | |
| edit_col = 8 | |
| elif component_type == ComponentType.FLOOR: | |
| cols[3].write(comp.floor_type) | |
| cols[4].write("Yes" if comp.ground_contact else "No") | |
| cols[5].write(comp.ground_temperature_c if comp.ground_contact else "N/A") | |
| cols[6].write(comp.perimeter) | |
| cols[7].write("Yes" if comp.insulated else "No") | |
| edit_col = 8 | |
| elif component_type == ComponentType.WINDOW: | |
| cols[3].write(comp.orientation.value) | |
| cols[4].write(comp.glazing_type) | |
| cols[5].write(comp.shgc) | |
| cols[6].write(comp.frame_type) | |
| cols[7].write(comp.drapery_openness) | |
| cols[8].write(comp.drapery_color) | |
| edit_col = 9 | |
| elif component_type == ComponentType.DOOR: | |
| cols[3].write(comp.orientation.value) | |
| cols[4].write(comp.door_type) | |
| cols[5].write(comp.crack_length) | |
| cols[6].write(comp.crack_width) | |
| edit_col = 7 | |
| elif component_type == ComponentType.SKYLIGHT: | |
| cols[3].write(comp.glazing_type) | |
| cols[4].write(comp.shgc) | |
| cols[5].write(comp.frame_type) | |
| cols[6].write(comp.drapery_openness) | |
| cols[7].write(comp.drapery_color) | |
| edit_col = 8 | |
| # Edit button | |
| if cols[edit_col].button("✏️", key=f"edit_{type_name}_{i}"): | |
| session_state[f"edit_{type_name}"] = i | |
| st.rerun() | |
| # Delete button | |
| if cols[edit_col + 1].button("🗑️", key=f"delete_{type_name}_{i}"): | |
| self.component_library.remove_component(comp.id) | |
| session_state.components[type_name + 's'].pop(i) | |
| st.rerun() | |
| # Edit form | |
| if f"edit_{type_name}" in session_state and session_state[f"edit_{type_name}"] is not None: | |
| edit_index = session_state[f"edit_{type_name}"] | |
| if 0 <= edit_index < len(components): | |
| comp = components[edit_index] | |
| st.subheader(f"Edit {type_name.capitalize()}: {comp.name}") | |
| if component_type == ComponentType.WALL: | |
| self._display_edit_wall_form(session_state, comp, edit_index) | |
| elif component_type == ComponentType.ROOF: | |
| self._display_edit_roof_form(session_state, comp, edit_index) | |
| elif component_type == ComponentType.FLOOR: | |
| self._display_edit_floor_form(session_state, comp, edit_index) | |
| elif component_type == ComponentType.WINDOW: | |
| self._display_edit_window_form(session_state, comp, edit_index) | |
| elif component_type == ComponentType.DOOR: | |
| self._display_edit_door_form(session_state, comp, edit_index) | |
| elif component_type == ComponentType.SKYLIGHT: | |
| self._display_edit_skylight_form(session_state, comp, edit_index) | |
| def _display_edit_wall_form(self, session_state: Any, wall: Wall, index: int) -> None: | |
| with st.form(f"edit_wall_form_{index}", clear_on_submit=True): | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| name = st.text_input("Name", wall.name) | |
| area = st.number_input("Area (m²)", min_value=0.0, value=wall.area, step=0.1) | |
| orientation = st.selectbox("Orientation", [o.value for o in Orientation if o != Orientation.HORIZONTAL and o != Orientation.NOT_APPLICABLE], index=[o.value for o in Orientation if o != Orientation.HORIZONTAL and o != Orientation.NOT_APPLICABLE].index(wall.orientation.value)) | |
| crack_length = st.number_input("Crack Length (m)", min_value=0.0, max_value=100.0, value=wall.crack_length, step=0.1) | |
| with col2: | |
| wall_options = self.reference_data.data["wall_types"] | |
| selected_wall = st.selectbox("Wall Type", options=list(wall_options.keys()), index=list(wall_options.keys()).index(wall.wall_type) if wall.wall_type in wall_options else 0) | |
| u_value = st.number_input("U-Value (W/m²·K)", min_value=0.0, value=wall.u_value, step=0.01) | |
| wall_group = st.selectbox("Wall Group (ASHRAE)", ["A", "B", "C", "D", "E", "F", "G", "H"], index=["A", "B", "C", "D", "E", "F", "G", "H"].index(wall.wall_group)) | |
| solar_absorptivity = st.selectbox("Solar Absorptivity", ["Light (0.3)", "Light to Medium (0.45)", "Medium (0.6)", "Medium to Dark (0.75)", "Dark (0.9)"], index=["Light (0.3)", "Light to Medium (0.45)", "Medium (0.6)", "Medium to Dark (0.75)", "Dark (0.9)"].index(f"{'Light (0.3)' if wall.solar_absorptivity == 0.3 else 'Light to Medium (0.45)' if wall.solar_absorptivity == 0.45 else 'Medium (0.6)' if wall.solar_absorptivity == 0.6 else 'Medium to Dark (0.75)' if wall.solar_absorptivity == 0.75 else 'Dark (0.9)'}")) | |
| shading_coefficient = st.number_input("Shading Coefficient", min_value=0.0, max_value=1.0, value=wall.shading_coefficient, step=0.05) | |
| infiltration_rate = st.number_input("Infiltration Rate (CFM)", min_value=0.0, value=wall.infiltration_rate_cfm, step=0.1) | |
| crack_width = st.number_input("Crack Width (m)", min_value=0.0, max_value=0.1, value=wall.crack_width, step=0.001) | |
| submitted = st.form_submit_button("Update Wall") | |
| if submitted: | |
| try: | |
| solar_absorptivity_value = float(solar_absorptivity.split("(")[1].strip(")")) | |
| updated_wall = Wall( | |
| id=wall.id, name=name, u_value=u_value, area=area, orientation=Orientation(orientation), | |
| wall_type=selected_wall, wall_group=wall_group, solar_absorptivity=solar_absorptivity_value, | |
| shading_coefficient=shading_coefficient, infiltration_rate_cfm=infiltration_rate, | |
| crack_length=crack_length, crack_width=crack_width | |
| ) | |
| self.component_library.components[wall.id] = updated_wall | |
| session_state.components['walls'][index] = updated_wall | |
| session_state[f"edit_wall"] = None | |
| st.success(f"Updated {name}") | |
| st.rerun() | |
| except ValueError as e: | |
| st.error(f"Error: {str(e)}") | |
| def _display_edit_roof_form(self, session_state: Any, roof: Roof, index: int) -> None: | |
| with st.form(f"edit_roof_form_{index}", clear_on_submit=True): | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| name = st.text_input("Name", roof.name) | |
| area = st.number_input("Area (m²)", min_value=0.0, value=roof.area, step=0.1) | |
| roof_height = st.number_input("Roof Height (m)", min_value=0.0, max_value=100.0, value=roof.roof_height, step=0.1) | |
| with col2: | |
| roof_options = self.reference_data.data["roof_types"] | |
| selected_roof = st.selectbox("Roof Type", options=list(roof_options.keys()), index=list(roof_options.keys()).index(roof.roof_type) if roof.roof_type in roof_options else 0) | |
| u_value = st.number_input("U-Value (W/m²·K)", min_value=0.0, value=roof.u_value, step=0.01) | |
| roof_group = st.selectbox("Roof Group (ASHRAE)", ["A", "B", "C", "D", "E", "F", "G"], index=["A", "B", "C", "D", "E", "F", "G"].index(roof.roof_group)) | |
| slope = st.selectbox("Roof Slope", ["Flat", "Low Slope", "Steep Slope"], index=["Flat", "Low Slope", "Steep Slope"].index(roof.slope)) | |
| solar_absorptivity = st.selectbox("Solar Absorptivity", ["Light (0.3)", "Light to Medium (0.45)", "Medium (0.6)", "Medium to Dark (0.75)", "Dark (0.9)"], index=["Light (0.3)", "Light to Medium (0.45)", "Medium (0.6)", "Medium to Dark (0.75)", "Dark (0.9)"].index(f"{'Light (0.3)' if roof.solar_absorptivity == 0.3 else 'Light to Medium (0.45)' if roof.solar_absorptivity == 0.45 else 'Medium (0.6)' if roof.solar_absorptivity == 0.6 else 'Medium to Dark (0.75)' if roof.solar_absorptivity == 0.75 else 'Dark (0.9)'}")) | |
| submitted = st.form_submit_button("Update Roof") | |
| if submitted: | |
| try: | |
| solar_absorptivity_value = float(solar_absorptivity.split("(")[1].strip(")")) | |
| updated_roof = Roof( | |
| id=roof.id, name=name, u_value=u_value, area=area, orientation=Orientation.HORIZONTAL, | |
| roof_type=selected_roof, roof_group=roof_group, slope=slope, | |
| solar_absorptivity=solar_absorptivity_value, roof_height=roof_height | |
| ) | |
| self.component_library.components[roof.id] = updated_roof | |
| session_state.components['roofs'][index] = updated_roof | |
| session_state[f"edit_roof"] = None | |
| st.success(f"Updated {name}") | |
| st.rerun() | |
| except ValueError as e: | |
| st.error(f"Error: {str(e)}") | |
| def _display_edit_floor_form(self, session_state: Any, floor: Floor, index: int) -> None: | |
| with st.form(f"edit_floor_form_{index}", clear_on_submit=True): | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| name = st.text_input("Name", floor.name) | |
| area = st.number_input("Area (m²)", min_value=0.0, value=floor.area, step=0.1) | |
| perimeter = st.number_input("Perimeter (m)", min_value=0.0, value=floor.perimeter, step=0.1) | |
| with col2: | |
| floor_options = self.reference_data.data["floor_types"] | |
| selected_floor = st.selectbox("Floor Type", options=list(floor_options.keys()), index=list(floor_options.keys()).index(floor.floor_type) if floor.floor_type in floor_options else 0) | |
| u_value = st.number_input("U-Value (W/m²·K)", min_value=0.0, value=floor.u_value, step=0.01) | |
| ground_contact = st.selectbox("Ground Contact", ["Yes", "No"], index=0 if floor.ground_contact else 1) | |
| ground_temp = st.number_input("Ground Temperature (°C)", min_value=-10.0, max_value=40.0, value=floor.ground_temperature_c, step=0.1) if ground_contact == "Yes" else 25.0 | |
| insulated = st.checkbox("Insulated Floor (e.g., R-10)", value=floor.insulated) | |
| submitted = st.form_submit_button("Update Floor") | |
| if submitted: | |
| try: | |
| updated_floor = Floor( | |
| id=floor.id, name=name, u_value=u_value, area=area, floor_type=selected_floor, | |
| ground_contact=(ground_contact == "Yes"), ground_temperature_c=ground_temp, | |
| perimeter=perimeter, insulated=insulated | |
| ) | |
| self.component_library.components[floor.id] = updated_floor | |
| session_state.components['floors'][index] = updated_floor | |
| session_state[f"edit_floor"] = None | |
| st.success(f"Updated {name}") | |
| st.rerun() | |
| except ValueError as e: | |
| st.error(f"Error: {str(e)}") | |
| def _display_edit_window_form(self, session_state: Any, window: Window, index: int) -> None: | |
| with st.form(f"edit_window_form_{index}", clear_on_submit=True): | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| name = st.text_input("Name", window.name) | |
| area = st.number_input("Area (m²)", min_value=0.0, value=window.area, step=0.1) | |
| orientation = st.selectbox("Orientation", [o.value for o in Orientation if o != Orientation.HORIZONTAL and o != Orientation.NOT_APPLICABLE], index=[o.value for o in Orientation if o != Orientation.HORIZONTAL and o != Orientation.NOT_APPLICABLE].index(window.orientation.value)) | |
| window_options = self.reference_data.data["window_types"] | |
| selected_window = st.selectbox("Window Type", options=list(window_options.keys()), index=list(window_options.keys()).index(window.glazing_type) if window.glazing_type in window_options else 0) | |
| glazing_type = st.selectbox("Glazing Type", [g.value for g in GlazingType], index=[g.value for g in GlazingType].index(window.glazing_type)) | |
| drapery_openness = st.selectbox("Drapery Openness", [o.value for o in DraperyOpenness], index=[o.value for o in DraperyOpenness].index(window.drapery_openness)) | |
| with col2: | |
| u_value = st.number_input("U-Value (W/m²·K)", min_value=0.0, value=window.u_value, step=0.01) | |
| shgc = st.number_input("SHGC", min_value=0.0, max_value=1.0, value=window.shgc, step=0.01) | |
| frame_type = st.selectbox("Frame Type", [f.value for f in FrameType], index=[f.value for f in FrameType].index(window.frame_type)) | |
| frame_percentage = st.slider("Frame Percentage (%)", min_value=0.0, max_value=30.0, value=window.frame_percentage) | |
| shading_options = {f"{k} (SC={v})": k for k, v in self.reference_data.data["shading_devices"].items()} | |
| shading_options["Custom"] = "Custom" | |
| shading_device = st.selectbox("Shading Device", options=list(shading_options.keys()), index=list(shading_options.values()).index(window.shading_device) if window.shading_device in shading_options.values() else 0) | |
| shading_coefficient = st.number_input("Custom Shading Coefficient", min_value=0.0, max_value=1.0, value=window.shading_coefficient, step=0.05) if shading_device == "Custom" else self.reference_data.data["shading_devices"][shading_options[shading_device]] | |
| infiltration_rate = st.number_input("Infiltration Rate (CFM)", min_value=0.0, value=window.infiltration_rate_cfm, step=0.1) | |
| drapery_color = st.selectbox("Drapery Color", [c.value for c in DraperyColor], index=[c.value for c in DraperyColor].index(window.drapery_color)) | |
| drapery_fullness = st.number_input("Drapery Fullness", min_value=1.0, max_value=2.0, value=window.drapery_fullness, step=0.1) | |
| submitted = st.form_submit_button("Update Window") | |
| if submitted: | |
| try: | |
| updated_window = Window( | |
| id=window.id, name=name, u_value=u_value, area=area, orientation=Orientation(orientation), | |
| shgc=shgc, shading_device=shading_options[shading_device], shading_coefficient=shading_coefficient, | |
| frame_type=frame_type, frame_percentage=frame_percentage, infiltration_rate_cfm=infiltration_rate, | |
| glazing_type=glazing_type, drapery_openness=drapery_openness, drapery_color=drapery_color, | |
| drapery_fullness=drapery_fullness | |
| ) | |
| self.component_library.components[window.id] = updated_window | |
| session_state.components['windows'][index] = updated_window | |
| session_state[f"edit_window"] = None | |
| st.success(f"Updated {name}") | |
| st.rerun() | |
| except ValueError as e: | |
| st.error(f"Error: {str(e)}") | |
| def _display_edit_door_form(self, session_state: Any, door: Door, index: int) -> None: | |
| with st.form(f"edit_door_form_{index}", clear_on_submit=True): | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| name = st.text_input("Name", door.name) | |
| area = st.number_input("Area (m²)", min_value=0.0, value=door.area, step=0.1) | |
| orientation = st.selectbox("Orientation", [o.value for o in Orientation if o != Orientation.HORIZONTAL and o != Orientation.NOT_APPLICABLE], index=[o.value for o in Orientation if o != Orientation.HORIZONTAL and o != Orientation.NOT_APPLICABLE].index(door.orientation.value)) | |
| crack_length = st.number_input("Crack Length (m)", min_value=0.0, max_value=100.0, value=door.crack_length, step=0.1) | |
| with col2: | |
| door_options = self.reference_data.data["door_types"] | |
| selected_door = st.selectbox("Door Type", options=list(door_options.keys()), index=list(door_options.keys()).index(door.door_type) if door.door_type in door_options else 0) | |
| u_value = st.number_input("U-Value (W/m²·K)", min_value=0.0, value=door.u_value, step=0.01) | |
| infiltration_rate = st.number_input("Infiltration Rate (CFM)", min_value=0.0, value=door.infiltration_rate_cfm, step=0.1) | |
| crack_width = st.number_input("Crack Width (m)", min_value=0.0, max_value=0.1, value=door.crack_width, step=0.001) | |
| submitted = st.form_submit_button("Update Door") | |
| if submitted: | |
| try: | |
| updated_door = Door( | |
| id=door.id, name=name, u_value=u_value, area=area, orientation=Orientation(orientation), | |
| door_type=selected_door, infiltration_rate_cfm=infiltration_rate, | |
| crack_length=crack_length, crack_width=crack_width | |
| ) | |
| self.component_library.components[door.id] = updated_door | |
| session_state.components['doors'][index] = updated_door | |
| session_state[f"edit_door"] = None | |
| st.success(f"Updated {name}") | |
| st.rerun() | |
| except ValueError as e: | |
| st.error(f"Error: {str(e)}") | |
| def _display_edit_skylight_form(self, session_state: Any, skylight: Skylight, index: int) -> None: | |
| with st.form(f"edit_skylight_form_{index}", clear_on_submit=True): | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| name = st.text_input("Name", skylight.name) | |
| area = st.number_input("Area (m²)", min_value=0.0, value=skylight.area, step=0.1) | |
| skylight_options = self.reference_data.data["skylight_types"] | |
| selected_skylight = st.selectbox("Skylight Type", options=list(skylight_options.keys()), index=list(skylight_options.keys()).index(skylight.glazing_type) if skylight.glazing_type in skylight_options else 0) | |
| glazing_type = st.selectbox("Glazing Type", [g.value for g in GlazingType], index=[g.value for g in GlazingType].index(skylight.glazing_type)) | |
| drapery_openness = st.selectbox("Drapery Openness", [o.value for o in DraperyOpenness], index=[o.value for o in DraperyOpenness].index(skylight.drapery_openness)) | |
| with col2: | |
| u_value = st.number_input("U-Value (W/m²·K)", min_value=0.0, value=skylight.u_value, step=0.01) | |
| shgc = st.number_input("SHGC", min_value=0.0, max_value=1.0, value=skylight.shgc, step=0.01) | |
| frame_type = st.selectbox("Frame Type", [f.value for f in FrameType], index=[f.value for f in FrameType].index(skylight.frame_type)) | |
| frame_percentage = st.slider("Frame Percentage (%)", min_value=0.0, max_value=30.0, value=skylight.frame_percentage) | |
| shading_options = {f"{k} (SC={v})": k for k, v in self.reference_data.data["shading_devices"].items()} | |
| shading_options["Custom"] = "Custom" | |
| shading_device = st.selectbox("Shading Device", options=list(shading_options.keys()), index=list(shading_options.values()).index(skylight.shading_device) if skylight.shading_device in shading_options.values() else 0) | |
| shading_coefficient = st.number_input("Custom Shading Coefficient", min_value=0.0, max_value=1.0, value=skylight.shading_coefficient, step=0.05) if shading_device == "Custom" else self.reference_data.data["shading_devices"][shading_options[shading_device]] | |
| infiltration_rate = st.number_input("Infiltration Rate (CFM)", min_value=0.0, value=skylight.infiltration_rate_cfm, step=0.1) | |
| drapery_color = st.selectbox("Drapery Color", [c.value for c in DraperyColor], index=[c.value for c in DraperyColor].index(skylight.drapery_color)) | |
| drapery_fullness = st.number_input("Drapery Fullness", min_value=1.0, max_value=2.0, value=skylight.drapery_fullness, step=0.1) | |
| submitted = st.form_submit_button("Update Skylight") | |
| if submitted: | |
| try: | |
| updated_skylight = Skylight( | |
| id=skylight.id, name=name, u_value=u_value, area=area, orientation=Orientation.HORIZONTAL, | |
| shgc=shgc, shading_device=shading_options[shading_device], shading_coefficient=shading_coefficient, | |
| frame_type=frame_type, frame_percentage=frame_percentage, infiltration_rate_cfm=infiltration_rate, | |
| glazing_type=glazing_type, drapery_openness=drapery_openness, drapery_color=drapery_color, | |
| drapery_fullness=drapery_fullness | |
| ) | |
| self.component_library.components[skylight.id] = updated_skylight | |
| session_state.components['skylights'][index] = updated_skylight | |
| session_state[f"edit_skylight"] = None | |
| st.success(f"Updated {name}") | |
| st.rerun() | |
| except ValueError as e: | |
| st.error(f"Error: {str(e)}") | |
| def _save_components(self, session_state: Any) -> None: | |
| components_data = { | |
| "walls": [comp.to_dict() for comp in session_state.components['walls']], | |
| "roofs": [comp.to_dict() for comp in session_state.components['roofs']], | |
| "floors": [comp.to_dict() for comp in session_state.components['floors']], | |
| "windows": [comp.to_dict() for comp in session_state.components['windows']], | |
| "doors": [comp.to_dict() for comp in session_state.components['doors']], | |
| "skylights": [comp.to_dict() for comp in session_state.components['skylights']], | |
| "roof_air_volume_m3": session_state.roof_air_volume_m3, | |
| "roof_ventilation_ach": session_state.roof_ventilation_ach | |
| } | |
| buffer = io.StringIO() | |
| json.dump(components_data, buffer, indent=2) | |
| st.download_button( | |
| label="Download Components JSON", | |
| data=buffer.getvalue(), | |
| file_name="building_components.json", | |
| mime="application/json" | |
| ) | |
| st.success("Components saved to JSON file for download.") |