mabuseif commited on
Commit
6175351
·
verified ·
1 Parent(s): 9d49b43

Update app/hvac_loads.py

Browse files
Files changed (1) hide show
  1. app/hvac_loads.py +115 -90
app/hvac_loads.py CHANGED
@@ -50,27 +50,55 @@ class TFMCalculations:
50
  }
51
 
52
  @staticmethod
53
- def calculate_conduction_load(component, outdoor_temp: float, indoor_temp: float, hour: int, mode: str = "none") -> tuple[float, float]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  """Calculate conduction load for heating and cooling in kW based on mode."""
55
  if mode == "none":
56
  return 0, 0
 
57
  delta_t = outdoor_temp - indoor_temp
58
  if mode == "cooling" and delta_t <= 0:
59
  return 0, 0
60
  if mode == "heating" and delta_t >= 0:
61
  return 0, 0
62
 
63
- # Get CTF coefficients using CTFCalculator
64
- ctf = CTFCalculator.calculate_ctf_coefficients(component)
65
-
66
- # Initialize history terms (simplified: assume steady-state history for demonstration)
67
- load = component.u_value * component.area * delta_t
68
- for i in range(len(ctf.Y)):
69
- load += component.area * ctf.Y[i] * (outdoor_temp - indoor_temp) * np.exp(-i * 3600 / 3600)
70
- load -= component.area * ctf.Z[i] * (outdoor_temp - indoor_temp) * np.exp(-i * 3600 / 3600)
71
- cooling_load = load / 1000 if mode == "cooling" else 0
72
- heating_load = -load / 1000 if mode == "heating" else 0
73
- return cooling_load, heating_load
 
 
 
 
 
 
 
 
 
 
74
 
75
  @staticmethod
76
  def day_of_year(month: int, day: int, year: int) -> int:
@@ -102,11 +130,12 @@ class TFMCalculations:
102
  return f_cos_theta
103
 
104
  @staticmethod
105
- def get_surface_parameters(component: Any, building_info: Dict, material_library: MaterialLibrary,
106
  project_materials: Dict, project_constructions: Dict,
107
  project_glazing_materials: Dict) -> Tuple[float, float, float, Optional[float], float]:
108
  """Determine surface parameters (tilt, azimuth, h_o, emissivity, absorptivity) for a component."""
109
- component_name = getattr(component, 'name', 'unnamed_component')
 
110
 
111
  surface_tilt = 90.0
112
  surface_azimuth = 0.0
@@ -115,19 +144,19 @@ class TFMCalculations:
115
  absorptivity = 0.6
116
 
117
  try:
118
- if component.component_type == ComponentType.ROOF:
119
- surface_tilt = getattr(component, 'tilt', 0.0)
120
  h_o = 23.0
121
- surface_azimuth = getattr(component, 'orientation', 0.0)
122
  logger.debug(f"Roof component {component_name}: using orientation={surface_azimuth}, tilt={surface_tilt}")
123
 
124
- elif component.component_type == ComponentType.SKYLIGHT:
125
- surface_tilt = getattr(component, 'tilt', 0.0)
126
  h_o = 23.0
127
- surface_azimuth = getattr(component, 'orientation', 0.0)
128
  logger.debug(f"Skylight component {component_name}: using orientation={surface_azimuth}, tilt={surface_tilt}")
129
 
130
- elif component.component_type == ComponentType.FLOOR:
131
  surface_tilt = 180.0
132
  h_o = 17.0
133
  surface_azimuth = 0.0
@@ -136,9 +165,9 @@ class TFMCalculations:
136
  else: # WALL, WINDOW
137
  surface_tilt = 90.0
138
  h_o = 17.0
139
- elevation = getattr(component, 'elevation', None)
140
  if not elevation:
141
- logger.warning(f"Component {component_name} ({component.component_type.value}) is missing 'elevation' field. Using default azimuth=0.")
142
  surface_azimuth = 0.0
143
  else:
144
  base_azimuth = building_info.get("orientation_angle", 0.0)
@@ -149,65 +178,62 @@ class TFMCalculations:
149
  "D": (base_azimuth + 270.0) % 360
150
  }
