Spaces:
Sleeping
Sleeping
Rename app/streamlit_app.py to app/intro.py
Browse files- app/intro.py +394 -0
- app/streamlit_app.py +0 -40
app/intro.py
ADDED
@@ -0,0 +1,394 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
HVAC Load Calculator - Introduction Module
|
3 |
+
|
4 |
+
This module handles the introduction page of the HVAC Load Calculator application,
|
5 |
+
including application information, instructions, and project management functionality
|
6 |
+
(new project, import, export).
|
7 |
+
|
8 |
+
Developed by: Dr Majed Abuseif, Deakin University
|
9 |
+
© 2025
|
10 |
+
"""
|
11 |
+
|
12 |
+
import streamlit as st
|
13 |
+
import json
|
14 |
+
import base64
|
15 |
+
import io
|
16 |
+
import logging
|
17 |
+
from datetime import datetime
|
18 |
+
from typing import Dict, Any, Optional
|
19 |
+
|
20 |
+
# Configure logging
|
21 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
22 |
+
logger = logging.getLogger(__name__)
|
23 |
+
|
24 |
+
def display_intro_page():
|
25 |
+
"""
|
26 |
+
Display the introduction page with app information, instructions, and project management options.
|
27 |
+
This is the main function called by main.py when the Intro page is selected.
|
28 |
+
"""
|
29 |
+
st.title("HVAC Load Calculator")
|
30 |
+
|
31 |
+
# Create tabs for different sections of the intro page
|
32 |
+
tab1, tab2, tab3 = st.tabs(["About", "Instructions", "Project Management"])
|
33 |
+
|
34 |
+
# About tab content
|
35 |
+
with tab1:
|
36 |
+
display_about_section()
|
37 |
+
|
38 |
+
# Instructions tab content
|
39 |
+
with tab2:
|
40 |
+
display_instructions_section()
|
41 |
+
|
42 |
+
# Project Management tab content
|
43 |
+
with tab3:
|
44 |
+
display_project_management_section()
|
45 |
+
|
46 |
+
def display_about_section():
|
47 |
+
"""Display information about the application, its purpose, and developers."""
|
48 |
+
st.header("About the HVAC Load Calculator")
|
49 |
+
|
50 |
+
st.markdown("""
|
51 |
+
### Overview
|
52 |
+
|
53 |
+
The HVAC Load Calculator is a comprehensive tool for calculating heating and cooling loads for buildings
|
54 |
+
using the ASHRAE Transfer Function Method (TFM) and Conduction Transfer Function (CTF) approaches.
|
55 |
+
|
56 |
+
### Features
|
57 |
+
|
58 |
+
* **Building Information**: Define basic building parameters
|
59 |
+
* **Climate Data**: Import EPW weather files for location-specific calculations
|
60 |
+
* **Material Library**: Access and customize building materials and fenestrations
|
61 |
+
* **Construction Library**: Create and manage multi-layer constructions
|
62 |
+
* **Building Components**: Define walls, roofs, floors, windows, doors, and skylights
|
63 |
+
* **Internal Loads**: Account for people, lighting, and equipment heat gains
|
64 |
+
* **HVAC Loads**: Calculate cooling and heating loads using ASHRAE methods
|
65 |
+
* **Building Energy**: Estimate energy consumption based on HVAC system efficiency
|
66 |
+
* **Renewable Energy**: Size PV systems for net-zero energy goals
|
67 |
+
* **Embodied Energy**: Calculate the embodied carbon of building materials
|
68 |
+
* **Materials Cost**: Estimate construction material costs
|
69 |
+
|
70 |
+
### Development
|
71 |
+
|
72 |
+
This application was developed by Dr. Majed Abuseif at the School of Architecture and Built Environment,
|
73 |
+
Deakin University. It is designed as an educational tool for students to understand HVAC load calculations
|
74 |
+
without manual calculations.
|
75 |
+
|
76 |
+
### Version
|
77 |
+
|
78 |
+
Version 3.0.0 (2025)
|
79 |
+
""")
|
80 |
+
|
81 |
+
# Add Deakin University logo or other relevant images if available
|
82 |
+
# st.image("path_to_logo.png", width=200)
|
83 |
+
|
84 |
+
def display_instructions_section():
|
85 |
+
"""Display instructions on how to use the application."""
|
86 |
+
st.header("Instructions")
|
87 |
+
|
88 |
+
st.markdown("""
|
89 |
+
### Getting Started
|
90 |
+
|
91 |
+
1. **Create a New Project**: Start by entering basic building information
|
92 |
+
2. **Import Climate Data**: Upload an EPW file or select from available locations
|
93 |
+
3. **Define Materials**: Use the library or create custom materials
|
94 |
+
4. **Create Constructions**: Define wall, roof, and floor assemblies
|
95 |
+
5. **Add Building Components**: Specify the building envelope components
|
96 |
+
6. **Define Internal Loads**: Add occupancy, lighting, and equipment
|
97 |
+
7. **Calculate Loads**: Generate cooling and heating load results
|
98 |
+
8. **Explore Additional Analyses**: Energy consumption, renewable energy, embodied carbon, and cost
|
99 |
+
|
100 |
+
### Navigation
|
101 |
+
|
102 |
+
Use the sidebar to navigate between different sections of the application. You can go back to previous
|
103 |
+
sections at any time to modify inputs.
|
104 |
+
|
105 |
+
### Saving Your Work
|
106 |
+
|
107 |
+
Use the Project Management tab to save your project as a JSON file that can be imported later to
|
108 |
+
continue your work.
|
109 |
+
|
110 |
+
### Calculation Methods
|
111 |
+
|
112 |
+
This calculator uses the following ASHRAE-approved methods:
|
113 |
+
|
114 |
+
* **Conduction Transfer Function (CTF)**: For transient heat transfer through opaque surfaces
|
115 |
+
* **Transfer Function Method (TFM)**: For calculating cooling and heating loads
|
116 |
+
* **Sol-Air Temperature**: For solar radiation effects on opaque surfaces
|
117 |
+
* **Dynamic Solar Heat Gain**: For fenestration with angle-dependent properties
|
118 |
+
""")
|
119 |
+
|
120 |
+
# Add any diagrams or flowcharts if available
|
121 |
+
# st.image("path_to_flowchart.png", width=600)
|
122 |
+
|
123 |
+
def display_project_management_section():
|
124 |
+
"""Display project management options (new, import, export)."""
|
125 |
+
st.header("Project Management")
|
126 |
+
|
127 |
+
col1, col2, col3 = st.columns(3)
|
128 |
+
|
129 |
+
# Start New Project button
|
130 |
+
with col1:
|
131 |
+
if st.button("Start New Project", key="start_new_project"):
|
132 |
+
start_new_project()
|
133 |
+
st.success("New project initialized. Navigate to Building Information to begin.")
|
134 |
+
|
135 |
+
# Import Project section
|
136 |
+
with col2:
|
137 |
+
uploaded_file = st.file_uploader("Import Project", type=["json"], key="import_project")
|
138 |
+
if uploaded_file is not None:
|
139 |
+
if load_project(uploaded_file):
|
140 |
+
st.success("Project imported successfully!")
|
141 |
+
else:
|
142 |
+
st.error("Failed to import project. The file may be corrupted or in an incorrect format.")
|
143 |
+
|
144 |
+
# Export Project button
|
145 |
+
with col3:
|
146 |
+
if st.button("Export Project", key="export_project"):
|
147 |
+
if st.session_state.project_data.get("project_name"):
|
148 |
+
export_project()
|
149 |
+
else:
|
150 |
+
st.warning("Please enter a project name in the Building Information section before exporting.")
|
151 |
+
|
152 |
+
def start_new_project():
|
153 |
+
"""Initialize a new project by resetting the project_data in session state."""
|
154 |
+
# Keep a backup of the current project data in case user wants to recover
|
155 |
+
if 'project_data_backup' not in st.session_state:
|
156 |
+
st.session_state.project_data_backup = {}
|
157 |
+
|
158 |
+
# Only backup if there's actual project data
|
159 |
+
if st.session_state.project_data.get("project_name"):
|
160 |
+
backup_timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
161 |
+
st.session_state.project_data_backup[backup_timestamp] = st.session_state.project_data.copy()
|
162 |
+
|
163 |
+
# Reset project data to default values
|
164 |
+
st.session_state.project_data = {
|
165 |
+
"project_name": "",
|
166 |
+
"building_info": {
|
167 |
+
"project_name": "",
|
168 |
+
"floor_area": 100.0,
|
169 |
+
"building_height": 3.0,
|
170 |
+
"indoor_design_temp": 24.0,
|
171 |
+
"indoor_design_rh": 50.0,
|
172 |
+
"ventilation_rate": 0.1,
|
173 |
+
"orientation_angle": 0.0,
|
174 |
+
"operation_hours": 8,
|
175 |
+
"building_type": "Office"
|
176 |
+
},
|
177 |
+
"climate_data": {},
|
178 |
+
"materials": {
|
179 |
+
"library": {},
|
180 |
+
"project": {}
|
181 |
+
},
|
182 |
+
"fenestrations": {
|
183 |
+
"library": {},
|
184 |
+
"project": {}
|
185 |
+
},
|
186 |
+
"constructions": {
|
187 |
+
"library": {},
|
188 |
+
"project": {}
|
189 |
+
},
|
190 |
+
"components": {
|
191 |
+
"walls": [],
|
192 |
+
"roofs": [],
|
193 |
+
"floors": [],
|
194 |
+
"windows": [],
|
195 |
+
"doors": [],
|
196 |
+
"skylights": []
|
197 |
+
},
|
198 |
+
"internal_loads": {
|
199 |
+
"people": [],
|
200 |
+
"lighting": {},
|
201 |
+
"equipment": {}
|
202 |
+
},
|
203 |
+
"hvac_loads": {
|
204 |
+
"cooling": {
|
205 |
+
"hourly": [],
|
206 |
+
"peak": 0,
|
207 |
+
"summary_tables": {},
|
208 |
+
"charts": {}
|
209 |
+
},
|
210 |
+
"heating": {
|
211 |
+
"hourly": [],
|
212 |
+
"peak": 0,
|
213 |
+
"summary_tables": {},
|
214 |
+
"charts": {}
|
215 |
+
}
|
216 |
+
},
|
217 |
+
"building_energy": {
|
218 |
+
"hvac_type": "",
|
219 |
+
"cop": 0.0,
|
220 |
+
"energy_consumption": {
|
221 |
+
"cooling": 0,
|
222 |
+
"heating": 0,
|
223 |
+
"lighting": 0,
|
224 |
+
"equipment": 0,
|
225 |
+
"total": 0
|
226 |
+
},
|
227 |
+
"charts": {}
|
228 |
+
},
|
229 |
+
"renewable_energy": {
|
230 |
+
"pv_system_size_kw": 0,
|
231 |
+
"pv_generation_kwh": 0,
|
232 |
+
"net_energy_kwh": 0,
|
233 |
+
"zero_energy_status": "",
|
234 |
+
"charts": {}
|
235 |
+
},
|
236 |
+
"embodied_energy": {
|
237 |
+
"total_embodied_carbon_kgco2e": 0,
|
238 |
+
"breakdown_by_component": {},
|
239 |
+
"charts": {}
|
240 |
+
},
|
241 |
+
"materials_cost": {
|
242 |
+
"total_cost_usd": 0,
|
243 |
+
"breakdown_by_component": {},
|
244 |
+
"charts": {}
|
245 |
+
}
|
246 |
+
}
|
247 |
+
|
248 |
+
logger.info("New project initialized")
|
249 |
+
|
250 |
+
def load_project(uploaded_file) -> bool:
|
251 |
+
"""
|
252 |
+
Load a project from an uploaded JSON file.
|
253 |
+
|
254 |
+
Args:
|
255 |
+
uploaded_file: The uploaded file object from st.file_uploader
|
256 |
+
|
257 |
+
Returns:
|
258 |
+
bool: True if project was loaded successfully, False otherwise
|
259 |
+
"""
|
260 |
+
try:
|
261 |
+
# Read the uploaded file
|
262 |
+
content = uploaded_file.read()
|
263 |
+
project_data = json.loads(content)
|
264 |
+
|
265 |
+
# Validate the project data structure
|
266 |
+
if not validate_project_data(project_data):
|
267 |
+
logger.error("Invalid project data structure")
|
268 |
+
return False
|
269 |
+
|
270 |
+
# Keep a backup of the current project data
|
271 |
+
if 'project_data_backup' not in st.session_state:
|
272 |
+
st.session_state.project_data_backup = {}
|
273 |
+
|
274 |
+
# Only backup if there's actual project data
|
275 |
+
if st.session_state.project_data.get("project_name"):
|
276 |
+
backup_timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
277 |
+
st.session_state.project_data_backup[backup_timestamp] = st.session_state.project_data.copy()
|
278 |
+
|
279 |
+
# Update session state with the loaded project data
|
280 |
+
st.session_state.project_data = project_data
|
281 |
+
|
282 |
+
logger.info(f"Project '{project_data.get('project_name', 'Unnamed')}' loaded successfully")
|
283 |
+
return True
|
284 |
+
|
285 |
+
except Exception as e:
|
286 |
+
logger.error(f"Error loading project: {str(e)}")
|
287 |
+
return False
|
288 |
+
|
289 |
+
def validate_project_data(project_data: Dict[str, Any]) -> bool:
|
290 |
+
"""
|
291 |
+
Validate the structure of the project data.
|
292 |
+
|
293 |
+
Args:
|
294 |
+
project_data: The project data dictionary to validate
|
295 |
+
|
296 |
+
Returns:
|
297 |
+
bool: True if the project data structure is valid, False otherwise
|
298 |
+
"""
|
299 |
+
# Check for required top-level keys
|
300 |
+
required_keys = [
|
301 |
+
"building_info",
|
302 |
+
"climate_data",
|
303 |
+
"materials",
|
304 |
+
"fenestrations",
|
305 |
+
"constructions",
|
306 |
+
"components",
|
307 |
+
"internal_loads",
|
308 |
+
"hvac_loads",
|
309 |
+
"building_energy",
|
310 |
+
"renewable_energy",
|
311 |
+
"embodied_energy",
|
312 |
+
"materials_cost"
|
313 |
+
]
|
314 |
+
|
315 |
+
for key in required_keys:
|
316 |
+
if key not in project_data:
|
317 |
+
logger.error(f"Missing required key in project data: {key}")
|
318 |
+
return False
|
319 |
+
|
320 |
+
# Check building_info structure
|
321 |
+
building_info_keys = [
|
322 |
+
"project_name",
|
323 |
+
"floor_area",
|
324 |
+
"building_height",
|
325 |
+
"indoor_design_temp",
|
326 |
+
"indoor_design_rh",
|
327 |
+
"orientation_angle",
|
328 |
+
"operation_hours",
|
329 |
+
"building_type"
|
330 |
+
]
|
331 |
+
|
332 |
+
for key in building_info_keys:
|
333 |
+
if key not in project_data["building_info"]:
|
334 |
+
logger.error(f"Missing required key in building_info: {key}")
|
335 |
+
return False
|
336 |
+
|
337 |
+
# Check components structure
|
338 |
+
component_types = ["walls", "roofs", "floors", "windows", "doors", "skylights"]
|
339 |
+
for component_type in component_types:
|
340 |
+
if component_type not in project_data["components"]:
|
341 |
+
logger.error(f"Missing component type in components: {component_type}")
|
342 |
+
return False
|
343 |
+
|
344 |
+
# Check hvac_loads structure
|
345 |
+
for load_type in ["cooling", "heating"]:
|
346 |
+
if load_type not in project_data["hvac_loads"]:
|
347 |
+
logger.error(f"Missing load type in hvac_loads: {load_type}")
|
348 |
+
return False
|
349 |
+
|
350 |
+
return True
|
351 |
+
|
352 |
+
def export_project():
|
353 |
+
"""Export the current project as a downloadable JSON file."""
|
354 |
+
try:
|
355 |
+
# Convert project data to JSON
|
356 |
+
project_json = json.dumps(st.session_state.project_data, indent=2)
|
357 |
+
|
358 |
+
# Generate filename based on project name
|
359 |
+
project_name = st.session_state.project_data.get("project_name", "unnamed_project")
|
360 |
+
safe_project_name = "".join(c if c.isalnum() else "_" for c in project_name)
|
361 |
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
362 |
+
filename = f"{safe_project_name}_{timestamp}.json"
|
363 |
+
|
364 |
+
# Create a download button for the JSON file
|
365 |
+
st.download_button(
|
366 |
+
label="Download Project File",
|
367 |
+
data=project_json,
|
368 |
+
file_name=filename,
|
369 |
+
mime="application/json",
|
370 |
+
key="download_project"
|
371 |
+
)
|
372 |
+
|
373 |
+
logger.info(f"Project '{project_name}' exported as {filename}")
|
374 |
+
|
375 |
+
except Exception as e:
|
376 |
+
st.error(f"Error exporting project: {str(e)}")
|
377 |
+
logger.error(f"Error exporting project: {str(e)}")
|
378 |
+
|
379 |
+
# Helper function to create a download link (alternative to st.download_button)
|
380 |
+
def get_download_link(data, filename, link_text):
|
381 |
+
"""
|
382 |
+
Generate a download link for a file.
|
383 |
+
|
384 |
+
Args:
|
385 |
+
data: The data to be downloaded
|
386 |
+
filename: The name of the file
|
387 |
+
link_text: The text to display for the download link
|
388 |
+
|
389 |
+
Returns:
|
390 |
+
str: HTML link for downloading the file
|
391 |
+
"""
|
392 |
+
b64 = base64.b64encode(data.encode()).decode()
|
393 |
+
href = f'<a href="data:application/json;base64,{b64}" download="{filename}">{link_text}</a>'
|
394 |
+
return href
|
app/streamlit_app.py
DELETED
@@ -1,40 +0,0 @@
|
|
1 |
-
import altair as alt
|
2 |
-
import numpy as np
|
3 |
-
import pandas as pd
|
4 |
-
import streamlit as st
|
5 |
-
|
6 |
-
"""
|
7 |
-
# Welcome to Streamlit!
|
8 |
-
|
9 |
-
Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
|
10 |
-
If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
|
11 |
-
forums](https://discuss.streamlit.io).
|
12 |
-
|
13 |
-
In the meantime, below is an example of what you can do with just a few lines of code:
|
14 |
-
"""
|
15 |
-
|
16 |
-
num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
|
17 |
-
num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
|
18 |
-
|
19 |
-
indices = np.linspace(0, 1, num_points)
|
20 |
-
theta = 2 * np.pi * num_turns * indices
|
21 |
-
radius = indices
|
22 |
-
|
23 |
-
x = radius * np.cos(theta)
|
24 |
-
y = radius * np.sin(theta)
|
25 |
-
|
26 |
-
df = pd.DataFrame({
|
27 |
-
"x": x,
|
28 |
-
"y": y,
|
29 |
-
"idx": indices,
|
30 |
-
"rand": np.random.randn(num_points),
|
31 |
-
})
|
32 |
-
|
33 |
-
st.altair_chart(alt.Chart(df, height=700, width=700)
|
34 |
-
.mark_point(filled=True)
|
35 |
-
.encode(
|
36 |
-
x=alt.X("x", axis=None),
|
37 |
-
y=alt.Y("y", axis=None),
|
38 |
-
color=alt.Color("idx", legend=None, scale=alt.Scale()),
|
39 |
-
size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
|
40 |
-
))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|