mabuseif commited on
Commit
5ad760b
·
verified ·
1 Parent(s): 77b75e8

Update app/main.py

Browse files
Files changed (1) hide show
  1. app/main.py +89 -93
app/main.py CHANGED
@@ -17,7 +17,7 @@ import uuid
17
 
18
  # Import application modules
19
  from app.building_info_form import BuildingInfoForm
20
- from app.component_selection import ComponentSelectionInterface, Orientation, ComponentType, Wall, Roof, Floor, Window, Door, Skylight, GlazingType, FrameType # CHANGED: Removed SurfaceColor
21
  from app.results_display import ResultsDisplay
22
  from app.data_validation import DataValidation
23
  from app.data_persistence import DataPersistence
@@ -41,7 +41,7 @@ from utils.component_visualization import ComponentVisualization
41
  from utils.scenario_comparison import ScenarioComparisonVisualization
42
  from utils.psychrometric_visualization import PsychrometricVisualization
43
  from utils.time_based_visualization import TimeBasedVisualization
44
- from data.drapery import Drapery # NEW: Drapery adjustments
45
 
46
  # NEW: ASHRAE 62.1 Ventilation Rates (Table 6.1)
47
  VENTILATION_RATES = {
@@ -75,7 +75,7 @@ class HVACCalculator:
75
  'floors': [],
76
  'windows': [],
77
  'doors': [],
78
- 'skylights': [] # NEW: Added skylights
79
  }
80
 
81
  if 'internal_loads' not in st.session_state:
@@ -109,7 +109,7 @@ class HVACCalculator:
109
  self.data_export = DataExport()
110
  self.cooling_calculator = CoolingLoadCalculator()
111
  self.heating_calculator = HeatingLoadCalculator()
112
- self.drapery = Drapery() # NEW: Drapery system
113
 
114
  # Persist ClimateData in session_state
115
  if 'climate_data_obj' not in st.session_state:
@@ -259,7 +259,7 @@ class HVACCalculator:
259
  existing_load['activity_level'] == new_load['activity_level'] and
260
  existing_load['zone_type'] == new_load['zone_type'] and
261
  existing_load['hours_in_operation'] == new_load['hours_in_operation'] and
262
- existing_load['latent_gain'] == new_load['latent_gain']): # NEW: Added latent_gain
263
  return False, f"Duplicate people load '{new_load['name']}' already exists."
264
  elif load_type == 'lighting':
265
  if (existing_load['name'] == new_load['name'] and
@@ -306,15 +306,15 @@ class HVACCalculator:
306
  )
307
  zone_type = st.selectbox(
308
  "Zone Type",
309
- ["A", "B", "C", "D"], # NEW: Updated per Plan.txt
310
  help="Select zone type for CLF accuracy per ASHRAE"
311
  )
312
  hours_in_operation = st.selectbox(
313
- "Hours Occupied", # NEW: Updated label per Plan.txt
314
- ["2h", "4h", "6h"], # NEW: Updated options per Plan.txt
315
  help="Select hours of occupancy for CLF calculations"
316
  )
317
- latent_gain = st.number_input( # NEW: Added per Plan.txt
318
  "Latent Gain per Person (Btu/h)",
319
  min_value=0.0,
320
  max_value=500.0,
@@ -332,7 +332,7 @@ class HVACCalculator:
332
  "activity_level": activity_level,
333
  "zone_type": zone_type,
334
  "hours_in_operation": hours_in_operation,
335
- "latent_gain": latent_gain # NEW: Added per Plan.txt
336
  }
337
  is_valid, message = self.validate_internal_load('people', people_load)
338
  if is_valid:
@@ -378,12 +378,12 @@ class HVACCalculator:
378
  )
379
  zone_type = st.selectbox(
380
  "Zone Type",
381
- ["A", "B", "C", "D"], # NEW: Updated per Plan.txt
382
  help="Select zone type for CLF accuracy per ASHRAE"
383
  )
384
  hours_in_operation = st.selectbox(
385
- "Hours On", # NEW: Updated label per Plan.txt
386
- ["8h", "10h", "12h"], # NEW: Updated options per Plan.txt
387
  help="Select hours of lighting operation for CLF calculations"
388
  )
389
  lighting_name = st.text_input("Name", value="General Lighting")
@@ -449,12 +449,12 @@ class HVACCalculator:
449
  )
450
  zone_type = st.selectbox(
451
  "Zone Type",
452
- ["A", "B", "C", "D"], # NEW: Updated per Plan.txt
453
  help="Select zone type for CLF accuracy per ASHRAE"
454
  )
455
  hours_in_operation = st.selectbox(
456
- "Hours Operated", # NEW: Updated label per Plan.txt
457
- ["2h", "4h", "6h"], # NEW: Updated options per Plan.txt
458
  help="Select hours of equipment operation for CLF calculations"
459
  )
460
  equipment_name = st.text_input("Name", value="Office Equipment")
@@ -524,7 +524,7 @@ class HVACCalculator:
524
  step=0.1,
525
  help="Custom ventilation rate per floor area (ASHRAE 62.1)"
526
  )
