mabuseif commited on
Commit
c055316
·
verified ·
1 Parent(s): b1504a6

Update app/internal_loads.py

Browse files
Files changed (1) hide show
  1. app/internal_loads.py +466 -396
app/internal_loads.py CHANGED
@@ -3,7 +3,7 @@ BuildSustain - Internal Loads Module
3
 
4
  This module handles the internal loads functionality of the BuildSustain application,
5
  allowing users to define occupancy, lighting, equipment, ventilation, infiltration, and schedules.
6
- It provides comprehensive load management with a UI structure matching the Components module.
7
 
8
  Developed by: Dr Majed Abuseif, Deakin University
9
  © 2025
@@ -53,11 +53,6 @@ def display_internal_loads_page():
53
  if "module_rerun_flags" not in st.session_state:
54
  st.session_state.module_rerun_flags = {}
55
 
56
- # Check if rerun is pending
57
- if st.session_state.module_rerun_flags.get("internal_loads", False):
58
- st.session_state.module_rerun_flags["internal_loads"] = False
59
- st.rerun()
60
-
61
  # Create tabs for different load types
62
  tabs = st.tabs(LOAD_TYPES)
63
 
@@ -103,7 +98,7 @@ def initialize_internal_loads():
103
  }
104
 
105
  def display_people_tab():
106
- """Display the people tab content with a two-column layout similar to components.py."""
107
  # Get people from session state
108
  people_groups = st.session_state.project_data["internal_loads"]["people"]
109
 
@@ -126,6 +121,10 @@ def display_people_tab():
126
  if "people_action" not in st.session_state:
127
  st.session_state.people_action = {"action": None, "id": None}
128
 
 
 
 
 
129
  # Display the editor form
130
  with st.form("people_editor_form", clear_on_submit=True):
131
  editor_state = st.session_state.get("people_editor", {})
@@ -205,15 +204,20 @@ def display_people_tab():
205
  refresh = st.form_submit_button("Refresh")
206
 
207
  # Handle form submission
208
- if submit:
 
 
 
209
  # Validate inputs
210
  if not name.strip():
211
  st.error("Group name is required.")
 
212
  return
213
  # Check for unique name
214
  existing_names = [group["name"] for group in people_groups if not (is_edit and group["name"] == editor_state.get("name"))]
215
  if name.strip() in existing_names:
216
  st.error("Group name must be unique.")
 
217
  return
218
 
219
  # Get activity data
@@ -245,19 +249,21 @@ def display_people_tab():
245
  st.session_state.project_data["internal_loads"]["people"].append(people_data)
246
  st.success(f"People group '{name}' added!")
247
 
248
- # Clear editor state
249
  st.session_state.people_editor = {}
250
- st.session_state.people_action = {"action": "save", "id": str(uuid.uuid4())}
251
  st.session_state.module_rerun_flags["internal_loads"] = True
252
 
253
- elif refresh:
254
- # Clear editor state
 
 
255
  st.session_state.people_editor = {}
256
- st.session_state.people_action = {"action": "refresh", "id": str(uuid.uuid4())}
257
  st.session_state.module_rerun_flags["internal_loads"] = True
258
 
259
  def display_lighting_tab():
260
- """Display the lighting tab content with a two-column layout similar to components.py."""
261
  # Get lighting from session state
262
  lighting_systems = st.session_state.project_data["internal_loads"]["lighting"]
263
 
@@ -280,6 +286,10 @@ def display_lighting_tab():
280
  if "lighting_action" not in st.session_state:
281
  st.session_state.lighting_action = {"action": None, "id": None}
282
 
 
 
 
 
283
  # Get building type for default values
284
  building_type = st.session_state.project_data["building_info"].get("building_type")
285
  default_lighting_density = DEFAULT_BUILDING_INTERNALS.get(building_type, DEFAULT_BUILDING_INTERNALS["Other"])["lighting_density"]
@@ -366,18 +376,24 @@ def display_lighting_tab():
366
  refresh = st.form_submit_button("Refresh")
367
 
368
  # Handle form submission
369
- if submit:
 
 
 
370
  # Validate inputs
371
  if not name.strip():
372
  st.error("System name is required.")
 
373
  return
374
  if abs(radiative_fraction + convective_fraction - 1.0) > 0.01:
375
  st.error("Radiative and convective fractions must sum to 1.0")
 
376
  return
377
  # Check for unique name
378
  existing_names = [system["name"] for system in lighting_systems if not (is_edit and system["name"] == editor_state.get("name"))]
379
  if name.strip() in existing_names:
380
  st.error("System name must be unique.")
 
381
  return
382
 
383
  # Create lighting system data
@@ -402,19 +418,21 @@ def display_lighting_tab():
402
  st.session_state.project_data["internal_loads"]["lighting"].append(lighting_data)
403
  st.success(f"Lighting system '{name}' added!")
404
 
405
- # Clear editor state
406
  st.session_state.lighting_editor = {}
407
- st.session_state.lighting_action = {"action": "save", "id": str(uuid.uuid4())}
408
  st.session_state.module_rerun_flags["internal_loads"] = True
409
 
410
- elif refresh:
411
- # Clear editor state
 
 
412
  st.session_state.lighting_editor = {}
413
- st.session_state.lighting_action = {"action": "refresh", "id": str(uuid.uuid4())}
414
  st.session_state.module_rerun_flags["internal_loads"] = True
415
 
416
  def display_equipment_tab():
417
- """Display the equipment tab content with a two-column layout similar to components.py."""
418
  # Get equipment from session state
419
  equipment_systems = st.session_state.project_data["internal_loads"]["equipment"]
420
 
@@ -437,8 +455,12 @@ def display_equipment_tab():
437
  if "equipment_action" not in st.session_state:
438
  st.session_state.equipment_action = {"action": None, "id": None}
439
 
 
 
 
 
440
  # Get building type for default values
441
- building_type = st.session_state.project_data["building_info"].get("building_type")
442
  default_equipment_data = DEFAULT_BUILDING_INTERNALS.get(building_type, DEFAULT_BUILDING_INTERNALS["Other"])["equipment_heat_gains"]
443
 
444
  # Display the editor form
@@ -463,64 +485,68 @@ def display_equipment_tab():
463
  help="Floor area served by this equipment."
464
  )
465
 
466
- # Heat gains
467
- st.write("**Heat Gains (W/m²):**")
468
- col_sens, col_lat = st.columns(2)
 
469
 
470
- with col_sens:
471
- sensible_gain = st.number_input(
472
  "Sensible Heat Gain",
473
  min_value=0.0,
474
  max_value=200.0,
475
- value=float(editor_state.get("sensible_gain", default_equipment_data["sensible"])),
476
  format="%.2f",
477
  help="Sensible heat gain in watts per square meter."
478
  )
479
 
480
- with col_lat:
481
- latent_gain = st.number_input(
482
  "Latent Heat Gain",
483
  min_value=0.0,
484
  max_value=200.0,
485
- value=float(editor_state.get("latent_gain", default_equipment_data["latent"])),
486
  format="%.2f",
487
  help="Latent heat gain in watts per square meter."
488
  )
489
 
490
  # Heat distribution
491
  st.write("**Heat Distribution:**")
492
- col_rad, col_conv = st.columns(2)
 
493
 
494
- with col_rad:
495
- radiative_fraction = st.number_input(
 
 
 
496
  "Radiative Fraction",
497
- min_value=0.0,
498
- max_value=1.0,
499
- value=float(editor_state.get("radiative_fraction", 0.5)),
500
  format="%.2f",
501
  help="Fraction of sensible heat released as radiation."
502
  )
503
 
504
  with col_conv:
505
  convective_fraction = st.number_input(
506
- "Convective Fraction",
507
  min_value=0.0,
508
  max_value=1.0,
509
- value=float(editor_state.get("convective_fraction", 0.5)),
510
  format="%.2f",
511
  help="Fraction of sensible heat released as convection."
512
  )
513
 
514
  # Schedule selection
515
- available_schedules = list(st.session_state.project_data["internal_loads"]["schedules"].keys())
516
- schedule = st.selectbox(
517
- "Schedule",
518
  available_schedules,
519
  index=available_schedules.index(editor_state.get("schedule", available_schedules[0])) if editor_state.get("schedule") in available_schedules else 0,
520
- help="Select the equipment schedule."
521
  )
522
 
523
- # Zone assignment
524
  zone = st.text_input(
525
  "Zone",
526
  value=editor_state.get("zone", "Whole Building"),
@@ -528,125 +554,139 @@ def display_equipment_tab():
528
  )
529
 
530
  # Submit buttons
531
- col1, col2 = st.columns(2)
532
  with col1:
533
- submit_label = "Update" if is_edit else "Add"
534
- submit = st.form_submit_button(f"{submit_label} Equipment")
535
 
