mabuseif commited on
Commit
2de4402
·
verified ·
1 Parent(s): 9a63b4c

Update utils/cooling_load.py

Browse files
Files changed (1) hide show
  1. utils/cooling_load.py +238 -283
utils/cooling_load.py CHANGED
@@ -3,10 +3,11 @@ Cooling load calculation module for HVAC Load Calculator.
3
  Based on ASHRAE steady-state calculation methods.
4
 
5
  Author: Dr Majed Abuseif
6
- Date: March 2025
7
- Version: 1.0.0
8
  """
9
 
 
10
  from typing import Dict, List, Any, Optional, Tuple
11
  import numpy as np
12
  from datetime import datetime
@@ -53,7 +54,8 @@ class CoolingLoadCalculator:
53
  parts = latitude.split('_')
54
  if len(parts) > 1:
55
  lat_part = parts[0]
56
- logger.warning(f"Detected concatenated input: {latitude}. Using latitude={lat_part}")
 
57
  latitude = lat_part
58
 
59
  # Handle formats like '31.973N', '1_31.973N'
@@ -68,17 +70,20 @@ class CoolingLoadCalculator:
68
  else:
69
  return '48N'
70
  except ValueError:
71
- logger.warning(f"Cannot parse latitude: {latitude}. Defaulting to '24N'")
 
72
  return '24N'
73
 
74
  if latitude not in self.valid_latitudes:
75
- logger.warning(f"Invalid latitude: {latitude}. Defaulting to '24N'")
 
76
  return '24N'
77
 
78
  return latitude
79
 
80
  except Exception as e:
81
- logger.error(f"Error validating latitude {latitude}: {str(e)}")
 
82
  return '24N'
83
 
84
  def validate_month(self, month: Any) -> str:
@@ -97,14 +102,38 @@ class CoolingLoadCalculator:
97
 
98
  month_upper = month.strip().upper()
99
  if month_upper not in self.valid_months:
100
- logger.warning(f"Invalid month: {month}. Defaulting to 'JUL'")
 
101
  return 'JUL'
102
  return month_upper
103
 
104
  except Exception as e:
105
- logger.error(f"Error validating month {month}: {str(e)}")
 
106
  return 'JUL'
107
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  def calculate_hourly_cooling_loads(
109
  self,
110
  building_components: Dict[str, List[Any]],
@@ -147,7 +176,8 @@ class CoolingLoadCalculator:
147
  # Validate inputs
148
  latitude = self.validate_latitude(outdoor_conditions.get('latitude', '24N'))
149
  month = self.validate_month(outdoor_conditions.get('month', 'JUL'))
150
- logger.debug(f"calculate_hourly_cooling_loads: latitude={latitude}, month={month}, outdoor_conditions={outdoor_conditions}")
 
151
 
152
  # Calculate loads for walls
153
  for wall in building_components.get('walls', []):
@@ -256,6 +286,8 @@ class CoolingLoadCalculator:
256
  return hourly_loads
257
 
258
  except Exception as e:
 
 
259
  raise Exception(f"Error in calculate_hourly_cooling_loads: {str(e)}")
260
 
261
  def calculate_design_cooling_load(self, hourly_loads: Dict[str, Any]) -> Dict[str, Any]:
@@ -314,6 +346,8 @@ class CoolingLoadCalculator:
314
  return design_loads
315
 
316
  except Exception as e:
 
 
317
  raise Exception(f"Error in calculate_design_cooling_load: {str(e)}")
318
 
319
  def calculate_cooling_load_summary(self, design_loads: Dict[str, Any]) -> Dict[str, float]:
@@ -356,6 +390,8 @@ class CoolingLoadCalculator:
356
  }
357
 
358
  except Exception as e:
 
 
359
  raise Exception(f"Error in calculate_cooling_load_summary: {str(e)}")
360
 
361
  def calculate_wall_cooling_load(
@@ -384,7 +420,9 @@ class CoolingLoadCalculator:
384
  try:
385
  latitude = self.validate_latitude(latitude)
386
  month = self.validate_month(month)
387
- logger.debug(f"calculate_wall_cooling_load: latitude={latitude}, month={month}, wall_group={wall.wall_group}, orientation={wall.orientation.value}")
 
 
388
 
389
  # Validate wall_group
390
  wall_group = str(wall.wall_group).upper()
@@ -392,9 +430,11 @@ class CoolingLoadCalculator:
392
  numeric_map = {'1': 'A', '2': 'B', '3': 'C', '4': 'D'}
393
  if wall_group in numeric_map:
394
  wall_group = numeric_map[wall_group]
395
- logger.info(f"Mapped wall_group {wall.wall_group} to {wall_group}")
 
396
  else:
397
- logger.warning(f"Invalid wall group: {wall_group}. Defaulting to 'A'")
 
398
  wall_group = 'A'
399
 
400
  try:
@@ -409,16 +449,19 @@ class CoolingLoadCalculator:
409
  outdoor_temp=outdoor_temp
410
  )
411
  except Exception as e:
412
- logger.error(f"calculate_corrected_cltd_wall failed for wall_group={wall_group}: {str(e)}")
413
- logger.warning("Using default CLTD=10.0°C")
414
- cltd = 10.0
 
415
 
416
  load = wall.u_value * wall.area * cltd
417
- logger.debug(f"Wall load: u_value={wall.u_value}, area={wall.area}, cltd={cltd}, load={load}")
 
418
  return max(load, 0.0)
419
 
420
  except Exception as e:
421
- logger.error(f"Error in calculate_wall_cooling_load: {str(e)}")
 
422
  raise Exception(f"Error in calculate_wall_cooling_load: {str(e)}")
423
 
424
  def calculate_roof_cooling_load(
@@ -447,11 +490,14 @@ class CoolingLoadCalculator:
447
  try:
448
  latitude = self.validate_latitude(latitude)
449
  month = self.validate_month(month)
450
- logger.debug(f"calculate_roof_cooling_load: latitude={latitude}, month={month}, roof_group={roof.roof_group}")
 
 
451
 
452
  roof_group = str(roof.roof_group).upper()
453
  if roof_group not in self.valid_roof_groups:
454
- logger.warning(f"Invalid roof group: {roof_group}. Defaulting to 'A'")
 
455
  roof_group = 'A'
456
 
457
  try:
@@ -465,16 +511,19 @@ class CoolingLoadCalculator:
465
  outdoor_temp=outdoor_temp
466
  )
467
  except Exception as e:
468
- logger.error(f"calculate_corrected_cltd_roof failed for roof_group={roof_group}: {str(e)}")
469
- logger.warning("Using default CLTD=10.0°C")
470
- cltd = 10.0
 
471
 
472
  load = roof.u_value * roof.area * cltd
473
- logger.debug(f"Roof load: u_value={roof.u_value}, area={roof.area}, cltd={cltd}, load={load}")
 
474
  return max(load, 0.0)
475
 
476
  except Exception as e:
477
- logger.error(f"Error in calculate_roof_cooling_load: {str(e)}")
 
478
  raise Exception(f"Error in calculate_roof_cooling_load: {str(e)}")
479
 
480
  def calculate_window_cooling_load(
@@ -494,74 +543,55 @@ class CoolingLoadCalculator:
494
  window: Window component
495
  outdoor_temp: Outdoor temperature (°C)
496
  indoor_temp: Indoor temperature (°C)
497
- month: Design month (e.g., 'Jan', 'Jul')
498
  hour: Hour of the day
499
- latitude: Latitude (e.g., '24N', '36N', '48N')
500
- shading_coefficient: Shading coefficient for drapery
501
 
502
  Returns:
503
- Dictionary with conduction and solar loads in Watts
504
  """
