mabuseif commited on
Commit
02ba5be
·
verified ·
1 Parent(s): ff66a9b

Upload heating_load.py

Browse files
Files changed (1) hide show
  1. utils/heating_load.py +155 -23
utils/heating_load.py CHANGED
@@ -1,7 +1,15 @@
1
  """
2
- Heating load calculation module for HVAC Load Calculator.
3
  Implements ASHRAE steady-state methods with simplified thermal lag for compatibility.
4
  Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Section 18.4.
 
 
 
 
 
 
 
 
5
  """
6
 
7
  from typing import Dict, List, Any, Optional, Tuple
@@ -20,7 +28,7 @@ from utils.psychrometrics import Psychrometrics
20
  from utils.heat_transfer import HeatTransferCalculations
21
 
22
  # Import data modules
23
- from data.building_components import Wall, Roof, Floor, Window, Door, Orientation, ComponentType
24
 
25
 
26
  class HeatingLoadCalculator:
@@ -91,6 +99,11 @@ class HeatingLoadCalculator:
91
  adjusted_delta_t = delta_t * lag_factor
92
 
93
  load = self.heat_transfer.conduction_heat_transfer(wall.u_value, wall.area, adjusted_delta_t)
 
 
 
 
 
94
  return max(0, load)
95
 
96
  def calculate_roof_heating_load(self, roof: Roof, outdoor_temp: float, indoor_temp: float) -> float:
@@ -114,6 +127,11 @@ class HeatingLoadCalculator:
114
  adjusted_delta_t = delta_t * lag_factor
115
 
116
  load = self.heat_transfer.conduction_heat_transfer(roof.u_value, roof.area, adjusted_delta_t)
 
 
 
 
 
117
  return max(0, load)
118
 
119
  def calculate_floor_heating_load(self, floor: Floor, ground_temp: float, indoor_temp: float) -> float:
@@ -133,10 +151,22 @@ class HeatingLoadCalculator:
133
  if delta_t <= 1:
134
  return 0.0
135
 
136
- if floor.is_ground_contact:
 
 
 
137
  # Dynamic F-factor based on insulation
138
- f_factor = 0.3 if floor.insulated else 0.73 # W/m·K
139
- load = f_factor * floor.perimeter_length * delta_t
 
 
 
 
 
 
 
 
 
140
  else:
141
  load = self.heat_transfer.conduction_heat_transfer(floor.u_value, floor.area, delta_t)
142
 
@@ -162,7 +192,32 @@ class HeatingLoadCalculator:
162
  if delta_t <= 1:
163
  return 0.0
164
 
165
- load = self.heat_transfer.conduction_heat_transfer(window.u_value, window.area, delta_t)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  return max(0, load)
167
 
168
  def calculate_door_heating_load(self, door: Door, outdoor_temp: float, indoor_temp: float) -> float:
@@ -182,7 +237,48 @@ class HeatingLoadCalculator:
182
  if delta_t <= 1:
183
  return 0.0
184
 
185
- load = self.heat_transfer.conduction_heat_transfer(door.u_value, door.area, delta_t)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  return max(0, load)
187
 
188
  def calculate_infiltration_heating_load(self, indoor_conditions: Dict[str, float],
@@ -217,9 +313,14 @@ class HeatingLoadCalculator:
217
  )
218
  total_pd = self.heat_transfer.combined_pressure_difference(wind_pd, stack_pd)
219
 
220
- # Calculate infiltration flow rate
221
- crack_length = infiltration.get('crack_length', 20.0)
222
- flow_rate = self.heat_transfer.crack_method_infiltration(crack_length, 0.0002, total_pd)
 
 
 
 
 
223
 
224
  # Calculate humidity ratio difference
