mabuseif commited on
Commit
72a2552
·
verified ·
1 Parent(s): 4aa3854

Update app/internal_loads.py

Browse files
Files changed (1) hide show
  1. 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 & Infiltration"]
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 2005 Handbook.")
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 & Infiltration":
74
- display_ventilation_infiltration_tab()
 
 
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
- "ventilation_infiltration": []
 
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 display_ventilation_infiltration_tab():
572
- """Display the ventilation/infiltration tab content with a two-column layout similar to components.py."""
573
- # Get ventilation/infiltration from session state
574
- vent_inf_systems = st.session_state.project_data["internal_loads"]["ventilation_infiltration"]
575
 
576
  # Split the display into two columns
577
  col1, col2 = st.columns([3, 2])
578
 
579
  with col1:
580
- st.subheader("Saved Ventilation & Infiltration")
581
- if vent_inf_systems:
582
- display_ventilation_infiltration_table(vent_inf_systems)
583
  else:
584
- st.write("No ventilation or infiltration systems defined.")
585
 
586
  with col2:
587
- st.subheader("Ventilation/Infiltration Editor/Creator")
588
 
589
  # Initialize editor and action states
590
- if "vent_inf_editor" not in st.session_state:
591
- st.session_state.vent_inf_editor = {}
592
- if "vent_inf_action" not in st.session_state:
593
- st.session_state.vent_inf_action = {"action": None, "id": None}
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("vent_inf_editor_form", clear_on_submit=True):
601
- editor_state = st.session_state.get("vent_inf_editor", {})
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/infiltration system."
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
- if system_type == "Ventilation":
630
- # Ventilation rate
631
- ventilation_rate = st.number_input(
632
- "Ventilation Rate (L/s·m²)",
 
 
 
 
 
 
 
 
 
633
  min_value=0.1,
634
  max_value=50.0,
635
- value=float(editor_state.get("ventilation_rate", default_building_data["ventilation_rate"])),
636
  format="%.2f",
637
- help="Ventilation rate in liters per second per square meter."
638
  )
639
- air_change_rate = 0.0
640
- else:
641
- # Air change rate for infiltration
642
- air_change_rate = st.number_input(
643
- "Air Change Rate (ACH)",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
644
  min_value=0.0,
645
- max_value=10.0,
646
- value=float(editor_state.get("air_change_rate", default_building_data["air_change_rate"])),
647
  format="%.2f",
648
- help="Air change rate in air changes per hour."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
649
  )
650
- ventilation_rate = 0.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 vent_inf_systems if not (is_edit and system["name"] == editor_state.get("name"))]
678
  if name.strip() in existing_names:
679
  st.error("System name must be unique.")
680
  return
681
 
682
- # Create ventilation/infiltration data
683
- vent_inf_data = {
684
  "id": str(uuid.uuid4()),
685
  "name": name.strip(),
686
  "system_type": system_type,
687
  "area": area,
688
- "ventilation_rate": ventilation_rate,
689
- "air_change_rate": air_change_rate,
690
- "schedule": schedule
 
 
 
 
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"]["ventilation_infiltration"][index] = vent_inf_data
697
- st.success(f"{system_type} System '{name}' updated!")
698
  else:
699
- st.session_state.project_data["internal_loads"]["ventilation_infiltration"].append(vent_inf_data)
700
- st.success(f"{system_type} System '{name}' added!")
701
 
702
  # Clear editor state
703
- st.session_state.vent_inf_editor = {}
704
- st.session_state.vent_inf_action = {"action": "save", "id": str(uuid.uuid4())}
705
  st.session_state.internal_loads_rerun_pending = True
706
 
707
  elif refresh:
708
  # Clear editor state
709
- st.session_state.vent_inf_editor = {}
710
- st.session_state.vent_inf_action = {"action": "refresh", "id": str(uuid.uuid4())}
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 display_ventilation_infiltration_table(vent_inf_systems: List[Dict[str, Any]]):
1091
- """Display ventilation/infiltration systems in a table format with edit/delete buttons."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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(vent_inf_systems):
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
- rate_info = f"{system.get('ventilation_rate', 0):.2f} L/s·m²" if system.get("system_type") == "Ventilation" else f"{system.get('air_change_rate', 0):.2f} ACH"
 
 
 
 
 
1108
  cols[3].write(rate_info)
1109
 
1110
  # Edit button
1111
- edit_key = f"edit_vent_inf_{system['name']}_{idx}"
1112
  with cols[4].container():
1113
  if st.button("Edit", key=edit_key):
1114
- st.session_state.vent_inf_editor = {
1115
  "index": idx,
1116
  "name": system.get("name", ""),
1117
- "system_type": system.get("system_type", "Ventilation"),
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.vent_inf_action = {"action": "edit", "id": str(uuid.uuid4())}
1125
  st.session_state.internal_loads_rerun_pending = True
1126
 
1127
  # Delete button
1128
- delete_key = f"delete_vent_inf_{system['name']}_{idx}"
1129
  with cols[5].container():
1130
  if st.button("Delete", key=delete_key):
1131
- st.session_state.project_data["internal_loads"]["ventilation_infiltration"].pop(idx)
1132
- st.success(f"{system.get('system_type', 'System')} '{system['name']}' deleted!")
1133
- st.session_state.vent_inf_action = {"action": "delete", "id": str(uuid.uuid4())}
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]):