mabuseif commited on
Commit
7b8f794
·
verified ·
1 Parent(s): 07f89c5

Update app/component_selection.py

Browse files
Files changed (1) hide show
  1. app/component_selection.py +80 -138
app/component_selection.py CHANGED
@@ -5,6 +5,7 @@ All dependencies are included within this file for standalone operation.
5
  Updated 2025-04-28: Added surface color selection, expanded component types, and skylight support.
6
  Updated 2025-05-02: Added crack dimensions for walls/doors, drapery properties for windows/skylights, and roof height per enhancement plan.
7
  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.
 
8
 
9
  Author: Dr Majed Abuseif
10
  """
@@ -97,7 +98,7 @@ class BuildingComponent:
97
  class Wall(BuildingComponent):
98
  wall_type: str = "Brick"
99
  wall_group: str = "A" # ASHRAE group
100
- absorptivity: float = 0.6
101
  shading_coefficient: float = 1.0
102
  infiltration_rate_cfm: float = 0.0
103
  crack_length: float = 0.0 # Added for infiltration (m)
@@ -106,8 +107,8 @@ class Wall(BuildingComponent):
106
  def __post_init__(self):
107
  super().__post_init__()
108
  self.component_type = ComponentType.WALL
109
- if not 0 <= self.absorptivity <= 1:
110
- raise ValueError("Absorptivity must be between 0 and 1")
111
  if not 0 <= self.shading_coefficient <= 1:
112
  raise ValueError("Shading coefficient must be between 0 and 1")
113
  if self.infiltration_rate_cfm < 0:
@@ -124,7 +125,7 @@ class Wall(BuildingComponent):
124
  def to_dict(self) -> dict:
125
  base_dict = super().to_dict()
126
  base_dict.update({
127
- "wall_type": self.wall_type, "wall_group": self.wall_group, "absorptivity": self.absorptivity,
128
  "shading_coefficient": self.shading_coefficient, "infiltration_rate_cfm": self.infiltration_rate_cfm,
129
  "crack_length": self.crack_length, "crack_width": self.crack_width
130
  })
@@ -135,7 +136,7 @@ class Roof(BuildingComponent):
135
  roof_type: str = "Concrete"
136
  roof_group: str = "A" # ASHRAE group
137
  slope: str = "Flat"
138
- absorptivity: float = 0.6
139
  roof_height: float = 3.0 # Added for stack effect (m)
140
 
141
  def __post_init__(self):
@@ -143,8 +144,8 @@ class Roof(BuildingComponent):
143
  self.component_type = ComponentType.ROOF
144
  if not self.orientation == Orientation.HORIZONTAL:
145
  self.orientation = Orientation.HORIZONTAL
146
- if not 0 <= self.absorptivity <= 1:
147
- raise ValueError("Absorptivity must be between 0 and 1")
148
  if not 0 <= self.roof_height <= 100:
149
  raise ValueError("Roof height must be between 0 and 100 meters")
150
  VALID_ROOF_GROUPS = {"A", "B", "C", "D", "E", "F", "G"}
@@ -156,7 +157,7 @@ class Roof(BuildingComponent):
156
  base_dict = super().to_dict()
157
  base_dict.update({
158
  "roof_type": self.roof_type, "roof_group": self.roof_group, "slope": self.slope,
159
- "absorptivity": self.absorptivity, "roof_height": self.roof_height
160
  })
161
  return base_dict
162
 
@@ -316,24 +317,24 @@ class ReferenceData:
316
  "Wood": {"conductivity": 0.15}
317
  },
318
  "wall_types": {
319
- "Brick Wall": {"u_value": 2.0, "absorptivity": 0.6, "wall_group": "A"},
320
- "Insulated Brick": {"u_value": 0.5, "absorptivity": 0.6, "wall_group": "B"},
321
- "Concrete Block": {"u_value": 1.8, "absorptivity": 0.6, "wall_group": "C"},
322
- "Insulated Concrete": {"u_value": 0.4, "absorptivity": 0.6, "wall_group": "D"},
323
- "Timber Frame": {"u_value": 0.3, "absorptivity": 0.6, "wall_group": "E"},
324
- "Cavity Brick": {"u_value": 0.6, "absorptivity": 0.6, "wall_group": "F"},
325
- "Lightweight Panel": {"u_value": 1.0, "absorptivity": 0.6, "wall_group": "G"},
326
- "Reinforced Concrete": {"u_value": 1.5, "absorptivity": 0.6, "wall_group": "H"},
327
- "SIP": {"u_value": 0.25, "absorptivity": 0.6, "wall_group": "A"},
328
- "Custom": {"u_value": 0.5, "absorptivity": 0.6, "wall_group": "A"}
329
  },
330
  "roof_types": {
331
- "Concrete Roof": {"u_value": 0.3, "absorptivity": 0.6, "group": "A"},
332
- "Metal Roof": {"u_value": 1.0, "absorptivity": 0.75, "group": "B"},
333
- "Built-up Roof": {"u_value": 0.5, "absorptivity": 0.8, "group": "C"},
334
- "Insulated Metal Deck": {"u_value": 0.4, "absorptivity": 0.7, "group": "D"},
335
- "Wood Shingle": {"u_value": 0.6, "absorptivity": 0.5, "group": "E"},
336
- "Custom": {"u_value": 0.5, "absorptivity": 0.6, "group": "A"}
337
  },
338
  "roof_ventilation_methods": {
339
  "No Ventilation": 0.0,
@@ -502,7 +503,7 @@ class ComponentSelectionInterface:
502
  selected_wall = st.selectbox("Wall Type", options=list(wall_options.keys()))
503
  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)
504
  wall_group = st.selectbox("Wall Group (ASHRAE)", ["A", "B", "C", "D", "E", "F", "G", "H"], index=0)
505
- 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)
506
  shading_coefficient = st.number_input("Shading Coefficient", min_value=0.0, max_value=1.0, value=1.0, step=0.05)
507
  infiltration_rate = st.number_input("Infiltration Rate (CFM)", min_value=0.0, value=0.0, step=0.1)
508
  crack_width = st.number_input("Crack Width (m)", min_value=0.0, max_value=0.1, value=0.0, step=0.001)
@@ -510,10 +511,10 @@ class ComponentSelectionInterface:
510
  submitted = st.form_submit_button("Add Wall")
511
  if submitted and not session_state.add_wall_submitted:
512
  try:
513
- absorptivity_value = float(absorptivity.split("(")[1].strip(")"))
514
  new_wall = Wall(
515
  name=name, u_value=u_value, area=area, orientation=Orientation(orientation),
516
- wall_type=selected_wall, wall_group=wall_group, absorptivity=absorptivity_value,
517
  shading_coefficient=shading_coefficient, infiltration_rate_cfm=infiltration_rate,
518
  crack_length=crack_length, crack_width=crack_width
519
  )
@@ -532,7 +533,7 @@ class ComponentSelectionInterface:
532
  uploaded_file = st.file_uploader("Upload Walls File", type=["csv", "xlsx"], key="wall_upload")
533
  required_cols = [
534
  "Name", "Area (m²)", "U-Value (W/m²·K)", "Orientation", "Wall Type", "Wall Group",
535
- "Absorptivity", "Shading Coefficient", "Infiltration Rate (CFM)",
536
  "Crack Length (m)", "Crack Width (m)"
537
  ]
538
  template_data = pd.DataFrame(columns=required_cols)
@@ -553,7 +554,7 @@ class ComponentSelectionInterface:
553
  new_wall = Wall(
554
  name=str(row["Name"]), u_value=float(row["U-Value (W/m²·K)"]), area=float(row["Area (m²)"]),
555
  orientation=Orientation(row["Orientation"]), wall_type=str(row["Wall Type"]),
556
- wall_group=str(row["Wall Group"]), absorptivity=float(row["Absorptivity"]),
557
  shading_coefficient=float(row["Shading Coefficient"]),
558
  infiltration_rate_cfm=float(row["Infiltration Rate (CFM)"]),
559
  crack_length=float(row["Crack Length (m)"]),
@@ -587,16 +588,16 @@ class ComponentSelectionInterface:
587
  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)
588
  roof_group = st.selectbox("Roof Group (ASHRAE)", ["A", "B", "C", "D", "E", "F", "G"], index=0)
589
  slope = st.selectbox("Roof Slope", ["Flat", "Low Slope", "Steep Slope"], index=0)
590
- 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)
591
 
592
  submitted = st.form_submit_button("Add Roof")
593
  if submitted and not session_state.add_roof_submitted:
594
  try:
595
- absorptivity_value = float(absorptivity.split("(")[1].strip(")"))
596
  new_roof = Roof(
597
  name=name, u_value=u_value, area=area, orientation=Orientation.HORIZONTAL,
598
  roof_type=selected_roof, roof_group=roof_group, slope=slope,
599
- absorptivity=absorptivity_value, roof_height=roof_height
600
  )
601
  self.component_library.add_component(new_roof)
602
  session_state.components['roofs'].append(new_roof)
@@ -628,7 +629,7 @@ class ComponentSelectionInterface:
628
  uploaded_file = st.file_uploader("Upload Roofs File", type=["csv", "xlsx"], key="roof_upload")
629
  required_cols = [
630
  "Name", "Area (m²)", "U-Value (W/m²·K)", "Roof Type", "Roof Group", "Slope",
631
- "Absorptivity", "Roof Height (m)"
632
  ]
633
  template_data = pd.DataFrame(columns=required_cols)
634
  template_data.loc[0] = [
@@ -649,7 +650,7 @@ class ComponentSelectionInterface:
649
  name=str(row["Name"]), u_value=float(row["U-Value (W/m²·K)"]), area=float(row["Area (m²)"]),
650
  orientation=Orientation.HORIZONTAL, roof_type=str(row["Roof Type"]),
651
  roof_group=str(row["Roof Group"]), slope=str(row["Slope"]),
652
- absorptivity=float(row["Absorptivity"]), roof_height=float(row["Roof Height (m)"])
653
  )
654
  self.component_library.add_component(new_roof)
655
  session_state.components['roofs'].append(new_roof)
@@ -1068,11 +1069,11 @@ class ComponentSelectionInterface:
1068
  headers = {
1069
  ComponentType.WALL: [
1070
  "Name", "Area (m²)", "U-Value (W/m²·K)", "Orientation", "Wall Type", "Wall Group",
1071
- "Absorptivity", "Crack Length (m)", "Crack Width (m)", "Edit", "Delete"
1072
  ],
1073
  ComponentType.ROOF: [
1074
  "Name", "Area (m²)", "U-Value (W/m²·K)", "Roof Type", "Roof Group", "Slope",
1075
- "Absorptivity", "Roof Height (m)", "Edit", "Delete"
1076
  ],
1077
  ComponentType.FLOOR: [
1078
  "Name", "Area (m²)", "U-Value (W/m²·K)", "Floor Type", "Ground Contact",
@@ -1105,7 +1106,7 @@ class ComponentSelectionInterface:
1105
  cols[3].write(comp.orientation.value)
1106
  cols[4].write(comp.wall_type)
1107
  cols[5].write(comp.wall_group)
1108
- cols[6].write(comp.absorptivity)
1109
  cols[7].write(comp.crack_length)
1110
  cols[8].write(comp.crack_width)
1111
  edit_col = 9
@@ -1113,7 +1114,7 @@ class ComponentSelectionInterface:
1113
  cols[3].write(comp.roof_type)
1114
  cols[4].write(comp.roof_group)
1115
  cols[5].write(comp.slope)
1116
- cols[6].write(comp.absorptivity)
1117
  cols[7].write(comp.roof_height)
1118
  edit_col = 8
1119
  elif component_type == ComponentType.FLOOR:
@@ -1189,37 +1190,28 @@ class ComponentSelectionInterface:
1189
  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)
1190
  u_value = st.number_input("U-Value (W/m²·K)", min_value=0.0, value=wall.u_value, step=0.01)
1191
  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))
1192
- 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)', 'Light to Medium (0.45)', 'Medium (0.6)', 'Medium to Dark (0.75)', 'Dark (0.9)'][np.argmin([abs(float(opt.split('(')[1].strip(')')) - wall.absorptivity) for opt in ['Light (0.3)', 'Light to Medium (0.45)', 'Medium (0.6)', 'Medium to Dark (0.75)', 'Dark (0.9)']])]}"))
1193
  shading_coefficient = st.number_input("Shading Coefficient", min_value=0.0, max_value=1.0, value=wall.shading_coefficient, step=0.05)
1194
  infiltration_rate = st.number_input("Infiltration Rate (CFM)", min_value=0.0, value=wall.infiltration_rate_cfm, step=0.1)
1195
  crack_width = st.number_input("Crack Width (m)", min_value=0.0, max_value=0.1, value=wall.crack_width, step=0.001)
1196
 
1197
- col3, col4 = st.columns(2)
1198
- with col3:
1199
- submitted = st.form_submit_button("Update Wall")
1200
- with col4:
1201
- cancelled = st.form_submit_button("Cancel")
1202
-
1203
- if submitted:
1204
- try:
1205
- absorptivity_value = float(absorptivity.split("(")[1].strip(")"))
1206
- updated_wall = Wall(
1207
- id=wall.id, name=name, u_value=u_value, area=area, orientation=Orientation(orientation),
1208
- wall_type=selected_wall, wall_group=wall_group, absorptivity=absorptivity_value,
1209
- shading_coefficient=shading_coefficient, infiltration_rate_cfm=infiltration_rate,
1210
- crack_length=crack_length, crack_width=crack_width
1211
- )
1212
- self.component_library.components[wall.id] = updated_wall
1213
- session_state.components['walls'][index] = updated_wall
1214
- st.success(f"Updated {name}")
1215
- session_state[f"edit_wall"] = None
1216
- st.rerun()
1217
- except ValueError as e:
1218
- st.error(f"Error: {str(e)}")
1219
-
1220
- if cancelled:
1221
  session_state[f"edit_wall"] = None
 
1222
  st.rerun()
 
 
1223
 
1224
  def _display_edit_roof_form(self, session_state: Any, roof: Roof, index: int) -> None:
1225
  with st.form(f"edit_roof_form_{index}", clear_on_submit=True):
@@ -1234,33 +1226,24 @@ class ComponentSelectionInterface:
1234
  u_value = st.number_input("U-Value (W/m²·K)", min_value=0.0, value=roof.u_value, step=0.01)
1235
  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))
1236
  slope = st.selectbox("Roof Slope", ["Flat", "Low Slope", "Steep Slope"], index=["Flat", "Low Slope", "Steep Slope"].index(roof.slope))
1237
- 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)', 'Light to Medium (0.45)', 'Medium (0.6)', 'Medium to Dark (0.75)', 'Dark (0.9)'][np.argmin([abs(float(opt.split('(')[1].strip(')')) - roof.absorptivity) for opt in ['Light (0.3)', 'Light to Medium (0.45)', 'Medium (0.6)', 'Medium to Dark (0.75)', 'Dark (0.9)']])]}"))
1238
-
1239
- col3, col4 = st.columns(2)
1240
- with col3:
1241
- submitted = st.form_submit_button("Update Roof")
1242
- with col4:
1243
- cancelled = st.form_submit_button("Cancel")
1244
 
 
1245
  if submitted:
1246
  try:
1247
- absorptivity_value = float(absorptivity.split("(")[1].strip(")"))
1248
  updated_roof = Roof(
1249
  id=roof.id, name=name, u_value=u_value, area=area, orientation=Orientation.HORIZONTAL,
1250
  roof_type=selected_roof, roof_group=roof_group, slope=slope,
1251
- absorptivity=absorptivity_value, roof_height=roof_height
1252
  )
1253
  self.component_library.components[roof.id] = updated_roof
1254
  session_state.components['roofs'][index] = updated_roof
1255
- st.success(f"Updated {name}")
1256
  session_state[f"edit_roof"] = None
 
1257
  st.rerun()
1258
  except ValueError as e:
1259
  st.error(f"Error: {str(e)}")
1260
-
1261
- if cancelled:
1262
- session_state[f"edit_roof"] = None
1263
- st.rerun()
1264
 
1265
  def _display_edit_floor_form(self, session_state: Any, floor: Floor, index: int) -> None:
1266
  with st.form(f"edit_floor_form_{index}", clear_on_submit=True):
@@ -1274,15 +1257,10 @@ class ComponentSelectionInterface:
1274
  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)
1275
  u_value = st.number_input("U-Value (W/m²·K)", min_value=0.0, value=floor.u_value, step=0.01)
1276
  ground_contact = st.selectbox("Ground Contact", ["Yes", "No"], index=0 if floor.ground_contact else 1)
1277
- 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 floor.ground_temperature_c
1278
  insulated = st.checkbox("Insulated Floor (e.g., R-10)", value=floor.insulated)
1279
 
1280
- col3, col4 = st.columns(2)
1281
- with col3:
1282
- submitted = st.form_submit_button("Update Floor")
1283
- with col4:
1284
- cancelled = st.form_submit_button("Cancel")
1285
-
1286
  if submitted:
1287
  try:
1288
  updated_floor = Floor(
@@ -1292,15 +1270,11 @@ class ComponentSelectionInterface:
1292
  )
1293
  self.component_library.components[floor.id] = updated_floor
1294
  session_state.components['floors'][index] = updated_floor
1295
- st.success(f"Updated {name}")
1296
  session_state[f"edit_floor"] = None
 
1297
  st.rerun()
1298
  except ValueError as e:
1299
  st.error(f"Error: {str(e)}")
1300
-
1301
- if cancelled:
1302
- session_state[f"edit_floor"] = None
1303
- st.rerun()
1304
 
1305
  def _display_edit_window_form(self, session_state: Any, window: Window, index: int) -> None:
1306
  with st.form(f"edit_window_form_{index}", clear_on_submit=True):
@@ -1326,12 +1300,7 @@ class ComponentSelectionInterface:
1326
  drapery_color = st.selectbox("Drapery Color", [c.value for c in DraperyColor], index=[c.value for c in DraperyColor].index(window.drapery_color))
1327
  drapery_fullness = st.number_input("Drapery Fullness", min_value=1.0, max_value=2.0, value=window.drapery_fullness, step=0.1)
1328
 
1329
- col3, col4 = st.columns(2)
1330
- with col3:
1331
- submitted = st.form_submit_button("Update Window")
1332
- with col4:
1333
- cancelled = st.form_submit_button("Cancel")
1334
-
1335
  if submitted:
1336
  try:
1337
  updated_window = Window(
@@ -1343,15 +1312,11 @@ class ComponentSelectionInterface:
1343
  )
1344
  self.component_library.components[window.id] = updated_window
1345
  session_state.components['windows'][index] = updated_window
1346
- st.success(f"Updated {name}")
1347
  session_state[f"edit_window"] = None
 
1348
  st.rerun()
1349
  except ValueError as e:
1350
  st.error(f"Error: {str(e)}")
1351
-
1352
- if cancelled:
1353
- session_state[f"edit_window"] = None
1354
- st.rerun()
1355
 
1356
  def _display_edit_door_form(self, session_state: Any, door: Door, index: int) -> None:
1357
  with st.form(f"edit_door_form_{index}", clear_on_submit=True):
@@ -1368,12 +1333,7 @@ class ComponentSelectionInterface:
1368
  infiltration_rate = st.number_input("Infiltration Rate (CFM)", min_value=0.0, value=door.infiltration_rate_cfm, step=0.1)
1369
  crack_width = st.number_input("Crack Width (m)", min_value=0.0, max_value=0.1, value=door.crack_width, step=0.001)
1370
 
1371
- col3, col4 = st.columns(2)
1372
- with col3:
1373
- submitted = st.form_submit_button("Update Door")
1374
- with col4:
1375
- cancelled = st.form_submit_button("Cancel")
1376
-
1377
  if submitted:
1378
  try:
1379
  updated_door = Door(
@@ -1383,15 +1343,11 @@ class ComponentSelectionInterface:
1383
  )
1384
  self.component_library.components[door.id] = updated_door
1385
  session_state.components['doors'][index] = updated_door
1386
- st.success(f"Updated {name}")
1387
  session_state[f"edit_door"] = None
 
1388
  st.rerun()
1389
  except ValueError as e:
1390
  st.error(f"Error: {str(e)}")
1391
-
1392
- if cancelled:
1393
- session_state[f"edit_door"] = None
1394
- st.rerun()
1395
 
1396
  def _display_edit_skylight_form(self, session_state: Any, skylight: Skylight, index: int) -> None:
1397
  with st.form(f"edit_skylight_form_{index}", clear_on_submit=True):
@@ -1416,12 +1372,7 @@ class ComponentSelectionInterface:
1416
  drapery_color = st.selectbox("Drapery Color", [c.value for c in DraperyColor], index=[c.value for c in DraperyColor].index(skylight.drapery_color))
1417
  drapery_fullness = st.number_input("Drapery Fullness", min_value=1.0, max_value=2.0, value=skylight.drapery_fullness, step=0.1)
1418
 
1419
- col3, col4 = st.columns(2)
1420
- with col3:
1421
- submitted = st.form_submit_button("Update Skylight")
1422
- with col4:
1423
- cancelled = st.form_submit_button("Cancel")
1424
-
1425
  if submitted:
1426
  try:
1427
  updated_skylight = Skylight(
@@ -1433,38 +1384,29 @@ class ComponentSelectionInterface:
1433
  )
1434
  self.component_library.components[skylight.id] = updated_skylight
1435
  session_state.components['skylights'][index] = updated_skylight
1436
- st.success(f"Updated {name}")
1437
  session_state[f"edit_skylight"] = None
 
1438
  st.rerun()
1439
  except ValueError as e:
1440
  st.error(f"Error: {str(e)}")
1441
-
1442
- if cancelled:
1443
- session_state[f"edit_skylight"] = None
1444
- st.rerun()
1445
 
1446
  def _save_components(self, session_state: Any) -> None:
1447
  components_data = {
1448
- "walls": [comp.to_dict() for comp in session_state.components.get('walls', [])],
1449
- "roofs": [comp.to_dict() for comp in session_state.components.get('roofs', [])],
1450
- "floors": [comp.to_dict() for comp in session_state.components.get('floors', [])],
1451
- "windows": [comp.to_dict() for comp in session_state.components.get('windows', [])],
1452
- "doors": [comp.to_dict() for comp in session_state.components.get('doors', [])],
1453
- "skylights": [comp.to_dict() for comp in session_state.components.get('skylights', [])],
1454
  "roof_air_volume_m3": session_state.roof_air_volume_m3,
1455
  "roof_ventilation_ach": session_state.roof_ventilation_ach
1456
  }
1457
- output = io.StringIO()
1458
- json.dump(components_data, output, indent=4)
1459
  st.download_button(
1460
  label="Download Components JSON",
1461
- data=output.getvalue(),
1462
- file_name="components.json",
1463
  mime="application/json"
1464
  )
1465
- st.success("Components saved! Click the button to download the JSON file.")
1466
-
1467
- # --- Main Execution ---
1468
- if __name__ == "__main__":
1469
- interface = ComponentSelectionInterface()
1470
- interface.display_component_selection(st.session_state)
 
5
  Updated 2025-04-28: Added surface color selection, expanded component types, and skylight support.
6
  Updated 2025-05-02: Added crack dimensions for walls/doors, drapery properties for windows/skylights, and roof height per enhancement plan.
7
  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.
8
+ Updated 2025-05-09: Changed 'absorptivity' to 'solar_absorptivity' to align with building_components.py.
9
 
10
  Author: Dr Majed Abuseif
11
  """
 
