Spaces:
Sleeping
Sleeping
Update app/component_selection.py
Browse files- app/component_selection.py +19 -17
app/component_selection.py
CHANGED
|
@@ -3,6 +3,7 @@ HVAC Component Selection Module
|
|
| 3 |
Provides UI for selecting building components in the HVAC Load Calculator.
|
| 4 |
All dependencies are included within this file for standalone operation.
|
| 5 |
Updated 2025-04-28: Added perimeter field to Floor class for F-factor calculations.
|
|
|
|
| 6 |
"""
|
| 7 |
|
| 8 |
import streamlit as st
|
|
@@ -66,7 +67,7 @@ class BuildingComponent:
|
|
| 66 |
@dataclass
|
| 67 |
class Wall(BuildingComponent):
|
| 68 |
wall_type: str = "Brick"
|
| 69 |
-
wall_group: str = "A" #
|
| 70 |
absorptivity: float = 0.6
|
| 71 |
shading_coefficient: float = 1.0
|
| 72 |
infiltration_rate_cfm: float = 0.0
|
|
@@ -80,7 +81,6 @@ class Wall(BuildingComponent):
|
|
| 80 |
raise ValueError("Shading coefficient must be between 0 and 1")
|
| 81 |
if self.infiltration_rate_cfm < 0:
|
| 82 |
raise ValueError("Infiltration rate cannot be negative")
|
| 83 |
-
# Validate wall_group
|
| 84 |
VALID_WALL_GROUPS = {"A", "B", "C", "D", "E", "F", "G", "H"}
|
| 85 |
if self.wall_group not in VALID_WALL_GROUPS:
|
| 86 |
st.warning(f"Invalid wall_group '{self.wall_group}' for wall '{self.name}'. Defaulting to 'A'.")
|
|
@@ -97,7 +97,7 @@ class Wall(BuildingComponent):
|
|
| 97 |
@dataclass
|
| 98 |
class Roof(BuildingComponent):
|
| 99 |
roof_type: str = "Concrete"
|
| 100 |
-
roof_group: str = "A" #
|
| 101 |
slope: str = "Flat"
|
| 102 |
absorptivity: float = 0.6
|
| 103 |
|
|
@@ -108,7 +108,6 @@ class Roof(BuildingComponent):
|
|
| 108 |
self.orientation = Orientation.HORIZONTAL
|
| 109 |
if not 0 <= self.absorptivity <= 1:
|
| 110 |
raise ValueError("Absorptivity must be between 0 and 1")
|
| 111 |
-
# Validate roof_group
|
| 112 |
VALID_ROOF_GROUPS = {"A", "B", "C", "D", "E", "F", "G"}
|
| 113 |
if self.roof_group not in VALID_ROOF_GROUPS:
|
| 114 |
st.warning(f"Invalid roof_group '{self.roof_group}' for roof '{self.name}'. Defaulting to 'A'.")
|
|
@@ -126,7 +125,8 @@ class Floor(BuildingComponent):
|
|
| 126 |
floor_type: str = "Concrete"
|
| 127 |
ground_contact: bool = True
|
| 128 |
ground_temperature_c: float = 25.0
|
| 129 |
-
perimeter: float = 0.0
|
|
|
|
| 130 |
|
| 131 |
def __post_init__(self):
|
| 132 |
super().__post_init__()
|
|
@@ -141,7 +141,8 @@ class Floor(BuildingComponent):
|
|
| 141 |
base_dict = super().to_dict()
|
| 142 |
base_dict.update({
|
| 143 |
"floor_type": self.floor_type, "ground_contact": self.ground_contact,
|
| 144 |
-
"ground_temperature_c": self.ground_temperature_c, "perimeter": self.perimeter
|
|
|
|
| 145 |
})
|
| 146 |
return base_dict
|
| 147 |
|
|
@@ -376,7 +377,6 @@ class ComponentSelectionInterface:
|
|
| 376 |
elif method == "File Upload":
|
| 377 |
uploaded_file = st.file_uploader("Upload Walls File", type=["csv", "xlsx"], key="wall_upload")
|
| 378 |
required_cols = ["Name", "Area (m²)", "U-Value (W/m²·K)", "Orientation", "Wall Type", "Wall Group", "Absorptivity", "Shading Coefficient", "Infiltration Rate (CFM)"]
|
| 379 |
-
# Provide template with valid wall_group values
|
| 380 |
template_data = pd.DataFrame(columns=required_cols)
|
| 381 |
template_data.loc[0] = ["Example Wall", 10.0, 2.0, "North", "Brick Wall", "A", 0.6, 1.0, 0.0]
|
| 382 |
st.download_button(label="Download Wall Template", data=template_data.to_csv(index=False), file_name="wall_template.csv", mime="text/csv")
|
|
@@ -411,7 +411,6 @@ class ComponentSelectionInterface:
|
|
| 411 |
if "add_roof_submitted" not in session_state:
|
| 412 |
session_state.add_roof_submitted = False
|
| 413 |
|
| 414 |
-
# Shared Roof-Level Fields
|
| 415 |
st.subheader("Roof System Ventilation")
|
| 416 |
air_volume = st.number_input("Air Volume (m³)", min_value=0.0, value=session_state.roof_air_volume_m3, step=1.0, help="Total volume between roof and ceiling")
|
| 417 |
vent_options = {f"{k} (ACH={v})": v for k, v in self.reference_data.data["roof_ventilation_methods"].items()}
|
|
@@ -432,7 +431,7 @@ class ComponentSelectionInterface:
|
|
| 432 |
roof_options = self.reference_data.data["roof_types"]
|
| 433 |
selected_roof = st.selectbox("Roof Type", options=list(roof_options.keys()))
|
| 434 |
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)
|
| 435 |
-
roof_group = st.selectbox("Roof Group (ASHRAE)", ["A", "B", "C", "D", "E", "F", "G"], index=0)
|
| 436 |
slope = st.selectbox("Slope", ["Flat", "Pitched"], index=0)
|
| 437 |
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)
|
| 438 |
|
|
@@ -458,7 +457,6 @@ class ComponentSelectionInterface:
|
|
| 458 |
elif method == "File Upload":
|
| 459 |
uploaded_file = st.file_uploader("Upload Roofs File", type=["csv", "xlsx"], key="roof_upload")
|
| 460 |
required_cols = ["Name", "Area (m²)", "U-Value (W/m²·K)", "Orientation", "Roof Type", "Roof Group", "Slope", "Absorptivity"]
|
| 461 |
-
# Provide template with valid roof_group values
|
| 462 |
template_data = pd.DataFrame(columns=required_cols)
|
| 463 |
template_data.loc[0] = ["Example Roof", 10.0, 0.3, "Horizontal", "Concrete Roof", "A", "Flat", 0.6]
|
| 464 |
st.download_button(label="Download Roof Template", data=template_data.to_csv(index=False), file_name="roof_template.csv", mime="text/csv")
|
|
@@ -498,13 +496,14 @@ class ComponentSelectionInterface:
|
|
| 498 |
with col1:
|
| 499 |
name = st.text_input("Name", "New Floor")
|
| 500 |
area = st.number_input("Area (m²)", min_value=0.0, value=1.0, step=0.1)
|
| 501 |
-
perimeter = st.number_input("Perimeter (m)", min_value=0.0, value=0.0, step=0.1)
|
| 502 |
with col2:
|
| 503 |
floor_options = self.reference_data.data["floor_types"]
|
| 504 |
selected_floor = st.selectbox("Floor Type", options=list(floor_options.keys()))
|
| 505 |
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)
|
| 506 |
ground_contact = st.selectbox("Ground Contact", ["Yes", "No"], index=0 if floor_options[selected_floor]["ground_contact"] else 1)
|
| 507 |
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
|
|
|
|
| 508 |
|
| 509 |
submitted = st.form_submit_button("Add Floor")
|
| 510 |
if submitted and not session_state.add_floor_submitted:
|
|
@@ -512,7 +511,7 @@ class ComponentSelectionInterface:
|
|
| 512 |
new_floor = Floor(
|
| 513 |
name=name, u_value=u_value, area=area, floor_type=selected_floor,
|
| 514 |
ground_contact=(ground_contact == "Yes"), ground_temperature_c=ground_temp,
|
| 515 |
-
perimeter=perimeter
|
| 516 |
)
|
| 517 |
self.component_library.add_component(new_floor)
|
| 518 |
session_state.components['floors'].append(new_floor)
|
|
@@ -527,19 +526,21 @@ class ComponentSelectionInterface:
|
|
| 527 |
|
| 528 |
elif method == "File Upload":
|
| 529 |
uploaded_file = st.file_uploader("Upload Floors File", type=["csv", "xlsx"], key="floor_upload")
|
| 530 |
-
required_cols = ["Name", "Area (m²)", "U-Value (W/m²·K)", "Floor Type", "Ground Contact", "Ground Temperature (°C)", "Perimeter (m)"] # NEW: Added
|
| 531 |
template_data = pd.DataFrame(columns=required_cols)
|
| 532 |
-
template_data.loc[0] = ["Example Floor", 10.0, 0.4, "Concrete Slab", "Yes", 25.0, 12.0]
|
| 533 |
st.download_button(label="Download Floor Template", data=template_data.to_csv(index=False), file_name="floor_template.csv", mime="text/csv")
|
| 534 |
if uploaded_file:
|
| 535 |
df = pd.read_csv(uploaded_file) if uploaded_file.name.endswith('.csv') else pd.read_excel(uploaded_file)
|
| 536 |
if all(col in df.columns for col in required_cols):
|
| 537 |
for _, row in df.iterrows():
|
| 538 |
try:
|
|
|
|
| 539 |
new_floor = Floor(
|
| 540 |
name=str(row["Name"]), u_value=float(row["U-Value (W/m²·K)"]), area=float(row["Area (m²)"]),
|
| 541 |
floor_type=str(row["Floor Type"]), ground_contact=(str(row["Ground Contact"]).lower() == "yes"),
|
| 542 |
-
ground_temperature_c=float(row["Ground Temperature (°C)"]), perimeter=float(row["Perimeter (m)"])
|
|
|
|
| 543 |
)
|
| 544 |
self.component_library.add_component(new_floor)
|
| 545 |
session_state.components['floors'].append(new_floor)
|
|
@@ -689,7 +690,7 @@ class ComponentSelectionInterface:
|
|
| 689 |
headers = {
|
| 690 |
ComponentType.WALL: ["Name", "Area (m²)", "U-Value (W/m²·K)", "Orientation", "Wall Type", "Wall Group", "Absorptivity", "Shading Coefficient", "Infiltration Rate (CFM)", "Delete"],
|
| 691 |
ComponentType.ROOF: ["Name", "Area (m²)", "U-Value (W/m²·K)", "Orientation", "Roof Type", "Roof Group", "Slope", "Absorptivity", "Delete"],
|
| 692 |
-
ComponentType.FLOOR: ["Name", "Area (m²)", "U-Value (W/m²·K)", "Floor Type", "Ground Contact", "Ground Temperature (°C)", "Perimeter (m)", "Delete"], # NEW: Added
|
| 693 |
ComponentType.WINDOW: ["Name", "Area (m²)", "U-Value (W/m²·K)", "Orientation", "SHGC", "Shading Device", "Shading Coefficient", "Frame Type", "Frame Percentage", "Infiltration Rate (CFM)", "Delete"],
|
| 694 |
ComponentType.DOOR: ["Name", "Area (m²)", "U-Value (W/m²·K)", "Orientation", "Door Type", "Infiltration Rate (CFM)", "Delete"]
|
| 695 |
}[component_type]
|
|
@@ -718,7 +719,8 @@ class ComponentSelectionInterface:
|
|
| 718 |
cols[4].write(comp.floor_type)
|
| 719 |
cols[5].write("Yes" if comp.ground_contact else "No")
|
| 720 |
cols[6].write(comp.ground_temperature_c if comp.ground_contact else "N/A")
|
| 721 |
-
cols[7].write(comp.perimeter)
|
|
|
|
| 722 |
elif component_type == ComponentType.WINDOW:
|
| 723 |
cols[4].write(comp.shgc)
|
| 724 |
cols[5].write(comp.shading_device)
|
|
|
|
| 3 |
Provides UI for selecting building components in the HVAC Load Calculator.
|
| 4 |
All dependencies are included within this file for standalone operation.
|
| 5 |
Updated 2025-04-28: Added perimeter field to Floor class for F-factor calculations.
|
| 6 |
+
Updated 2025-04-29: Fixed Floors table headings, added insulated field for dynamic F-factor.
|
| 7 |
"""
|
| 8 |
|
| 9 |
import streamlit as st
|
|
|
|
| 67 |
@dataclass
|
| 68 |
class Wall(BuildingComponent):
|
| 69 |
wall_type: str = "Brick"
|
| 70 |
+
wall_group: str = "A" # ASHRAE group
|
| 71 |
absorptivity: float = 0.6
|
| 72 |
shading_coefficient: float = 1.0
|
| 73 |
infiltration_rate_cfm: float = 0.0
|
|
|
|
| 81 |
raise ValueError("Shading coefficient must be between 0 and 1")
|
| 82 |
if self.infiltration_rate_cfm < 0:
|
| 83 |
raise ValueError("Infiltration rate cannot be negative")
|
|
|
|
| 84 |
VALID_WALL_GROUPS = {"A", "B", "C", "D", "E", "F", "G", "H"}
|
| 85 |
if self.wall_group not in VALID_WALL_GROUPS:
|
| 86 |
st.warning(f"Invalid wall_group '{self.wall_group}' for wall '{self.name}'. Defaulting to 'A'.")
|
|
|
|
| 97 |
@dataclass
|
| 98 |
class Roof(BuildingComponent):
|
| 99 |
roof_type: str = "Concrete"
|
| 100 |
+
roof_group: str = "A" # ASHRAE group
|
| 101 |
slope: str = "Flat"
|
| 102 |
absorptivity: float = 0.6
|
| 103 |
|
|
|
|
| 108 |
self.orientation = Orientation.HORIZONTAL
|
| 109 |
if not 0 <= self.absorptivity <= 1:
|
| 110 |
raise ValueError("Absorptivity must be between 0 and 1")
|
|
|
|
| 111 |
VALID_ROOF_GROUPS = {"A", "B", "C", "D", "E", "F", "G"}
|
| 112 |
if self.roof_group not in VALID_ROOF_GROUPS:
|
| 113 |
st.warning(f"Invalid roof_group '{self.roof_group}' for roof '{self.name}'. Defaulting to 'A'.")
|
|
|
|
| 125 |
floor_type: str = "Concrete"
|
| 126 |
ground_contact: bool = True
|
| 127 |
ground_temperature_c: float = 25.0
|
| 128 |
+
perimeter: float = 0.0
|
| 129 |
+
insulated: bool = False # NEW: For dynamic F-factor
|
| 130 |
|
| 131 |
def __post_init__(self):
|
| 132 |
super().__post_init__()
|
|
|
|
| 141 |
base_dict = super().to_dict()
|
| 142 |
base_dict.update({
|
| 143 |
"floor_type": self.floor_type, "ground_contact": self.ground_contact,
|
| 144 |
+
"ground_temperature_c": self.ground_temperature_c, "perimeter": self.perimeter,
|
| 145 |
+
"insulated": self.insulated
|
| 146 |
})
|
| 147 |
return base_dict
|
| 148 |
|
|
|
|
| 377 |
elif method == "File Upload":
|
| 378 |
uploaded_file = st.file_uploader("Upload Walls File", type=["csv", "xlsx"], key="wall_upload")
|
| 379 |
required_cols = ["Name", "Area (m²)", "U-Value (W/m²·K)", "Orientation", "Wall Type", "Wall Group", "Absorptivity", "Shading Coefficient", "Infiltration Rate (CFM)"]
|
|
|
|
| 380 |
template_data = pd.DataFrame(columns=required_cols)
|
| 381 |
template_data.loc[0] = ["Example Wall", 10.0, 2.0, "North", "Brick Wall", "A", 0.6, 1.0, 0.0]
|
| 382 |
st.download_button(label="Download Wall Template", data=template_data.to_csv(index=False), file_name="wall_template.csv", mime="text/csv")
|
|
|
|
| 411 |
if "add_roof_submitted" not in session_state:
|
| 412 |
session_state.add_roof_submitted = False
|
| 413 |
|
|
|
|
| 414 |
st.subheader("Roof System Ventilation")
|
| 415 |
air_volume = st.number_input("Air Volume (m³)", min_value=0.0, value=session_state.roof_air_volume_m3, step=1.0, help="Total volume between roof and ceiling")
|
| 416 |
vent_options = {f"{k} (ACH={v})": v for k, v in self.reference_data.data["roof_ventilation_methods"].items()}
|
|
|
|
| 431 |
roof_options = self.reference_data.data["roof_types"]
|
| 432 |
selected_roof = st.selectbox("Roof Type", options=list(roof_options.keys()))
|
| 433 |
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)
|
| 434 |
+
roof_group = st.selectbox("Roof Group (ASHRAE)", ["A", "B", "C", "D", "E", "F", "G"], index=0)
|
| 435 |
slope = st.selectbox("Slope", ["Flat", "Pitched"], index=0)
|
| 436 |
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)
|
| 437 |
|
|
|
|
| 457 |
elif method == "File Upload":
|
| 458 |
uploaded_file = st.file_uploader("Upload Roofs File", type=["csv", "xlsx"], key="roof_upload")
|
| 459 |
required_cols = ["Name", "Area (m²)", "U-Value (W/m²·K)", "Orientation", "Roof Type", "Roof Group", "Slope", "Absorptivity"]
|
|
|
|
| 460 |
template_data = pd.DataFrame(columns=required_cols)
|
| 461 |
template_data.loc[0] = ["Example Roof", 10.0, 0.3, "Horizontal", "Concrete Roof", "A", "Flat", 0.6]
|
| 462 |
st.download_button(label="Download Roof Template", data=template_data.to_csv(index=False), file_name="roof_template.csv", mime="text/csv")
|
|
|
|
| 496 |
with col1:
|
| 497 |
name = st.text_input("Name", "New Floor")
|
| 498 |
area = st.number_input("Area (m²)", min_value=0.0, value=1.0, step=0.1)
|
| 499 |
+
perimeter = st.number_input("Perimeter (m)", min_value=0.0, value=0.0, step=0.1)
|
| 500 |
with col2:
|
| 501 |
floor_options = self.reference_data.data["floor_types"]
|
| 502 |
selected_floor = st.selectbox("Floor Type", options=list(floor_options.keys()))
|
| 503 |
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)
|
| 504 |
ground_contact = st.selectbox("Ground Contact", ["Yes", "No"], index=0 if floor_options[selected_floor]["ground_contact"] else 1)
|
| 505 |
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
|
| 506 |
+
insulated = st.checkbox("Insulated Floor (e.g., R-10)", value=False) # NEW: Insulation option
|
| 507 |
|
| 508 |
submitted = st.form_submit_button("Add Floor")
|
| 509 |
if submitted and not session_state.add_floor_submitted:
|
|
|
|
| 511 |
new_floor = Floor(
|
| 512 |
name=name, u_value=u_value, area=area, floor_type=selected_floor,
|
| 513 |
ground_contact=(ground_contact == "Yes"), ground_temperature_c=ground_temp,
|
| 514 |
+
perimeter=perimeter, insulated=insulated
|
| 515 |
)
|
| 516 |
self.component_library.add_component(new_floor)
|
| 517 |
session_state.components['floors'].append(new_floor)
|
|
|
|
| 526 |
|
| 527 |
elif method == "File Upload":
|
| 528 |
uploaded_file = st.file_uploader("Upload Floors File", type=["csv", "xlsx"], key="floor_upload")
|
| 529 |
+
required_cols = ["Name", "Area (m²)", "U-Value (W/m²·K)", "Floor Type", "Ground Contact", "Ground Temperature (°C)", "Perimeter (m)", "Insulated"] # NEW: Added Insulated
|
| 530 |
template_data = pd.DataFrame(columns=required_cols)
|
| 531 |
+
template_data.loc[0] = ["Example Floor", 10.0, 0.4, "Concrete Slab", "Yes", 25.0, 12.0, "No"]
|
| 532 |
st.download_button(label="Download Floor Template", data=template_data.to_csv(index=False), file_name="floor_template.csv", mime="text/csv")
|
| 533 |
if uploaded_file:
|
| 534 |
df = pd.read_csv(uploaded_file) if uploaded_file.name.endswith('.csv') else pd.read_excel(uploaded_file)
|
| 535 |
if all(col in df.columns for col in required_cols):
|
| 536 |
for _, row in df.iterrows():
|
| 537 |
try:
|
| 538 |
+
insulated = str(row["Insulated"]).lower() in ["yes", "true", "1"]
|
| 539 |
new_floor = Floor(
|
| 540 |
name=str(row["Name"]), u_value=float(row["U-Value (W/m²·K)"]), area=float(row["Area (m²)"]),
|
| 541 |
floor_type=str(row["Floor Type"]), ground_contact=(str(row["Ground Contact"]).lower() == "yes"),
|
| 542 |
+
ground_temperature_c=float(row["Ground Temperature (°C)"]), perimeter=float(row["Perimeter (m)"]),
|
| 543 |
+
insulated=insulated
|
| 544 |
)
|
| 545 |
self.component_library.add_component(new_floor)
|
| 546 |
session_state.components['floors'].append(new_floor)
|
|
|
|
| 690 |
headers = {
|
| 691 |
ComponentType.WALL: ["Name", "Area (m²)", "U-Value (W/m²·K)", "Orientation", "Wall Type", "Wall Group", "Absorptivity", "Shading Coefficient", "Infiltration Rate (CFM)", "Delete"],
|
| 692 |
ComponentType.ROOF: ["Name", "Area (m²)", "U-Value (W/m²·K)", "Orientation", "Roof Type", "Roof Group", "Slope", "Absorptivity", "Delete"],
|
| 693 |
+
ComponentType.FLOOR: ["Name", "Area (m²)", "U-Value (W/m²·K)", "Floor Type", "Ground Contact", "Ground Temperature (°C)", "Perimeter (m)", "Insulated", "Delete"], # NEW: Added Insulated
|
| 694 |
ComponentType.WINDOW: ["Name", "Area (m²)", "U-Value (W/m²·K)", "Orientation", "SHGC", "Shading Device", "Shading Coefficient", "Frame Type", "Frame Percentage", "Infiltration Rate (CFM)", "Delete"],
|
| 695 |
ComponentType.DOOR: ["Name", "Area (m²)", "U-Value (W/m²·K)", "Orientation", "Door Type", "Infiltration Rate (CFM)", "Delete"]
|
| 696 |
}[component_type]
|
|
|
|
| 719 |
cols[4].write(comp.floor_type)
|
| 720 |
cols[5].write("Yes" if comp.ground_contact else "No")
|
| 721 |
cols[6].write(comp.ground_temperature_c if comp.ground_contact else "N/A")
|
| 722 |
+
cols[7].write(comp.perimeter)
|
| 723 |
+
cols[8].write("Yes" if comp.insulated else "No") # NEW: Display insulated
|
| 724 |
elif component_type == ComponentType.WINDOW:
|
| 725 |
cols[4].write(comp.shgc)
|
| 726 |
cols[5].write(comp.shading_device)
|