505
  try:
506
- scl_latitude = self.validate_latitude(latitude)
507
- month_upper = self.validate_month(month)
508
- logger.debug(f"calculate_window_cooling_load: latitude={latitude} -> {scl_latitude}, month={month} -> {month_upper}, orientation={window.orientation.value}")
 
 
509
 
510
  # Conduction load
511
- delta_t = outdoor_temp - indoor_temp
512
- conduction_load = window.u_value * window.area * delta_t
513
-
514
  # Solar load
515
- logger.debug(f"Calling get_scl with month={month_upper}, orientation={window.orientation.value}, hour={hour}, latitude={scl_latitude}")
516
-
517
- month_formats = [
518
- (month_upper, "uppercase"),
519
- (month_upper.capitalize(), "capitalized"),
520
- (str({'JAN': 1, 'FEB': 2, 'MAR': 3, 'APR': 4, 'MAY': 5, 'JUN': 6,
521
- 'JUL': 7, 'AUG': 8, 'SEP': 9, 'OCT': 10, 'NOV': 11, 'DEC': 12}[month_upper]), "numeric")
522
- ]
523
-
524
- scl = None
525
- for month_value, format_name in month_formats:
526
- try:
527
- logger.debug(f"Trying get_scl with month format '{format_name}': month={month_value}, latitude={scl_latitude}")
528
- scl = self.ashrae_tables.get_scl(
529
- latitude=scl_latitude,
530
- month=month_value,
531
- orientation=window.orientation.value,
532
- hour=hour
533
- )
534
- logger.debug(f"Success with month format '{format_name}'")
535
- break
536
- except Exception as e:
537
- logger.warning(f"get_scl failed with month format '{format_name}': {str(e)}")
538
- continue
539
-
540
- if scl is None:
541
- logger.warning(f"All month formats failed. Retrying with fallback latitude='24N', month='JUL'")
542
- try:
543
- scl = self.ashrae_tables.get_scl(
544
- latitude='24N',
545
- month='JUL',
546
- orientation=window.orientation.value,
547
- hour=hour
548
- )
549
- except Exception as e:
550
- logger.error(f"Fallback get_scl failed: {str(e)}")
551
  logger.warning("Using default SCL=100 W/m²")
552
- scl = 100.0 # Conservative fallback
553
-
554
- solar_load = window.area * window.shgc * scl * shading_coefficient
555
-
556
- logger.debug(f"Window load: conduction={conduction_load}, solar={solar_load}")
 
 
 
557
  return {
558
  'conduction': max(conduction_load, 0.0),
559
  'solar': max(solar_load, 0.0),
560
- 'total': max(conduction_load + solar_load, 0.0)
561
  }
562
 
563
  except Exception as e:
564
- logger.error(f"Error in calculate_window_cooling_load: {str(e)}")
 
565
  raise Exception(f"Error in calculate_window_cooling_load: {str(e)}")
566
 
