mabuseif commited on
Commit
a022bc3
·
verified ·
1 Parent(s): 83da2d5

Update app/main.py

Browse files
Files changed (1) hide show
  1. app/main.py +22 -33
app/main.py CHANGED
@@ -3,7 +3,8 @@ HVAC Calculator Code Documentation.
3
  Updated 2025-05-02: Integrated skylights, surface color, glazing type, frame type, and drapery adjustments from main_new.py.
4
  Updated 2025-05-02: Enhanced per Plan.txt to include winter design temperature, humidity, building height, ventilation rate, internal load enhancements, and calculation parameters.
5
  Updated 2025-05-09: Fixed latitude parsing to return string (e.g., "24N") to match ASHRAE table keys and added group validation.
6
- Updated 2025-05-09: Corrected group validation to use alphabetical groups (A, B, C, ...) and enhanced stale component handling.
 
7
  """
8
 
9
  import streamlit as st
@@ -54,8 +55,8 @@ VENTILATION_RATES = {
54
  "Custom": {"people_rate": 0.0, "area_rate": 0.0}
55
  }
56
 
57
- # Valid wall and roof groups for ASHRAE CLTD tables (alphabetical, based on ASHRAE tables)
58
- VALID_WALL_GROUPS = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J']
59
  VALID_ROOF_GROUPS = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
60
 
61
  class HVACCalculator:
@@ -231,7 +232,7 @@ class HVACCalculator:
231
  return False, f"Ground temperature for {comp.name} must be between -10°C and 40°C"
232
  if getattr(comp, 'perimeter', 0) < 0:
233
  return False, f"Perimeter for {comp.name} cannot be negative"
234
- # NEW: Validate solar absorptivity for walls and roofs
235
  if component_type in ['walls', 'roofs']:
236
  if not 0.1 <= getattr(comp, 'solar_absorptivity', 0.6) <= 1.0:
237
  return False, f"Invalid solar absorptivity for {component_type}: {comp.name} (must be 0.1-1.0)"
@@ -243,7 +244,7 @@ class HVACCalculator:
243
  return False, f"Glazing type missing for {component_type}: {comp.name}"
244
  if getattr(comp, 'frame_type', None) is None:
245
  return False, f"Frame type missing for {component_type}: {comp.name}"
246
- # NEW: Validate wall and roof groups
247
  if component_type == 'walls':
248
  if getattr(comp, 'wall_group', '') not in VALID_WALL_GROUPS:
249
  return False, f"Invalid wall group '{comp.wall_group}' for {comp.name}. Valid groups: {', '.join(VALID_WALL_GROUPS)}"
@@ -257,7 +258,7 @@ class HVACCalculator:
257
  if building_info.get('zone_type', '') == 'Custom' and building_info.get('ventilation_rate', 0) == 0:
258
  return False, "Custom ventilation rate must be specified"
259
 
260
- # NEW: Validate new inputs from Plan.txt
261
  if not -50 <= building_info.get('winter_temp', -10) <= 20:
262
  return False, "Winter design temperature must be -50 to 20°C"
263
  if not 0 <= building_info.get('outdoor_rh', 50) <= 100:
@@ -307,26 +308,11 @@ class HVACCalculator:
307
  def parse_latitude(self, latitude: Any) -> str:
308
  """Parse latitude from string or number to ASHRAE table format (e.g., '24N')."""
309
  try:
310
- if isinstance(latitude, (int, float)):
311
- lat_value = float(latitude)
312
- elif isinstance(latitude, str):
313
- lat_str = latitude.strip().upper().replace('N', '').replace('S', '')
314
- lat_value = float(lat_str)
315
- else:
316
- raise ValueError("Invalid latitude format")
317
-
318
- # Convert to ASHRAE table format (e.g., '24N', '32N')
319
- supported_latitudes = [24, 32, 40, 48]
320
- if lat_value in supported_latitudes:
321
- return f"{int(lat_value)}N"
322
- else:
323
- closest_lat = min(supported_latitudes, key=lambda x: abs(x - lat_value))
324
- st.warning(f"Latitude {lat_value} not in ASHRAE tables. Using closest: {closest_lat}N")
325
- return f"{closest_lat}N"
326
-
327
- except (ValueError, AttributeError) as e:
328
  st.error(f"Invalid latitude: {latitude}. Using default 32N.")
329
- return "32N" # Default to 32N
330
 
331
  def display_internal_loads(self):
332
  st.title("Internal Loads")
@@ -664,13 +650,16 @@ class HVACCalculator:
664
  "Jul": 196, "Aug": 227, "Sep": 258, "Oct": 288, "Nov": 319, "Dec": 350
665
  }
666
 
 
 
 
667
  # Format conditions
668
  outdoor_conditions = {
669
  'temperature': location['summer_design_temp_db'],
670
  'relative_humidity': building_info.get('outdoor_rh', location['monthly_humidity'].get('Jul', 50.0)),
671
  'ground_temperature': location['monthly_temps'].get('Jul', 20.0),
672
  'month': 'Jul',
673
- 'latitude': location['latitude'],
674
  'wind_speed': building_info.get('wind_speed', 4.0),
675
  'day_of_year': month_to_day.get('Jul', 182)
676
  }
@@ -821,7 +810,7 @@ class HVACCalculator:
821
  'group': wall.wall_group,
822
  'orientation': wall.orientation.value,
823
  'hour': design_loads['design_hour'],
824
- 'latitude': self.parse_latitude(outdoor_conditions['latitude']),
825
  'solar_absorptivity': wall.solar_absorptivity
826
  })
827
  results['detailed_loads']['walls'].append({
@@ -835,7 +824,7 @@ class HVACCalculator:
835
  group=wall.wall_group,
836
  orientation=wall.orientation.value,
837
  hour=design_loads['design_hour'],
838
- latitude=self.parse_latitude(outdoor_conditions['latitude']),
839
  solar_absorptivity=wall.solar_absorptivity
840
  ),
841
  'load': load / 1000
@@ -862,7 +851,7 @@ class HVACCalculator:
862
  'group': roof.roof_group,
863
  'orientation': roof.orientation.value,
864
  'hour': design_loads['design_hour'],
865
- 'latitude': self.parse_latitude(outdoor_conditions['latitude']),
866
  'solar_absorptivity': roof.solar_absorptivity
867
  })
868
  results['detailed_loads']['roofs'].append({
@@ -876,7 +865,7 @@ class HVACCalculator:
876
  group=roof.roof_group,
877
  orientation=roof.orientation.value,
878
  hour=design_loads['design_hour'],
879
- latitude=self.parse_latitude(outdoor_conditions['latitude']),
880
  solar_absorptivity=roof.solar_absorptivity
881
  ),
882
  'load': load / 1000
@@ -922,7 +911,7 @@ class HVACCalculator:
922
  'shading_device': window.shading_device,
923
  'shading_coefficient': window.shading_coefficient,
924
  'scl': self.cooling_calculator.ashrae_tables.get_scl(
925
- latitude=self.parse_latitude(outdoor_conditions['latitude']),
926
  month=outdoor_conditions['month'].lower(),
927
  orientation=window.orientation.value,
928
  hour=design_loads['design_hour']
@@ -980,7 +969,7 @@ class HVACCalculator:
980
  'drapery_type': skylight.drapery_type if hasattr(skylight, 'drapery_type') else 'None',
981
  'shading_coefficient': skylight.shading_coefficient,
982
  'scl': self.cooling_calculator.ashrae_tables.get_scl(
983
- latitude=self.parse_latitude(outdoor_conditions['latitude']),
984
  month=outdoor_conditions['month'].lower(),
985
  orientation='Horizontal',
986
  hour=design_loads['design_hour']
@@ -1292,7 +1281,7 @@ class HVACCalculator:
1292
  results['detailed_loads']['floors'].append({
1293
  'name': floor.name,
1294
  'area': floor.area,
1295
- 'u_value': floor.u_value, # FIXED: Corrected typo from previous version
1296
  'delta_t': indoor_conditions['temperature'] - outdoor_conditions['ground_temperature'],
1297
  'load': load / 1000
1298
  })
 
3
  Updated 2025-05-02: Integrated skylights, surface color, glazing type, frame type, and drapery adjustments from main_new.py.
4
  Updated 2025-05-02: Enhanced per Plan.txt to include winter design temperature, humidity, building height, ventilation rate, internal load enhancements, and calculation parameters.
5
  Updated 2025-05-09: Fixed latitude parsing to return string (e.g., "24N") to match ASHRAE table keys and added group validation.
6
+ Updated 2025-05-09: Corrected group validation to use alphabetical groups (A-H for walls, A-G for roofs) and enhanced stale component handling.
7
+ Updated 2025-05-10: Aligned latitude parsing with cooling_load.py's validate_latitude and updated wall groups to A-H to match cooling_load.py.
8
  """
