| | """ |
| | Multiple building management module for HVAC Load Calculator. |
| | This module provides functionality for managing multiple buildings. |
| | """ |
| |
|
| | import streamlit as st |
| | import pandas as pd |
| | import numpy as np |
| | from typing import Dict, List, Any, Optional, Tuple |
| | from models.building import Building, CoolingLoadResult |
| | from controllers.enhanced_cooling_load_calculator import calculate_cooling_load |
| | from controllers.monthly_breakdown import calculate_monthly_breakdown |
| | from views.building_comparison import compare_buildings |
| | from utils.data_io import export_building_to_json, import_building_from_json |
| |
|
| | class BuildingManager: |
| | """ |
| | Manager for multiple buildings. |
| | """ |
| | |
| | def __init__(self): |
| | """ |
| | Initialize building manager. |
| | """ |
| | |
| | if 'buildings' not in st.session_state: |
| | st.session_state.buildings = {} |
| | |
| | if 'results' not in st.session_state: |
| | st.session_state.results = {} |
| | |
| | if 'monthly_breakdowns' not in st.session_state: |
| | st.session_state.monthly_breakdowns = {} |
| | |
| | if 'current_building' not in st.session_state: |
| | st.session_state.current_building = None |
| | |
| | def get_buildings(self) -> Dict[str, Building]: |
| | """ |
| | Get all buildings. |
| | |
| | Returns: |
| | Dictionary of buildings {name: Building} |
| | """ |
| | return st.session_state.buildings |
| | |
| | def get_results(self) -> Dict[str, CoolingLoadResult]: |
| | """ |
| | Get all results. |
| | |
| | Returns: |
| | Dictionary of results {name: CoolingLoadResult} |
| | """ |
| | return st.session_state.results |
| | |
| | def get_monthly_breakdowns(self) -> Dict[str, Dict[str, Any]]: |
| | """ |
| | Get all monthly breakdowns. |
| | |
| | Returns: |
| | Dictionary of monthly breakdowns {name: monthly_breakdown} |
| | """ |
| | return st.session_state.monthly_breakdowns |
| | |
| | def get_current_building(self) -> Optional[str]: |
| | """ |
| | Get current building name. |
| | |
| | Returns: |
| | Current building name or None if no building is selected |
| | """ |
| | return st.session_state.current_building |
| | |
| | def set_current_building(self, name: str): |
| | """ |
| | Set current building. |
| | |
| | Args: |
| | name: Building name |
| | """ |
| | st.session_state.current_building = name |
| | |
| | def add_building(self, building: Building) -> bool: |
| | """ |
| | Add building to manager. |
| | |
| | Args: |
| | building: Building to add |
| | |
| | Returns: |
| | True if building was added, False if building with same name already exists |
| | """ |
| | if building.settings.name in st.session_state.buildings: |
| | return False |
| | |
| | st.session_state.buildings[building.settings.name] = building |
| | return True |
| | |
| | def update_building(self, building: Building) -> bool: |
| | """ |
| | Update existing building. |
| | |
| | Args: |
| | building: Building to update |
| | |
| | Returns: |
| | True if building was updated, False if building does not exist |
| | """ |
| | if building.settings.name not in st.session_state.buildings: |
| | return False |
| | |
| | st.session_state.buildings[building.settings.name] = building |
| | |
| | |
| | if building.settings.name in st.session_state.results: |
| | del st.session_state.results[building.settings.name] |
| | |
| | if building.settings.name in st.session_state.monthly_breakdowns: |
| | del st.session_state.monthly_breakdowns[building.settings.name] |
| | |
| | return True |
| | |
| | def remove_building(self, name: str) -> bool: |
| | """ |
| | Remove building from manager. |
| | |
| | Args: |
| | name: Building name |
| | |
| | Returns: |
| | True if building was removed, False if building does not exist |
| | """ |
| | if name not in st.session_state.buildings: |
| | return False |
| | |
| | del st.session_state.buildings[name] |
| | |
| | |
| | if name in st.session_state.results: |
| | del st.session_state.results[name] |
| | |
| | if name in st.session_state.monthly_breakdowns: |
| | del st.session_state.monthly_breakdowns[name] |
| | |
| | |
| | if st.session_state.current_building == name: |
| | if st.session_state.buildings: |
| | st.session_state.current_building = next(iter(st.session_state.buildings)) |
| | else: |
| | st.session_state.current_building = None |
| | |
| | return True |
| | |
| | def rename_building(self, old_name: str, new_name: str) -> bool: |
| | """ |
| | Rename building. |
| | |
| | Args: |
| | old_name: Old building name |
| | new_name: New building name |
| | |
| | Returns: |
| | True if building was renamed, False if building does not exist or new name already exists |
| | """ |
| | if old_name not in st.session_state.buildings: |
| | return False |
| | |
| | if new_name in st.session_state.buildings: |
| | return False |
| | |
| | |
| | building = st.session_state.buildings[old_name] |
| | |
| | |
| | building.settings.name = new_name |
| | |
| | |
| | st.session_state.buildings[new_name] = building |
| | |
| | |
| | if old_name in st.session_state.results: |
| | st.session_state.results[new_name] = st.session_state.results[old_name] |
| | del st.session_state.results[old_name] |
| | |
| | if old_name in st.session_state.monthly_breakdowns: |
| | st.session_state.monthly_breakdowns[new_name] = st.session_state.monthly_breakdowns[old_name] |
| | del st.session_state.monthly_breakdowns[old_name] |
| | |
| | |
| | del st.session_state.buildings[old_name] |
| | |
| | |
| | if st.session_state.current_building == old_name: |
| | st.session_state.current_building = new_name |
| | |
| | return True |
| | |
| | def duplicate_building(self, name: str, new_name: str) -> bool: |
| | """ |
| | Duplicate building. |
| | |
| | Args: |
| | name: Building name to duplicate |
| | new_name: New building name |
| | |
| | Returns: |
| | True if building was duplicated, False if building does not exist or new name already exists |
| | """ |
| | if name not in st.session_state.buildings: |
| | return False |
| | |
| | if new_name in st.session_state.buildings: |
| | return False |
| | |
| | |
| | building = st.session_state.buildings[name] |
| | |
| | |
| | building_json = export_building_to_json(building) |
| | |
| | |
| | new_building = import_building_from_json(building_json) |
| | |
| | |
| | new_building.settings.name = new_name |
| | |
| | |
| | st.session_state.buildings[new_name] = new_building |
| | |
| | return True |
| | |
| | def calculate_building(self, name: str) -> Tuple[CoolingLoadResult, Dict[str, Any]]: |
| | """ |
| | Calculate cooling load for building. |
| | |
| | Args: |
| | name: Building name |
| | |
| | Returns: |
| | Tuple of (result, monthly_breakdown) |
| | """ |
| | if name not in st.session_state.buildings: |
| | return None, None |
| | |
| | |
| | building = st.session_state.buildings[name] |
| | |
| | |
| | result = calculate_cooling_load(building) |
| | |
| | |
| | monthly_breakdown = calculate_monthly_breakdown(building) |
| | |
| | |
| | st.session_state.results[name] = result |
| | st.session_state.monthly_breakdowns[name] = monthly_breakdown |
| | |
| | return result, monthly_breakdown |
| | |
| | def display_building_manager(self): |
| | """ |
| | Display building manager UI. |
| | """ |
| | st.subheader("Building Manager") |
| | |
| | |
| | buildings = self.get_buildings() |
| | |
| | if not buildings: |
| | st.info("No buildings available. Create a new building to get started.") |
| | return |
| | |
| | |
| | col1, col2, col3 = st.columns([2, 1, 1]) |
| | |
| | with col1: |
| | |
| | building_names = list(buildings.keys()) |
| | current_building = self.get_current_building() |
| | |
| | if current_building not in building_names and building_names: |
| | current_building = building_names[0] |
| | |
| | selected_building = st.selectbox( |
| | "Select Building", |
| | building_names, |
| | index=building_names.index(current_building) if current_building in building_names else 0 |
| | ) |
| | |
| | |
| | self.set_current_building(selected_building) |
| | |
| | with col2: |
| | |
| | if st.button("Rename Building"): |
| | new_name = st.text_input("Enter new name:", value=selected_building) |
| | if new_name and new_name != selected_building: |
| | if self.rename_building(selected_building, new_name): |
| | st.success(f"Building renamed to {new_name}") |
| | else: |
| | st.error(f"Failed to rename building. Name {new_name} already exists.") |
| | |
| | with col3: |
| | |
| | if st.button("Remove Building"): |
| | if st.checkbox(f"Confirm removal of {selected_building}"): |
| | if self.remove_building(selected_building): |
| | st.success(f"Building {selected_building} removed") |
| | else: |
| | st.error(f"Failed to remove building {selected_building}") |
| | |
| | |
| | st.subheader("Building Actions") |
| | |
| | col1, col2, col3 = st.columns(3) |
| | |
| | with col1: |
| | |
| | if st.button("Duplicate Building"): |
| | new_name = st.text_input("Enter name for duplicate:", value=f"{selected_building} (Copy)") |
| | if new_name: |
| | if self.duplicate_building(selected_building, new_name): |
| | st.success(f"Building duplicated as {new_name}") |
| | else: |
| | st.error(f"Failed to duplicate building. Name {new_name} already exists.") |
| | |
| | with col2: |
| | |
| | if st.button("Calculate Building"): |
| | with st.spinner(f"Calculating cooling load for {selected_building}..."): |
| | result, monthly_breakdown = self.calculate_building(selected_building) |
| | if result: |
| | st.success(f"Calculation completed for {selected_building}") |
| | else: |
| | st.error(f"Failed to calculate cooling load for {selected_building}") |
| | |
| | with col3: |
| | |
| | if st.button("Compare Buildings"): |
| | st.session_state.show_comparison = True |
| | |
| | def display_building_comparison(self): |
| | """ |
| | Display building comparison UI. |
| | """ |
| | |
| | buildings = self.get_buildings() |
| | results = self.get_results() |
| | monthly_breakdowns = self.get_monthly_breakdowns() |
| | |
| | |
| | if len(buildings) < 2: |
| | st.warning("At least two buildings are required for comparison. Please add more buildings.") |
| | return |
| | |
| | |
| | missing_results = [name for name in buildings if name not in results] |
| | if missing_results: |
| | st.warning(f"Missing results for buildings: {', '.join(missing_results)}. Please calculate these buildings first.") |
| | |
| | |
| | if st.button("Calculate Missing Results"): |
| | with st.spinner("Calculating missing results..."): |
| | for name in missing_results: |
| | self.calculate_building(name) |
| | st.success("All calculations completed") |
| | |
| | return |
| | |
| | |
| | compare_buildings(buildings, results, monthly_breakdowns) |
| | |
| | def display_building_list(self): |
| | """ |
| | Display building list UI. |
| | """ |
| | st.subheader("Building List") |
| | |
| | |
| | buildings = self.get_buildings() |
| | results = self.get_results() |
| | monthly_breakdowns = self.get_monthly_breakdowns() |
| | |
| | if not buildings: |
| | st.info("No buildings available. Create a new building to get started.") |
| | return |
| | |
| | |
| | table_data = [] |
| | for name, building in buildings.items(): |
| | result = results.get(name) |
| | monthly_breakdown = monthly_breakdowns.get(name) |
| | |
| | peak_load = result.peak_total_load if result else None |
| | peak_load_per_m2 = peak_load / building.settings.floor_area if peak_load else None |
| | |
| | annual_energy = monthly_breakdown.get("annual", {}).get("energy_kwh") if monthly_breakdown else None |
| | annual_energy_per_m2 = annual_energy / building.settings.floor_area if annual_energy else None |
| | |
| | table_data.append({ |
| | "Building": name, |
| | "Location": building.location.city, |
| | "Floor Area (m²)": building.settings.floor_area, |
| | "Peak Load (kW)": peak_load / 1000 if peak_load else None, |
| | "Peak Load (W/m²)": peak_load_per_m2 if peak_load_per_m2 else None, |
| | "Annual Energy (kWh)": annual_energy if annual_energy else None, |
| | "Annual Energy (kWh/m²)": annual_energy_per_m2 if annual_energy_per_m2 else None, |
| | "Calculated": "Yes" if result else "No" |
| | }) |
| | |
| | |
| | df = pd.DataFrame(table_data) |
| | |
| | |
| | st.dataframe(df, use_container_width=True) |
| | |
| | |
| | col1, col2 = st.columns(2) |
| | |
| | with col1: |
| | |
| | if st.button("Calculate All Buildings"): |
| | with st.spinner("Calculating all buildings..."): |
| | for name in buildings: |
| | self.calculate_building(name) |
| | st.success("All calculations completed") |
| | |
| | with col2: |
| | |
| | if st.button("Compare All Buildings"): |
| | |
| | missing_results = [name for name in buildings if name not in results] |
| | if missing_results: |
| | st.warning(f"Missing results for buildings: {', '.join(missing_results)}. Please calculate these buildings first.") |
| | return |
| | |
| | st.session_state.show_comparison = True |
| |
|