151
  if elevation not in elevation_angles:
152
- logger.warning(f"Invalid elevation '{elevation}' for component {component_name}. Using default azimuth=0.")
153
  surface_azimuth = 0.0
154
  else:
155
- surface_azimuth = (elevation_angles[elevation] + getattr(component, 'rotation', 0.0)) % 360
156
- logger.debug(f"Component {component_name}: elevation={elevation}, base_azimuth={elevation_angles[elevation]}, rotation={getattr(component, 'rotation', 0.0)}, total_azimuth={surface_azimuth}")
157
 
158
- if component.component_type in [ComponentType.WALL, ComponentType.ROOF]:
159
- construction = getattr(component, 'construction', None)
160
- if not construction:
161
  logger.warning(f"No construction defined for {component_name}. Using defaults: absorptivity=0.6, emissivity=0.9.")
162
  else:
163
- construction_obj = None
164
- if hasattr(construction, 'name'):
165
- construction_obj = (project_constructions.get(construction.name) or
166
- material_library.library_constructions.get(construction.name))
167
-
168
  if not construction_obj:
169
- logger.warning(f"Construction not found for {component_name}. Using defaults: absorptivity=0.6, emissivity=0.9.")
170
- elif not construction_obj.layers:
171
  logger.warning(f"No layers in construction for {component_name}. Using defaults: absorptivity=0.6, emissivity=0.9.")
172
  else:
173
- first_layer = construction_obj.layers[0]
174
- material = first_layer.get("material")
175
- if material:
176
- absorptivity = getattr(material, 'absorptivity', 0.6)
177
- emissivity = getattr(material, 'emissivity', 0.9)
178
- logger.debug(f"Using first layer material for {component_name}: absorptivity={absorptivity}, emissivity={emissivity}")
179
-
180
- elif component.component_type in [ComponentType.WINDOW, ComponentType.SKYLIGHT]:
181
- glazing_material = getattr(component, 'glazing_material', None)
182
- if not glazing_material:
 
 
 
183
  logger.warning(f"No glazing material defined for {component_name}. Using default SHGC=0.7, h_o={h_o}.")
184
  shgc = 0.7
185
  else:
186
- glazing_material_obj = None
187
- if hasattr(glazing_material, 'name'):
188
- glazing_material_obj = (project_glazing_materials.get(glazing_material.name) or
189
- material_library.library_glazing_materials.get(glazing_material.name))
190
-
191
  if not glazing_material_obj:
192
- logger.warning(f"Glazing material not found for {component_name}. Using default SHGC=0.7, h_o={h_o}.")
193
  shgc = 0.7
194
  else:
195
- shgc = getattr(glazing_material_obj, 'shgc', 0.7)
196
- h_o = getattr(glazing_material_obj, 'h_o', h_o)
197
- logger.debug(f"Using glazing material for {component_name}: shgc={shgc}, h_o={h_o}")
198
  emissivity = None
199
 
200
  except Exception as e:
201
  logger.error(f"Error retrieving surface parameters for {component_name}: {str(e)}")
202
- if component.component_type == ComponentType.ROOF:
203
  surface_tilt = 0.0
204
  h_o = 23.0
205
  surface_azimuth = 0.0
206
- elif component.component_type == ComponentType.SKYLIGHT:
207
  surface_tilt = 0.0
208
  h_o = 23.0
209
  surface_azimuth = 0.0
210
- elif component.component_type == ComponentType.FLOOR:
211
  surface_tilt = 180.0
212
  h_o = 17.0
213
  surface_azimuth = 0.0
@@ -216,7 +242,7 @@ class TFMCalculations:
216
  h_o = 17.0
217
  surface_azimuth = 0.0
218
 
219
- if component.component_type in [ComponentType.WALL, ComponentType.ROOF]:
220
  absorptivity = 0.6
221
  emissivity = 0.9
222
  else: # WINDOW, SKYLIGHT
@@ -227,15 +253,16 @@ class TFMCalculations:
227
  return surface_tilt, surface_azimuth, h_o, emissivity, absorptivity
228
 
229
  @staticmethod