9
 
10
  import streamlit as st
 
55
  "Custom": {"people_rate": 0.0, "area_rate": 0.0}
56
  }
57
 
58
+ # Valid wall and roof groups for ASHRAE CLTD tables (aligned with cooling_load.py)
59
+ VALID_WALL_GROUPS = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
60
  VALID_ROOF_GROUPS = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
61
 
62
  class HVACCalculator:
 
232
  return False, f"Ground temperature for {comp.name} must be between -10°C and 40°C"
233
  if getattr(comp, 'perimeter', 0) < 0:
234
  return False, f"Perimeter for {comp.name} cannot be negative"
235
+ # Validate solar absorptivity for walls and roofs
236
  if component_type in ['walls', 'roofs']:
237
  if not 0.1 <= getattr(comp, 'solar_absorptivity', 0.6) <= 1.0:
238
  return False, f"Invalid solar absorptivity for {component_type}: {comp.name} (must be 0.1-1.0)"
 
244
  return False, f"Glazing type missing for {component_type}: {comp.name}"
245
  if getattr(comp, 'frame_type', None) is None:
246
  return False, f"Frame type missing for {component_type}: {comp.name}"
247
+ # Validate wall and roof groups
248
  if component_type == 'walls':
249
  if getattr(comp, 'wall_group', '') not in VALID_WALL_GROUPS:
250
  return False, f"Invalid wall group '{comp.wall_group}' for {comp.name}. Valid groups: {', '.join(VALID_WALL_GROUPS)}"
 
258
  if building_info.get('zone_type', '') == 'Custom' and building_info.get('ventilation_rate', 0) == 0:
259
  return False, "Custom ventilation rate must be specified"
260
 
261
+ # Validate new inputs from Plan.txt
262
  if not -50 <= building_info.get('winter_temp', -10) <= 20:
263
  return False, "Winter design temperature must be -50 to 20°C"
264
  if not 0 <= building_info.get('outdoor_rh', 50) <= 100:
 
308
  def parse_latitude(self, latitude: Any) -> str:
309
  """Parse latitude from string or number to ASHRAE table format (e.g., '24N')."""
310
  try:
311
+ # Use cooling_calculator's validate_latitude for consistency
312
+ return self.cooling_calculator.validate_latitude(latitude)
313
+ except Exception as e:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
  st.error(f"Invalid latitude: {latitude}. Using default 32N.")
315
+ return "32N"
316
 
317
  def display_internal_loads(self):
318
  st.title("Internal Loads")
 
650
  "Jul": 196, "Aug": 227, "Sep": 258, "Oct": 288, "Nov": 319, "Dec": 350
651
  }
652
 
653
+ # Validate latitude using cooling_calculator
654
+ latitude = self.cooling_calculator.validate_latitude(location['latitude'])
655
+
656
  # Format conditions
657
  outdoor_conditions = {
658
  'temperature': location['summer_design_temp_db'],
659
  'relative_humidity': building_info.get('outdoor_rh', location['monthly_humidity'].get('Jul', 50.0)),
660
  'ground_temperature': location['monthly_temps'].get('Jul', 20.0),
661
  'month': 'Jul',
662
+ 'latitude': latitude,
663
  'wind_speed': building_info.get('wind_speed', 4.0),
664
  'day_of_year': month_to_day.get('Jul', 182)
665
  }
 
810
  'group': wall.wall_group,
811
  'orientation': wall.orientation.value,
812
  'hour': design_loads['design_hour'],
813
+ 'latitude': outdoor_conditions['latitude'],
814
  'solar_absorptivity': wall.solar_absorptivity
815
  })
816
  results['detailed_loads']['walls'].append({
 
824
  group=wall.wall_group,
825
  orientation=wall.orientation.value,
826
  hour=design_loads['design_hour'],
827
+ latitude=float(outdoor_conditions['latitude'].replace('N', '')),
828
  solar_absorptivity=wall.solar_absorptivity
829
  ),
830
  'load': load / 1000
 
851
  'group': roof.roof_group,
852
  'orientation': roof.orientation.value,
853
  'hour': design_loads['design_hour'],
854
+ 'latitude': outdoor_conditions['latitude'],
855
  'solar_absorptivity': roof.solar_absorptivity
856
  })
857
  results['detailed_loads']['roofs'].append({
 
865
  group=roof.roof_group,
866
  orientation=roof.orientation.value,
867
  hour=design_loads['design_hour'],
868
+ latitude=float(outdoor_conditions['latitude'].replace('N', '')),
869
  solar_absorptivity=roof.solar_absorptivity
870
  ),
871
  'load': load / 1000
 
911
  'shading_device': window.shading_device,
912
  'shading_coefficient': window.shading_coefficient,
913
  'scl': self.cooling_calculator.ashrae_tables.get_scl(
914
+ latitude=outdoor_conditions['latitude'],
915
  month=outdoor_conditions['month'].lower(),
916
  orientation=window.orientation.value,
917
  hour=design_loads['design_hour']
 
969
  'drapery_type': skylight.drapery_type if hasattr(skylight, 'drapery_type') else 'None',
970
  'shading_coefficient': skylight.shading_coefficient,
971
  'scl': self.cooling_calculator.ashrae_tables.get_scl(
972
+ latitude=outdoor_conditions['latitude'],
973
  month=outdoor_conditions['month'].lower(),
974
  orientation='Horizontal',
975
  hour=design_loads['design_hour']
 
1281
  results['detailed_loads']['floors'].append({
1282
  'name': floor.name,
1283
  'area': floor.area,
1284
+ 'u_value': floor.u_value,
1285
  'delta_t': indoor_conditions['temperature'] - outdoor_conditions['ground_temperature'],
1286
  'load': load / 1000
1287
  })