Spaces:
Sleeping
Sleeping
Update app/internal_loads.py
Browse files- app/internal_loads.py +464 -75
app/internal_loads.py
CHANGED
@@ -25,7 +25,7 @@ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(
|
|
25 |
logger = logging.getLogger(__name__)
|
26 |
|
27 |
# Define constants
|
28 |
-
LOAD_TYPES = ["Schedules", "People", "Lighting", "Equipment", "Ventilation
|
29 |
|
30 |
def display_internal_loads_page():
|
31 |
"""
|
@@ -33,7 +33,7 @@ def display_internal_loads_page():
|
|
33 |
This is the main function called by main.py when the Internal Loads page is selected.
|
34 |
"""
|
35 |
st.title("Internal Loads")
|
36 |
-
st.write("Define internal heat gains from people, lighting, equipment, ventilation, and infiltration based on ASHRAE
|
37 |
|
38 |
# Check if building type is set
|
39 |
building_type = st.session_state.project_data["building_info"].get("building_type")
|
@@ -59,7 +59,6 @@ def display_internal_loads_page():
|
|
59 |
|
60 |
# Create tabs for different load types
|
61 |
tabs = st.tabs(LOAD_TYPES)
|
62 |
-
|
63 |
for i, load_type in enumerate(LOAD_TYPES):
|
64 |
with tabs[i]:
|
65 |
if load_type == "Schedules":
|
@@ -70,8 +69,10 @@ def display_internal_loads_page():
|
|
70 |
display_lighting_tab()
|
71 |
elif load_type == "Equipment":
|
72 |
display_equipment_tab()
|
73 |
-
elif load_type == "Ventilation
|
74 |
-
|
|
|
|
|
75 |
|
76 |
# Navigation buttons
|
77 |
col1, col2 = st.columns(2)
|
@@ -98,7 +99,8 @@ def initialize_internal_loads():
|
|
98 |
"people": [],
|
99 |
"lighting": [],
|
100 |
"equipment": [],
|
101 |
-
"
|
|
|
102 |
}
|
103 |
|
104 |
def display_people_tab():
|
@@ -568,55 +570,55 @@ def display_equipment_tab():
|
|
568 |
st.session_state.equipment_action = {"action": "refresh", "id": str(uuid.uuid4())}
|
569 |
st.session_state.internal_loads_rerun_pending = True
|
570 |
|
571 |
-
def
|
572 |
-
"""Display the ventilation
|
573 |
-
# Get ventilation
|
574 |
-
|
575 |
|
576 |
# Split the display into two columns
|
577 |
col1, col2 = st.columns([3, 2])
|
578 |
|
579 |
with col1:
|
580 |
-
st.subheader("Saved Ventilation
|
581 |
-
if
|
582 |
-
|
583 |
else:
|
584 |
-
st.write("No ventilation
|
585 |
|
586 |
with col2:
|
587 |
-
st.subheader("Ventilation
|
588 |
|
589 |
# Initialize editor and action states
|
590 |
-
if "
|
591 |
-
st.session_state.
|
592 |
-
if "
|
593 |
-
st.session_state.
|
594 |
|
595 |
# Get building type for default values
|
596 |
building_type = st.session_state.project_data["building_info"].get("building_type")
|
597 |
default_building_data = DEFAULT_BUILDING_INTERNALS.get(building_type, DEFAULT_BUILDING_INTERNALS["Other"])
|
598 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
599 |
# Display the editor form
|
600 |
-
with st.form("
|
601 |
-
editor_state = st.session_state.get("
|
602 |
is_edit = editor_state.get("is_edit", False)
|
603 |
|
604 |
# System name
|
605 |
name = st.text_input(
|
606 |
"System Name",
|
607 |
value=editor_state.get("name", ""),
|
608 |
-
help="Enter a unique name for this ventilation
|
609 |
-
)
|
610 |
-
|
611 |
-
# System type
|
612 |
-
system_type = st.selectbox(
|
613 |
-
"System Type",
|
614 |
-
["Ventilation", "Infiltration"],
|
615 |
-
index=["Ventilation", "Infiltration"].index(editor_state.get("system_type", "Ventilation")) if editor_state.get("system_type") in ["Ventilation", "Infiltration"] else 0,
|
616 |
-
help="Select whether this is ventilation or infiltration."
|
617 |
)
|
618 |
|
619 |
-
# Area
|
620 |
area = st.number_input(
|
621 |
"Area (m²)",
|
622 |
min_value=1.0,
|
@@ -626,28 +628,266 @@ def display_ventilation_infiltration_tab():
|
|
626 |
help="Floor area served by this system."
|
627 |
)
|
628 |
|
629 |
-
|
630 |
-
|
631 |
-
|
632 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
633 |
min_value=0.1,
|
634 |
max_value=50.0,
|
635 |
-
value=float(editor_state.get("
|
636 |
format="%.2f",
|
637 |
-
help="
|
638 |
)
|
639 |
-
|
640 |
-
|
641 |
-
|
642 |
-
|
643 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
644 |
min_value=0.0,
|
645 |
-
max_value=
|
646 |
-
value=float(editor_state.get("
|
647 |
format="%.2f",
|
648 |
-
help="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
649 |
)
|
650 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
651 |
|
652 |
# Schedule selection
|
653 |
available_schedules = list(st.session_state.project_data["internal_loads"]["schedules"].keys())
|
@@ -658,11 +898,90 @@ def display_ventilation_infiltration_tab():
|
|
658 |
help="Select the operation schedule for this system."
|
659 |
)
|
660 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
661 |
# Submit buttons
|
662 |
col1, col2 = st.columns(2)
|
663 |
with col1:
|
664 |
submit_label = "Update" if is_edit else "Add"
|
665 |
-
submit = st.form_submit_button(f"{submit_label} System")
|
666 |
|
667 |
with col2:
|
668 |
refresh = st.form_submit_button("Refresh")
|
@@ -674,40 +993,44 @@ def display_ventilation_infiltration_tab():
|
|
674 |
st.error("System name is required.")
|
675 |
return
|
676 |
# Check for unique name
|
677 |
-
existing_names = [system["name"] for system in
|
678 |
if name.strip() in existing_names:
|
679 |
st.error("System name must be unique.")
|
680 |
return
|
681 |
|
682 |
-
# Create
|
683 |
-
|
684 |
"id": str(uuid.uuid4()),
|
685 |
"name": name.strip(),
|
686 |
"system_type": system_type,
|
687 |
"area": area,
|
688 |
-
"
|
689 |
-
"
|
690 |
-
"
|
|
|
|
|
|
|
|
|
691 |
}
|
692 |
|
693 |
# Handle edit mode
|
694 |
if is_edit:
|
695 |
index = editor_state.get("index", 0)
|
696 |
-
st.session_state.project_data["internal_loads"]["
|
697 |
-
st.success(f"
|
698 |
else:
|
699 |
-
st.session_state.project_data["internal_loads"]["
|
700 |
-
st.success(f"
|
701 |
|
702 |
# Clear editor state
|
703 |
-
st.session_state.
|
704 |
-
st.session_state.
|
705 |
st.session_state.internal_loads_rerun_pending = True
|
706 |
|
707 |
elif refresh:
|
708 |
# Clear editor state
|
709 |
-
st.session_state.
|
710 |
-
st.session_state.
|
711 |
st.session_state.internal_loads_rerun_pending = True
|
712 |
|
713 |
def display_schedules_tab():
|
@@ -1087,8 +1410,65 @@ def display_equipment_table(equipment_systems: List[Dict[str, Any]]):
|
|
1087 |
st.session_state.equipment_action = {"action": "delete", "id": str(uuid.uuid4())}
|
1088 |
st.session_state.internal_loads_rerun_pending = True
|
1089 |
|
1090 |
-
def
|
1091 |
-
"""Display ventilation
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1092 |
# Create column headers
|
1093 |
cols = st.columns([2, 1, 1, 1, 1, 1])
|
1094 |
cols[0].write("**Name**")
|
@@ -1099,38 +1479,47 @@ def display_ventilation_infiltration_table(vent_inf_systems: List[Dict[str, Any]
|
|
1099 |
cols[5].write("**Delete**")
|
1100 |
|
1101 |
# Display each system
|
1102 |
-
for idx, system in enumerate(
|
1103 |
cols = st.columns([2, 1, 1, 1, 1, 1])
|
1104 |
cols[0].write(system["name"])
|
1105 |
cols[1].write(system.get("system_type", "Unknown"))
|
1106 |
cols[2].write(f"{system.get('area', 0):.1f}")
|
1107 |
-
|
|
|
|
|
|
|
|
|
|
|
1108 |
cols[3].write(rate_info)
|
1109 |
|
1110 |
# Edit button
|
1111 |
-
edit_key = f"
|
1112 |
with cols[4].container():
|
1113 |
if st.button("Edit", key=edit_key):
|
1114 |
-
st.session_state.
|
1115 |
"index": idx,
|
1116 |
"name": system.get("name", ""),
|
1117 |
-
"system_type": system.get("system_type", "
|
1118 |
"area": system.get("area", 100.0),
|
1119 |
-
"ventilation_rate": system.get("ventilation_rate", 10.0),
|
1120 |
-
"air_change_rate": system.get("air_change_rate", 1.0),
|
1121 |
"schedule": system.get("schedule", "Continuous"),
|
|
|
|
|
|
|
|
|
|
|
|
|
1122 |
"is_edit": True
|
1123 |
}
|
1124 |
-
st.session_state.
|
1125 |
st.session_state.internal_loads_rerun_pending = True
|
1126 |
|
1127 |
# Delete button
|
1128 |
-
delete_key = f"
|
1129 |
with cols[5].container():
|
1130 |
if st.button("Delete", key=delete_key):
|
1131 |
-
st.session_state.project_data["internal_loads"]["
|
1132 |
-
st.success(f"
|
1133 |
-
st.session_state.
|
1134 |
st.session_state.internal_loads_rerun_pending = True
|
1135 |
|
1136 |
def display_schedules_table(schedules: Dict[str, Any]):
|
|
|
25 |
logger = logging.getLogger(__name__)
|
26 |
|
27 |
# Define constants
|
28 |
+
LOAD_TYPES = ["Schedules", "People", "Lighting", "Equipment", "Ventilation", "Infiltration"]
|
29 |
|
30 |
def display_internal_loads_page():
|
31 |
"""
|
|
|
33 |
This is the main function called by main.py when the Internal Loads page is selected.
|
34 |
"""
|
35 |
st.title("Internal Loads")
|
36 |
+
st.write("Define internal heat gains from people, lighting, equipment, ventilation, and infiltration based on ASHRAE 2021 Handbook.")
|
37 |
|
38 |
# Check if building type is set
|
39 |
building_type = st.session_state.project_data["building_info"].get("building_type")
|
|
|
59 |
|
60 |
# Create tabs for different load types
|
61 |
tabs = st.tabs(LOAD_TYPES)
|
|
|
62 |
for i, load_type in enumerate(LOAD_TYPES):
|
63 |
with tabs[i]:
|
64 |
if load_type == "Schedules":
|
|
|
69 |
display_lighting_tab()
|
70 |
elif load_type == "Equipment":
|
71 |
display_equipment_tab()
|
72 |
+
elif load_type == "Ventilation":
|
73 |
+
display_ventilation_tab()
|
74 |
+
elif load_type == "Infiltration":
|
75 |
+
display_infiltration_tab()
|
76 |
|
77 |
# Navigation buttons
|
78 |
col1, col2 = st.columns(2)
|
|
|
99 |
"people": [],
|
100 |
"lighting": [],
|
101 |
"equipment": [],
|
102 |
+
"ventilation": [],
|
103 |
+
"infiltration": []
|
104 |
}
|
105 |
|
106 |
def display_people_tab():
|
|
|
570 |
st.session_state.equipment_action = {"action": "refresh", "id": str(uuid.uuid4())}
|
571 |
st.session_state.internal_loads_rerun_pending = True
|
572 |
|
573 |
+
def display_ventilation_tab():
|
574 |
+
"""Display the ventilation tab content with a two-column layout."""
|
575 |
+
# Get ventilation systems from session state
|
576 |
+
ventilation_systems = st.session_state.project_data["internal_loads"].setdefault("ventilation", [])
|
577 |
|
578 |
# Split the display into two columns
|
579 |
col1, col2 = st.columns([3, 2])
|
580 |
|
581 |
with col1:
|
582 |
+
st.subheader("Saved Ventilation Systems")
|
583 |
+
if ventilation_systems:
|
584 |
+
display_ventilation_table(ventilation_systems)
|
585 |
else:
|
586 |
+
st.write("No ventilation systems defined.")
|
587 |
|
588 |
with col2:
|
589 |
+
st.subheader("Ventilation System Editor/Creator")
|
590 |
|
591 |
# Initialize editor and action states
|
592 |
+
if "ventilation_editor" not in st.session_state:
|
593 |
+
st.session_state.ventilation_editor = {}
|
594 |
+
if "ventilation_action" not in st.session_state:
|
595 |
+
st.session_state.ventilation_action = {"action": None, "id": None}
|
596 |
|
597 |
# Get building type for default values
|
598 |
building_type = st.session_state.project_data["building_info"].get("building_type")
|
599 |
default_building_data = DEFAULT_BUILDING_INTERNALS.get(building_type, DEFAULT_BUILDING_INTERNALS["Other"])
|
600 |
|
601 |
+
# System type selection (outside form)
|
602 |
+
system_type = st.selectbox(
|
603 |
+
"System Type",
|
604 |
+
["AirChanges/Hour", "Wind and Stack Open Area", "Balanced Flow", "Heat Recovery"],
|
605 |
+
key="ventilation_system_type",
|
606 |
+
help="Select the type of ventilation system."
|
607 |
+
)
|
608 |
+
|
609 |
# Display the editor form
|
610 |
+
with st.form("ventilation_editor_form", clear_on_submit=True):
|
611 |
+
editor_state = st.session_state.get("ventilation_editor", {})
|
612 |
is_edit = editor_state.get("is_edit", False)
|
613 |
|
614 |
# System name
|
615 |
name = st.text_input(
|
616 |
"System Name",
|
617 |
value=editor_state.get("name", ""),
|
618 |
+
help="Enter a unique name for this ventilation system."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
619 |
)
|
620 |
|
621 |
+
# Area (required for all types)
|
622 |
area = st.number_input(
|
623 |
"Area (m²)",
|
624 |
min_value=1.0,
|
|
|
628 |
help="Floor area served by this system."
|
629 |
)
|
630 |
|
631 |
+
# Schedule selection
|
632 |
+
available_schedules = list(st.session_state.project_data["internal_loads"]["schedules"].keys())
|
633 |
+
schedule = st.selectbox(
|
634 |
+
"Schedule",
|
635 |
+
available_schedules,
|
636 |
+
index=available_schedules.index(editor_state.get("schedule", available_schedules[0])) if editor_state.get("schedule") in available_schedules else 0,
|
637 |
+
help="Select the operation schedule for this system."
|
638 |
+
)
|
639 |
+
|
640 |
+
# Type-specific inputs
|
641 |
+
if system_type == "AirChanges/Hour":
|
642 |
+
design_flow_rate = st.number_input(
|
643 |
+
"Design Flow Rate (ACH)",
|
644 |
min_value=0.1,
|
645 |
max_value=50.0,
|
646 |
+
value=float(editor_state.get("design_flow_rate", default_building_data.get("ventilation_rate", 1.0))),
|
647 |
format="%.2f",
|
648 |
+
help="Air change rate in air changes per hour."
|
649 |
)
|
650 |
+
ventilation_type = st.selectbox(
|
651 |
+
"Ventilation Type",
|
652 |
+
["Natural", "Mechanical"],
|
653 |
+
index=["Natural", "Mechanical"].index(editor_state.get("ventilation_type", "Natural")) if editor_state.get("ventilation_type") in ["Natural", "Mechanical"] else 0,
|
654 |
+
help="Select whether the ventilation is natural or mechanical."
|
655 |
+
)
|
656 |
+
fan_pressure_rise = 0.0
|
657 |
+
fan_efficiency = 0.0
|
658 |
+
if ventilation_type == "Mechanical":
|
659 |
+
fan_pressure_rise = st.number_input(
|
660 |
+
"Fan Pressure Rise (Pa)",
|
661 |
+
min_value=0.0,
|
662 |
+
max_value=1000.0,
|
663 |
+
value=float(editor_state.get("fan_pressure_rise", 200.0)),
|
664 |
+
format="%.2f",
|
665 |
+
help="Fan pressure rise in Pascals for power calculation."
|
666 |
+
)
|
667 |
+
fan_efficiency = st.number_input(
|
668 |
+
"Fan Efficiency (0–1)",
|
669 |
+
min_value=0.0,
|
670 |
+
max_value=1.0,
|
671 |
+
value=float(editor_state.get("fan_efficiency", 0.7)),
|
672 |
+
format="%.2f",
|
673 |
+
help="Fan efficiency as a fraction between 0 and 1."
|
674 |
+
)
|
675 |
+
sensible_effectiveness = 0.0
|
676 |
+
latent_effectiveness = 0.0
|
677 |
+
elif system_type == "Wind and Stack Open Area":
|
678 |
+
opening_effectiveness = st.number_input(
|
679 |
+
"Opening Effectiveness (%)",
|
680 |
min_value=0.0,
|
681 |
+
max_value=100.0,
|
682 |
+
value=float(editor_state.get("opening_effectiveness", 50.0)),
|
683 |
format="%.2f",
|
684 |
+
help="Effectiveness of the opening for ventilation (0–100%)."
|
685 |
+
)
|
686 |
+
design_flow_rate = 0.0
|
687 |
+
ventilation_type = ""
|
688 |
+
fan_pressure_rise = 0.0
|
689 |
+
fan_efficiency = 0.0
|
690 |
+
sensible_effectiveness = 0.0
|
691 |
+
latent_effectiveness = 0.0
|
692 |
+
elif system_type == "Balanced Flow":
|
693 |
+
design_flow_rate = st.number_input(
|
694 |
+
"Design Flow Rate (m³/s)",
|
695 |
+
min_value=0.0,
|
696 |
+
max_value=100.0,
|
697 |
+
value=float(editor_state.get("design_flow_rate", 0.5)),
|
698 |
+
format="%.2f",
|
699 |
+
help="Balanced supply and exhaust flow rate in cubic meters per second."
|
700 |
)
|
701 |
+
fan_pressure_rise = st.number_input(
|
702 |
+
"Fan Pressure Rise (Pa)",
|
703 |
+
min_value=0.0,
|
704 |
+
max_value=1000.0,
|
705 |
+
value=float(editor_state.get("fan_pressure_rise", 200.0)),
|
706 |
+
format="%.2f",
|
707 |
+
help="Fan pressure rise in Pascals for power calculation."
|
708 |
+
)
|
709 |
+
fan_efficiency = st.number_input(
|
710 |
+
"Fan Total Efficiency (0–1)",
|
711 |
+
min_value=0.0,
|
712 |
+
max_value=1.0,
|
713 |
+
value=float(editor_state.get("fan_efficiency", 0.7)),
|
714 |
+
format="%.2f",
|
715 |
+
help="Total fan efficiency as a fraction between 0 and 1."
|
716 |
+
)
|
717 |
+
zone_name = st.text_input(
|
718 |
+
"Zone Name",
|
719 |
+
value=editor_state.get("zone_name", ""),
|
720 |
+
help="Name of the target zone for this ventilation system."
|
721 |
+
)
|
722 |
+
ventilation_type = ""
|
723 |
+
sensible_effectiveness = 0.0
|
724 |
+
latent_effectiveness = 0.0
|
725 |
+
elif system_type == "Heat Recovery":
|
726 |
+
design_flow_rate = st.number_input(
|
727 |
+
"Design Flow Rate (m³/s)",
|
728 |
+
min_value=0.0,
|
729 |
+
max_value=100.0,
|
730 |
+
value=float(editor_state.get("design_flow_rate", 0.5)),
|
731 |
+
format="%.2f",
|
732 |
+
help="Balanced supply and exhaust flow rate in cubic meters per second."
|
733 |
+
)
|
734 |
+
fan_pressure_rise = st.number_input(
|
735 |
+
"Fan Pressure Rise (Pa)",
|
736 |
+
min_value=0.0,
|
737 |
+
max_value=1000.0,
|
738 |
+
value=float(editor_state.get("fan_pressure_rise", 200.0)),
|
739 |
+
format="%.2f",
|
740 |
+
help="Fan pressure rise in Pascals for power calculation."
|
741 |
+
)
|
742 |
+
fan_efficiency = st.number_input(
|
743 |
+
"Fan Total Efficiency (0–1)",
|
744 |
+
min_value=0.0,
|
745 |
+
max_value=1.0,
|
746 |
+
value=float(editor_state.get("fan_efficiency", 0.7)),
|
747 |
+
format="%.2f",
|
748 |
+
help="Total fan efficiency as a fraction between 0 and 1."
|
749 |
+
)
|
750 |
+
zone_name = st.text_input(
|
751 |
+
"Zone Name",
|
752 |
+
value=editor_state.get("zone_name", ""),
|
753 |
+
help="Name of the target zone for this ventilation system."
|
754 |
+
)
|
755 |
+
sensible_effectiveness = st.number_input(
|
756 |
+
"Sensible Effectiveness (0–1)",
|
757 |
+
min_value=0.0,
|
758 |
+
max_value=1.0,
|
759 |
+
value=float(editor_state.get("sensible_effectiveness", 0.8)),
|
760 |
+
format="%.2f",
|
761 |
+
help="Sensible heat recovery effectiveness as a fraction between 0 and 1."
|
762 |
+
)
|
763 |
+
latent_effectiveness = st.number_input(
|
764 |
+
"Latent Effectiveness (0–1)",
|
765 |
+
min_value=0.0,
|
766 |
+
max_value=1.0,
|
767 |
+
value=float(editor_state.get("latent_effectiveness", 0.6)),
|
768 |
+
format="%.2f",
|
769 |
+
help="Latent heat recovery effectiveness as a fraction between 0 and 1."
|
770 |
+
)
|
771 |
+
ventilation_type = ""
|
772 |
+
|
773 |
+
# Submit buttons
|
774 |
+
col1, col2 = st.columns(2)
|
775 |
+
with col1:
|
776 |
+
submit_label = "Update" if is_edit else "Add"
|
777 |
+
submit = st.form_submit_button(f"{submit_label} Ventilation System")
|
778 |
+
|
779 |
+
with col2:
|
780 |
+
refresh = st.form_submit_button("Refresh")
|
781 |
+
|
782 |
+
# Handle form submission
|
783 |
+
if submit:
|
784 |
+
# Validate inputs
|
785 |
+
if not name.strip():
|
786 |
+
st.error("System name is required.")
|
787 |
+
return
|
788 |
+
# Check for unique name
|
789 |
+
existing_names = [system["name"] for system in ventilation_systems if not (is_edit and system["name"] == editor_state.get("name"))]
|
790 |
+
if name.strip() in existing_names:
|
791 |
+
st.error("System name must be unique.")
|
792 |
+
return
|
793 |
+
if system_type in ["Balanced Flow", "Heat Recovery"] and not zone_name.strip():
|
794 |
+
st.error("Zone name is required for Balanced Flow and Heat Recovery systems.")
|
795 |
+
return
|
796 |
+
|
797 |
+
# Create ventilation data
|
798 |
+
ventilation_data = {
|
799 |
+
"id": str(uuid.uuid4()),
|
800 |
+
"name": name.strip(),
|
801 |
+
"system_type": system_type,
|
802 |
+
"area": area,
|
803 |
+
"schedule": schedule,
|
804 |
+
"design_flow_rate": design_flow_rate,
|
805 |
+
"ventilation_type": ventilation_type,
|
806 |
+
"fan_pressure_rise": fan_pressure_rise,
|
807 |
+
"fan_efficiency": fan_efficiency,
|
808 |
+
"zone_name": zone_name if system_type in ["Balanced Flow", "Heat Recovery"] else "",
|
809 |
+
"opening_effectiveness": opening_effectiveness if system_type == "Wind and Stack Open Area" else 0.0,
|
810 |
+
"sensible_effectiveness": sensible_effectiveness,
|
811 |
+
"latent_effectiveness": latent_effectiveness
|
812 |
+
}
|
813 |
+
|
814 |
+
# Handle edit mode
|
815 |
+
if is_edit:
|
816 |
+
index = editor_state.get("index", 0)
|
817 |
+
st.session_state.project_data["internal_loads"]["ventilation"][index] = ventilation_data
|
818 |
+
st.success(f"Ventilation System '{name}' updated!")
|
819 |
+
else:
|
820 |
+
st.session_state.project_data["internal_loads"]["ventilation"].append(ventilation_data)
|
821 |
+
st.success(f"Ventilation System '{name}' added!")
|
822 |
+
|
823 |
+
# Clear editor state
|
824 |
+
st.session_state.ventilation_editor = {}
|
825 |
+
st.session_state.ventilation_action = {"action": "save", "id": str(uuid.uuid4())}
|
826 |
+
st.session_state.internal_loads_rerun_pending = True
|
827 |
+
|
828 |
+
elif refresh:
|
829 |
+
# Clear editor state
|
830 |
+
st.session_state.ventilation_editor = {}
|
831 |
+
st.session_state.ventilation_action = {"action": "refresh", "id": str(uuid.uuid4())}
|
832 |
+
st.session_state.internal_loads_rerun_pending = True
|
833 |
+
|
834 |
+
def display_infiltration_tab():
|
835 |
+
"""Display the infiltration tab content with a two-column layout."""
|
836 |
+
# Get infiltration systems from session state
|
837 |
+
infiltration_systems = st.session_state.project_data["internal_loads"].setdefault("infiltration", [])
|
838 |
+
|
839 |
+
# Split the display into two columns
|
840 |
+
col1, col2 = st.columns([3, 2])
|
841 |
+
|
842 |
+
with col1:
|
843 |
+
st.subheader("Saved Infiltration Systems")
|
844 |
+
if infiltration_systems:
|
845 |
+
display_infiltration_table(infiltration_systems)
|
846 |
+
else:
|
847 |
+
st.write("No infiltration systems defined.")
|
848 |
+
|
849 |
+
with col2:
|
850 |
+
st.subheader("Infiltration System Editor/Creator")
|
851 |
+
|
852 |
+
# Initialize editor and action states
|
853 |
+
if "infiltration_editor" not in st.session_state:
|
854 |
+
st.session_state.infiltration_editor = {}
|
855 |
+
if "infiltration_action" not in st.session_state:
|
856 |
+
st.session_state.infiltration_action = {"action": None, "id": None}
|
857 |
+
|
858 |
+
# Get building type for default values
|
859 |
+
building_type = st.session_state.project_data["building_info"].get("building_type")
|
860 |
+
default_building_data = DEFAULT_BUILDING_INTERNALS.get(building_type, DEFAULT_BUILDING_INTERNALS["Other"])
|
861 |
+
|
862 |
+
# System type selection (outside form)
|
863 |
+
system_type = st.selectbox(
|
864 |
+
"System Type",
|
865 |
+
["AirChanges/Hour", "Effective Leakage Area", "Flow Coefficient"],
|
866 |
+
key="infiltration_system_type",
|
867 |
+
help="Select the type of infiltration system."
|
868 |
+
)
|
869 |
+
|
870 |
+
# Display the editor form
|
871 |
+
with st.form("infiltration_editor_form", clear_on_submit=True):
|
872 |
+
editor_state = st.session_state.get("infiltration_editor", {})
|
873 |
+
is_edit = editor_state.get("is_edit", False)
|
874 |
+
|
875 |
+
# System name
|
876 |
+
name = st.text_input(
|
877 |
+
"System Name",
|
878 |
+
value=editor_state.get("name", ""),
|
879 |
+
help="Enter a unique name for this infiltration system."
|
880 |
+
)
|
881 |
+
|
882 |
+
# Area (required for all types)
|
883 |
+
area = st.number_input(
|
884 |
+
"Area (m²)",
|
885 |
+
min_value=1.0,
|
886 |
+
max_value=100000.0,
|
887 |
+
value=float(editor_state.get("area", st.session_state.project_data["building_info"].get("floor_area", 100.0))),
|
888 |
+
format="%.2f",
|
889 |
+
help="Floor area served by this system."
|
890 |
+
)
|
891 |
|
892 |
# Schedule selection
|
893 |
available_schedules = list(st.session_state.project_data["internal_loads"]["schedules"].keys())
|
|
|
898 |
help="Select the operation schedule for this system."
|
899 |
)
|
900 |
|
901 |
+
# Type-specific inputs
|
902 |
+
if system_type == "AirChanges/Hour":
|
903 |
+
design_flow_rate = st.number_input(
|
904 |
+
"Design Flow Rate (ACH)",
|
905 |
+
min_value=0.0,
|
906 |
+
max_value=10.0,
|
907 |
+
value=float(editor_state.get("design_flow_rate", default_building_data.get("air_change_rate", 0.3))),
|
908 |
+
format="%.2f",
|
909 |
+
help="Air change rate in air changes per hour."
|
910 |
+
)
|
911 |
+
effective_air_leakage_area = 0.0
|
912 |
+
stack_coefficient = 0.0
|
913 |
+
wind_coefficient = 0.0
|
914 |
+
flow_coefficient = 0.0
|
915 |
+
pressure_exponent = 0.0
|
916 |
+
elif system_type == "Effective Leakage Area":
|
917 |
+
effective_air_leakage_area = st.number_input(
|
918 |
+
"Effective Air Leakage Area (cm²)",
|
919 |
+
min_value=0.0,
|
920 |
+
max_value=10000.0,
|
921 |
+
value=float(editor_state.get("effective_air_leakage_area", 100.0)),
|
922 |
+
format="%.2f",
|
923 |
+
help="Effective air leakage area in square centimeters at 4 Pa or 10 Pa."
|
924 |
+
)
|
925 |
+
stack_coefficient = st.number_input(
|
926 |
+
"Stack Coefficient",
|
927 |
+
min_value=0.0,
|
928 |
+
max_value=1.0,
|
929 |
+
value=float(editor_state.get("stack_coefficient", 0.0001)),
|
930 |
+
format="%.6f",
|
931 |
+
help="Stack coefficient from standards or measurements."
|
932 |
+
)
|
933 |
+
wind_coefficient = st.number_input(
|
934 |
+
"Wind Coefficient",
|
935 |
+
min_value=0.0,
|
936 |
+
max_value=1.0,
|
937 |
+
value=float(editor_state.get("wind_coefficient", 0.0001)),
|
938 |
+
format="%.6f",
|
939 |
+
help="Wind coefficient from standards or measurements."
|
940 |
+
)
|
941 |
+
design_flow_rate = 0.0
|
942 |
+
flow_coefficient = 0.0
|
943 |
+
pressure_exponent = 0.0
|
944 |
+
elif system_type == "Flow Coefficient":
|
945 |
+
flow_coefficient = st.number_input(
|
946 |
+
"Flow Coefficient (m³/s·Paⁿ)",
|
947 |
+
min_value=0.0,
|
948 |
+
max_value=1.0,
|
949 |
+
value=float(editor_state.get("flow_coefficient", 0.0001)),
|
950 |
+
format="%.6f",
|
951 |
+
help="Airflow at reference pressure in cubic meters per second per Pascal raised to the pressure exponent."
|
952 |
+
)
|
953 |
+
pressure_exponent = st.number_input(
|
954 |
+
"Pressure Exponent",
|
955 |
+
min_value=0.0,
|
956 |
+
max_value=1.0,
|
957 |
+
value=float(editor_state.get("pressure_exponent", 0.6)),
|
958 |
+
format="%.2f",
|
959 |
+
help="Pressure exponent, typically 0.6."
|
960 |
+
)
|
961 |
+
stack_coefficient = st.number_input(
|
962 |
+
"Stack Coefficient",
|
963 |
+
min_value=0.0,
|
964 |
+
max_value=1.0,
|
965 |
+
value=float(editor_state.get("stack_coefficient", 0.0001)),
|
966 |
+
format="%.6f",
|
967 |
+
help="Stack coefficient from standards or measurements."
|
968 |
+
)
|
969 |
+
wind_coefficient = st.number_input(
|
970 |
+
"Wind Coefficient",
|
971 |
+
min_value=0.0,
|
972 |
+
max_value=1.0,
|
973 |
+
value=float(editor_state.get("wind_coefficient", 0.0001)),
|
974 |
+
format="%.6f",
|
975 |
+
help="Wind coefficient from standards or measurements."
|
976 |
+
)
|
977 |
+
design_flow_rate = 0.0
|
978 |
+
effective_air_leakage_area = 0.0
|
979 |
+
|
980 |
# Submit buttons
|
981 |
col1, col2 = st.columns(2)
|
982 |
with col1:
|
983 |
submit_label = "Update" if is_edit else "Add"
|
984 |
+
submit = st.form_submit_button(f"{submit_label} Infiltration System")
|
985 |
|
986 |
with col2:
|
987 |
refresh = st.form_submit_button("Refresh")
|
|
|
993 |
st.error("System name is required.")
|
994 |
return
|
995 |
# Check for unique name
|
996 |
+
existing_names = [system["name"] for system in infiltration_systems if not (is_edit and system["name"] == editor_state.get("name"))]
|
997 |
if name.strip() in existing_names:
|
998 |
st.error("System name must be unique.")
|
999 |
return
|
1000 |
|
1001 |
+
# Create infiltration data
|
1002 |
+
infiltration_data = {
|
1003 |
"id": str(uuid.uuid4()),
|
1004 |
"name": name.strip(),
|
1005 |
"system_type": system_type,
|
1006 |
"area": area,
|
1007 |
+
"schedule": schedule,
|
1008 |
+
"design_flow_rate": design_flow_rate,
|
1009 |
+
"effective_air_leakage_area": effective_air_leakage_area,
|
1010 |
+
"stack_coefficient": stack_coefficient,
|
1011 |
+
"wind_coefficient": wind_coefficient,
|
1012 |
+
"flow_coefficient": flow_coefficient,
|
1013 |
+
"pressure_exponent": pressure_exponent
|
1014 |
}
|
1015 |
|
1016 |
# Handle edit mode
|
1017 |
if is_edit:
|
1018 |
index = editor_state.get("index", 0)
|
1019 |
+
st.session_state.project_data["internal_loads"]["infiltration"][index] = infiltration_data
|
1020 |
+
st.success(f"Infiltration System '{name}' updated!")
|
1021 |
else:
|
1022 |
+
st.session_state.project_data["internal_loads"]["infiltration"].append(infiltration_data)
|
1023 |
+
st.success(f"Infiltration System '{name}' added!")
|
1024 |
|
1025 |
# Clear editor state
|
1026 |
+
st.session_state.infiltration_editor = {}
|
1027 |
+
st.session_state.infiltration_action = {"action": "save", "id": str(uuid.uuid4())}
|
1028 |
st.session_state.internal_loads_rerun_pending = True
|
1029 |
|
1030 |
elif refresh:
|
1031 |
# Clear editor state
|
1032 |
+
st.session_state.infiltration_editor = {}
|
1033 |
+
st.session_state.infiltration_action = {"action": "refresh", "id": str(uuid.uuid4())}
|
1034 |
st.session_state.internal_loads_rerun_pending = True
|
1035 |
|
1036 |
def display_schedules_tab():
|
|
|
1410 |
st.session_state.equipment_action = {"action": "delete", "id": str(uuid.uuid4())}
|
1411 |
st.session_state.internal_loads_rerun_pending = True
|
1412 |
|
1413 |
+
def display_ventilation_table(ventilation_systems: List[Dict[str, Any]]):
|
1414 |
+
"""Display ventilation systems in a table format with edit/delete buttons."""
|
1415 |
+
# Create column headers
|
1416 |
+
cols = st.columns([2, 1, 1, 1, 1, 1])
|
1417 |
+
cols[0].write("**Name**")
|
1418 |
+
cols[1].write("**Type**")
|
1419 |
+
cols[2].write("**Area (m²)**")
|
1420 |
+
cols[3].write("**Rate**")
|
1421 |
+
cols[4].write("**Edit**")
|
1422 |
+
cols[5].write("**Delete**")
|
1423 |
+
|
1424 |
+
# Display each system
|
1425 |
+
for idx, system in enumerate(ventilation_systems):
|
1426 |
+
cols = st.columns([2, 1, 1, 1, 1, 1])
|
1427 |
+
cols[0].write(system["name"])
|
1428 |
+
cols[1].write(system.get("system_type", "Unknown"))
|
1429 |
+
cols[2].write(f"{system.get('area', 0):.1f}")
|
1430 |
+
if system.get("system_type") == "AirChanges/Hour":
|
1431 |
+
rate_info = f"{system.get('design_flow_rate', 0):.2f} ACH"
|
1432 |
+
elif system.get("system_type") in ["Balanced Flow", "Heat Recovery"]:
|
1433 |
+
rate_info = f"{system.get('design_flow_rate', 0):.2f} m³/s"
|
1434 |
+
else:
|
1435 |
+
rate_info = f"{system.get('opening_effectiveness', 0):.2f} %"
|
1436 |
+
cols[3].write(rate_info)
|
1437 |
+
|
1438 |
+
# Edit button
|
1439 |
+
edit_key = f"edit_ventilation_{system['name']}_{idx}"
|
1440 |
+
with cols[4].container():
|
1441 |
+
if st.button("Edit", key=edit_key):
|
1442 |
+
st.session_state.ventilation_editor = {
|
1443 |
+
"index": idx,
|
1444 |
+
"name": system.get("name", ""),
|
1445 |
+
"system_type": system.get("system_type", "AirChanges/Hour"),
|
1446 |
+
"area": system.get("area", 100.0),
|
1447 |
+
"schedule": system.get("schedule", "Continuous"),
|
1448 |
+
"design_flow_rate": system.get("design_flow_rate", 1.0),
|
1449 |
+
"ventilation_type": system.get("ventilation_type", "Natural"),
|
1450 |
+
"fan_pressure_rise": system.get("fan_pressure_rise", 200.0),
|
1451 |
+
"fan_efficiency": system.get("fan_efficiency", 0.7),
|
1452 |
+
"zone_name": system.get("zone_name", ""),
|
1453 |
+
"opening_effectiveness": system.get("opening_effectiveness", 50.0),
|
1454 |
+
"sensible_effectiveness": system.get("sensible_effectiveness", 0.8),
|
1455 |
+
"latent_effectiveness": system.get("latent_effectiveness", 0.6),
|
1456 |
+
"is_edit": True
|
1457 |
+
}
|
1458 |
+
st.session_state.ventilation_action = {"action": "edit", "id": str(uuid.uuid4())}
|
1459 |
+
st.session_state.internal_loads_rerun_pending = True
|
1460 |
+
|
1461 |
+
# Delete button
|
1462 |
+
delete_key = f"delete_ventilation_{system['name']}_{idx}"
|
1463 |
+
with cols[5].container():
|
1464 |
+
if st.button("Delete", key=delete_key):
|
1465 |
+
st.session_state.project_data["internal_loads"]["ventilation"].pop(idx)
|
1466 |
+
st.success(f"Ventilation System '{system['name']}' deleted!")
|
1467 |
+
st.session_state.ventilation_action = {"action": "delete", "id": str(uuid.uuid4())}
|
1468 |
+
st.session_state.internal_loads_rerun_pending = True
|
1469 |
+
|
1470 |
+
def display_infiltration_table(infiltration_systems: List[Dict[str, Any]]):
|
1471 |
+
"""Display infiltration systems in a table format with edit/delete buttons."""
|
1472 |
# Create column headers
|
1473 |
cols = st.columns([2, 1, 1, 1, 1, 1])
|
1474 |
cols[0].write("**Name**")
|
|
|
1479 |
cols[5].write("**Delete**")
|
1480 |
|
1481 |
# Display each system
|
1482 |
+
for idx, system in enumerate(infiltration_systems):
|
1483 |
cols = st.columns([2, 1, 1, 1, 1, 1])
|
1484 |
cols[0].write(system["name"])
|
1485 |
cols[1].write(system.get("system_type", "Unknown"))
|
1486 |
cols[2].write(f"{system.get('area', 0):.1f}")
|
1487 |
+
if system.get("system_type") == "AirChanges/Hour":
|
1488 |
+
rate_info = f"{system.get('design_flow_rate', 0):.2f} ACH"
|
1489 |
+
elif system.get("system_type") == "Effective Leakage Area":
|
1490 |
+
rate_info = f"{system.get('effective_air_leakage_area', 0):.2f} cm²"
|
1491 |
+
else:
|
1492 |
+
rate_info = f"{system.get('flow_coefficient', 0):.6f} m³/s·Paⁿ"
|
1493 |
cols[3].write(rate_info)
|
1494 |
|
1495 |
# Edit button
|
1496 |
+
edit_key = f"edit_infiltration_{system['name']}_{idx}"
|
1497 |
with cols[4].container():
|
1498 |
if st.button("Edit", key=edit_key):
|
1499 |
+
st.session_state.infiltration_editor = {
|
1500 |
"index": idx,
|
1501 |
"name": system.get("name", ""),
|
1502 |
+
"system_type": system.get("system_type", "AirChanges/Hour"),
|
1503 |
"area": system.get("area", 100.0),
|
|
|
|
|
1504 |
"schedule": system.get("schedule", "Continuous"),
|
1505 |
+
"design_flow_rate": system.get("design_flow_rate", 0.3),
|
1506 |
+
"effective_air_leakage_area": system.get("effective_air_leakage_area", 100.0),
|
1507 |
+
"stack_coefficient": system.get("stack_coefficient", 0.0001),
|
1508 |
+
"wind_coefficient": system.get("wind_coefficient", 0.0001),
|
1509 |
+
"flow_coefficient": system.get("flow_coefficient", 0.0001),
|
1510 |
+
"pressure_exponent": system.get("pressure_exponent", 0.6),
|
1511 |
"is_edit": True
|
1512 |
}
|
1513 |
+
st.session_state.infiltration_action = {"action": "edit", "id": str(uuid.uuid4())}
|
1514 |
st.session_state.internal_loads_rerun_pending = True
|
1515 |
|
1516 |
# Delete button
|
1517 |
+
delete_key = f"delete_infiltration_{system['name']}_{idx}"
|
1518 |
with cols[5].container():
|
1519 |
if st.button("Delete", key=delete_key):
|
1520 |
+
st.session_state.project_data["internal_loads"]["infiltration"].pop(idx)
|
1521 |
+
st.success(f"Infiltration System '{system['name']}' deleted!")
|
1522 |
+
st.session_state.infiltration_action = {"action": "delete", "id": str(uuid.uuid4())}
|
1523 |
st.session_state.internal_loads_rerun_pending = True
|
1524 |
|
1525 |
def display_schedules_table(schedules: Dict[str, Any]):
|