225
  w_indoor = self.psychrometrics.humidity_ratio(
@@ -307,16 +408,19 @@ class HeatingLoadCalculator:
307
 
308
  return max(0, sensible_load), max(0, latent_load)
309
 
310
- def calculate_internal_gains(self, internal_loads: Dict[str, Any]) -> float:
 
311
  """
312
  Calculate internal heat gains from people, lighting, and equipment.
313
  Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Section 18.4.4.
314
 
315
  Args:
316
  internal_loads: Internal loads (people, lights, equipment)
 
 
317
 
318
  Returns:
319
- Total internal gains in W
320
  """
321
  total_gains = 0.0
322
 
@@ -336,13 +440,15 @@ class HeatingLoadCalculator:
336
  if equipment.get('power', 0) > 0:
337
  total_gains += equipment['power'] * equipment.get('use_factor', 0.7)
338
 
339
- return max(0, total_gains)
 
340
 
341
  def calculate_design_heating_load(self, building_components: Dict[str, List[Any]],
342
  outdoor_conditions: Dict[str, float],
343
  indoor_conditions: Dict[str, float],
344
  internal_loads: Dict[str, Any],
345
- p_atm: float = 101325) -> Dict[str, float]:
 
346
  """
347
  Calculate design heating loads for all components.
348
  Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Section 18.4.
@@ -353,6 +459,7 @@ class HeatingLoadCalculator:
353
  indoor_conditions: Indoor conditions (temperature, relative_humidity)
354
  internal_loads: Internal loads (people, lights, equipment, infiltration, ventilation)
355
  p_atm: Atmospheric pressure in Pa (default: 101325 Pa)
 
356
 
357
  Returns:
358
  Dictionary of design loads in W
@@ -368,6 +475,7 @@ class HeatingLoadCalculator:
368
  'floors': 0.0,
369
  'windows': 0.0,
370
  'doors': 0.0,
 
371
  'infiltration_sensible': 0.0,
372
  'infiltration_latent': 0.0,
373
  'ventilation_sensible': 0.0,
@@ -391,6 +499,10 @@ class HeatingLoadCalculator:
391
  for door in building_components.get('doors', []):
392
  loads['doors'] += self.calculate_door_heating_load(door, outdoor_conditions['design_temperature'], indoor_conditions['temperature'])
393
 
 
 
 
 
394
  # Calculate infiltration and ventilation loads
395
  building_height = internal_loads.get('infiltration', {}).get('height', 3.0)
396
  infiltration_sensible, infiltration_latent = self.calculate_infiltration_heating_load(
@@ -405,8 +517,10 @@ class HeatingLoadCalculator:
405
  loads['ventilation_sensible'] = ventilation_sensible
406
  loads['ventilation_latent'] = ventilation_latent
407
 
408
- # Calculate internal gains (negative for heating)
409
- loads['internal_gains'] = -self.calculate_internal_gains(internal_loads)
 
 
410
 
411
  return loads
412
 
@@ -421,17 +535,26 @@ class HeatingLoadCalculator:
421
  Returns:
422
  Summary dictionary with total, subtotal, and safety factor
423
  """
 
424
  subtotal = sum(
425
  load for key, load in design_loads.items()
426
  if key not in ['internal_gains'] and load > 0
427
  )
428
  internal_gains = design_loads.get('internal_gains', 0)
429
 
430
- total = max(0, subtotal + internal_gains) * self.safety_factor
 
 
 
 
 
 
 
431
 
432
  return {
433
  'subtotal': subtotal,
434
  'internal_gains': internal_gains,
 
435
  'total': total,
436
  'safety_factor': self.safety_factor
437
  }
@@ -502,7 +625,8 @@ class HeatingLoadCalculator:
502
  indoor_conditions: Dict[str, float],
503
  internal_loads: Dict[str, Any],
504
  monthly_temps: Dict[str, float],
505
- p_atm: float = 101325) -> Dict[str, float]:
 
