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.") |