536
  with col2:
537
- refresh = st.form_submit_button("Refresh")
538
 
539
  # Handle form submission
540
- if submit:
 
 
 
541
  # Validate inputs
542
  if not name.strip():
543
  st.error("Equipment name is required.")
 
544
  return
545
  if abs(radiative_fraction + convective_fraction - 1.0) > 0.01:
546
  st.error("Radiative and convective fractions must sum to 1.0")
 
547
  return
548
  # Check for unique name
549
  existing_names = [system["name"] for system in equipment_systems if not (is_edit and system["name"] == editor_state.get("name"))]
550
  if name.strip() in existing_names:
551
- st.error("Equipment name must be unique.")
 
552
  return
553
-
554
  # Create equipment data
555
- equipment_data = {
556
  "id": str(uuid.uuid4()),
557
  "name": name.strip(),
558
  "area": area,
559
- "sensible_gain": sensible_gain,
560
  "latent_gain": latent_gain,
561
  "total_sensible_power": area * sensible_gain,
562
  "total_latent_power": area * latent_gain,
563
  "radiative_fraction": radiative_fraction,
564
  "convective_fraction": convective_fraction,
565
  "schedule": schedule,
566
- "zone": zone
 
567
  }
568
 
569
  # Handle edit mode
570
  if is_edit:
571
- index = editor_state.get("index", 0)
572
- st.session_state.project_data["internal_loads"]["equipment"][index] = equipment_data
573
- st.success(f"Equipment '{name}' updated!")
574
  else:
575
  st.session_state.project_data["internal_loads"]["equipment"].append(equipment_data)
576
  st.success(f"Equipment '{name}' added!")
577
-
578
  # Clear editor state
579
- st.session_state.equipment_editor = {}
580
- st.session_state.equipment_action = {"action": "save", "id": str(uuid.uuid4())}
581
- st.session_state.module_rerun_flags["internal_loads"] = True
582
-
583
- elif refresh:
584
- # Clear editor state
585
- st.session_state.equipment_editor = {}
586
- st.session_state.equipment_action = {"action": "refresh", "id": str(uuid.uuid4())}
587
  st.session_state.module_rerun_flags["internal_loads"] = True
 
 
 
 
 
 
 
588
 
589
  def display_ventilation_infiltration_tab():
590
- """Display the ventilation/infiltration tab content with a two-column layout similar to components.py."""
591
- # Get ventilation/infiltration from session state
592
- vent_inf_systems = st.session_state.project_data["internal_loads"]["ventilation_infiltration"]
593
 
594
- # Split the display into two columns
595
- col1, col2 = st.columns([3, 2])
596
 
597
  with col1:
598
  st.subheader("Saved Ventilation & Infiltration")
599
- if vent_inf_systems:
600
  display_ventilation_infiltration_table(vent_inf_systems)
601
  else:
602
- st.write("No ventilation or infiltration systems defined.")
603
-
604
  with col2:
605
  st.subheader("Ventilation/Infiltration Editor/Creator")
606
 
607
- # Initialize editor and action states
608
- if "vent_inf_editor" not in st.session_state:
609
- st.session_state.vent_inf_editor = {}
610
- if "vent_inf_action" not in st.session_state:
611
- st.session_state.vent_inf_action = {"action": None, "id": None}
 
 
 
 
612
 
613
  # Get building type for default values
614
- building_type = st.session_state.project_data["building_info"].get("building_type")
615
- default_building_data = DEFAULT_BUILDING_INTERNALS.get(building_type, DEFAULT_BUILDING_INTERNALS["Other"])
616
 
617
  # Display the editor form
618
- with st.form("vent_inf_editor_form", clear_on_submit=True):
619
- editor_state = st.session_state.get("vent_inf_editor", {})
620
- is_edit = editor_state.get("is_edit", False)
 
621
 
622
  # System name
623
- name = st.text_input(
624
  "System Name",
625
  value=editor_state.get("name", ""),
626
  help="Enter a unique name for this ventilation/infiltration system."
627
  )
628
 
629
  # System type
630
- system_type = st.selectbox(
631
  "System Type",
632
  ["Ventilation", "Infiltration"],
633
- index=["Ventilation", "Infiltration"].index(editor_state.get("system_type", "Ventilation")) if editor_state.get("system_type") in ["Ventilation", "Infiltration"] else 0,
634
- help="Select whether this is ventilation or infiltration."
635
  )
636
 
637
  # Area
638
- area = st.number_input(
639
  "Area (m²)",
640
  min_value=1.0,
641
- max_value=100000.0,
642
- value=float(editor_state.get("area", st.session_state.project_data["building_info"].get("floor_area", 100.0))),
 
643
  format="%.2f",
644
- help="Floor area served by this system."
645
  )
646
 
647
  if system_type == "Ventilation":
648
  # Ventilation rate
649
- ventilation_rate = st.number_input(
650
  "Ventilation Rate (L/s·m²)",
651
  min_value=0.1,
652
  max_value=50.0,
@@ -654,10 +694,10 @@ def display_ventilation_infiltration_tab():
654
  format="%.2f",
655
  help="Ventilation rate in liters per second per square meter."
656
  )
657
- air_change_rate = 0.0
658
  else:
659
  # Air change rate for infiltration
660
- air_change_rate = st.number_input(
661
  "Air Change Rate (ACH)",
662
  min_value=0.0,
663
  max_value=10.0,
@@ -665,11 +705,11 @@ def display_ventilation_infiltration_tab():
665
  format="%.2f",
666
  help="Air change rate in air changes per hour."
667
  )
668
- ventilation_rate = 0.0
669
-
670
  # Schedule selection
671
- available_schedules = list(st.session_state.project_data["internal_loads"]["schedules"].keys())
672
- schedule = st.selectbox(
673
  "Schedule",
674
  available_schedules,
675
  index=available_schedules.index(editor_state.get("schedule", available_schedules[0])) if editor_state.get("schedule") in available_schedules else 0,
@@ -677,64 +717,72 @@ def display_ventilation_infiltration_tab():
677
  )
678
 
679
  # Zone assignment
680
- zone = st.text_input(
681
  "Zone",
682
  value=editor_state.get("zone", "Whole Building"),
683
  help="Zone or area where this system operates."
684
  )
685
 
686
  # Submit buttons
687
- col1, col2 = st.columns(2)
688
- with col1:
689
- submit_label = "Update" if is_edit else "Add"
690
- submit = st.form_submit_button(f"{submit_label} System")
 
 
 
691
 
692
- with col2:
693
- refresh = st.form_submit_button("Refresh")
694
-
695
  # Handle form submission
696
- if submit:
 
 
 
697
  # Validate inputs
698
  if not name.strip():
699
  st.error("System name is required.")
 
700
  return
701
  # Check for unique name
702
- existing_names = [system["name"] for system in vent_inf_systems if not (is_edit and system["name"] == editor_state.get("name"))]
703
- if name.strip() in existing_names:
704
  st.error("System name must be unique.")
 
705
  return
706
-
707
- # Create ventilation/infiltration data
708
- vent_inf_data = {
709
- "id": str(uuid.uuid4()),
710
  "name": name.strip(),
711
  "system_type": system_type,
712
  "area": area,
713
  "ventilation_rate": ventilation_rate,
714
  "air_change_rate": air_change_rate,
715
  "schedule": schedule,
716
- "zone": zone
 
717
  }
718
-
719
  # Handle edit mode
720
  if is_edit:
721
- index = editor_state.get("index", 0)
722
- st.session_state.project_data["internal_loads"]["ventilation_infiltration"][index] = vent_inf_data
 
723
  st.success(f"{system_type} system '{name}' updated!")
724
  else:
725
- st.session_state.project_data["internal_loads"]["ventilation_infiltration"].append(vent_inf_data)
726
  st.success(f"{system_type} system '{name}' added!")
 
 
 
 
 
727
 
728
- # Clear editor state
729
- st.session_state.vent_inf_editor = {}
730
- st.session_state.vent_inf_action = {"action": "save", "id": str(uuid.uuid4())}
731
- st.session_state.module_rerun_flags["internal_loads"] = True
732
-
733
- elif refresh:
734
- # Clear editor state
735
- st.session_state.vent_inf_editor = {}
736
- st.session_state.vent_inf_action = {"action": "refresh", "id": str(uuid.uuid4())}
737
- st.session_state.module_rerun_flags["internal_loads"] = True
738
 
739
  def display_schedules_tab():
740
  import streamlit as st
@@ -891,333 +939,355 @@ def display_schedules_tab():
891
  st.session_state.module_rerun_flags["internal_loads"] = True
892
  st.rerun()
893
 
894
- # Edit logic
895
  if edit_submitted and saved_schedule:
896
- action_id = str(uuid.uuid4())
897
- if st.session_state.schedule_action.get("id") != action_id:
898
- st.session_state.schedule_action = {"action": "edit", "id": action_id}
899
- if saved_schedule in DEFAULT_SCHEDULE_TEMPLATES:
900
- st.error("Default schedules cannot be edited.")
901
- else:
902
- schedule_data = schedules[saved_schedule]
903
- st.session_state.schedule_editor = {
904
- "is_edit": True,
905
- "original_name": saved_schedule,
906
- "name": saved_schedule,
907
- "description": schedule_data.get("description", ""),
908
- "weekday": schedule_data.get("weekday", [0.0] * 24),
909
- "weekend": schedule_data.get("weekend", [0.0] * 24),
910
- "template": "None"
911
- }
912
- for hour in range(24):
913
- st.session_state[f"weekday_slider_{hour}_value"] = schedule_data.get("weekday", [0.0] * 24)[hour]
914
- st.session_state[f"weekend_slider_{hour}_value"] = schedule_data.get("weekend", [0.0] * 24)[hour]
915
- st.session_state.schedule_action = {"action": None, "id": None}
916
- st.session_state.module_rerun_flags["internal_loads"] = True
917
- st.rerun()
918
 
919
- # Delete logic
920
- if delete_submitted and saved_schedule:
921
- action_id = str(uuid.uuid4())
922
- if st.session_state.schedule_action.get("id") != action_id:
923
- st.session_state.schedule_action = {"action": "delete", "id": action_id}
924
- if saved_schedule in DEFAULT_SCHEDULE_TEMPLATES:
925
- st.error("Default schedules cannot be deleted.")
926
- elif is_schedule_in_use(saved_schedule):
927
- st.error(f"Schedule '{saved_schedule}' is in use and cannot be deleted.")
928
- else:
929
- del schedules[saved_schedule]
930
- st.session_state.schedule_editor = DEFAULT_STATE.copy()
931
- for hour in range(24):
932
- st.session_state[f"weekday_slider_{hour}_value"] = 0.0
933
- st.session_state[f"weekend_slider_{hour}_value"] = 0.0
934
- st.success(f"Schedule '{saved_schedule}' deleted!")
935
- st.session_state.schedule_action = {"action": None, "id": None}
936
- st.session_state.module_rerun_flags["internal_loads"] = True
937
- st.rerun()
938
 
939
- def is_schedule_in_use(schedule_name: str) -> bool:
940
  """
941
- Check if a schedule is in use by any people, lighting, equipment, or ventilation/infiltration systems.
942
-
943
  Args:
944
- schedule_name: Name of the schedule to check.
 
 
945
 
946
  Returns:
947
- bool: True if the schedule is in use, False otherwise.
948
  """
949
- internal_loads = st.session_state.project_data["internal_loads"]
950
- for group in internal_loads.get("people", []):
951
- if group.get("schedule") == schedule_name:
952
  return True
953
- for system in internal_loads.get("lighting", []):
954
- if system.get("schedule") == schedule_name:
955
  return True
956
- for system in internal_loads.get("equipment", []):
957
- if system.get("schedule") == schedule_name:
958
  return True
959
- for system in internal_loads.get("ventilation_infiltration", []):
960
- if system.get("schedule") == schedule_name:
961
  return True
962
  return False
963
 
964
  def display_people_table(people_groups: List[Dict[str, Any]]):
965
- """Display people groups in a table format with edit/delete buttons."""
966
- # Create column headers
967
- cols = st.columns([2, 1, 1, 1, 1, 1, 1])
968
- cols[0].write("**Name**")
969
- cols[1].write("**Number**")
970
- cols[2].write("**Sensible (W)**")
971
- cols[3].write("**Latent (W)**")
972
- cols[4].write("**Zone**")
973
- cols[5].write("**Edit**")
974
- cols[6].write("**Delete**")
975
-
976
- # Display each group
977
- for idx, group in enumerate(people_groups):
978
- cols = st.columns([2, 1, 1, 1, 1, 1, 1])
979
- cols[0].write(group["name"])
980
- cols[1].write(str(group.get("num_people", 0)))
981
- cols[2].write(f"{group.get('total_sensible_heat', 0):.1f}")
982
- cols[3].write(f"{group.get('total_latent_heat', 0):.1f}")
983
- cols[4].write(group.get("zone", "Unknown"))
984
-
985
- # Edit button
986
- edit_key = f"edit_people_{group['id']}_{idx}"
987
- with cols[5].container():
988
- if st.button("Edit", key=edit_key):
989
- st.session_state.people_editor = {
990
- "index": idx,
991
- "name": group.get("name", ""),
992
- "num_people": group.get("num_people", 10),
993
- "activity_level": group.get("activity_level", list(PEOPLE_ACTIVITY_LEVELS.keys())[0]),
994
- "clo_summer": group.get("clo_summer", 0.5),
995
- "clo_winter": group.get("clo_winter", 1.0),
996
- "schedule": group.get("schedule", "Continuous"),
997
- "zone": group.get("zone", "Whole Building"),
998
- "is_edit": True
999
- }
1000
- st.session_state.people_action = {"action": "edit", "id": str(uuid.uuid4())}
1001
- st.session_state.module_rerun_flags["internal_loads"] = True
1002
-
1003
- # Delete button
1004
- delete_key = f"delete_people_{group['id']}_{idx}"
1005
- with cols[6].container():
1006
- if st.button("Delete", key=delete_key):
1007
- st.session_state.project_data["internal_loads"]["people"].pop(idx)
1008
- st.success(f"People group '{group['name']}' deleted!")
1009
- st.session_state.people_action = {"action": "delete", "id": str(uuid.uuid4())}
1010
- st.session_state.module_rerun_flags["internal_loads"] = True
1011
 
1012
- def display_lighting_table(lighting_systems: List[Dict[str, Any]]):
1013
- """Display lighting systems in a table format with edit/delete buttons."""
1014
- # Create column headers
1015
- cols = st.columns([2, 1, 1, 1, 1, 1, 1])
1016
- cols[0].write("**Name**")
1017
- cols[1].write("**Area (m²)**")
1018
- cols[2].write("**LPD (W/m²)**")
1019
- cols[3].write("**Total Power (W)**")
1020
- cols[4].write("**Zone**")
1021
- cols[5].write("**Edit**")
1022
- cols[6].write("**Delete**")
1023
-
1024
- # Display each system
1025
- for idx, system in enumerate(lighting_systems):
1026
- cols = st.columns([2, 1, 1, 1, 1, 1, 1])
1027
- cols[0].write(system["name"])
1028
- cols[1].write(f"{system.get('area', 0):.1f}")
1029
- cols[2].write(f"{system.get('lpd', 0):.2f}")
1030
- cols[3].write(f"{system.get('total_power', 0):.1f}")
1031
- cols[4].write(system.get("zone", "Unknown"))
1032
 
1033
  # Edit button
1034
- edit_key = f"edit_lighting_{system['id']}_{idx}"
1035
- with cols[5].container():
1036
- if st.button("Edit", key=edit_key):
1037
- st.session_state.lighting_editor = {
1038
- "index": idx,
1039
- "name": system.get("name", ""),
1040
- "area": system.get("area", 100.0),
1041
- "lpd": system.get("lpd", 12.0),
1042
- "radiative_fraction": system.get("radiative_fraction", 0.4),
1043
- "convective_fraction": system.get("convective_fraction", 0.6),
1044
- "schedule": system.get("schedule", "Continuous"),
1045
- "zone": system.get("zone", "Whole Building"),
 
1046
  "is_edit": True
1047
  }
1048
- st.session_state.lighting_action = {"action": "edit", "id": str(uuid.uuid4())}
1049
- st.session_state.module_rerun_flags["internal_loads"] = True
1050
-
1051
  # Delete button