506
  """
507
  Calculate monthly heating loads.
508
  Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Section 18.4.
@@ -514,6 +638,7 @@ class HeatingLoadCalculator:
514
  internal_loads: Internal loads
515
  monthly_temps: Dictionary of monthly average temperatures
516
  p_atm: Atmospheric pressure in Pa (default: 101325 Pa)
 
517
 
518
  Returns:
519
  Dictionary of monthly heating loads in kW
@@ -531,7 +656,8 @@ class HeatingLoadCalculator:
531
 
532
  try:
533
  design_loads = self.calculate_design_heating_load(
534
- building_components, modified_outdoor, indoor_conditions, internal_loads, p_atm
 
535
  )
536
  summary = self.calculate_heating_load_summary(design_loads)
537
  monthly_loads[month] = summary['total'] / 1000 # kW
@@ -552,7 +678,8 @@ if __name__ == "__main__":
552
  is_ground_contact=True, insulated=True, ground_temperature_c=10.0)],
553
  'windows': [Window(id="win1", name="South Window", area=10.0, u_value=2.8, orientation=Orientation.SOUTH,
554
  shgc=0.7, shading_coefficient=0.8)],
555
- 'doors': [Door(id="d1", name="Main Door", area=2.0, u_value=2.0, orientation=Orientation.NORTH)]
 
556
  }
557
 
558
  outdoor_conditions = {
@@ -569,7 +696,7 @@ if __name__ == "__main__":
569
  'people': {'number': 10, 'sensible_gain': 70.0, 'operating_hours': '8:00-18:00'},
570
  'lights': {'power': 1000.0, 'use_factor': 0.8, 'hours_operation': '8h'},
571
  'equipment': {'power': 500.0, 'use_factor': 0.7, 'hours_operation': '8h'},
572
- 'infiltration': {'flow_rate': 0.05, 'height': 3.0, 'crack_length': 20.0},
573
  'ventilation': {'flow_rate': 0.1},
574
  'operating_hours': '8:00-18:00'
575
  }
@@ -579,7 +706,10 @@ if __name__ == "__main__":
579
  }
580
 
581
  # Calculate design loads
582
- design_loads = calculator.calculate_design_heating_load(components, outdoor_conditions, indoor_conditions, internal_loads)
 
 
 
583
  summary = calculator.calculate_heating_load_summary(design_loads)
584
 
585
  # Log results
@@ -589,6 +719,7 @@ if __name__ == "__main__":
589
  logger.info(f"Floor Load: {design_loads['floors']:.2f} W")
590
  logger.info(f"Window Load: {design_loads['windows']:.2f} W")
591
  logger.info(f"Door Load: {design_loads['doors']:.2f} W")
 
592
  logger.info(f"Infiltration Sensible Load: {design_loads['infiltration_sensible']:.2f} W")
593
  logger.info(f"Infiltration Latent Load: {design_loads['infiltration_latent']:.2f} W")
594
  logger.info(f"Ventilation Sensible Load: {design_loads['ventilation_sensible']:.2f} W")
@@ -603,7 +734,8 @@ if __name__ == "__main__":
603
 
604
  # Calculate monthly loads
605
  monthly_loads = calculator.calculate_monthly_heating_loads(
606
- components, outdoor_conditions, indoor_conditions, internal_loads, monthly_temps
 
607
  )
608
  logger.info("Monthly Heating Loads (kW):")
609
  for month, load in monthly_loads.items():
 
1
  """
2
+ Enhanced heating load calculation module for HVAC Load Calculator.
3
  Implements ASHRAE steady-state methods with simplified thermal lag for compatibility.
4
  Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Section 18.4.
