Upload 31 files
Browse files- app/main.py +90 -298
- utils/psychrometric_visualization.py +163 -364
app/main.py
CHANGED
|
@@ -116,6 +116,10 @@ class HVACCalculator:
|
|
| 116 |
self.data_persistence = DataPersistence()
|
| 117 |
self.data_export = DataExport()
|
| 118 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
# Set up the application layout
|
| 120 |
self.setup_layout()
|
| 121 |
|
|
@@ -142,6 +146,10 @@ class HVACCalculator:
|
|
| 142 |
if selected_page != st.session_state.page:
|
| 143 |
st.session_state.page = selected_page
|
| 144 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
# Display the selected page
|
| 146 |
self.display_page(st.session_state.page)
|
| 147 |
|
|
@@ -272,323 +280,48 @@ class HVACCalculator:
|
|
| 272 |
)
|
| 273 |
|
| 274 |
st.plotly_chart(fig, use_container_width=True)
|
| 275 |
-
|
| 276 |
-
# Navigation buttons
|
| 277 |
-
col1, col2 = st.columns(2)
|
| 278 |
-
with col1:
|
| 279 |
-
st.button("Back to Building Information", on_click=self.navigate_to, args=["Building Information"])
|
| 280 |
-
with col2:
|
| 281 |
-
st.button("Continue to Building Components", on_click=self.navigate_to, args=["Building Components"])
|
| 282 |
|
| 283 |
def display_internal_loads(self):
|
| 284 |
"""Display the internal loads page."""
|
| 285 |
st.title("Internal Loads")
|
| 286 |
|
| 287 |
-
# Check if building
|
| 288 |
-
if not
|
| 289 |
-
st.warning("Please
|
| 290 |
-
st.button("Go to Building
|
| 291 |
return
|
| 292 |
|
| 293 |
-
#
|
| 294 |
-
|
| 295 |
|
| 296 |
-
|
| 297 |
-
with tabs[0]:
|
| 298 |
self.display_people_loads()
|
| 299 |
|
| 300 |
-
|
| 301 |
-
with tabs[1]:
|
| 302 |
self.display_lighting_loads()
|
| 303 |
|
| 304 |
-
|
| 305 |
-
with tabs[2]:
|
| 306 |
self.display_equipment_loads()
|
| 307 |
|
| 308 |
# Display summary of internal loads
|
| 309 |
-
self.display_internal_loads_summary()
|
| 310 |
-
|
| 311 |
-
# Navigation buttons
|
| 312 |
-
col1, col2 = st.columns(2)
|
| 313 |
-
with col1:
|
| 314 |
-
st.button("Back to Building Components", on_click=self.navigate_to, args=["Building Components"])
|
| 315 |
-
with col2:
|
| 316 |
-
# Validate internal loads before proceeding
|
| 317 |
-
if self.data_validation.validate_internal_loads(st.session_state.get("internal_loads", {})):
|
| 318 |
-
st.button("Continue to Calculation Results", on_click=self.navigate_to, args=["Calculation Results"])
|
| 319 |
-
else:
|
| 320 |
-
st.button("Continue to Calculation Results", disabled=True)
|
| 321 |
-
|
| 322 |
-
def display_people_loads(self):
|
| 323 |
-
"""Display the people loads section."""
|
| 324 |
-
st.subheader("People")
|
| 325 |
-
|
| 326 |
-
# Form for adding people loads
|
| 327 |
-
with st.form("people_load_form"):
|
| 328 |
-
col1, col2 = st.columns(2)
|
| 329 |
-
|
| 330 |
-
with col1:
|
| 331 |
-
name = st.text_input("Name", "Occupants")
|
| 332 |
-
num_people = st.number_input("Number of People", min_value=1, value=10)
|
| 333 |
-
|
| 334 |
-
with col2:
|
| 335 |
-
activity_level = st.selectbox(
|
| 336 |
-
"Activity Level",
|
| 337 |
-
options=["Seated, resting", "Seated, light work", "Office work", "Standing, light work", "Walking", "Heavy work"]
|
| 338 |
-
)
|
| 339 |
-
zone_type = st.selectbox(
|
| 340 |
-
"Zone Type",
|
| 341 |
-
options=["Office", "Residential", "Retail", "Educational", "Healthcare"]
|
| 342 |
-
)
|
| 343 |
-
|
| 344 |
-
hours_in_operation = st.slider("Hours in Operation", min_value=1, max_value=24, value=8)
|
| 345 |
-
|
| 346 |
-
submitted = st.form_submit_button("Add People Load")
|
| 347 |
-
|
| 348 |
-
if submitted:
|
| 349 |
-
# Create people load
|
| 350 |
-
people_load = {
|
| 351 |
-
"id": f"people_{len(st.session_state.internal_loads['people'])}",
|
| 352 |
-
"name": name,
|
| 353 |
-
"num_people": num_people,
|
| 354 |
-
"activity_level": activity_level,
|
| 355 |
-
"zone_type": zone_type,
|
| 356 |
-
"hours_in_operation": hours_in_operation
|
| 357 |
-
}
|
| 358 |
-
|
| 359 |
-
# Add to session state
|
| 360 |
-
st.session_state.internal_loads['people'].append(people_load)
|
| 361 |
-
st.success(f"Added {name} with {num_people} people")
|
| 362 |
-
|
| 363 |
-
# Display existing people loads
|
| 364 |
-
if st.session_state.internal_loads['people']:
|
| 365 |
-
st.subheader("Existing People Loads")
|
| 366 |
-
|
| 367 |
-
people_data = []
|
| 368 |
-
for load in st.session_state.internal_loads['people']:
|
| 369 |
-
people_data.append({
|
| 370 |
-
"Name": load['name'],
|
| 371 |
-
"Number of People": load['num_people'],
|
| 372 |
-
"Activity Level": load['activity_level'],
|
| 373 |
-
"Zone Type": load['zone_type'],
|
| 374 |
-
"Hours in Operation": load['hours_in_operation'],
|
| 375 |
-
"Actions": load['id']
|
| 376 |
-
})
|
| 377 |
-
|
| 378 |
-
df = pd.DataFrame(people_data)
|
| 379 |
-
|
| 380 |
-
# Display table with edit and delete buttons
|
| 381 |
-
for i, row in df.iterrows():
|
| 382 |
-
col1, col2, col3, col4, col5, col6, col7 = st.columns([2, 1, 2, 2, 1, 1, 1])
|
| 383 |
-
|
| 384 |
-
with col1:
|
| 385 |
-
st.write(row["Name"])
|
| 386 |
-
with col2:
|
| 387 |
-
st.write(row["Number of People"])
|
| 388 |
-
with col3:
|
| 389 |
-
st.write(row["Activity Level"])
|
| 390 |
-
with col4:
|
| 391 |
-
st.write(row["Zone Type"])
|
| 392 |
-
with col5:
|
| 393 |
-
st.write(row["Hours in Operation"])
|
| 394 |
-
with col6:
|
| 395 |
-
if st.button("Edit", key=f"edit_{row['Actions']}"):
|
| 396 |
-
# Set session state for editing
|
| 397 |
-
st.session_state.editing_people = row['Actions']
|
| 398 |
-
with col7:
|
| 399 |
-
if st.button("Delete", key=f"delete_{row['Actions']}"):
|
| 400 |
-
# Remove from session state
|
| 401 |
-
st.session_state.internal_loads['people'] = [
|
| 402 |
-
load for load in st.session_state.internal_loads['people']
|
| 403 |
-
if load['id'] != row['Actions']
|
| 404 |
-
]
|
| 405 |
-
st.experimental_rerun()
|
| 406 |
-
|
| 407 |
-
def display_lighting_loads(self):
|
| 408 |
-
"""Display the lighting loads section."""
|
| 409 |
-
st.subheader("Lighting")
|
| 410 |
-
|
| 411 |
-
# Form for adding lighting loads
|
| 412 |
-
with st.form("lighting_load_form"):
|
| 413 |
-
col1, col2 = st.columns(2)
|
| 414 |
-
|
| 415 |
-
with col1:
|
| 416 |
-
name = st.text_input("Name", "General Lighting")
|
| 417 |
-
power = st.number_input("Power (W)", min_value=0.0, value=1000.0)
|
| 418 |
-
|
| 419 |
-
with col2:
|
| 420 |
-
usage_factor = st.slider("Usage Factor", min_value=0.0, max_value=1.0, value=0.8, step=0.1)
|
| 421 |
-
zone_type = st.selectbox(
|
| 422 |
-
"Zone Type",
|
| 423 |
-
options=["Office", "Residential", "Retail", "Educational", "Healthcare"]
|
| 424 |
-
)
|
| 425 |
-
|
| 426 |
-
hours_in_operation = st.slider("Hours in Operation", min_value=1, max_value=24, value=8)
|
| 427 |
-
|
| 428 |
-
submitted = st.form_submit_button("Add Lighting Load")
|
| 429 |
-
|
| 430 |
-
if submitted:
|
| 431 |
-
# Create lighting load
|
| 432 |
-
lighting_load = {
|
| 433 |
-
"id": f"lighting_{len(st.session_state.internal_loads['lighting'])}",
|
| 434 |
-
"name": name,
|
| 435 |
-
"power": power,
|
| 436 |
-
"usage_factor": usage_factor,
|
| 437 |
-
"zone_type": zone_type,
|
| 438 |
-
"hours_in_operation": hours_in_operation
|
| 439 |
-
}
|
| 440 |
-
|
| 441 |
-
# Add to session state
|
| 442 |
-
st.session_state.internal_loads['lighting'].append(lighting_load)
|
| 443 |
-
st.success(f"Added {name} with {power} W")
|
| 444 |
-
|
| 445 |
-
# Display existing lighting loads
|
| 446 |
-
if st.session_state.internal_loads['lighting']:
|
| 447 |
-
st.subheader("Existing Lighting Loads")
|
| 448 |
-
|
| 449 |
-
lighting_data = []
|
| 450 |
-
for load in st.session_state.internal_loads['lighting']:
|
| 451 |
-
lighting_data.append({
|
| 452 |
-
"Name": load['name'],
|
| 453 |
-
"Power (W)": load['power'],
|
| 454 |
-
"Usage Factor": load['usage_factor'],
|
| 455 |
-
"Zone Type": load['zone_type'],
|
| 456 |
-
"Hours in Operation": load['hours_in_operation'],
|
| 457 |
-
"Actions": load['id']
|
| 458 |
-
})
|
| 459 |
-
|
| 460 |
-
df = pd.DataFrame(lighting_data)
|
| 461 |
-
|
| 462 |
-
# Display table with edit and delete buttons
|
| 463 |
-
for i, row in df.iterrows():
|
| 464 |
-
col1, col2, col3, col4, col5, col6, col7 = st.columns([2, 1, 1, 2, 1, 1, 1])
|
| 465 |
-
|
| 466 |
-
with col1:
|
| 467 |
-
st.write(row["Name"])
|
| 468 |
-
with col2:
|
| 469 |
-
st.write(f"{row['Power (W)']:.1f}")
|
| 470 |
-
with col3:
|
| 471 |
-
st.write(f"{row['Usage Factor']:.1f}")
|
| 472 |
-
with col4:
|
| 473 |
-
st.write(row["Zone Type"])
|
| 474 |
-
with col5:
|
| 475 |
-
st.write(row["Hours in Operation"])
|
| 476 |
-
with col6:
|
| 477 |
-
if st.button("Edit", key=f"edit_{row['Actions']}"):
|
| 478 |
-
# Set session state for editing
|
| 479 |
-
st.session_state.editing_lighting = row['Actions']
|
| 480 |
-
with col7:
|
| 481 |
-
if st.button("Delete", key=f"delete_{row['Actions']}"):
|
| 482 |
-
# Remove from session state
|
| 483 |
-
st.session_state.internal_loads['lighting'] = [
|
| 484 |
-
load for load in st.session_state.internal_loads['lighting']
|
| 485 |
-
if load['id'] != row['Actions']
|
| 486 |
-
]
|
| 487 |
-
st.experimental_rerun()
|
| 488 |
-
|
| 489 |
-
def display_equipment_loads(self):
|
| 490 |
-
"""Display the equipment loads section."""
|
| 491 |
-
st.subheader("Equipment")
|
| 492 |
-
|
| 493 |
-
# Form for adding equipment loads
|
| 494 |
-
with st.form("equipment_load_form"):
|
| 495 |
-
col1, col2 = st.columns(2)
|
| 496 |
-
|
| 497 |
-
with col1:
|
| 498 |
-
name = st.text_input("Name", "Office Equipment")
|
| 499 |
-
power = st.number_input("Power (W)", min_value=0.0, value=500.0)
|
| 500 |
-
|
| 501 |
-
with col2:
|
| 502 |
-
usage_factor = st.slider("Usage Factor", min_value=0.0, max_value=1.0, value=0.7, step=0.1)
|
| 503 |
-
radiation_fraction = st.slider("Radiation Fraction", min_value=0.0, max_value=1.0, value=0.3, step=0.1)
|
| 504 |
-
|
| 505 |
-
zone_type = st.selectbox(
|
| 506 |
-
"Zone Type",
|
| 507 |
-
options=["Office", "Residential", "Retail", "Educational", "Healthcare"]
|
| 508 |
-
)
|
| 509 |
-
|
| 510 |
-
hours_in_operation = st.slider("Hours in Operation", min_value=1, max_value=24, value=8)
|
| 511 |
-
|
| 512 |
-
submitted = st.form_submit_button("Add Equipment Load")
|
| 513 |
-
|
| 514 |
-
if submitted:
|
| 515 |
-
# Create equipment load
|
| 516 |
-
equipment_load = {
|
| 517 |
-
"id": f"equipment_{len(st.session_state.internal_loads['equipment'])}",
|
| 518 |
-
"name": name,
|
| 519 |
-
"power": power,
|
| 520 |
-
"usage_factor": usage_factor,
|
| 521 |
-
"radiation_fraction": radiation_fraction,
|
| 522 |
-
"zone_type": zone_type,
|
| 523 |
-
"hours_in_operation": hours_in_operation
|
| 524 |
-
}
|
| 525 |
-
|
| 526 |
-
# Add to session state
|
| 527 |
-
st.session_state.internal_loads['equipment'].append(equipment_load)
|
| 528 |
-
st.success(f"Added {name} with {power} W")
|
| 529 |
-
|
| 530 |
-
# Display existing equipment loads
|
| 531 |
-
if st.session_state.internal_loads['equipment']:
|
| 532 |
-
st.subheader("Existing Equipment Loads")
|
| 533 |
-
|
| 534 |
-
equipment_data = []
|
| 535 |
-
for load in st.session_state.internal_loads['equipment']:
|
| 536 |
-
equipment_data.append({
|
| 537 |
-
"Name": load['name'],
|
| 538 |
-
"Power (W)": load['power'],
|
| 539 |
-
"Usage Factor": load['usage_factor'],
|
| 540 |
-
"Radiation Fraction": load['radiation_fraction'],
|
| 541 |
-
"Zone Type": load['zone_type'],
|
| 542 |
-
"Hours in Operation": load['hours_in_operation'],
|
| 543 |
-
"Actions": load['id']
|
| 544 |
-
})
|
| 545 |
-
|
| 546 |
-
df = pd.DataFrame(equipment_data)
|
| 547 |
-
|
| 548 |
-
# Display table with edit and delete buttons
|
| 549 |
-
for i, row in df.iterrows():
|
| 550 |
-
col1, col2, col3, col4, col5, col6, col7, col8 = st.columns([2, 1, 1, 1, 2, 1, 1, 1])
|
| 551 |
-
|
| 552 |
-
with col1:
|
| 553 |
-
st.write(row["Name"])
|
| 554 |
-
with col2:
|
| 555 |
-
st.write(f"{row['Power (W)']:.1f}")
|
| 556 |
-
with col3:
|
| 557 |
-
st.write(f"{row['Usage Factor']:.1f}")
|
| 558 |
-
with col4:
|
| 559 |
-
st.write(f"{row['Radiation Fraction']:.1f}")
|
| 560 |
-
with col5:
|
| 561 |
-
st.write(row["Zone Type"])
|
| 562 |
-
with col6:
|
| 563 |
-
st.write(row["Hours in Operation"])
|
| 564 |
-
with col7:
|
| 565 |
-
if st.button("Edit", key=f"edit_{row['Actions']}"):
|
| 566 |
-
# Set session state for editing
|
| 567 |
-
st.session_state.editing_equipment = row['Actions']
|
| 568 |
-
with col8:
|
| 569 |
-
if st.button("Delete", key=f"delete_{row['Actions']}"):
|
| 570 |
-
# Remove from session state
|
| 571 |
-
st.session_state.internal_loads['equipment'] = [
|
| 572 |
-
load for load in st.session_state.internal_loads['equipment']
|
| 573 |
-
if load['id'] != row['Actions']
|
| 574 |
-
]
|
| 575 |
-
st.experimental_rerun()
|
| 576 |
-
|
| 577 |
-
def display_internal_loads_summary(self):
|
| 578 |
-
"""Display a summary of all internal loads."""
|
| 579 |
st.subheader("Internal Loads Summary")
|
| 580 |
|
| 581 |
-
#
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 585 |
|
| 586 |
-
# Calculate total
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
|
| 590 |
|
| 591 |
-
# Display
|
| 592 |
col1, col2, col3 = st.columns(3)
|
| 593 |
|
| 594 |
with col1:
|
|
@@ -623,6 +356,65 @@ class HVACCalculator:
|
|
| 623 |
"""
|
| 624 |
st.session_state.page = page
|
| 625 |
st.experimental_rerun()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 626 |
|
| 627 |
|
| 628 |
if __name__ == "__main__":
|
|
|
|
| 116 |
self.data_persistence = DataPersistence()
|
| 117 |
self.data_export = DataExport()
|
| 118 |
|
| 119 |
+
# Initialize calculation modules
|
| 120 |
+
self.cooling_load = CoolingLoad()
|
| 121 |
+
self.heating_load = HeatingLoad()
|
| 122 |
+
|
| 123 |
# Set up the application layout
|
| 124 |
self.setup_layout()
|
| 125 |
|
|
|
|
| 146 |
if selected_page != st.session_state.page:
|
| 147 |
st.session_state.page = selected_page
|
| 148 |
|
| 149 |
+
# Add calculate button in sidebar
|
| 150 |
+
if st.sidebar.button("Run Calculations"):
|
| 151 |
+
self.run_calculations()
|
| 152 |
+
|
| 153 |
# Display the selected page
|
| 154 |
self.display_page(st.session_state.page)
|
| 155 |
|
|
|
|
| 280 |
)
|
| 281 |
|
| 282 |
st.plotly_chart(fig, use_container_width=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 283 |
|
| 284 |
def display_internal_loads(self):
|
| 285 |
"""Display the internal loads page."""
|
| 286 |
st.title("Internal Loads")
|
| 287 |
|
| 288 |
+
# Check if building information is available
|
| 289 |
+
if not st.session_state.building_info:
|
| 290 |
+
st.warning("Please enter building information first.")
|
| 291 |
+
st.button("Go to Building Information", on_click=self.navigate_to, args=["Building Information"])
|
| 292 |
return
|
| 293 |
|
| 294 |
+
# Create tabs for different internal load types
|
| 295 |
+
tab1, tab2, tab3 = st.tabs(["People", "Lighting", "Equipment"])
|
| 296 |
|
| 297 |
+
with tab1:
|
|
|
|
| 298 |
self.display_people_loads()
|
| 299 |
|
| 300 |
+
with tab2:
|
|
|
|
| 301 |
self.display_lighting_loads()
|
| 302 |
|
| 303 |
+
with tab3:
|
|
|
|
| 304 |
self.display_equipment_loads()
|
| 305 |
|
| 306 |
# Display summary of internal loads
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 307 |
st.subheader("Internal Loads Summary")
|
| 308 |
|
| 309 |
+
# Calculate total people
|
| 310 |
+
total_people = 0
|
| 311 |
+
for people_load in st.session_state.internal_loads.get('people', []):
|
| 312 |
+
total_people += people_load.get('count', 0)
|
| 313 |
+
|
| 314 |
+
# Calculate total lighting power
|
| 315 |
+
total_lighting_power = 0
|
| 316 |
+
for lighting_load in st.session_state.internal_loads.get('lighting', []):
|
| 317 |
+
total_lighting_power += lighting_load.get('power', 0) * lighting_load.get('usage_factor', 1.0)
|
| 318 |
|
| 319 |
+
# Calculate total equipment power
|
| 320 |
+
total_equipment_power = 0
|
| 321 |
+
for equipment_load in st.session_state.internal_loads.get('equipment', []):
|
| 322 |
+
total_equipment_power += equipment_load.get('power', 0) * equipment_load.get('usage_factor', 1.0)
|
| 323 |
|
| 324 |
+
# Display metrics
|
| 325 |
col1, col2, col3 = st.columns(3)
|
| 326 |
|
| 327 |
with col1:
|
|
|
|
| 356 |
"""
|
| 357 |
st.session_state.page = page
|
| 358 |
st.experimental_rerun()
|
| 359 |
+
|
| 360 |
+
def run_calculations(self):
|
| 361 |
+
"""Run HVAC load calculations and update session state with results."""
|
| 362 |
+
# Check if required data is available
|
| 363 |
+
if not self.data_validation.validate_calculation_inputs(st.session_state):
|
| 364 |
+
st.error("Cannot run calculations. Please complete all required inputs first.")
|
| 365 |
+
return
|
| 366 |
+
|
| 367 |
+
# Get building components and design conditions
|
| 368 |
+
building_components = st.session_state.components
|
| 369 |
+
building_info = st.session_state.building_info
|
| 370 |
+
internal_loads = st.session_state.internal_loads
|
| 371 |
+
|
| 372 |
+
# Extract design conditions
|
| 373 |
+
design_conditions = building_info.get('design_conditions', {})
|
| 374 |
+
|
| 375 |
+
# Run cooling load calculations
|
| 376 |
+
cooling_results = self.cooling_load.calculate_total_cooling_load(
|
| 377 |
+
building_components=building_components,
|
| 378 |
+
outdoor_temp=design_conditions.get('summer_outdoor_db', 35),
|
| 379 |
+
indoor_temp=design_conditions.get('summer_indoor_db', 24),
|
| 380 |
+
outdoor_humidity=design_conditions.get('summer_outdoor_wb', 25), # Using wet-bulb for outdoor humidity
|
| 381 |
+
indoor_humidity=design_conditions.get('summer_indoor_rh', 50),
|
| 382 |
+
ground_temp=design_conditions.get('summer_ground_temp', 20),
|
| 383 |
+
daily_range=design_conditions.get('summer_daily_range', 8),
|
| 384 |
+
month=design_conditions.get('summer_design_month', 7),
|
| 385 |
+
hour=design_conditions.get('summer_design_hour', 15),
|
| 386 |
+
latitude=building_info.get('latitude', 40),
|
| 387 |
+
internal_loads=internal_loads
|
| 388 |
+
)
|
| 389 |
+
|
| 390 |
+
# Run heating load calculations
|
| 391 |
+
heating_results = self.heating_load.calculate_design_heating_load(
|
| 392 |
+
building_components=building_components,
|
| 393 |
+
outdoor_temp=design_conditions.get('winter_outdoor_db', 0),
|
| 394 |
+
indoor_temp=design_conditions.get('winter_indoor_db', 22),
|
| 395 |
+
outdoor_humidity=design_conditions.get('winter_outdoor_rh', 80),
|
| 396 |
+
indoor_humidity=design_conditions.get('winter_indoor_rh', 40),
|
| 397 |
+
ground_temp=design_conditions.get('winter_ground_temp', 10),
|
| 398 |
+
safety_factor=design_conditions.get('heating_safety_factor', 15)
|
| 399 |
+
)
|
| 400 |
+
|
| 401 |
+
# Calculate load per area
|
| 402 |
+
floor_area = building_info.get('floor_area', 100) # Default to 100 m² if not specified
|
| 403 |
+
|
| 404 |
+
cooling_results['load_per_area'] = cooling_results['total_load'] * 1000 / floor_area # Convert kW to W/m²
|
| 405 |
+
heating_results['load_per_area'] = heating_results['total_load'] * 1000 / floor_area # Convert kW to W/m²
|
| 406 |
+
|
| 407 |
+
# Update session state with calculation results
|
| 408 |
+
st.session_state.calculation_results = {
|
| 409 |
+
'cooling': cooling_results,
|
| 410 |
+
'heating': heating_results
|
| 411 |
+
}
|
| 412 |
+
|
| 413 |
+
# Show success message
|
| 414 |
+
st.success("Calculations completed successfully!")
|
| 415 |
+
|
| 416 |
+
# Navigate to results page
|
| 417 |
+
self.navigate_to("Calculation Results")
|
| 418 |
|
| 419 |
|
| 420 |
if __name__ == "__main__":
|
utils/psychrometric_visualization.py
CHANGED
|
@@ -225,13 +225,7 @@ class PsychrometricVisualization:
|
|
| 225 |
marker=dict(size=10, color=color),
|
| 226 |
text=[name],
|
| 227 |
textposition="top center",
|
| 228 |
-
name=name
|
| 229 |
-
hovertemplate=(
|
| 230 |
-
f"<b>{name}</b><br>" +
|
| 231 |
-
"Temperature: %{x:.1f}°C<br>" +
|
| 232 |
-
"Humidity Ratio: %{y:.5f} kg/kg<br>" +
|
| 233 |
-
f"Relative Humidity: {rh:.1f}%<br>"
|
| 234 |
-
)
|
| 235 |
))
|
| 236 |
|
| 237 |
# Add processes if specified
|
|
@@ -256,380 +250,185 @@ class PsychrometricVisualization:
|
|
| 256 |
y=[start_w, end_w],
|
| 257 |
mode="lines+markers",
|
| 258 |
line=dict(color=color, width=2, dash="solid"),
|
| 259 |
-
marker=dict(size=8
|
| 260 |
name=name
|
| 261 |
))
|
| 262 |
-
|
| 263 |
-
# Add arrow to indicate direction
|
| 264 |
-
fig.add_annotation(
|
| 265 |
-
x=end_temp,
|
| 266 |
-
y=end_w,
|
| 267 |
-
ax=start_temp,
|
| 268 |
-
ay=start_w,
|
| 269 |
-
xref="x",
|
| 270 |
-
yref="y",
|
| 271 |
-
axref="x",
|
| 272 |
-
ayref="y",
|
| 273 |
-
showarrow=True,
|
| 274 |
-
arrowhead=2,
|
| 275 |
-
arrowsize=1,
|
| 276 |
-
arrowwidth=2,
|
| 277 |
-
arrowcolor=color
|
| 278 |
-
)
|
| 279 |
|
| 280 |
# Update layout
|
| 281 |
fig.update_layout(
|
| 282 |
title="Psychrometric Chart",
|
| 283 |
xaxis_title="Dry-Bulb Temperature (°C)",
|
| 284 |
yaxis_title="Humidity Ratio (kg/kg)",
|
| 285 |
-
|
| 286 |
-
range=[self.temp_min, self.temp_max],
|
| 287 |
-
gridcolor="rgba(0, 0, 0, 0.1)",
|
| 288 |
-
showgrid=True
|
| 289 |
-
),
|
| 290 |
-
yaxis=dict(
|
| 291 |
-
range=[self.w_min, self.w_max],
|
| 292 |
-
gridcolor="rgba(0, 0, 0, 0.1)",
|
| 293 |
-
showgrid=True
|
| 294 |
-
),
|
| 295 |
height=700,
|
| 296 |
-
margin=dict(l=50, r=50,
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
y=1.02,
|
| 301 |
-
xanchor="right",
|
| 302 |
-
x=1
|
| 303 |
-
),
|
| 304 |
-
hovermode="closest"
|
| 305 |
)
|
| 306 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 307 |
return fig
|
| 308 |
-
|
| 309 |
-
def
|
| 310 |
"""
|
| 311 |
-
|
| 312 |
|
| 313 |
Args:
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
Returns:
|
| 317 |
-
Plotly figure with process visualization
|
| 318 |
"""
|
| 319 |
-
# Extract
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
# Add process to psychrometric chart
|
| 363 |
-
chart_fig = self.create_psychrometric_chart(
|
| 364 |
-
points=[
|
| 365 |
-
{"temp": start_temp, "rh": start_rh, "name": "Start", "color": "blue"},
|
| 366 |
-
{"temp": end_temp, "rh": end_rh, "name": "End", "color": "red"}
|
| 367 |
-
],
|
| 368 |
-
processes=[
|
| 369 |
-
{"start": {"temp": start_temp, "rh": start_rh},
|
| 370 |
-
"end": {"temp": end_temp, "rh": end_rh},
|
| 371 |
-
"name": process_type,
|
| 372 |
-
"color": "green"}
|
| 373 |
-
]
|
| 374 |
-
)
|
| 375 |
-
|
| 376 |
-
# Create process diagram
|
| 377 |
-
# Create data for process parameters
|
| 378 |
-
params = [
|
| 379 |
-
"Dry-Bulb Temperature (°C)",
|
| 380 |
-
"Relative Humidity (%)",
|
| 381 |
-
"Humidity Ratio (g/kg)",
|
| 382 |
-
"Enthalpy (kJ/kg)",
|
| 383 |
-
"Wet-Bulb Temperature (°C)",
|
| 384 |
-
"Dew Point Temperature (°C)",
|
| 385 |
-
"Specific Volume (m³/kg)"
|
| 386 |
-
]
|
| 387 |
-
|
| 388 |
-
start_values = [
|
| 389 |
-
start_props["dry_bulb_temperature"],
|
| 390 |
-
start_props["relative_humidity"],
|
| 391 |
-
start_props["humidity_ratio"] * 1000, # Convert to g/kg
|
| 392 |
-
start_props["enthalpy"] / 1000, # Convert to kJ/kg
|
| 393 |
-
start_props["wet_bulb_temperature"],
|
| 394 |
-
start_props["dew_point_temperature"],
|
| 395 |
-
start_props["specific_volume"]
|
| 396 |
-
]
|
| 397 |
-
|
| 398 |
-
end_values = [
|
| 399 |
-
end_props["dry_bulb_temperature"],
|
| 400 |
-
end_props["relative_humidity"],
|
| 401 |
-
end_props["humidity_ratio"] * 1000, # Convert to g/kg
|
| 402 |
-
end_props["enthalpy"] / 1000, # Convert to kJ/kg
|
| 403 |
-
end_props["wet_bulb_temperature"],
|
| 404 |
-
end_props["dew_point_temperature"],
|
| 405 |
-
end_props["specific_volume"]
|
| 406 |
]
|
| 407 |
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
[f"{val:.2f}" for val in start_values],
|
| 422 |
-
[f"{val:.2f}" for val in end_values],
|
| 423 |
-
[f"{val:.2f}" for val in delta_values]
|
| 424 |
-
],
|
| 425 |
-
fill_color="lavender",
|
| 426 |
-
align="left",
|
| 427 |
-
font=dict(size=11)
|
| 428 |
-
)
|
| 429 |
-
)])
|
| 430 |
-
|
| 431 |
-
table_fig.update_layout(
|
| 432 |
-
title=f"Process Parameters: {process_type}",
|
| 433 |
-
height=300,
|
| 434 |
-
margin=dict(l=0, r=0, b=0, t=30)
|
| 435 |
-
)
|
| 436 |
-
|
| 437 |
-
return chart_fig, table_fig
|
| 438 |
-
|
| 439 |
-
def display_psychrometric_visualization(self) -> None:
|
| 440 |
-
"""
|
| 441 |
-
Display psychrometric visualization in Streamlit.
|
| 442 |
-
"""
|
| 443 |
-
st.header("Psychrometric Visualization")
|
| 444 |
-
|
| 445 |
-
# Create tabs for different visualizations
|
| 446 |
-
tab1, tab2, tab3 = st.tabs([
|
| 447 |
-
"Interactive Psychrometric Chart",
|
| 448 |
-
"Process Visualization",
|
| 449 |
-
"Comfort Zone Analysis"
|
| 450 |
-
])
|
| 451 |
-
|
| 452 |
-
with tab1:
|
| 453 |
-
st.subheader("Interactive Psychrometric Chart")
|
| 454 |
-
|
| 455 |
-
# Add controls for points
|
| 456 |
-
st.write("Add points to the chart:")
|
| 457 |
-
|
| 458 |
-
col1, col2, col3 = st.columns(3)
|
| 459 |
-
|
| 460 |
-
with col1:
|
| 461 |
-
point1_temp = st.number_input("Point 1 Temperature (°C)", -10.0, 50.0, 20.0, key="point1_temp")
|
| 462 |
-
point1_rh = st.number_input("Point 1 RH (%)", 0.0, 100.0, 50.0, key="point1_rh")
|
| 463 |
-
|
| 464 |
-
with col2:
|
| 465 |
-
point2_temp = st.number_input("Point 2 Temperature (°C)", -10.0, 50.0, 30.0, key="point2_temp")
|
| 466 |
-
point2_rh = st.number_input("Point 2 RH (%)", 0.0, 100.0, 40.0, key="point2_rh")
|
| 467 |
-
|
| 468 |
-
with col3:
|
| 469 |
-
show_process = st.checkbox("Show Process Line", True, key="show_process")
|
| 470 |
-
process_name = st.text_input("Process Name", "Cooling Process", key="process_name")
|
| 471 |
-
|
| 472 |
-
# Create points
|
| 473 |
-
points = [
|
| 474 |
-
{"temp": point1_temp, "rh": point1_rh, "name": "Point 1", "color": "blue"},
|
| 475 |
-
{"temp": point2_temp, "rh": point2_rh, "name": "Point 2", "color": "red"}
|
| 476 |
-
]
|
| 477 |
-
|
| 478 |
-
# Create process if enabled
|
| 479 |
-
processes = []
|
| 480 |
-
if show_process:
|
| 481 |
-
processes.append({
|
| 482 |
-
"start": {"temp": point1_temp, "rh": point1_rh},
|
| 483 |
-
"end": {"temp": point2_temp, "rh": point2_rh},
|
| 484 |
-
"name": process_name,
|
| 485 |
-
"color": "green"
|
| 486 |
-
})
|
| 487 |
-
|
| 488 |
-
# Create and display chart
|
| 489 |
-
fig = self.create_psychrometric_chart(points=points, processes=processes)
|
| 490 |
-
st.plotly_chart(fig, use_container_width=True)
|
| 491 |
-
|
| 492 |
-
# Display point properties
|
| 493 |
-
col1, col2 = st.columns(2)
|
| 494 |
-
|
| 495 |
-
with col1:
|
| 496 |
-
st.subheader("Point 1 Properties")
|
| 497 |
-
props1 = self.psychrometrics.moist_air_properties(point1_temp, point1_rh, self.pressure)
|
| 498 |
-
st.write(f"Dry-Bulb Temperature: {props1['dry_bulb_temperature']:.2f} °C")
|
| 499 |
-
st.write(f"Relative Humidity: {props1['relative_humidity']:.2f} %")
|
| 500 |
-
st.write(f"Humidity Ratio: {props1['humidity_ratio']*1000:.2f} g/kg")
|
| 501 |
-
st.write(f"Enthalpy: {props1['enthalpy']/1000:.2f} kJ/kg")
|
| 502 |
-
st.write(f"Wet-Bulb Temperature: {props1['wet_bulb_temperature']:.2f} °C")
|
| 503 |
-
st.write(f"Dew Point Temperature: {props1['dew_point_temperature']:.2f} °C")
|
| 504 |
-
|
| 505 |
-
with col2:
|
| 506 |
-
st.subheader("Point 2 Properties")
|
| 507 |
-
props2 = self.psychrometrics.moist_air_properties(point2_temp, point2_rh, self.pressure)
|
| 508 |
-
st.write(f"Dry-Bulb Temperature: {props2['dry_bulb_temperature']:.2f} °C")
|
| 509 |
-
st.write(f"Relative Humidity: {props2['relative_humidity']:.2f} %")
|
| 510 |
-
st.write(f"Humidity Ratio: {props2['humidity_ratio']*1000:.2f} g/kg")
|
| 511 |
-
st.write(f"Enthalpy: {props2['enthalpy']/1000:.2f} kJ/kg")
|
| 512 |
-
st.write(f"Wet-Bulb Temperature: {props2['wet_bulb_temperature']:.2f} °C")
|
| 513 |
-
st.write(f"Dew Point Temperature: {props2['dew_point_temperature']:.2f} °C")
|
| 514 |
-
|
| 515 |
-
with tab2:
|
| 516 |
-
st.subheader("Process Visualization")
|
| 517 |
-
|
| 518 |
-
# Add controls for process
|
| 519 |
-
st.write("Define a psychrometric process:")
|
| 520 |
-
|
| 521 |
-
col1, col2 = st.columns(2)
|
| 522 |
-
|
| 523 |
-
with col1:
|
| 524 |
-
st.write("Starting Point")
|
| 525 |
-
start_temp = st.number_input("Temperature (°C)", -10.0, 50.0, 24.0, key="start_temp")
|
| 526 |
-
start_rh = st.number_input("RH (%)", 0.0, 100.0, 50.0, key="start_rh")
|
| 527 |
-
|
| 528 |
-
with col2:
|
| 529 |
-
st.write("Ending Point")
|
| 530 |
-
end_temp = st.number_input("Temperature (°C)", -10.0, 50.0, 14.0, key="end_temp")
|
| 531 |
-
end_rh = st.number_input("RH (%)", 0.0, 100.0, 90.0, key="end_rh")
|
| 532 |
-
|
| 533 |
-
# Create process
|
| 534 |
-
process = {
|
| 535 |
-
"start": {"temp": start_temp, "rh": start_rh},
|
| 536 |
-
"end": {"temp": end_temp, "rh": end_rh}
|
| 537 |
}
|
| 538 |
-
|
| 539 |
-
# Create and display process visualization
|
| 540 |
-
chart_fig, table_fig = self.create_process_visualization(process)
|
| 541 |
-
|
| 542 |
-
st.plotly_chart(chart_fig, use_container_width=True)
|
| 543 |
-
st.plotly_chart(table_fig, use_container_width=True)
|
| 544 |
-
|
| 545 |
-
# Calculate process energy requirements
|
| 546 |
-
start_props = self.psychrometrics.moist_air_properties(start_temp, start_rh, self.pressure)
|
| 547 |
-
end_props = self.psychrometrics.moist_air_properties(end_temp, end_rh, self.pressure)
|
| 548 |
-
|
| 549 |
-
delta_h = end_props["enthalpy"] - start_props["enthalpy"] # J/kg
|
| 550 |
-
|
| 551 |
-
st.subheader("Energy Calculations")
|
| 552 |
-
|
| 553 |
-
air_flow = st.number_input("Air Flow Rate (m³/s)", 0.1, 100.0, 1.0, key="air_flow")
|
| 554 |
-
|
| 555 |
-
# Calculate mass flow rate
|
| 556 |
-
density = start_props["density"] # kg/m³
|
| 557 |
-
mass_flow = air_flow * density # kg/s
|
| 558 |
-
|
| 559 |
-
# Calculate energy rate
|
| 560 |
-
energy_rate = mass_flow * delta_h # W
|
| 561 |
-
|
| 562 |
-
st.write(f"Air Density: {density:.2f} kg/m³")
|
| 563 |
-
st.write(f"Mass Flow Rate: {mass_flow:.2f} kg/s")
|
| 564 |
-
st.write(f"Enthalpy Change: {delta_h/1000:.2f} kJ/kg")
|
| 565 |
-
st.write(f"Energy Rate: {energy_rate/1000:.2f} kW")
|
| 566 |
|
| 567 |
-
|
| 568 |
-
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
|
| 572 |
-
|
| 573 |
-
|
| 574 |
-
|
| 575 |
-
|
| 576 |
-
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
|
| 607 |
-
|
| 608 |
-
|
| 609 |
-
|
| 610 |
-
|
| 611 |
-
|
| 612 |
-
|
| 613 |
-
|
| 614 |
-
|
| 615 |
-
|
| 616 |
-
|
| 617 |
-
|
| 618 |
-
|
| 619 |
-
|
| 620 |
-
|
| 621 |
-
|
| 622 |
-
|
| 623 |
-
|
| 624 |
-
|
| 625 |
-
|
| 626 |
-
|
| 627 |
-
|
| 628 |
-
|
| 629 |
-
|
| 630 |
-
|
| 631 |
-
|
| 632 |
-
|
| 633 |
-
|
| 634 |
-
|
| 635 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
marker=dict(size=10, color=color),
|
| 226 |
text=[name],
|
| 227 |
textposition="top center",
|
| 228 |
+
name=name
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
))
|
| 230 |
|
| 231 |
# Add processes if specified
|
|
|
|
| 250 |
y=[start_w, end_w],
|
| 251 |
mode="lines+markers",
|
| 252 |
line=dict(color=color, width=2, dash="solid"),
|
| 253 |
+
marker=dict(size=8),
|
| 254 |
name=name
|
| 255 |
))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
|
| 257 |
# Update layout
|
| 258 |
fig.update_layout(
|
| 259 |
title="Psychrometric Chart",
|
| 260 |
xaxis_title="Dry-Bulb Temperature (°C)",
|
| 261 |
yaxis_title="Humidity Ratio (kg/kg)",
|
| 262 |
+
legend_title="Legend",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 263 |
height=700,
|
| 264 |
+
margin=dict(l=50, r=50, t=50, b=50),
|
| 265 |
+
plot_bgcolor="white",
|
| 266 |
+
paper_bgcolor="white",
|
| 267 |
+
font=dict(size=12)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
)
|
| 269 |
|
| 270 |
+
# Set axis ranges
|
| 271 |
+
fig.update_xaxes(range=[self.temp_min, self.temp_max], gridcolor="lightgray")
|
| 272 |
+
fig.update_yaxes(range=[self.w_min, self.w_max], gridcolor="lightgray")
|
| 273 |
+
|
| 274 |
return fig
|
| 275 |
+
|
| 276 |
+
def display_psychrometric_chart(self, calculation_results: Dict[str, Any], design_conditions: Dict[str, Any]) -> None:
|
| 277 |
"""
|
| 278 |
+
Display psychrometric chart with calculation results.
|
| 279 |
|
| 280 |
Args:
|
| 281 |
+
calculation_results: Dictionary containing calculation results
|
| 282 |
+
design_conditions: Dictionary containing design conditions
|
|
|
|
|
|
|
| 283 |
"""
|
| 284 |
+
# Extract design conditions
|
| 285 |
+
summer_outdoor_db = design_conditions.get("summer_outdoor_db", 35)
|
| 286 |
+
summer_outdoor_wb = design_conditions.get("summer_outdoor_wb", 25)
|
| 287 |
+
summer_indoor_db = design_conditions.get("summer_indoor_db", 24)
|
| 288 |
+
summer_indoor_rh = design_conditions.get("summer_indoor_rh", 50)
|
| 289 |
+
|
| 290 |
+
winter_outdoor_db = design_conditions.get("winter_outdoor_db", 0)
|
| 291 |
+
winter_outdoor_rh = design_conditions.get("winter_outdoor_rh", 80)
|
| 292 |
+
winter_indoor_db = design_conditions.get("winter_indoor_db", 22)
|
| 293 |
+
winter_indoor_rh = design_conditions.get("winter_indoor_rh", 40)
|
| 294 |
+
|
| 295 |
+
# Calculate humidity ratios
|
| 296 |
+
summer_outdoor_w = self.psychrometrics.humidity_ratio_from_wb(summer_outdoor_db, summer_outdoor_wb, self.pressure)
|
| 297 |
+
summer_indoor_w = self.psychrometrics.humidity_ratio(summer_indoor_db, summer_indoor_rh, self.pressure)
|
| 298 |
+
winter_outdoor_w = self.psychrometrics.humidity_ratio(winter_outdoor_db, winter_outdoor_rh, self.pressure)
|
| 299 |
+
winter_indoor_w = self.psychrometrics.humidity_ratio(winter_indoor_db, winter_indoor_rh, self.pressure)
|
| 300 |
+
|
| 301 |
+
# Create points for psychrometric chart
|
| 302 |
+
points = [
|
| 303 |
+
{
|
| 304 |
+
"temp": summer_outdoor_db,
|
| 305 |
+
"w": summer_outdoor_w,
|
| 306 |
+
"name": "Summer Outdoor",
|
| 307 |
+
"color": "red"
|
| 308 |
+
},
|
| 309 |
+
{
|
| 310 |
+
"temp": summer_indoor_db,
|
| 311 |
+
"w": summer_indoor_w,
|
| 312 |
+
"name": "Summer Indoor",
|
| 313 |
+
"color": "blue"
|
| 314 |
+
},
|
| 315 |
+
{
|
| 316 |
+
"temp": winter_outdoor_db,
|
| 317 |
+
"w": winter_outdoor_w,
|
| 318 |
+
"name": "Winter Outdoor",
|
| 319 |
+
"color": "purple"
|
| 320 |
+
},
|
| 321 |
+
{
|
| 322 |
+
"temp": winter_indoor_db,
|
| 323 |
+
"w": winter_indoor_w,
|
| 324 |
+
"name": "Winter Indoor",
|
| 325 |
+
"color": "green"
|
| 326 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 327 |
]
|
| 328 |
|
| 329 |
+
# Create processes for psychrometric chart
|
| 330 |
+
processes = [
|
| 331 |
+
{
|
| 332 |
+
"start": {"temp": summer_outdoor_db, "w": summer_outdoor_w},
|
| 333 |
+
"end": {"temp": summer_indoor_db, "w": summer_indoor_w},
|
| 334 |
+
"name": "Cooling Process",
|
| 335 |
+
"color": "blue"
|
| 336 |
+
},
|
| 337 |
+
{
|
| 338 |
+
"start": {"temp": winter_outdoor_db, "w": winter_outdoor_w},
|
| 339 |
+
"end": {"temp": winter_indoor_db, "w": winter_indoor_w},
|
| 340 |
+
"name": "Heating Process",
|
| 341 |
+
"color": "red"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 342 |
}
|
| 343 |
+
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 344 |
|
| 345 |
+
# Create comfort zone
|
| 346 |
+
comfort_zone = {
|
| 347 |
+
"temp_min": 20,
|
| 348 |
+
"temp_max": 26,
|
| 349 |
+
"rh_min": 30,
|
| 350 |
+
"rh_max": 60
|
| 351 |
+
}
|
| 352 |
+
|
| 353 |
+
# Create psychrometric chart
|
| 354 |
+
fig = self.create_psychrometric_chart(points, processes, comfort_zone)
|
| 355 |
+
|
| 356 |
+
# Display chart in Streamlit
|
| 357 |
+
st.plotly_chart(fig, use_container_width=True)
|
| 358 |
+
|
| 359 |
+
# Display psychrometric properties
|
| 360 |
+
st.subheader("Psychrometric Properties")
|
| 361 |
+
|
| 362 |
+
# Create dataframe for properties
|
| 363 |
+
properties = []
|
| 364 |
+
|
| 365 |
+
# Summer outdoor properties
|
| 366 |
+
summer_outdoor_rh = self.psychrometrics.relative_humidity_from_wb(summer_outdoor_db, summer_outdoor_wb, self.pressure)
|
| 367 |
+
summer_outdoor_dp = self.psychrometrics.dew_point(summer_outdoor_db, summer_outdoor_rh, self.pressure)
|
| 368 |
+
summer_outdoor_h = self.psychrometrics.enthalpy(summer_outdoor_db, summer_outdoor_w)
|
| 369 |
+
summer_outdoor_v = self.psychrometrics.specific_volume(summer_outdoor_db, summer_outdoor_w, self.pressure)
|
| 370 |
+
|
| 371 |
+
properties.append({
|
| 372 |
+
"Point": "Summer Outdoor",
|
| 373 |
+
"Dry-Bulb (°C)": f"{summer_outdoor_db:.1f}",
|
| 374 |
+
"Wet-Bulb (°C)": f"{summer_outdoor_wb:.1f}",
|
| 375 |
+
"Relative Humidity (%)": f"{summer_outdoor_rh:.1f}",
|
| 376 |
+
"Humidity Ratio (g/kg)": f"{summer_outdoor_w*1000:.1f}",
|
| 377 |
+
"Dew Point (°C)": f"{summer_outdoor_dp:.1f}",
|
| 378 |
+
"Enthalpy (kJ/kg)": f"{summer_outdoor_h/1000:.1f}",
|
| 379 |
+
"Specific Volume (m³/kg)": f"{summer_outdoor_v:.3f}"
|
| 380 |
+
})
|
| 381 |
+
|
| 382 |
+
# Summer indoor properties
|
| 383 |
+
summer_indoor_wb = self.psychrometrics.wet_bulb_temperature(summer_indoor_db, summer_indoor_rh, self.pressure)
|
| 384 |
+
summer_indoor_dp = self.psychrometrics.dew_point(summer_indoor_db, summer_indoor_rh, self.pressure)
|
| 385 |
+
summer_indoor_h = self.psychrometrics.enthalpy(summer_indoor_db, summer_indoor_w)
|
| 386 |
+
summer_indoor_v = self.psychrometrics.specific_volume(summer_indoor_db, summer_indoor_w, self.pressure)
|
| 387 |
+
|
| 388 |
+
properties.append({
|
| 389 |
+
"Point": "Summer Indoor",
|
| 390 |
+
"Dry-Bulb (°C)": f"{summer_indoor_db:.1f}",
|
| 391 |
+
"Wet-Bulb (°C)": f"{summer_indoor_wb:.1f}",
|
| 392 |
+
"Relative Humidity (%)": f"{summer_indoor_rh:.1f}",
|
| 393 |
+
"Humidity Ratio (g/kg)": f"{summer_indoor_w*1000:.1f}",
|
| 394 |
+
"Dew Point (°C)": f"{summer_indoor_dp:.1f}",
|
| 395 |
+
"Enthalpy (kJ/kg)": f"{summer_indoor_h/1000:.1f}",
|
| 396 |
+
"Specific Volume (m³/kg)": f"{summer_indoor_v:.3f}"
|
| 397 |
+
})
|
| 398 |
+
|
| 399 |
+
# Winter outdoor properties
|
| 400 |
+
winter_outdoor_wb = self.psychrometrics.wet_bulb_temperature(winter_outdoor_db, winter_outdoor_rh, self.pressure)
|
| 401 |
+
winter_outdoor_dp = self.psychrometrics.dew_point(winter_outdoor_db, winter_outdoor_rh, self.pressure)
|
| 402 |
+
winter_outdoor_h = self.psychrometrics.enthalpy(winter_outdoor_db, winter_outdoor_w)
|
| 403 |
+
winter_outdoor_v = self.psychrometrics.specific_volume(winter_outdoor_db, winter_outdoor_w, self.pressure)
|
| 404 |
+
|
| 405 |
+
properties.append({
|
| 406 |
+
"Point": "Winter Outdoor",
|
| 407 |
+
"Dry-Bulb (°C)": f"{winter_outdoor_db:.1f}",
|
| 408 |
+
"Wet-Bulb (°C)": f"{winter_outdoor_wb:.1f}",
|
| 409 |
+
"Relative Humidity (%)": f"{winter_outdoor_rh:.1f}",
|
| 410 |
+
"Humidity Ratio (g/kg)": f"{winter_outdoor_w*1000:.1f}",
|
| 411 |
+
"Dew Point (°C)": f"{winter_outdoor_dp:.1f}",
|
| 412 |
+
"Enthalpy (kJ/kg)": f"{winter_outdoor_h/1000:.1f}",
|
| 413 |
+
"Specific Volume (m³/kg)": f"{winter_outdoor_v:.3f}"
|
| 414 |
+
})
|
| 415 |
+
|
| 416 |
+
# Winter indoor properties
|
| 417 |
+
winter_indoor_wb = self.psychrometrics.wet_bulb_temperature(winter_indoor_db, winter_indoor_rh, self.pressure)
|
| 418 |
+
winter_indoor_dp = self.psychrometrics.dew_point(winter_indoor_db, winter_indoor_rh, self.pressure)
|
| 419 |
+
winter_indoor_h = self.psychrometrics.enthalpy(winter_indoor_db, winter_indoor_w)
|
| 420 |
+
winter_indoor_v = self.psychrometrics.specific_volume(winter_indoor_db, winter_indoor_w, self.pressure)
|
| 421 |
+
|
| 422 |
+
properties.append({
|
| 423 |
+
"Point": "Winter Indoor",
|
| 424 |
+
"Dry-Bulb (°C)": f"{winter_indoor_db:.1f}",
|
| 425 |
+
"Wet-Bulb (°C)": f"{winter_indoor_wb:.1f}",
|
| 426 |
+
"Relative Humidity (%)": f"{winter_indoor_rh:.1f}",
|
| 427 |
+
"Humidity Ratio (g/kg)": f"{winter_indoor_w*1000:.1f}",
|
| 428 |
+
"Dew Point (°C)": f"{winter_indoor_dp:.1f}",
|
| 429 |
+
"Enthalpy (kJ/kg)": f"{winter_indoor_h/1000:.1f}",
|
| 430 |
+
"Specific Volume (m³/kg)": f"{winter_indoor_v:.3f}"
|
| 431 |
+
})
|
| 432 |
+
|
| 433 |
+
# Display properties table
|
| 434 |
+
st.dataframe(pd.DataFrame(properties), use_container_width=True)
|