98
  class Wall(BuildingComponent):
99
  wall_type: str = "Brick"
100
  wall_group: str = "A" # ASHRAE group
101
+ solar_absorptivity: float = 0.6
102
  shading_coefficient: float = 1.0
103
  infiltration_rate_cfm: float = 0.0
104
  crack_length: float = 0.0 # Added for infiltration (m)
 
107
  def __post_init__(self):
108
  super().__post_init__()
109
  self.component_type = ComponentType.WALL
110
+ if not 0 <= self.solar_absorptivity <= 1:
111
+ raise ValueError("Solar absorptivity must be between 0 and 1")
112
  if not 0 <= self.shading_coefficient <= 1:
113
  raise ValueError("Shading coefficient must be between 0 and 1")
114
  if self.infiltration_rate_cfm < 0:
 
125
  def to_dict(self) -> dict:
126
  base_dict = super().to_dict()
127
  base_dict.update({
128
+ "wall_type": self.wall_type, "wall_group": self.wall_group, "solar_absorptivity": self.solar_absorptivity,
129
  "shading_coefficient": self.shading_coefficient, "infiltration_rate_cfm": self.infiltration_rate_cfm,
130
  "crack_length": self.crack_length, "crack_width": self.crack_width
131
  })
 
136
  roof_type: str = "Concrete"