5
+
6
+ ENHANCEMENTS:
7
+ - Added support for equipment and internal loads as heating sources
8
+ - Added support for custom U-values for materials
9
+ - Improved integration with enhanced component selection features (crack dimensions, drapery inputs)
10
+ - Ensured compatibility with winter design parameters from enhanced climate data
11
+ - Added support for skylights in heating load calculations
12
+ - Improved infiltration calculations using crack dimensions
13
  """
14
 
15
  from typing import Dict, List, Any, Optional, Tuple
 
28
  from utils.heat_transfer import HeatTransferCalculations
29
 
30
  # Import data modules
31
+ from app.component_selection import Wall, Roof, Floor, Window, Door, Orientation, ComponentType, Skylight
32
 
33
 
34
  class HeatingLoadCalculator:
 
99
  adjusted_delta_t = delta_t * lag_factor
100
 
101
  load = self.heat_transfer.conduction_heat_transfer(wall.u_value, wall.area, adjusted_delta_t)
102
+
103
+ # ENHANCEMENT: Log detailed information in debug mode
104
+ if self.debug_mode:
105
+ logger.debug(f"Wall {wall.name} heating load: u_value={wall.u_value}, area={wall.area}, delta_t={adjusted_delta_t}, load={load:.2f} W")
106
+
107
  return max(0, load)
108
 
109
  def calculate_roof_heating_load(self, roof: Roof, outdoor_temp: float, indoor_temp: float) -> float:
 
127
  adjusted_delta_t = delta_t * lag_factor
128
 
129
  load = self.heat_transfer.conduction_heat_transfer(roof.u_value, roof.area, adjusted_delta_t)
130
+
131
+ # ENHANCEMENT: Log detailed information in debug mode
132
+ if self.debug_mode:
133
+ logger.debug(f"Roof {roof.name} heating load: u_value={roof.u_value}, area={roof.area}, delta_t={adjusted_delta_t}, load={load:.2f} W")
134
+
135
  return max(0, load)
136
 
137
  def calculate_floor_heating_load(self, floor: Floor, ground_temp: float, indoor_temp: float) -> float:
 
151
  if delta_t <= 1:
152
  return 0.0
153
 
154
+ # ENHANCEMENT: Check if floor has ground_contact attribute
155
+ is_ground_contact = floor.ground_contact if hasattr(floor, 'ground_contact') else False
156
+
157
+ if is_ground_contact:
158
  # Dynamic F-factor based on insulation
159
+ # ENHANCEMENT: Check if floor has insulated attribute
160
+ is_insulated = floor.insulated if hasattr(floor, 'insulated') else False
161
+ f_factor = 0.3 if is_insulated else 0.73 # W/m·K
162
+
163
+ # ENHANCEMENT: Check if floor has perimeter attribute
164
+ perimeter = floor.perimeter if hasattr(floor, 'perimeter') else (
165
+ floor.perimeter_length if hasattr(floor, 'perimeter_length') else
166
+ math.sqrt(4 * floor.area) # Estimate perimeter if not provided
167
+ )
168
+
169
+ load = f_factor * perimeter * delta_t
170
  else:
171
  load = self.heat_transfer.conduction_heat_transfer(floor.u_value, floor.area, delta_t)
172
 
 
192
  if delta_t <= 1:
193
  return 0.0
194
 
195
+ # ENHANCEMENT: Adjust U-value if window has drapery
196
+ adjusted_u_value = window.u_value
197
+ if hasattr(window, 'has_drapery') and window.has_drapery:
198
+ # Simple U-value adjustment for drapery (typically reduces U-value by 10-20%)
199
+ drapery_reduction = 0.15 # 15% reduction as a default
200
+
201
+ # More detailed adjustment if drapery properties are available
202
+ if hasattr(window, 'drapery_type'):
203
+ drapery_reductions = {
204
+ 'None': 0.0,
205
+ 'Sheer': 0.05,
206
+ 'Light': 0.10,
207
+ 'Medium': 0.15,
208
+ 'Heavy': 0.20,
209
+ 'Blackout': 0.25
210
+ }
211
+ drapery_reduction = drapery_reductions.get(window.drapery_type, 0.15)
212
+
213
+ adjusted_u_value = window.u_value * (1 - drapery_reduction)
214
+
215
+ load = self.heat_transfer.conduction_heat_transfer(adjusted_u_value, window.area, delta_t)
216
+
217
+ # ENHANCEMENT: Log detailed information in debug mode
218
+ if self.debug_mode:
219
+ logger.debug(f"Window {window.name} heating load: u_value={adjusted_u_value}, area={window.area}, delta_t={delta_t}, load={load:.2f} W")
220
+
221
  return max(0, load)
222
 
223
  def calculate_door_heating_load(self, door: Door, outdoor_temp: float, indoor_temp: float) -> float:
 
237
  if delta_t <= 1:
238
  return 0.0
239
 
240
+ # ENHANCEMENT: Adjust U-value if door has weatherstripping
241
+ adjusted_u_value = door.u_value
242
+ if hasattr(door, 'has_weatherstripping') and door.has_weatherstripping:
243
+ # Simple U-value adjustment for weatherstripping (typically reduces U-value by 5-10%)
244
+ adjusted_u_value = door.u_value * 0.95 # 5% reduction
245
+
246
+ load = self.heat_transfer.conduction_heat_transfer(adjusted_u_value, door.area, delta_t)
247
+
248
+ # ENHANCEMENT: Log detailed information in debug mode
249
+ if self.debug_mode:
250
+ logger.debug(f"Door {wall.name} heating load: u_value={adjusted_u_value}, area={door.area}, delta_t={delta_t}, load={load:.2f} W")
251
+
252
+ return max(0, load)
253
+
254
+ # ENHANCEMENT: Added skylight heating load calculation
255
+ def calculate_skylight_heating_load(self, skylight: Skylight, outdoor_temp: float, indoor_temp: float) -> float:
256
+ """
257
+ Calculate heating load for a skylight.
258
+ Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Equation 18.1.
259
+
260
+ Args:
261
+ skylight: Skylight component
262
+ outdoor_temp: Outdoor temperature in °C
263
+ indoor_temp: Indoor temperature in °C
264
+
265
+ Returns:
266
+ Heating load in W
267
+ """
268
+ delta_t = indoor_temp - outdoor_temp
269
+ if delta_t <= 1:
270
+ return 0.0
271
+
272
+ # Skylights typically have higher U-values due to their horizontal orientation
273
+ # Apply a 10% increase to account for this
274
+ adjusted_u_value = skylight.u_value * 1.1
275
+
276
+ load = self.heat_transfer.conduction_heat_transfer(adjusted_u_value, skylight.area, delta_t)
277
+
278
+ # Log detailed information in debug mode
279
+ if self.debug_mode:
280
+ logger.debug(f"Skylight {skylight.name} heating load: u_value={adjusted_u_value}, area={skylight.area}, delta_t={delta_t}, load={load:.2f} W")
281
+
282
  return max(0, load)
283
 
284
  def calculate_infiltration_heating_load(self, indoor_conditions: Dict[str, float],
 
313
  )
314
  total_pd = self.heat_transfer.combined_pressure_difference(wind_pd, stack_pd)
315
 
316
+ # ENHANCEMENT: Use crack dimensions if available for more accurate infiltration calculation
317
+ if 'crack_length' in infiltration and 'crack_width' in infiltration:
318
+ crack_length = infiltration['crack_length']
319
+ crack_width = infiltration['crack_width'] / 1000.0 # Convert from mm to m
320
+ flow_rate = self.heat_transfer.crack_method_infiltration(crack_length, crack_width, total_pd)
321
+ else:
322
+ # Use provided flow rate or default
323
+ flow_rate = infiltration.get('flow_rate', 0.05) # m³/s
324
 
325
  # Calculate humidity ratio difference
326
  w_indoor = self.psychrometrics.humidity_ratio(
 
408
 
409
  return max(0, sensible_load), max(0, latent_load)
410
 
411
+ # ENHANCEMENT: Modified to include equipment and internal loads as heating sources
412
+ def calculate_internal_gains(self, internal_loads: Dict[str, Any], include_as_heating_source: bool = True) -> float:
413
  """