1052
- delete_key = f"delete_lighting_{system['id']}_{idx}"
1053
- with cols[6].container():
1054
- if st.button("Delete", key=delete_key):
1055
- st.session_state.project_data["internal_loads"]["lighting"].pop(idx)
1056
- st.success(f"Lighting system '{system['name']}' deleted!")
1057
- st.session_state.lighting_action = {"action": "delete", "id": str(uuid.uuid4())}
1058
- st.session_state.module_rerun_flags["internal_loads"] = True
1059
-
1060
- def display_equipment_table(equipment_systems: List[Dict[str, Any]]):
1061
- """Display equipment systems in a table format with edit/delete buttons."""
1062
- # Create column headers
1063
- cols = st.columns([2, 1, 1, 1, 1, 1, 1])
1064
- cols[0].write("**Name**")
1065
- cols[1].write("**Area (m²)**")
1066
- cols[2].write("**Sensible (W)**")
1067
- cols[3].write("**Latent (W)**")
1068
- cols[4].write("**Zone**")
1069
- cols[5].write("**Edit**")
1070
- cols[6].write("**Delete**")
1071
-
1072
- # Display each system
1073
- for idx, system in enumerate(equipment_systems):
1074
- cols = st.columns([2, 1, 1, 1, 1, 1, 1])
1075
- cols[0].write(system["name"])
1076
- cols[1].write(f"{system.get('area', 0):.1f}")
1077
- cols[2].write(f"{system.get('total_sensible_power', 0):.1f}")
1078
- cols[3].write(f"{system.get('total_latent_power', 0):.1f}")
1079
- cols[4].write(system.get("zone", "Unknown"))
1080
-
1081
- # Edit button
1082
- edit_key = f"edit_equipment_{system['id']}_{idx}"
1083
  with cols[5].container():
1084
- if st.button("Edit", key=edit_key):
1085
- st.session_state.equipment_editor = {
1086
- "index": idx,
1087
- "name": system.get("name", ""),
1088
- "area": system.get("area", 100.0),
1089
- "sensible_gain": system.get("sensible_gain", 10.0),
1090
- "latent_gain": system.get("latent_gain", 2.0),
1091
- "radiative_fraction": system.get("radiative_fraction", 0.5),
1092
- "convective_fraction": system.get("convective_fraction", 0.5),
1093
- "schedule": system.get("schedule", "Continuous"),
1094
- "zone": system.get("zone", "Whole Building"),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1095
  "is_edit": True
1096
- }
1097
- st.session_state.equipment_action = {"action": "edit", "id": str(uuid.uuid4())}
1098
- st.session_state.module_rerun_flags["internal_loads"] = True
1099
 
1100
  # Delete button
1101
- delete_key = f"delete_equipment_{system['id']}_{idx}"
1102
- with cols[6].container():
1103
- if st.button("Delete", key=delete_key):
1104
- st.session_state.project_data["internal_loads"]["equipment"].pop(idx)
1105
- st.success(f"Equipment '{system['name']}' deleted!")
1106
- st.session_state.equipment_action = {"action": "delete", "id": str(uuid.uuid4())}
1107
- st.session_state.module_rerun_flags["internal_loads"] = True
 
1108
 
1109
- def display_ventilation_infiltration_table(vent_inf_systems: List[Dict[str, Any]]):
1110
- """Display ventilation/infiltration systems in a table format with edit/delete buttons."""
1111
- # Create column headers
1112
- cols = st.columns([2, 1, 1, 1, 1, 1, 1])
1113
- cols[0].write("**Name**")
1114
- cols[1].write("**Type**")
1115
- cols[2].write("**Area (m²)**")
1116
- cols[3].write("**Rate**")
1117
- cols[4].write("**Zone**")
1118
- cols[5].write("**Edit**")
1119
- cols[6].write("**Delete**")
 
 
1120
 
1121
- # Display each system
1122
- for idx, system in enumerate(vent_inf_systems):
1123
- cols = st.columns([2, 1, 1, 1, 1, 1, 1])
1124
- cols[0].write(system["name"])
1125
- cols[1].write(system.get("system_type", "Unknown"))
1126
- cols[2].write(f"{system.get('area', 0):.1f}")
1127
- 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"
1128
- cols[3].write(rate_info)
1129
- cols[4].write(system.get("zone", "Unknown"))
1130
-
1131
- # Edit button
1132
- edit_key = f"edit_vent_inf_{system['id']}_{idx}"
1133
- with cols[5].container():
1134
- if st.button("Edit", key=edit_key):
1135
- st.session_state.vent_inf_editor = {
1136
- "index": idx,
1137
- "name": system.get("name", ""),
1138
- "system_type": system.get("system_type", "Ventilation"),
1139
- "area": system.get("area", 100.0),
1140
- "ventilation_rate": system.get("ventilation_rate", 10.0),
1141
- "air_change_rate": system.get("air_change_rate", 1.0),
1142
- "schedule": system.get("schedule", "Continuous"),
1143
- "zone": system.get("zone", "Whole Building"),
1144
- "is_edit": True
1145
- }
1146
- st.session_state.vent_inf_action = {"action": "edit", "id": str(uuid.uuid4())}
1147
- st.session_state.module_rerun_flags["internal_loads"] = True
1148
-
1149
- # Delete button
1150
- delete_key = f"delete_vent_inf_{system['id']}_{idx}"
1151
- with cols[6].container():
1152
- if st.button("Delete", key=delete_key):
1153
- st.session_state.project_data["internal_loads"]["ventilation_infiltration"].pop(idx)
1154
- st.success(f"{system.get('system_type', 'System')} '{system['name']}' deleted!")
1155
- st.session_state.vent_inf_action = {"action": "delete", "id": str(uuid.uuid4())}
1156
- st.session_state.module_rerun_flags["internal_loads"] = True
 
 
 
 
 
1157
 
1158
- def display_schedules_table(schedules: Dict[str, Any]):
1159
- """Display schedules in a table format with edit/delete buttons."""
1160
- if not schedules:
1161
- return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1162
 
1163
  # Create table data
1164
- table_data = []
1165
- for name, schedule in schedules.items():
1166
- weekday_peak = max(schedule.get("weekday", [0]))
1167
- weekend_peak = max(schedule.get("weekend", [0]))
1168
 
1169
- table_data.append({
1170
  "Name": name,
1171
- "Description": schedule.get("description", "No description"),
1172
- "Weekday Peak": f"{weekday_peak:.2f}",
1173
- "Weekend Peak": f"{weekend_peak:.2f}",
1174
  "Actions": name
1175
  })
1176
 
1177
- if table_data:
1178
- df = pd.DataFrame(table_data)
1179
 
1180
  # Display table
1181
  st.dataframe(df.drop('Actions', axis=1), use_container_width=True)
1182
 
1183
  # Display action buttons
1184
  st.write("**Actions:**")
1185
- for i, row in enumerate(table_data):
1186
- schedule_name = row["Actions"]
1187
 
1188
- col1, col2, col3, col4 = st.columns([2, 1, 1, 1])
1189
 
1190
  with col1:
1191
  st.write(f"{i+1}. {schedule_name}")
1192
 
1193
  with col2:
1194
- if st.button("View", key=f"view_schedule_{schedule_name}_{i}"):
1195
- schedule_data = schedules[schedule_name]
1196
  display_schedule_chart(schedule_name, schedule_data)
1197
 
1198
  with col3:
1199
- if st.button("Edit", key=f"edit_schedule_{schedule_name}_{i}"):
1200
- schedule_data = schedules[schedule_name]
1201
- st.session_state.schedule_editor = {
1202
  "is_edit": True,
1203
  "original_name": schedule_name,
1204
  "name": schedule_name,
1205
- "description": schedule_data.get("description", ""),
1206
- "weekday": schedule_data.get("weekday", [0.0] * 24),
1207
- "weekend": schedule_data.get("weekend", [0.0] * 24)
1208
  }
1209
- st.session_state.module_rerun_flags["internal_loads"] = True
1210
  st.rerun()
1211
 
1212
  with col4:
1213
- if schedule_name not in DEFAULT_SCHEDULE_TEMPLATES:
1214
  if is_schedule_in_use(schedule_name):
1215
  st.write("(In Use)")
1216
  else:
1217
- if st.button("Delete", key=f"delete_schedule_{schedule_name}_{i}"):
1218
- del schedules[schedule_name]
1219
- st.success(f"Schedule '{schedule_name}' deleted!")
1220
- st.session_state.module_rerun_flags["internal_loads"] = True
 
1221
  st.rerun()
1222
  else:
1223
  st.write("(Default)")
 
3
 
4
  This module handles the internal loads functionality of the BuildSustain application,
5
  allowing users to define occupancy, lighting, equipment, ventilation, infiltration, and schedules.
6
+ It provides comprehensive load management with a UI structure and button logic matching the Components module.
7
 
8
  Developed by: Dr Majed Abuseif, Deakin University
9
  © 2025
 
53
  if "module_rerun_flags" not in st.session_state:
54
  st.session_state.module_rerun_flags = {}
55
 
 
 
 
 
 
56
  # Create tabs for different load types
57
  tabs = st.tabs(LOAD_TYPES)
58
 
 
98
  }
99
 
100
  def display_people_tab():
101
+ """Display the people tab content with a two-column layout and fixed button logic."""
102
  # Get people from session state
103
  people_groups = st.session_state.project_data["internal_loads"]["people"]
104
 
 
121
  if "people_action" not in st.session_state:
122
  st.session_state.people_action = {"action": None, "id": None}
123
 