137
  roof_group: str = "A" # ASHRAE group
138
  slope: str = "Flat"
139
+ solar_absorptivity: float = 0.6
140
  roof_height: float = 3.0 # Added for stack effect (m)
141
 
142
  def __post_init__(self):
 
144
  self.component_type = ComponentType.ROOF
145
  if not self.orientation == Orientation.HORIZONTAL:
146
  self.orientation = Orientation.HORIZONTAL
147
+ if not 0 <= self.solar_absorptivity <= 1:
148
+ raise ValueError("Solar absorptivity must be between 0 and 1")
149
  if not 0 <= self.roof_height <= 100:
150
  raise ValueError("Roof height must be between 0 and 100 meters")
151
  VALID_ROOF_GROUPS = {"A", "B", "C", "D", "E", "F", "G"}
 
157
  base_dict = super().to_dict()
158
  base_dict.update({
159
  "roof_type": self.roof_type, "roof_group": self.roof_group, "slope": self.slope,
160
+ "solar_absorptivity": self.solar_absorptivity, "roof_height": self.roof_height
161
  })
162
  return base_dict
163
 
 
317
  "Wood": {"conductivity": 0.15}
318
  },
319
  "wall_types": {
320
+ "Brick Wall": {"u_value": 2.0, "solar_absorptivity": 0.6, "wall_group": "A"},
321
+ "Insulated Brick": {"u_value": 0.5, "solar_absorptivity": 0.6, "wall_group": "B"},
322
+ "Concrete Block": {"u_value": 1.8, "solar_absorptivity": 0.6, "wall_group": "C"},
323
+ "Insulated Concrete": {"u_value": 0.4, "solar_absorptivity": 0.6, "wall_group": "D"},
324
+ "Timber Frame": {"u_value": 0.3, "solar_absorptivity": 0.6, "wall_group": "E"},
325
+ "Cavity Brick": {"u_value": 0.6, "solar_absorptivity": 0.6, "wall_group": "F"},
326
+ "Lightweight Panel": {"u_value": 1.0, "solar_absorptivity": 0.6, "wall_group": "G"},
327
+ "Reinforced Concrete": {"u_value": 1.5, "solar_absorptivity": 0.6, "wall_group": "H"},
328
+ "SIP": {"u_value": 0.25, "solar_absorptivity": 0.6, "wall_group": "A"},
329
+ "Custom": {"u_value": 0.5, "solar_absorptivity": 0.6, "wall_group": "A"}
330
  },