527
- ventilation_rate = st.number_input( # NEW: Added per Plan.txt
528
  "Ventilation Rate (m³/s)",
529
  min_value=0.0,
530
  max_value=10.0,
@@ -537,7 +537,7 @@ class HVACCalculator:
537
  area_rate = VENTILATION_RATES[zone_type]["area_rate"]
538
  st.write(f"People Rate: {people_rate} L/s/person (ASHRAE 62.1)")
539
  st.write(f"Area Rate: {area_rate} L/s/m² (ASHRAE 62.1)")
540
- ventilation_rate = st.number_input( # NEW: Added per Plan.txt
541
  "Ventilation Rate (m³/s)",
542
  min_value=0.0,
543
  max_value=10.0,
@@ -618,16 +618,16 @@ class HVACCalculator:
618
  # Format conditions
619
  outdoor_conditions = {
620
  'temperature': location['summer_design_temp_db'],
621
- 'relative_humidity': building_info.get('outdoor_rh', location['monthly_humidity'].get('Jul', 50.0)), # NEW: Use UI input
622
  'ground_temperature': location['monthly_temps'].get('Jul', 20.0),
623
  'month': 'Jul',
624
  'latitude': location['latitude'],
625
- 'wind_speed': building_info.get('wind_speed', 4.0), # NEW: Use UI input
626
- 'day_of_year': month_to_day.get('Jul', 182) # NEW: Derive from month
627
  }
628
  indoor_conditions = {
629
  'temperature': building_info.get('indoor_temp', 24.0),
630
- 'relative_humidity': building_info.get('indoor_rh', 50.0) # NEW: Use UI input
631
  }
632
 
633
  if st.session_state.get('debug_mode', False):
@@ -649,31 +649,31 @@ class HVACCalculator:
649
  'people': {
650
  'number': sum(load['num_people'] for load in internal_loads.get('people', [])),
651
  'activity_level': internal_loads.get('people', [{}])[0].get('activity_level', 'Seated/Resting'),
652
- 'operating_hours': internal_loads.get('people', [{}])[0].get('hours_in_operation', '8h'), # NEW: Use updated hours
653
- 'zone_type': internal_loads.get('people', [{}])[0].get('zone_type', 'A'), # NEW: Use updated zone type
654
- 'latent_gain': internal_loads.get('people', [{}])[0].get('latent_gain', 200.0) # NEW: Added latent gain
655
  },
656
  'lights': {
657
  'power': sum(load['power'] for load in internal_loads.get('lighting', [])),
658
  'use_factor': internal_loads.get('lighting', [{}])[0].get('usage_factor', 0.8),
659
  'special_allowance': 0.1,
660
- 'hours_operation': internal_loads.get('lighting', [{}])[0].get('hours_in_operation', '8h'), # NEW: Use updated hours
661
- 'zone_type': internal_loads.get('lighting', [{}])[0].get('zone_type', 'A') # NEW: Use updated zone type
662
  },
663
  'equipment': {
664
  'power': sum(load['power'] for load in internal_loads.get('equipment', [])),
665
  'use_factor': internal_loads.get('equipment', [{}])[0].get('usage_factor', 0.7),
666
  'radiation_factor': internal_loads.get('equipment', [{}])[0].get('radiation_fraction', 0.3),
667
- 'hours_operation': internal_loads.get('equipment', [{}])[0].get('hours_in_operation', '8h'), # NEW: Use updated hours
668
- 'zone_type': internal_loads.get('equipment', [{}])[0].get('zone_type', 'A') # NEW: Use updated zone type
669
  },
670
  'infiltration': {
671
- 'flow_rate': building_info.get('infiltration_rate', 0.05),
672
- 'height': building_info.get('building_height', 3.0), # NEW: Use UI input
673
  'crack_length': building_info.get('crack_length', 10.0)
674
  },
675
  'ventilation': {
676
- 'flow_rate': building_info.get('ventilation_rate', 0.1) # NEW: Use UI input
677
  },
678
  'operating_hours': building_info.get('operating_hours', '8:00-18:00')
679
  }
@@ -723,7 +723,7 @@ class HVACCalculator:
723
  'roof': design_loads['roofs'] / 1000,
724
  'windows': (design_loads['windows_conduction'] + design_loads['windows_solar']) / 1000,
725
  'doors': design_loads['doors'] / 1000,
726
- 'skylights': design_loads.get('skylights', 0) / 1000, # NEW: Skylights
727
  'people': (design_loads['people_sensible'] + design_loads['people_latent']) / 1000,
728
  'lighting': design_loads['lights'] / 1000,
729
  'equipment': (design_loads['equipment_sensible'] + design_loads['equipment_latent']) / 1000,
@@ -735,7 +735,7 @@ class HVACCalculator:
735
  'roofs': [],
736
  'windows': [],
737
  'doors': [],
738
- 'skylights': [], # NEW: Skylights
739
  'internal': [],
740
  'infiltration': {
741
  'air_flow': formatted_internal_loads['infiltration']['flow_rate'],
@@ -762,23 +762,22 @@ class HVACCalculator:
762
  month=outdoor_conditions['month'],
763
  hour=design_loads['design_hour'],
764
  latitude=outdoor_conditions['latitude'],
765
- solar_absorptivity=wall.solar_absorptivity # CHANGED: Replaced surface_color with solar_absorptivity
766
  )
767
  results['detailed_loads']['walls'].append({
768
  'name': wall.name,
769
  'orientation': wall.orientation.value,
770
  'area': wall.area,
771
  'u_value': wall.u_value,
772
- 'solar_absorptivity': wall.solar_absorptivity, # CHANGED: Replaced surface_color
773
- 'cltd': self.cooling_calculator.ashrae_tables.calculate_corrected_cltd_wall(
774
- wall_group=wall.wall_group,
 
775
  orientation=wall.orientation.value,
776
  hour=design_loads['design_hour'],
777
- solar_absorptivity=wall.solar_absorptivity, # CHANGED: Pass solar_absorptivity directly
778
- month=outdoor_conditions['month'],
779
- latitude=outdoor_conditions['latitude'],
780
- indoor_temp=indoor_conditions['temperature'],
781
- outdoor_temp=outdoor_conditions['temperature']
782
  ),
783
  'load': load / 1000
784
  })
@@ -791,29 +790,28 @@ class HVACCalculator:
791
  month=outdoor_conditions['month'],
792
  hour=design_loads['design_hour'],
793
  latitude=outdoor_conditions['latitude'],
794
- solar_absorptivity=roof.solar_absorptivity # CHANGED: Replaced surface_color with solar_absorptivity
795
  )
