mabuseif commited on
Commit
4126eed
·
verified ·
1 Parent(s): 4a4dc8d

Update app/hvac_loads.py

Browse files
Files changed (1) hide show
  1. app/hvac_loads.py +457 -447
app/hvac_loads.py CHANGED
@@ -1150,476 +1150,486 @@ def display_hvac_results_ui(loads: List[Dict[str, Any]], run_id: str = "default"
1150
  def display_hvac_loads_page():
1151
  """
1152
  Display the HVAC Loads page in the Streamlit application.
1153
- Checks for existing results in session state before recalculating.
1154
- Clears previous results when a new simulation is run.
1155
  """
1156
  try:
1157
  st.header("HVAC Loads")
1158
  st.markdown("Configure and calculate HVAC loads for the building.")
1159
 
1160
- # Initialize hvac_loads in project_data if not present
1161
- if "hvac_loads" not in st.session_state.project_data:
1162
- st.session_state.project_data["hvac_loads"] = {
1163
- "cooling": {"hourly": [], "peak": 0, "charts": {}, "breakdown": {}},
1164
- "heating": {"hourly": [], "peak": 0, "charts": {}, "breakdown": {}}
1165
- }
1166
-
1167
- # Generate a unique run ID for this session
1168
- run_id = str(uuid.uuid4())
1169
-
1170
- # Check for existing results
1171
- loads = []
1172
- if st.session_state.project_data["hvac_loads"]["cooling"]["hourly"] or \
1173
- st.session_state.project_data["hvac_loads"]["heating"]["hourly"]:
1174
- loads = (
1175
- st.session_state.project_data["hvac_loads"]["cooling"]["hourly"] +
1176
- st.session_state.project_data["hvac_loads"]["heating"]["hourly"]
1177
- )
1178
- # Sort loads by month, day, hour to ensure consistent display
1179
- loads = sorted(loads, key=lambda x: (x["month"], x["day"], x["hour"]))
1180
- if loads:
1181
- st.info("Displaying previously calculated HVAC load results.")
1182
- display_hvac_results_ui(loads, run_id=run_id)
1183
-
1184
- # Location Information
1185
- st.subheader("Location Information")
1186
- climate_data = st.session_state.project_data["climate_data"]
1187
- location_data = {
1188
- "Country": climate_data.get("location", {}).get("country", ""),
1189
- "City": climate_data.get("location", {}).get("city", ""),
1190
- "State/Province": climate_data.get("location", {}).get("state_province", ""),
1191
- "Latitude": climate_data.get("location", {}).get("latitude", 0.0),
1192
- "Longitude": climate_data.get("location", {}).get("longitude", 0.0),
1193
- "Elevation": climate_data.get("location", {}).get("elevation", 0.0),
1194
- "Time Zone": climate_data.get("location", {}).get("timezone", "UTC"),
1195
- "Ground Reflectivity": climate_data.get("ground_reflectivity", 0.2)
1196
- }
1197
-
1198
- # Create two rows with four columns each
1199
- col1, col2, col3, col4 = st.columns(4)
1200
- with col1:
1201
- country = st.text_input("Country", value=location_data["Country"], key="hvac_country")
1202
- with col2:
1203
- city = st.text_input("City", value=location_data["City"], key="hvac_city")
1204
- with col3:
1205
- state_province = st.text_input("State/Province", value=location_data["State/Province"], key="hvac_state_province")
1206
- with col4:
1207
- latitude = st.number_input(
1208
- "Latitude (°)",
1209
- min_value=-90.0,
1210
- max_value=90.0,
1211
- value=location_data["Latitude"],
1212
- step=0.1,
1213
- key="hvac_latitude"
1214
  )
1215
-
1216
- col5, col6, col7, col8 = st.columns(4)
1217
- with col5:
1218
- longitude = st.number_input(
1219
- "Longitude (°)",
1220
- min_value=-180.0,
1221
- max_value=180.0,
1222
- value=location_data["Longitude"],
1223
- step=0.1,
1224
- key="hvac_longitude"
1225
- )
1226
- with col6:
1227
- elevation = st.number_input(
1228
- "Elevation (m)",
1229
- min_value=0.0,
1230
- max_value=10000.0,
1231
- value=location_data["Elevation"],
1232
- step=1.0,
1233
- key="hvac_elevation"
1234
- )
1235
- with col7:
1236
- timezone = st.number_input(
1237
- "Time Zone (UTC offset)",
1238
- min_value=-12.0,
1239
- max_value=14.0,
1240
- value=float(location_data["Time Zone"]) if isinstance(location_data["Time Zone"], (int, float, str)) else 0.0,
1241
- step=0.5,
1242
- key="hvac_timezone"
1243
- )
1244
- with col8:
1245
- ground_reflectivity = st.number_input(
1246
- "Ground Reflectivity",
1247
- min_value=0.0,
1248
- max_value=1.0,
1249
- value=location_data["Ground Reflectivity"],
1250
- step=0.01,
1251
- key="hvac_ground_reflectivity"
1252
- )
1253
-
1254
- if st.button("Save Location"):
1255
- st.session_state.project_data["climate_data"]["location"].update({
1256
- "country": country,
1257
- "city": city,
1258
- "state_province": state_province,
1259
- "latitude": latitude,
1260
- "longitude": longitude,
1261
- "elevation": elevation,
1262
- "timezone": timezone
1263
- })
1264
- st.session_state.project_data["climate_data"]["ground_reflectivity"] = ground_reflectivity
1265
- st.success("Location information saved successfully.")
1266
- logger.info("Location information updated in session state")
1267
-
1268
- # Simulation Period Configuration
1269
- st.subheader("Simulation Period")
1270
- sim_type = st.selectbox(
1271
- "Simulation Type",
1272
- ["Full Year", "From Date to Date", "Heating Only", "Cooling Only",
1273
- "Summer Extreme", "Summer Typical", "Winter Extreme", "Winter Typical"],
1274
- key="hvac_sim_type",
1275
- index=["Full Year", "From Date to Date", "Heating Only", "Cooling Only",
1276
- "Summer Extreme", "Summer Typical", "Winter Extreme", "Winter Typical"].index(
1277
- st.session_state.project_data["sim_period"]["type"]
1278
- ) if st.session_state.project_data["sim_period"]["type"] in
1279
- ["Full Year", "From Date to Date", "Heating Only", "Cooling Only",
1280
- "Summer Extreme", "Summer Typical", "Winter Extreme", "Winter Typical"] else 0
1281
- )
1282
- st.session_state.project_data["sim_period"]["type"] = sim_type
1283
 
1284
- if sim_type == "From Date to Date":
1285
- col1, col2 = st.columns(2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1286
  with col1:
1287
- start_date = st.date_input(
1288
- "Start Date",
1289
- value=st.session_state.project_data["sim_period"]["start_date"] or datetime(2025, 1, 1),
1290
- key="hvac_start_date"
1291
- )
1292
  with col2:
1293
- end_date = st.date_input(
1294
- "End Date",
1295
- value=st.session_state.project_data["sim_period"]["end_date"] or datetime(2025, 12, 31),
1296
- key="hvac_end_date"
 
 
 
 
 
 
 
1297
  )
1298
- st.session_state.project_data["sim_period"]["start_date"] = start_date
1299
- st.session_state.project_data["sim_period"]["end_date"] = end_date
1300
- elif sim_type in ["Heating Only", "Cooling Only"]:
1301
- base_temp = st.number_input(
1302
- "Base Temperature C)",
1303
- min_value=0.0,
1304
- max_value=40.0,
1305
- value=st.session_state.project_data["sim_period"].get("base_temp", 18.3 if sim_type == "Heating Only" else 23.9),
1306
- step=0.1,
1307
- key="hvac_base_temp"
1308
- )
1309
- st.session_state.project_data["sim_period"]["base_temp"] = base_temp
1310
-
1311
- # Indoor Conditions Configuration
1312
- st.subheader("Indoor Conditions")
1313
- indoor_type = st.selectbox(
1314
- "Indoor Conditions Type",
1315
- ["Fixed Setpoints", "ASHRAE 55 Adaptive Comfort"],
1316
- key="hvac_indoor_type",
1317
- index=["Fixed Setpoints", "ASHRAE 55 Adaptive Comfort"].index(st.session_state.project_data["indoor_conditions"]["type"])
1318
- )
1319
- st.session_state.project_data["indoor_conditions"]["type"] = indoor_type
1320
-
1321
- if indoor_type == "Fixed Setpoints":
1322
- col1, col2 = st.columns(2)
1323
- with col1:
1324
- cooling_temp = st.number_input(
1325
- "Cooling Setpoint Temperature (°C)",
1326
- min_value=18.0,
1327
- max_value=30.0,
1328
- value=st.session_state.project_data["indoor_conditions"]["cooling_setpoint"]["temperature"],
1329
  step=0.1,
1330
- key="hvac_cooling_temp"
1331
  )
1332
- cooling_rh = st.number_input(
1333
- "Cooling Setpoint Relative Humidity (%)",
1334
- min_value=30.0,
1335
- max_value=70.0,
1336
- value=st.session_state.project_data["indoor_conditions"]["cooling_setpoint"]["rh"],
 
1337
  step=1.0,
1338
- key="hvac_cooling_rh"
1339
  )
1340
- with col2:
1341
- heating_temp = st.number_input(
1342
- "Heating Setpoint Temperature (°C)",
1343
- min_value=16.0,
1344
- max_value=26.0,
1345
- value=st.session_state.project_data["indoor_conditions"]["heating_setpoint"]["temperature"],
1346
- step=0.1,
1347
- key="hvac_heating_temp"
1348
  )
1349
- heating_rh = st.number_input(
1350
- "Heating Setpoint Relative Humidity (%)",
1351
- min_value=30.0,
1352
- max_value=70.0,
1353
- value=st.session_state.project_data["indoor_conditions"]["heating_setpoint"]["rh"],
1354
- step=1.0,
1355
- key="hvac_heating_rh"
 
1356
  )
1357
- st.session_state.project_data["indoor_conditions"]["cooling_setpoint"] = {
1358
- "temperature": cooling_temp,
1359
- "rh": cooling_rh
1360
- }
1361
- st.session_state.project_data["indoor_conditions"]["heating_setpoint"] = {
1362
- "temperature": heating_temp,
1363
- "rh": heating_rh
1364
- }
1365
- elif indoor_type == "ASHRAE 55 Adaptive Comfort":
1366
- acceptability = st.selectbox(
1367
- "Adaptive Comfort Acceptability (%)",
1368
- ["80", "85", "90", "95"],
1369
- index=["80", "85", "90", "95"].index(st.session_state.project_data["indoor_conditions"].get("adaptive_acceptability", "90")),
1370
- key="adaptive_acceptability"
1371
- )
1372
- st.session_state.project_data["indoor_conditions"]["adaptive_acceptability"] = acceptability
1373
-
1374
- # Internal Loads Conditions Configuration
1375
- st.subheader("Internal Loads Conditions")
1376
- col1, col2, col3, col4, col5 = st.columns(5)
1377
-
1378
- # Initialize internal_loads_conditions if not present
1379
- if "internal_loads_conditions" not in st.session_state.project_data:
1380
- st.session_state.project_data["internal_loads_conditions"] = {
1381
- "air_velocity": 0.1,
1382
- "lighting_convective_fraction": 0.5,
1383
- "lighting_radiative_fraction": 0.5,
1384
- "equipment_convective_fraction": 0.5,
1385
- "equipment_radiative_fraction": 0.5
1386
- }
1387
-
1388
- # Retrieve internal loads data
1389
- internal_loads = st.session_state.project_data.get("internal_loads", {})
1390
- lighting_systems = internal_loads.get("lighting", [])
1391
- equipment_systems = internal_loads.get("equipment", [])
1392
-
1393
- # Calculate default lighting fractions from lighting systems
1394
- if lighting_systems:
1395
- lighting_convective_avg = sum(system.get("convective_fraction", 0.5) for system in lighting_systems) / len(lighting_systems)
1396
- lighting_radiative_avg = sum(system.get("radiative_fraction", 0.5) for system in lighting_systems) / len(lighting_systems)
1397
- else:
1398
- lighting_convective_avg = st.session_state.project_data["internal_loads_conditions"].get("lighting_convective_fraction", 0.5)
1399
- lighting_radiative_avg = st.session_state.project_data["internal_loads_conditions"].get("lighting_radiative_fraction", 0.5)
1400
-
1401
- # Calculate default equipment fractions from equipment systems
1402
- if equipment_systems:
1403
- equipment_convective_avg = sum(system.get("convective_fraction", 0.5) for system in equipment_systems) / len(equipment_systems)
1404
- equipment_radiative_avg = sum(system.get("radiative_fraction", 0.5) for system in equipment_systems) / len(equipment_systems)
1405
- else:
1406
- equipment_convective_avg = st.session_state.project_data["internal_loads_conditions"].get("equipment_convective_fraction", 0.5)
1407
- equipment_radiative_avg = st.session_state.project_data["internal_loads_conditions"].get("equipment_radiative_fraction", 0.5)
1408
-
1409
- with col1:
1410
- air_velocity = st.number_input(
1411
- "Air Velocity (m/s)",
1412
- min_value=0.0,
1413
- max_value=2.0,
1414
- value=st.session_state.project_data["internal_loads_conditions"].get("air_velocity", 0.1),
1415
- step=0.01,
1416
- key="hvac_air_velocity"
1417
- )
1418
- if air_velocity < 0.0 or air_velocity > 2.0:
1419
- st.error("Air velocity must be between 0 and 2 m/s.")
1420
- air_velocity = max(0.0, min(2.0, air_velocity))
1421
-
1422
- with col2:
1423
- lighting_convective_fraction = st.number_input(
1424
- "Lighting Convective Fraction",
1425
- min_value=0.0,
1426
- max_value=1.0,
1427
- value=lighting_convective_avg,
1428
- step=0.01,
1429
- key="hvac_lighting_convective"
1430
- )
1431
-
1432
- with col3:
1433
- lighting_radiative_fraction = st.number_input(
1434
- "Lighting Radiative Fraction",
1435
- min_value=0.0,
1436
- max_value=1.0,
1437
- value=lighting_radiative_avg,
1438
- step=0.01,
1439
- key="hvac_lighting_radiative"
1440
- )
1441
- # Validate lighting fractions sum to 1.0
1442
- if abs(lighting_convective_fraction + lighting_radiative_fraction - 1.0) > 0.01:
1443
- st.error("Lighting convective and radiative fractions must sum to 1.0.")
1444
- lighting_radiative_fraction = 1.0 - lighting_convective_fraction # Auto-correct radiative fraction
1445
- st.warning(f"Adjusted Lighting Radiative Fraction to {lighting_radiative_fraction:.2f} to ensure sum equals 1.0.")
1446
-
1447
- with col4:
1448
- equipment_convective_fraction = st.number_input(
1449
- "Equipment Convective Fraction",
1450
- min_value=0.0,
1451
- max_value=1.0,
1452
- value=equipment_convective_avg,
1453
- step=0.01,
1454
- key="hvac_equipment_convective"
1455
- )
1456
-
1457
- with col5:
1458
- equipment_radiative_fraction = st.number_input(
1459
- "Equipment Radiative Fraction",
1460
- min_value=0.0,
1461
- max_value=1.0,
1462
- value=equipment_radiative_avg,
1463
- step=0.01,
1464
- key="hvac_equipment_radiative"
1465
  )
1466
- # Validate equipment fractions sum to 1.0
1467
- if abs(equipment_convective_fraction + equipment_radiative_fraction - 1.0) > 0.01:
1468
- st.error("Equipment convective and radiative fractions must sum to 1.0.")
1469
- equipment_radiative_fraction = 1.0 - equipment_convective_fraction # Auto-correct radiative fraction
1470
- st.warning(f"Adjusted Equipment Radiative Fraction to {equipment_radiative_fraction:.2f} to ensure sum equals 1.0.")
1471
-
1472
- if st.button("Save Internal Loads Conditions"):
1473
- # Update internal loads conditions in session state
1474
- st.session_state.project_data["internal_loads_conditions"].update({
1475
- "air_velocity": air_velocity,
1476
- "lighting_convective_fraction": lighting_convective_fraction,
1477
- "lighting_radiative_fraction": lighting_radiative_fraction,
1478
- "equipment_convective_fraction": equipment_convective_fraction,
1479
- "equipment_radiative_fraction": equipment_radiative_fraction
1480
- })
1481
- # Update lighting systems with new fractions
1482
- for system in lighting_systems:
1483
- system["convective_fraction"] = lighting_convective_fraction
1484
- system["radiative_fraction"] = lighting_radiative_fraction
1485
- # Update equipment systems with new fractions
1486
- for system in equipment_systems:
1487
- system["convective_fraction"] = equipment_convective_fraction
1488
- system["radiative_fraction"] = equipment_radiative_fraction
1489
- st.success("Internal loads conditions saved successfully.")
1490
- logger.info("Internal loads conditions updated in session state.")
1491
-
1492
- # Ground Temperature Configuration
1493
- st.subheader("Ground Temperature Configuration")
1494
- has_ground_contact = any(
1495
- comp.get('ground_contact', False)
1496
- for comp_list in st.session_state.project_data["components"].values()
1497
- for comp in comp_list
1498
- )
1499
- if has_ground_contact:
1500
- st.markdown("Configure monthly ground temperatures for components in contact with the ground (e.g., floors, walls, roofs). Typical ranges are 10–20°C at 2 m depth (ASHRAE Fundamentals, Chapter 18).")
1501
-
1502
- depth_options = ["0.5", "2", "4"]
1503
- default_depth = "2"
1504
- selected_depth = st.selectbox(
1505
- "Ground Temperature Depth (m)",
1506
- options=depth_options,
1507
- index=depth_options.index(default_depth),
1508
- key="ground_temp_depth"
1509
  )
1510
-
1511
- climate_data = st.session_state.project_data.get("climate_data", {})
1512
- ground_temperatures = climate_data.get("ground_temperatures", {})
1513
- default_temps = {"0.5": [20.0]*12, "2": [18.0]*12, "4": [16.0]*12}
1514
-
1515
- if selected_depth not in ground_temperatures or not ground_temperatures[selected_depth]:
1516
- st.warning(f"No ground temperature data available for depth {selected_depth} m. Using default temperatures: {default_temps[selected_depth][0]}°C.")
1517
- monthly_temps = default_temps[selected_depth]
1518
- else:
1519
- monthly_temps = ground_temperatures[selected_depth]
1520
-
1521
- st.write("Enter monthly ground temperatures (°C):")
1522
- months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
1523
- temp_inputs = []
1524
- cols = st.columns(12)
1525
- for i, month in enumerate(months):
1526
- with cols[i]:
1527
- temp = st.number_input(
1528
- month,
1529
- min_value=-20.0,
1530
- max_value=40.0,
1531
- value=monthly_temps[i],
1532
  step=0.1,
1533
- key=f"ground_temp_{selected_depth}_{month}"
1534
  )
1535
- temp_inputs.append(temp)
1536
-
1537
- if st.button("Save Ground Temperatures"):
1538
- if len(temp_inputs) != 12:
1539
- st.error("Please provide temperatures for all 12 months.")
1540
- elif any(not -20.0 <= t <= 40.0 for t in temp_inputs):
1541
- st.error("All temperatures must be between -20°C and 40°C.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1542
  else:
1543
- st.session_state.project_data["climate_data"]["ground_temperatures"][selected_depth] = temp_inputs
1544
- st.success(f"Ground temperatures for depth {selected_depth} m saved successfully.")
1545
- logger.info(f"Ground temperatures for depth {selected_depth} m updated in session state")
1546
- else:
1547
- st.info("No ground-contact components detected. Ground temperature configuration is not required.")
1548
-
1549
- # Calculate HVAC Loads
1550
- if st.button("Calculate HVAC Loads"):
1551
- try:
1552
- with st.spinner("Running simulation... this may take up to a minute depending on data size."):
1553
- components = st.session_state.project_data["components"]
1554
- hourly_data = st.session_state.project_data["climate_data"]["hourly_data"]
1555
- indoor_conditions = st.session_state.project_data["indoor_conditions"]
1556
- internal_loads = st.session_state.project_data["internal_loads"]
1557
- building_info = st.session_state.project_data["building_info"]
1558
- sim_period = st.session_state.project_data["sim_period"]
1559
- hvac_settings = st.session_state.project_data["hvac_settings"]
1560
-
1561
- if not hourly_data:
1562
- st.error("No climate data available. Please configure climate data first.")
1563
- logger.error("HVAC calculation failed: No climate data available")
1564
- return
1565
- elif not any(comp_list for comp_list in components.values()):
1566
- st.error("No building components defined. Please configure components first.")
1567
- logger.error("HVAC calculation failed: No building components defined")
1568
- return
1569
- else:
1570
- # Clear previous HVAC loads before running new simulation
1571
- st.session_state.project_data["hvac_loads"] = {
1572
- "cooling": {"hourly": [], "peak": 0, "charts": {}, "breakdown": {}},
1573
- "heating": {"hourly": [], "peak": 0, "charts": {}, "breakdown": {}}
1574
- }
1575
-
1576
- loads = TFMCalculations.calculate_tfm_loads(
1577
- components=components,
1578
- hourly_data=hourly_data,
1579
- indoor_conditions=indoor_conditions,
1580
- internal_loads=internal_loads,
1581
- building_info=building_info,
1582
- sim_period=sim_period,
1583
- hvac_settings=hvac_settings
1584
  )
 
1585
 
1586
- # Update session state with new results
1587
- cooling_loads = [load for load in loads if load["total_cooling"] > 0]
1588
- heating_loads = [load for load in loads if load["total_heating"] > 0]
1589
- st.session_state.project_data["hvac_loads"]["cooling"]["hourly"] = cooling_loads
1590
- st.session_state.project_data["hvac_loads"]["heating"]["hourly"] = heating_loads
1591
- st.session_state.project_data["hvac_loads"]["cooling"]["peak"] = max([load["total_cooling"] for load in cooling_loads], default=0)
1592
- st.session_state.project_data["hvac_loads"]["heating"]["peak"] = max([load["total_heating"] for load in heating_loads], default=0)
1593
- st.session_state.project_data["hvac_loads"]["cooling"]["charts"] = {}
1594
- st.session_state.project_data["hvac_loads"]["heating"]["charts"] = {}
1595
-
1596
- # Store breakdown
1597
- cooling_breakdown = {
1598
- "Conduction": sum(load["conduction_cooling"] for load in cooling_loads),
1599
- "Solar Gains": sum(load["solar"] for load in cooling_loads),
1600
- "Internal": sum(load["internal"] for load in cooling_loads),
1601
- "Ventilation Sensible": sum(system.get("sensible_load", 0.0) for system in st.session_state.project_data["internal_loads"].get("ventilation", [])),
1602
- "Ventilation Latent": sum(system.get("latent_load", 0.0) for system in st.session_state.project_data["internal_loads"].get("ventilation", [])),
1603
- "Infiltration Sensible": sum(system.get("sensible_load", 0.0) for system in st.session_state.project_data["internal_loads"].get("infiltration", [])),
1604
- "Infiltration Latent": sum(system.get("latent_load", 0.0) for system in st.session_state.project_data["internal_loads"].get("infiltration", []))
1605
- }
1606
- heating_breakdown = {
1607
- "Conduction": sum(load["conduction_heating"] for load in heating_loads),
1608
- "Ventilation": sum(load["ventilation_heating"] for load in heating_loads),
1609
- "Infiltration": sum(load["infiltration_heating"] for load in heating_loads)
1610
- }
1611
- st.session_state.project_data["hvac_loads"]["cooling"]["charts"]["pie_by_component"] = cooling_breakdown
1612
- st.session_state.project_data["hvac_loads"]["heating"]["breakdown"] = heating_breakdown
1613
-
1614
- # Display Results UI with unique run_id
1615
- display_hvac_results_ui(loads, run_id=run_id)
1616
-
1617
- st.success("HVAC loads calculated successfully.")
1618
- logger.info("HVAC loads calculated and stored in session state")
1619
-
1620
- except Exception as e:
1621
- st.error(f"Error calculating HVAC loads: {str(e)}")
1622
- logger.error(f"HVAC calculation error: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1623
 
1624
  except Exception as e:
1625
  st.error(f"Error rendering HVAC Loads page: {str(e)}")
 
1150
  def display_hvac_loads_page():
1151
  """
1152
  Display the HVAC Loads page in the Streamlit application.
1153
+ Organizes input configuration and results in separate tabs, with clearing and updating of session state.
 
1154
  """
1155
  try:
1156
  st.header("HVAC Loads")
1157
  st.markdown("Configure and calculate HVAC loads for the building.")
1158
 
1159
+ # Notify if HVAC load data exists in session state
1160
+ if (
1161
+ st.session_state.project_data.get("hvac_loads", {}).get("cooling", {}).get("hourly")
1162
+ or st.session_state.project_data.get("hvac_loads", {}).get("heating", {}).get("hourly")
1163
+ ):
1164
+ st.info(
1165
+ f"HVAC load results already calculated. "
1166
+ f"View details in the 'HVAC Load Results' tab or configure new calculations below."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1167
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1168
 
1169
+ # Create tabs for input and results
1170
+ tab1, tab2 = st.tabs(["HVAC Load Input", "HVAC Load Results"])
1171
+
1172
+ # HVAC Load Input tab
1173
+ with tab1:
1174
+ # Generate a unique run ID for this session
1175
+ import uuid
1176
+ run_id = str(uuid.uuid4())
1177
+
1178
+ # Location Information
1179
+ st.subheader("Location Information")
1180
+ climate_data = st.session_state.project_data["climate_data"]
1181
+ location_data = {
1182
+ "Country": climate_data.get("location", {}).get("country", ""),
1183
+ "City": climate_data.get("location", {}).get("city", ""),
1184
+ "State/Province": climate_data.get("location", {}).get("state_province", ""),
1185
+ "Latitude": climate_data.get("location", {}).get("latitude", 0.0),
1186
+ "Longitude": climate_data.get("location", {}).get("longitude", 0.0),
1187
+ "Elevation": climate_data.get("location", {}).get("elevation", 0.0),
1188
+ "Time Zone": climate_data.get("location", {}).get("timezone", "UTC"),
1189
+ "Ground Reflectivity": climate_data.get("ground_reflectivity", 0.2)
1190
+ }
1191
+
1192
+ # Create two rows with four columns each
1193
+ col1, col2, col3, col4 = st.columns(4)
1194
  with col1:
1195
+ country = st.text_input("Country", value=location_data["Country"], key="hvac_country")
 
 
 
 
1196
  with col2:
1197
+ city = st.text_input("City", value=location_data["City"], key="hvac_city")
1198
+ with col3:
1199
+ state_province = st.text_input("State/Province", value=location_data["State/Province"], key="hvac_state_province")
1200
+ with col4:
1201
+ latitude = st.number_input(
1202
+ "Latitude (°)",
1203
+ min_value=-90.0,
1204
+ max_value=90.0,
1205
+ value=location_data["Latitude"],
1206
+ step=0.1,
1207
+ key="hvac_latitude"
1208
  )
1209
+
1210
+ col5, col6, col7, col8 = st.columns(4)
1211
+ with col5:
1212
+ longitude = st.number_input(
1213
+ "Longitude (°)",
1214
+ min_value=-180.0,
1215
+ max_value=180.0,
1216
+ value=location_data["Longitude"],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1217
  step=0.1,
1218
+ key="hvac_longitude"
1219
  )
1220
+ with col6:
1221
+ elevation = st.number_input(
1222
+ "Elevation (m)",
1223
+ min_value=0.0,
1224
+ max_value=10000.0,
1225
+ value=location_data["Elevation"],
1226
  step=1.0,
1227
+ key="hvac_elevation"
1228
  )
1229
+ with col7:
1230
+ timezone = st.number_input(
1231
+ "Time Zone (UTC offset)",
1232
+ min_value=-12.0,
1233
+ max_value=14.0,
1234
+ value=float(location_data["Time Zone"]) if isinstance(location_data["Time Zone"], (int, float, str)) else 0.0,
1235
+ step=0.5,
1236
+ key="hvac_timezone"
1237
  )
1238
+ with col8:
1239
+ ground_reflectivity = st.number_input(
1240
+ "Ground Reflectivity",
1241
+ min_value=0.0,
1242
+ max_value=1.0,
1243
+ value=location_data["Ground Reflectivity"],
1244
+ step=0.01,
1245
+ key="hvac_ground_reflectivity"
1246
  )
1247
+
1248
+ if st.button("Save Location"):
1249
+ st.session_state.project_data["climate_data"]["location"].update({
1250
+ "country": country,
1251
+ "city": city,
1252
+ "state_province": state_province,
1253
+ "latitude": latitude,
1254
+ "longitude": longitude,
1255
+ "elevation": elevation,
1256
+ "timezone": timezone
1257
+ })
1258
+ st.session_state.project_data["climate_data"]["ground_reflectivity"] = ground_reflectivity
1259
+ st.success("Location information saved successfully.")
1260
+ logger.info("Location information updated in session state")
1261
+
1262
+ # Simulation Period Configuration
1263
+ st.subheader("Simulation Period")
1264
+ sim_type = st.selectbox(
1265
+ "Simulation Type",
1266
+ ["Full Year", "From Date to Date", "Heating Only", "Cooling Only",
1267
+ "Summer Extreme", "Summer Typical", "Winter Extreme", "Winter Typical"],
1268
+ key="hvac_sim_type",
1269
+ index=["Full Year", "From Date to Date", "Heating Only", "Cooling Only",
1270
+ "Summer Extreme", "Summer Typical", "Winter Extreme", "Winter Typical"].index(
1271
+ st.session_state.project_data["sim_period"]["type"]
1272
+ ) if st.session_state.project_data["sim_period"]["type"] in
1273
+ ["Full Year", "From Date to Date", "Heating Only", "Cooling Only",
1274
+ "Summer Extreme", "Summer Typical", "Winter Extreme", "Winter Typical"] else 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1275
  )
1276
+ st.session_state.project_data["sim_period"]["type"] = sim_type
1277
+
1278
+ if sim_type == "From Date to Date":
1279
+ col1, col2 = st.columns(2)
1280
+ with col1:
1281
+ start_date = st.date_input(
1282
+ "Start Date",
1283
+ value=st.session_state.project_data["sim_period"]["start_date"] or datetime(2025, 1, 1),
1284
+ key="hvac_start_date"
1285
+ )
1286
+ with col2:
1287
+ end_date = st.date_input(
1288
+ "End Date",
1289
+ value=st.session_state.project_data["sim_period"]["end_date"] or datetime(2025, 12, 31),
1290
+ key="hvac_end_date"
1291
+ )
1292
+ st.session_state.project_data["sim_period"]["start_date"] = start_date
1293
+ st.session_state.project_data["sim_period"]["end_date"] = end_date
1294
+ elif sim_type in ["Heating Only", "Cooling Only"]:
1295
+ base_temp = st.number_input(
1296
+ "Base Temperature (°C)",
1297
+ min_value=0.0,
1298
+ max_value=40.0,
1299
+ value=st.session_state.project_data["sim_period"].get("base_temp", 18.3 if sim_type == "Heating Only" else 23.9),
1300
+ step=0.1,
1301
+ key="hvac_base_temp"
1302
+ )
1303
+ st.session_state.project_data["sim_period"]["base_temp"] = base_temp
1304
+
1305
+ # Indoor Conditions Configuration
1306
+ st.subheader("Indoor Conditions")
1307
+ indoor_type = st.selectbox(
1308
+ "Indoor Conditions Type",
1309
+ ["Fixed Setpoints", "ASHRAE 55 Adaptive Comfort"],
1310
+ key="hvac_indoor_type",
1311
+ index=["Fixed Setpoints", "ASHRAE 55 Adaptive Comfort"].index(st.session_state.project_data["indoor_conditions"]["type"])
 
 
 
 
 
 
 
1312
  )
1313
+ st.session_state.project_data["indoor_conditions"]["type"] = indoor_type
1314
+
1315
+ if indoor_type == "Fixed Setpoints":
1316
+ col1, col2 = st.columns(2)
1317
+ with col1:
1318
+ cooling_temp = st.number_input(
1319
+ "Cooling Setpoint Temperature (°C)",
1320
+ min_value=18.0,
1321
+ max_value=30.0,
1322
+ value=st.session_state.project_data["indoor_conditions"]["cooling_setpoint"]["temperature"],
 
 
 
 
 
 
 
 
 
 
 
 
1323
  step=0.1,
1324
+ key="hvac_cooling_temp"
1325
  )
1326
+ cooling_rh = st.number_input(
1327
+ "Cooling Setpoint Relative Humidity (%)",
1328
+ min_value=30.0,
1329
+ max_value=70.0,
1330
+ value=st.session_state.project_data["indoor_conditions"]["cooling_setpoint"]["rh"],
1331
+ step=1.0,
1332
+ key="hvac_cooling_rh"
1333
+ )
1334
+ with col2:
1335
+ heating_temp = st.number_input(
1336
+ "Heating Setpoint Temperature (°C)",
1337
+ min_value=16.0,
1338
+ max_value=26.0,
1339
+ value=st.session_state.project_data["indoor_conditions"]["heating_setpoint"]["temperature"],
1340
+ step=0.1,
1341
+ key="hvac_heating_temp"
1342
+ )
1343
+ heating_rh = st.number_input(
1344
+ "Heating Setpoint Relative Humidity (%)",
1345
+ min_value=30.0,
1346
+ max_value=70.0,
1347
+ value=st.session_state.project_data["indoor_conditions"]["heating_setpoint"]["rh"],
1348
+ step=1.0,
1349
+ key="hvac_heating_rh"
1350
+ )
1351
+ st.session_state.project_data["indoor_conditions"]["cooling_setpoint"] = {
1352
+ "temperature": cooling_temp,
1353
+ "rh": cooling_rh
1354
+ }
1355
+ st.session_state.project_data["indoor_conditions"]["heating_setpoint"] = {
1356
+ "temperature": heating_temp,
1357
+ "rh": heating_rh
1358
+ }
1359
+ elif indoor_type == "ASHRAE 55 Adaptive Comfort":
1360
+ acceptability = st.selectbox(
1361
+ "Adaptive Comfort Acceptability (%)",
1362
+ ["80", "85", "90", "95"],
1363
+ index=["80", "85", "90", "95"].index(st.session_state.project_data["indoor_conditions"].get("adaptive_acceptability", "90")),
1364
+ key="adaptive_acceptability"
1365
+ )
1366
+ st.session_state.project_data["indoor_conditions"]["adaptive_acceptability"] = acceptability
1367
+
1368
+ # Internal Loads Conditions Configuration
1369
+ st.subheader("Internal Loads Conditions")
1370
+ col1, col2, col3, col4, col5 = st.columns(5)
1371
+
1372
+ # Initialize internal_loads_conditions if not present
1373
+ if "internal_loads_conditions" not in st.session_state.project_data:
1374
+ st.session_state.project_data["internal_loads_conditions"] = {
1375
+ "air_velocity": 0.1,
1376
+ "lighting_convective_fraction": 0.5,
1377
+ "lighting_radiative_fraction": 0.5,
1378
+ "equipment_convective_fraction": 0.5,
1379
+ "equipment_radiative_fraction": 0.5
1380
+ }
1381
+
1382
+ # Retrieve internal loads data
1383
+ internal_loads = st.session_state.project_data.get("internal_loads", {})
1384
+ lighting_systems = internal_loads.get("lighting", [])
1385
+ equipment_systems = internal_loads.get("equipment", [])
1386
+
1387
+ # Calculate default lighting fractions from lighting systems
1388
+ if lighting_systems:
1389
+ lighting_convective_avg = sum(system.get("convective_fraction", 0.5) for system in lighting_systems) / len(lighting_systems)
1390
+ lighting_radiative_avg = sum(system.get("radiative_fraction", 0.5) for system in lighting_systems) / len(lighting_systems)
1391
+ else:
1392
+ lighting_convective_avg = st.session_state.project_data["internal_loads_conditions"].get("lighting_convective_fraction", 0.5)
1393
+ lighting_radiative_avg = st.session_state.project_data["internal_loads_conditions"].get("lighting_radiative_fraction", 0.5)
1394
+
1395
+ # Calculate default equipment fractions from equipment systems
1396
+ if equipment_systems:
1397
+ equipment_convective_avg = sum(system.get("convective_fraction", 0.5) for system in equipment_systems) / len(equipment_systems)
1398
+ equipment_radiative_avg = sum(system.get("radiative_fraction", 0.5) for system in equipment_systems) / len(equipment_systems)
1399
+ else:
1400
+ equipment_convective_avg = st.session_state.project_data["internal_loads_conditions"].get("equipment_convective_fraction", 0.5)
1401
+ equipment_radiative_avg = st.session_state.project_data["internal_loads_conditions"].get("equipment_radiative_fraction", 0.5)
1402
+
1403
+ with col1:
1404
+ air_velocity = st.number_input(
1405
+ "Air Velocity (m/s)",
1406
+ min_value=0.0,
1407
+ max_value=2.0,
1408
+ value=st.session_state.project_data["internal_loads_conditions"].get("air_velocity", 0.1),
1409
+ step=0.01,
1410
+ key="hvac_air_velocity"
1411
+ )
1412
+ if air_velocity < 0.0 or air_velocity > 2.0:
1413
+ st.error("Air velocity must be between 0 and 2 m/s.")
1414
+ air_velocity = max(0.0, min(2.0, air_velocity))
1415
+
1416
+ with col2:
1417
+ lighting_convective_fraction = st.number_input(
1418
+ "Lighting Convective Fraction",
1419
+ min_value=0.0,
1420
+ max_value=1.0,
1421
+ value=lighting_convective_avg,
1422
+ step=0.01,
1423
+ key="hvac_lighting_convective"
1424
+ )
1425
+
1426
+ with col3:
1427
+ lighting_radiative_fraction = st.number_input(
1428
+ "Lighting Radiative Fraction",
1429
+ min_value=0.0,
1430
+ max_value=1.0,
1431
+ value=lighting_radiative_avg,
1432
+ step=0.01,
1433
+ key="hvac_lighting_radiative"
1434
+ )
1435
+ # Validate lighting fractions sum to 1.0
1436
+ if abs(lighting_convective_fraction + lighting_radiative_fraction - 1.0) > 0.01:
1437
+ st.error("Lighting convective and radiative fractions must sum to 1.0.")
1438
+ lighting_radiative_fraction = 1.0 - lighting_convective_fraction # Auto-correct radiative fraction
1439
+ st.warning(f"Adjusted Lighting Radiative Fraction to {lighting_radiative_fraction:.2f} to ensure sum equals 1.0.")
1440
+
1441
+ with col4:
1442
+ equipment_convective_fraction = st.number_input(
1443
+ "Equipment Convective Fraction",
1444
+ min_value=0.0,
1445
+ max_value=1.0,
1446
+ value=equipment_convective_avg,
1447
+ step=0.01,
1448
+ key="hvac_equipment_convective"
1449
+ )
1450
+
1451
+ with col5:
1452
+ equipment_radiative_fraction = st.number_input(
1453
+ "Equipment Radiative Fraction",
1454
+ min_value=0.0,
1455
+ max_value=1.0,
1456
+ value=equipment_radiative_avg,
1457
+ step=0.01,
1458
+ key="hvac_equipment_radiative"
1459
+ )
1460
+ # Validate equipment fractions sum to 1.0
1461
+ if abs(equipment_convective_fraction + equipment_radiative_fraction - 1.0) > 0.01:
1462
+ st.error("Equipment convective and radiative fractions must sum to 1.0.")
1463
+ equipment_radiative_fraction = 1.0 - equipment_convective_fraction # Auto-correct radiative fraction
1464
+ st.warning(f"Adjusted Equipment Radiative Fraction to {equipment_radiative_fraction:.2f} to ensure sum equals 1.0.")
1465
+
1466
+ if st.button("Save Internal Loads Conditions"):
1467
+ # Update internal loads conditions in session state
1468
+ st.session_state.project_data["internal_loads_conditions"].update({
1469
+ "air_velocity": air_velocity,
1470
+ "lighting_convective_fraction": lighting_convective_fraction,
1471
+ "lighting_radiative_fraction": lighting_radiative_fraction,
1472
+ "equipment_convective_fraction": equipment_convective_fraction,
1473
+ "equipment_radiative_fraction": equipment_radiative_fraction
1474
+ })
1475
+ # Update lighting systems with new fractions
1476
+ for system in lighting_systems:
1477
+ system["convective_fraction"] = lighting_convective_fraction
1478
+ system["radiative_fraction"] = lighting_radiative_fraction
1479
+ # Update equipment systems with new fractions
1480
+ for system in equipment_systems:
1481
+ system["convective_fraction"] = equipment_convective_fraction
1482
+ system["radiative_fraction"] = equipment_radiative_fraction
1483
+ st.success("Internal loads conditions saved successfully.")
1484
+ logger.info("Internal loads conditions updated in session state.")
1485
+
1486
+ # Ground Temperature Configuration
1487
+ st.subheader("Ground Temperature Configuration")
1488
+ has_ground_contact = any(
1489
+ comp.get('ground_contact', False)
1490
+ for comp_list in st.session_state.project_data["components"].values()
1491
+ for comp in comp_list
1492
+ )
1493
+ if has_ground_contact:
1494
+ st.markdown("Configure monthly ground temperatures for components in contact with the ground (e.g., floors, walls, roofs). Typical ranges are 10–20°C at 2 m depth (ASHRAE Fundamentals, Chapter 18).")
1495
+
1496
+ depth_options = ["0.5", "2", "4"]
1497
+ default_depth = "2"
1498
+ selected_depth = st.selectbox(
1499
+ "Ground Temperature Depth (m)",
1500
+ options=depth_options,
1501
+ index=depth_options.index(default_depth),
1502
+ key="ground_temp_depth"
1503
+ )
1504
+
1505
+ climate_data = st.session_state.project_data.get("climate_data", {})
1506
+ ground_temperatures = climate_data.get("ground_temperatures", {})
1507
+ default_temps = {"0.5": [20.0]*12, "2": [18.0]*12, "4": [16.0]*12}
1508
+
1509
+ if selected_depth not in ground_temperatures or not ground_temperatures[selected_depth]:
1510
+ st.warning(f"No ground temperature data available for depth {selected_depth} m. Using default temperatures: {default_temps[selected_depth][0]}°C.")
1511
+ monthly_temps = default_temps[selected_depth]
1512
  else:
1513
+ monthly_temps = ground_temperatures[selected_depth]
1514
+
1515
+ st.write("Enter monthly ground temperatures (°C):")
1516
+ months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
1517
+ temp_inputs = []
1518
+ cols = st.columns(12)
1519
+ for i, month in enumerate(months):
1520
+ with cols[i]:
1521
+ temp = st.number_input(
1522
+ month,
1523
+ min_value=-20.0,
1524
+ max_value=40.0,
1525
+ value=monthly_temps[i],
1526
+ step=0.1,
1527
+ key=f"ground_temp_{selected_depth}_{month}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1528
  )
1529
+ temp_inputs.append(temp)
1530
 
1531
+ if st.button("Save Ground Temperatures"):
1532
+ if len(temp_inputs) != 12:
1533
+ st.error("Please provide temperatures for all 12 months.")
1534
+ elif any(not -20.0 <= t <= 40.0 for t in temp_inputs):
1535
+ st.error("All temperatures must be between -20°C and 40°C.")
1536
+ else:
1537
+ st.session_state.project_data["climate_data"]["ground_temperatures"][selected_depth] = temp_inputs
1538
+ st.success(f"Ground temperatures for depth {selected_depth} m saved successfully.")
1539
+ logger.info(f"Ground temperatures for depth {selected_depth} m updated in session state")
1540
+ else:
1541
+ st.info("No ground-contact components detected. Ground temperature configuration is not required.")
1542
+
1543
+ # Calculate HVAC Loads
1544
+ if st.button("Calculate HVAC Loads"):
1545
+ try:
1546
+ with st.spinner("Running simulation... this may take up to a minute depending on data size."):
1547
+ components = st.session_state.project_data["components"]
1548
+ hourly_data = st.session_state.project_data["climate_data"]["hourly_data"]
1549
+ indoor_conditions = st.session_state.project_data["indoor_conditions"]
1550
+ internal_loads = st.session_state.project_data["internal_loads"]
1551
+ building_info = st.session_state.project_data["building_info"]
1552
+ sim_period = st.session_state.project_data["sim_period"]
1553
+ hvac_settings = st.session_state.project_data["hvac_settings"]
1554
+
1555
+ if not hourly_data:
1556
+ st.error("No climate data available. Please configure climate data first.")
1557
+ logger.error("HVAC calculation failed: No climate data available")
1558
+ return
1559
+ elif not any(comp_list for comp_list in components.values()):
1560
+ st.error("No building components defined. Please configure components first.")
1561
+ logger.error("HVAC calculation failed: No building components defined")
1562
+ return
1563
+ else:
1564
+ loads = TFMCalculations.calculate_tfm_loads(
1565
+ components=components,
1566
+ hourly_data=hourly_data,
1567
+ indoor_conditions=indoor_conditions,
1568
+ internal_loads=internal_loads,
1569
+ building_info=building_info,
1570
+ sim_period=sim_period,
1571
+ hvac_settings=hvac_settings
1572
+ )
1573
+
1574
+ # Clear previous HVAC loads from session state
1575
+ st.session_state.project_data["hvac_loads"] = {
1576
+ "cooling": {"hourly": [], "peak": 0, "charts": {}, "breakdown": {}},
1577
+ "heating": {"hourly": [], "peak": 0, "charts": {}, "breakdown": {}}
1578
+ }
1579
+
1580
+ # Update session state with new results
1581
+ cooling_loads = [load for load in loads if load["total_cooling"] > 0]
1582
+ heating_loads = [load for load in loads if load["total_heating"] > 0]
1583
+ st.session_state.project_data["hvac_loads"]["cooling"]["hourly"] = cooling_loads
1584
+ st.session_state.project_data["hvac_loads"]["heating"]["hourly"] = heating_loads
1585
+ st.session_state.project_data["hvac_loads"]["cooling"]["peak"] = max([load["total_cooling"] for load in cooling_loads], default=0)
1586
+ st.session_state.project_data["hvac_loads"]["heating"]["peak"] = max([load["total_heating"] for load in heating_loads], default=0)
1587
+ st.session_state.project_data["hvac_loads"]["cooling"]["charts"] = {}
1588
+ st.session_state.project_data["hvac_loads"]["heating"]["charts"] = {}
1589
+
1590
+ # Store breakdown
1591
+ cooling_breakdown = {
1592
+ "Conduction": sum(load["conduction_cooling"] for load in cooling_loads),
1593
+ "Solar Gains": sum(load["solar"] for load in cooling_loads),
1594
+ "Internal": sum(load["internal"] for load in cooling_loads),
1595
+ "Ventilation Sensible": sum(system.get("sensible_load", 0.0) for system in st.session_state.project_data["internal_loads"].get("ventilation", [])),
1596
+ "Ventilation Latent": sum(system.get("latent_load", 0.0) for system in st.session_state.project_data["internal_loads"].get("ventilation", [])),
1597
+ "Infiltration Sensible": sum(system.get("sensible_load", 0.0) for system in st.session_state.project_data["internal_loads"].get("infiltration", [])),
1598
+ "Infiltration Latent": sum(system.get("latent_load", 0.0) for system in st.session_state.project_data["internal_loads"].get("infiltration", []))
1599
+ }
1600
+ heating_breakdown = {
1601
+ "conduction": sum(load["conduction_heating"] for load in heating_loads),
1602
+ "ventilation": sum(load["ventilation_heating"] for load in heating_loads),
1603
+ "infiltration": sum(load["infiltration_heating"] for load in heating_loads)
1604
+ }
1605
+ st.session_state.project_data["hvac_loads"]["cooling"]["charts"]["pie_by_component"] = cooling_breakdown
1606
+ st.session_state.project_data["hvac_loads"]["heating"]["breakdown"] = heating_breakdown
1607
+
1608
+ st.success("HVAC loads calculated successfully.")
1609
+ logger.info("HVAC loads calculated and stored in session state")
1610
+ st.button("View HVAC Load Results", on_click=lambda: st.session_state.update({"hvac_tab": "HVAC Load Results"}))
1611
+
1612
+ except Exception as e:
1613
+ st.error(f"Error calculating HVAC loads: {str(e)}")
1614
+ logger.error(f"HVAC calculation error: {str(e)}")
1615
+
1616
+ # HVAC Load Results tab
1617
+ with tab2:
1618
+ if (
1619
+ st.session_state.project_data.get("hvac_loads", {}).get("cooling", {}).get("hourly")
1620
+ or st.session_state.project_data.get("hvac_loads", {}).get("heating", {}).get("hourly")
1621
+ ):
1622
+ loads = []
1623
+ cooling_loads = st.session_state.project_data["hvac_loads"]["cooling"].get("hourly", [])
1624
+ heating_loads = st.session_state.project_data["hvac_loads"]["heating"].get("hourly", [])
1625
+ loads.extend(cooling_loads)
1626
+ loads.extend(heating_loads)
1627
+ # Sort loads by month, day, hour to ensure consistent display
1628
+ loads = sorted(loads, key=lambda x: (x["month"], x["day"], x["hour"]))
1629
+ if loads:
1630
+ display_hvac_results_ui(loads, run_id=run_id)
1631
+ else:
1632
+ st.info("Please configure and calculate HVAC loads in the 'HVAC Load Input' tab to view results.")
1633
 
1634
  except Exception as e:
1635
  st.error(f"Error rendering HVAC Loads page: {str(e)}")