""" Building information input form for HVAC Load Calculator. This module provides the UI components for entering building information. Author: Dr Majed Abuseif Date: March 2025 Version: 1.0.0 """ import streamlit as st import pandas as pd import numpy as np import pycountry from typing import Dict, List, Any, Optional, Tuple import os # Import data models from data.building_components import Orientation, ComponentType class BuildingInfoForm: """Class for building information input form.""" def __init__(self): """Initialize the building information form.""" self.countries = sorted([country.name for country in pycountry.countries]) def display(self): """Display the building information form.""" self.display_building_info_form(st.session_state) def display_building_info_form(self, session_state: Dict[str, Any]) -> None: """Display building information input form in Streamlit.""" st.header("Building Information") if "building_info" not in session_state: session_state["building_info"] = { "project_name": "", "building_name": "", "country": "", "city": "", "building_type": "", "floor_area": 0.0, "width": 0.0, "depth": 0.0, "building_height": 3.0, "orientation": "NORTH", "operating_hours": "8:00-18:00" } default_values = { "project_name": "", "building_name": "", "country": "", "city": "", "building_type": "", "floor_area": 0.0, "width": 0.0, "depth": 0.0, "building_height": 3.0, "orientation": "NORTH", "operating_hours": "8:00-18:00" } for key, default_value in default_values.items(): if key not in session_state["building_info"]: session_state["building_info"][key] = default_value if "data_saved" not in session_state: session_state["data_saved"] = False with st.form(key="building_info_form"): st.subheader("Project Information") col1, col2 = st.columns(2) with col1: session_state["building_info"]["project_name"] = st.text_input( "Project Name", value=session_state["building_info"]["project_name"], help="Enter the project's identification name" ) session_state["building_info"]["building_name"] = st.text_input( "Building Name", value=session_state["building_info"]["building_name"], help="Enter the building's identification name" ) with col2: session_state["building_info"]["country"] = st.selectbox( "Country", options=[""] + self.countries, index=0 if not session_state["building_info"]["country"] else self.countries.index(session_state["building_info"]["country"]) + 1, help="Select the building's country location" ) session_state["building_info"]["city"] = st.text_input( "City", value=session_state["building_info"]["city"], help="Enter the building's city location" ) st.subheader("Building Characteristics") col1, col2 = st.columns(2) with col1: session_state["building_info"]["building_type"] = st.selectbox( "Building Type", ["Residential", "Office", "Retail", "Educational", "Healthcare", "Industrial", "Other"], index=1 if session_state["building_info"]["building_type"] == "" else ["Residential", "Office", "Retail", "Educational", "Healthcare", "Industrial", "Other"].index(session_state["building_info"]["building_type"]), help="Select the building's purpose or usage type" ) with col2: session_state["building_info"]["building_height"] = st.number_input( "Building Height (m)", min_value=2.0, max_value=1000.0, value=float(session_state["building_info"]["building_height"]), step=0.1, help="Enter the total height of the building in meters" ) st.subheader("Building Dimensions") session_state["building_info"]["floor_area"] = st.number_input( "Total Floor Area (m²)", min_value=0.0, value=float(session_state["building_info"]["floor_area"]), step=10.0, help="Enter the total floor area of the building in square meters (optional if width and depth provided)" ) # Center the OR using columns col1, col2, col3 = st.columns([2, 1, 2]) with col2: st.markdown("Enter the total floor area above OR the building width and depth below") col1, col2 = st.columns(2) with col1: session_state["building_info"]["width"] = st.number_input( "Width (m)", min_value=0.0, value=float(session_state["building_info"]["width"]), step=1.0, help="Enter the building's width in meters (optional if area provided)" ) with col2: session_state["building_info"]["depth"] = st.number_input( "Depth (m)", min_value=0.0, value=float(session_state["building_info"]["depth"]), step=1.0, help="Enter the building's depth in meters (optional if area provided)" ) st.subheader("Building Orientation") session_state["building_info"]["orientation"] = st.selectbox( "Building Orientation", ["NORTH", "NORTHEAST", "EAST", "SOUTHEAST", "SOUTH", "SOUTHWEST", "WEST", "NORTHWEST"], index=["NORTH", "NORTHEAST", "EAST", "SOUTHEAST", "SOUTH", "SOUTHWEST", "WEST", "NORTHWEST"].index(session_state["building_info"]["orientation"]), help="Select the direction of the building's main facade" ) st.subheader("Operating Hours") session_state["building_info"]["operating_hours"] = st.text_input( "Operating Hours", value=session_state["building_info"]["operating_hours"], help="Enter the building's daily operating hours (e.g., 8:00-18:00)" ) submitted = st.form_submit_button("Save Building Information") if submitted: valid, errors = self.validate_building_info(session_state["building_info"]) if not valid: for error in errors: st.error(error) else: if session_state["building_info"]["width"] > 0 and session_state["building_info"]["depth"] > 0: calculated_area = session_state["building_info"]["width"] * session_state["building_info"]["depth"] if session_state["building_info"]["floor_area"] == 0: session_state["building_info"]["floor_area"] = calculated_area total_volume = session_state["building_info"]["floor_area"] * session_state["building_info"]["building_height"] session_state["save_results"] = { "success": "Building information saved successfully!", "area": f"Total Floor Area: {session_state['building_info']['floor_area']:.1f} m²", "volume": f"Total Building Volume: {total_volume:.1f} m³" } session_state["data_saved"] = True # Display results if they exist if "save_results" in session_state and session_state["data_saved"]: st.success(session_state["save_results"]["success"]) st.info(session_state["save_results"]["area"]) st.info(session_state["save_results"]["volume"]) # Proceed button with immediate navigation if session_state["data_saved"]: if st.button("Proceed to Climate Data"): session_state["page"] = "Climate Data" session_state["data_saved"] = False if "save_results" in session_state: del session_state["save_results"] @staticmethod def validate_building_info(building_info: Dict[str, Any]) -> Tuple[bool, List[str]]: """Validate building information.""" valid = True errors = [] required_fields = ["project_name", "building_name", "country", "city", "building_type"] for field in required_fields: if field not in building_info or not building_info[field]: valid = False errors.append(f"Missing required field: {field}") if building_info.get("floor_area", 0) <= 0 and (building_info.get("width", 0) <= 0 or building_info.get("depth", 0) <= 0): valid = False errors.append("Must provide either floor area or both width and depth dimensions") if building_info.get("building_height", 0) <= 0: valid = False errors.append("Building height must be greater than zero") return valid, errors if __name__ == "__main__": form = BuildingInfoForm() form.display()