796
  results['detailed_loads']['roofs'].append({
797
  'name': roof.name,
798
  'orientation': roof.orientation.value,
799
  'area': roof.area,
800
  'u_value': roof.u_value,
801
- 'solar_absorptivity': roof.solar_absorptivity, # CHANGED: Replaced surface_color
802
- 'cltd': self.cooling_calculator.ashrae_tables.calculate_corrected_cltd_roof(
803
- roof_group=roof.roof_group,
 
 
804
  hour=design_loads['design_hour'],
805
- solar_absorptivity=roof.solar_absorptivity, # CHANGED: Pass solar_absorptivity directly
806
- month=outdoor_conditions['month'],
807
- latitude=outdoor_conditions['latitude'],
808
- indoor_temp=indoor_conditions['temperature'],
809
- outdoor_temp=outdoor_conditions['temperature']
810
  ),
811
  'load': load / 1000
812
  })
813
 
814
  for window in building_components.get('windows', []):
815
- # NEW: Apply drapery adjustment
816
- adjusted_shgc = self.drapery.adjust_shgc( # CHANGED: Fixed typo (drapery_system to drapery)
817
  base_shgc=window.shgc,
818
  glazing_type=window.glazing_type,
819
  drapery_type=window.drapery_type if hasattr(window, 'drapery_type') else None
@@ -826,9 +824,9 @@ class HVACCalculator:
826
  hour=design_loads['design_hour'],
827
  latitude=outdoor_conditions['latitude'],
828
  shading_coefficient=window.shading_coefficient,
829
- adjusted_shgc=adjusted_shgc, # NEW: Adjusted SHGC
830
- glazing_type=window.glazing_type, # NEW: Glazing type
831
- frame_type=window.frame_type # NEW: Frame type
832
  )
833
  if 'total' not in load_dict:
834
  if 'conduction' in load_dict and 'solar' in load_dict:
@@ -842,15 +840,15 @@ class HVACCalculator:
842
  'area': window.area,
843
  'u_value': window.u_value,
844
  'shgc': window.shgc,
845
- 'adjusted_shgc': adjusted_shgc, # NEW: Adjusted SHGC
846
- 'glazing_type': window.glazing_type, # NEW: Glazing type
847
- 'frame_type': window.frame_type, # NEW: Frame type
848
- 'drapery_type': window.drapery_type if hasattr(window, 'drapery_type') else 'None', # NEW: Drapery
849
  'shading_device': window.shading_device,
850
  'shading_coefficient': window.shading_coefficient,
851
  'scl': self.cooling_calculator.ashrae_tables.get_scl(
852
- latitude=outdoor_conditions['latitude'],
853
- month=outdoor_conditions['month'].title(),
854
  orientation=window.orientation.value,
855
  hour=design_loads['design_hour']
856
  ),
@@ -872,9 +870,8 @@ class HVACCalculator:
872
  'load': load / 1000
873
  })
874
 
875
- # NEW: Skylights
876
  for skylight in building_components.get('skylights', []):
877
- adjusted_shgc = self.drapery.adjust_shgc( # CHANGED: Fixed typo (drapery_system to drapery)
878
  base_shgc=skylight.shgc,
879
  glazing_type=skylight.glazing_type,
880
  drapery_type=skylight.drapery_type if hasattr(skylight, 'drapery_type') else None
@@ -908,9 +905,9 @@ class HVACCalculator:
908
  'drapery_type': skylight.drapery_type if hasattr(skylight, 'drapery_type') else 'None',
909
  'shading_coefficient': skylight.shading_coefficient,
910
  'scl': self.cooling_calculator.ashrae_tables.get_scl(
911
- latitude=outdoor_conditions['latitude'],
912
- month=outdoor_conditions['month'].title(),
913
- orientation='Horizontal', # Skylights are horizontal
914
  hour=design_loads['design_hour']
915
  ),
916
  'load': load_dict['total'] / 1000
@@ -923,7 +920,7 @@ class HVACCalculator:
923
  num_people=load['num_people'],
924
  activity_level=load['activity_level'],
925
  hour=design_loads['design_hour'],
926
- latent_gain=load.get('latent_gain', 200.0) # NEW: Pass latent gain
927
  )
