Spaces:
Sleeping
Sleeping
| """ | |
| Data persistence module for HVAC Load Calculator. | |
| This module provides functionality for saving and loading project data. | |
| """ | |
| import streamlit as st | |
| import pandas as pd | |
| import numpy as np | |
| from typing import Dict, List, Any, Optional, Tuple | |
| import json | |
| import os | |
| import base64 | |
| import io | |
| import pickle | |
| from datetime import datetime | |
| # Import data models | |
| from data.building_components import Wall, Roof, Floor, Window, Door, Orientation, ComponentType | |
| class DataPersistence: | |
| """Class for data persistence functionality.""" | |
| def save_project_to_json(session_state: Dict[str, Any], file_path: str = None) -> Optional[str]: | |
| """ | |
| Save project data to a JSON file. | |
| Args: | |
| session_state: Streamlit session state containing project data | |
| file_path: Optional path to save the JSON file | |
| Returns: | |
| JSON string if file_path is None, otherwise None | |
| """ | |
| try: | |
| # Create project data dictionary | |
| project_data = { | |
| "building_info": session_state.get("building_info", {}), | |
| "components": DataPersistence._serialize_components(session_state.get("components", {})), | |
| "internal_loads": session_state.get("internal_loads", {}), | |
| "calculation_settings": session_state.get("calculation_settings", {}), | |
| "saved_scenarios": DataPersistence._serialize_scenarios(session_state.get("saved_scenarios", {})), | |
| "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S") | |
| } | |
| # Convert to JSON | |
| json_data = json.dumps(project_data, indent=4) | |
| # Save to file if path provided | |
| if file_path: | |
| with open(file_path, "w") as f: | |
| f.write(json_data) | |
| return None | |
| # Return JSON string if no path provided | |
| return json_data | |
| except Exception as e: | |
| st.error(f"Error saving project data: {e}") | |
| return None | |
| def load_project_from_json(json_data: str = None, file_path: str = None) -> Optional[Dict[str, Any]]: | |
| """ | |
| Load project data from a JSON file or string. | |
| Args: | |
| json_data: Optional JSON string containing project data | |
| file_path: Optional path to the JSON file | |
| Returns: | |
| Dictionary with project data if successful, None otherwise | |
| """ | |
| try: | |
| # Load from file if path provided | |
| if file_path and not json_data: | |
| with open(file_path, "r") as f: | |
| json_data = f.read() | |
| # Parse JSON data | |
| if json_data: | |
| project_data = json.loads(json_data) | |
| # Deserialize components | |
| if "components" in project_data: | |
| project_data["components"] = DataPersistence._deserialize_components(project_data["components"]) | |
| # Deserialize scenarios | |
| if "saved_scenarios" in project_data: | |
| project_data["saved_scenarios"] = DataPersistence._deserialize_scenarios(project_data["saved_scenarios"]) | |
| return project_data | |
| return None | |
| except Exception as e: | |
| st.error(f"Error loading project data: {e}") | |
| return None | |
| def _serialize_components(components: Dict[str, List[Any]]) -> Dict[str, List[Dict[str, Any]]]: | |
| """ | |
| Serialize components for JSON storage. | |
| Args: | |
| components: Dictionary with building components | |
| Returns: | |
| Dictionary with serialized components | |
| """ | |
| serialized_components = { | |
| "walls": [], | |
| "roofs": [], | |
| "floors": [], | |
| "windows": [], | |
| "doors": [] | |
| } | |
| # Serialize walls | |
| for wall in components.get("walls", []): | |
| serialized_wall = wall.__dict__.copy() | |
| # Convert enums to strings | |
| if hasattr(serialized_wall["orientation"], "name"): | |
| serialized_wall["orientation"] = serialized_wall["orientation"].name | |
| if hasattr(serialized_wall["component_type"], "name"): | |
| serialized_wall["component_type"] = serialized_wall["component_type"].name | |
| serialized_components["walls"].append(serialized_wall) | |
| # Serialize roofs | |
| for roof in components.get("roofs", []): | |
| serialized_roof = roof.__dict__.copy() | |
| # Convert enums to strings | |
| if hasattr(serialized_roof["orientation"], "name"): | |
| serialized_roof["orientation"] = serialized_roof["orientation"].name | |
| if hasattr(serialized_roof["component_type"], "name"): | |
| serialized_roof["component_type"] = serialized_roof["component_type"].name | |
| serialized_components["roofs"].append(serialized_roof) | |
| # Serialize floors | |
| for floor in components.get("floors", []): | |
| serialized_floor = floor.__dict__.copy() | |
| # Convert enums to strings | |
| if hasattr(serialized_floor["component_type"], "name"): | |
| serialized_floor["component_type"] = serialized_floor["component_type"].name | |
| serialized_components["floors"].append(serialized_floor) | |
| # Serialize windows | |
| for window in components.get("windows", []): | |
| serialized_window = window.__dict__.copy() | |
| # Convert enums to strings | |
| if hasattr(serialized_window["orientation"], "name"): | |
| serialized_window["orientation"] = serialized_window["orientation"].name | |
| if hasattr(serialized_window["component_type"], "name"): | |
| serialized_window["component_type"] = serialized_window["component_type"].name | |
| serialized_components["windows"].append(serialized_window) | |
| # Serialize doors | |
| for door in components.get("doors", []): | |
| serialized_door = door.__dict__.copy() | |
| # Convert enums to strings | |
| if hasattr(serialized_door["orientation"], "name"): | |
| serialized_door["orientation"] = serialized_door["orientation"].name | |
| if hasattr(serialized_door["component_type"], "name"): | |
| serialized_door["component_type"] = serialized_door["component_type"].name | |
| serialized_components["doors"].append(serialized_door) | |
| return serialized_components | |
| def _deserialize_components(serialized_components: Dict[str, List[Dict[str, Any]]]) -> Dict[str, List[Any]]: | |
| """ | |
| Deserialize components from JSON storage. | |
| Args: | |
| serialized_components: Dictionary with serialized components | |
| Returns: | |
| Dictionary with deserialized components | |
| """ | |
| components = { | |
| "walls": [], | |
| "roofs": [], | |
| "floors": [], | |
| "windows": [], | |
| "doors": [] | |
| } | |
| # Deserialize walls | |
| for wall_dict in serialized_components.get("walls", []): | |
| wall = Wall( | |
| id=wall_dict.get("id", ""), | |
| name=wall_dict.get("name", ""), | |
| component_type=ComponentType[wall_dict.get("component_type", "WALL")], | |
| u_value=wall_dict.get("u_value", 0.0), | |
| area=wall_dict.get("area", 0.0), | |
| orientation=Orientation[wall_dict.get("orientation", "NORTH")], | |
| wall_type=wall_dict.get("wall_type", ""), | |
| wall_group=wall_dict.get("wall_group", "") | |
| ) | |
| components["walls"].append(wall) | |
| # Deserialize roofs | |
| for roof_dict in serialized_components.get("roofs", []): | |
| roof = Roof( | |
| id=roof_dict.get("id", ""), | |
| name=roof_dict.get("name", ""), | |
| component_type=ComponentType[roof_dict.get("component_type", "ROOF")], | |
| u_value=roof_dict.get("u_value", 0.0), | |
| area=roof_dict.get("area", 0.0), | |
| orientation=Orientation[roof_dict.get("orientation", "HORIZONTAL")], | |
| roof_type=roof_dict.get("roof_type", ""), | |
| roof_group=roof_dict.get("roof_group", "") | |
| ) | |
| components["roofs"].append(roof) | |
| # Deserialize floors | |
| for floor_dict in serialized_components.get("floors", []): | |
| floor = Floor( | |
| id=floor_dict.get("id", ""), | |
| name=floor_dict.get("name", ""), | |
| component_type=ComponentType[floor_dict.get("component_type", "FLOOR")], | |
| u_value=floor_dict.get("u_value", 0.0), | |
| area=floor_dict.get("area", 0.0), | |
| floor_type=floor_dict.get("floor_type", "") | |
| ) | |
| components["floors"].append(floor) | |
| # Deserialize windows | |
| for window_dict in serialized_components.get("windows", []): | |
| window = Window( | |
| id=window_dict.get("id", ""), | |
| name=window_dict.get("name", ""), | |
| component_type=ComponentType[window_dict.get("component_type", "WINDOW")], | |
| u_value=window_dict.get("u_value", 0.0), | |
| area=window_dict.get("area", 0.0), | |
| orientation=Orientation[window_dict.get("orientation", "NORTH")], | |
| shgc=window_dict.get("shgc", 0.0), | |
| vt=window_dict.get("vt", 0.0), | |
| window_type=window_dict.get("window_type", ""), | |
| glazing_layers=window_dict.get("glazing_layers", 1), | |
| gas_fill=window_dict.get("gas_fill", ""), | |
| low_e_coating=window_dict.get("low_e_coating", False) | |
| ) | |
| components["windows"].append(window) | |
| # Deserialize doors | |
| for door_dict in serialized_components.get("doors", []): | |
| door = Door( | |
| id=door_dict.get("id", ""), | |
| name=door_dict.get("name", ""), | |
| component_type=ComponentType[door_dict.get("component_type", "DOOR")], | |
| u_value=door_dict.get("u_value", 0.0), | |
| area=door_dict.get("area", 0.0), | |
| orientation=Orientation[door_dict.get("orientation", "NORTH")], | |
| door_type=door_dict.get("door_type", "") | |
| ) | |
| components["doors"].append(door) | |
| return components | |
| def _serialize_scenarios(scenarios: Dict[str, Dict[str, Any]]) -> Dict[str, Dict[str, Any]]: | |
| """ | |
| Serialize scenarios for JSON storage. | |
| Args: | |
| scenarios: Dictionary with saved scenarios | |
| Returns: | |
| Dictionary with serialized scenarios | |
| """ | |
| serialized_scenarios = {} | |
| for scenario_name, scenario_data in scenarios.items(): | |
| serialized_scenario = { | |
| "results": scenario_data.get("results", {}), | |
| "building_info": scenario_data.get("building_info", {}), | |
| "components": DataPersistence._serialize_components(scenario_data.get("components", {})), | |
| "timestamp": scenario_data.get("timestamp", datetime.now().strftime("%Y-%m-%d %H:%M:%S")) | |
| } | |
| serialized_scenarios[scenario_name] = serialized_scenario | |
| return serialized_scenarios | |
| def _deserialize_scenarios(serialized_scenarios: Dict[str, Dict[str, Any]]) -> Dict[str, Dict[str, Any]]: | |
| """ | |
| Deserialize scenarios from JSON storage. | |
| Args: | |
| serialized_scenarios: Dictionary with serialized scenarios | |
| Returns: | |
| Dictionary with deserialized scenarios | |
| """ | |
| scenarios = {} | |
| for scenario_name, serialized_scenario in serialized_scenarios.items(): | |
| scenario = { | |
| "results": serialized_scenario.get("results", {}), | |
| "building_info": serialized_scenario.get("building_info", {}), | |
| "components": DataPersistence._deserialize_components(serialized_scenario.get("components", {})), | |
| "timestamp": serialized_scenario.get("timestamp", datetime.now().strftime("%Y-%m-%d %H:%M:%S")) | |
| } | |
| scenarios[scenario_name] = scenario | |
| return scenarios | |
| def get_download_link(data: str, filename: str, text: str) -> str: | |
| """ | |
| Generate a download link for data. | |
| Args: | |
| data: Data to download | |
| filename: Name of the file to download | |
| text: Text to display for the download link | |
| Returns: | |
| HTML string with download link | |
| """ | |
| b64 = base64.b64encode(data.encode()).decode() | |
| href = f'<a href="data:file/txt;base64,{b64}" download="{filename}">{text}</a>' | |
| return href | |
| def display_project_management(session_state: Dict[str, Any]) -> None: | |
| """ | |
| Display project management interface in Streamlit. | |
| Args: | |
| session_state: Streamlit session state containing project data | |
| """ | |
| st.header("Project Management") | |
| # Create tabs for different project management functions | |
| tab1, tab2, tab3 = st.tabs(["Save Project", "Load Project", "Project History"]) | |
| with tab1: | |
| DataPersistence._display_save_project(session_state) | |
| with tab2: | |
| DataPersistence._display_load_project(session_state) | |
| with tab3: | |
| DataPersistence._display_project_history(session_state) | |
| def _display_save_project(session_state: Dict[str, Any]) -> None: | |
| """ | |
| Display save project interface. | |
| Args: | |
| session_state: Streamlit session state containing project data | |
| """ | |
| st.subheader("Save Project") | |
| # Get project name | |
| project_name = st.text_input( | |
| "Project Name", | |
| value=session_state.get("building_info", {}).get("project_name", "HVAC_Project"), | |
| key="save_project_name" | |
| ) | |
| # Add description | |
| project_description = st.text_area( | |
| "Project Description", | |
| value=session_state.get("project_description", ""), | |
| key="save_project_description" | |
| ) | |
| # Save project description | |
| session_state["project_description"] = project_description | |
| # Add save button | |
| if st.button("Save Project"): | |
| # Validate project data | |
| if "building_info" not in session_state or not session_state["building_info"]: | |
| st.error("No building information found. Please enter building information before saving.") | |
| return | |
| if "components" not in session_state or not any(session_state["components"].values()): | |
| st.warning("No building components found. It's recommended to add components before saving.") | |
| # Save project data to JSON | |
| json_data = DataPersistence.save_project_to_json(session_state) | |
| if json_data: | |
| # Generate download link | |
| filename = f"{project_name.replace(' ', '_')}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.hvac" | |
| download_link = DataPersistence.get_download_link(json_data, filename, "Download Project File") | |
| # Display download link | |
| st.success("Project saved successfully!") | |
| st.markdown(download_link, unsafe_allow_html=True) | |
| # Save to project history | |
| if "project_history" not in session_state: | |
| session_state["project_history"] = [] | |
| session_state["project_history"].append({ | |
| "name": project_name, | |
| "description": project_description, | |
| "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), | |
| "data": json_data | |
| }) | |
| else: | |
| st.error("Error saving project data.") | |
| def _display_load_project(session_state: Dict[str, Any]) -> None: | |
| """ | |
| Display load project interface. | |
| Args: | |
| session_state: Streamlit session state containing project data | |
| """ | |
| st.subheader("Load Project") | |
| # Add file uploader | |
| uploaded_file = st.file_uploader("Upload Project File", type=["hvac", "json"]) | |
| if uploaded_file is not None: | |
| # Read file content | |
| json_data = uploaded_file.read().decode("utf-8") | |
| # Load project data | |
| project_data = DataPersistence.load_project_from_json(json_data) | |
| if project_data: | |
| # Add load button | |
| if st.button("Load Project Data"): | |
| # Update session state with project data | |
| for key, value in project_data.items(): | |
| session_state[key] = value | |
| st.success("Project loaded successfully!") | |
| st.experimental_rerun() | |
| else: | |
| st.error("Error loading project data. Invalid file format.") | |
| def _display_project_history(session_state: Dict[str, Any]) -> None: | |
| """ | |
| Display project history interface. | |
| Args: | |
| session_state: Streamlit session state containing project data | |
| """ | |
| st.subheader("Project History") | |
| # Check if project history exists | |
| if "project_history" not in session_state or not session_state["project_history"]: | |
| st.info("No project history found. Save a project to see it in the history.") | |
| return | |
| # Display project history | |
| for i, project in enumerate(reversed(session_state["project_history"])): | |
| with st.expander(f"{project['name']} - {project['timestamp']}"): | |
| st.write(f"**Description:** {project['description']}") | |
| # Add load button | |
| if st.button(f"Load Project", key=f"load_history_{i}"): | |
| # Load project data | |
| project_data = DataPersistence.load_project_from_json(project["data"]) | |
| if project_data: | |
| # Update session state with project data | |
| for key, value in project_data.items(): | |
| session_state[key] = value | |
| st.success("Project loaded successfully!") | |
| st.experimental_rerun() | |
| # Add download button | |
| filename = f"{project['name'].replace(' ', '_')}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.hvac" | |
| download_link = DataPersistence.get_download_link(project["data"], filename, "Download Project File") | |
| st.markdown(download_link, unsafe_allow_html=True) | |
| # Add delete button | |
| if st.button(f"Delete from History", key=f"delete_history_{i}"): | |
| # Remove project from history | |
| session_state["project_history"].remove(project) | |
| st.success("Project removed from history.") | |
| st.experimental_rerun() | |
| # Create a singleton instance | |
| data_persistence = DataPersistence() | |
| # Example usage | |
| if __name__ == "__main__": | |
| import streamlit as st | |
| # Initialize session state with dummy data for testing | |
| if "building_info" not in st.session_state: | |
| st.session_state["building_info"] = { | |
| "project_name": "Test Project", | |
| "building_name": "Test Building", | |
| "location": "New York", | |
| "climate_zone": "4A", | |
| "building_type": "Office", | |
| "floor_area": 1000.0, | |
| "num_floors": 2, | |
| "floor_height": 3.0, | |
| "orientation": "NORTH", | |
| "occupancy": 50, | |
| "operating_hours": "8:00-18:00", | |
| "design_conditions": { | |
| "summer_outdoor_db": 35.0, | |
| "summer_outdoor_wb": 25.0, | |
| "summer_indoor_db": 24.0, | |
| "summer_indoor_rh": 50.0, | |
| "winter_outdoor_db": -5.0, | |
| "winter_outdoor_rh": 80.0, | |
| "winter_indoor_db": 21.0, | |
| "winter_indoor_rh": 40.0 | |
| } | |
| } | |
| # Display project management interface | |
| data_persistence.display_project_management(st.session_state) | |