230
- def calculate_solar_load(component, hourly_data: Dict, hour: int, building_orientation: float, mode: str = "none") -> float:
231
  """Calculate solar load in kW (cooling only) using ASHRAE-compliant solar calculations."""
232
  if mode != "cooling":
233
  return 0
234
 
235
- if component.component_type == ComponentType.FLOOR:
 
236
  return 0
237
 
238
- component_name = getattr(component, 'name', 'unnamed_component')
239
 
240
  try:
241
  material_library = st.session_state.get("material_library")
@@ -281,7 +308,7 @@ class TFMCalculations:
281
  day = hourly_data["day"]
282
  hour = hourly_data["hour"]
283
  ghi = hourly_data["global_horizontal_radiation"]
284
- dni = hourly_data.get("direct_normal_radiation", echi * 0.7)
285
  dhi = hourly_data.get("diffuse_horizontal_radiation", ghi * 0.3)
286
  outdoor_temp = hourly_data["dry_bulb"]
287
 
@@ -329,25 +356,25 @@ class TFMCalculations:
329
  )
330
  except Exception as e:
331
  logger.error(f"Error getting surface parameters for {component_name}: {str(e)}. Using defaults.")
332
- if component.component_type == ComponentType.ROOF:
333
  surface_tilt = 0.0
334
  surface_azimuth = 0.0
335
- elif component.component_type == ComponentType.SKYLIGHT:
336
  surface_tilt = 0.0
337
  surface_azimuth = 0.0
338
- elif component.component_type == ComponentType.FLOOR:
339
  surface_tilt = 180.0
340
  surface_azimuth = 0.0
341
  else: # WALL, WINDOW
342
  surface_tilt = 90.0
343
  surface_azimuth = 0.0
344
 
345
- if component.component_type in [ComponentType.WALL, ComponentType.ROOF]:
346
  absorptivity = 0.6
347
- h_o = 17.0 if component.component_type == ComponentType.WALL else 23.0
348
  else: # WINDOW, SKYLIGHT
349
  absorptivity = 0.0
350
- h_o = 17.0 if component.component_type == ComponentType.WINDOW else 23.0
351
 
352
  alpha_rad = math.radians(alpha)
353
  surface_tilt_rad = math.radians(surface_tilt)
@@ -374,42 +401,39 @@ class TFMCalculations:
374
 
375
  solar_heat_gain = 0.0
376
 
377
- if component.component_type in [ComponentType.WINDOW, ComponentType.SKYLIGHT]:
378
  shgc = 0.7
379
- glazing_material = getattr(component, 'glazing_material', None)
380
- if glazing_material:
381
- glazing_material_obj = None
382
- if hasattr(glazing_material, 'name'):
383
- glazing_material_obj = (project_glazing_materials.get(glazing_material.name) or
384
- material_library.library_glazing_materials.get(glazing_material.name))
385
-
386
  if glazing_material_obj:
387
- shgc = getattr(glazing_material_obj, 'shgc', 0.7)
388
- h_o = getattr(glazing_material_obj, 'h_o', h_o)
389
  else:
390
- logger.warning(f"Glazing material not found for {component_name}. Using default SHGC=0.7.")
391
 
392
  glazing_type = "Single Clear"
393
- if hasattr(component, 'name') and component.name in TFMCalculations.GLAZING_TYPE_MAPPING:
394
- glazing_type = TFMCalculations.GLAZING_TYPE_MAPPING[component.name]
395
 
396
- iac = getattr(component, 'iac', 1.0)
397
 
398
  shgc_dynamic = shgc * TFMCalculations.calculate_dynamic_shgc(glazing_type, cos_theta)
399
 
400
- solar_heat_gain = component.area * shgc_dynamic * I_t * iac / 1000
401
 
402
  logger.info(f"Fenestration solar heat gain for {component_name} at {month}/{day}/{hour}: "
403
- f"{solar_heat_gain:.4f} kW (area={component.area}, shgc_dynamic={shgc_dynamic:.4f}, "
404
  f"I_t={I_t:.2f}, iac={iac})")
405
 
406
- elif component.component_type in [ComponentType.WALL, ComponentType.ROOF]:
407
  surface_resistance = 1/h_o