928
  if 'total' not in load_dict and ('sensible' in load_dict or 'latent' in load_dict):
929
  load_dict['total'] = load_dict.get('sensible', 0) + load_dict.get('latent', 0)
@@ -1023,7 +1020,7 @@ class HVACCalculator:
1023
 
1024
  # Skip heating calculation if outdoor temp exceeds indoor temp
1025
  indoor_temp = building_info.get('indoor_temp', 21.0)
1026
- outdoor_temp = building_info.get('winter_temp', location['winter_design_temp']) # NEW: Use UI input if available
1027
  if outdoor_temp >= indoor_temp:
1028
  results = {
1029
  'total_load': 0.0,
@@ -1036,7 +1033,7 @@ class HVACCalculator:
1036
  'floor': 0.0,
1037
  'windows': 0.0,
1038
  'doors': 0.0,
1039
- 'skylights': 0.0, # NEW: Skylights
1040
  'infiltration': 0.0,
1041
  'ventilation': 0.0
1042
  },
@@ -1046,7 +1043,7 @@ class HVACCalculator:
1046
  'floors': [],
1047
  'windows': [],
1048
  'doors': [],
1049
- 'skylights': [], # NEW: Skylights
1050
  'infiltration': {'air_flow': 0.0, 'delta_t': 0.0, 'load': 0.0},
1051
  'ventilation': {'air_flow': 0.0, 'delta_t': 0.0, 'load': 0.0}
1052
  },
@@ -1056,14 +1053,14 @@ class HVACCalculator:
1056
 
1057
  # Format conditions
1058
  outdoor_conditions = {
1059
- 'design_temperature': outdoor_temp, # NEW: Use UI input
1060
- 'design_relative_humidity': building_info.get('outdoor_rh', location['monthly_humidity'].get('Jan', 80.0)), # NEW: Use UI input
1061
  'ground_temperature': ground_temperature,
1062
- 'wind_speed': building_info.get('wind_speed', 4.0) # NEW: Use UI input
1063
  }
1064
  indoor_conditions = {
1065
  'temperature': indoor_temp,
1066
- 'relative_humidity': building_info.get('indoor_rh', 40.0) # NEW: Use UI input
1067
  }
1068
 
1069
  if st.session_state.get('debug_mode', False):
@@ -1096,29 +1093,29 @@ class HVACCalculator:
1096
  internal_loads.get('people', [{}])[0].get('activity_level', 'Seated/Resting'),
1097
  70.0
1098
  ),
1099
- 'latent_gain': internal_loads.get('people', [{}])[0].get('latent_gain', 200.0), # NEW: Added latent gain
1100
- 'operating_hours': internal_loads.get('people', [{}])[0].get('hours_in_operation', '8h'), # NEW: Use updated hours
1101
- 'zone_type': internal_loads.get('people', [{}])[0].get('zone_type', 'A') # NEW: Use updated zone type
1102
  },
1103
  'lights': {
1104
  'power': sum(load['power'] for load in internal_loads.get('lighting', [])),
1105
  'use_factor': internal_loads.get('lighting', [{}])[0].get('usage_factor', 0.8),
1106
- 'hours_operation': internal_loads.get('lighting', [{}])[0].get('hours_in_operation', '8h'), # NEW: Use updated hours
1107
- 'zone_type': internal_loads.get('lighting', [{}])[0].get('zone_type', 'A') # NEW: Use updated zone type
1108
  },
1109
  'equipment': {
1110
  'power': sum(load['power'] for load in internal_loads.get('equipment', [])),
1111
  'use_factor': internal_loads.get('equipment', [{}])[0].get('usage_factor', 0.7),
1112
- 'hours_operation': internal_loads.get('equipment', [{}])[0].get('hours_in_operation', '8h'), # NEW: Use updated hours
1113
- 'zone_type': internal_loads.get('equipment', [{}])[0].get('zone_type', 'A') # NEW: Use updated zone type
1114
  },
1115
  'infiltration': {
1116
  'flow_rate': building_info.get('infiltration_rate', 0.05),
1117
- 'height': building_info.get('building_height', 3.0), # NEW: Use UI input
1118
  'crack_length': building_info.get('crack_length', 10.0)
1119
  },
1120
  'ventilation': {
1121
- 'flow_rate': building_info.get('ventilation_rate', 0.1) # NEW: Use UI input
1122
  },
1123
  'usage_factor': 0.7,
1124
  'operating_hours': building_info.get('operating_hours', '8:00-18:00')
@@ -1152,7 +1149,7 @@ class HVACCalculator:
1152
  'floor': design_loads['floors'] / 1000,
1153
  'windows': design_loads['windows'] / 1000,
1154
  'doors': design_loads['doors'] / 1000,
1155
- 'skylights': design_loads.get('skylights', 0) / 1000, # NEW: Skylights
1156
  'infiltration': (design_loads['infiltration_sensible'] + design_loads['infiltration_latent']) / 1000,
1157
  'ventilation': (design_loads['ventilation_sensible'] + design_loads['ventilation_latent']) / 1000