331
  "roof_types": {
332
+ "Concrete Roof": {"u_value": 0.3, "solar_absorptivity": 0.6, "group": "A"},
333
+ "Metal Roof": {"u_value": 1.0, "solar_absorptivity": 0.75, "group": "B"},
334
+ "Built-up Roof": {"u_value": 0.5, "solar_absorptivity": 0.8, "group": "C"},
335
+ "Insulated Metal Deck": {"u_value": 0.4, "solar_absorptivity": 0.7, "group": "D"},
336
+ "Wood Shingle": {"u_value": 0.6, "solar_absorptivity": 0.5, "group": "E"},
337
+ "Custom": {"u_value": 0.5, "solar_absorptivity": 0.6, "group": "A"}
338
  },
339
  "roof_ventilation_methods": {
340
  "No Ventilation": 0.0,
 
503
  selected_wall = st.selectbox("Wall Type", options=list(wall_options.keys()))
504
  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)
505
  wall_group = st.selectbox("Wall Group (ASHRAE)", ["A", "B", "C", "D", "E", "F", "G", "H"], index=0)
506
+ 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)
507
  shading_coefficient = st.number_input("Shading Coefficient", min_value=0.0, max_value=1.0, value=1.0, step=0.05)
508
  infiltration_rate = st.number_input("Infiltration Rate (CFM)", min_value=0.0, value=0.0, step=0.1)