408
 
409
- solar_heat_gain = component.area * absorptivity * I_t * surface_resistance / 1000
410
 
411
  logger.info(f"Opaque surface solar heat gain for {component_name} at {month}/{day}/{hour}: "
412
- f"{solar_heat_gain:.4f} kW (area={component.area}, absorptivity={absorptivity:.2f}, "
413
  f"I_t={I_t:.2f}, surface_resistance={surface_resistance:.4f})")
414
 
415
  return solar_heat_gain
@@ -681,7 +705,8 @@ class TFMCalculations:
681
 
682
  for comp_list in components.values():
683
  for comp in comp_list:
684
- comp.ctf = CTFCalculator.calculate_ctf_coefficients(comp)
 
685
 
686
  for hour_data in filtered_data:
687
  hour = hour_data["hour"]
@@ -704,7 +729,7 @@ class TFMCalculations:
704
  conduction_cooling += cool_load
705
  component_solar_load = TFMCalculations.calculate_solar_load(comp, hour_data, hour, building_orientation, mode="cooling")
706
  solar += component_solar_load
707
- logger.info(f"Component {comp.name} ({comp.component_type.value}) solar load: {component_solar_load:.3f} kW, accumulated solar: {solar:.3f} kW")
708
 
709
  internal = TFMCalculations.calculate_internal_load(internal_loads, hour, max([p["end"] - p["start"] for p in operating_periods]), area)
710
  ventilation_cooling, _ = TFMCalculations.calculate_ventilation_load(internal_loads, outdoor_temp, indoor_temp, area, building_info, mode="cooling")
 
50
  }
51
 
52
  @staticmethod
53
+ def get_component_type(component: Dict[str, Any]) -> ComponentType:
54
+ """Map component dictionary 'type' to ComponentType enum."""
55
+ comp_type_map = {
56
+ 'walls': ComponentType.WALL,
57
+ 'roofs': ComponentType.ROOF,
58
+ 'floors': ComponentType.FLOOR,
59
+ 'windows': ComponentType.WINDOW,
60
+ 'skylights': ComponentType.SKYLIGHT
61
+ }
62
+ comp_type_str = component.get('type', '').lower()
63
+ component_type = comp_type_map.get(comp_type_str, None)
64
+ if not component_type:
65
+ logger.warning(f"Invalid component type '{comp_type_str}' for component '{component.get('name', 'Unknown')}'. Defaulting to WALL.")
66
+ return ComponentType.WALL
67
+ return component_type
68
+
69
+ @staticmethod
70
+ def calculate_conduction_load(component: Dict[str, Any], outdoor_temp: float, indoor_temp: float, hour: int, mode: str = "none") -> tuple[float, float]:
71
  """Calculate conduction load for heating and cooling in kW based on mode."""
72
  if mode == "none":
73
  return 0, 0
74
+ component_name = component.get('name', 'unnamed_component')
75
  delta_t = outdoor_temp - indoor_temp
76
  if mode == "cooling" and delta_t <= 0:
77
  return 0, 0
78
  if mode == "heating" and delta_t >= 0:
79
  return 0, 0
80
 
81
+ try:
82
+ # Get CTF coefficients, preferring stored value
83
+ ctf = component.get('ctf')
84
+ if not ctf:
85
+ logger.debug(f"No CTF coefficients found for {component_name}. Calculating CTF coefficients.")
86
+ ctf = CTFCalculator.calculate_ctf_coefficients(component)
87
+ component['ctf'] = ctf # Store in dictionary
88
+
89
+ # Initialize history terms (simplified: assume steady-state history for demonstration)
90
+ load = component.get('u_value', 0.0) * component.get('area', 0.0) * delta_t
91
+ for i in range(len(ctf.Y)):
92
+ load += component.get('area', 0.0) * ctf.Y[i] * (outdoor_temp - indoor_temp) * np.exp(-i * 3600 / 3600)
93
+ load -= component.get('area', 0.0) * ctf.Z[i] * (outdoor_temp - indoor_temp) * np.exp(-i * 3600 / 3600)
94
+ cooling_load = load / 1000 if mode == "cooling" else 0
95
+ heating_load = -load / 1000 if mode == "heating" else 0
96
+ logger.info(f"Conduction load for {component_name} at hour {hour}: cooling={cooling_load:.3f} kW, heating={heating_load:.3f} kW")
97
+ return cooling_load, heating_load
98
+
99
+ except Exception as e:
100
+ logger.error(f"Error calculating conduction load for {component_name} at hour {hour}: {str(e)}")
101
+ return 0, 0
102
 