1158
  },
@@ -1162,7 +1159,7 @@ class HVACCalculator:
1162
  'floors': [],
1163
  'windows': [],
1164
  'doors': [],
1165
- 'skylights': [], # NEW: Skylights
1166
  'infiltration': {
1167
  'air_flow': formatted_internal_loads['infiltration']['flow_rate'],
1168
  'delta_t': indoor_conditions['temperature'] - outdoor_conditions['design_temperature'],
@@ -1190,7 +1187,7 @@ class HVACCalculator:
1190
  'orientation': wall.orientation.value,
1191
  'area': wall.area,
1192
  'u_value': wall.u_value,
1193
- 'solar_absorptivity': wall.solar_absorptivity, # CHANGED: Replaced surface_color
1194
  'delta_t': delta_t,
1195
  'load': load / 1000
1196
  })
@@ -1206,7 +1203,7 @@ class HVACCalculator:
1206
  'orientation': roof.orientation.value,
1207
  'area': roof.area,
1208
  'u_value': roof.u_value,
1209
- 'solar_absorptivity': roof.solar_absorptivity, # CHANGED: Replaced surface_color
1210
  'delta_t': delta_t,
1211
  'load': load / 1000
1212
  })
@@ -1230,15 +1227,15 @@ class HVACCalculator:
1230
  window=window,
1231
  outdoor_temp=outdoor_conditions['design_temperature'],
1232
  indoor_temp=indoor_conditions['temperature'],
1233
- frame_type=window.frame_type # NEW: Frame type
1234
  )
1235
  results['detailed_loads']['windows'].append({
1236
  'name': window.name,
1237
  'orientation': window.orientation.value,
1238
  'area': window.area,
1239
  'u_value': window.u_value,
1240
- 'glazing_type': window.glazing_type, # NEW: Glazing type
1241
- 'frame_type': window.frame_type, # NEW: Frame type
1242
  'delta_t': delta_t,
1243
  'load': load / 1000
1244
  })
@@ -1258,7 +1255,6 @@ class HVACCalculator:
1258
  'load': load / 1000
1259
  })
1260
 
1261
- # NEW: Skylights
1262
  for skylight in building_components.get('skylights', []):
1263
  load = self.heating_calculator.calculate_skylight_heating_load(
1264
  skylight=skylight,
 
17
 
18
  # Import application modules
19
  from app.building_info_form import BuildingInfoForm
20
+ from app.component_selection import ComponentSelectionInterface, Orientation, ComponentType, Wall, Roof, Floor, Window, Door, Skylight, GlazingType, FrameType
21
  from app.results_display import ResultsDisplay
22
  from app.data_validation import DataValidation
23
  from app.data_persistence import DataPersistence
 
41
  from utils.scenario_comparison import ScenarioComparisonVisualization
42
  from utils.psychrometric_visualization import PsychrometricVisualization
43
  from utils.time_based_visualization import TimeBasedVisualization
44
+ from data.drapery import Drapery
45
 
46
  # NEW: ASHRAE 62.1 Ventilation Rates (Table 6.1)
47
  VENTILATION_RATES = {
 
75
  'floors': [],
76
  'windows': [],
77
  'doors': [],
78
+ 'skylights': []
79
  }
80
 
81
  if 'internal_loads' not in st.session_state:
 
109
  self.data_export = DataExport()
110
  self.cooling_calculator = CoolingLoadCalculator()
111
  self.heating_calculator = HeatingLoadCalculator()
112
+ self.drapery = Drapery()
113
 
114
  # Persist ClimateData in session_state
115
  if 'climate_data_obj' not in st.session_state:
 
259
  existing_load['activity_level'] == new_load['activity_level'] and
260
  existing_load['zone_type'] == new_load['zone_type'] and
261
  existing_load['hours_in_operation'] == new_load['hours_in_operation'] and
262
+ existing_load['latent_gain'] == new_load['latent_gain']):
263
  return False, f"Duplicate people load '{new_load['name']}' already exists."
264
  elif load_type == 'lighting':
265
  if (existing_load['name'] == new_load['name'] and
 
306
  )
307
  zone_type = st.selectbox(
308
  "Zone Type",
309
+ ["A", "B", "C", "D"],
310
  help="Select zone type for CLF accuracy per ASHRAE"
311
  )
312
  hours_in_operation = st.selectbox(
313
+ "Hours Occupied",
314
+ ["2h", "4h", "6h"],
315
  help="Select hours of occupancy for CLF calculations"
316
  )
317
+ latent_gain = st.number_input(
318
  "Latent Gain per Person (Btu/h)",
319
  min_value=0.0,
320
  max_value=500.0,
 
332
  "activity_level": activity_level,
333
  "zone_type": zone_type,
334
  "hours_in_operation": hours_in_operation,
335
+ "latent_gain": latent_gain
336
  }
337
  is_valid, message = self.validate_internal_load('people', people_load)
338
  if is_valid:
 
378
  )
379
  zone_type = st.selectbox(
380
  "Zone Type",
381
+ ["A", "B", "C", "D"],
382
  help="Select zone type for CLF accuracy per ASHRAE"
383
  )
384
  hours_in_operation = st.selectbox(
385
+ "Hours On",
386
+ ["8h", "10h", "12h"],
387
  help="Select hours of lighting operation for CLF calculations"
388
  )
