mabuseif commited on
Commit
82d8035
·
verified ·
1 Parent(s): 9bf0764

Rename app/streamlit_app.py to app/intro.py

Browse files
Files changed (2) hide show
  1. app/intro.py +394 -0
  2. 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
- ))