509
  crack_width = st.number_input("Crack Width (m)", min_value=0.0, max_value=0.1, value=0.0, step=0.001)
 
511
  submitted = st.form_submit_button("Add Wall")
512
  if submitted and not session_state.add_wall_submitted:
513
  try:
514
+ solar_absorptivity_value = float(solar_absorptivity.split("(")[1].strip(")"))
515
  new_wall = Wall(
516
  name=name, u_value=u_value, area=area, orientation=Orientation(orientation),
517
+ wall_type=selected_wall, wall_group=wall_group, solar_absorptivity=solar_absorptivity_value,
518
  shading_coefficient=shading_coefficient, infiltration_rate_cfm=infiltration_rate,
519
  crack_length=crack_length, crack_width=crack_width
520
  )
 
533
  uploaded_file = st.file_uploader("Upload Walls File", type=["csv", "xlsx"], key="wall_upload")
534
  required_cols = [
535
  "Name", "Area (m²)", "U-Value (W/m²·K)", "Orientation", "Wall Type", "Wall Group",
536
+ "Solar Absorptivity", "Shading Coefficient", "Infiltration Rate (CFM)",
537
  "Crack Length (m)", "Crack Width (m)"
538
  ]
539
  template_data = pd.DataFrame(columns=required_cols)
 