389
  lighting_name = st.text_input("Name", value="General Lighting")
 
449
  )
450
  zone_type = st.selectbox(
451
  "Zone Type",
452
+ ["A", "B", "C", "D"],
453
  help="Select zone type for CLF accuracy per ASHRAE"
454
  )
455
  hours_in_operation = st.selectbox(
456
+ "Hours Operated",
457
+ ["2h", "4h", "6h"],
458
  help="Select hours of equipment operation for CLF calculations"
459
  )
460
  equipment_name = st.text_input("Name", value="Office Equipment")
 
524
  step=0.1,
525
  help="Custom ventilation rate per floor area (ASHRAE 62.1)"
526
  )
527
+ ventilation_rate = st.number_input(
528
  "Ventilation Rate (m³/s)",
529
  min_value=0.0,
530
  max_value=10.0,
 
537
  area_rate = VENTILATION_RATES[zone_type]["area_rate"]
538
  st.write(f"People Rate: {people_rate} L/s/person (ASHRAE 62.1)")
539
  st.write(f"Area Rate: {area_rate} L/s/m² (ASHRAE 62.1)")
540
+ ventilation_rate = st.number_input(
541
  "Ventilation Rate (m³/s)",
542
  min_value=0.0,
543
  max_value=10.0,
 
618
  # Format conditions
619
  outdoor_conditions = {
620
  'temperature': location['summer_design_temp_db'],
621
+ 'relative_humidity': building_info.get('outdoor_rh', location['monthly_humidity'].get('Jul', 50.0)),
622
  'ground_temperature': location['monthly_temps'].get('Jul', 20.0),
623
  'month': 'Jul',
624
  'latitude': location['latitude'],
625
+ 'wind_speed': building_info.get('wind_speed', 4.0),
626
+ 'day_of_year': month_to_day.get('Jul', 182)
627
  }
628
  indoor_conditions = {
629
  'temperature': building_info.get('indoor_temp', 24.0),
630
+ 'relative_humidity': building_info.get('indoor_rh', 50.0)
631
  }
632
 
633
  if st.session_state.get('debug_mode', False):
 
649
  'people': {
650
  'number': sum(load['num_people'] for load in internal_loads.get('people', [])),
651
  'activity_level': internal_loads.get('people', [{}])[0].get('activity_level', 'Seated/Resting'),
652
+ 'operating_hours': internal_loads.get('people', [{}])[0].get('hours_in_operation', '8h'),
653
+ 'zone_type': internal_loads.get('people', [{}])[0].get('zone_type', 'A'),
654
+ 'latent_gain': internal_loads.get('people', [{}])[0].get('latent_gain', 200.0)
655
  },
656
  'lights': {
657
  'power': sum(load['power'] for load in internal_loads.get('lighting', [])),
658
  'use_factor': internal_loads.get('lighting', [{}])[0].get('usage_factor', 0.8),
659
  'special_allowance': 0.1,
660
+ 'hours_operation': internal_loads.get('lighting', [{}])[0].get('hours_in_operation', '8h'),
661
+ 'zone_type': internal_loads.get('lighting', [{}])[0].get('zone_type', 'A')
662
  },
663
  'equipment': {
664
  'power': sum(load['power'] for load in internal_loads.get('equipment', [])),
665
  'use_factor': internal_loads.get('equipment', [{}])[0].get('usage_factor', 0.7),
666
  'radiation_factor': internal_loads.get('equipment', [{}])[0].get('radiation_fraction', 0.3),
667
+ 'hours_operation': internal_loads.get('equipment', [{}])[0].get('hours_in_operation', '8h'),
668
+ 'zone_type': internal_loads.get('equipment', [{}])[0].get('zone_type', 'A')
669
  },
670
  'infiltration': {
671
+ 'flow_rate': building_info.get('infiltration_rate', 0.5),
672
+ 'height': building_info.get('building_height', 3.0),
673
  'crack_length': building_info.get('crack_length', 10.0)
674
  },
675
  'ventilation': {
676
+ 'flow_rate': building_info.get('ventilation_rate', 0.1)
677
  },
678
  'operating_hours': building_info.get('operating_hours', '8:00-18:00')
679
  }
 
723
  'roof': design_loads['roofs'] / 1000,
724
  'windows': (design_loads['windows_conduction'] + design_loads['windows_solar']) / 1000,
725
  'doors': design_loads['doors'] / 1000,
726
+ 'skylights': design_loads.get('skylights', 0) / 1000,
727
  'people': (design_loads['people_sensible'] + design_loads['people_latent']) / 1000,
728
  'lighting': design_loads['lights'] / 1000,
729
  'equipment': (design_loads['equipment_sensible'] + design_loads['equipment_latent']) / 1000,
 
735
  'roofs': [],
736
  'windows': [],
737
  'doors': [],
738
+ 'skylights': [],
739
  'internal': [],
740
  'infiltration': {
741
  'air_flow': formatted_internal_loads['infiltration']['flow_rate'],
 
762
  month=outdoor_conditions['month'],
763
  hour=design_loads['design_hour'],
764
  latitude=outdoor_conditions['latitude'],
765
+ solar_absorptivity=wall.solar_absorptivity
766
  )
767
  results['detailed_loads']['walls'].append({
768
  'name': wall.name,
769
  'orientation': wall.orientation.value,
770
  'area': wall.area,
771
  'u_value': wall.u_value,
772
+ 'solar_absorptivity': wall.solar_absorptivity,
773
+ 'cltd': self.cooling_calculator.ashrae_tables.get_cltd(
774
+ element_type='wall',
775
+ group=wall.wall_group,
776
  orientation=wall.orientation.value,
777
  hour=design_loads['design_hour'],
778
+ latitude=float(outdoor_conditions['latitude'].replace('N', '')),
779
+ solar_absorptivity=wall.solar_absorptivity,
780
+ month=outdoor_conditions['month'].lower()
 
 
781
  ),
782
  'load': load / 1000
783
  })
 
790
  month=outdoor_conditions['month'],
791
  hour=design_loads['design_hour'],
792
  latitude=outdoor_conditions['latitude'],
793
+ solar_absorptivity=roof.solar_absorptivity
794
  )
