not-sure / main.py
mabuseif's picture
Upload 25 files
6cc22a6 verified
"""
Main application module for HVAC Load Calculator.
"""
import streamlit as st
import pandas as pd
import numpy as np
import plotly.graph_objects as go
import plotly.express as px
import matplotlib.pyplot as plt
import json
import pycountry
import os
import sys
from typing import Dict, List, Any, Optional, Tuple
# Import application modules
from app.building_info_form import BuildingInfoForm
from app.component_selection import ComponentSelectionInterface, Orientation, ComponentType, Wall, Roof, Floor, Window, Door
from app.results_display import ResultsDisplay
from app.data_validation import DataValidation
from app.data_persistence import DataPersistence
from app.data_export import DataExport
# Import data modules
from data.reference_data import ReferenceData
from data.climate_data import ClimateData
from data.ashrae_tables import ASHRAETables
# Import utility modules
from utils.u_value_calculator import UValueCalculator
from utils.shading_system import ShadingSystem
from utils.area_calculation_system import AreaCalculationSystem
from utils.psychrometrics import Psychrometrics
from utils.heat_transfer import HeatTransferCalculations
from utils.cooling_load import CoolingLoadCalculator
from utils.heating_load import HeatingLoadCalculator
from utils.component_visualization import ComponentVisualization
from utils.scenario_comparison import ScenarioComparisonVisualization
from utils.psychrometric_visualization import PsychrometricVisualization
from utils.time_based_visualization import TimeBasedVisualization
class HVACCalculator:
def __init__(self):
st.set_page_config(
page_title="HVAC Load Calculator",
page_icon="🌡️",
layout="wide",
initial_sidebar_state="expanded"
)
if 'page' not in st.session_state:
st.session_state.page = 'Building Information'
if 'building_info' not in st.session_state:
st.session_state.building_info = {"project_name": ""}
if 'components' not in st.session_state:
st.session_state.components = {
'walls': [],
'roofs': [],
'floors': [],
'windows': [],
'doors': []
}
if 'internal_loads' not in st.session_state:
st.session_state.internal_loads = {
'people': [],
'lighting': [],
'equipment': []
}
if 'calculation_results' not in st.session_state:
st.session_state.calculation_results = {
'cooling': {},
'heating': {}
}
if 'scenarios' not in st.session_state:
st.session_state.scenarios = []
if 'climate_data' not in st.session_state:
st.session_state.climate_data = {}
self.building_info_form = BuildingInfoForm()
self.component_selection = ComponentSelectionInterface()
self.results_display = ResultsDisplay()
self.data_validation = DataValidation()
self.data_persistence = DataPersistence()
self.data_export = DataExport()
self.cooling_calculator = CoolingLoadCalculator()
self.heating_calculator = HeatingLoadCalculator()
self.setup_layout()
def setup_layout(self):
st.sidebar.title("HVAC Load Calculator")
st.sidebar.markdown("---")
st.sidebar.subheader("Navigation")
pages = [
"Building Information",
"Climate Data",
"Building Components",
"Internal Loads",
"Calculation Results",
"Export Data"
]
selected_page = st.sidebar.radio("Go to", pages, index=pages.index(st.session_state.page))
if selected_page != st.session_state.page:
st.session_state.page = selected_page
self.display_page(st.session_state.page)
st.sidebar.markdown("---")
st.sidebar.info(
"HVAC Load Calculator v1.0.0\n\n"
"Based on ASHRAE steady-state calculation methods\n\n"
"Developed by: Dr Majed Abuseif\n\n"
"School of Architecture and Built Environment\n\n"
"Deakin University\n\n"
"© 2025"
)
def display_page(self, page: str):
if page == "Building Information":
self.building_info_form.display_building_info_form(st.session_state)
elif page == "Climate Data":
self.display_climate_data()
elif page == "Building Components":
self.component_selection.display_component_selection(st.session_state)
elif page == "Internal Loads":
self.display_internal_loads()
elif page == "Calculation Results":
self.display_calculation_results()
elif page == "Export Data":
self.data_export.display()
def calculate_cooling(self) -> Tuple[bool, str, Dict]:
"""
Calculate cooling loads using CoolingLoadCalculator.
Returns: (success, message, results)
"""
try:
# Gather inputs
building_components = st.session_state.get('components', {})
internal_loads = st.session_state.get('internal_loads', {})
climate_data = st.session_state.get('climate_data', {})
building_info = st.session_state.get('building_info', {})
# Default conditions if missing
outdoor_conditions = {
'temperature': climate_data.get('summer_outdoor_db', 35.0),
'relative_humidity': climate_data.get('summer_outdoor_rh', 50.0),
'ground_temperature': climate_data.get('ground_temperature', 20.0),
'month': climate_data.get('design_month', 'Jul'),
'latitude': climate_data.get('latitude', '40N')
}
indoor_conditions = {
'temperature': building_info.get('indoor_temp', 24.0),
'relative_humidity': building_info.get('indoor_rh', 50.0)
}
# Format internal loads to match CoolingLoadCalculator
formatted_internal_loads = {
'people': {
'number': sum(load['num_people'] for load in internal_loads.get('people', [])),
'activity_level': internal_loads.get('people', [{}])[0].get('activity_level', 'Seated/Resting'),
'hours_occupancy': f"{internal_loads.get('people', [{}])[0].get('hours_in_operation', 8)}h"
},
'lights': {
'power': sum(load['power'] for load in internal_loads.get('lighting', [])),
'use_factor': internal_loads.get('lighting', [{}])[0].get('usage_factor', 0.8),
'special_allowance': 0.1, # Default
'hours_operation': f"{internal_loads.get('lighting', [{}])[0].get('hours_in_operation', 8)}h",
'lighting_type': internal_loads.get('lighting', [{}])[0].get('lighting_type', 'Fluorescent')
},
'equipment': {
'power': sum(load['power'] for load in internal_loads.get('equipment', [])),
'use_factor': internal_loads.get('equipment', [{}])[0].get('usage_factor', 0.7),
'radiation_factor': internal_loads.get('equipment', [{}])[0].get('radiation_fraction', 0.3),
'hours_operation': f"{internal_loads.get('equipment', [{}])[0].get('hours_in_operation', 8)}h",
'equipment_type': internal_loads.get('equipment', [{}])[0].get('equipment_type', 'General')
},
'infiltration': {'flow_rate': building_info.get('infiltration_rate', 0.05)},
'ventilation': {'flow_rate': building_info.get('ventilation_rate', 0.1)}
}
# Calculate hourly loads
hourly_loads = self.cooling_calculator.calculate_hourly_cooling_loads(
building_components=building_components,
outdoor_conditions=outdoor_conditions,
indoor_conditions=indoor_conditions,
internal_loads=formatted_internal_loads
)
# Get design loads
design_loads = self.cooling_calculator.calculate_design_cooling_load(hourly_loads)
# Get summary
summary = self.cooling_calculator.calculate_cooling_load_summary(design_loads)
# Format results for results_display.py
floor_area = building_info.get('floor_area', 100.0) or 100.0
results = {
'total_load': summary['total'] / 1000, # kW
'sensible_load': summary['total_sensible'] / 1000, # kW
'latent_load': summary['total_latent'] / 1000, # kW
'load_per_area': summary['total'] / floor_area, # W/m²
'component_loads': {
'walls': design_loads['walls'] / 1000,
'roof': design_loads['roofs'] / 1000,
'windows': (design_loads['windows_conduction'] + design_loads['windows_solar']) / 1000,
'doors': design_loads['doors'] / 1000,
'people': (design_loads['people_sensible'] + design_loads['people_latent']) / 1000,
'lighting': design_loads['lights'] / 1000,
'equipment': (design_loads['equipment_sensible'] + design_loads['equipment_latent']) / 1000,
'infiltration': (design_loads['infiltration_sensible'] + design_loads['infiltration_latent']) / 1000,
'ventilation': (design_loads['ventilation_sensible'] + design_loads['ventilation_latent']) / 1000
},
'detailed_loads': {
'walls': [],
'roofs': [],
'windows': [],
'doors': [],
'internal': [],
'infiltration': {
'air_flow': formatted_internal_loads['infiltration']['flow_rate'],
'sensible_load': design_loads['infiltration_sensible'] / 1000,
'latent_load': design_loads['infiltration_latent'] / 1000,
'total_load': (design_loads['infiltration_sensible'] + design_loads['infiltration_latent']) / 1000
},
'ventilation': {
'air_flow': formatted_internal_loads['ventilation']['flow_rate'],
'sensible_load': design_loads['ventilation_sensible'] / 1000,
'latent_load': design_loads['ventilation_latent'] / 1000,
'total_load': (design_loads['ventilation_sensible'] + design_loads['ventilation_latent']) / 1000
}
}
}
# Populate detailed loads
for wall in building_components.get('walls', []):
load = self.cooling_calculator.calculate_wall_cooling_load(
wall=wall,
outdoor_temp=outdoor_conditions['temperature'],
indoor_temp=indoor_conditions['temperature'],
month=outdoor_conditions['month'],
hour=design_loads['design_hour'],
latitude=outdoor_conditions['latitude']
)
results['detailed_loads']['walls'].append({
'name': wall.name,
'orientation': wall.orientation.value,
'area': wall.area,
'u_value': wall.u_value,
'cltd': self.cooling_calculator.ashrae_tables.calculate_corrected_cltd_wall(
wall_group=wall.wall_group,
orientation=wall.orientation.value,
hour=design_loads['design_hour'],
color='Dark',
month=outdoor_conditions['month'],
latitude=outdoor_conditions['latitude'],
indoor_temp=indoor_conditions['temperature'],
outdoor_temp=outdoor_conditions['temperature']
),
'load': load / 1000
})
for roof in building_components.get('roofs', []):
load = self.cooling_calculator.calculate_roof_cooling_load(
roof=roof,
outdoor_temp=outdoor_conditions['temperature'],
indoor_temp=indoor_conditions['temperature'],
month=outdoor_conditions['month'],
hour=design_loads['design_hour'],
latitude=outdoor_conditions['latitude']
)
results['detailed_loads']['roofs'].append({
'name': roof.name,
'orientation': roof.orientation.value,
'area': roof.area,
'u_value': roof.u_value,
'cltd': self.cooling_calculator.ashrae_tables.calculate_corrected_cltd_roof(
roof_group=roof.roof_group,
hour=design_loads['design_hour'],
color='Dark',
month=outdoor_conditions['month'],
latitude=outdoor_conditions['latitude'],
indoor_temp=indoor_conditions['temperature'],
outdoor_temp=outdoor_conditions['temperature']
),
'load': load / 1000
})
for window in building_components.get('windows', []):
load_dict = self.cooling_calculator.calculate_window_cooling_load(
window=window,
outdoor_temp=outdoor_conditions['temperature'],
indoor_temp=indoor_conditions['temperature'],
month=outdoor_conditions['month'],
hour=design_loads['design_hour'],
latitude=f"{outdoor_conditions['latitude']}_{outdoor_conditions['month'].upper()}"
)
results['detailed_loads']['windows'].append({
'name': window.name,
'orientation': window.orientation.value,
'area': window.area,
'u_value': window.u_value,
'shgc': window.shgc,
'scl': self.cooling_calculator.ashrae_tables.get_scl(
window.orientation.value,
design_loads['design_hour'],
f"{outdoor_conditions['latitude']}_{outdoor_conditions['month'].upper()}"
),
'load': load_dict['total'] / 1000
})
for door in building_components.get('doors', []):
load = self.cooling_calculator.calculate_door_cooling_load(
door=door,
outdoor_temp=outdoor_conditions['temperature'],
indoor_temp=indoor_conditions['temperature']
)
results['detailed_loads']['doors'].append({
'name': door.name,
'orientation': door.orientation.value,
'area': door.area,
'u_value': door.u_value,
'cltd': outdoor_conditions['temperature'] - indoor_conditions['temperature'],
'load': load / 1000
})
for load_type, key in [('people', 'people'), ('lighting', 'lights'), ('equipment', 'equipment')]:
for load in internal_loads.get(key, []):
if load_type == 'people':
load_dict = self.cooling_calculator.calculate_people_cooling_load(
num_people=load['num_people'],
activity_level=load['activity_level'],
hours_occupancy=f"{load['hours_in_operation']}h",
hour=design_loads['design_hour']
)
# Add activity level and heat gain information to the detailed loads
results['detailed_loads']['internal'].append({
'type': load_type.capitalize(),
'name': load['name'],
'quantity': load['num_people'],
'activity_level': load_dict.get('activity_level', load['activity_level']),
'sensible_gain_per_person': load_dict.get('sensible_gain_per_person', 0),
'latent_gain_per_person': load_dict.get('latent_gain_per_person', 0),
'heat_gain': load_dict.get('sensible', 0),
'clf': self.cooling_calculator.ashrae_tables.get_clf_people(
design_loads['design_hour'],
f"{load['hours_in_operation']}h"
),
'load': load_dict['total'] / 1000
})
elif load_type == 'lighting':
load_dict = self.cooling_calculator.calculate_lights_cooling_load(
power=load['power'],
use_factor=load['usage_factor'],
special_allowance=0.1,
hours_operation=f"{load['hours_in_operation']}h",
hour=design_loads['design_hour'],
lighting_type=load.get('lighting_type', 'Fluorescent')
)
results['detailed_loads']['internal'].append({
'type': load_type.capitalize(),
'name': load['name'],
'quantity': load['power'],
'lighting_type': load_dict.get('lighting_type', 'Fluorescent'),
'heat_emission_factor': load_dict.get('heat_emission_factor', 0.85),
'heat_gain': load_dict['total'],
'clf': load_dict.get('clf', 1.0),
'load': load_dict['total'] / 1000
})
else: # equipment
load_dict = self.cooling_calculator.calculate_equipment_cooling_load(
power=load['power'],
use_factor=load['usage_factor'],
radiation_factor=load['radiation_fraction'],
hours_operation=f"{load['hours_in_operation']}h",
hour=design_loads['design_hour'],
equipment_type=load.get('equipment_type', 'General')
)
results['detailed_loads']['internal'].append({
'type': load_type.capitalize(),
'name': load['name'],
'quantity': load['power'],
'equipment_type': load_dict.get('equipment_type', 'General'),
'sensible_factor': load_dict.get('sensible_factor', 1.0),
'latent_factor': load_dict.get('latent_factor', 0.0),
'heat_gain': load_dict.get('sensible', load_dict['total']),
'clf': load_dict.get('clf', 1.0),
'load': load_dict['total'] / 1000
})
return True, "Cooling calculation completed.", results
except Exception as e:
return False, f"Cooling calculation error: {str(e)}", {}
def calculate_heating(self) -> Tuple[bool, str, Dict]:
"""
Calculate heating loads using HeatingLoadCalculator.
Returns: (success, message, results)
"""
try:
# Gather inputs
building_components = st.session_state.get('components', {})
internal_loads = st.session_state.get('internal_loads', {})
climate_data = st.session_state.get('climate_data', {})
building_info = st.session_state.get('building_info', {})
# Default conditions if missing
outdoor_conditions = {
'design_temperature': climate_data.get('winter_outdoor_db', -10.0),
'design_relative_humidity': climate_data.get('winter_outdoor_rh', 80.0),
'ground_temperature': climate_data.get('ground_temperature', 10.0)
}
indoor_conditions = {
'temperature': building_info.get('indoor_temp', 21.0),
'relative_humidity': building_info.get('indoor_rh', 40.0)
}
# Format internal loads to match HeatingLoadCalculator
formatted_internal_loads = {
'people': {
'number': sum(load['num_people'] for load in internal_loads.get('people', [])),
'sensible_gain': 70 # Default per person
},
'lights': {
'power': sum(load['power'] for load in internal_loads.get('lighting', [])),
'use_factor': internal_loads.get('lighting', [{}])[0].get('usage_factor', 0.8)
},
'equipment': {
'power': sum(load['power'] for load in internal_loads.get('equipment', [])),
'use_factor': internal_loads.get('equipment', [{}])[0].get('usage_factor', 0.7)
},
'infiltration': {'flow_rate': building_info.get('infiltration_rate', 0.05)},
'ventilation': {'flow_rate': building_info.get('ventilation_rate', 0.1)},
'usage_factor': 0.7
}
# Calculate design loads
design_loads = self.heating_calculator.calculate_design_heating_load(
building_components=building_components,
outdoor_conditions=outdoor_conditions,
indoor_conditions=indoor_conditions,
internal_loads=formatted_internal_loads
)
# Get summary
summary = self.heating_calculator.calculate_heating_load_summary(design_loads)
# Format results for results_display.py
floor_area = building_info.get('floor_area', 100.0) or 100.0
results = {
'total_load': summary['total'] / 1000, # kW
'load_per_area': summary['total'] / floor_area, # W/m²
'design_heat_loss': summary['subtotal'] / 1000, # kW
'safety_factor': summary['safety_factor'] * 100, # %
'component_loads': {
'walls': design_loads['walls'] / 1000,
'roof': design_loads['roofs'] / 1000,
'floor': design_loads['floors'] / 1000,
'windows': design_loads['windows'] / 1000,
'doors': design_loads['doors'] / 1000,
'infiltration': (design_loads['infiltration_sensible'] + design_loads['infiltration_latent']) / 1000,
'ventilation': (design_loads['ventilation_sensible'] + design_loads['ventilation_latent']) / 1000
},
'detailed_loads': {
'walls': [],
'roofs': [],
'floors': [],
'windows': [],
'doors': [],
'infiltration': {
'air_flow': formatted_internal_loads['infiltration']['flow_rate'],
'delta_t': indoor_conditions['temperature'] - outdoor_conditions['design_temperature'],
'load': (design_loads['infiltration_sensible'] + design_loads['infiltration_latent']) / 1000
},
'ventilation': {
'air_flow': formatted_internal_loads['ventilation']['flow_rate'],
'delta_t': indoor_conditions['temperature'] - outdoor_conditions['design_temperature'],
'load': (design_loads['ventilation_sensible'] + design_loads['ventilation_latent']) / 1000
}
}
}
# Populate detailed loads
delta_t = indoor_conditions['temperature'] - outdoor_conditions['design_temperature']
for wall in building_components.get('walls', []):
load = self.heating_calculator.calculate_wall_heating_load(
wall=wall,
outdoor_temp=outdoor_conditions['design_temperature'],
indoor_temp=indoor_conditions['temperature']
)
results['detailed_loads']['walls'].append({
'name': wall.name,
'orientation': wall.orientation.value,
'area': wall.area,
'u_value': wall.u_value,
'delta_t': delta_t,
'load': load / 1000
})
for roof in building_components.get('roofs', []):
load = self.heating_calculator.calculate_roof_heating_load(
roof=roof,
outdoor_temp=outdoor_conditions['design_temperature'],
indoor_temp=indoor_conditions['temperature']
)
results['detailed_loads']['roofs'].append({
'name': roof.name,
'orientation': roof.orientation.value,
'area': roof.area,
'u_value': roof.u_value,
'delta_t': delta_t,
'load': load / 1000
})
for floor in building_components.get('floors', []):
load = self.heating_calculator.calculate_floor_heating_load(
floor=floor,
ground_temp=outdoor_conditions['ground_temperature'],
indoor_temp=indoor_conditions['temperature']
)
results['detailed_loads']['floors'].append({
'name': floor.name,
'area': floor.area,
'u_value': floor.u_value,
'delta_t': indoor_conditions['temperature'] - outdoor_conditions['ground_temperature'],
'load': load / 1000
})
for window in building_components.get('windows', []):
load = self.heating_calculator.calculate_window_heating_load(
window=window,
outdoor_temp=outdoor_conditions['design_temperature'],
indoor_temp=indoor_conditions['temperature']
)
results['detailed_loads']['windows'].append({
'name': window.name,
'orientation': window.orientation.value,
'area': window.area,
'u_value': window.u_value,
'delta_t': delta_t,
'load': load / 1000
})
for door in building_components.get('doors', []):
load = self.heating_calculator.calculate_door_heating_load(
door=door,
outdoor_temp=outdoor_conditions['design_temperature'],
indoor_temp=indoor_conditions['temperature']
)
results['detailed_loads']['doors'].append({
'name': door.name,
'orientation': door.orientation.value,
'area': door.area,
'u_value': door.u_value,
'delta_t': delta_t,
'load': load / 1000
})
return True, "Heating calculation completed.", results
except Exception as e:
return False, f"Heating calculation error: {str(e)}", {}
def display_calculation_results(self):
"""
Display calculation results with separate tabs for cooling and heating.
"""
st.title("Calculation Results")
# Toggle debug output
debug = st.checkbox("Show debug information")
# Create tabs
cooling_tab, heating_tab = st.tabs(["Cooling Load", "Heating Load"])
# Cooling Tab
with cooling_tab:
st.subheader("Cooling Load")
success, message, cooling_results = self.calculate_cooling()
if debug:
st.write("Cooling Inputs:", {
'components': st.session_state.get('components', {}),
'internal_loads': st.session_state.get('internal_loads', {}),
'climate_data': st.session_state.get('climate_data', {}),
'building_info': st.session_state.get('building_info', {})
})
st.write("Cooling Results:", cooling_results)
if not success:
st.error(message)
elif not cooling_results:
st.warning("No cooling load data available. Please ensure all inputs are provided.")
else:
# Store cooling results
st.session_state.calculation_results['cooling'] = cooling_results
# Display using ResultsDisplay
self.results_display._display_summary_results(st.session_state)
# Show only cooling table
cooling_details = []
for wall in cooling_results['detailed_loads']['walls']:
cooling_details.append({
"Component Type": "Wall",
"Name": wall["name"],
"Orientation": wall["orientation"],
"Area (m²)": wall["area"],
"U-Value (W/m²·K)": wall["u_value"],
"CLTD (°C)": wall["cltd"],
"Load (kW)": wall["load"]
})
for roof in cooling_results['detailed_loads']['roofs']:
cooling_details.append({
"Component Type": "Roof",
"Name": roof["name"],
"Orientation": roof["orientation"],
"Area (m²)": roof["area"],
"U-Value (W/m²·K)": roof["u_value"],
"CLTD (°C)": roof["cltd"],
"Load (kW)": roof["load"]
})
for window in cooling_results['detailed_loads']['windows']:
cooling_details.append({
"Component Type": "Window",
"Name": window["name"],
"Orientation": window["orientation"],
"Area (m²)": window["area"],
"U-Value (W/m²·K)": window["u_value"],
"SHGC": window["shgc"],
"SCL (W/m²)": window["scl"],
"Load (kW)": window["load"]
})
for door in cooling_results['detailed_loads']['doors']:
cooling_details.append({
"Component Type": "Door",
"Name": door["name"],
"Orientation": door["orientation"],
"Area (m²)": door["area"],
"U-Value (W/m²·K)": door["u_value"],
"CLTD (°C)": door["cltd"],
"Load (kW)": door["load"]
})
for internal_load in cooling_results['detailed_loads']['internal']:
# Enhanced display for people loads to show activity level and heat gain details
if internal_load["type"] == "People":
cooling_details.append({
"Component Type": internal_load["type"],
"Name": internal_load["name"],
"Quantity": internal_load["quantity"],
"Activity Level": internal_load.get("activity_level", "N/A"),
"Sensible Gain (W/person)": internal_load.get("sensible_gain_per_person", "N/A"),
"Latent Gain (W/person)": internal_load.get("latent_gain_per_person", "N/A"),
"Heat Gain (W)": internal_load["heat_gain"],
"CLF": internal_load["clf"],
"Load (kW)": internal_load["load"]
})
# Enhanced display for lighting loads to show lighting type and heat emission factor
elif internal_load["type"] == "Lighting":
cooling_details.append({
"Component Type": internal_load["type"],
"Name": internal_load["name"],
"Power (W)": internal_load["quantity"],
"Lighting Type": internal_load.get("lighting_type", "N/A"),
"Heat Emission Factor": internal_load.get("heat_emission_factor", "N/A"),
"Heat Gain (W)": internal_load["heat_gain"],
"CLF": internal_load["clf"],
"Load (kW)": internal_load["load"]
})
# Enhanced display for equipment loads to show equipment type and heat factors
elif internal_load["type"] == "Equipment":
cooling_details.append({
"Component Type": internal_load["type"],
"Name": internal_load["name"],
"Power (W)": internal_load["quantity"],
"Equipment Type": internal_load.get("equipment_type", "N/A"),
"Sensible Factor": internal_load.get("sensible_factor", "N/A"),
"Latent Factor": internal_load.get("latent_factor", "N/A"),
"Heat Gain (W)": internal_load["heat_gain"],
"CLF": internal_load["clf"],
"Load (kW)": internal_load["load"]
})
else:
cooling_details.append({
"Component Type": internal_load["type"],
"Name": internal_load["name"],
"Quantity": internal_load["quantity"],
"Heat Gain (W)": internal_load["heat_gain"],
"CLF": internal_load["clf"],
"Load (kW)": internal_load["load"]
})
cooling_details.append({
"Component Type": "Infiltration",
"Name": "Air Infiltration",
"Air Flow (m³/s)": cooling_results['detailed_loads']['infiltration']['air_flow'],
"Sensible Load (kW)": cooling_results['detailed_loads']['infiltration']['sensible_load'],
"Latent Load (kW)": cooling_results['detailed_loads']['infiltration']['latent_load'],
"Load (kW)": cooling_results['detailed_loads']['infiltration']['total_load']
})
cooling_details.append({
"Component Type": "Ventilation",
"Name": "Fresh Air",
"Air Flow (m³/s)": cooling_results['detailed_loads']['ventilation']['air_flow'],
"Sensible Load (kW)": cooling_results['detailed_loads']['ventilation']['sensible_load'],
"Latent Load (kW)": cooling_results['detailed_loads']['ventilation']['latent_load'],
"Load (kW)": cooling_results['detailed_loads']['ventilation']['total_load']
})
cooling_df = pd.DataFrame(cooling_details)
st.dataframe(cooling_df, use_container_width=True)
# Heating Tab
with heating_tab:
st.subheader("Heating Load")
success, message, heating_results = self.calculate_heating()
if debug:
st.write("Heating Inputs:", {
'components': st.session_state.get('components', {}),
'internal_loads': st.session_state.get('internal_loads', {}),
'climate_data': st.session_state.get('climate_data', {}),
'building_info': st.session_state.get('building_info', {})
})
st.write("Heating Results:", heating_results)
if not success:
st.error(message)
elif not heating_results:
st.warning("No heating load data available. Please ensure all inputs are provided.")
else:
# Store heating results
st.session_state.calculation_results['heating'] = heating_results
# Display using ResultsDisplay
self.results_display._display_summary_results(st.session_state)
# Show only heating table
heating_details = []
for wall in heating_results['detailed_loads']['walls']:
heating_details.append({
"Component Type": "Wall",
"Name": wall["name"],
"Orientation": wall["orientation"],
"Area (m²)": wall["area"],
"U-Value (W/m²·K)": wall["u_value"],
"Temperature Difference (°C)": wall["delta_t"],
"Load (kW)": wall["load"]
})
for roof in heating_results['detailed_loads']['roofs']:
heating_details.append({
"Component Type": "Roof",
"Name": roof["name"],
"Orientation": roof["orientation"],
"Area (m²)": roof["area"],
"U-Value (W/m²·K)": roof["u_value"],
"Temperature Difference (°C)": roof["delta_t"],
"Load (kW)": roof["load"]
})
for floor in heating_results['detailed_loads']['floors']:
heating_details.append({
"Component Type": "Floor",
"Name": floor["name"],
"Area (m²)": floor["area"],
"U-Value (W/m²·K)": floor["u_value"],
"Temperature Difference (°C)": floor["delta_t"],
"Load (kW)": floor["load"]
})
for window in heating_results['detailed_loads']['windows']:
heating_details.append({
"Component Type": "Window",
"Name": window["name"],
"Orientation": window["orientation"],
"Area (m²)": window["area"],
"U-Value (W/m²·K)": window["u_value"],
"Temperature Difference (°C)": window["delta_t"],
"Load (kW)": window["load"]
})
for door in heating_results['detailed_loads']['doors']:
heating_details.append({
"Component Type": "Door",
"Name": door["name"],
"Orientation": door["orientation"],
"Area (m²)": door["area"],
"U-Value (W/m²·K)": door["u_value"],
"Temperature Difference (°C)": door["delta_t"],
"Load (kW)": door["load"]
})
heating_details.append({
"Component Type": "Infiltration",
"Name": "Air Infiltration",
"Air Flow (m³/s)": heating_results['detailed_loads']['infiltration']['air_flow'],
"Temperature Difference (°C)": heating_results['detailed_loads']['infiltration']['delta_t'],
"Load (kW)": heating_results['detailed_loads']['infiltration']['load']
})
heating_details.append({
"Component Type": "Ventilation",
"Name": "Fresh Air",
"Air Flow (m³/s)": heating_results['detailed_loads']['ventilation']['air_flow'],
"Temperature Difference (°C)": heating_results['detailed_loads']['ventilation']['delta_t'],
"Load (kW)": heating_results['detailed_loads']['ventilation']['load']
})
heating_df = pd.DataFrame(heating_details)
st.dataframe(heating_df, use_container_width=True)
# Navigation
col1, col2 = st.columns(2)
with col1:
st.button("Back to Internal Loads", on_click=self.navigate_to, args=["Internal Loads"])
with col2:
st.button("Continue to Export Data", on_click=self.navigate_to, args=["Export Data"])
def display_climate_data(self):
climate_data = ClimateData()
climate_data.display_climate_input(st.session_state)
if climate_data.locations:
st.session_state["climate_data"] = climate_data.locations
def display_internal_loads(self):
st.title("Internal Loads")
if not any(st.session_state.components.values()):
st.warning("Please define building components first.")
st.button("Go to Building Components", on_click=self.navigate_to, args=["Building Components"])
return
tabs = st.tabs(["People", "Lighting", "Equipment"])
with tabs[0]:
self.display_people_loads()
with tabs[1]:
self.display_lighting_loads()
with tabs[2]:
self.display_equipment_loads()
self.display_internal_loads_summary()
col1, col2 = st.columns(2)
with col1:
st.button("Back to Building Components", on_click=self.navigate_to, args=["Building Components"])
with col2:
is_valid, messages = self.data_validation.validate_internal_loads(st.session_state.internal_loads)
if messages:
self.data_validation.display_validation_messages(messages)
if is_valid:
st.button("Continue to Calculation Results", on_click=self.navigate_to, args=["Calculation Results"])
else:
st.button("Continue to Calculation Results", disabled=True)
def display_people_loads(self):
st.subheader("People")
# Add information about activity levels and their heat gains
st.info("""
**Activity Level Heat Gains:**
- Seated/Resting: 70W sensible, 45W latent
- Light work: 75W sensible, 55W latent
- Medium work: 85W sensible, 80W latent
- Heavy work: 95W sensible, 145W latent
The activity level selection directly affects the heat gain calculations.
""")
with st.form("people_load_form"):
col1, col2 = st.columns(2)
with col1:
name = st.text_input("Name", "Occupants")
num_people = st.number_input("Number of People", min_value=1, value=10)
with col2:
activity_level = st.selectbox(
"Activity Level",
options=["Seated/Resting", "Light work", "Medium work", "Heavy work"]
)
zone_type = st.selectbox(
"Zone Type",
options=["Office", "Residential", "Retail", "Educational", "Healthcare"]
)
hours_in_operation = st.slider("Hours in Operation", min_value=1, max_value=24, value=8)
submitted = st.form_submit_button("Add People Load")
if submitted:
people_load = {
"id": f"people_{len(st.session_state.internal_loads['people'])}",
"name": name,
"num_people": num_people,
"activity_level": activity_level,
"zone_type": zone_type,
"hours_in_operation": hours_in_operation
}
st.session_state.internal_loads['people'].append(people_load)
st.success(f"Added {name} with {num_people} people at {activity_level} activity level")
if st.session_state.internal_loads['people']:
st.subheader("Existing People Loads")
people_data = []
for load in st.session_state.internal_loads['people']:
people_data.append({
"Name": load['name'],
"Number of People": load['num_people'],
"Activity Level": load['activity_level'],
"Zone Type": load['zone_type'],
"Hours in Operation": load['hours_in_operation'],
"Actions": load['id']
})
df = pd.DataFrame(people_data)
for i, row in df.iterrows():
col1, col2, col3, col4, col5, col6, col7 = st.columns([2, 1, 2, 2, 1, 1, 1])
with col1:
st.write(row["Name"])
with col2:
st.write(row["Number of People"])
with col3:
st.write(row["Activity Level"])
with col4:
st.write(row["Zone Type"])
with col5:
st.write(row["Hours in Operation"])
with col6:
if st.button("Edit", key=f"edit_{row['Actions']}"):
st.session_state.editing_people = row['Actions']
with col7:
if st.button("Delete", key=f"delete_{row['Actions']}"):
st.session_state.internal_loads['people'] = [
load for load in st.session_state.internal_loads['people']
if load['id'] != row['Actions']
]
st.experimental_rerun()
def display_lighting_loads(self):
st.subheader("Lighting")
# Add information about lighting types and their heat emission factors
st.info("""
**Lighting Type Heat Emission Factors:**
- LED: 80% of power converted to heat
- Fluorescent: 85% of power converted to heat
- Halogen: 95% of power converted to heat
- Incandescent: 98% of power converted to heat
The lighting type selection directly affects the heat gain calculations.
""")
with st.form("lighting_load_form"):
col1, col2 = st.columns(2)
with col1:
name = st.text_input("Name", "General Lighting")
power = st.number_input("Power (W)", min_value=0.0, value=1000.0)
lighting_type = st.selectbox(
"Lighting Type",
options=["LED", "Fluorescent", "Halogen", "Incandescent"]
)
with col2:
usage_factor = st.slider("Usage Factor", min_value=0.0, max_value=1.0, value=0.8, step=0.1)
zone_type = st.selectbox(
"Zone Type",
options=["Office", "Residential", "Retail", "Educational", "Healthcare"]
)
hours_in_operation = st.slider("Hours in Operation", min_value=1, max_value=24, value=8)
submitted = st.form_submit_button("Add Lighting Load")
if submitted:
lighting_load = {
"id": f"lighting_{len(st.session_state.internal_loads['lighting'])}",
"name": name,
"power": power,
"lighting_type": lighting_type,
"usage_factor": usage_factor,
"zone_type": zone_type,
"hours_in_operation": hours_in_operation
}
st.session_state.internal_loads['lighting'].append(lighting_load)
st.success(f"Added {name} with {power} W ({lighting_type})")
if st.session_state.internal_loads['lighting']:
st.subheader("Existing Lighting Loads")
lighting_data = []
for load in st.session_state.internal_loads['lighting']:
lighting_data.append({
"Name": load['name'],
"Power (W)": load['power'],
"Lighting Type": load.get('lighting_type', 'Fluorescent'),
"Usage Factor": load['usage_factor'],
"Zone Type": load['zone_type'],
"Hours in Operation": load['hours_in_operation'],
"Actions": load['id']
})
df = pd.DataFrame(lighting_data)
for i, row in df.iterrows():
col1, col2, col3, col4, col5, col6, col7, col8 = st.columns([2, 1, 1.5, 1, 2, 1, 1, 1])
with col1:
st.write(row["Name"])
with col2:
st.write(f"{row['Power (W)']:.1f}")
with col3:
st.write(row["Lighting Type"])
with col4:
st.write(f"{row['Usage Factor']:.1f}")
with col5:
st.write(row["Zone Type"])
with col6:
st.write(row["Hours in Operation"])
with col7:
if st.button("Edit", key=f"edit_{row['Actions']}"):
st.session_state.editing_lighting = row['Actions']
with col8:
if st.button("Delete", key=f"delete_{row['Actions']}"):
st.session_state.internal_loads['lighting'] = [
load for load in st.session_state.internal_loads['lighting']
if load['id'] != row['Actions']
]
st.experimental_rerun()
def display_equipment_loads(self):
st.subheader("Equipment")
# Add information about equipment types and their heat factors
st.info("""
**Equipment Type Heat Factors:**
- General: 100% sensible, 0% latent
- Computer: 95% sensible, 0% latent
- Kitchen: 80% sensible, 20% latent
- Medical: 90% sensible, 10% latent
- Laboratory: 85% sensible, 15% latent
The equipment type selection directly affects the heat gain calculations.
""")
with st.form("equipment_load_form"):
col1, col2 = st.columns(2)
with col1:
name = st.text_input("Name", "Office Equipment")
power = st.number_input("Power (W)", min_value=0.0, value=500.0)
equipment_type = st.selectbox(
"Equipment Type",
options=["General", "Computer", "Kitchen", "Medical", "Laboratory"]
)
with col2:
usage_factor = st.slider("Usage Factor", min_value=0.0, max_value=1.0, value=0.7, step=0.1)
radiation_fraction = st.slider("Radiation Fraction", min_value=0.0, max_value=1.0, value=0.3, step=0.1)
zone_type = st.selectbox(
"Zone Type",
options=["Office", "Residential", "Retail", "Educational", "Healthcare"]
)
hours_in_operation = st.slider("Hours in Operation", min_value=1, max_value=24, value=8)
submitted = st.form_submit_button("Add Equipment Load")
if submitted:
equipment_load = {
"id": f"equipment_{len(st.session_state.internal_loads['equipment'])}",
"name": name,
"power": power,
"equipment_type": equipment_type,
"usage_factor": usage_factor,
"radiation_fraction": radiation_fraction,
"zone_type": zone_type,
"hours_in_operation": hours_in_operation
}
st.session_state.internal_loads['equipment'].append(equipment_load)
st.success(f"Added {name} with {power} W ({equipment_type})")
if st.session_state.internal_loads['equipment']:
st.subheader("Existing Equipment Loads")
equipment_data = []
for load in st.session_state.internal_loads['equipment']:
equipment_data.append({
"Name": load['name'],
"Power (W)": load['power'],
"Equipment Type": load.get('equipment_type', 'General'),
"Usage Factor": load['usage_factor'],
"Radiation Fraction": load['radiation_fraction'],
"Zone Type": load['zone_type'],
"Hours in Operation": load['hours_in_operation'],
"Actions": load['id']
})
df = pd.DataFrame(equipment_data)
for i, row in df.iterrows():
col1, col2, col3, col4, col5, col6, col7, col8, col9 = st.columns([2, 1, 1.5, 1, 1, 1.5, 1, 1, 1])
with col1:
st.write(row["Name"])
with col2:
st.write(f"{row['Power (W)']:.1f}")
with col3:
st.write(row["Equipment Type"])
with col4:
st.write(f"{row['Usage Factor']:.1f}")
with col5:
st.write(f"{row['Radiation Fraction']:.1f}")
with col6:
st.write(row["Zone Type"])
with col7:
st.write(row["Hours in Operation"])
with col8:
if st.button("Edit", key=f"edit_{row['Actions']}"):
st.session_state.editing_equipment = row['Actions']
with col9:
if st.button("Delete", key=f"delete_{row['Actions']}"):
st.session_state.internal_loads['equipment'] = [
load for load in st.session_state.internal_loads['equipment']
if load['id'] != row['Actions']
]
st.experimental_rerun()
def display_internal_loads_summary(self):
st.subheader("Internal Loads Summary")
if not any(st.session_state.internal_loads.values()):
st.info("No internal loads defined yet.")
return
total_people = sum(load['num_people'] for load in st.session_state.internal_loads['people'])
total_lighting_power = sum(load['power'] * load['usage_factor'] for load in st.session_state.internal_loads['lighting'])
total_equipment_power = sum(load['power'] * load['usage_factor'] for load in st.session_state.internal_loads['equipment'])
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Total People", f"{total_people}")
with col2:
st.metric("Total Lighting Power", f"{total_lighting_power:.1f} W")
with col3:
st.metric("Total Equipment Power", f"{total_equipment_power:.1f} W")
if total_lighting_power > 0 or total_equipment_power > 0:
# Calculate people power based on activity levels
people_power = 0
for load in st.session_state.internal_loads['people']:
activity_level = load['activity_level']
num_people = load['num_people']
# Use the same heat gain values as in cooling_load.py
if activity_level == "Seated/Resting":
people_power += num_people * (70 + 45) # sensible + latent
elif activity_level == "Light work":
people_power += num_people * (75 + 55) # sensible + latent
elif activity_level == "Medium work":
people_power += num_people * (85 + 80) # sensible + latent
elif activity_level == "Heavy work":
people_power += num_people * (95 + 145) # sensible + latent
else:
people_power += num_people * 115 # default value
# Calculate lighting power based on lighting types
adjusted_lighting_power = 0
for load in st.session_state.internal_loads['lighting']:
lighting_type = load.get('lighting_type', 'Fluorescent')
power = load['power'] * load['usage_factor']
# Use the same heat emission factors as in cooling_load.py
if lighting_type == "LED":
adjusted_lighting_power += power * 0.80
elif lighting_type == "Fluorescent":
adjusted_lighting_power += power * 0.85
elif lighting_type == "Halogen":
adjusted_lighting_power += power * 0.95
elif lighting_type == "Incandescent":
adjusted_lighting_power += power * 0.98
else:
adjusted_lighting_power += power * 0.85 # default value
# Calculate equipment power based on equipment types
adjusted_equipment_power = 0
for load in st.session_state.internal_loads['equipment']:
equipment_type = load.get('equipment_type', 'General')
power = load['power'] * load['usage_factor']
# Use the same equipment factors as in cooling_load.py
if equipment_type == "General":
adjusted_equipment_power += power * 1.0
elif equipment_type == "Computer":
adjusted_equipment_power += power * 0.95
elif equipment_type == "Kitchen":
adjusted_equipment_power += power * 1.0 # Total of sensible (0.8) and latent (0.2)
elif equipment_type == "Medical":
adjusted_equipment_power += power * 1.0 # Total of sensible (0.9) and latent (0.1)
elif equipment_type == "Laboratory":
adjusted_equipment_power += power * 1.0 # Total of sensible (0.85) and latent (0.15)
else:
adjusted_equipment_power += power * 1.0 # default value
fig = px.pie(
values=[people_power, adjusted_lighting_power, adjusted_equipment_power],
names=['People', 'Lighting', 'Equipment'],
title='Internal Loads Distribution'
)
st.plotly_chart(fig, use_container_width=True)
def navigate_to(self, page: str):
st.session_state.page = page
st.experimental_rerun()
if __name__ == "__main__":
app = HVACCalculator()