414
  Calculate internal heat gains from people, lighting, and equipment.
415
  Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Section 18.4.4.
416
 
417
  Args:
418
  internal_loads: Internal loads (people, lights, equipment)
419
+ include_as_heating_source: Whether to include internal gains as heating sources (positive value)
420
+ or as load reductions (negative value)
421
 
422
  Returns:
423
+ Total internal gains in W (positive if heating source, negative if load reduction)
424
  """
425
  total_gains = 0.0
426
 
 
440
  if equipment.get('power', 0) > 0:
441
  total_gains += equipment['power'] * equipment.get('use_factor', 0.7)
442
 
443
+ # ENHANCEMENT: Return positive value if including as heating source, negative if reducing load
444
+ return total_gains if include_as_heating_source else -total_gains
445
 
446
  def calculate_design_heating_load(self, building_components: Dict[str, List[Any]],
447
  outdoor_conditions: Dict[str, float],
448
  indoor_conditions: Dict[str, float],
449
  internal_loads: Dict[str, Any],
450
+ p_atm: float = 101325,
451
+ include_internal_gains_as_heating_source: bool = False) -> Dict[str, float]:
452
  """
453
  Calculate design heating loads for all components.
454
  Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Section 18.4.
 
459
  indoor_conditions: Indoor conditions (temperature, relative_humidity)
