mabuseif commited on
Commit
39b5dca
·
verified ·
1 Parent(s): 32bccf6

Update utils/cooling_load.py

Browse files
Files changed (1) hide show
  1. utils/cooling_load.py +17 -673
utils/cooling_load.py CHANGED
@@ -4,675 +4,7 @@ Based on ASHRAE steady-state calculation methods.
4
 
5
  Author: Dr Majed Abuseif
6
  Date: April 2025
7
- Version: 1.0.2
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
14
- import logging
15
- from data.ashrae_tables import ASHRAETables
16
- from utils.heat_transfer import HeatTransferCalculations
17
- from app.component_selection import Wall, Roof, Window, Door, Orientation
18
-
19
- # Set up logging
20
- logging.basicConfig(level=logging.DEBUG)
21
- logger = logging.getLogger(__name__)
22
-
23
- class CoolingLoadCalculator:
24
- """Class for cooling load calculations."""
25
-
26
- def __init__(self):
27
- """Initialize cooling load calculator."""
28
- self.ashrae_tables = ASHRAETables()
29
- self.heat_transfer = HeatTransferCalculations()
30
- self.hours = list(range(24))
31
- self.valid_latitudes = ['24N', '36N', '48N']
32
- self.valid_months = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']
33
- self.valid_wall_groups = ['A', 'B', 'C', 'D']
34
- self.valid_roof_groups = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
35
-
36
- def validate_latitude(self, latitude: Any) -> str:
37
- """
38
- Validate and normalize latitude input.
39
-
40
- Args:
41
- latitude: Latitude input (str, float, or other)
42
-
43
- Returns:
44
- Valid latitude string ('24N', '36N', or '48N')
45
- """
46
- try:
47
- if not isinstance(latitude, str):
48
- latitude = str(latitude)
49
-
50
- latitude = latitude.strip().upper()
51
-
52
- # Handle concatenated formats like '24N_JUL'
53
- if '_' in latitude:
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'
62
- if '.' in latitude or any(c.isdigit() for c in latitude):
63
- num_part = ''.join(c for c in latitude if c.isdigit() or c == '.')
64
- try:
65
- lat_val = float(num_part)
66
- if lat_val <= 30:
67
- return '24N'
68
- elif lat_val <= 42:
69
- return '36N'
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:
90
- """
91
- Validate and normalize month input.
92
-
93
- Args:
94
- month: Month input (str or other)
95
-
96
- Returns:
97
- Valid month string in uppercase
98
- """
99
- try:
100
- if not isinstance(month, str):
101
- month = str(month)
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]],
140
- outdoor_conditions: Dict[str, Any],
141
- indoor_conditions: Dict[str, Any],
142
- internal_loads: Dict[str, Any],
143
- building_volume: float
144
- ) -> Dict[str, Any]:
145
- """
146
- Calculate hourly cooling loads for all components.
147
-
148
- Args:
149
- building_components: Dictionary of building components
150
- outdoor_conditions: Outdoor weather conditions
151
- indoor_conditions: Indoor design conditions
152
- internal_loads: Internal heat gains
153
- building_volume: Building volume in cubic meters
154
-
155
- Returns:
156
- Dictionary containing hourly cooling loads
157
- """
158
- hourly_loads = {
159
- 'walls': {h: 0.0 for h in range(1, 25)},
160
- 'roofs': {h: 0.0 for h in range(1, 25)},
161
- 'windows_conduction': {h: 0.0 for h in range(1, 25)},
162
- 'windows_solar': {h: 0.0 for h in range(1, 25)},
163
- 'doors': {h: 0.0 for h in range(1, 25)},
164
- 'people_sensible': {h: 0.0 for h in range(1, 25)},
165
- 'people_latent': {h: 0.0 for h in range(1, 25)},
166
- 'lights': {h: 0.0 for h in range(1, 25)},
167
- 'equipment_sensible': {h: 0.0 for h in range(1, 25)},
168
- 'equipment_latent': {h: 0.0 for h in range(1, 25)},
169
- 'infiltration_sensible': {h: 0.0 for h in range(1, 25)},
170
- 'infiltration_latent': {h: 0.0 for h in range(1, 25)},
171
- 'ventilation_sensible': {h: 0.0 for h in range(1, 25)},
172
- 'ventilation_latent': {h: 0.0 for h in range(1, 25)}
173
- }
174
-
175
- try:
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', []):
184
- for hour in range(24):
185
- load = self.calculate_wall_cooling_load(
186
- wall=wall,
187
- outdoor_temp=outdoor_conditions['temperature'],
188
- indoor_temp=indoor_conditions['temperature'],
189
- month=month,
190
- hour=hour,
191
- latitude=latitude
192
- )
193
- hourly_loads['walls'][hour + 1] += load
194
-
195
- # Calculate loads for roofs
196
- for roof in building_components.get('roofs', []):
197
- for hour in range(24):
198
- load = self.calculate_roof_cooling_load(
199
- roof=roof,
200
- outdoor_temp=outdoor_conditions['temperature'],
201
- indoor_temp=indoor_conditions['temperature'],
202
- month=month,
203
- hour=hour,
204
- latitude=latitude
205
- )
206
- hourly_loads['roofs'][hour + 1] += load
207
-
208
- # Calculate loads for windows
209
- for window in building_components.get('windows', []):
210
- for hour in range(24):
211
- load_dict = self.calculate_window_cooling_load(
212
- window=window,
213
- outdoor_temp=outdoor_conditions['temperature'],
214
- indoor_temp=indoor_conditions['temperature'],
215
- month=month,
216
- hour=hour,
217
- latitude=latitude,
218
- shading_coefficient=window.shading_coefficient
219
- )
220
- hourly_loads['windows_conduction'][hour + 1] += load_dict['conduction']
221
- hourly_loads['windows_solar'][hour + 1] += load_dict['solar']
222
-
223
- # Calculate loads for doors
224
- for door in building_components.get('doors', []):
225
- for hour in range(24):
226
- load = self.calculate_door_cooling_load(
227
- door=door,
228
- outdoor_temp=outdoor_conditions['temperature'],
229
- indoor_temp=indoor_conditions['temperature']
230
- )
231
- hourly_loads['doors'][hour + 1] += load
232
-
233
- # Calculate internal loads
234
- for hour in range(24):
235
- # People loads
236
- people_load = self.calculate_people_cooling_load(
237
- num_people=internal_loads['people']['number'],
238
- activity_level=internal_loads['people']['activity_level'],
239
- hour=hour
240
- )
241
- hourly_loads['people_sensible'][hour + 1] += people_load['sensible']
242
- hourly_loads['people_latent'][hour + 1] += people_load['latent']
243
-
244
- # Lighting loads
245
- lights_load = self.calculate_lights_cooling_load(
246
- power=internal_loads['lights']['power'],
247
- use_factor=internal_loads['lights']['use_factor'],
248
- special_allowance=internal_loads['lights']['special_allowance'],
249
- hour=hour
250
- )
251
- hourly_loads['lights'][hour + 1] += lights_load
252
-
253
- # Equipment loads
254
- equipment_load = self.calculate_equipment_cooling_load(
255
- power=internal_loads['equipment']['power'],
256
- use_factor=internal_loads['equipment']['use_factor'],
257
- radiation_factor=internal_loads['equipment']['radiation_factor'],
258
- hour=hour
259
- )
260
- hourly_loads['equipment_sensible'][hour + 1] += equipment_load['sensible']
261
- hourly_loads['equipment_latent'][hour + 1] += equipment_load['latent']
262
-
263
- # Infiltration loads
264
- infiltration_load = self.calculate_infiltration_cooling_load(
265
- flow_rate=internal_loads['infiltration']['flow_rate'],
266
- building_volume=building_volume,
267
- outdoor_temp=outdoor_conditions['temperature'],
268
- outdoor_rh=outdoor_conditions['relative_humidity'],
269
- indoor_temp=indoor_conditions['temperature'],
270
- indoor_rh=indoor_conditions['relative_humidity']
271
- )
272
- hourly_loads['infiltration_sensible'][hour + 1] += infiltration_load['sensible']
273
- hourly_loads['infiltration_latent'][hour + 1] += infiltration_load['latent']
274
-
275
- # Ventilation loads
276
- ventilation_load = self.calculate_ventilation_cooling_load(
277
- flow_rate=internal_loads['ventilation']['flow_rate'],
278
- outdoor_temp=outdoor_conditions['temperature'],
279
- outdoor_rh=outdoor_conditions['relative_humidity'],
280
- indoor_temp=indoor_conditions['temperature'],
281
- indoor_rh=indoor_conditions['relative_humidity']
282
- )
283
- hourly_loads['ventilation_sensible'][hour + 1] += ventilation_load['sensible']
284
- hourly_loads['ventilation_latent'][hour + 1] += ventilation_load['latent']
285
-
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]:
294
- """
295
- Calculate design cooling load based on peak hourly loads.
296
-
297
- Args:
298
- hourly_loads: Dictionary of hourly cooling loads
299
-
300
- Returns:
301
- Dictionary containing design cooling loads
302
- """
303
- try:
304
- design_loads = {}
305
- total_loads = []
306
-
307
- for hour in range(1, 25):
308
- total_load = sum([
309
- hourly_loads['walls'][hour],
310
- hourly_loads['roofs'][hour],
311
- hourly_loads['windows_conduction'][hour],
312
- hourly_loads['windows_solar'][hour],
313
- hourly_loads['doors'][hour],
314
- hourly_loads['people_sensible'][hour],
315
- hourly_loads['people_latent'][hour],
316
- hourly_loads['lights'][hour],
317
- hourly_loads['equipment_sensible'][hour],
318
- hourly_loads['equipment_latent'][hour],
319
- hourly_loads['infiltration_sensible'][hour],
320
- hourly_loads['infiltration_latent'][hour],
321
- hourly_loads['ventilation_sensible'][hour],
322
- hourly_loads['ventilation_latent'][hour]
323
- ])
324
- total_loads.append(total_load)
325
-
326
- design_hour = range(1, 25)[np.argmax(total_loads)]
327
-
328
- design_loads = {
329
- 'design_hour': design_hour,
330
- 'walls': hourly_loads['walls'][design_hour],
331
- 'roofs': hourly_loads['roofs'][design_hour],
332
- 'windows_conduction': hourly_loads['windows_conduction'][design_hour],
333
- 'windows_solar': hourly_loads['windows_solar'][design_hour],
334
- 'doors': hourly_loads['doors'][design_hour],
335
- 'people_sensible': hourly_loads['people_sensible'][design_hour],
336
- 'people_latent': hourly_loads['people_latent'][design_hour],
337
- 'lights': hourly_loads['lights'][design_hour],
338
- 'equipment_sensible': hourly_loads['equipment_sensible'][design_hour],
339
- 'equipment_latent': hourly_loads['equipment_latent'][design_hour],
340
- 'infiltration_sensible': hourly_loads['infiltration_sensible'][design_hour],
341
- 'infiltration_latent': hourly_loads['infiltration_latent'][design_hour],
342
- 'ventilation_sensible': hourly_loads['ventilation_sensible'][design_hour],
343
- 'ventilation_latent': hourly_loads['ventilation_latent'][design_hour]
344
- }
345
-
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]:
354
- """
355
- Calculate summary of cooling loads.
356
-
357
- Args:
358
- design_loads: Dictionary of design cooling loads
359
-
360
- Returns:
361
- Dictionary containing cooling load summary
362
- """
363
- try:
364
- total_sensible = (
365
- design_loads['walls'] +
366
- design_loads['roofs'] +
367
- design_loads['windows_conduction'] +
368
- design_loads['windows_solar'] +
369
- design_loads['doors'] +
370
- design_loads['people_sensible'] +
371
- design_loads['lights'] +
372
- design_loads['equipment_sensible'] +
373
- design_loads['infiltration_sensible'] +
374
- design_loads['ventilation_sensible']
375
- )
376
-
377
- total_latent = (
378
- design_loads['people_latent'] +
379
- design_loads['equipment_latent'] +
380
- design_loads['infiltration_latent'] +
381
- design_loads['ventilation_latent']
382
- )
383
-
384
- total = total_sensible + total_latent
385
-
386
- return {
387
- 'total_sensible': total_sensible,
388
- 'total_latent': total_latent,
389
- 'total': total
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(
398
- self,
399
- wall: Wall,
400
- outdoor_temp: float,
401
- indoor_temp: float,
402
- month: str,
403
- hour: int,
404
- latitude: str
405
- ) -> float:
406
- """
407
- Calculate cooling load for a wall.
408
-
409
- Args:
410
- wall: Wall component
411
- outdoor_temp: Outdoor temperature (°C)
412
- indoor_temp: Indoor temperature (°C)
413
- month: Design month
414
- hour: Hour of the day
415
- latitude: Latitude (e.g., '24N')
416
-
417
- Returns:
418
- Cooling load in Watts
419
- """
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()
429
- if wall_group not in self.valid_wall_groups:
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:
441
- cltd = self.ashrae_tables.calculate_corrected_cltd_wall(
442
- wall_group=wall_group,
443
- orientation=wall.orientation.value,
444
- hour=hour,
445
- color='Dark',
446
- month=month,
447
- latitude=latitude,
448
- indoor_temp=indoor_temp,
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(
468
- self,
469
- roof: Roof,
470
- outdoor_temp: float,
471
- indoor_temp: float,
472
- month: str,
473
- hour: int,
474
- latitude: str
475
- ) -> float:
476
- """
477
- Calculate cooling load for a roof.
478
-
479
- Args:
480
- roof: Roof component
481
- outdoor_temp: Outdoor temperature (°C)
482
- indoor_temp: Indoor temperature (°C)
483
- month: Design month
484
- hour: Hour of the day
485
- latitude: Latitude (e.g., '24N')
486
-
487
- Returns:
488
- Cooling load in Watts
489
- """
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:
504
- cltd = self.ashrae_tables.calculate_corrected_cltd_roof(
505
- roof_group=roof_group,
506
- hour=hour,
507
- color='Dark',
508
- month=month,
509
- latitude=latitude,
510
- indoor_temp=indoor_temp,
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(
530
- self,
531
- window: Window,
532
- outdoor_temp: float,
533
- indoor_temp: float,
534
- month: str,
535
- hour: int,
536
- latitude: str,
537
- shading_coefficient: float
538
- ) -> Dict[str, float]:
539
- """
540
- Calculate cooling load for a window (conduction and solar).
541
-
542
- Args:
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(
598
- self,
599
- door: Door,
600
- outdoor_temp: float,
601
- indoor_temp: float
602
- ) -> float:
603
- """
604
- Calculate cooling load for a door.
605
-
606
- Args:
607
- door: Door component
608
- outdoor_temp: Outdoor temperature (°C)
609
- indoor_temp: Indoor temperature (°C)
610
-
611
- Returns:
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(
630
- self,
631
- num_people: int,
632
- activity_level: str,
633
- hour: int
634
- ) -> Dict[str, float]:
635
- """
636
- Calculate cooling load from people.
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
650
-
651
- System: You are Grok 3 built by xAI.
652
-
653
- The current date is April 27, 2025.
654
-
655
- Thank you for providing the `heat_transfer.py` file and the updated `cooling_load.py`. The error `'HeatTransferCalculations' object has no attribute 'calculate_humidity_ratio'` has been resolved by updating `cooling_load.py` to use `self.heat_transfer.psychrometrics.humidity_ratio` instead of `self.heat_transfer.calculate_humidity_ratio`, as the `Psychrometrics` class in `utils.psychrometrics` handles humidity ratio calculations. Below, I’ll provide the complete, corrected `cooling_load.py` to ensure all functionality is preserved and the error is fixed.
656
-
657
- ### Analysis Recap
658
- - **Error Cause**: `cooling_load.py` incorrectly called `self.heat_transfer.calculate_humidity_ratio` in `calculate_infiltration_cooling_load` and `calculate_ventilation_cooling_load`. The `HeatTransferCalculations` class (in `heat_transfer.py`) delegates psychrometric calculations to `self.psychrometrics.humidity_ratio`.
659
- - **Fix**: Replace the incorrect method calls with `self.heat_transfer.psychrometrics.humidity_ratio`.
660
- - **Additional Checks**:
661
- - The previous response included a temporary `calculate_humidity_ratio` method in `cooling_load.py`, which is no longer needed and has been removed.
662
- - All other methods, validations, and logging remain unchanged to maintain compatibility with `main.py`, `ashrae_tables.py`, and `heat_transfer.py`.
663
- - The artifact ID (`c47f4e86-6a73-4d0d-a7c2-2656f6dc55a2`) is retained as this is an update to the same file.
664
-
665
- ### Updated `cooling_load.py`
666
- The updated file corrects the method calls in `calculate_infiltration_cooling_load` and `calculate_ventilation_cooling_load`, removes the unnecessary `calculate_humidity_ratio` method, and ensures all other functionality (e.g., wall, roof, window calculations, debug logging with `st.session_state.debug_mode`) is intact. The version is incremented to 1.0.3 to reflect the fix.
667
-
668
- <xaiArtifact artifact_id="c47f4e86-6a73-4d0d-a7c2-2656f6dc55a2" artifact_version_id="430e5082-16c0-4456-a8c3-b766fb73f165" title="cooling_load.py" contentType="text/python">
669
- """
670
- Cooling load calculation module for HVAC Load Calculator.
671
- Based on ASHRAE steady-state calculation methods.
672
-
673
- Author: Dr Majed Abuseif
674
- Date: April 2025
675
- Version: 1.0.3
676
  """
677
 
678
  import streamlit as st
@@ -1491,8 +823,14 @@ class CoolingLoadCalculator:
1491
  sensible_load = 1.2 * flow_rate * (outdoor_temp - indoor_temp)
1492
 
1493
  # Latent load using psychrometrics
1494
- outdoor_w = self.heat_transfer.psychrometrics.humidity_ratio(outdoor_temp, outdoor_rh)
1495
- indoor_w = self.heat_transfer.psychrometrics.humidity_ratio(indoor_temp, indoor_rh)
 
 
 
 
 
 
1496
  latent_load = 1.2 * flow_rate * 2500 * (outdoor_w - indoor_w) # 2500 kJ/kg for latent heat
1497
 
1498
  if st.session_state.get('debug_mode', False):
@@ -1537,8 +875,14 @@ class CoolingLoadCalculator:
1537
  sensible_load = 1.2 * flow_rate * (outdoor_temp - indoor_temp)
1538
 
1539
  # Latent load using psychrometrics
1540
- outdoor_w = self.heat_transfer.psychrometrics.humidity_ratio(outdoor_temp, outdoor_rh)
1541
- indoor_w = self.heat_transfer.psychrometrics.humidity_ratio(indoor_temp, indoor_rh)
 
 
 
 
 
 
1542
  latent_load = 1.2 * flow_rate * 2500 * (outdoor_w - indoor_w) # 2500 kJ/kg for latent heat
1543
 
1544
  if st.session_state.get('debug_mode', False):
 
4
 
5
  Author: Dr Majed Abuseif
6
  Date: April 2025
7
+ Version: 1.0.4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  """
9
 
10
  import streamlit as st
 
823
  sensible_load = 1.2 * flow_rate * (outdoor_temp - indoor_temp)
824
 
825
  # Latent load using psychrometrics
826
+ try:
827
+ outdoor_w = self.heat_transfer.psychrometrics.humidity_ratio(outdoor_temp, outdoor_rh)
828
+ indoor_w = self.heat_transfer.psychrometrics.humidity_ratio(indoor_temp, indoor_rh)
829
+ except Exception as e:
830
+ if st.session_state.get('debug_mode', False):
831
+ logger.error(f"humidity_ratio failed: {str(e)}")
832
+ logger.warning("Using default humidity ratio=0.01 kg/kg")
833
+ outdoor_w = indoor_w = 0.01
834
  latent_load = 1.2 * flow_rate * 2500 * (outdoor_w - indoor_w) # 2500 kJ/kg for latent heat
835
 
836
  if st.session_state.get('debug_mode', False):
 
875
  sensible_load = 1.2 * flow_rate * (outdoor_temp - indoor_temp)
876
 
877
  # Latent load using psychrometrics
878
+ try:
879
+ outdoor_w = self.heat_transfer.psychrometrics.humidity_ratio(outdoor_temp, outdoor_rh)
880
+ indoor_w = self.heat_transfer.psychrometrics.humidity_ratio(indoor_temp, indoor_rh)
881
+ except Exception as e:
882
+ if st.session_state.get('debug_mode', False):
883
+ logger.error(f"humidity_ratio failed: {str(e)}")
884
+ logger.warning("Using default humidity ratio=0.01 kg/kg")
885
+ outdoor_w = indoor_w = 0.01
886
  latent_load = 1.2 * flow_rate * 2500 * (outdoor_w - indoor_w) # 2500 kJ/kg for latent heat
887
 
888
  if st.session_state.get('debug_mode', False):