554
  new_wall = Wall(
555
  name=str(row["Name"]), u_value=float(row["U-Value (W/m²·K)"]), area=float(row["Area (m²)"]),
556
  orientation=Orientation(row["Orientation"]), wall_type=str(row["Wall Type"]),
557
+ wall_group=str(row["Wall Group"]), solar_absorptivity=float(row["Solar Absorptivity"]),
558
  shading_coefficient=float(row["Shading Coefficient"]),
559
  infiltration_rate_cfm=float(row["Infiltration Rate (CFM)"]),
560
  crack_length=float(row["Crack Length (m)"]),
 
588
  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)
589
  roof_group = st.selectbox("Roof Group (ASHRAE)", ["A", "B", "C", "D", "E", "F", "G"], index=0)
590
  slope = st.selectbox("Roof Slope", ["Flat", "Low Slope", "Steep Slope"], index=0)
591
+ 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)
592
 
593
  submitted = st.form_submit_button("Add Roof")
594
  if submitted and not session_state.add_roof_submitted:
595
  try:
596
+ solar_absorptivity_value = float(solar_absorptivity.split("(")[1].strip(")"))
597
  new_roof = Roof(
598
  name=name, u_value=u_value, area=area, orientation=Orientation.HORIZONTAL,
599
  roof_type=selected_roof, roof_group=roof_group, slope=slope,
600
+ solar_absorptivity=solar_absorptivity_value, roof_height=roof_height
601
  )
602
  self.component_library.add_component(new_roof)
603
  session_state.components['roofs'].append(new_roof)
 
629
  uploaded_file = st.file_uploader("Upload Roofs File", type=["csv", "xlsx"], key="roof_upload")
630
  required_cols = [
631
  "Name", "Area (m²)", "U-Value (W/m²·K)", "Roof Type", "Roof Group", "Slope",
632
+ "Solar Absorptivity", "Roof Height (m)"
633
  ]
634
  template_data = pd.DataFrame(columns=required_cols)