460
  internal_loads: Internal loads (people, lights, equipment, infiltration, ventilation)
461
  p_atm: Atmospheric pressure in Pa (default: 101325 Pa)
462
+ include_internal_gains_as_heating_source: Whether to include internal gains as heating sources
463
 
464
  Returns:
465
  Dictionary of design loads in W
 
475
  'floors': 0.0,
476
  'windows': 0.0,
477
  'doors': 0.0,
478
+ 'skylights': 0.0, # ENHANCEMENT: Added skylights
479
  'infiltration_sensible': 0.0,
480
  'infiltration_latent': 0.0,
481
  'ventilation_sensible': 0.0,
 
499
  for door in building_components.get('doors', []):
500
  loads['doors'] += self.calculate_door_heating_load(door, outdoor_conditions['design_temperature'], indoor_conditions['temperature'])
501
 
502
+ # ENHANCEMENT: Added skylights
503
+ for skylight in building_components.get('skylights', []):
504
+ loads['skylights'] += self.calculate_skylight_heating_load(skylight, outdoor_conditions['design_temperature'], indoor_conditions['temperature'])
505
+
506
  # Calculate infiltration and ventilation loads
507
  building_height = internal_loads.get('infiltration', {}).get('height', 3.0)
508
  infiltration_sensible, infiltration_latent = self.calculate_infiltration_heating_load(
 
517
  loads['ventilation_sensible'] = ventilation_sensible
518
  loads['ventilation_latent'] = ventilation_latent
519
 
520
+ # ENHANCEMENT: Calculate internal gains (positive or negative based on parameter)
521
+ loads['internal_gains'] = self.calculate_internal_gains(
522
+ internal_loads, include_as_heating_source=include_internal_gains_as_heating_source
523
+ )
524
 
525
  return loads
526
 
 
535
  Returns:
536
  Summary dictionary with total, subtotal, and safety factor
537
  """
538
+ # ENHANCEMENT: Include skylights in subtotal
539
  subtotal = sum(
540
  load for key, load in design_loads.items()
541
  if key not in ['internal_gains'] and load > 0
542
  )
543
  internal_gains = design_loads.get('internal_gains', 0)
544
 
545
+ # ENHANCEMENT: If internal gains are positive (heating source), add them to subtotal
546
+ # If negative (load reduction), subtract them from subtotal
547
+ if internal_gains >= 0:
548
+ adjusted_subtotal = subtotal + internal_gains
549
+ else:
550
+ adjusted_subtotal = subtotal + internal_gains # internal_gains is negative, so this is subtraction
551
+
552
+ total = max(0, adjusted_subtotal) * self.safety_factor
553
 
554
  return {
555
  'subtotal': subtotal,
556
  'internal_gains': internal_gains,
557
+ 'adjusted_subtotal': adjusted_subtotal,
558
  'total': total,
559
  'safety_factor': self.safety_factor
560
  }
 
625
  indoor_conditions: Dict[str, float],
626
  internal_loads: Dict[str, Any],
627
  monthly_temps: Dict[str, float],
628
+ p_atm: float = 101325,
629
+ include_internal_gains_as_heating_source: bool = False) -> Dict[str, float]:
630
  """
631
  Calculate monthly heating loads.
632
  Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 18, Section 18.4.
 
638
  internal_loads: Internal loads
639
  monthly_temps: Dictionary of monthly average temperatures
640
  p_atm: Atmospheric pressure in Pa (default: 101325 Pa)
641
+ include_internal_gains_as_heating_source: Whether to include internal gains as heating sources
642
 
643
  Returns:
644
  Dictionary of monthly heating loads in kW
 
656
 
657
  try:
658
  design_loads = self.calculate_design_heating_load(
659
+ building_components, modified_outdoor, indoor_conditions, internal_loads, p_atm,
660
+ include_internal_gains_as_heating_source
661
  )
662
  summary = self.calculate_heating_load_summary(design_loads)
663
  monthly_loads[month] = summary['total'] / 1000 # kW
 
678
  is_ground_contact=True, insulated=True, ground_temperature_c=10.0)],