795
  results['detailed_loads']['roofs'].append({
796
  'name': roof.name,
797
  'orientation': roof.orientation.value,
798
  'area': roof.area,
799
  'u_value': roof.u_value,
800
+ 'solar_absorptivity': roof.solar_absorptivity,
801
+ 'cltd': self.cooling_calculator.ashrae_tables.get_cltd(
802
+ element_type='roof',
803
+ group=roof.roof_group,
804
+ orientation=roof.orientation.value,
805
  hour=design_loads['design_hour'],
806
+ latitude=float(outdoor_conditions['latitude'].replace('N', '')),
807
+ solar_absorptivity=roof.solar_absorptivity,
808
+ month=outdoor_conditions['month'].lower()
 
 
809
  ),
810
  'load': load / 1000
811
  })
812
 
813
  for window in building_components.get('windows', []):
814
+ adjusted_shgc = self.drapery.adjust_shgc(
 
815
  base_shgc=window.shgc,
816
  glazing_type=window.glazing_type,
817
  drapery_type=window.drapery_type if hasattr(window, 'drapery_type') else None
 
824
  hour=design_loads['design_hour'],
825
  latitude=outdoor_conditions['latitude'],
826
  shading_coefficient=window.shading_coefficient,
827
+ adjusted_shgc=adjusted_shgc,
828
+ glazing_type=window.glazing_type,
829
+ frame_type=window.frame_type
830
  )
831
  if 'total' not in load_dict:
832
  if 'conduction' in load_dict and 'solar' in load_dict:
 
840
  'area': window.area,
841
  'u_value': window.u_value,
842
  'shgc': window.shgc,
843
+ 'adjusted_shgc': adjusted_shgc,
844
+ 'glazing_type': window.glazing_type,
845
+ 'frame_type': window.frame_type,
846
+ 'drapery_type': window.drapery_type if hasattr(window, 'drapery_type') else 'None',
847
  'shading_device': window.shading_device,
848
  'shading_coefficient': window.shading_coefficient,
849
  'scl': self.cooling_calculator.ashrae_tables.get_scl(
850
+ latitude=float(outdoor_conditions['latitude'].replace('N', '')),
851
+ month=outdoor_conditions['month'].lower(),
852
  orientation=window.orientation.value,
853
  hour=design_loads['design_hour']
854
  ),
 
870
  'load': load / 1000
871
  })
872
 
 
873
  for skylight in building_components.get('skylights', []):
874
+ adjusted_shgc = self.drapery.adjust_shgc(
875
  base_shgc=skylight.shgc,
876
  glazing_type=skylight.glazing_type,
877
  drapery_type=skylight.drapery_type if hasattr(skylight, 'drapery_type') else None
 
905
  'drapery_type': skylight.drapery_type if hasattr(skylight, 'drapery_type') else 'None',
906
  'shading_coefficient': skylight.shading_coefficient,
907
  'scl': self.cooling_calculator.ashrae_tables.get_scl(
908
+ latitude=float(outdoor_conditions['latitude'].replace('N', '')),
909
+ month=outdoor_conditions['month'].lower(),
910
+ orientation='Horizontal',
911
  hour=design_loads['design_hour']
912
  ),
913
  'load': load_dict['total'] / 1000
 
920
  num_people=load['num_people'],
921
  activity_level=load['activity_level'],
922
  hour=design_loads['design_hour'],
923
+ latent_gain=load.get('latent_gain', 200.0)
924
  )
925
  if 'total' not in load_dict and ('sensible' in load_dict or 'latent' in load_dict):
926
  load_dict['total'] = load_dict.get('sensible', 0) + load_dict.get('latent', 0)
 
1020
 
1021
  # Skip heating calculation if outdoor temp exceeds indoor temp
1022
  indoor_temp = building_info.get('indoor_temp', 21.0)
1023
+ outdoor_temp = building_info.get('winter_temp', location['winter_design_temp'])
1024
  if outdoor_temp >= indoor_temp:
1025
  results = {
1026
  'total_load': 0.0,
 
1033
  'floor': 0.0,
1034
  'windows': 0.0,
1035
  'doors': 0.0,
1036
+ 'skylights': 0.0,
1037
  'infiltration': 0.0,
1038
  'ventilation': 0.0
1039
  },
 
