HVAC-testing / utils /cooling_load.py
mabuseif's picture
Update utils/cooling_load.py
32bccf6 verified
raw
history blame
66.8 kB
"""
Cooling load calculation module for HVAC Load Calculator.
Based on ASHRAE steady-state calculation methods.
Author: Dr Majed Abuseif
Date: April 2025
Version: 1.0.2
"""
import streamlit as st
from typing import Dict, List, Any, Optional, Tuple
import numpy as np
from datetime import datetime
import logging
from data.ashrae_tables import ASHRAETables
from utils.heat_transfer import HeatTransferCalculations
from app.component_selection import Wall, Roof, Window, Door, Orientation
# Set up logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
class CoolingLoadCalculator:
"""Class for cooling load calculations."""
def __init__(self):
"""Initialize cooling load calculator."""
self.ashrae_tables = ASHRAETables()
self.heat_transfer = HeatTransferCalculations()
self.hours = list(range(24))
self.valid_latitudes = ['24N', '36N', '48N']
self.valid_months = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']
self.valid_wall_groups = ['A', 'B', 'C', 'D']
self.valid_roof_groups = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
def validate_latitude(self, latitude: Any) -> str:
"""
Validate and normalize latitude input.
Args:
latitude: Latitude input (str, float, or other)
Returns:
Valid latitude string ('24N', '36N', or '48N')
"""
try:
if not isinstance(latitude, str):
latitude = str(latitude)
latitude = latitude.strip().upper()
# Handle concatenated formats like '24N_JUL'
if '_' in latitude:
parts = latitude.split('_')
if len(parts) > 1:
lat_part = parts[0]
if st.session_state.get('debug_mode', False):
logger.warning(f"Detected concatenated input: {latitude}. Using latitude={lat_part}")
latitude = lat_part
# Handle formats like '31.973N', '1_31.973N'
if '.' in latitude or any(c.isdigit() for c in latitude):
num_part = ''.join(c for c in latitude if c.isdigit() or c == '.')
try:
lat_val = float(num_part)
if lat_val <= 30:
return '24N'
elif lat_val <= 42:
return '36N'
else:
return '48N'
except ValueError:
if st.session_state.get('debug_mode', False):
logger.warning(f"Cannot parse latitude: {latitude}. Defaulting to '24N'")
return '24N'
if latitude not in self.valid_latitudes:
if st.session_state.get('debug_mode', False):
logger.warning(f"Invalid latitude: {latitude}. Defaulting to '24N'")
return '24N'
return latitude
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"Error validating latitude {latitude}: {str(e)}")
return '24N'
def validate_month(self, month: Any) -> str:
"""
Validate and normalize month input.
Args:
month: Month input (str or other)
Returns:
Valid month string in uppercase
"""
try:
if not isinstance(month, str):
month = str(month)
month_upper = month.strip().upper()
if month_upper not in self.valid_months:
if st.session_state.get('debug_mode', False):
logger.warning(f"Invalid month: {month}. Defaulting to 'JUL'")
return 'JUL'
return month_upper
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"Error validating month {month}: {str(e)}")
return 'JUL'
def validate_hour(self, hour: Any) -> int:
"""
Validate and normalize hour input.
Args:
hour: Hour input (int, float, or other)
Returns:
Valid hour integer (0-23)
"""
try:
hour = int(float(str(hour)))
if not 0 <= hour <= 23:
if st.session_state.get('debug_mode', False):
logger.warning(f"Invalid hour: {hour}. Defaulting to 15")
return 15
return hour
except (ValueError, TypeError):
if st.session_state.get('debug_mode', False):
logger.warning(f"Invalid hour format: {hour}. Defaulting to 15")
return 15
def calculate_hourly_cooling_loads(
self,
building_components: Dict[str, List[Any]],
outdoor_conditions: Dict[str, Any],
indoor_conditions: Dict[str, Any],
internal_loads: Dict[str, Any],
building_volume: float
) -> Dict[str, Any]:
"""
Calculate hourly cooling loads for all components.
Args:
building_components: Dictionary of building components
outdoor_conditions: Outdoor weather conditions
indoor_conditions: Indoor design conditions
internal_loads: Internal heat gains
building_volume: Building volume in cubic meters
Returns:
Dictionary containing hourly cooling loads
"""
hourly_loads = {
'walls': {h: 0.0 for h in range(1, 25)},
'roofs': {h: 0.0 for h in range(1, 25)},
'windows_conduction': {h: 0.0 for h in range(1, 25)},
'windows_solar': {h: 0.0 for h in range(1, 25)},
'doors': {h: 0.0 for h in range(1, 25)},
'people_sensible': {h: 0.0 for h in range(1, 25)},
'people_latent': {h: 0.0 for h in range(1, 25)},
'lights': {h: 0.0 for h in range(1, 25)},
'equipment_sensible': {h: 0.0 for h in range(1, 25)},
'equipment_latent': {h: 0.0 for h in range(1, 25)},
'infiltration_sensible': {h: 0.0 for h in range(1, 25)},
'infiltration_latent': {h: 0.0 for h in range(1, 25)},
'ventilation_sensible': {h: 0.0 for h in range(1, 25)},
'ventilation_latent': {h: 0.0 for h in range(1, 25)}
}
try:
# Validate inputs
latitude = self.validate_latitude(outdoor_conditions.get('latitude', '24N'))
month = self.validate_month(outdoor_conditions.get('month', 'JUL'))
if st.session_state.get('debug_mode', False):
logger.debug(f"calculate_hourly_cooling_loads: latitude={latitude}, month={month}, outdoor_conditions={outdoor_conditions}")
# Calculate loads for walls
for wall in building_components.get('walls', []):
for hour in range(24):
load = self.calculate_wall_cooling_load(
wall=wall,
outdoor_temp=outdoor_conditions['temperature'],
indoor_temp=indoor_conditions['temperature'],
month=month,
hour=hour,
latitude=latitude
)
hourly_loads['walls'][hour + 1] += load
# Calculate loads for roofs
for roof in building_components.get('roofs', []):
for hour in range(24):
load = self.calculate_roof_cooling_load(
roof=roof,
outdoor_temp=outdoor_conditions['temperature'],
indoor_temp=indoor_conditions['temperature'],
month=month,
hour=hour,
latitude=latitude
)
hourly_loads['roofs'][hour + 1] += load
# Calculate loads for windows
for window in building_components.get('windows', []):
for hour in range(24):
load_dict = self.calculate_window_cooling_load(
window=window,
outdoor_temp=outdoor_conditions['temperature'],
indoor_temp=indoor_conditions['temperature'],
month=month,
hour=hour,
latitude=latitude,
shading_coefficient=window.shading_coefficient
)
hourly_loads['windows_conduction'][hour + 1] += load_dict['conduction']
hourly_loads['windows_solar'][hour + 1] += load_dict['solar']
# Calculate loads for doors
for door in building_components.get('doors', []):
for hour in range(24):
load = self.calculate_door_cooling_load(
door=door,
outdoor_temp=outdoor_conditions['temperature'],
indoor_temp=indoor_conditions['temperature']
)
hourly_loads['doors'][hour + 1] += load
# Calculate internal loads
for hour in range(24):
# People loads
people_load = self.calculate_people_cooling_load(
num_people=internal_loads['people']['number'],
activity_level=internal_loads['people']['activity_level'],
hour=hour
)
hourly_loads['people_sensible'][hour + 1] += people_load['sensible']
hourly_loads['people_latent'][hour + 1] += people_load['latent']
# Lighting loads
lights_load = self.calculate_lights_cooling_load(
power=internal_loads['lights']['power'],
use_factor=internal_loads['lights']['use_factor'],
special_allowance=internal_loads['lights']['special_allowance'],
hour=hour
)
hourly_loads['lights'][hour + 1] += lights_load
# Equipment loads
equipment_load = self.calculate_equipment_cooling_load(
power=internal_loads['equipment']['power'],
use_factor=internal_loads['equipment']['use_factor'],
radiation_factor=internal_loads['equipment']['radiation_factor'],
hour=hour
)
hourly_loads['equipment_sensible'][hour + 1] += equipment_load['sensible']
hourly_loads['equipment_latent'][hour + 1] += equipment_load['latent']
# Infiltration loads
infiltration_load = self.calculate_infiltration_cooling_load(
flow_rate=internal_loads['infiltration']['flow_rate'],
building_volume=building_volume,
outdoor_temp=outdoor_conditions['temperature'],
outdoor_rh=outdoor_conditions['relative_humidity'],
indoor_temp=indoor_conditions['temperature'],
indoor_rh=indoor_conditions['relative_humidity']
)
hourly_loads['infiltration_sensible'][hour + 1] += infiltration_load['sensible']
hourly_loads['infiltration_latent'][hour + 1] += infiltration_load['latent']
# Ventilation loads
ventilation_load = self.calculate_ventilation_cooling_load(
flow_rate=internal_loads['ventilation']['flow_rate'],
outdoor_temp=outdoor_conditions['temperature'],
outdoor_rh=outdoor_conditions['relative_humidity'],
indoor_temp=indoor_conditions['temperature'],
indoor_rh=indoor_conditions['relative_humidity']
)
hourly_loads['ventilation_sensible'][hour + 1] += ventilation_load['sensible']
hourly_loads['ventilation_latent'][hour + 1] += ventilation_load['latent']
return hourly_loads
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"Error in calculate_hourly_cooling_loads: {str(e)}")
raise Exception(f"Error in calculate_hourly_cooling_loads: {str(e)}")
def calculate_design_cooling_load(self, hourly_loads: Dict[str, Any]) -> Dict[str, Any]:
"""
Calculate design cooling load based on peak hourly loads.
Args:
hourly_loads: Dictionary of hourly cooling loads
Returns:
Dictionary containing design cooling loads
"""
try:
design_loads = {}
total_loads = []
for hour in range(1, 25):
total_load = sum([
hourly_loads['walls'][hour],
hourly_loads['roofs'][hour],
hourly_loads['windows_conduction'][hour],
hourly_loads['windows_solar'][hour],
hourly_loads['doors'][hour],
hourly_loads['people_sensible'][hour],
hourly_loads['people_latent'][hour],
hourly_loads['lights'][hour],
hourly_loads['equipment_sensible'][hour],
hourly_loads['equipment_latent'][hour],
hourly_loads['infiltration_sensible'][hour],
hourly_loads['infiltration_latent'][hour],
hourly_loads['ventilation_sensible'][hour],
hourly_loads['ventilation_latent'][hour]
])
total_loads.append(total_load)
design_hour = range(1, 25)[np.argmax(total_loads)]
design_loads = {
'design_hour': design_hour,
'walls': hourly_loads['walls'][design_hour],
'roofs': hourly_loads['roofs'][design_hour],
'windows_conduction': hourly_loads['windows_conduction'][design_hour],
'windows_solar': hourly_loads['windows_solar'][design_hour],
'doors': hourly_loads['doors'][design_hour],
'people_sensible': hourly_loads['people_sensible'][design_hour],
'people_latent': hourly_loads['people_latent'][design_hour],
'lights': hourly_loads['lights'][design_hour],
'equipment_sensible': hourly_loads['equipment_sensible'][design_hour],
'equipment_latent': hourly_loads['equipment_latent'][design_hour],
'infiltration_sensible': hourly_loads['infiltration_sensible'][design_hour],
'infiltration_latent': hourly_loads['infiltration_latent'][design_hour],
'ventilation_sensible': hourly_loads['ventilation_sensible'][design_hour],
'ventilation_latent': hourly_loads['ventilation_latent'][design_hour]
}
return design_loads
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"Error in calculate_design_cooling_load: {str(e)}")
raise Exception(f"Error in calculate_design_cooling_load: {str(e)}")
def calculate_cooling_load_summary(self, design_loads: Dict[str, Any]) -> Dict[str, float]:
"""
Calculate summary of cooling loads.
Args:
design_loads: Dictionary of design cooling loads
Returns:
Dictionary containing cooling load summary
"""
try:
total_sensible = (
design_loads['walls'] +
design_loads['roofs'] +
design_loads['windows_conduction'] +
design_loads['windows_solar'] +
design_loads['doors'] +
design_loads['people_sensible'] +
design_loads['lights'] +
design_loads['equipment_sensible'] +
design_loads['infiltration_sensible'] +
design_loads['ventilation_sensible']
)
total_latent = (
design_loads['people_latent'] +
design_loads['equipment_latent'] +
design_loads['infiltration_latent'] +
design_loads['ventilation_latent']
)
total = total_sensible + total_latent
return {
'total_sensible': total_sensible,
'total_latent': total_latent,
'total': total
}
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"Error in calculate_cooling_load_summary: {str(e)}")
raise Exception(f"Error in calculate_cooling_load_summary: {str(e)}")
def calculate_wall_cooling_load(
self,
wall: Wall,
outdoor_temp: float,
indoor_temp: float,
month: str,
hour: int,
latitude: str
) -> float:
"""
Calculate cooling load for a wall.
Args:
wall: Wall component
outdoor_temp: Outdoor temperature (°C)
indoor_temp: Indoor temperature (°C)
month: Design month
hour: Hour of the day
latitude: Latitude (e.g., '24N')
Returns:
Cooling load in Watts
"""
try:
latitude = self.validate_latitude(latitude)
month = self.validate_month(month)
hour = self.validate_hour(hour)
if st.session_state.get('debug_mode', False):
logger.debug(f"calculate_wall_cooling_load: latitude={latitude}, month={month}, hour={hour}, wall_group={wall.wall_group}, orientation={wall.orientation.value}")
# Validate wall_group
wall_group = str(wall.wall_group).upper()
if wall_group not in self.valid_wall_groups:
numeric_map = {'1': 'A', '2': 'B', '3': 'C', '4': 'D'}
if wall_group in numeric_map:
wall_group = numeric_map[wall_group]
if st.session_state.get('debug_mode', False):
logger.info(f"Mapped wall_group {wall.wall_group} to {wall_group}")
else:
if st.session_state.get('debug_mode', False):
logger.warning(f"Invalid wall group: {wall_group}. Defaulting to 'A'")
wall_group = 'A'
try:
cltd = self.ashrae_tables.calculate_corrected_cltd_wall(
wall_group=wall_group,
orientation=wall.orientation.value,
hour=hour,
color='Dark',
month=month,
latitude=latitude,
indoor_temp=indoor_temp,
outdoor_temp=outdoor_temp
)
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"calculate_corrected_cltd_wall failed for wall_group={wall_group}: {str(e)}")
logger.warning("Using default CLTD=8.0°C")
cltd = 8.0
load = wall.u_value * wall.area * cltd
if st.session_state.get('debug_mode', False):
logger.debug(f"Wall load: u_value={wall.u_value}, area={wall.area}, cltd={cltd}, load={load}")
return max(load, 0.0)
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"Error in calculate_wall_cooling_load: {str(e)}")
raise Exception(f"Error in calculate_wall_cooling_load: {str(e)}")
def calculate_roof_cooling_load(
self,
roof: Roof,
outdoor_temp: float,
indoor_temp: float,
month: str,
hour: int,
latitude: str
) -> float:
"""
Calculate cooling load for a roof.
Args:
roof: Roof component
outdoor_temp: Outdoor temperature (°C)
indoor_temp: Indoor temperature (°C)
month: Design month
hour: Hour of the day
latitude: Latitude (e.g., '24N')
Returns:
Cooling load in Watts
"""
try:
latitude = self.validate_latitude(latitude)
month = self.validate_month(month)
hour = self.validate_hour(hour)
if st.session_state.get('debug_mode', False):
logger.debug(f"calculate_roof_cooling_load: latitude={latitude}, month={month}, hour={hour}, roof_group={roof.roof_group}")
roof_group = str(roof.roof_group).upper()
if roof_group not in self.valid_roof_groups:
if st.session_state.get('debug_mode', False):
logger.warning(f"Invalid roof group: {roof_group}. Defaulting to 'A'")
roof_group = 'A'
try:
cltd = self.ashrae_tables.calculate_corrected_cltd_roof(
roof_group=roof_group,
hour=hour,
color='Dark',
month=month,
latitude=latitude,
indoor_temp=indoor_temp,
outdoor_temp=outdoor_temp
)
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"calculate_corrected_cltd_roof failed for roof_group={roof_group}: {str(e)}")
logger.warning("Using default CLTD=8.0°C")
cltd = 8.0
load = roof.u_value * roof.area * cltd
if st.session_state.get('debug_mode', False):
logger.debug(f"Roof load: u_value={roof.u_value}, area={roof.area}, cltd={cltd}, load={load}")
return max(load, 0.0)
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"Error in calculate_roof_cooling_load: {str(e)}")
raise Exception(f"Error in calculate_roof_cooling_load: {str(e)}")
def calculate_window_cooling_load(
self,
window: Window,
outdoor_temp: float,
indoor_temp: float,
month: str,
hour: int,
latitude: str,
shading_coefficient: float
) -> Dict[str, float]:
"""
Calculate cooling load for a window (conduction and solar).
Args:
window: Window component
outdoor_temp: Outdoor temperature (°C)
indoor_temp: Indoor temperature (°C)
month: Design month
hour: Hour of the day
latitude: Latitude (e.g., '24N')
shading_coefficient: Shading coefficient
Returns:
Dictionary with conduction, solar, and total loads in Watts
"""
try:
latitude = self.validate_latitude(latitude)
month = self.validate_month(month)
hour = self.validate_hour(hour)
if st.session_state.get('debug_mode', False):
logger.debug(f"calculate_window_cooling_load: latitude={latitude}, month={month}, hour={hour}, orientation={window.orientation.value}")
# Conduction load
cltd = outdoor_temp - indoor_temp # Simplified for windows
conduction_load = window.u_value * window.area * cltd
# Solar load
scl_latitude = f"{latitude}_{month}"
try:
scl = self.ashrae_tables.get_scl(
latitude=scl_latitude,
month=month,
orientation=window.orientation.value,
hour=hour
)
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"get_scl failed for latitude={scl_latitude}, month={month}, orientation={window.orientation.value}: {str(e)}")
logger.warning("Using default SCL=100 W/m²")
scl = 100.0
solar_load = window.area * window.shgc * shading_coefficient * scl
total_load = conduction_load + solar_load
if st.session_state.get('debug_mode', False):
logger.debug(f"Window load: conduction={conduction_load}, solar={solar_load}, total={total_load}")
return {
'conduction': max(conduction_load, 0.0),
'solar': max(solar_load, 0.0),
'total': max(total_load, 0.0)
}
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"Error in calculate_window_cooling_load: {str(e)}")
raise Exception(f"Error in calculate_window_cooling_load: {str(e)}")
def calculate_door_cooling_load(
self,
door: Door,
outdoor_temp: float,
indoor_temp: float
) -> float:
"""
Calculate cooling load for a door.
Args:
door: Door component
outdoor_temp: Outdoor temperature (°C)
indoor_temp: Indoor temperature (°C)
Returns:
Cooling load in Watts
"""
try:
if st.session_state.get('debug_mode', False):
logger.debug(f"calculate_door_cooling_load: u_value={door.u_value}, area={door.area}")
cltd = outdoor_temp - indoor_temp
load = door.u_value * door.area * cltd
if st.session_state.get('debug_mode', False):
logger.debug(f"Door load: cltd={cltd}, load={load}")
return max(load, 0.0)
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"Error in calculate_door_cooling_load: {str(e)}")
raise Exception(f"Error in calculate_door_cooling_load: {str(e)}")
def calculate_people_cooling_load(
self,
num_people: int,
activity_level: str,
hour: int
) -> Dict[str, float]:
"""
Calculate cooling load from people.
Args:
num_people: Number of people
activity_level: Activity level ('Seated/Resting', 'Light Work', etc.)
hour: Hour of the day
Returns:
Dictionary with sensible and latent loads in Watts
"""
try:
hour = self.validate_hour(hour)
if st.session_state.get('debug_mode', False):
logger.debug(f"calculate_people_cooling_load
System: You are Grok 3 built by xAI.
The current date is April 27, 2025.
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.
### Analysis Recap
- **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`.
- **Fix**: Replace the incorrect method calls with `self.heat_transfer.psychrometrics.humidity_ratio`.
- **Additional Checks**:
- The previous response included a temporary `calculate_humidity_ratio` method in `cooling_load.py`, which is no longer needed and has been removed.
- All other methods, validations, and logging remain unchanged to maintain compatibility with `main.py`, `ashrae_tables.py`, and `heat_transfer.py`.
- The artifact ID (`c47f4e86-6a73-4d0d-a7c2-2656f6dc55a2`) is retained as this is an update to the same file.
### Updated `cooling_load.py`
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.
<xaiArtifact artifact_id="c47f4e86-6a73-4d0d-a7c2-2656f6dc55a2" artifact_version_id="430e5082-16c0-4456-a8c3-b766fb73f165" title="cooling_load.py" contentType="text/python">
"""
Cooling load calculation module for HVAC Load Calculator.
Based on ASHRAE steady-state calculation methods.
Author: Dr Majed Abuseif
Date: April 2025
Version: 1.0.3
"""
import streamlit as st
from typing import Dict, List, Any, Optional, Tuple
import numpy as np
from datetime import datetime
import logging
from data.ashrae_tables import ASHRAETables
from utils.heat_transfer import HeatTransferCalculations
from app.component_selection import Wall, Roof, Window, Door, Orientation
# Set up logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
class CoolingLoadCalculator:
"""Class for cooling load calculations."""
def __init__(self):
"""Initialize cooling load calculator."""
self.ashrae_tables = ASHRAETables()
self.heat_transfer = HeatTransferCalculations()
self.hours = list(range(24))
self.valid_latitudes = ['24N', '36N', '48N']
self.valid_months = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']
self.valid_wall_groups = ['A', 'B', 'C', 'D']
self.valid_roof_groups = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
def validate_latitude(self, latitude: Any) -> str:
"""
Validate and normalize latitude input.
Args:
latitude: Latitude input (str, float, or other)
Returns:
Valid latitude string ('24N', '36N', or '48N')
"""
try:
if not isinstance(latitude, str):
latitude = str(latitude)
latitude = latitude.strip().upper()
# Handle concatenated formats like '24N_JUL'
if '_' in latitude:
parts = latitude.split('_')
if len(parts) > 1:
lat_part = parts[0]
if st.session_state.get('debug_mode', False):
logger.warning(f"Detected concatenated input: {latitude}. Using latitude={lat_part}")
latitude = lat_part
# Handle formats like '31.973N', '1_31.973N'
if '.' in latitude or any(c.isdigit() for c in latitude):
num_part = ''.join(c for c in latitude if c.isdigit() or c == '.')
try:
lat_val = float(num_part)
if lat_val <= 30:
return '24N'
elif lat_val <= 42:
return '36N'
else:
return '48N'
except ValueError:
if st.session_state.get('debug_mode', False):
logger.warning(f"Cannot parse latitude: {latitude}. Defaulting to '24N'")
return '24N'
if latitude not in self.valid_latitudes:
if st.session_state.get('debug_mode', False):
logger.warning(f"Invalid latitude: {latitude}. Defaulting to '24N'")
return '24N'
return latitude
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"Error validating latitude {latitude}: {str(e)}")
return '24N'
def validate_month(self, month: Any) -> str:
"""
Validate and normalize month input.
Args:
month: Month input (str or other)
Returns:
Valid month string in uppercase
"""
try:
if not isinstance(month, str):
month = str(month)
month_upper = month.strip().upper()
if month_upper not in self.valid_months:
if st.session_state.get('debug_mode', False):
logger.warning(f"Invalid month: {month}. Defaulting to 'JUL'")
return 'JUL'
return month_upper
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"Error validating month {month}: {str(e)}")
return 'JUL'
def validate_hour(self, hour: Any) -> int:
"""
Validate and normalize hour input.
Args:
hour: Hour input (int, float, or other)
Returns:
Valid hour integer (0-23)
"""
try:
hour = int(float(str(hour)))
if not 0 <= hour <= 23:
if st.session_state.get('debug_mode', False):
logger.warning(f"Invalid hour: {hour}. Defaulting to 15")
return 15
return hour
except (ValueError, TypeError):
if st.session_state.get('debug_mode', False):
logger.warning(f"Invalid hour format: {hour}. Defaulting to 15")
return 15
def calculate_hourly_cooling_loads(
self,
building_components: Dict[str, List[Any]],
outdoor_conditions: Dict[str, Any],
indoor_conditions: Dict[str, Any],
internal_loads: Dict[str, Any],
building_volume: float
) -> Dict[str, Any]:
"""
Calculate hourly cooling loads for all components.
Args:
building_components: Dictionary of building components
outdoor_conditions: Outdoor weather conditions
indoor_conditions: Indoor design conditions
internal_loads: Internal heat gains
building_volume: Building volume in cubic meters
Returns:
Dictionary containing hourly cooling loads
"""
hourly_loads = {
'walls': {h: 0.0 for h in range(1, 25)},
'roofs': {h: 0.0 for h in range(1, 25)},
'windows_conduction': {h: 0.0 for h in range(1, 25)},
'windows_solar': {h: 0.0 for h in range(1, 25)},
'doors': {h: 0.0 for h in range(1, 25)},
'people_sensible': {h: 0.0 for h in range(1, 25)},
'people_latent': {h: 0.0 for h in range(1, 25)},
'lights': {h: 0.0 for h in range(1, 25)},
'equipment_sensible': {h: 0.0 for h in range(1, 25)},
'equipment_latent': {h: 0.0 for h in range(1, 25)},
'infiltration_sensible': {h: 0.0 for h in range(1, 25)},
'infiltration_latent': {h: 0.0 for h in range(1, 25)},
'ventilation_sensible': {h: 0.0 for h in range(1, 25)},
'ventilation_latent': {h: 0.0 for h in range(1, 25)}
}
try:
# Validate inputs
latitude = self.validate_latitude(outdoor_conditions.get('latitude', '24N'))
month = self.validate_month(outdoor_conditions.get('month', 'JUL'))
if st.session_state.get('debug_mode', False):
logger.debug(f"calculate_hourly_cooling_loads: latitude={latitude}, month={month}, outdoor_conditions={outdoor_conditions}")
# Calculate loads for walls
for wall in building_components.get('walls', []):
for hour in range(24):
load = self.calculate_wall_cooling_load(
wall=wall,
outdoor_temp=outdoor_conditions['temperature'],
indoor_temp=indoor_conditions['temperature'],
month=month,
hour=hour,
latitude=latitude
)
hourly_loads['walls'][hour + 1] += load
# Calculate loads for roofs
for roof in building_components.get('roofs', []):
for hour in range(24):
load = self.calculate_roof_cooling_load(
roof=roof,
outdoor_temp=outdoor_conditions['temperature'],
indoor_temp=indoor_conditions['temperature'],
month=month,
hour=hour,
latitude=latitude
)
hourly_loads['roofs'][hour + 1] += load
# Calculate loads for windows
for window in building_components.get('windows', []):
for hour in range(24):
load_dict = self.calculate_window_cooling_load(
window=window,
outdoor_temp=outdoor_conditions['temperature'],
indoor_temp=indoor_conditions['temperature'],
month=month,
hour=hour,
latitude=latitude,
shading_coefficient=window.shading_coefficient
)
hourly_loads['windows_conduction'][hour + 1] += load_dict['conduction']
hourly_loads['windows_solar'][hour + 1] += load_dict['solar']
# Calculate loads for doors
for door in building_components.get('doors', []):
for hour in range(24):
load = self.calculate_door_cooling_load(
door=door,
outdoor_temp=outdoor_conditions['temperature'],
indoor_temp=indoor_conditions['temperature']
)
hourly_loads['doors'][hour + 1] += load
# Calculate internal loads
for hour in range(24):
# People loads
people_load = self.calculate_people_cooling_load(
num_people=internal_loads['people']['number'],
activity_level=internal_loads['people']['activity_level'],
hour=hour
)
hourly_loads['people_sensible'][hour + 1] += people_load['sensible']
hourly_loads['people_latent'][hour + 1] += people_load['latent']
# Lighting loads
lights_load = self.calculate_lights_cooling_load(
power=internal_loads['lights']['power'],
use_factor=internal_loads['lights']['use_factor'],
special_allowance=internal_loads['lights']['special_allowance'],
hour=hour
)
hourly_loads['lights'][hour + 1] += lights_load
# Equipment loads
equipment_load = self.calculate_equipment_cooling_load(
power=internal_loads['equipment']['power'],
use_factor=internal_loads['equipment']['use_factor'],
radiation_factor=internal_loads['equipment']['radiation_factor'],
hour=hour
)
hourly_loads['equipment_sensible'][hour + 1] += equipment_load['sensible']
hourly_loads['equipment_latent'][hour + 1] += equipment_load['latent']
# Infiltration loads
infiltration_load = self.calculate_infiltration_cooling_load(
flow_rate=internal_loads['infiltration']['flow_rate'],
building_volume=building_volume,
outdoor_temp=outdoor_conditions['temperature'],
outdoor_rh=outdoor_conditions['relative_humidity'],
indoor_temp=indoor_conditions['temperature'],
indoor_rh=indoor_conditions['relative_humidity']
)
hourly_loads['infiltration_sensible'][hour + 1] += infiltration_load['sensible']
hourly_loads['infiltration_latent'][hour + 1] += infiltration_load['latent']
# Ventilation loads
ventilation_load = self.calculate_ventilation_cooling_load(
flow_rate=internal_loads['ventilation']['flow_rate'],
outdoor_temp=outdoor_conditions['temperature'],
outdoor_rh=outdoor_conditions['relative_humidity'],
indoor_temp=indoor_conditions['temperature'],
indoor_rh=indoor_conditions['relative_humidity']
)
hourly_loads['ventilation_sensible'][hour + 1] += ventilation_load['sensible']
hourly_loads['ventilation_latent'][hour + 1] += ventilation_load['latent']
return hourly_loads
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"Error in calculate_hourly_cooling_loads: {str(e)}")
raise Exception(f"Error in calculate_hourly_cooling_loads: {str(e)}")
def calculate_design_cooling_load(self, hourly_loads: Dict[str, Any]) -> Dict[str, Any]:
"""
Calculate design cooling load based on peak hourly loads.
Args:
hourly_loads: Dictionary of hourly cooling loads
Returns:
Dictionary containing design cooling loads
"""
try:
design_loads = {}
total_loads = []
for hour in range(1, 25):
total_load = sum([
hourly_loads['walls'][hour],
hourly_loads['roofs'][hour],
hourly_loads['windows_conduction'][hour],
hourly_loads['windows_solar'][hour],
hourly_loads['doors'][hour],
hourly_loads['people_sensible'][hour],
hourly_loads['people_latent'][hour],
hourly_loads['lights'][hour],
hourly_loads['equipment_sensible'][hour],
hourly_loads['equipment_latent'][hour],
hourly_loads['infiltration_sensible'][hour],
hourly_loads['infiltration_latent'][hour],
hourly_loads['ventilation_sensible'][hour],
hourly_loads['ventilation_latent'][hour]
])
total_loads.append(total_load)
design_hour = range(1, 25)[np.argmax(total_loads)]
design_loads = {
'design_hour': design_hour,
'walls': hourly_loads['walls'][design_hour],
'roofs': hourly_loads['roofs'][design_hour],
'windows_conduction': hourly_loads['windows_conduction'][design_hour],
'windows_solar': hourly_loads['windows_solar'][design_hour],
'doors': hourly_loads['doors'][design_hour],
'people_sensible': hourly_loads['people_sensible'][design_hour],
'people_latent': hourly_loads['people_latent'][design_hour],
'lights': hourly_loads['lights'][design_hour],
'equipment_sensible': hourly_loads['equipment_sensible'][design_hour],
'equipment_latent': hourly_loads['equipment_latent'][design_hour],
'infiltration_sensible': hourly_loads['infiltration_sensible'][design_hour],
'infiltration_latent': hourly_loads['infiltration_latent'][design_hour],
'ventilation_sensible': hourly_loads['ventilation_sensible'][design_hour],
'ventilation_latent': hourly_loads['ventilation_latent'][design_hour]
}
return design_loads
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"Error in calculate_design_cooling_load: {str(e)}")
raise Exception(f"Error in calculate_design_cooling_load: {str(e)}")
def calculate_cooling_load_summary(self, design_loads: Dict[str, Any]) -> Dict[str, float]:
"""
Calculate summary of cooling loads.
Args:
design_loads: Dictionary of design cooling loads
Returns:
Dictionary containing cooling load summary
"""
try:
total_sensible = (
design_loads['walls'] +
design_loads['roofs'] +
design_loads['windows_conduction'] +
design_loads['windows_solar'] +
design_loads['doors'] +
design_loads['people_sensible'] +
design_loads['lights'] +
design_loads['equipment_sensible'] +
design_loads['infiltration_sensible'] +
design_loads['ventilation_sensible']
)
total_latent = (
design_loads['people_latent'] +
design_loads['equipment_latent'] +
design_loads['infiltration_latent'] +
design_loads['ventilation_latent']
)
total = total_sensible + total_latent
return {
'total_sensible': total_sensible,
'total_latent': total_latent,
'total': total
}
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"Error in calculate_cooling_load_summary: {str(e)}")
raise Exception(f"Error in calculate_cooling_load_summary: {str(e)}")
def calculate_wall_cooling_load(
self,
wall: Wall,
outdoor_temp: float,
indoor_temp: float,
month: str,
hour: int,
latitude: str
) -> float:
"""
Calculate cooling load for a wall.
Args:
wall: Wall component
outdoor_temp: Outdoor temperature (°C)
indoor_temp: Indoor temperature (°C)
month: Design month
hour: Hour of the day
latitude: Latitude (e.g., '24N')
Returns:
Cooling load in Watts
"""
try:
latitude = self.validate_latitude(latitude)
month = self.validate_month(month)
hour = self.validate_hour(hour)
if st.session_state.get('debug_mode', False):
logger.debug(f"calculate_wall_cooling_load: latitude={latitude}, month={month}, hour={hour}, wall_group={wall.wall_group}, orientation={wall.orientation.value}")
# Validate wall_group
wall_group = str(wall.wall_group).upper()
if wall_group not in self.valid_wall_groups:
numeric_map = {'1': 'A', '2': 'B', '3': 'C', '4': 'D'}
if wall_group in numeric_map:
wall_group = numeric_map[wall_group]
if st.session_state.get('debug_mode', False):
logger.info(f"Mapped wall_group {wall.wall_group} to {wall_group}")
else:
if st.session_state.get('debug_mode', False):
logger.warning(f"Invalid wall group: {wall_group}. Defaulting to 'A'")
wall_group = 'A'
try:
cltd = self.ashrae_tables.calculate_corrected_cltd_wall(
wall_group=wall_group,
orientation=wall.orientation.value,
hour=hour,
color='Dark',
month=month,
latitude=latitude,
indoor_temp=indoor_temp,
outdoor_temp=outdoor_temp
)
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"calculate_corrected_cltd_wall failed for wall_group={wall_group}: {str(e)}")
logger.warning("Using default CLTD=8.0°C")
cltd = 8.0
load = wall.u_value * wall.area * cltd
if st.session_state.get('debug_mode', False):
logger.debug(f"Wall load: u_value={wall.u_value}, area={wall.area}, cltd={cltd}, load={load}")
return max(load, 0.0)
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"Error in calculate_wall_cooling_load: {str(e)}")
raise Exception(f"Error in calculate_wall_cooling_load: {str(e)}")
def calculate_roof_cooling_load(
self,
roof: Roof,
outdoor_temp: float,
indoor_temp: float,
month: str,
hour: int,
latitude: str
) -> float:
"""
Calculate cooling load for a roof.
Args:
roof: Roof component
outdoor_temp: Outdoor temperature (°C)
indoor_temp: Indoor temperature (°C)
month: Design month
hour: Hour of the day
latitude: Latitude (e.g., '24N')
Returns:
Cooling load in Watts
"""
try:
latitude = self.validate_latitude(latitude)
month = self.validate_month(month)
hour = self.validate_hour(hour)
if st.session_state.get('debug_mode', False):
logger.debug(f"calculate_roof_cooling_load: latitude={latitude}, month={month}, hour={hour}, roof_group={roof.roof_group}")
roof_group = str(roof.roof_group).upper()
if roof_group not in self.valid_roof_groups:
if st.session_state.get('debug_mode', False):
logger.warning(f"Invalid roof group: {roof_group}. Defaulting to 'A'")
roof_group = 'A'
try:
cltd = self.ashrae_tables.calculate_corrected_cltd_roof(
roof_group=roof_group,
hour=hour,
color='Dark',
month=month,
latitude=latitude,
indoor_temp=indoor_temp,
outdoor_temp=outdoor_temp
)
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"calculate_corrected_cltd_roof failed for roof_group={roof_group}: {str(e)}")
logger.warning("Using default CLTD=8.0°C")
cltd = 8.0
load = roof.u_value * roof.area * cltd
if st.session_state.get('debug_mode', False):
logger.debug(f"Roof load: u_value={roof.u_value}, area={roof.area}, cltd={cltd}, load={load}")
return max(load, 0.0)
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"Error in calculate_roof_cooling_load: {str(e)}")
raise Exception(f"Error in calculate_roof_cooling_load: {str(e)}")
def calculate_window_cooling_load(
self,
window: Window,
outdoor_temp: float,
indoor_temp: float,
month: str,
hour: int,
latitude: str,
shading_coefficient: float
) -> Dict[str, float]:
"""
Calculate cooling load for a window (conduction and solar).
Args:
window: Window component
outdoor_temp: Outdoor temperature (°C)
indoor_temp: Indoor temperature (°C)
month: Design month
hour: Hour of the day
latitude: Latitude (e.g., '24N')
shading_coefficient: Shading coefficient
Returns:
Dictionary with conduction, solar, and total loads in Watts
"""
try:
latitude = self.validate_latitude(latitude)
month = self.validate_month(month)
hour = self.validate_hour(hour)
if st.session_state.get('debug_mode', False):
logger.debug(f"calculate_window_cooling_load: latitude={latitude}, month={month}, hour={hour}, orientation={window.orientation.value}")
# Conduction load
cltd = outdoor_temp - indoor_temp # Simplified for windows
conduction_load = window.u_value * window.area * cltd
# Solar load
scl_latitude = f"{latitude}_{month}"
try:
scl = self.ashrae_tables.get_scl(
latitude=scl_latitude,
month=month,
orientation=window.orientation.value,
hour=hour
)
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"get_scl failed for latitude={scl_latitude}, month={month}, orientation={window.orientation.value}: {str(e)}")
logger.warning("Using default SCL=100 W/m²")
scl = 100.0
solar_load = window.area * window.shgc * shading_coefficient * scl
total_load = conduction_load + solar_load
if st.session_state.get('debug_mode', False):
logger.debug(f"Window load: conduction={conduction_load}, solar={solar_load}, total={total_load}")
return {
'conduction': max(conduction_load, 0.0),
'solar': max(solar_load, 0.0),
'total': max(total_load, 0.0)
}
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"Error in calculate_window_cooling_load: {str(e)}")
raise Exception(f"Error in calculate_window_cooling_load: {str(e)}")
def calculate_door_cooling_load(
self,
door: Door,
outdoor_temp: float,
indoor_temp: float
) -> float:
"""
Calculate cooling load for a door.
Args:
door: Door component
outdoor_temp: Outdoor temperature (°C)
indoor_temp: Indoor temperature (°C)
Returns:
Cooling load in Watts
"""
try:
if st.session_state.get('debug_mode', False):
logger.debug(f"calculate_door_cooling_load: u_value={door.u_value}, area={door.area}")
cltd = outdoor_temp - indoor_temp
load = door.u_value * door.area * cltd
if st.session_state.get('debug_mode', False):
logger.debug(f"Door load: cltd={cltd}, load={load}")
return max(load, 0.0)
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"Error in calculate_door_cooling_load: {str(e)}")
raise Exception(f"Error in calculate_door_cooling_load: {str(e)}")
def calculate_people_cooling_load(
self,
num_people: int,
activity_level: str,
hour: int
) -> Dict[str, float]:
"""
Calculate cooling load from people.
Args:
num_people: Number of people
activity_level: Activity level ('Seated/Resting', 'Light Work', etc.)
hour: Hour of the day
Returns:
Dictionary with sensible and latent loads in Watts
"""
try:
hour = self.validate_hour(hour)
if st.session_state.get('debug_mode', False):
logger.debug(f"calculate_people_cooling_load: num_people={num_people}, activity_level={activity_level}, hour={hour}")
# Sensible and latent heat gains per person (W)
heat_gains = {
'Seated/Resting': {'sensible': 70, 'latent': 45},
'Light Work': {'sensible': 85, 'latent': 65},
'Moderate Work': {'sensible': 100, 'latent': 100},
'Heavy Work': {'sensible': 145, 'latent': 170}
}
gains = heat_gains.get(activity_level, heat_gains['Seated/Resting'])
if activity_level not in heat_gains:
if st.session_state.get('debug_mode', False):
logger.warning(f"Invalid activity_level: {activity_level}. Defaulting to 'Seated/Resting'")
try:
clf = self.ashrae_tables.get_clf_people(
zone_type='A',
hours_occupied='8h',
hour=hour
)
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"get_clf_people failed: {str(e)}")
logger.warning("Using default CLF=0.5")
clf = 0.5
sensible_load = num_people * gains['sensible'] * clf
latent_load = num_people * gains['latent'] # Latent load typically not adjusted by CLF
if st.session_state.get('debug_mode', False):
logger.debug(f"People load: sensible={sensible_load}, latent={latent_load}, clf={clf}")
return {
'sensible': max(sensible_load, 0.0),
'latent': max(latent_load, 0.0)
}
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"Error in calculate_people_cooling_load: {str(e)}")
raise Exception(f"Error in calculate_people_cooling_load: {str(e)}")
def calculate_lights_cooling_load(
self,
power: float,
use_factor: float,
special_allowance: float,
hour: int
) -> float:
"""
Calculate cooling load from lighting.
Args:
power: Total lighting power (W)
use_factor: Usage factor (0.0 to 1.0)
special_allowance: Special allowance factor
hour: Hour of the day
Returns:
Cooling load in Watts
"""
try:
hour = self.validate_hour(hour)
if st.session_state.get('debug_mode', False):
logger.debug(f"calculate_lights_cooling_load: power={power}, use_factor={use_factor}, special_allowance={special_allowance}, hour={hour}")
try:
clf = self.ashrae_tables.get_clf_lights(
zone_type='A',
hours_occupied='8h',
hour=hour
)
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"get_clf_lights failed: {str(e)}")
logger.warning("Using default CLF=0.8")
clf = 0.8
load = power * use_factor * special_allowance * clf
if st.session_state.get('debug_mode', False):
logger.debug(f"Lights load: clf={clf}, load={load}")
return max(load, 0.0)
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"Error in calculate_lights_cooling_load: {str(e)}")
raise Exception(f"Error in calculate_lights_cooling_load: {str(e)}")
def calculate_equipment_cooling_load(
self,
power: float,
use_factor: float,
radiation_factor: float,
hour: int
) -> Dict[str, float]:
"""
Calculate cooling load from equipment.
Args:
power: Total equipment power (W)
use_factor: Usage factor (0.0 to 1.0)
radiation_factor: Radiation factor (0.0 to 1.0)
hour: Hour of the day
Returns:
Dictionary with sensible and latent loads in Watts
"""
try:
hour = self.validate_hour(hour)
if st.session_state.get('debug_mode', False):
logger.debug(f"calculate_equipment_cooling_load: power={power}, use_factor={use_factor}, radiation_factor={radiation_factor}, hour={hour}")
try:
clf = self.ashrae_tables.get_clf_equipment(
zone_type='A',
hours_occupied='8h',
hour=hour
)
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"get_clf_equipment failed: {str(e)}")
logger.warning("Using default CLF=0.7")
clf = 0.7
sensible_load = power * use_factor * radiation_factor * clf
latent_load = power * use_factor * (1 - radiation_factor) # Assume non-radiative is latent
if st.session_state.get('debug_mode', False):
logger.debug(f"Equipment load: sensible={sensible_load}, latent={latent_load}, clf={clf}")
return {
'sensible': max(sensible_load, 0.0),
'latent': max(latent_load, 0.0)
}
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"Error in calculate_equipment_cooling_load: {str(e)}")
raise Exception(f"Error in calculate_equipment_cooling_load: {str(e)}")
def calculate_infiltration_cooling_load(
self,
flow_rate: float,
building_volume: float,
outdoor_temp: float,
outdoor_rh: float,
indoor_temp: float,
indoor_rh: float
) -> Dict[str, float]:
"""
Calculate cooling load from infiltration.
Args:
flow_rate: Infiltration flow rate (m³/s)
building_volume: Building volume (m³)
outdoor_temp: Outdoor temperature (°C)
outdoor_rh: Outdoor relative humidity (%)
indoor_temp: Indoor temperature (°C)
indoor_rh: Indoor relative humidity (%)
Returns:
Dictionary with sensible and latent loads in Watts
"""
try:
if st.session_state.get('debug_mode', False):
logger.debug(f"calculate_infiltration_cooling_load: flow_rate={flow_rate}, building_volume={building_volume}, outdoor_temp={outdoor_temp}, indoor_temp={indoor_temp}")
# Calculate air changes per hour (ACH)
ach = (flow_rate * 3600) / building_volume if building_volume > 0 else 0.5
if ach < 0:
if st.session_state.get('debug_mode', False):
logger.warning(f"Invalid ACH: {ach}. Defaulting to 0.5")
ach = 0.5
# Sensible load: Q = 1.2 * flow_rate * (Tout - Tin)
sensible_load = 1.2 * flow_rate * (outdoor_temp - indoor_temp)
# Latent load using psychrometrics
outdoor_w = self.heat_transfer.psychrometrics.humidity_ratio(outdoor_temp, outdoor_rh)
indoor_w = self.heat_transfer.psychrometrics.humidity_ratio(indoor_temp, indoor_rh)
latent_load = 1.2 * flow_rate * 2500 * (outdoor_w - indoor_w) # 2500 kJ/kg for latent heat
if st.session_state.get('debug_mode', False):
logger.debug(f"Infiltration load: sensible={sensible_load}, latent={latent_load}, ach={ach}, outdoor_w={outdoor_w}, indoor_w={indoor_w}")
return {
'sensible': max(sensible_load, 0.0),
'latent': max(latent_load, 0.0)
}
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"Error in calculate_infiltration_cooling_load: {str(e)}")
raise Exception(f"Error in calculate_infiltration_cooling_load: {str(e)}")
def calculate_ventilation_cooling_load(
self,
flow_rate: float,
outdoor_temp: float,
outdoor_rh: float,
indoor_temp: float,
indoor_rh: float
) -> Dict[str, float]:
"""
Calculate cooling load from ventilation.
Args:
flow_rate: Ventilation flow rate (m³/s)
outdoor_temp: Outdoor temperature (°C)
outdoor_rh: Outdoor relative humidity (%)
indoor_temp: Indoor temperature (°C)
indoor_rh: Indoor relative humidity (%)
Returns:
Dictionary with sensible and latent loads in Watts
"""
try:
if st.session_state.get('debug_mode', False):
logger.debug(f"calculate_ventilation_cooling_load: flow_rate={flow_rate}, outdoor_temp={outdoor_temp}, indoor_temp={indoor_temp}")
# Sensible load: Q = 1.2 * flow_rate * (Tout - Tin)
sensible_load = 1.2 * flow_rate * (outdoor_temp - indoor_temp)
# Latent load using psychrometrics
outdoor_w = self.heat_transfer.psychrometrics.humidity_ratio(outdoor_temp, outdoor_rh)
indoor_w = self.heat_transfer.psychrometrics.humidity_ratio(indoor_temp, indoor_rh)
latent_load = 1.2 * flow_rate * 2500 * (outdoor_w - indoor_w) # 2500 kJ/kg for latent heat
if st.session_state.get('debug_mode', False):
logger.debug(f"Ventilation load: sensible={sensible_load}, latent={latent_load}, outdoor_w={outdoor_w}, indoor_w={indoor_w}")
return {
'sensible': max(sensible_load, 0.0),
'latent': max(latent_load, 0.0)
}
except Exception as e:
if st.session_state.get('debug_mode', False):
logger.error(f"Error in calculate_ventilation_cooling_load: {str(e)}")
raise Exception(f"Error in calculate_ventilation_cooling_load: {str(e)}")