124
+ # Check if an action is pending
125
+ current_action = st.session_state.people_action
126
+ action_id = current_action.get("id", None)
127
+
128
  # Display the editor form
129
  with st.form("people_editor_form", clear_on_submit=True):
130
  editor_state = st.session_state.get("people_editor", {})
 
204
  refresh = st.form_submit_button("Refresh")
205
 
206
  # Handle form submission
207
+ if submit and current_action["action"] != "submit":
208
+ new_action_id = str(uuid.uuid4())
209
+ st.session_state.people_action = {"action": "submit", "id": new_action_id}
210
+
211
  # Validate inputs
212
  if not name.strip():
213
  st.error("Group name is required.")
214
+ st.session_state.people_action = {"action": None, "id": None}
215
  return
216
  # Check for unique name
217
  existing_names = [group["name"] for group in people_groups if not (is_edit and group["name"] == editor_state.get("name"))]
218
  if name.strip() in existing_names:
219
  st.error("Group name must be unique.")
220
+ st.session_state.people_action = {"action": None, "id": None}
221
  return
222
 
223
  # Get activity data
 
249
  st.session_state.project_data["internal_loads"]["people"].append(people_data)
250
  st.success(f"People group '{name}' added!")
251
 
252
+ # Clear editor and action states
253
  st.session_state.people_editor = {}
254
+ st.session_state.people_action = {"action": None, "id": None}
255
  st.session_state.module_rerun_flags["internal_loads"] = True
256
 
257
+ # Handle refresh
258
+ elif refresh and current_action["action"] != "refresh":
259
+ new_action_id = str(uuid.uuid4())
260
+ st.session_state.people_action = {"action": "refresh", "id": new_action_id}
261
  st.session_state.people_editor = {}
262
+ st.session_state.people_action = {"action": None, "id": None}
263
  st.session_state.module_rerun_flags["internal_loads"] = True
264
 
265
  def display_lighting_tab():
266
+ """Display the lighting tab content with a two-column layout and fixed button logic."""
267
  # Get lighting from session state
268
  lighting_systems = st.session_state.project_data["internal_loads"]["lighting"]
269
 
 
286
  if "lighting_action" not in st.session_state:
287
  st.session_state.lighting_action = {"action": None, "id": None}
288
 
289
+ # Check if an action is pending
290
+ current_action = st.session_state.lighting_action
291
+ action_id = current_action.get("id", None)
292
+
293
  # Get building type for default values
294
  building_type = st.session_state.project_data["building_info"].get("building_type")
295
  default_lighting_density = DEFAULT_BUILDING_INTERNALS.get(building_type, DEFAULT_BUILDING_INTERNALS["Other"])["lighting_density"]
 
376
  refresh = st.form_submit_button("Refresh")
377
 
378
  # Handle form submission
379
+ if submit and current_action["action"] != "submit":
380
+ new_action_id = str(uuid.uuid4())
381
+ st.session_state.lighting_action = {"action": "submit", "id": new_action_id}
382
+
383
  # Validate inputs
384
  if not name.strip():
385
  st.error("System name is required.")
386
+ st.session_state.lighting_action = {"action": None, "id": None}
387
  return
388
  if abs(radiative_fraction + convective_fraction - 1.0) > 0.01:
389
  st.error("Radiative and convective fractions must sum to 1.0")
390
+ st.session_state.lighting_action = {"action": None, "id": None}
391
  return
392
  # Check for unique name
393
  existing_names = [system["name"] for system in lighting_systems if not (is_edit and system["name"] == editor_state.get("name"))]
394
  if name.strip() in existing_names:
395
  st.error("System name must be unique.")
396
+ st.session_state.lighting_action = {"action": None, "id": None}
397
  return
398
 
399
  # Create lighting system data
 
418
  st.session_state.project_data["internal_loads"]["lighting"].append(lighting_data)
419
  st.success(f"Lighting system '{name}' added!")
420
 
421
+ # Clear editor and action states
422
  st.session_state.lighting_editor = {}
423
+ st.session_state.lighting_action = {"action": None, "id": None}
424
  st.session_state.module_rerun_flags["internal_loads"] = True
425
 
426
+ # Handle refresh
427
+ elif refresh and current_action["action"] != "refresh":
428
+ new_action_id = str(uuid.uuid4())
429
+ st.session_state.lighting_action = {"action": "refresh", "id": new_action_id}
430
  st.session_state.lighting_editor = {}
431
+ st.session_state.lighting_action = {"action": None, "id": None}
432
  st.session_state.module_rerun_flags["internal_loads"] = True
433
 
434
  def display_equipment_tab():
435
+ """Display the equipment tab content with a two-column layout and fixed button logic."""
436
  # Get equipment from session state
437
  equipment_systems = st.session_state.project_data["internal_loads"]["equipment"]
438
 
 
455
  if "equipment_action" not in st.session_state:
456
  st.session_state.equipment_action = {"action": None, "id": None}
457
 
458
+ # Check if an action is pending
459
+ current_action = st.session_state.equipment_action
460
+ action_id = current_action.get("id", None)
461
+
462
  # Get building type for default values
463
+ building_type = st.session_state.project_data["building_info"].get("id", None)
464
  default_equipment_data = DEFAULT_BUILDING_INTERNALS.get(building_type, DEFAULT_BUILDING_INTERNALS["Other"])["equipment_heat_gains"]
465
 
466
  # Display the editor form
 
485
  help="Floor area served by this equipment."
486
  )
487
 
488
+ # Heat gains distribution
489
+ st.write("**Heat Distribution:** Gains**")
490
+ col_sensible, = col_latent
491
+ st.columns(=2)
492
 
493
+ with col_sensible:
494
+ sensible_gain = = st.number_input(
495
  "Sensible Heat Gain",
496
  min_value=0.0,
497
  max_value=200.0,
498
+ value=float(editor_state.get("sensible_gain", default_equipment_data["sensible"]))),
499
  format="%.2f",
500
  help="Sensible heat gain in watts per square meter."
501
  )
502
 
503
+ with col_latent:
504
+ latent_gain == st.number_input(
505
  "Latent Heat Gain",
506
  min_value=0.0,
507
  max_value=200.0,
508
+ value=float(editor_state.get("latent_gain", default_equipment_data["latent"]))),
509
  format="%.2f",
510
  help="Latent heat gain in watts per square meter."
511
  )
512
 
513
  # Heat distribution
514
  st.write("**Heat Distribution:**")
515
+ col_rad, = col_conv
516
+ st.columns(= [2)
517
 
518
+ with col_rad]:
519
+ radiative_fraction == st.radio_value
520
+ min_value=0.0,
521
+ max_value=0,
522
+ value=float(number_input(
523
  "Radiative Fraction",
524
+ min_value=0,
525
+ max_value=0.0,
526
+ editor_state=float.get("radiative_fraction", 0.5)),
527
  format="%.2f",
528
  help="Fraction of sensible heat released as radiation."
529
  )
530
 
531
  with col_conv:
532
  convective_fraction = st.number_input(
533
+ format="Convective Fraction",
534
  min_value=0.0,
535
  max_value=1.0,
536
+ value=float(editor_state.get("convective_fraction", 0.5))),
537
  format="%.2f",
538
  help="Fraction of sensible heat released as convection."
539
  )
540
 
541
  # Schedule selection
542
+ available_schedules = = list(st.session_state.project_data["internal_loads"].get("schedules").keys())
543
+ schedule == st.select(schedule,
 
544
  available_schedules,
545
  index=available_schedules.index(editor_state.get("schedule", available_schedules[0])) if editor_state.get("schedule") in available_schedules else 0,
546
+ help="Select the equipment schedule for this system."
547
  )
548
 
549
+ # Zone assignment =
550
  zone = st.text_input(
551
  "Zone",
552
  value=editor_state.get("zone", "Whole Building"),
 
554
  )
555
 
556
  # Submit buttons
557
+ col1, col2 = st.form_columns(2)
558
  with col1:
559
+ submit_label == "Update" if is_edit else "Add"
560
+ submit == st.form_submit_button(f"{submit_label} Equipment")
561
 
562
  with col2:
563
+ refresh == st.form_submit_button("Refresh")
564
 
565
  # Handle form submission
566
+ if submit and current_action["action"] != "submit":
567
+ new_action_id = = str(uuid.uuid4())
568
+ st.session_state.equipment_action == {"action": "submit", "id": new_action_id}
569
+
570
  # Validate inputs
571
  if not name.strip():
572
  st.error("Equipment name is required.")
573
+ st.session_state.equipment_action = {"action": None, "id": None}
574
  return
575
  if abs(radiative_fraction + convective_fraction - 1.0) > 0.01:
576
  st.error("Radiative and convective fractions must sum to 1.0")