1043
  'floors': [],
1044
  'windows': [],
1045
  'doors': [],
1046
+ 'skylights': [],
1047
  'infiltration': {'air_flow': 0.0, 'delta_t': 0.0, 'load': 0.0},
1048
  'ventilation': {'air_flow': 0.0, 'delta_t': 0.0, 'load': 0.0}
1049
  },
 
1053
 
1054
  # Format conditions
1055
  outdoor_conditions = {
1056
+ 'design_temperature': outdoor_temp,
1057
+ 'design_relative_humidity': building_info.get('outdoor_rh', location['monthly_humidity'].get('Jan', 80.0)),
1058
  'ground_temperature': ground_temperature,
1059
+ 'wind_speed': building_info.get('wind_speed', 4.0)
1060
  }
1061
  indoor_conditions = {
1062
  'temperature': indoor_temp,
1063
+ 'relative_humidity': building_info.get('indoor_rh', 40.0)
1064
  }
1065
 
1066
  if st.session_state.get('debug_mode', False):
 
1093
  internal_loads.get('people', [{}])[0].get('activity_level', 'Seated/Resting'),
1094
  70.0
1095
  ),
1096
+ 'latent_gain': internal_loads.get('people', [{}])[0].get('latent_gain', 200.0),
1097
+ 'operating_hours': internal_loads.get('people', [{}])[0].get('hours_in_operation', '8h'),
1098
+ 'zone_type': internal_loads.get('people', [{}])[0].get('zone_type', 'A')
1099
  },
1100
  'lights': {
1101
  'power': sum(load['power'] for load in internal_loads.get('lighting', [])),
1102
  'use_factor': internal_loads.get('lighting', [{}])[0].get('usage_factor', 0.8),
1103
+ 'hours_operation': internal_loads.get('lighting', [{}])[0].get('hours_in_operation', '8h'),
1104
+ 'zone_type': internal_loads.get('lighting', [{}])[0].get('zone_type', 'A')
1105
  },
1106
  'equipment': {
1107
  'power': sum(load['power'] for load in internal_loads.get('equipment', [])),
1108
  'use_factor': internal_loads.get('equipment', [{}])[0].get('usage_factor', 0.7),
1109
+ 'hours_operation': internal_loads.get('equipment', [{}])[0].get('hours_in_operation', '8h'),
1110
+ 'zone_type': internal_loads.get('equipment', [{}])[0].get('zone_type', 'A')
1111
  },
1112
  'infiltration': {
1113
  'flow_rate': building_info.get('infiltration_rate', 0.05),
1114
+ 'height': building_info.get('building_height', 3.0),
1115
  'crack_length': building_info.get('crack_length', 10.0)
1116
  },
1117
  'ventilation': {
1118
+ 'flow_rate': building_info.get('ventilation_rate', 0.1)
1119
  },
1120
  'usage_factor': 0.7,
1121
  'operating_hours': building_info.get('operating_hours', '8:00-18:00')
 
1149
  'floor': design_loads['floors'] / 1000,
1150
  'windows': design_loads['windows'] / 1000,
1151
  'doors': design_loads['doors'] / 1000,
1152
+ 'skylights': design_loads.get('skylights', 0) / 1000,
1153
  'infiltration': (design_loads['infiltration_sensible'] + design_loads['infiltration_latent']) / 1000,
1154
  'ventilation': (design_loads['ventilation_sensible'] + design_loads['ventilation_latent']) / 1000
1155
  },
 
1159
  'floors': [],
1160
  'windows': [],
1161
  'doors': [],
1162
+ 'skylights': [],
1163
  'infiltration': {
1164
  'air_flow': formatted_internal_loads['infiltration']['flow_rate'],
1165
  'delta_t': indoor_conditions['temperature'] - outdoor_conditions['design_temperature'],
 
1187
  'orientation': wall.orientation.value,
1188
  'area': wall.area,
1189
  'u_value': wall.u_value,
1190
+ 'solar_absorptivity': wall.solar_absorptivity,
1191
  'delta_t': delta_t,
1192
  'load': load / 1000
1193
  })
 
1203
  'orientation': roof.orientation.value,
1204
  'area': roof.area,
1205
  'u_value': roof.u_value,
1206
+ 'solar_absorptivity': roof.solar_absorptivity,
1207
  'delta_t': delta_t,
1208
  'load': load / 1000
1209
  })
 
1227
  window=window,
1228
  outdoor_temp=outdoor_conditions['design_temperature'],
1229
  indoor_temp=indoor_conditions['temperature'],
1230
+ frame_type=window.frame_type
1231
  )
1232
  results['detailed_loads']['windows'].append({
1233
  'name': window.name,
1234
  'orientation': window.orientation.value,
1235
  'area': window.area,
1236
  'u_value': window.u_value,
1237
+ 'glazing_type': window.glazing_type,
1238
+ 'frame_type': window.frame_type,
1239
  'delta_t': delta_t,
1240
  'load': load / 1000
1241
  })
 
1255
  'load': load / 1000
1256
  })
1257
 
 
1258
  for skylight in building_components.get('skylights', []):
1259
  load = self.heating_calculator.calculate_skylight_heating_load(
1260
  skylight=skylight,