103
  @staticmethod
104
  def day_of_year(month: int, day: int, year: int) -> int:
 
130
  return f_cos_theta
131
 
132
  @staticmethod
133
+ def get_surface_parameters(component: Dict[str, Any], building_info: Dict, material_library: MaterialLibrary,
134
  project_materials: Dict, project_constructions: Dict,
135
  project_glazing_materials: Dict) -> Tuple[float, float, float, Optional[float], float]:
136
  """Determine surface parameters (tilt, azimuth, h_o, emissivity, absorptivity) for a component."""
137
+ component_name = component.get('name', 'unnamed_component')
138
+ component_type = TFMCalculations.get_component_type(component)
139
 
140
  surface_tilt = 90.0
141
  surface_azimuth = 0.0
 
144
  absorptivity = 0.6
145
 
146
  try:
147
+ if component_type == ComponentType.ROOF:
148
+ surface_tilt = component.get('tilt', 0.0)
149
  h_o = 23.0
150
+ surface_azimuth = component.get('orientation', 0.0)
151
  logger.debug(f"Roof component {component_name}: using orientation={surface_azimuth}, tilt={surface_tilt}")
152
 
153
+ elif component_type == ComponentType.SKYLIGHT:
154
+ surface_tilt = component.get('tilt', 0.0)
155
  h_o = 23.0
156
+ surface_azimuth = component.get('orientation', 0.0)
157
  logger.debug(f"Skylight component {component_name}: using orientation={surface_azimuth}, tilt={surface_tilt}")
158
 
159
+ elif component_type == ComponentType.FLOOR:
160
  surface_tilt = 180.0
161
  h_o = 17.0
162
  surface_azimuth = 0.0
 
165
  else: # WALL, WINDOW
166
  surface_tilt = 90.0
167
  h_o = 17.0
168
+ elevation = component.get('elevation', None)
169
  if not elevation:
170
+ logger.warning(f"Component {component_name} ({component_type.value}) is missing 'elevation' field. Using default azimuth=0.")
171
  surface_azimuth = 0.0
172
  else:
173
  base_azimuth = building_info.get("orientation_angle", 0.0)
 
178
  "D": (base_azimuth + 270.0) % 360
179
  }
180
  if elevation not in elevation_angles:
181
+ logger.warning(f"Invalid elevation '{elevation}' for {component_name}. Using default azimuth=0.")
182
  surface_azimuth = 0.0
183
  else:
184
+ surface_azimuth = (elevation_angles[elevation] + component.get('rotation', 0.0)) % 360
185
+ logger.debug(f"Component {component_name}: elevation={elevation}, base_azimuth={elevation_angles[elevation]}, rotation={component.get('rotation', 0.0)}, total_azimuth={surface_azimuth}")
186
 
187
+ if component_type in [ComponentType.WALL, ComponentType.ROOF]:
188
+ construction_name = component.get('construction', None)
189
+ if not construction_name:
190
  logger.warning(f"No construction defined for {component_name}. Using defaults: absorptivity=0.6, emissivity=0.9.")
191
  else:
192
+ construction_obj = (project_constructions.get(construction_name) or
193
+ material_library.library_constructions.get(construction_name))
 
 
 
194
  if not construction_obj:
195
+ logger.warning(f"Construction '{construction_name}' not found for {component_name}. Using defaults: absorptivity=0.6, emissivity=0.9.")
196
+ elif not construction_obj.get('layers', []):
197
  logger.warning(f"No layers in construction for {component_name}. Using defaults: absorptivity=0.6, emissivity=0.9.")
198
  else:
199
+ first_layer = construction_obj['layers'][0]
200
+ material_name = first_layer.get("material")
201
+ if material_name:
202
+ material_obj = (project_materials.get(material_name) or
203
+ material_library.library_materials.get(material_name))
204
+ if material_obj:
205
+ absorptivity = material_obj.get('absorptivity', 0.6)
206
+ emissivity = material_obj.get('emissivity', 0.9)
207
+ logger.debug(f"Using first layer material '{material_name}' for {component_name}: absorptivity={absorptivity}, emissivity={emissivity}")
208
+
209
+ elif component_type in [ComponentType.WINDOW, ComponentType.SKYLIGHT]:
210
+ glazing_material_name = component.get('glazing_material', None)
211
+ if not glazing_material_name:
212
  logger.warning(f"No glazing material defined for {component_name}. Using default SHGC=0.7, h_o={h_o}.")
213
  shgc = 0.7
214
  else:
215
+ glazing_material_obj = (project_glazing_materials.get(glazing_material_name) or
216
+ material_library.library_glazing_materials.get(glazing_material_name))
 
 
 
217
  if not glazing_material_obj:
218
+ logger.warning(f"Glazing material '{glazing_material_name}' not found for {component_name}. Using default SHGC=0.7, h_o={h_o}.")
219
  shgc = 0.7
220
  else:
221
+ shgc = glazing_material_obj.get('shgc', 0.7)
222
+ h_o = glazing_material_obj.get('h_o', h_o)
223
+ logger.debug(f"Using glazing material '{glazing_material_name}' for {component_name}: shgc={shgc}, h_o={h_o}")
224
  emissivity = None
225
 
226
  except Exception as e:
227
  logger.error(f"Error retrieving surface parameters for {component_name}: {str(e)}")
228
+ if component_type == ComponentType.ROOF:
229
  surface_tilt = 0.0
230
  h_o = 23.0
231
  surface_azimuth = 0.0
232
+ elif component_type == ComponentType.SKYLIGHT:
233
  surface_tilt = 0.0
234
  h_o = 23.0
235
  surface_azimuth = 0.0
236
+ elif component_type == ComponentType.FLOOR:
237
  surface_tilt = 180.0
238
  h_o = 17.0
239
  surface_azimuth = 0.0
 
242
  h_o = 17.0
243
  surface_azimuth = 0.0
244
 
245
+ if component_type in [ComponentType.WALL, ComponentType.ROOF]:
246
  absorptivity = 0.6
247
  emissivity = 0.9
248
  else: # WINDOW, SKYLIGHT
 
253
  return surface_tilt, surface_azimuth, h_o, emissivity, absorptivity
254
 
255
  @staticmethod
256
+ def calculate_solar_load(component: Dict[str, Any], hourly_data: Dict, hour: int, building_orientation: float, mode: str = "none") -> float:
257
  """Calculate solar load in kW (cooling only) using ASHRAE-compliant solar calculations."""
258
  if mode != "cooling":
259
  return 0
260
 
261
+ component_type = TFMCalculations.get_component_type(component)
262
+ if component_type == ComponentType.FLOOR:
263
  return 0
264
 
265
+ component_name = component.get('name', 'unnamed_component')
266
 
267
  try:
268
  material_library = st.session_state.get("material_library")
 
308
  day = hourly_data["day"]
309
  hour = hourly_data["hour"]
310
  ghi = hourly_data["global_horizontal_radiation"]
311
+ dni = hourly_data.get("direct_normal_radiation", ghi * 0.7)
312
  dhi = hourly_data.get("diffuse_horizontal_radiation", ghi * 0.3)
313
  outdoor_temp = hourly_data["dry_bulb"]
314
 
 
356
  )
357
  except Exception as e:
358
  logger.error(f"Error getting surface parameters for {component_name}: {str(e)}. Using defaults.")
359
+ if component_type == ComponentType.ROOF:
360
  surface_tilt = 0.0
361
  surface_azimuth = 0.0
362
+ elif component_type == ComponentType.SKYLIGHT:
363
  surface_tilt = 0.0
364
  surface_azimuth = 0.0
365
+ elif component_type == ComponentType.FLOOR:
366
  surface_tilt = 180.0
367
  surface_azimuth = 0.0