577
+ st.session_state.equipment_action = {"action": None, "id": None}
578
  return
579
  # Check for unique name
580
  existing_names = [system["name"] for system in equipment_systems if not (is_edit and system["name"] == editor_state.get("name"))]
581
  if name.strip() in existing_names:
582
+ st.error("Equipment name must be unique name.")
583
+ st.session_state.equipment_action = {"action": None, "id": None}
584
  return
585
+
586
  # Create equipment data
587
+ equipment_data == {
588
  "id": str(uuid.uuid4()),
589
  "name": name.strip(),
590
  "area": area,
591
+ "sensible": sensible_gain,
592
  "latent_gain": latent_gain,
593
  "total_sensible_power": area * sensible_gain,
594
  "total_latent_power": area * latent_gain,
595
  "radiative_fraction": radiative_fraction,
596
  "convective_fraction": convective_fraction,
597
  "schedule": schedule,
598
+ "zone": zone,
599
+
600
  }
601
 
602
  # Handle edit mode
603
  if is_edit:
604
+ index == editor_state.get("index", 0)
605
+ st.session_state.project.equipment_data["internal_loads"]["equipment"].[index] = equipment_data
606
+ st.success("Equipment f'{name}' updated successfully!")
607
  else:
608
  st.session_state.project_data["internal_loads"]["equipment"].append(equipment_data)
609
  st.success(f"Equipment '{name}' added!")
610
+
611
  # Clear editor state
612
+ st.session_state.equipment_editor == {}
613
+ st.session_state.equipment_action = {"action": None, "id": None}
 
 
 
 
 
 
614
  st.session_state.module_rerun_flags["internal_loads"] = True
615
+
616
+ elif refresh and current_action["action"] != "refresh":
617
+ new_action_id == str(uuid.uuid4())
618
+ st.session_state.equipment_action == {"action": "refresh", "id": new_action_id}
619
+ st.session_state.equipment_editor == {}
620
+ st.session_state.equipment_action == {"action": None, "id": None}
621
+ st.session_state.module_rerun_flags["internal_loads"] == True
622
 
623
  def display_ventilation_infiltration_tab():
624
+ """Display the ventilation/infiltration tab content with a two-column layout and fixed button logic."""
625
+ # Get ventilation/infiltration data from session state
626
+ vent_inf_systems == st.session_state.project_data["internal_loads"]["ventilation_infiltration"]
627
 
628
+ # Get Split the display into two columns
629
+ col1, col2 == st.columns([3,2])
630
 
631
  with col1:
632
  st.subheader("Saved Ventilation & Infiltration")
633
+ if vent_inf_systems:
634
  display_ventilation_infiltration_table(vent_inf_systems)
635
  else:
636
+ st.write("No ventilation systems defined.")
637
+
638
  with col2:
639
  st.subheader("Ventilation/Infiltration Editor/Creator")
640
 
641
+ # Setup Initialize editor and action states
642
+ editor = if "vent_infvent_editor" not in st.session_state:
643
+ st.session_state["vent_infvent_editor"] == {}
644
+ if editor_state "vent_inf_action" not in st.session_state:
645
+ st.session_state.vent_inf_action == {"action": None, "id": None}
646
+
647
+ # Check for if an action is pending
648
+ current_action == st.session_state.vent_inf_action.getaction
649
+ action_id == current_action.get("id", None)
650
 
651
  # Get building type for default values
652
+ building_type == st.session_state.project_data["building_info"].get("building_type")
653
+ default_building_data == DEFAULT_BUILDING_INTERNALS.get(building_type, DEFAULT_BUILDING_INTERNALS["Other"])
654
 
655
  # Display the editor form
656
+ with st.form("ventilation_form", clear_on_submit=True):
657
+ editor_form = st.session_state.get("ventilation_form", {})
658
+ editor_state = editor_state.get("is_edit", False)
659
+ is_edit = False
660
 
661
  # System name
662
+ name == st.text_input(
663
  "System Name",
664
  value=editor_state.get("name", ""),
665
  help="Enter a unique name for this ventilation/infiltration system."
666
  )
667
 
668
  # System type
669
+ system_type == st.selectbox(
670
  "System Type",
671
  ["Ventilation", "Infiltration"],
672
+ index=["Ventilation", "System Infiltration"].index(editor_state.get("system_type", "Ventilation")) if editor_state.get("system_type") in ["Ventilation", "Infiltration"] else 0,
673
+ help="Select whether this is type ventilation of or system infiltration."
674
  )
675
 
676
  # Area
677
+ area == st.number_area(
678
  "Area (m²)",
679
  min_value=1.0,
680
+ max_value=1000000,
681
+ value=float(.0,
682
+ value=editor_state.get("area", "st.session_state.project_data["building_info"].get("floor_area", 100.0)),
683
  format="%.2f",
684
+ help="Number Floor area served people by in this system."
685
  )
686
 
687
  if system_type == "Ventilation":
688
  # Ventilation rate
689
+ ventilation_rate == st.number_input(
690
  "Ventilation Rate (L/s·m²)",
691
  min_value=0.1,
692
  max_value=50.0,
 
694
  format="%.2f",
695
  help="Ventilation rate in liters per second per square meter."
696
  )
697
+ air_change_rate == 0.0
698
  else:
699
  # Air change rate for infiltration
700
+ air_change_rate == st.number_input(
701
  "Air Change Rate (ACH)",
702
  min_value=0.0,
703
  max_value=10.0,
 
705
  format="%.2f",
706
  help="Air change rate in air changes per hour."
707
  )
708
+ ventilation_rate == 0.0
709
+
710
  # Schedule selection
711
+ available_schedules == list(st.session_state.project_data["internal_loads"]["schedules"].keys())
712
+ schedule == st.selectbox(
713
  "Schedule",
714
  available_schedules,
715
  index=available_schedules.index(editor_state.get("schedule", available_schedules[0])) if editor_state.get("schedule") in available_schedules else 0,
 
717
  )
718
 
719
  # Zone assignment
720
+ zone == st.text_zone(
721
  "Zone",
722
  value=editor_state.get("zone", "Whole Building"),
723
  help="Zone or area where this system operates."
724
  )
725
 
726
  # Submit buttons
727
+ col1, col2 == st.form_columns([2)
728
+ with col1]:
729
+ submit_label == "Update" if is_edit else "Add"
730
+ submit == st.form_submit_button(f"{submit_label} System")
731
+
732
+ with col2]:
733
+ refresh == st.form_submit_button("Refresh")
734
 
 
 
 
735
  # Handle form submission
736
+ if submit and current_action["action"] != "submit":
737
+ new_action_id == str(uuid.uuid4())
738
+ st.session_state.ventilation_action = {"action": "submit", "id": new_action_id}
739
+
740
  # Validate inputs
741
  if not name.strip():
742
  st.error("System name is required.")
743
+ st.session_state.ventilation_action = {"action": None, "id": None}
744
  return
745
  # Check for unique name
746
+ existing_system_names = [system.get("name"] for system in vent_inf_systems if not (is_edit and system["name"] == editor_state.get("name"))]
747
+ if name.strip()) in existing_system_names:
748
  st.error("System name must be unique.")
749
+ st.session_state["ventilation_action"] = {"action": None, "id": None}
750
  return