635
  template_data.loc[0] = [
 
650
  name=str(row["Name"]), u_value=float(row["U-Value (W/m²·K)"]), area=float(row["Area (m²)"]),
651
  orientation=Orientation.HORIZONTAL, roof_type=str(row["Roof Type"]),
652
  roof_group=str(row["Roof Group"]), slope=str(row["Slope"]),
653
+ solar_absorptivity=float(row["Solar Absorptivity"]), roof_height=float(row["Roof Height (m)"])
654
  )
655
  self.component_library.add_component(new_roof)
656
  session_state.components['roofs'].append(new_roof)
 
1069
  headers = {
1070
  ComponentType.WALL: [
1071
  "Name", "Area (m²)", "U-Value (W/m²·K)", "Orientation", "Wall Type", "Wall Group",
1072
+ "Solar Absorptivity", "Crack Length (m)", "Crack Width (m)", "Edit", "Delete"
1073
  ],
1074
  ComponentType.ROOF: [
1075
  "Name", "Area (m²)", "U-Value (W/m²·K)", "Roof Type", "Roof Group", "Slope",
1076
+ "Solar Absorptivity", "Roof Height (m)", "Edit", "Delete"
1077
  ],
1078
  ComponentType.FLOOR: [
1079
  "Name", "Area (m²)", "U-Value (W/m²·K)", "Floor Type", "Ground Contact",
 
1106
  cols[3].write(comp.orientation.value)
1107
  cols[4].write(comp.wall_type)
1108
  cols[5].write(comp.wall_group)
1109
+ cols[6].write(comp.solar_absorptivity)
1110
  cols[7].write(comp.crack_length)
1111
  cols[8].write(comp.crack_width)
1112
  edit_col = 9
 
1114
  cols[3].write(comp.roof_type)
1115
  cols[4].write(comp.roof_group)
1116
  cols[5].write(comp.slope)
1117
+ cols[6].write(comp.solar_absorptivity)
1118
  cols[7].write(comp.roof_height)
1119
  edit_col = 8
1120
  elif component_type == ComponentType.FLOOR:
 
1190
  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)
1191
  u_value = st.number_input("U-Value (W/m²·K)", min_value=0.0, value=wall.u_value, step=0.01)
1192
  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))
1193
+ 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)'}"))
1194
  shading_coefficient = st.number_input("Shading Coefficient", min_value=0.0, max_value=1.0, value=wall.shading_coefficient, step=0.05)
1195
  infiltration_rate = st.number_input("Infiltration Rate (CFM)", min_value=0.0, value=wall.infiltration_rate_cfm, step=0.1)
1196
  crack_width = st.number_input("Crack Width (m)", min_value=0.0, max_value=0.1, value=wall.crack_width, step=0.001)
1197
 
1198
+ submitted = st.form_submit_button("Update Wall")
1199
+ if submitted:
1200
+ try:
1201
+ solar_absorptivity_value = float(solar_absorptivity.split("(")[1].strip(")"))
1202
+ updated_wall = Wall(
1203
+ id=wall.id, name=name, u_value=u_value, area=area, orientation=Orientation(orientation),
1204
+ wall_type=selected_wall, wall_group=wall_group, solar_absorptivity=solar_absorptivity_value,
1205
+ shading_coefficient=shading_coefficient, infiltration_rate_cfm=infiltration_rate,
1206
+ crack_length=crack_length, crack_width=crack_width
1207
+ )
1208
+ self.component_library.components[wall.id] = updated_wall
1209
+ session_state.components['walls'][index] = updated_wall
 
 
 
 
 
 
 
 
 
 
 
 
1210
  session_state[f"edit_wall"] = None
1211
+ st.success(f"Updated {name}")
1212
  st.rerun()
1213
+ except ValueError as e:
1214
+ st.error(f"Error: {str(e)}")
1215
 
1216
  def _display_edit_roof_form(self, session_state: Any, roof: Roof, index: int) -> None:
1217
  with st.form(f"edit_roof_form_{index}", clear_on_submit=True):
 
1226
  u_value = st.number_input("U-Value (W/m²·K)", min_value=0.0, value=roof.u_value, step=0.01)
1227
  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))
1228
  slope = st.selectbox("Roof Slope", ["Flat", "Low Slope", "Steep Slope"], index=["Flat", "Low Slope", "Steep Slope"].index(roof.slope))
1229
+ 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)'}"))
 
 
 
 
 
 
1230
 
1231
+ submitted = st.form_submit_button("Update Roof")
1232
  if submitted:
1233
  try:
1234
+ solar_absorptivity_value = float(solar_absorptivity.split("(")[1].strip(")"))
1235
  updated_roof = Roof(
1236
  id=roof.id, name=name, u_value=u_value, area=area, orientation=Orientation.HORIZONTAL,
1237
  roof_type=selected_roof, roof_group=roof_group, slope=slope,
1238
+ solar_absorptivity=solar_absorptivity_value, roof_height=roof_height
1239
  )
1240
  self.component_library.components[roof.id] = updated_roof
1241
  session_state.components['roofs'][index] = updated_roof
 
1242
  session_state[f"edit_roof"] = None
1243
+ st.success(f"Updated {name}")
1244
  st.rerun()
1245
  except ValueError as e:
1246
  st.error(f"Error: {str(e)}")
 
 
 
 
1247
 
1248
  def _display_edit_floor_form(self, session_state: Any, floor: Floor, index: int) -> None:
1249
  with st.form(f"edit_floor_form_{index}", clear_on_submit=True):
 
1257
  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)
1258
  u_value = st.number_input("U-Value (W/m²·K)", min_value=0.0, value=floor.u_value, step=0.01)
1259
  ground_contact = st.selectbox("Ground Contact", ["Yes", "No"], index=0 if floor.ground_contact else 1)
1260
+ 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
1261
  insulated = st.checkbox("Insulated Floor (e.g., R-10)", value=floor.insulated)
1262
 
1263
+ submitted = st.form_submit_button("Update Floor")
 
 
 
 
 
1264
  if submitted:
1265
  try:
1266
  updated_floor = Floor(
 
1270
  )
1271
  self.component_library.components[floor.id] = updated_floor
1272
  session_state.components['floors'][index] = updated_floor
 
1273
  session_state[f"edit_floor"] = None
1274
+ st.success(f"Updated {name}")
1275
  st.rerun()
1276
  except ValueError as e:
1277
  st.error(f"Error: {str(e)}")
 
 
 
 
1278
 
1279
  def _display_edit_window_form(self, session_state: Any, window: Window, index: int) -> None:
1280
  with st.form(f"edit_window_form_{index}", clear_on_submit=True):
 
1300
  drapery_color = st.selectbox("Drapery Color", [c.value for c in DraperyColor], index=[c.value for c in DraperyColor].index(window.drapery_color))
1301
  drapery_fullness = st.number_input("Drapery Fullness", min_value=1.0, max_value=2.0, value=window.drapery_fullness, step=0.1)
1302
 
1303
+ submitted = st.form_submit_button("Update Window")
 
 
 
 
 
1304
  if submitted:
1305
  try:
1306
  updated_window = Window(
 
1312
  )
1313
  self.component_library.components[window.id] = updated_window
1314
  session_state.components['windows'][index] = updated_window
 
1315
  session_state[f"edit_window"] = None
1316
+ st.success(f"Updated {name}")
1317
  st.rerun()
1318
  except ValueError as e:
1319
  st.error(f"Error: {str(e)}")
 
 
 
 
1320
 
1321
  def _display_edit_door_form(self, session_state: Any, door: Door, index: int) -> None:
1322
  with st.form(f"edit_door_form_{index}", clear_on_submit=True):
 
1333
  infiltration_rate = st.number_input("Infiltration Rate (CFM)", min_value=0.0, value=door.infiltration_rate_cfm, step=0.1)
1334
  crack_width = st.number_input("Crack Width (m)", min_value=0.0, max_value=0.1, value=door.crack_width, step=0.001)
1335
 
1336
+ submitted = st.form_submit_button("Update Door")
 
 
 
 
 
1337
  if submitted:
1338
  try:
1339
  updated_door = Door(
 
1343
  )
1344
  self.component_library.components[door.id] = updated_door
1345
  session_state.components['doors'][index] = updated_door
 
1346
  session_state[f"edit_door"] = None
1347
+ st.success(f"Updated {name}")
1348
  st.rerun()
1349
  except ValueError as e:
1350
  st.error(f"Error: {str(e)}")
 
 
 
 
1351
 
1352
  def _display_edit_skylight_form(self, session_state: Any, skylight: Skylight, index: int) -> None:
1353
  with st.form(f"edit_skylight_form_{index}", clear_on_submit=True):
 
1372
  drapery_color = st.selectbox("Drapery Color", [c.value for c in DraperyColor], index=[c.value for c in DraperyColor].index(skylight.drapery_color))
1373
  drapery_fullness = st.number_input("Drapery Fullness", min_value=1.0, max_value=2.0, value=skylight.drapery_fullness, step=0.1)
1374
 
1375
+ submitted = st.form_submit_button("Update Skylight")
 
 
 
 
 
1376
  if submitted:
1377
  try:
1378
  updated_skylight = Skylight(
 
1384
  )
1385
  self.component_library.components[skylight.id] = updated_skylight
1386
  session_state.components['skylights'][index] = updated_skylight
 
1387
  session_state[f"edit_skylight"] = None
1388
+ st.success(f"Updated {name}")
1389
  st.rerun()
1390
  except ValueError as e:
1391
  st.error(f"Error: {str(e)}")
 
 
 
 
1392
 
1393
  def _save_components(self, session_state: Any) -> None:
1394
  components_data = {
1395
+ "walls": [comp.to_dict() for comp in session_state.components['walls']],
1396
+ "roofs": [comp.to_dict() for comp in session_state.components['roofs']],
1397
+ "floors": [comp.to_dict() for comp in session_state.components['floors']],
1398
+ "windows": [comp.to_dict() for comp in session_state.components['windows']],
1399
+ "doors": [comp.to_dict() for comp in session_state.components['doors']],
1400
+ "skylights": [comp.to_dict() for comp in session_state.components['skylights']],
1401
  "roof_air_volume_m3": session_state.roof_air_volume_m3,
1402
  "roof_ventilation_ach": session_state.roof_ventilation_ach
1403
  }
1404
+ buffer = io.StringIO()
1405
+ json.dump(components_data, buffer, indent=2)
1406
  st.download_button(
1407
  label="Download Components JSON",
1408
+ data=buffer.getvalue(),
1409
+ file_name="building_components.json",
1410
  mime="application/json"
1411
  )
1412
+ st.success("Components saved to JSON file for download.")