368
  else: # WALL, WINDOW
369
  surface_tilt = 90.0
370
  surface_azimuth = 0.0
371
 
372
+ if component_type in [ComponentType.WALL, ComponentType.ROOF]:
373
  absorptivity = 0.6
374
+ h_o = 17.0 if component_type == ComponentType.WALL else 23.0
375
  else: # WINDOW, SKYLIGHT
376
  absorptivity = 0.0
377
+ h_o = 17.0 if component_type == ComponentType.WINDOW else 23.0
378
 
379
  alpha_rad = math.radians(alpha)
380
  surface_tilt_rad = math.radians(surface_tilt)
 
401
 
402
  solar_heat_gain = 0.0
403
 
404
+ if component_type in [ComponentType.WINDOW, ComponentType.SKYLIGHT]:
405
  shgc = 0.7
406
+ glazing_material_name = component.get('glazing_material', None)
407
+ if glazing_material_name:
408
+ glazing_material_obj = (project_glazing_materials.get(glazing_material_name) or
409
+ material_library.library_glazing_materials.get(glazing_material_name))
 
 
 
410
  if glazing_material_obj:
411
+ shgc = glazing_material_obj.get('shgc', 0.7)
412
+ h_o = glazing_material_obj.get('h_o', h_o)
413
  else:
414
+ logger.warning(f"Glazing material '{glazing_material_name}' not found for {component_name}. Using default SHGC=0.7.")
415
 
416
  glazing_type = "Single Clear"
417
+ if component.get('name') in TFMCalculations.GLAZING_TYPE_MAPPING:
418
+ glazing_type = TFMCalculations.GLAZING_TYPE_MAPPING[component.get('name')]
419
 
420
+ iac = component.get('iac', 1.0)
421
 
422
  shgc_dynamic = shgc * TFMCalculations.calculate_dynamic_shgc(glazing_type, cos_theta)
423
 
424
+ solar_heat_gain = component.get('area', 0.0) * shgc_dynamic * I_t * iac / 1000
425
 
426
  logger.info(f"Fenestration solar heat gain for {component_name} at {month}/{day}/{hour}: "
427
+ f"{solar_heat_gain:.4f} kW (area={component.get('area', 0.0)}, shgc_dynamic={shgc_dynamic:.4f}, "
428
  f"I_t={I_t:.2f}, iac={iac})")
429
 
430
+ elif component_type in [ComponentType.WALL, ComponentType.ROOF]:
431
  surface_resistance = 1/h_o
432
 
433
+ solar_heat_gain = component.get('area', 0.0) * absorptivity * I_t * surface_resistance / 1000
434
 
435
  logger.info(f"Opaque surface solar heat gain for {component_name} at {month}/{day}/{hour}: "
436
+ f"{solar_heat_gain:.4f} kW (area={component.get('area', 0.0)}, absorptivity={absorptivity:.2f}, "
437
  f"I_t={I_t:.2f}, surface_resistance={surface_resistance:.4f})")
438
 
439
  return solar_heat_gain
 
705
 
706
  for comp_list in components.values():
707
  for comp in comp_list:
708
+ comp['ctf'] = CTFCalculator.calculate_ctf_coefficients(comp)
709
+ logger.debug(f"Stored CTF coefficients for component {comp.get('name', 'Unknown')}")
710
 
711
  for hour_data in filtered_data:
712
  hour = hour_data["hour"]
 
729
  conduction_cooling += cool_load
730
  component_solar_load = TFMCalculations.calculate_solar_load(comp, hour_data, hour, building_orientation, mode="cooling")
731
  solar += component_solar_load
732
+ logger.info(f"Component {comp.get('name', 'Unknown')} ({TFMCalculations.get_component_type(comp).value}) solar load: {component_solar_load:.3f} kW, accumulated solar: {solar:.3f} kW")
733
 
734
  internal = TFMCalculations.calculate_internal_load(internal_loads, hour, max([p["end"] - p["start"] for p in operating_periods]), area)
735
  ventilation_cooling, _ = TFMCalculations.calculate_ventilation_load(internal_loads, outdoor_temp, indoor_temp, area, building_info, mode="cooling")