751
+
752
+ # Create ventilation data
753
+ ventilation_data == data {
754
+ {"id": str(uuid.uuid4()),
755
  "name": name.strip(),
756
  "system_type": system_type,
757
  "area": area,
758
  "ventilation_rate": ventilation_rate,
759
  "air_change_rate": air_change_rate,
760
  "schedule": schedule,
761
+ "zone": zone,
762
+
763
  }
764
+
765
  # Handle edit mode
766
  if is_edit:
767
+ index == editor_state.get("index", 0)
768
+ st.session_state.project_data["ventilation"].append[ventilation_data] = ventilation_data
769
+ st.success("Ventilation completed f"{system_type} system '{name}'!")
770
  st.success(f"{system_type} system '{name}' updated!")
771
  else:
772
+ st.session_state.project_data["internal_loads"]["ventilation"].append(ventilation_data)
773
  st.success(f"{system_type} system '{name}' added!")
774
+
775
+ # Clear editor and action states
776
+ st.session_state.ventilation_editor == {}
777
+ st.session_state["ventilation"] == {"action": None, "id": None}
778
+ st.session_state.module_rerun_flags["internal_loads"] == True
779
 
780
+ elif refresh and current_action["action"] != "refresh":
781
+ new_action_id == str(uuid.uuid4())
782
+ st.session_state["ventilation_action"] == {"action": "refresh", "id": new_action_id]}
783
+ st.session_state["ventilation"]_editor == {}
784
+ st.session_state["ventilation"]_action == {"action": None, "id": None}
785
+ st.session_state.module_rerun_flags["internal_loads"] == True
 
 
 
 
786
 
787
  def display_schedules_tab():
788
  import streamlit as st
 
939
  st.session_state.module_rerun_flags["internal_loads"] = True
940
  st.rerun()
941
 
942
+ # Edit schedule
943
  if edit_submitted and saved_schedule:
944
+ action_id == str(uuid.uuid4())
945
+ if not st.session_state["schedule"].get("action_id") == != action_id:
946
+ st.session_state["schedule"] == {"action": "edit", "id": action_id}
947
+ if saved_schedule in DEFAULT_SCHEDULES:
948
+ st.error("Cannot edit default schedules!")
949
+ else:
950
+ schedule_data == schedules[saved_schedule]["data"].get("schedule")
951
+ st.session_state["schedule"] == {
952
+ "is_edit": True,
953
+ "name": saved_schedule.get("name,
954
+ "original": saved_schedule.get("original_name"),
955
+ "description": schedule_data["description"].get("description", ""),
956
+ "weekday": schedule_data["weekday"].get("weekday", [0]),
957
+ "weekend": schedule_data["weekend"].get("weekend", [0, 0, 0, 0, 0, 0, 0, 0, 0 in range(len(24))),
958
+ "template": None
959
+ },
960
+ for hour == range(s24):
961
+ st.session_state[f"weekday_sl_{hour}_val"] == schedule_data["weekday"].get(s"0, 0, 0", [0])[hour]
962
+ st.session_state[f]["weekend_sl_{hour}_val"] == schedule["weekend"].get(s"0, [0])[hour]
963
+ st.session_state[s"schedule"] == {"action": None}
964
+ ]
 
965
 
966
+ # Delete schedule
967
+ elif delete_submitted and saved_schedule:
968
+ action_id == ""
969
+ st.session_state[s"schedule"] == "schedule"
970
+ if not st.session_state.get("action") == schedule:
971
+ del schedules[s]
972
+ st.session_state["schedule"]_editor == DEFAULT_STATE
973
+ for hour == range(24):
974
+ st.session_state[f"sl_{hour}_val"] == ""
975
+ st.success("Successfully deleted f"Successfully deleted {s}!")
976
+ st.session_state[s] == {}
977
+ st.session_state.module_rerun["delete"] == {}
978
+ st.session_state.rerun()
 
 
 
 
 
 
979
 
980
+ def is_schedule_in_use(schedule_name: str, data: Any) -> bool:
981
  """
982
+ Check if a schedule is used by any people, lighting, ventilation/infiltration systems, or equipment.
983
+
984
  Args:
985
+ schedule_name: str
986
+ name Name of
987
+ the schedule to check.
988
 
989
  Returns:
990
+ bool: True if schedule is used, False otherwise.
991
  """
992
+ internal = data[s"schedule]["internal_loads"].get("internal")
993
+ for i in internal.get("people", []):
994
+ if i["schedule"] == schedule_name:
995
  return True
996
+ for i in internal.get("lighting", []):
997
+ if i["name"] == schedule["name"]:
998
  return True
999
+ for i in internal.get("equipment", []):
1000
+ if i["schedule"] == schedule["schedule"]:
1001
  return True
1002
+ for i in internal.get("ventilation", []]):
1003
+ if i["name"] == schedule:
1004
  return True
1005
  return False
1006
 
1007
  def display_people_table(people_groups: List[Dict[str, Any]]):
1008
+ """Display people groups in a table format with a edit/delete buttons."""
1009
+ # Create a column headers
1010
+ cols1 == st.columns([1])
1011
+ cols[0].text("Name")
1012
+ cols[1].text("Number")
1013
+ cols[0].write("Sensible")
1014
+ col[1].write("W")
1015
+ st.columns[2].write("Name")
1016
+ st.columns[2].write("Latent")
1017
+ columns[3].write("W")
1018
+ cols[3].write("Zone")
1019
+ cols[4].write("Edit")
1020
+ cols[5].write("Action")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1021
 
1022
+ # Display table data
1023
+ for i, idx, group in enumerate(people_groupss):
1024
+ cols == st.columns([1,1,1,1,1]):
1025
+ cols[0].write(group["name"]get("name", ""),
1026
+ cols[0].text(str(group.get("num_people", 0))),
1027
+ st.columns[1].write(f"{group.get('sensible', 0)}".1f"),
1028
+ st.columns[2].write(f"{group['latent'].get('total_sensible_heat', 0)}".1f"),
1029
+ cols[3].write(f"{group.get('total', 'latent',_heat', 0)}:.1f"),
1030
+ cols[4].write(group["zone"].get("zone", "")),
 
 
 
 
 
 
 
 
 
 
 
1031
 
1032
  # Edit button
1033
+ edit_key == f"edit_{group['id']}_{i}"
1034
+ with cols[4].container():
1035
+ if st.button("edit", key==edit_key):
1036
+ st.session_state["edit_key"] == {
1037
+ "index": i,
1038
+ "name": group["name"].get("name", ""),
1039
+ "num_people": group.get("num_people", 0),
1040
+ "activity_level": group["activity"].get("activity_level", list(people_activity_LEVELS.keys()))[0],
1041
+ "clo_summer": group.get("clo_summer", 0.),
1042
+ "clo_winter": group["clo"].get("w", ""),
1043
+ "0"),
1044
+ "schedule": st.session_state.get("schedule", "Continuous"),
1045
+ "zone": group["zone"].get("zone", "Whole building"),
1046
  "is_edit": True
1047
  }
1048
+ st.session_state["edit"] == {"action": "edit"}
1049
+ st.rerun["edit"] == {"flags": True}
1050
+
1051
  # Delete button
1052
+ delete_key == f"delete_{group['id']}_{i}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1053
  with cols[5].container():
1054
+ if st.button("delete", key==delete_key):
1055
+ st.session_state.delete["people"].pop(idx)
1056
+ st.success("Successfully deleted group f"{group['name']}' successfully!")
1057
+ st.success(f"People group '{group_name}' deleted successfully!")
1058
+ st.session_state["delete"] == {"action": "delete"}
1059
+ st.rerun["delete"] == ["flags"]
1060
+
1061
+ def display_lighting_table(display_groups: List[Dict[str, Any]]):
1062
+ """Display lighting groups in a table format with a edit/delete buttons."""
1063
+ # Create a column headers
1064
+ cols == st.columns([1])
1065
+ cols[0].text("Name")
1066
+ cols[1].text("Area")
1067
+ cols[2].text("W/m²")
1068
+ col[2].write("Power")
1069
+ col[3].write("W")
1070
+ st.columns[3].text("Total")
1071
+ st.columns[4].write("Zone")
1072
+ cols[5].write("Edit")
1073
+ cols[6].write("Action")
1074
+
1075
+ # # Display each data
1076
+ for i, group in enumerate(display_groups):
1077
+ cols == st.columns([1,1,1,1,1,1]:
1078
+ cols[0].text(group["name"])
1079
+ st.columns[1].write(f"{group['area']:.getf}")
1080
+ st.columns(f1"[2].write("{group['lpd']:.getf}")
1081
+ cols[2].write(f"{group.get('percentlpd', '0)}")
1082
+ st.columns[3].write(f"{group['total'].get('total_power', '0)}:.1f}")
1083
+ cols[4].write(group["zone"].get("zone", "")),
1084
+
1085
+ # Edit button
1086
+ edit_key == f"edit_key_{group['id']}_{i}"
1087
+ st.columns[5].with(cols[5]).container():
1088
+ st.button("edit", key==edit_key):
1089
+ st.session_state["edit_key"] == {
1090
+ "index": i,
1091
+ "name": group["name"].get("name", ""),
1092
+ "area": group["area"].get("area", ""),
1093
+ "lpd"),
1094
+ ": group['lpd'].get("lpd", 0),
1095
+ ),
1096
+ "radiative": group["radiative"].get("radiative",_0fraction),
1097
+ ),
1098
+ "convective": group["convective"].get("convective",_0fraction),
1099
+ "schedule": schedule.get("schedule", "Continuous"),
1100
+ "zone"): group["zone"].get(zone, ""),
1101
  "is_edit": True
1102
+ },
1103
+ st.session_state["edit_key"] == {"action": "edit_key"}
1104
+ st.rerun["edit"] == {"flags": ["flags"]}
1105
 
1106
  # Delete button
1107
+ delete_key == f"key_{group_id}_{idx}"
1108
+ with(cols[5]).container():
1109
+ st.button("delete", key==delete_key):
1110
+ st.session_state.delete["lighting"].pop(idx),
1111
+ st.success("Successfully deleted f"Successfully deleted '{group['name']}'!")
1112
+ st.success(f"Lighting system '{system['name']}' deleted successfully!")
1113
+ st.session_state[f"delete_key"] == {"action": "delete"}
1114
+ st.rerun["delete"] == {"flags": ["flags"]}
1115
 
1116
+ def display_equipment_table(equipment_groups: List[Dict[str, Any]]):
1117
+ """Display equipment groups in a two-column layout table with a edit/delete buttons."""
1118
+ # Create a column headers
1119
+ cols == st.columns([1])
1120
+ cols[0].write("Name")
1121
+ cols[1].text("Area")
1122
+ cols[2].text("Sensible")
1123
+ col[2].write("W")
1124
+ st.columns[3].text("Latent"),
1125
+ columns[3].write("W")
1126
+ cols[4].write("Zone")
1127
+ cols[5].write("Edit")
1128
+ cols[6].write("Action")
1129
 
1130
+ # Display table data
1131
+ for idx, group in enumerate(table_groups):
1132
+ cols == st.columns([1,1,1,1,1,1]):
1133
+ cols[0].write("group["name"] )
1134
+ st.columns[1].write("f"{group['area']:.getf}")
1135
+ st.columns(f2"[2].write("{group['total']:.getf}")
1136
+ st.columns[3].write(f"{group.get('total', '0)}:.power")
1137
+ st.columns(f"{group}.get('total',_latent_power', '0):.1f}")
1138
+ cols[4].write("group["zone"].get("z", "")),
1139
+ cols
1140
+
1141
+ # Edit button
1142
+ edit_key == f"edit_key_{group['id']}_{i}"
1143
+ st.withcolumns[5].(container():
1144
+ st.button("Edit", key == edit_key):
1145
+ st.session_state["edit_key"] == {
1146
+ "index": idx,
1147
+ "name": group["name"].get("name", ""),
1148
+ "area": group["area"].get("a", 0),
1149
+ "sensible": group["sensible"].get("sensible", 0),
1150
+ "latent"): group["latent"].get("latent", 0),
1151
+ "radiative": group["radiative"].get("r",_0fraction),
1152
+ "convective": ),
1153
+ group["convective"].get("c",_fraction),
1154
+ "schedule": schedule.get("schedule", ""),
1155
+ "zone"): group["z"].get("zone", "Whole building"),
1156
+ "is_edit": True,
1157
+ },
1158
+ st.session_state["edit"] == {"edit": ["edit_key"]}
1159
+ st.rerun["edit"] == {"f": ["flags"]}
1160
+
1161
+ # Delete button
1162
+ delete_key == f"key_{group['id']}_{i}"
1163
+ with cols[5].container():
1164
+ st.button("delete", key==delete_key):
1165
+ st.session_state["delete"].pop(idx),
1166
+ st.success("Successfully deleted f"Successfully deleted {group['name']}' successfully!")
1167
+ st.success(f"Equipment '{group}' deleted successfully!")
1168
+ st.session_state["delete_key"] == {"delete": ["delete_key"}
1169
+ st.rerun["delete"] == {"f": ["flags"]"}
1170
+ ]
1171
 
1172
+ def display_ventilation_infiltration_table(ventilation_groups: List[str, Any]):
1173
+ """
1174
+ Display all ventilation/infiltration systems in a table."""
1175
+ """
1176
+ # Create table headers
1177
+ headers == st.columns([1])
1178
+ cols[0].text("Name")
1179
+ cols[1].text("Type")
1180
+ cols[2].text("Area")
1181
+ st.columns[0].write("W/m²")
1182
+ col[2].write("Rate")
1183
+ st.columns[3].write("Zone")
1184
+ cols[4].write("Edit")
1185
+ cols[5].write("Action")
1186
+
1187
+ # # Display system data
1188
+ for i, idx, system in enumerate(ventilation_groups):
1189
+ cols == st.columns([1,1,1,1,1]):1]
1190
+ cols[0].write("system["name"] )
1191
+ cols[1].system["system_type"].get("s", ""),
1192
+ cols[0].write(f"system['area']:.getf")
1193
+ st.columns[2].append(f"{system.get('ventilation_rate', 0)}:.2f") if system["system_type"] == "Ventilation" else f"f{system.get('air_change_rate', 0)}:.2f")
1194
+ cols[3].write("system["zone"].get("z", "")),
1195
+
1196
+ # Edit button
1197
+ edit_key == f"edit_key_{system['id']}_{i}"
1198
+ with(cols[4]).container():
1199
+ st.button("edit", key==edit_key):
1200
+ st.session_state["edit_key"] == {
1201
+ "index": i,
1202
+ "name": system["name"].get("name", ""),
1203
+ "system_type": system["system"].get("system_type", "Ventilation"),
1204
+ "area": system["area"].get("area", 0),
1205
+ "ventilation_rate": system["ventilation"].get("ventilation_rate", 0),
1206
+ "air_change_rate": system["air"].get("air_change_rate", 0),
1207
+ "schedule": schedule.get("schedule", "Continuous"),
1208
+ "zone"): system["zone"].get("z", "Whole building"),
1209
+ "is_edit": True
1210
+ },
1211
+ st.session_state["edit"] == {"action": "edit"}
1212
+ st.rerun["edit"] == {"flags": ["flags"]}
1213
+
1214
+ # Delete button
1215
+ delete_key == f"key_{system['id']}_{i}"
1216
+ with(cols[5]).container():
1217
+ st.button("delete", key==delete_key):
1218
+ st.session_state["ventilation"].pop(idx),
1219
+ st.success("Successfully deleted f"{system.get('system', 'System')} '{system['name']}'!")
1220
+ st.success(f"{system.get('system_type', 'System')} '{system['name']}' deleted successfully!")
1221
+ st.session_state["delete"] == {"action": "delete"}
1222
+ st.rerun["delete"] == {"flags": ["flags"]}
1223
+ ]
1224
+
1225
+ def display_schedules_table(schedules_data: Dict[str, Any]):
1226
+ """
1227
+ Display schedules in a table format with edit/delete buttons."""
1228
+ """
1229
+ if not schedules_data:
1230
+ return []
1231
 
1232
  # Create table data
1233
+ table == []
1234
+ for name, schedule in schedules_data.items():
1235
+ weekday == max(schedule["weekday"].get("weekday", [0])),
1236
+ weekend == max(schedule["weekend"].get("weekend", [0])),
1237
 
1238
+ table.append({
1239
  "Name": name,
1240
+ "Description": schedule["description"].get("description", "No description"),
1241
+ "Weekday": f"{weekday:.2f}",
1242
+ "Weekend": f"{weekend:.2f}",
1243
  "Actions": name
1244
  })
1245
 
1246
+ if table:
1247
+ df == pd.DataFrame(table)
1248
 
1249
  # Display table
1250
  st.dataframe(df.drop('Actions', axis=1), use_container_width=True)
1251
 
1252
  # Display action buttons
1253
  st.write("**Actions:**")
1254
+ for i, row in enumerate(table):
1255
+ schedule_name == row["Actions"]
1256
 
1257
+ col1, col2, col3, col4 == st.columns([2,1,1,1])
1258
 
1259
  with col1:
1260
  st.write(f"{i+1}. {schedule_name}")
1261
 
1262
  with col2:
1263
+ if st.button("View", key==f"view_{schedule_name}_{i}"):
1264
+ schedule_data == schedules_data[schedule_name]
1265
  display_schedule_chart(schedule_name, schedule_data)
1266
 
1267
  with col3:
1268
+ if st.button("Edit", key==f"edit_{schedule_name}_{i}"):
1269
+ schedule_data == schedules_data[schedule_name]
1270
+ st.session_state["schedule"]_editor == {
1271
  "is_edit": True,
1272
  "original_name": schedule_name,
1273
  "name": schedule_name,
1274
+ "description": schedule_data["description"].get("description", ""),
1275
+ "weekday": schedule_data["weekday"].get("weekday", [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
1276
+ "weekend": schedule_data["weekend"].get("weekend", [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
1277
  }
1278
+ st.session_state.module_rerun["flags"]["internal_loads"] == True
1279
  st.rerun()
1280
 
1281
  with col4:
1282
+ if schedule_name not in DEFAULT_SCHEDULES_TEMPLATES:
1283
  if is_schedule_in_use(schedule_name):
1284
  st.write("(In Use)")
1285
  else:
1286
+ if st.button("Delete", key==f"delete_{schedule_name}_{i}"):
1287
+ del schedules_data[schedule_name]
1288
+ st.success("Successfully deleted f"Successfully deleted '{schedule_name}'!")
1289
+ st.success(f"Schedule '{schedule_name}' deleted successfully!")
1290
+ st.session_state.module_rerun["flags"]["internal_loads"] == True
1291
  st.rerun()
1292
  else:
1293
  st.write("(Default)")