679
  'windows': [Window(id="win1", name="South Window", area=10.0, u_value=2.8, orientation=Orientation.SOUTH,
680
  shgc=0.7, shading_coefficient=0.8)],
681
+ 'doors': [Door(id="d1", name="Main Door", area=2.0, u_value=2.0, orientation=Orientation.NORTH)],
682
+ 'skylights': [Skylight(id="s1", name="Main Skylight", area=5.0, u_value=3.0, orientation=Orientation.HORIZONTAL)]
683
  }
684
 
685
  outdoor_conditions = {
 
696
  'people': {'number': 10, 'sensible_gain': 70.0, 'operating_hours': '8:00-18:00'},
697
  'lights': {'power': 1000.0, 'use_factor': 0.8, 'hours_operation': '8h'},
698
  'equipment': {'power': 500.0, 'use_factor': 0.7, 'hours_operation': '8h'},
699
+ 'infiltration': {'flow_rate': 0.05, 'height': 3.0, 'crack_length': 20.0, 'crack_width': 2.0},
700
  'ventilation': {'flow_rate': 0.1},
701
  'operating_hours': '8:00-18:00'
702
  }
 
706
  }
707
 
708
  # Calculate design loads
709
+ design_loads = calculator.calculate_design_heating_load(
710
+ components, outdoor_conditions, indoor_conditions, internal_loads,
711
+ include_internal_gains_as_heating_source=True # ENHANCEMENT: Include internal gains as heating sources
712
+ )
713
  summary = calculator.calculate_heating_load_summary(design_loads)
714
 
715
  # Log results
 
719
  logger.info(f"Floor Load: {design_loads['floors']:.2f} W")
720
  logger.info(f"Window Load: {design_loads['windows']:.2f} W")
721
  logger.info(f"Door Load: {design_loads['doors']:.2f} W")
722
+ logger.info(f"Skylight Load: {design_loads['skylights']:.2f} W") # ENHANCEMENT: Added skylight load
723
  logger.info(f"Infiltration Sensible Load: {design_loads['infiltration_sensible']:.2f} W")
724
  logger.info(f"Infiltration Latent Load: {design_loads['infiltration_latent']:.2f} W")
725
  logger.info(f"Ventilation Sensible Load: {design_loads['ventilation_sensible']:.2f} W")
 
734
 
735
  # Calculate monthly loads
736
  monthly_loads = calculator.calculate_monthly_heating_loads(
737
+ components, outdoor_conditions, indoor_conditions, internal_loads, monthly_temps,
738
+ include_internal_gains_as_heating_source=True # ENHANCEMENT: Include internal gains as heating sources
739
  )
740
  logger.info("Monthly Heating Loads (kW):")
741
  for month, load in monthly_loads.items():