Spaces:
Sleeping
Sleeping
""" | |
Cooling Load Calculator Page | |
This module implements the cooling load calculator interface for the HVAC Load Calculator web application. | |
It provides a step-by-step form for inputting building information and calculates cooling loads | |
using the ASHRAE method. | |
""" | |
import streamlit as st | |
import pandas as pd | |
import numpy as np | |
import plotly.express as px | |
import plotly.graph_objects as go | |
import json | |
import os | |
import sys | |
from pathlib import Path | |
from datetime import datetime | |
# Add the parent directory to sys.path to import modules | |
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | |
# Import custom modules | |
from cooling_load import CoolingLoadCalculator | |
from reference_data import ReferenceData | |
from utils.validation import validate_input, ValidationWarning | |
from utils.export import export_data | |
def load_session_state(): | |
"""Initialize or load session state variables.""" | |
# Initialize session state for form data | |
if 'cooling_form_data' not in st.session_state: | |
st.session_state.cooling_form_data = { | |
'building_info': {}, | |
'building_envelope': {}, | |
'windows': {}, | |
'internal_loads': {}, | |
'ventilation': {}, | |
'results': {} | |
} | |
# Initialize session state for validation warnings | |
if 'cooling_warnings' not in st.session_state: | |
st.session_state.cooling_warnings = { | |
'building_info': [], | |
'building_envelope': [], | |
'windows': [], | |
'internal_loads': [], | |
'ventilation': [] | |
} | |
# Initialize session state for form completion status | |
if 'cooling_completed' not in st.session_state: | |
st.session_state.cooling_completed = { | |
'building_info': False, | |
'building_envelope': False, | |
'windows': False, | |
'internal_loads': False, | |
'ventilation': False | |
} | |
# Initialize session state for calculation results | |
if 'cooling_results' not in st.session_state: | |
st.session_state.cooling_results = None | |
def building_info_form(ref_data): | |
""" | |
Form for building information. | |
Args: | |
ref_data: Reference data object | |
""" | |
st.subheader("Building Information") | |
st.write("Enter general building information, location, and design temperatures.") | |
# Get location options from reference data | |
location_options = {loc_id: loc_data['name'] for loc_id, loc_data in ref_data.locations.items()} | |
col1, col2 = st.columns(2) | |
with col1: | |
# Building name | |
building_name = st.text_input( | |
"Building Name", | |
value=st.session_state.cooling_form_data['building_info'].get('building_name', ''), | |
help="Enter a name for this building or project" | |
) | |
# Location selection | |
location = st.selectbox( | |
"Location", | |
options=list(location_options.keys()), | |
format_func=lambda x: location_options[x], | |
index=list(location_options.keys()).index(st.session_state.cooling_form_data['building_info'].get('location', 'sydney')) if st.session_state.cooling_form_data['building_info'].get('location') in location_options else 0, | |
help="Select the location of the building" | |
) | |
# Get climate data for selected location | |
location_data = ref_data.get_location_data(location) | |
# Indoor design temperature | |
indoor_temp = st.number_input( | |
"Indoor Design Temperature (°C)", | |
value=float(st.session_state.cooling_form_data['building_info'].get('indoor_temp', 24.0)), | |
min_value=18.0, | |
max_value=30.0, | |
step=0.5, | |
help="Recommended indoor design temperature for cooling is 24°C" | |
) | |
with col2: | |
# Building type | |
building_type = st.selectbox( | |
"Building Type", | |
options=["Residential", "Small Office", "Educational", "Other"], | |
index=["Residential", "Small Office", "Educational", "Other"].index(st.session_state.cooling_form_data['building_info'].get('building_type', 'Residential')), | |
help="Select the type of building" | |
) | |
# Outdoor design temperature (with default from location data) | |
outdoor_temp = st.number_input( | |
"Outdoor Design Temperature (°C)", | |
value=float(st.session_state.cooling_form_data['building_info'].get('outdoor_temp', location_data['summer_design_temp'])), | |
min_value=25.0, | |
max_value=45.0, | |
step=0.5, | |
help=f"Default value is based on selected location ({location_data['name']})" | |
) | |
# Daily temperature range | |
daily_range_options = { | |
"low": "Low (< 8.5°C)", | |
"medium": "Medium (8.5-14°C)", | |
"high": "High (> 14°C)" | |
} | |
daily_range = st.selectbox( | |
"Daily Temperature Range", | |
options=list(daily_range_options.keys()), | |
format_func=lambda x: daily_range_options[x], | |
index=list(daily_range_options.keys()).index(st.session_state.cooling_form_data['building_info'].get('daily_range', location_data['daily_temp_range'])), | |
help="Daily temperature range affects solar heat gain calculations" | |
) | |
# Building dimensions | |
st.subheader("Building Dimensions") | |
col1, col2, col3 = st.columns(3) | |
with col1: | |
length = st.number_input( | |
"Length (m)", | |
value=float(st.session_state.cooling_form_data['building_info'].get('length', 10.0)), | |
min_value=1.0, | |
step=0.1, | |
help="Building length in meters" | |
) | |
with col2: | |
width = st.number_input( | |
"Width (m)", | |
value=float(st.session_state.cooling_form_data['building_info'].get('width', 8.0)), | |
min_value=1.0, | |
step=0.1, | |
help="Building width in meters" | |
) | |
with col3: | |
height = st.number_input( | |
"Height (m)", | |
value=float(st.session_state.cooling_form_data['building_info'].get('height', 2.7)), | |
min_value=1.0, | |
step=0.1, | |
help="Floor-to-ceiling height in meters" | |
) | |
# Calculate floor area and volume | |
floor_area = length * width | |
volume = floor_area * height | |
st.info(f"Floor Area: {floor_area:.2f} m² | Volume: {volume:.2f} m³") | |
# Save form data to session state | |
form_data = { | |
'building_name': building_name, | |
'building_type': building_type, | |
'location': location, | |
'location_name': location_data['name'], | |
'indoor_temp': indoor_temp, | |
'outdoor_temp': outdoor_temp, | |
'daily_range': daily_range, | |
'length': length, | |
'width': width, | |
'height': height, | |
'floor_area': floor_area, | |
'volume': volume, | |
'temp_diff': outdoor_temp - indoor_temp | |
} | |
# Validate inputs | |
warnings = [] | |
# Check if building name is provided | |
if not building_name: | |
warnings.append(ValidationWarning("Building name is empty", "Consider adding a building name for reference")) | |
# Check if temperature difference is reasonable | |
if form_data['temp_diff'] <= 0: | |
warnings.append(ValidationWarning( | |
"Invalid temperature difference", | |
"Outdoor temperature should be higher than indoor temperature for cooling load calculation", | |
is_critical=False # Changed to non-critical to allow proceeding with warnings | |
)) | |
# Check if dimensions are reasonable | |
if floor_area > 500: | |
warnings.append(ValidationWarning( | |
"Large floor area", | |
"Floor area exceeds 500 m², verify if this is correct for a residential building" | |
)) | |
if height < 2.4 or height > 3.5: | |
warnings.append(ValidationWarning( | |
"Unusual ceiling height", | |
"Typical residential ceiling heights are between 2.4m and 3.5m" | |
)) | |
# Save warnings to session state | |
st.session_state.cooling_warnings['building_info'] = warnings | |
# Display warnings if any | |
if warnings: | |
st.warning("Please review the following warnings:") | |
for warning in warnings: | |
st.write(f"- {warning.message}" + (" (Critical)" if warning.is_critical else "")) | |
st.write(f" Suggestion: {warning.suggestion}") | |
# Save form data regardless of warnings | |
st.session_state.cooling_form_data['building_info'] = form_data | |
# Mark this step as completed if there are no critical warnings | |
st.session_state.cooling_completed['building_info'] = not any(w.is_critical for w in warnings) | |
# Navigation buttons | |
col1, col2 = st.columns([1, 1]) | |
with col2: | |
next_button = st.button("Next: Building Envelope →", key="building_info_next") | |
if next_button: | |
st.session_state.cooling_active_tab = "building_envelope" | |
st.experimental_rerun() | |
def building_envelope_form(ref_data): | |
""" | |
Form for building envelope information. | |
Args: | |
ref_data: Reference data object | |
""" | |
st.subheader("Building Envelope") | |
st.write("Enter information about walls, roof, and floor construction.") | |
# Get building dimensions from previous step | |
building_info = st.session_state.cooling_form_data['building_info'] | |
length = building_info.get('length', 10.0) | |
width = building_info.get('width', 8.0) | |
height = building_info.get('height', 2.7) | |
temp_diff = building_info.get('temp_diff', 11.0) | |
# Calculate default areas | |
default_wall_area = 2 * (length + width) * height | |
default_roof_area = length * width | |
default_floor_area = length * width | |
# Initialize envelope data if not already in session state | |
if 'walls' not in st.session_state.cooling_form_data['building_envelope']: | |
st.session_state.cooling_form_data['building_envelope']['walls'] = [] | |
if 'roof' not in st.session_state.cooling_form_data['building_envelope']: | |
st.session_state.cooling_form_data['building_envelope']['roof'] = {} | |
if 'floor' not in st.session_state.cooling_form_data['building_envelope']: | |
st.session_state.cooling_form_data['building_envelope']['floor'] = {} | |
# Walls section | |
st.write("### Walls") | |
# Get wall material options from reference data | |
wall_material_options = {mat_id: mat_data['name'] for mat_id, mat_data in ref_data.materials['walls'].items()} | |
# Add custom option | |
wall_material_options["custom_walls"] = "Custom Wall (User-defined)" | |
# Display existing wall entries | |
if st.session_state.cooling_form_data['building_envelope']['walls']: | |
st.write("Current walls:") | |
walls_df = pd.DataFrame(st.session_state.cooling_form_data['building_envelope']['walls']) | |
walls_df['Material'] = walls_df['material_id'].map(lambda x: wall_material_options.get(x, "Unknown")) | |
# Add orientation column with default value if not present | |
walls_df['orientation'] = walls_df['orientation'].fillna('not specified') | |
walls_df = walls_df[['name', 'Material', 'area', 'u_value', 'orientation']] | |
walls_df.columns = ['Name', 'Material', 'Area (m²)', 'U-Value (W/m²°C)', 'Orientation'] | |
st.dataframe(walls_df) | |
# Add new wall form | |
st.write("Add a new wall:") | |
col1, col2 = st.columns(2) | |
with col1: | |
wall_name = st.text_input("Wall Name", value="", key="new_wall_name") | |
wall_material = st.selectbox( | |
"Wall Material", | |
options=list(wall_material_options.keys()), | |
format_func=lambda x: wall_material_options[x], | |
key="new_wall_material" | |
) | |
# Add wall orientation selection | |
wall_orientation = st.selectbox( | |
"Wall Orientation", | |
options=["north", "east", "south", "west"], | |
key="new_wall_orientation" | |
) | |
# Get material properties | |
material_data = ref_data.get_material_by_type("walls", wall_material) | |
u_value = material_data['u_value'] | |
# Add custom U-value input if custom material is selected | |
if wall_material == "custom_walls": | |
u_value = st.number_input( | |
"Custom U-Value (W/m²°C)", | |
value=1.0, | |
min_value=0.1, | |
max_value=5.0, | |
step=0.1, | |
key="custom_wall_u_value" | |
) | |
# Store custom material in session state | |
if "custom_materials" not in st.session_state: | |
st.session_state.custom_materials = {} | |
st.session_state.custom_materials["walls"] = { | |
"name": "Custom Wall", | |
"u_value": u_value, | |
"r_value": 1.0 / u_value if u_value > 0 else 1.0, | |
"description": "Custom wall with user-defined properties" | |
} | |
with col2: | |
wall_area = st.number_input( | |
"Wall Area (m²)", | |
value=default_wall_area / 4, # Default to 1/4 of total wall area as a starting point | |
min_value=0.1, | |
step=0.1, | |
key="new_wall_area" | |
) | |
st.write(f"Material U-Value: {u_value} W/m²°C") | |
st.write(f"Heat Transfer: {u_value * wall_area * temp_diff:.2f} W") | |
# Add wall button | |
if st.button("Add Wall"): | |
new_wall = { | |
'name': wall_name if wall_name else f"Wall {len(st.session_state.cooling_form_data['building_envelope']['walls']) + 1}", | |
'material_id': wall_material, | |
'area': wall_area, | |
'u_value': u_value, | |
'temp_diff': temp_diff, | |
'orientation': wall_orientation # Add orientation to wall data | |
} | |
st.session_state.cooling_form_data['building_envelope']['walls'].append(new_wall) | |
st.experimental_rerun() | |
# Roof section | |
st.write("### Roof") | |
# Get roof material options from reference data | |
roof_material_options = {mat_id: mat_data['name'] for mat_id, mat_data in ref_data.materials['roofs'].items()} | |
# Add custom option | |
roof_material_options["custom_roofs"] = "Custom Roof (User-defined)" | |
col1, col2 = st.columns(2) | |
with col1: | |
roof_material = st.selectbox( | |
"Roof Material", | |
options=list(roof_material_options.keys()), | |
format_func=lambda x: roof_material_options[x], | |
index=list(roof_material_options.keys()).index(st.session_state.cooling_form_data['building_envelope'].get('roof', {}).get('material_id', 'metal_deck_insulated')) if st.session_state.cooling_form_data['building_envelope'].get('roof', {}).get('material_id') in roof_material_options else 0 | |
) | |
# Get material properties | |
material_data = ref_data.get_material_by_type("roofs", roof_material) | |
roof_u_value = material_data['u_value'] | |
# Add custom U-value input if custom material is selected | |
if roof_material == "custom_roofs": | |
roof_u_value = st.number_input( | |
"Custom Roof U-Value (W/m²°C)", | |
value=1.0, | |
min_value=0.1, | |
max_value=5.0, | |
step=0.1, | |
key="custom_roof_u_value" | |
) | |
# Store custom material in session state | |
if "custom_materials" not in st.session_state: | |
st.session_state.custom_materials = {} | |
st.session_state.custom_materials["roofs"] = { | |
"name": "Custom Roof", | |
"u_value": roof_u_value, | |
"r_value": 1.0 / roof_u_value if roof_u_value > 0 else 1.0, | |
"description": "Custom roof with user-defined properties" | |
} | |
with col2: | |
roof_area = st.number_input( | |
"Roof Area (m²)", | |
value=float(st.session_state.cooling_form_data['building_envelope'].get('roof', {}).get('area', default_roof_area)), | |
min_value=0.1, | |
step=0.1 | |
) | |
st.write(f"Material U-Value: {roof_u_value} W/m²°C") | |
st.write(f"Heat Transfer: {roof_u_value * roof_area * temp_diff:.2f} W") | |
# Save roof data | |
st.session_state.cooling_form_data['building_envelope']['roof'] = { | |
'material_id': roof_material, | |
'area': roof_area, | |
'u_value': roof_u_value, | |
'temp_diff': temp_diff | |
} | |
# Floor section | |
st.write("### Floor") | |
# Get floor material options from reference data | |
floor_material_options = {mat_id: mat_data['name'] for mat_id, mat_data in ref_data.materials['floors'].items()} | |
# Add custom option | |
floor_material_options["custom_floors"] = "Custom Floor (User-defined)" | |
col1, col2 = st.columns(2) | |
with col1: | |
floor_material = st.selectbox( | |
"Floor Material", | |
options=list(floor_material_options.keys()), | |
format_func=lambda x: floor_material_options[x], | |
index=list(floor_material_options.keys()).index(st.session_state.cooling_form_data['building_envelope'].get('floor', {}).get('material_id', 'concrete_slab_ground')) if st.session_state.cooling_form_data['building_envelope'].get('floor', {}).get('material_id') in floor_material_options else 0 | |
) | |
# Get material properties | |
material_data = ref_data.get_material_by_type("floors", floor_material) | |
floor_u_value = material_data['u_value'] | |
# Add custom U-value input if custom material is selected | |
if floor_material == "custom_floors": | |
floor_u_value = st.number_input( | |
"Custom Floor U-Value (W/m²°C)", | |
value=1.0, | |
min_value=0.1, | |
max_value=5.0, | |
step=0.1, | |
key="custom_floor_u_value" | |
) | |
# Store custom material in session state | |
if "custom_materials" not in st.session_state: | |
st.session_state.custom_materials = {} | |
st.session_state.custom_materials["floors"] = { | |
"name": "Custom Floor", | |
"u_value": floor_u_value, | |
"r_value": 1.0 / floor_u_value if floor_u_value > 0 else 1.0, | |
"description": "Custom floor with user-defined properties" | |
} | |
with col2: | |
floor_area = st.number_input( | |
"Floor Area (m²)", | |
value=float(st.session_state.cooling_form_data['building_envelope'].get('floor', {}).get('area', default_floor_area)), | |
min_value=0.1, | |
step=0.1 | |
) | |
st.write(f"Material U-Value: {floor_u_value} W/m²°C") | |
st.write(f"Heat Transfer: {floor_u_value * floor_area * temp_diff:.2f} W") | |
# Save floor data | |
st.session_state.cooling_form_data['building_envelope']['floor'] = { | |
'material_id': floor_material, | |
'area': floor_area, | |
'u_value': floor_u_value, | |
'temp_diff': temp_diff | |
} | |
# Validate inputs | |
warnings = [] | |
# Check if walls are defined | |
if not st.session_state.cooling_form_data['building_envelope']['walls']: | |
warnings.append(ValidationWarning( | |
"No walls defined", | |
"Add at least one wall to continue", | |
is_critical=False # Changed to non-critical to allow proceeding with warnings | |
)) | |
# Check if total wall area is reasonable | |
total_wall_area = sum(wall['area'] for wall in st.session_state.cooling_form_data['building_envelope']['walls']) | |
expected_wall_area = 2 * (length + width) * height | |
if total_wall_area < expected_wall_area * 0.8 or total_wall_area > expected_wall_area * 1.2: | |
warnings.append(ValidationWarning( | |
"Unusual wall area", | |
f"Total wall area ({total_wall_area:.2f} m²) differs significantly from the expected area ({expected_wall_area:.2f} m²) based on building dimensions" | |
)) | |
# Check if roof area matches floor area | |
if abs(roof_area - floor_area) > 1.0: | |
warnings.append(ValidationWarning( | |
"Roof area doesn't match floor area", | |
"For a simple building, roof area should approximately match floor area" | |
)) | |
# Save warnings to session state | |
st.session_state.cooling_warnings['building_envelope'] = warnings | |
# Display warnings if any | |
if warnings: | |
st.warning("Please review the following warnings:") | |
for warning in warnings: | |
st.write(f"- {warning.message}" + (" (Critical)" if warning.is_critical else "")) | |
st.write(f" Suggestion: {warning.suggestion}") | |
# Mark this step as completed if there are no critical warnings | |
st.session_state.cooling_completed['building_envelope'] = not any(w.is_critical for w in warnings) | |
# Navigation buttons | |
col1, col2 = st.columns([1, 1]) | |
with col1: | |
prev_button = st.button("← Back: Building Information", key="building_envelope_prev") | |
if prev_button: | |
st.session_state.cooling_active_tab = "building_info" | |
st.experimental_rerun() | |
with col2: | |
next_button = st.button("Next: Windows & Doors →", key="building_envelope_next") | |
if next_button: | |
st.session_state.cooling_active_tab = "windows" | |
st.experimental_rerun() | |
def windows_form(ref_data): | |
""" | |
Form for windows and doors information. | |
Args: | |
ref_data: Reference data object | |
""" | |
st.subheader("Windows & Doors") | |
st.write("Enter information about windows and doors.") | |
# Get temperature difference from building info | |
temp_diff = st.session_state.cooling_form_data['building_info'].get('temp_diff', 11.0) | |
daily_range = st.session_state.cooling_form_data['building_info'].get('daily_range', 'medium') | |
# Initialize windows data if not already in session state | |
if 'windows' not in st.session_state.cooling_form_data['windows']: | |
st.session_state.cooling_form_data['windows']['windows'] = [] | |
if 'doors' not in st.session_state.cooling_form_data['windows']: | |
st.session_state.cooling_form_data['windows']['doors'] = [] | |
# Windows section | |
st.write("### Windows") | |
# Get glass type options from reference data | |
glass_type_options = {glass_id: glass_data['name'] for glass_id, glass_data in ref_data.glass_types.items()} | |
# Get shading options from reference data | |
shading_options = {shade_id: shade_data['name'] for shade_id, shade_data in ref_data.shading_factors.items()} | |
# Display existing window entries | |
if st.session_state.cooling_form_data['windows']['windows']: | |
st.write("Current windows:") | |
windows_df = pd.DataFrame(st.session_state.cooling_form_data['windows']['windows']) | |
windows_df['Glass Type'] = windows_df['glass_type'].map(lambda x: glass_type_options.get(x, "Unknown")) | |
windows_df['Shading'] = windows_df['shading'].map(lambda x: shading_options.get(x, "Unknown")) | |
windows_df = windows_df[['name', 'orientation', 'Glass Type', 'Shading', 'area', 'u_value']] | |
windows_df.columns = ['Name', 'Orientation', 'Glass Type', 'Shading', 'Area (m²)', 'U-Value (W/m²°C)'] | |
st.dataframe(windows_df) | |
# Add new window form | |
st.write("Add a new window:") | |
col1, col2 = st.columns(2) | |
with col1: | |
window_name = st.text_input("Window Name", value="", key="new_window_name") | |
orientation = st.selectbox( | |
"Orientation", | |
options=["north", "east", "south", "west", "horizontal"], | |
key="new_window_orientation" | |
) | |
glass_type = st.selectbox( | |
"Glass Type", | |
options=list(glass_type_options.keys()), | |
format_func=lambda x: glass_type_options[x], | |
key="new_window_glass_type" | |
) | |
# Get glass properties | |
glass_data = ref_data.get_glass_type(glass_type) | |
window_u_value = glass_data['u_value'] | |
with col2: | |
window_area = st.number_input( | |
"Window Area (m²)", | |
value=2.0, | |
min_value=0.1, | |
step=0.1, | |
key="new_window_area" | |
) | |
shading = st.selectbox( | |
"Shading", | |
options=list(shading_options.keys()), | |
format_func=lambda x: shading_options[x], | |
key="new_window_shading" | |
) | |
# Get shading factor | |
shading_data = ref_data.get_shading_factor(shading) | |
shade_factor = shading_data['factor'] | |
st.write(f"Glass U-Value: {window_u_value} W/m²°C") | |
st.write(f"Conduction Heat Transfer: {window_u_value * window_area * temp_diff:.2f} W") | |
# Add window button | |
if st.button("Add Window"): | |
# Calculate solar heat gain factor | |
calculator = CoolingLoadCalculator() | |
shgf = calculator.get_solar_heat_gain_factor( | |
orientation=orientation, | |
glass_type=glass_type, | |
daily_range=daily_range | |
) | |
new_window = { | |
'name': window_name if window_name else f"Window {len(st.session_state.cooling_form_data['windows']['windows']) + 1}", | |
'orientation': orientation, | |
'glass_type': glass_type, | |
'shading': shading, | |
'area': window_area, | |
'u_value': window_u_value, | |
'shgf': shgf, | |
'shade_factor': shade_factor, | |
'temp_diff': temp_diff | |
} | |
st.session_state.cooling_form_data['windows']['windows'].append(new_window) | |
st.experimental_rerun() | |
# Doors section | |
st.write("### Doors") | |
# Display existing door entries | |
if st.session_state.cooling_form_data['windows']['doors']: | |
st.write("Current doors:") | |
doors_df = pd.DataFrame(st.session_state.cooling_form_data['windows']['doors']) | |
doors_df = doors_df[['name', 'type', 'area', 'u_value']] | |
doors_df.columns = ['Name', 'Type', 'Area (m²)', 'U-Value (W/m²°C)'] | |
st.dataframe(doors_df) | |
# Add new door form | |
st.write("Add a new door:") | |
col1, col2 = st.columns(2) | |
with col1: | |
door_name = st.text_input("Door Name", value="", key="new_door_name") | |
door_type = st.selectbox( | |
"Door Type", | |
options=["Solid wood", "Hollow core", "Glass", "Insulated"], | |
key="new_door_type" | |
) | |
# Set U-value based on door type | |
door_u_values = { | |
"Solid wood": 2.0, | |
"Hollow core": 2.5, | |
"Glass": 5.0, | |
"Insulated": 1.2 | |
} | |
door_u_value = door_u_values[door_type] | |
with col2: | |
door_area = st.number_input( | |
"Door Area (m²)", | |
value=2.0, | |
min_value=0.1, | |
step=0.1, | |
key="new_door_area" | |
) | |
st.write(f"Door U-Value: {door_u_value} W/m²°C") | |
st.write(f"Heat Transfer: {door_u_value * door_area * temp_diff:.2f} W") | |
# Add door button | |
if st.button("Add Door"): | |
new_door = { | |
'name': door_name if door_name else f"Door {len(st.session_state.cooling_form_data['windows']['doors']) + 1}", | |
'type': door_type, | |
'area': door_area, | |
'u_value': door_u_value, | |
'temp_diff': temp_diff | |
} | |
st.session_state.cooling_form_data['windows']['doors'].append(new_door) | |
st.experimental_rerun() | |
# Validate inputs | |
warnings = [] | |
# Check if windows are defined | |
if not st.session_state.cooling_form_data['windows']['windows']: | |
warnings.append(ValidationWarning( | |
"No windows defined", | |
"Add at least one window to continue" | |
)) | |
# Check window-to-wall ratio | |
if st.session_state.cooling_form_data['windows']['windows']: | |
total_window_area = sum(window['area'] for window in st.session_state.cooling_form_data['windows']['windows']) | |
total_wall_area = sum(wall['area'] for wall in st.session_state.cooling_form_data['building_envelope']['walls']) | |
window_wall_ratio = total_window_area / total_wall_area if total_wall_area > 0 else 0 | |
if window_wall_ratio > 0.6: | |
warnings.append(ValidationWarning( | |
"High window-to-wall ratio", | |
f"Window-to-wall ratio is {window_wall_ratio:.2f}, which is unusually high. Typical ratios are 0.2-0.4." | |
)) | |
# Save warnings to session state | |
st.session_state.cooling_warnings['windows'] = warnings | |
# Display warnings if any | |
if warnings: | |
st.warning("Please review the following warnings:") | |
for warning in warnings: | |
st.write(f"- {warning.message}" + (" (Critical)" if warning.is_critical else "")) | |
st.write(f" Suggestion: {warning.suggestion}") | |
# Mark this step as completed if there are no critical warnings | |
st.session_state.cooling_completed['windows'] = not any(w.is_critical for w in warnings) | |
# Navigation buttons | |
col1, col2 = st.columns([1, 1]) | |
with col1: | |
prev_button = st.button("← Back: Building Envelope", key="windows_prev") | |
if prev_button: | |
st.session_state.cooling_active_tab = "building_envelope" | |
st.experimental_rerun() | |
with col2: | |
next_button = st.button("Next: Internal Loads →", key="windows_next") | |
if next_button: | |
st.session_state.cooling_active_tab = "internal_loads" | |
st.experimental_rerun() | |
def internal_loads_form(ref_data): | |
""" | |
Form for internal loads information. | |
Args: | |
ref_data: Reference data object | |
""" | |
st.subheader("Internal Loads") | |
st.write("Enter information about occupants, lighting, and equipment.") | |
# Initialize internal loads data if not already in session state | |
if 'occupants' not in st.session_state.cooling_form_data['internal_loads']: | |
st.session_state.cooling_form_data['internal_loads']['occupants'] = { | |
'count': 4, | |
'activity_level': 'seated_resting' | |
} | |
if 'lighting' not in st.session_state.cooling_form_data['internal_loads']: | |
st.session_state.cooling_form_data['internal_loads']['lighting'] = { | |
'type': 'led', | |
'power_density': 5.0 # W/m² | |
} | |
if 'appliances' not in st.session_state.cooling_form_data['internal_loads']: | |
st.session_state.cooling_form_data['internal_loads']['appliances'] = { | |
'kitchen': True, | |
'living_room': True, | |
'bedroom': True, | |
'office': False | |
} | |
# Occupants section | |
st.write("### Occupants") | |
col1, col2 = st.columns(2) | |
with col1: | |
occupant_count = st.number_input( | |
"Number of Occupants", | |
value=int(st.session_state.cooling_form_data['internal_loads']['occupants'].get('count', 4)), | |
min_value=1, | |
step=1 | |
) | |
with col2: | |
# Get activity level options from reference data | |
activity_options = {act_id: act_data['name'] for act_id, act_data in ref_data.internal_loads['people'].items()} | |
activity_level = st.selectbox( | |
"Activity Level", | |
options=list(activity_options.keys()), | |
format_func=lambda x: activity_options[x], | |
index=list(activity_options.keys()).index(st.session_state.cooling_form_data['internal_loads']['occupants'].get('activity_level', 'seated_resting')) if st.session_state.cooling_form_data['internal_loads']['occupants'].get('activity_level') in activity_options else 0 | |
) | |
# Get heat gain per person | |
activity_data = ref_data.get_internal_load('people', activity_level) | |
sensible_heat_pp = activity_data['sensible_heat'] | |
latent_heat_pp = activity_data['latent_heat'] | |
total_heat_pp = sensible_heat_pp + latent_heat_pp | |
st.write(f"Heat gain per person: {total_heat_pp} W ({sensible_heat_pp} W sensible + {latent_heat_pp} W latent)") | |
st.write(f"Total occupant heat gain: {total_heat_pp * occupant_count} W") | |
# Save occupants data | |
st.session_state.cooling_form_data['internal_loads']['occupants'] = { | |
'count': occupant_count, | |
'activity_level': activity_level, | |
'sensible_heat_pp': sensible_heat_pp, | |
'latent_heat_pp': latent_heat_pp, | |
'total_heat_gain': total_heat_pp * occupant_count | |
} | |
# Lighting section | |
st.write("### Lighting") | |
col1, col2 = st.columns(2) | |
with col1: | |
# Get lighting type options from reference data | |
lighting_options = {light_id: light_data['name'] for light_id, light_data in ref_data.internal_loads['lighting'].items()} | |
lighting_type = st.selectbox( | |
"Lighting Type", | |
options=list(lighting_options.keys()), | |
format_func=lambda x: lighting_options[x], | |
index=list(lighting_options.keys()).index(st.session_state.cooling_form_data['internal_loads']['lighting'].get('type', 'led')) if st.session_state.cooling_form_data['internal_loads']['lighting'].get('type') in lighting_options else 0 | |
) | |
with col2: | |
lighting_power_density = st.number_input( | |
"Lighting Power Density (W/m²)", | |
value=float(st.session_state.cooling_form_data['internal_loads']['lighting'].get('power_density', 5.0)), | |
min_value=1.0, | |
max_value=20.0, | |
step=0.5, | |
help="Typical values: Residential 5-10 W/m², Office 10-15 W/m²" | |
) | |
# Get lighting heat factor | |
lighting_data = ref_data.get_internal_load('lighting', lighting_type) | |
lighting_heat_factor = lighting_data['heat_factor'] | |
# Calculate lighting heat gain | |
floor_area = st.session_state.cooling_form_data['building_info'].get('floor_area', 80.0) | |
lighting_heat_gain = lighting_power_density * floor_area * lighting_heat_factor | |
st.write(f"Lighting heat factor: {lighting_heat_factor}") | |
st.write(f"Total lighting heat gain: {lighting_heat_gain:.2f} W") | |
# Save lighting data | |
st.session_state.cooling_form_data['internal_loads']['lighting'] = { | |
'type': lighting_type, | |
'power_density': lighting_power_density, | |
'heat_factor': lighting_heat_factor, | |
'total_heat_gain': lighting_heat_gain | |
} | |
# Appliances section | |
st.write("### Appliances") | |
# Get appliance options from reference data | |
appliance_options = {app_id: app_data for app_id, app_data in ref_data.internal_loads['appliances'].items()} | |
col1, col2 = st.columns(2) | |
with col1: | |
has_kitchen = st.checkbox( | |
"Kitchen Appliances", | |
value=st.session_state.cooling_form_data['internal_loads']['appliances'].get('kitchen', True), | |
help=f"Heat gain: {appliance_options['kitchen']['heat_gain']} W" | |
) | |
has_living_room = st.checkbox( | |
"Living Room Appliances", | |
value=st.session_state.cooling_form_data['internal_loads']['appliances'].get('living_room', True), | |
help=f"Heat gain: {appliance_options['living_room']['heat_gain']} W" | |
) | |
with col2: | |
has_bedroom = st.checkbox( | |
"Bedroom Appliances", | |
value=st.session_state.cooling_form_data['internal_loads']['appliances'].get('bedroom', True), | |
help=f"Heat gain: {appliance_options['bedroom']['heat_gain']} W" | |
) | |
has_office = st.checkbox( | |
"Home Office Equipment", | |
value=st.session_state.cooling_form_data['internal_loads']['appliances'].get('office', False), | |
help=f"Heat gain: {appliance_options['office']['heat_gain']} W" | |
) | |
# Calculate appliance heat gain | |
appliance_heat_gain = 0 | |
if has_kitchen: | |
appliance_heat_gain += appliance_options['kitchen']['heat_gain'] | |
if has_living_room: | |
appliance_heat_gain += appliance_options['living_room']['heat_gain'] | |
if has_bedroom: | |
appliance_heat_gain += appliance_options['bedroom']['heat_gain'] | |
if has_office: | |
appliance_heat_gain += appliance_options['office']['heat_gain'] | |
st.write(f"Total appliance heat gain: {appliance_heat_gain} W") | |
# Save appliances data | |
st.session_state.cooling_form_data['internal_loads']['appliances'] = { | |
'kitchen': has_kitchen, | |
'living_room': has_living_room, | |
'bedroom': has_bedroom, | |
'office': has_office, | |
'total_heat_gain': appliance_heat_gain | |
} | |
# Calculate total internal heat gain | |
total_internal_gain = ( | |
st.session_state.cooling_form_data['internal_loads']['occupants']['total_heat_gain'] + | |
st.session_state.cooling_form_data['internal_loads']['lighting']['total_heat_gain'] + | |
st.session_state.cooling_form_data['internal_loads']['appliances']['total_heat_gain'] | |
) | |
st.info(f"Total Internal Heat Gain: {total_internal_gain:.2f} W") | |
# Save total internal gain | |
st.session_state.cooling_form_data['internal_loads']['total_internal_gain'] = total_internal_gain | |
# Validate inputs | |
warnings = [] | |
# Check if occupant count is reasonable for the floor area | |
floor_area = st.session_state.cooling_form_data['building_info'].get('floor_area', 80.0) | |
area_per_person = floor_area / occupant_count if occupant_count > 0 else float('inf') | |
if area_per_person < 10: | |
warnings.append(ValidationWarning( | |
"High occupant density", | |
f"Area per person ({area_per_person:.2f} m²) is low. Typical residential values are 20-30 m² per person." | |
)) | |
# Check if lighting power density is reasonable | |
if lighting_power_density > 15: | |
warnings.append(ValidationWarning( | |
"High lighting power density", | |
"Lighting power density exceeds 15 W/m², which is high for residential buildings." | |
)) | |
# Save warnings to session state | |
st.session_state.cooling_warnings['internal_loads'] = warnings | |
# Display warnings if any | |
if warnings: | |
st.warning("Please review the following warnings:") | |
for warning in warnings: | |
st.write(f"- {warning.message}" + (" (Critical)" if warning.is_critical else "")) | |
st.write(f" Suggestion: {warning.suggestion}") | |
# Mark this step as completed if there are no critical warnings | |
st.session_state.cooling_completed['internal_loads'] = not any(w.is_critical for w in warnings) | |
# Navigation buttons | |
col1, col2 = st.columns([1, 1]) | |
with col1: | |
prev_button = st.button("← Back: Windows & Doors", key="internal_loads_prev") | |
if prev_button: | |
st.session_state.cooling_active_tab = "windows" | |
st.experimental_rerun() | |
with col2: | |
next_button = st.button("Next: Ventilation →", key="internal_loads_next") | |
if next_button: | |
st.session_state.cooling_active_tab = "ventilation" | |
st.experimental_rerun() | |
def ventilation_form(ref_data): | |
""" | |
Form for ventilation and infiltration information. | |
Args: | |
ref_data: Reference data object | |
""" | |
st.subheader("Ventilation & Infiltration") | |
st.write("Enter information about ventilation and infiltration rates.") | |
# Get building info | |
building_info = st.session_state.cooling_form_data['building_info'] | |
volume = building_info.get('volume', 216.0) | |
temp_diff = building_info.get('temp_diff', 11.0) | |
# Initialize ventilation data if not already in session state | |
if 'infiltration' not in st.session_state.cooling_form_data['ventilation']: | |
st.session_state.cooling_form_data['ventilation']['infiltration'] = { | |
'air_changes': 0.5 | |
} | |
if 'ventilation' not in st.session_state.cooling_form_data['ventilation']: | |
st.session_state.cooling_form_data['ventilation']['ventilation'] = { | |
'type': 'natural', | |
'air_changes': 0.0 | |
} | |
# Infiltration section | |
st.write("### Infiltration") | |
st.write("Infiltration is the unintended air leakage through the building envelope.") | |
infiltration_ach = st.slider( | |
"Infiltration Rate (air changes per hour)", | |
value=float(st.session_state.cooling_form_data['ventilation']['infiltration'].get('air_changes', 0.5)), | |
min_value=0.1, | |
max_value=2.0, | |
step=0.1, | |
help="Typical values: 0.5 ACH for modern construction, 1.0 ACH for average construction, 1.5+ ACH for older buildings" | |
) | |
# Calculate infiltration heat gain | |
infiltration_heat_gain = 0.33 * volume * infiltration_ach * temp_diff | |
st.write(f"Infiltration heat gain: {infiltration_heat_gain:.2f} W") | |
# Save infiltration data | |
st.session_state.cooling_form_data['ventilation']['infiltration'] = { | |
'air_changes': infiltration_ach, | |
'volume': volume, | |
'temp_diff': temp_diff, | |
'heat_gain': infiltration_heat_gain | |
} | |
# Ventilation section | |
st.write("### Ventilation") | |
st.write("Ventilation is the intentional introduction of outside air into the building.") | |
col1, col2 = st.columns(2) | |
with col1: | |
ventilation_type = st.selectbox( | |
"Ventilation Type", | |
options=["natural", "mechanical", "mixed"], | |
format_func=lambda x: x.capitalize(), | |
index=["natural", "mechanical", "mixed"].index(st.session_state.cooling_form_data['ventilation']['ventilation'].get('type', 'natural')) | |
) | |
with col2: | |
ventilation_ach = st.number_input( | |
"Ventilation Rate (air changes per hour)", | |
value=float(st.session_state.cooling_form_data['ventilation']['ventilation'].get('air_changes', 0.0)), | |
min_value=0.0, | |
max_value=5.0, | |
step=0.1, | |
help="Typical values: 0.35-1.0 ACH for residential buildings" | |
) | |
# Calculate ventilation heat gain | |
ventilation_heat_gain = 0.33 * volume * ventilation_ach * temp_diff | |
st.write(f"Ventilation heat gain: {ventilation_heat_gain:.2f} W") | |
# Save ventilation data | |
st.session_state.cooling_form_data['ventilation']['ventilation'] = { | |
'type': ventilation_type, | |
'air_changes': ventilation_ach, | |
'volume': volume, | |
'temp_diff': temp_diff, | |
'heat_gain': ventilation_heat_gain | |
} | |
# Calculate total ventilation and infiltration heat gain | |
total_ventilation_gain = infiltration_heat_gain + ventilation_heat_gain | |
st.info(f"Total Ventilation & Infiltration Heat Gain: {total_ventilation_gain:.2f} W") | |
# Save total ventilation gain | |
st.session_state.cooling_form_data['ventilation']['total_gain'] = total_ventilation_gain | |
# Validate inputs | |
warnings = [] | |
# Check if infiltration rate is reasonable | |
if infiltration_ach < 0.3: | |
warnings.append(ValidationWarning( | |
"Low infiltration rate", | |
"Infiltration rate below 0.3 ACH is unusually low for most buildings." | |
)) | |
elif infiltration_ach > 1.5: | |
warnings.append(ValidationWarning( | |
"High infiltration rate", | |
"Infiltration rate above 1.5 ACH indicates a leaky building envelope." | |
)) | |
# Check if ventilation rate is reasonable | |
if ventilation_ach > 0 and ventilation_ach < 0.35: | |
warnings.append(ValidationWarning( | |
"Low ventilation rate", | |
"Ventilation rate below 0.35 ACH may not provide adequate fresh air." | |
)) | |
elif ventilation_ach > 2.0: | |
warnings.append(ValidationWarning( | |
"High ventilation rate", | |
"Ventilation rate above 2.0 ACH is unusually high for residential buildings." | |
)) | |
# Save warnings to session state | |
st.session_state.cooling_warnings['ventilation'] = warnings | |
# Display warnings if any | |
if warnings: | |
st.warning("Please review the following warnings:") | |
for warning in warnings: | |
st.write(f"- {warning.message}" + (" (Critical)" if warning.is_critical else "")) | |
st.write(f" Suggestion: {warning.suggestion}") | |
# Mark this step as completed if there are no critical warnings | |
st.session_state.cooling_completed['ventilation'] = not any(w.is_critical for w in warnings) | |
# Navigation buttons | |
col1, col2 = st.columns([1, 1]) | |
with col1: | |
prev_button = st.button("← Back: Internal Loads", key="ventilation_prev") | |
if prev_button: | |
st.session_state.cooling_active_tab = "internal_loads" | |
st.experimental_rerun() | |
with col2: | |
calculate_button = st.button("Calculate Results →", key="ventilation_calculate") | |
if calculate_button: | |
# Calculate cooling load | |
calculate_cooling_load() | |
st.session_state.cooling_active_tab = "results" | |
st.experimental_rerun() | |
def calculate_cooling_load(): | |
"""Calculate cooling load based on input data.""" | |
# Create calculator instance | |
calculator = CoolingLoadCalculator() | |
# Get form data | |
form_data = st.session_state.cooling_form_data | |
# Prepare building components for calculation | |
building_components = [] | |
# Add walls | |
for wall in form_data['building_envelope'].get('walls', []): | |
building_components.append({ | |
'name': wall['name'], | |
'area': wall['area'], | |
'u_value': wall['u_value'], | |
'temp_diff': wall['temp_diff'] | |
}) | |
# Add roof | |
roof = form_data['building_envelope'].get('roof', {}) | |
if roof: | |
building_components.append({ | |
'name': 'Roof', | |
'area': roof['area'], | |
'u_value': roof['u_value'], | |
'temp_diff': roof['temp_diff'] | |
}) | |
# Add floor | |
floor = form_data['building_envelope'].get('floor', {}) | |
if floor: | |
building_components.append({ | |
'name': 'Floor', | |
'area': floor['area'], | |
'u_value': floor['u_value'], | |
'temp_diff': floor['temp_diff'] | |
}) | |
# Prepare windows for calculation | |
windows = [] | |
for window in form_data['windows'].get('windows', []): | |
windows.append({ | |
'name': window['name'], | |
'area': window['area'], | |
'u_value': window['u_value'], | |
'orientation': window['orientation'], | |
'glass_type': window['glass_type'], | |
'shading': window['shading'], | |
'shgf': window['shgf'], | |
'shade_factor': 1.0 - window['shade_factor'], | |
'temp_diff': window['temp_diff'] | |
}) | |
# Add doors to building components | |
for door in form_data['windows'].get('doors', []): | |
building_components.append({ | |
'name': door['name'], | |
'area': door['area'], | |
'u_value': door['u_value'], | |
'temp_diff': door['temp_diff'] | |
}) | |
# Prepare infiltration data | |
infiltration = form_data['ventilation'].get('infiltration', {}) | |
ventilation = form_data['ventilation'].get('ventilation', {}) | |
infiltration_data = { | |
'volume': infiltration.get('volume', 0), | |
'air_changes': infiltration.get('air_changes', 0) + ventilation.get('air_changes', 0), | |
'temp_diff': infiltration.get('temp_diff', 0) | |
} | |
# Prepare internal gains data | |
internal_gains = { | |
'num_people': form_data['internal_loads'].get('occupants', {}).get('count', 0), | |
'has_kitchen': form_data['internal_loads'].get('appliances', {}).get('kitchen', False), | |
'equipment_watts': ( | |
form_data['internal_loads'].get('lighting', {}).get('total_heat_gain', 0) + | |
form_data['internal_loads'].get('appliances', {}).get('total_heat_gain', 0) - | |
(1000 if form_data['internal_loads'].get('appliances', {}).get('kitchen', False) else 0) # Subtract kitchen heat gain if included | |
) | |
} | |
# Calculate cooling load | |
results = calculator.calculate_total_cooling_load( | |
building_components=building_components, | |
windows=windows, | |
infiltration=infiltration_data, | |
internal_gains=internal_gains | |
) | |
# Save results to session state | |
st.session_state.cooling_results = results | |
# Add timestamp | |
st.session_state.cooling_results['timestamp'] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") | |
# Add building info | |
st.session_state.cooling_results['building_info'] = form_data['building_info'] | |
return results | |
def results_page(): | |
"""Display calculation results.""" | |
st.subheader("Cooling Load Calculation Results") | |
# Check if results are available | |
if not st.session_state.cooling_results: | |
st.warning("No calculation results available. Please complete the input forms and calculate results.") | |
return | |
# Get results | |
results = st.session_state.cooling_results | |
# Display summary | |
st.write("### Summary") | |
col1, col2 = st.columns(2) | |
with col1: | |
st.metric("Sensible Cooling Load", f"{results['sensible_load']:.2f} W") | |
st.metric("Total Cooling Load", f"{results['total_load']:.2f} W") | |
# Convert to kW | |
total_load_kw = results['total_load'] / 1000 | |
st.metric("Total Cooling Load", f"{total_load_kw:.2f} kW") | |
with col2: | |
st.metric("Latent Cooling Load", f"{results['latent_load']:.2f} W") | |
# Calculate cooling load per area | |
floor_area = results['building_info'].get('floor_area', 80.0) | |
cooling_load_per_area = results['total_load'] / floor_area | |
st.metric("Cooling Load per Area", f"{cooling_load_per_area:.2f} W/m²") | |
# Equipment sizing recommendation | |
# Add 10% safety factor | |
recommended_size = total_load_kw * 1.1 | |
st.metric("Recommended Equipment Size", f"{recommended_size:.2f} kW") | |
# Display load breakdown | |
st.write("### Load Breakdown") | |
# Prepare data for pie chart | |
load_components = { | |
'Conduction (Opaque Surfaces)': results['conduction_gain'], | |
'Conduction (Windows)': results['window_conduction_gain'], | |
'Solar Radiation (Windows)': results['window_solar_gain'], | |
'Infiltration & Ventilation': results['infiltration_gain'], | |
'Internal Gains': results['internal_gain'] | |
} | |
# Create pie chart | |
fig = px.pie( | |
values=list(load_components.values()), | |
names=list(load_components.keys()), | |
title="Cooling Load Components", | |
color_discrete_sequence=px.colors.sequential.Turbo, | |
hole=0.4, # Create a donut chart for better readability | |
labels={'label': 'Component', 'value': 'Heat Gain (W)'} | |
) | |
# Improve layout and formatting | |
fig.update_traces( | |
textposition='inside', | |
textinfo='percent+label', | |
hoverinfo='label+percent+value', | |
marker=dict(line=dict(color='#FFFFFF', width=2)) | |
) | |
# Improve layout | |
fig.update_layout( | |
legend_title_text='Load Components', | |
font=dict(size=14), | |
title_font=dict(size=18), | |
title_x=0.5, # Center the title | |
margin=dict(t=50, b=50, l=50, r=50) | |
) | |
st.plotly_chart(fig) | |
# Display load components in a table | |
load_df = pd.DataFrame({ | |
'Component': list(load_components.keys()), | |
'Load (W)': list(load_components.values()), | |
'Percentage (%)': [value / results['sensible_load'] * 100 for value in load_components.values()] | |
}) | |
# Sort by load value for better readability | |
load_df = load_df.sort_values(by='Load (W)', ascending=False).reset_index(drop=True) | |
st.dataframe(load_df.style.format({ | |
'Load (W)': '{:.2f}', | |
'Percentage (%)': '{:.2f}' | |
}).background_gradient(cmap='Blues', subset=['Percentage (%)'])) | |
# Display detailed results | |
st.write("### Detailed Results") | |
# Create tabs for different result sections | |
tabs = st.tabs([ | |
"Building Envelope", | |
"Windows & Doors", | |
"Internal Loads", | |
"Ventilation" | |
]) | |
with tabs[0]: | |
st.subheader("Building Envelope Heat Gains") | |
# Get building components | |
building_components = [] | |
# Add walls | |
for wall in st.session_state.cooling_form_data['building_envelope'].get('walls', []): | |
building_components.append({ | |
'Component': wall['name'], | |
'Area (m²)': wall['area'], | |
'U-Value (W/m²°C)': wall['u_value'], | |
'Temperature Difference (°C)': wall['temp_diff'], | |
'Heat Gain (W)': wall['area'] * wall['u_value'] * wall['temp_diff'] | |
}) | |
# Add roof | |
roof = st.session_state.cooling_form_data['building_envelope'].get('roof', {}) | |
if roof: | |
building_components.append({ | |
'Component': 'Roof', | |
'Area (m²)': roof['area'], | |
'U-Value (W/m²°C)': roof['u_value'], | |
'Temperature Difference (°C)': roof['temp_diff'], | |
'Heat Gain (W)': roof['area'] * roof['u_value'] * roof['temp_diff'] | |
}) | |
# Add floor | |
floor = st.session_state.cooling_form_data['building_envelope'].get('floor', {}) | |
if floor: | |
building_components.append({ | |
'Component': 'Floor', | |
'Area (m²)': floor['area'], | |
'U-Value (W/m²°C)': floor['u_value'], | |
'Temperature Difference (°C)': floor['temp_diff'], | |
'Heat Gain (W)': floor['area'] * floor['u_value'] * floor['temp_diff'] | |
}) | |
# Create dataframe | |
envelope_df = pd.DataFrame(building_components) | |
# Display table | |
st.dataframe(envelope_df.style.format({ | |
'Area (m²)': '{:.2f}', | |
'U-Value (W/m²°C)': '{:.2f}', | |
'Temperature Difference (°C)': '{:.2f}', | |
'Heat Gain (W)': '{:.2f}' | |
})) | |
# Create bar chart | |
fig = px.bar( | |
envelope_df, | |
x='Component', | |
y='Heat Gain (W)', | |
title="Heat Gain by Building Component", | |
color='Component', | |
color_discrete_sequence=px.colors.qualitative.Set3 | |
) | |
st.plotly_chart(fig) | |
with tabs[1]: | |
st.subheader("Windows & Doors Heat Gains") | |
# Windows section | |
st.write("#### Windows") | |
# Get windows | |
windows_data = [] | |
for window in st.session_state.cooling_form_data['windows'].get('windows', []): | |
windows_data.append({ | |
'Component': window['name'], | |
'Orientation': window['orientation'].capitalize(), | |
'Area (m²)': window['area'], | |
'U-Value (W/m²°C)': window['u_value'], | |
'Temperature Difference (°C)': window['temp_diff'], | |
'Conduction Heat Gain (W)': window['area'] * window['u_value'] * window['temp_diff'], | |
'Solar Heat Gain Factor (W/m²)': window['shgf'], | |
'Shading Factor': 1.0 - window['shade_factor'], | |
'Solar Heat Gain (W)': window['area'] * window['shgf'] * (1.0 - window['shade_factor']), | |
'Total Heat Gain (W)': (window['area'] * window['u_value'] * window['temp_diff']) + | |
(window['area'] * window['shgf'] * (1.0 - window['shade_factor'])) | |
}) | |
if windows_data: | |
# Create dataframe | |
windows_df = pd.DataFrame(windows_data) | |
# Display table | |
st.dataframe(windows_df.style.format({ | |
'Area (m²)': '{:.2f}', | |
'U-Value (W/m²°C)': '{:.2f}', | |
'Temperature Difference (°C)': '{:.2f}', | |
'Conduction Heat Gain (W)': '{:.2f}', | |
'Solar Heat Gain Factor (W/m²)': '{:.2f}', | |
'Shading Factor': '{:.2f}', | |
'Solar Heat Gain (W)': '{:.2f}', | |
'Total Heat Gain (W)': '{:.2f}' | |
})) | |
# Create grouped bar chart | |
fig = go.Figure() | |
fig.add_trace(go.Bar( | |
x=windows_df['Component'], | |
y=windows_df['Conduction Heat Gain (W)'], | |
name='Conduction Heat Gain', | |
marker_color='#1f77b4', | |
text=windows_df['Conduction Heat Gain (W)'].round(1), | |
textposition='auto', | |
hovertemplate='<b>%{x}</b><br>Conduction Heat Gain: %{y:.1f} W<extra></extra>' | |
)) | |
fig.add_trace(go.Bar( | |
x=windows_df['Component'], | |
y=windows_df['Solar Heat Gain (W)'], | |
name='Solar Heat Gain', | |
marker_color='#ff7f0e', | |
text=windows_df['Solar Heat Gain (W)'].round(1), | |
textposition='auto', | |
hovertemplate='<b>%{x}</b><br>Solar Heat Gain: %{y:.1f} W<extra></extra>' | |
)) | |
fig.update_layout( | |
title="Window Heat Gains", | |
xaxis_title="Window", | |
yaxis_title="Heat Gain (W)", | |
barmode='stack', | |
font=dict(size=14), | |
title_font=dict(size=18), | |
title_x=0.5, # Center the title | |
margin=dict(t=50, b=50, l=50, r=50), | |
legend=dict( | |
orientation="h", | |
yanchor="bottom", | |
y=1.02, | |
xanchor="right", | |
x=1 | |
) | |
) | |
st.plotly_chart(fig) | |
else: | |
st.write("No windows defined.") | |
# Doors section | |
st.write("#### Doors") | |
# Get doors | |
doors_data = [] | |
for door in st.session_state.cooling_form_data['windows'].get('doors', []): | |
doors_data.append({ | |
'Component': door['name'], | |
'Type': door['type'], | |
'Area (m²)': door['area'], | |
'U-Value (W/m²°C)': door['u_value'], | |
'Temperature Difference (°C)': door['temp_diff'], | |
'Heat Gain (W)': door['area'] * door['u_value'] * door['temp_diff'] | |
}) | |
if doors_data: | |
# Create dataframe | |
doors_df = pd.DataFrame(doors_data) | |
# Display table | |
st.dataframe(doors_df.style.format({ | |
'Area (m²)': '{:.2f}', | |
'U-Value (W/m²°C)': '{:.2f}', | |
'Temperature Difference (°C)': '{:.2f}', | |
'Heat Gain (W)': '{:.2f}' | |
})) | |
# Create bar chart | |
fig = px.bar( | |
doors_df, | |
x='Component', | |
y='Heat Gain (W)', | |
title="Door Heat Gains", | |
color='Type', | |
color_discrete_sequence=px.colors.qualitative.Pastel | |
) | |
st.plotly_chart(fig) | |
else: | |
st.write("No doors defined.") | |
with tabs[2]: | |
st.subheader("Internal Heat Gains") | |
# Get internal loads data | |
internal_loads = st.session_state.cooling_form_data['internal_loads'] | |
# Create dataframe | |
internal_loads_data = [ | |
{ | |
'Source': 'Occupants', | |
'Details': f"{internal_loads['occupants']['count']} people", | |
'Heat Gain (W)': internal_loads['occupants']['total_heat_gain'] | |
}, | |
{ | |
'Source': 'Lighting', | |
'Details': f"{internal_loads['lighting']['type']} lighting", | |
'Heat Gain (W)': internal_loads['lighting']['total_heat_gain'] | |
}, | |
{ | |
'Source': 'Appliances', | |
'Details': ', '.join([k for k, v in internal_loads['appliances'].items() if v and k != 'total_heat_gain']), | |
'Heat Gain (W)': internal_loads['appliances']['total_heat_gain'] | |
} | |
] | |
internal_loads_df = pd.DataFrame(internal_loads_data) | |
# Display table | |
st.dataframe(internal_loads_df.style.format({ | |
'Heat Gain (W)': '{:.2f}' | |
})) | |
# Create bar chart | |
fig = px.bar( | |
internal_loads_df, | |
x='Source', | |
y='Heat Gain (W)', | |
title="Internal Heat Gains", | |
color='Source', | |
color_discrete_sequence=px.colors.qualitative.Pastel1 | |
) | |
st.plotly_chart(fig) | |
with tabs[3]: | |
st.subheader("Ventilation & Infiltration Heat Gains") | |
# Get ventilation data | |
ventilation_data = st.session_state.cooling_form_data['ventilation'] | |
# Create dataframe | |
ventilation_df = pd.DataFrame([ | |
{ | |
'Source': 'Infiltration', | |
'Air Changes per Hour': ventilation_data['infiltration']['air_changes'], | |
'Volume (m³)': ventilation_data['infiltration']['volume'], | |
'Temperature Difference (°C)': ventilation_data['infiltration']['temp_diff'], | |
'Heat Gain (W)': ventilation_data['infiltration']['heat_gain'] | |
}, | |
{ | |
'Source': 'Ventilation', | |
'Air Changes per Hour': ventilation_data['ventilation']['air_changes'], | |
'Volume (m³)': ventilation_data['ventilation']['volume'], | |
'Temperature Difference (°C)': ventilation_data['ventilation']['temp_diff'], | |
'Heat Gain (W)': ventilation_data['ventilation']['heat_gain'] | |
} | |
]) | |
# Display table | |
st.dataframe(ventilation_df.style.format({ | |
'Air Changes per Hour': '{:.2f}', | |
'Volume (m³)': '{:.2f}', | |
'Temperature Difference (°C)': '{:.2f}', | |
'Heat Gain (W)': '{:.2f}' | |
})) | |
# Create bar chart | |
fig = px.bar( | |
ventilation_df, | |
x='Source', | |
y='Heat Gain (W)', | |
title="Ventilation & Infiltration Heat Gains", | |
color='Source', | |
color_discrete_sequence=px.colors.qualitative.Pastel2 | |
) | |
st.plotly_chart(fig) | |
# Export options | |
st.write("### Export Options") | |
col1, col2 = st.columns(2) | |
with col1: | |
if st.button("Export Results as CSV"): | |
# Create a CSV file with results | |
csv_data = export_data(st.session_state.cooling_form_data, st.session_state.cooling_results, format='csv') | |
# Provide download link | |
st.download_button( | |
label="Download CSV", | |
data=csv_data, | |
file_name=f"cooling_load_results_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv", | |
mime="text/csv" | |
) | |
with col2: | |
if st.button("Export Results as JSON"): | |
# Create a JSON file with results | |
json_data = export_data(st.session_state.cooling_form_data, st.session_state.cooling_results, format='json') | |
# Provide download link | |
st.download_button( | |
label="Download JSON", | |
data=json_data, | |
file_name=f"cooling_load_results_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json", | |
mime="application/json" | |
) | |
# Navigation buttons | |
col1, col2 = st.columns([1, 1]) | |
with col1: | |
prev_button = st.button("← Back: Ventilation", key="results_prev") | |
if prev_button: | |
st.session_state.cooling_active_tab = "ventilation" | |
st.experimental_rerun() | |
with col2: | |
recalculate_button = st.button("Recalculate", key="results_recalculate") | |
if recalculate_button: | |
# Recalculate cooling load | |
calculate_cooling_load() | |
st.experimental_rerun() | |
def cooling_calculator(): | |
"""Main function for the cooling load calculator page.""" | |
st.title("Cooling Load Calculator") | |
# Initialize reference data | |
ref_data = ReferenceData() | |
# Initialize session state | |
load_session_state() | |
# Initialize active tab if not already set | |
if 'cooling_active_tab' not in st.session_state: | |
st.session_state.cooling_active_tab = "building_info" | |
# Create tabs for different steps | |
tabs = st.tabs([ | |
"1. Building Information", | |
"2. Building Envelope", | |
"3. Windows & Doors", | |
"4. Internal Loads", | |
"5. Ventilation", | |
"6. Results" | |
]) | |
# Add direct navigation buttons at the top | |
st.write("### Navigation") | |
st.write("Click on any button below to navigate directly to that section:") | |
col1, col2, col3 = st.columns(3) | |
with col1: | |
if st.button("1. Building Information", key="direct_nav_building_info"): | |
st.session_state.cooling_active_tab = "building_info" | |
st.experimental_rerun() | |
if st.button("2. Building Envelope", key="direct_nav_building_envelope"): | |
st.session_state.cooling_active_tab = "building_envelope" | |
st.experimental_rerun() | |
with col2: | |
if st.button("3. Windows & Doors", key="direct_nav_windows"): | |
st.session_state.cooling_active_tab = "windows" | |
st.experimental_rerun() | |
if st.button("4. Internal Loads", key="direct_nav_internal_loads"): | |
st.session_state.cooling_active_tab = "internal_loads" | |
st.experimental_rerun() | |
with col3: | |
if st.button("5. Ventilation", key="direct_nav_ventilation"): | |
st.session_state.cooling_active_tab = "ventilation" | |
st.experimental_rerun() | |
if st.button("6. Results", key="direct_nav_results"): | |
# Only enable if all previous steps are completed | |
if all(st.session_state.cooling_completed.values()): | |
st.session_state.cooling_active_tab = "results" | |
st.experimental_rerun() | |
else: | |
st.warning("Please complete all previous steps before viewing results.") | |
# Display the active tab | |
with tabs[0]: | |
if st.session_state.cooling_active_tab == "building_info": | |
building_info_form(ref_data) | |
with tabs[1]: | |
if st.session_state.cooling_active_tab == "building_envelope": | |
building_envelope_form(ref_data) | |
with tabs[2]: | |
if st.session_state.cooling_active_tab == "windows": | |
windows_form(ref_data) | |
with tabs[3]: | |
if st.session_state.cooling_active_tab == "internal_loads": | |
internal_loads_form(ref_data) | |
with tabs[4]: | |
if st.session_state.cooling_active_tab == "ventilation": | |
ventilation_form(ref_data) | |
with tabs[5]: | |
if st.session_state.cooling_active_tab == "results": | |
results_page() | |
if __name__ == "__main__": | |
cooling_calculator() | |