567
  def calculate_door_cooling_load(
@@ -582,13 +612,18 @@ class CoolingLoadCalculator:
582
  Cooling load in Watts
583
  """
584
  try:
585
- delta_t = outdoor_temp - indoor_temp
586
- load = door.u_value * door.area * delta_t
587
- logger.debug(f"Door load: u_value={door.u_value}, area={door.area}, delta_t={delta_t}, load={load}")
 
 
 
 
588
  return max(load, 0.0)
589
 
590
  except Exception as e:
591
- logger.error(f"Error in calculate_door_cooling_load: {str(e)}")
 
592
  raise Exception(f"Error in calculate_door_cooling_load: {str(e)}")
593
 
594
  def calculate_people_cooling_load(
@@ -602,52 +637,55 @@ class CoolingLoadCalculator:
602
 
603
  Args:
604
  num_people: Number of people
605
- activity_level: Activity level
606
  hour: Hour of the day
607
 
608
  Returns:
609
  Dictionary with sensible and latent loads in Watts
610
  """
611
  try:
612
- valid_activities = ['Seated/Resting', 'Light Work', 'Moderate Work', 'Heavy Work']
613
- if activity_level not in valid_activities:
614
- logger.warning(f"Invalid activity_level: {activity_level}. Defaulting to 'Seated/Resting'")
615
- activity_level = 'Seated/Resting'
616
-
617
- sensible_gain = {
618
- 'Seated/Resting': 70,
619
- 'Light Work': 100,
620
- 'Moderate Work': 150,
621
- 'Heavy Work': 200
622
- }[activity_level]
623
-
624
- latent_gain = {
625
- 'Seated/Resting': 45,
626
- 'Light Work': 75,
627
- 'Moderate Work': 120,
628
- 'Heavy Work': 180
629
- }[activity_level]
630
-
631
- logger.debug(f"Calling get_clf_people with zone_type='A', hours_occupied='6h', hour={hour}")
632
  try:
633
- clf = self.ashrae_tables.get_clf_people('A', '6h', hour)
 
 
 
 
634
  except Exception as e:
635
- logger.error(f"get_clf_people('A', '6h', {hour}) failed: {str(e)}")
636
- logger.warning("Using default CLF=1.0 for people")
637
- clf = 1.0
638
-
639
- sensible_load = num_people * sensible_gain * clf
640
- latent_load = num_people * latent_gain
641
-
642
- logger.debug(f"People load: num_people={num_people}, sensible={sensible_load}, latent={latent_load}")
 
 
643
  return {
644
  'sensible': max(sensible_load, 0.0),
645
- 'latent': max(latent_load, 0.0),
646
- 'total': max(sensible_load + latent_load, 0.0)
647
  }
648
 
649
  except Exception as e:
650
- logger.error(f"Error in calculate_people_cooling_load: {str(e)}")
 
651
  raise Exception(f"Error in calculate_people_cooling_load: {str(e)}")
652
 
653
  def calculate_lights_cooling_load(
@@ -661,8 +699,8 @@ class CoolingLoadCalculator:
661
  Calculate cooling load from lighting.
662
 
663
  Args:
664
- power: Lighting power (W)
665
- use_factor: Usage factor
666
  special_allowance: Special allowance factor
667
  hour: Hour of the day
668
 
@@ -670,19 +708,30 @@ class CoolingLoadCalculator:
670
  Cooling load in Watts
671
  """
672
  try:
673
- logger.debug(f"Calling get_clf_lights with zone_type='A', hours_on='8h', hour={hour}")
 
 
 
674
  try:
675
- clf = self.ashrae_tables.get_clf_lights('A', '8h', hour)
 
 
 
 
676
  except Exception as e:
677
- logger.error(f"get_clf_lights('A', '8h', {hour}) failed: {str(e)}")
678
- logger.warning("Using default CLF=1.0 for lights")
679
- clf = 1.0
 
 
680
  load = power * use_factor * special_allowance * clf
681
- logger.debug(f"Lights load: power={power}, use_factor={use_factor}, special_allowance={special_allowance}, clf={clf}, load={load}")
 
682
  return max(load, 0.0)
683
 
684
  except Exception as e:
685
- logger.error(f"Error in calculate_lights_cooling_load: {str(e)}")
 
686
  raise Exception(f"Error in calculate_lights_cooling_load: {str(e)}")
687
 
688
  def calculate_equipment_cooling_load(
@@ -696,34 +745,44 @@ class CoolingLoadCalculator:
696
  Calculate cooling load from equipment.
697
 
698
  Args:
699
- power: Equipment power (W)
700
- use_factor: Usage factor
701
- radiation_factor: Radiation factor
702
  hour: Hour of the day
703
 
704
  Returns:
705
  Dictionary with sensible and latent loads in Watts
706
  """
707
  try:
708
- logger.debug(f"Calling get_clf_equipment with zone_type='A', hours_operated='6h', hour={hour}")
 
 
 
709
  try:
710
- clf = self.ashrae_tables.get_clf_equipment('A', '6h', hour)
 
 
 
 
711
  except Exception as e:
712
- logger.error(f"get_clf_equipment('A', '6h', {hour}) failed: {str(e)}")
713
- logger.warning("Using default CLF=1.0 for equipment")
714
- clf = 1.0
 
 
715
  sensible_load = power * use_factor * radiation_factor * clf
716
- latent_load = power * use_factor * (1 - radiation_factor)
717
-
718
- logger.debug(f"Equipment load: power={power}, use_factor={use_factor}, radiation_factor={radiation_factor}, clf={clf}, sensible={sensible_load}, latent={latent_load}")
 
719
  return {
720
  'sensible': max(sensible_load, 0.0),
721
- 'latent': max(latent_load, 0.0),
722
- 'total': max(sensible_load + latent_load, 0.0)
723
  }
724
 
725
  except Exception as e:
726
- logger.error(f"Error in calculate_equipment_cooling_load: {str(e)}")
 
727
  raise Exception(f"Error in calculate_equipment_cooling_load: {str(e)}")
728
 
729
  def calculate_infiltration_cooling_load(
@@ -750,42 +809,35 @@ class CoolingLoadCalculator:
750
  Dictionary with sensible and latent loads in Watts
751
  """
752
  try:
753
- if flow_rate < 0 or building_volume <= 0:
754
- logger.warning(f"Invalid inputs: flow_rate={flow_rate}, building_volume={building_volume}. Returning zero loads")
755
- return {'sensible': 0.0, 'latent': 0.0, 'total': 0.0}
756
-
757
- air_changes_per_hour = (flow_rate * 3600) / building_volume
758
- sensible_load = 1.2 * flow_rate * 1000 * (outdoor_temp - indoor_temp)
759
-
760
- logger.debug(f"Calculating outdoor humidity ratio: temp={outdoor_temp}°C, rh={outdoor_rh}%")
761
- try:
762
- outdoor_rh = min(max(outdoor_rh, 0), 100)
763
- outdoor_w = self.heat_transfer.psychrometrics.humidity_ratio(outdoor_temp, outdoor_rh)
764
- except Exception as e:
765
- logger.error(f"Failed to calculate outdoor humidity ratio: {str(e)}")
766
- logger.warning("Using default humidity ratio=0.0 for outdoor air")
767
- outdoor_w = 0.0
768
-
769
- logger.debug(f"Calculating indoor humidity ratio: temp={indoor_temp}°C, rh={indoor_rh}%")
770
- try:
771
- indoor_rh = min(max(indoor_rh, 0), 100)
772
- indoor_w = self.heat_transfer.psychrometrics.humidity_ratio(indoor_temp, indoor_rh)
773
- except Exception as e:
774
- logger.error(f"Failed to calculate indoor humidity ratio: {str(e)}")
775
- logger.warning("Using default humidity ratio=0.0 for indoor air")
776
- indoor_w = 0.0
777
-
778
- latent_load = 2501 * flow_rate * 1000 * (outdoor_w - indoor_w)
779
-
780
- logger.debug(f"Infiltration load: sensible={sensible_load}, latent={latent_load}")
781
  return {
782
  'sensible': max(sensible_load, 0.0),
783
- 'latent': max(latent_load, 0.0),
784
- 'total': max(sensible_load + latent_load, 0.0)
785
  }
786
 
787
  except Exception as e:
788
- logger.error(f"Error in calculate_infiltration_cooling_load: {str(e)}")
 
789
  raise Exception(f"Error in calculate_infiltration_cooling_load: {str(e)}")
790
 
791
  def calculate_ventilation_cooling_load(
@@ -810,123 +862,26 @@ class CoolingLoadCalculator:
810
  Dictionary with sensible and latent loads in Watts
811
  """
812
  try:
813
- if flow_rate < 0:
814
- logger.warning(f"Invalid flow_rate={flow_rate}. Returning zero loads")
815
- return {'sensible': 0.0, 'latent': 0.0, 'total': 0.0}
816
-
817
- sensible_load = 1.2 * flow_rate * 1000 * (outdoor_temp - indoor_temp)
818
-
819
- logger.debug(f"Calculating outdoor humidity ratio: temp={outdoor_temp}°C, rh={outdoor_rh}%")
820
- try:
821
- outdoor_rh = min(max(outdoor_rh, 0), 100)
822
- outdoor_w = self.heat_transfer.psychrometrics.humidity_ratio(outdoor_temp, outdoor_rh)
823
- except Exception as e:
824
- logger.error(f"Failed to calculate outdoor humidity ratio: {str(e)}")
825
- logger.warning("Using default humidity ratio=0.0 for outdoor air")
826
- outdoor_w = 0.0
827
-
828
- logger.debug(f"Calculating indoor humidity ratio: temp={indoor_temp}°C, rh={indoor_rh}%")
829
- try:
830
- indoor_rh = min(max(indoor_rh, 0), 100)
831
- indoor_w = self.heat_transfer.psychrometrics.humidity_ratio(indoor_temp, indoor_rh)
832
- except Exception as e:
833
- logger.error(f"Failed to calculate indoor humidity ratio: {str(e)}")
834
- logger.warning("Using default humidity ratio=0.0 for indoor air")
835
- indoor_w = 0.0
836
-
837
- latent_load = 2501 * flow_rate * 1000 * (outdoor_w - indoor_w)
838
-
839
- logger.debug(f"Ventilation load: sensible={sensible_load}, latent={latent_load}")
840
  return {
841
  'sensible': max(sensible_load, 0.0),
842
- 'latent': max(latent_load, 0.0),
843
- 'total': max(sensible_load + latent_load, 0.0)
844
  }
845
 
846
  except Exception as e:
847
- logger.error(f"Error in calculate_ventilation_cooling_load: {str(e)}")
848
- raise Exception(f"Error in calculate_ventilation_cooling_load: {str(e)}")
849
-
850
-
851
- if __name__ == "__main__":
852
- calculator = CoolingLoadCalculator()
853
-
854
- building_components = {
855
- 'walls': [Wall(
856
- name="North Wall",
857
- orientation=Orientation.NORTH,
858
- area=20.0,
859
- u_value=0.5,
860
- wall_group="1"
861
- )],
862
- 'roofs': [Roof(
863
- name="Main Roof",
864
- orientation=Orientation.HORIZONTAL,
865
- area=100.0,
866
- u_value=0.3,
867
- roof_group="A"
868
- )],
869
- 'windows': [Window(
870
- name="North Window",
871
- orientation=Orientation.NORTH,
872
- area=10.0,
873
- u_value=2.8,
874
- shgc=0.7,
875
- shading_device="Medium drapery",
876
- shading_coefficient=0.8
877
- )],
878
- 'doors': [Door(
879
- name="Main Door",
880
- orientation=Orientation.NORTH,
881
- area=2.0,
882
- u_value=2.0
883
- )]
884
- }
885
-
886
- outdoor_conditions = {
887
- 'temperature': 35.0,
888
- 'relative_humidity': 50.0,
889
- 'ground_temperature': 20.0,
890
- 'month': 'Jul',
891
- 'latitude': '24N',
892
- 'wind_speed': 4
893
- }
894
-
895
- indoor_conditions = {
896
- 'temperature': 24.0,
897
- 'relative_humidity': 50.0
898
- }
899
-
900
- internal_loads = {
901
- 'people': {'number': 10, 'activity_level': 'Seated/Resting'},
902
- 'lights': {'power': 1000, 'use_factor': 0.8, 'special_allowance': 1.0},
903
- 'equipment': {'power': 500, 'use_factor': 0.7, 'radiation_factor': 0.5},
904
- 'infiltration': {'flow_rate': 0.01},
905
- 'ventilation': {'flow_rate': 0.02}
906
- }
907
-
908
- building_volume = 300.0
909
-
910
- test_cases = [
911
- {'latitude': '24N', 'month': 'Jul', 'wall_group': 'A'},
912
- {'latitude': '36N', 'month': 'Jul', 'wall_group': 'B'},
913
- {'latitude': '48N', 'month': 'Jan', 'wall_group': '1'},
914
- {'latitude': '24N_JUL', 'month': 'Jul', 'wall_group': 'invalid'},
915
- {'latitude': 'invalid', 'month': 'invalid', 'wall_group': '2'}
916
- ]
917
-
918
- for case in test_cases:
919
- outdoor_conditions['latitude'] = case['latitude']
920
- outdoor_conditions['month'] = case['month']
921
- building_components['walls'][0].wall_group = case['wall_group']
922
- try:
923
- hourly_loads = calculator.calculate_hourly_cooling_loads(
924
- building_components,
925
- outdoor_conditions,
926
- indoor_conditions,
927
- internal_loads,
928
- building_volume
929
- )
930
- print(f"Success for latitude={case['latitude']}, month={case['month']}, wall_group={case['wall_group']}: Hourly Loads calculated")
931
- except Exception as e:
932
- print(f"Error for latitude={case['latitude']}, month={case['month']}, wall_group={case['wall_group']}: {str(e)}")
 
3
  Based on ASHRAE steady-state calculation methods.
4
 
5
  Author: Dr Majed Abuseif
6
+ Date: April 2025
7
+ Version: 1.0.1
8
  """
9
 
10
+ import streamlit as st
11
  from typing import Dict, List, Any, Optional, Tuple
12
  import numpy as np
13
  from datetime import datetime
 
54
  parts = latitude.split('_')
55
  if len(parts) > 1:
56
  lat_part = parts[0]
57
+ if st.session_state.get('debug_mode', False):
58
+ logger.warning(f"Detected concatenated input: {latitude}. Using latitude={lat_part}")
59
  latitude = lat_part
60
 
61
  # Handle formats like '31.973N', '1_31.973N'
 
70
  else:
71
  return '48N'
72
  except ValueError:
73
+ if st.session_state.get('debug_mode', False):
74
+ logger.warning(f"Cannot parse latitude: {latitude}. Defaulting to '24N'")
75
  return '24N'
76
 
77
  if latitude not in self.valid_latitudes:
78
+ if st.session_state.get('debug_mode', False):
79
+ logger.warning(f"Invalid latitude: {latitude}. Defaulting to '24N'")
80
  return '24N'
81
 
82
  return latitude
83
 
84
  except Exception as e:
85
+ if st.session_state.get('debug_mode', False):
86
+ logger.error(f"Error validating latitude {latitude}: {str(e)}")
87
  return '24N'
88
 
89
  def validate_month(self, month: Any) -> str:
 
102
 
103
  month_upper = month.strip().upper()
104
  if month_upper not in self.valid_months:
105
+ if st.session_state.get('debug_mode', False):
106
+ logger.warning(f"Invalid month: {month}. Defaulting to 'JUL'")
107
  return 'JUL'
108
  return month_upper
109
 
110
  except Exception as e:
111
+ if st.session_state.get('debug_mode', False):
112
+ logger.error(f"Error validating month {month}: {str(e)}")
113
  return 'JUL'
114
 
115
+ def validate_hour(self, hour: Any) -> int:
116
+ """
117
+ Validate and normalize hour input.
118
+
119
+ Args:
120
+ hour: Hour input (int, float, or other)
121
+
122
+ Returns:
123
+ Valid hour integer (0-23)
124
+ """
125
+ try:
126
+ hour = int(float(str(hour)))
127
+ if not 0 <= hour <= 23:
128
+ if st.session_state.get('debug_mode', False):
129
+ logger.warning(f"Invalid hour: {hour}. Defaulting to 15")
130
+ return 15
131
+ return hour
132
+ except (ValueError, TypeError):
133
+ if st.session_state.get('debug_mode', False):
134
+ logger.warning(f"Invalid hour format: {hour}. Defaulting to 15")
135
+ return 15
136
+
137
  def calculate_hourly_cooling_loads(
138
  self,
139
  building_components: Dict[str, List[Any]],
 
176
  # Validate inputs
177
  latitude = self.validate_latitude(outdoor_conditions.get('latitude', '24N'))
178
  month = self.validate_month(outdoor_conditions.get('month', 'JUL'))
179
+ if st.session_state.get('debug_mode', False):
180
+ logger.debug(f"calculate_hourly_cooling_loads: latitude={latitude}, month={month}, outdoor_conditions={outdoor_conditions}")
181
 
182
  # Calculate loads for walls
183
  for wall in building_components.get('walls', []):
 
286
  return hourly_loads
287
 
288
  except Exception as e:
289
+ if st.session_state.get('debug_mode', False):
290
+ logger.error(f"Error in calculate_hourly_cooling_loads: {str(e)}")
291
  raise Exception(f"Error in calculate_hourly_cooling_loads: {str(e)}")
292
 
293
  def calculate_design_cooling_load(self, hourly_loads: Dict[str, Any]) -> Dict[str, Any]:
 
346
  return design_loads
347
 
348
  except Exception as e:
349
+ if st.session_state.get('debug_mode', False):
350
+ logger.error(f"Error in calculate_design_cooling_load: {str(e)}")
351
  raise Exception(f"Error in calculate_design_cooling_load: {str(e)}")
352
 
353
  def calculate_cooling_load_summary(self, design_loads: Dict[str, Any]) -> Dict[str, float]:
 
390
  }
391
 
392
  except Exception as e:
393
+ if st.session_state.get('debug_mode', False):
394
+ logger.error(f"Error in calculate_cooling_load_summary: {str(e)}")
395
  raise Exception(f"Error in calculate_cooling_load_summary: {str(e)}")
396
 
397
  def calculate_wall_cooling_load(
 
420
  try:
421
  latitude = self.validate_latitude(latitude)
422
  month = self.validate_month(month)
423
+ hour = self.validate_hour(hour)
424
+ if st.session_state.get('debug_mode', False):
425
+ logger.debug(f"calculate_wall_cooling_load: latitude={latitude}, month={month}, hour={hour}, wall_group={wall.wall_group}, orientation={wall.orientation.value}")
426
 
427
  # Validate wall_group
428
  wall_group = str(wall.wall_group).upper()
 
430
  numeric_map = {'1': 'A', '2': 'B', '3': 'C', '4': 'D'}
431
  if wall_group in numeric_map:
432
  wall_group = numeric_map[wall_group]
433
+ if st.session_state.get('debug_mode', False):
434
+ logger.info(f"Mapped wall_group {wall.wall_group} to {wall_group}")
435
  else:
436
+ if st.session_state.get('debug_mode', False):
437
+ logger.warning(f"Invalid wall group: {wall_group}. Defaulting to 'A'")
438
  wall_group = 'A'
439
 
440
  try:
 
449
  outdoor_temp=outdoor_temp
450
  )
451
  except Exception as e:
452
+ if st.session_state.get('debug_mode', False):
453
+ logger.error(f"calculate_corrected_cltd_wall failed for wall_group={wall_group}: {str(e)}")
454
+ logger.warning("Using default CLTD=8.0°C")
455
+ cltd = 8.0
456
 
457
  load = wall.u_value * wall.area * cltd
458
+ if st.session_state.get('debug_mode', False):
459
+ logger.debug(f"Wall load: u_value={wall.u_value}, area={wall.area}, cltd={cltd}, load={load}")
460
  return max(load, 0.0)
461
 
462
  except Exception as e:
463
+ if st.session_state.get('debug_mode', False):
464
+ logger.error(f"Error in calculate_wall_cooling_load: {str(e)}")
465
  raise Exception(f"Error in calculate_wall_cooling_load: {str(e)}")
466
 
467
  def calculate_roof_cooling_load(
 
490
  try:
491
  latitude = self.validate_latitude(latitude)
492
  month = self.validate_month(month)
493
+ hour = self.validate_hour(hour)
494
+ if st.session_state.get('debug_mode', False):
495
+ logger.debug(f"calculate_roof_cooling_load: latitude={latitude}, month={month}, hour={hour}, roof_group={roof.roof_group}")
496
 
497
  roof_group = str(roof.roof_group).upper()
498
  if roof_group not in self.valid_roof_groups:
499
+ if st.session_state.get('debug_mode', False):
500
+ logger.warning(f"Invalid roof group: {roof_group}. Defaulting to 'A'")
501
  roof_group = 'A'
502
 
503
  try:
 
511
  outdoor_temp=outdoor_temp
512
  )
513
  except Exception as e:
514
+ if st.session_state.get('debug_mode', False):
515
+ logger.error(f"calculate_corrected_cltd_roof failed for roof_group={roof_group}: {str(e)}")
516
+ logger.warning("Using default CLTD=8.0°C")
517
+ cltd = 8.0
518
 
519
  load = roof.u_value * roof.area * cltd
520
+ if st.session_state.get('debug_mode', False):
521
+ logger.debug(f"Roof load: u_value={roof.u_value}, area={roof.area}, cltd={cltd}, load={load}")
522
  return max(load, 0.0)
523
 
524
  except Exception as e:
525
+ if st.session_state.get('debug_mode', False):
526
+ logger.error(f"Error in calculate_roof_cooling_load: {str(e)}")
527
  raise Exception(f"Error in calculate_roof_cooling_load: {str(e)}")
528
 
529
  def calculate_window_cooling_load(
 
543
  window: Window component
544
  outdoor_temp: Outdoor temperature (°C)
545
  indoor_temp: Indoor temperature (°C)
546
+ month: Design month
547
  hour: Hour of the day
548
+ latitude: Latitude (e.g., '24N')
549
+ shading_coefficient: Shading coefficient
550
 
551
  Returns:
552
+ Dictionary with conduction, solar, and total loads in Watts
553
  """
554
  try:
555
+ latitude = self.validate_latitude(latitude)
556
+ month = self.validate_month(month)
557
+ hour = self.validate_hour(hour)
558
+ if st.session_state.get('debug_mode', False):
559
+ logger.debug(f"calculate_window_cooling_load: latitude={latitude}, month={month}, hour={hour}, orientation={window.orientation.value}")
560
 
561
  # Conduction load
562
+ cltd = outdoor_temp - indoor_temp # Simplified for windows
563
+ conduction_load = window.u_value * window.area * cltd
564
+
565
  # Solar load
566
+ scl_latitude = f"{latitude}_{month}"
567
+ try:
568
+ scl = self.ashrae_tables.get_scl(
569
+ latitude=scl_latitude,
570
+ month=month,
571
+ orientation=window.orientation.value,
572
+ hour=hour
573
+ )
574
+ except Exception as e:
575
+ if st.session_state.get('debug_mode', False):
576
+ logger.error(f"get_scl failed for latitude={scl_latitude}, month={month}, orientation={window.orientation.value}: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
577
  logger.warning("Using default SCL=100 W/m²")
578
+ scl = 100.0
579
+
580
+ solar_load = window.area * window.shgc * shading_coefficient * scl
581
+
582
+ total_load = conduction_load + solar_load
583
+ if st.session_state.get('debug_mode', False):
584
+ logger.debug(f"Window load: conduction={conduction_load}, solar={solar_load}, total={total_load}")
585
+
586
  return {
587
  'conduction': max(conduction_load, 0.0),
588
  'solar': max(solar_load, 0.0),
589
+ 'total': max(total_load, 0.0)
590
  }
591
 
592
  except Exception as e:
593
+ if st.session_state.get('debug_mode', False):
594
+ logger.error(f"Error in calculate_window_cooling_load: {str(e)}")
595
  raise Exception(f"Error in calculate_window_cooling_load: {str(e)}")
596
 
597
  def calculate_door_cooling_load(
 
612
  Cooling load in Watts
613
  """
614
  try:
615
+ if st.session_state.get('debug_mode', False):
616
+ logger.debug(f"calculate_door_cooling_load: u_value={door.u_value}, area={door.area}")
617
+
618
+ cltd = outdoor_temp - indoor_temp
619
+ load = door.u_value * door.area * cltd
620
+ if st.session_state.get('debug_mode', False):
621
+ logger.debug(f"Door load: cltd={cltd}, load={load}")
622
  return max(load, 0.0)
623
 
624
  except Exception as e:
625
+ if st.session_state.get('debug_mode', False):
626
+ logger.error(f"Error in calculate_door_cooling_load: {str(e)}")
627
  raise Exception(f"Error in calculate_door_cooling_load: {str(e)}")
628
 
629
  def calculate_people_cooling_load(
 
637
 
638
  Args:
639
  num_people: Number of people
640
+ activity_level: Activity level ('Seated/Resting', 'Light Work', etc.)
641
  hour: Hour of the day
642
 
643
  Returns:
644
  Dictionary with sensible and latent loads in Watts
645
  """
646
  try:
647
+ hour = self.validate_hour(hour)
648
+ if st.session_state.get('debug_mode', False):
649
+ logger.debug(f"calculate_people_cooling_load: num_people={num_people}, activity_level={activity_level}, hour={hour}")
650
+
651
+ # Sensible and latent heat gains per person (W)
652
+ heat_gains = {
653
+ 'Seated/Resting': {'sensible': 70, 'latent': 45},
654
+ 'Light Work': {'sensible': 85, 'latent': 65},
655
+ 'Moderate Work': {'sensible': 100, 'latent': 100},
656
+ 'Heavy Work': {'sensible': 145, 'latent': 170}
657
+ }
658
+
659
+ gains = heat_gains.get(activity_level, heat_gains['Seated/Resting'])
660
+ if activity_level not in heat_gains:
661
+ if st.session_state.get('debug_mode', False):
662
+ logger.warning(f"Invalid activity_level: {activity_level}. Defaulting to 'Seated/Resting'")
663
+
 
 
 
664
  try:
665
+ clf = self.ashrae_tables.get_clf_people(
666
+ zone_type='A',
667
+ hours_occupied='8h',
668
+ hour=hour
669
+ )
670
  except Exception as e:
671
+ if st.session_state.get('debug_mode', False):
672
+ logger.error(f"get_clf_people failed: {str(e)}")
673
+ logger.warning("Using default CLF=0.5")
674
+ clf = 0.5
675
+
676
+ sensible_load = num_people * gains['sensible'] * clf
677
+ latent_load = num_people * gains['latent'] # Latent load typically not adjusted by CLF
678
+ if st.session_state.get('debug_mode', False):
679
+ logger.debug(f"People load: sensible={sensible_load}, latent={latent_load}, clf={clf}")
680
+
681
  return {
682
  'sensible': max(sensible_load, 0.0),
683
+ 'latent': max(latent_load, 0.0)
 
684
  }
685
 
686
  except Exception as e:
687
+ if st.session_state.get('debug_mode', False):
688
+ logger.error(f"Error in calculate_people_cooling_load: {str(e)}")
689
  raise Exception(f"Error in calculate_people_cooling_load: {str(e)}")
690
 
691
  def calculate_lights_cooling_load(
 
699
  Calculate cooling load from lighting.
700
 
701
  Args:
702
+ power: Total lighting power (W)
703
+ use_factor: Usage factor (0.0 to 1.0)
704
  special_allowance: Special allowance factor
705
  hour: Hour of the day
706
 
 
708
  Cooling load in Watts
709
  """
710
  try:
711
+ hour = self.validate_hour(hour)
712
+ if st.session_state.get('debug_mode', False):
713
+ logger.debug(f"calculate_lights_cooling_load: power={power}, use_factor={use_factor}, special_allowance={special_allowance}, hour={hour}")
714
+
715
  try:
716
+ clf = self.ashrae_tables.get_clf_lights(
717
+ zone_type='A',
718
+ hours_occupied='8h',
719
+ hour=hour
720
+ )
721
  except Exception as e:
722
+ if st.session_state.get('debug_mode', False):
723
+ logger.error(f"get_clf_lights failed: {str(e)}")
724
+ logger.warning("Using default CLF=0.8")
725
+ clf = 0.8
726
+
727
  load = power * use_factor * special_allowance * clf
728
+ if st.session_state.get('debug_mode', False):
729
+ logger.debug(f"Lights load: clf={clf}, load={load}")
730
  return max(load, 0.0)
731
 
732
  except Exception as e:
733
+ if st.session_state.get('debug_mode', False):
734
+ logger.error(f"Error in calculate_lights_cooling_load: {str(e)}")
735
  raise Exception(f"Error in calculate_lights_cooling_load: {str(e)}")
736
 
737
  def calculate_equipment_cooling_load(
 
745
  Calculate cooling load from equipment.
746
 
747
  Args:
748
+ power: Total equipment power (W)
749
+ use_factor: Usage factor (0.0 to 1.0)
750
+ radiation_factor: Radiation factor (0.0 to 1.0)
751
  hour: Hour of the day
752
 
753
  Returns:
754
  Dictionary with sensible and latent loads in Watts
755
  """
756
  try:
757
+ hour = self.validate_hour(hour)
758
+ if st.session_state.get('debug_mode', False):
759
+ logger.debug(f"calculate_equipment_cooling_load: power={power}, use_factor={use_factor}, radiation_factor={radiation_factor}, hour={hour}")
760
+
761
  try:
762
+ clf = self.ashrae_tables.get_clf_equipment(
763
+ zone_type='A',
764
+ hours_occupied='8h',
765
+ hour=hour
766
+ )
767
  except Exception as e:
768
+ if st.session_state.get('debug_mode', False):
769
+ logger.error(f"get_clf_equipment failed: {str(e)}")
770
+ logger.warning("Using default CLF=0.7")
771
+ clf = 0.7
772
+
773
  sensible_load = power * use_factor * radiation_factor * clf
774
+ latent_load = power * use_factor * (1 - radiation_factor) # Assume non-radiative is latent
775
+ if st.session_state.get('debug_mode', False):
776
+ logger.debug(f"Equipment load: sensible={sensible_load}, latent={latent_load}, clf={clf}")
777
+
778
  return {
779
  'sensible': max(sensible_load, 0.0),
780
+ 'latent': max(latent_load, 0.0)
 
781
  }
782
 
783
  except Exception as e:
784
+ if st.session_state.get('debug_mode', False):
785
+ logger.error(f"Error in calculate_equipment_cooling_load: {str(e)}")
786
  raise Exception(f"Error in calculate_equipment_cooling_load: {str(e)}")
787
 
788
  def calculate_infiltration_cooling_load(
 
809
  Dictionary with sensible and latent loads in Watts
810
  """
811
  try:
812
+ if st.session_state.get('debug_mode', False):
813
+ logger.debug(f"calculate_infiltration_cooling_load: flow_rate={flow_rate}, building_volume={building_volume}, outdoor_temp={outdoor_temp}, indoor_temp={indoor_temp}")
814
+
815
+ # Calculate air changes per hour (ACH)
816
+ ach = (flow_rate * 3600) / building_volume if building_volume > 0 else 0.5
817
+ if ach < 0:
818
+ if st.session_state.get('debug_mode', False):
819
+ logger.warning(f"Invalid ACH: {ach}. Defaulting to 0.5")
820
+ ach = 0.5
821
+
822
+ # Sensible load: Q = 1.2 * flow_rate * (Tout - Tin)
823
+ sensible_load = 1.2 * flow_rate * (outdoor_temp - indoor_temp)
824
+
825
+ # Latent load using simplified psychrometrics
826
+ outdoor_w = self.heat_transfer.calculate_humidity_ratio(outdoor_temp, outdoor_rh)
827
+ indoor_w = self.heat_transfer.calculate_humidity_ratio(indoor_temp, indoor_rh)
828
+ latent_load = 1.2 * flow_rate * 2500 * (outdoor_w - indoor_w) # 2500 kJ/kg for latent heat
829
+
830
+ if st.session_state.get('debug_mode', False):
831
+ logger.debug(f"Infiltration load: sensible={sensible_load}, latent={latent_load}, ach={ach}")
832
+
 
 
 
 
 
 
 
833
  return {
834
  'sensible': max(sensible_load, 0.0),
835
+ 'latent': max(latent_load, 0.0)
 
836
  }
837
 
838
  except Exception as e:
839
+ if st.session_state.get('debug_mode', False):
840
+ logger.error(f"Error in calculate_infiltration_cooling_load: {str(e)}")
841
  raise Exception(f"Error in calculate_infiltration_cooling_load: {str(e)}")
842
 
843
  def calculate_ventilation_cooling_load(
 
862
  Dictionary with sensible and latent loads in Watts
863
  """
864
  try:
865
+ if st.session_state.get('debug_mode', False):
866
+ logger.debug(f"calculate_ventilation_cooling_load: flow_rate={flow_rate}, outdoor_temp={outdoor_temp}, indoor_temp={indoor_temp}")
867
+
868
+ # Sensible load: Q = 1.2 * flow_rate * (Tout - Tin)
869
+ sensible_load = 1.2 * flow_rate * (outdoor_temp - indoor_temp)
870
+
871
+ # Latent load using simplified psychrometrics
872
+ outdoor_w = self.heat_transfer.calculate_humidity_ratio(outdoor_temp, outdoor_rh)
873
+ indoor_w = self.heat_transfer.calculate_humidity_ratio(indoor_temp, indoor_rh)
874
+ latent_load = 1.2 * flow_rate * 2500 * (outdoor_w - indoor_w) # 2500 kJ/kg for latent heat
875
+
876
+ if st.session_state.get('debug_mode', False):
877
+ logger.debug(f"Ventilation load: sensible={sensible_load}, latent={latent_load}")
878
+
 
 
 
 
 
 
 
 
 
 
 
 
 
879
  return {
880
  'sensible': max(sensible_load, 0.0),
881
+ 'latent': max(latent_load, 0.0)
 
882
  }
883
 
884
  except Exception as e:
885
+ if st.session_state.get('debug_mode', False):
886
+ logger.error(f"Error in calculate_ventilation_cooling_load: {str(e)}")
887
+ raise Exception(f"Error in calculate_ventilation_cooling_load: {str(e)}")