mabuseif commited on
Commit
23f3437
verified
1 Parent(s): 3670292

Update app/internal_loads.py

Browse files
Files changed (1) hide show
  1. app/internal_loads.py +487 -466
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 and button logic matching the Components module.
7
 
8
  Developed by: Dr Majed Abuseif, Deakin University
9
  漏 2025
@@ -53,6 +53,11 @@ def display_internal_loads_page():
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
 
@@ -75,11 +80,13 @@ def display_internal_loads_page():
75
  with col1:
76
  if st.button("Back to Building Components", key="back_to_components"):
77
  st.session_state.current_page = "Building Components"
 
78
  st.rerun()
79
 
80
  with col2:
81
  if st.button("Continue to HVAC Loads", key="continue_to_hvac_loads"):
82
  st.session_state.current_page = "HVAC Loads"
 
83
  st.rerun()
84
 
85
  def initialize_internal_loads():
@@ -213,7 +220,6 @@ def display_people_tab():
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.")
@@ -254,7 +260,6 @@ def display_people_tab():
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}
@@ -389,7 +394,6 @@ def display_lighting_tab():
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.")
@@ -423,7 +427,6 @@ def display_lighting_tab():
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}
@@ -460,7 +463,7 @@ def display_equipment_tab():
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,13 +488,12 @@ def display_equipment_tab():
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,
@@ -500,8 +502,8 @@ def display_equipment_tab():
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,
@@ -512,25 +514,21 @@ def display_equipment_tab():
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)),
@@ -539,14 +537,15 @@ def display_equipment_tab():
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,18 +553,18 @@ def display_equipment_tab():
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():
@@ -576,117 +575,112 @@ def display_equipment_tab():
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,10 +688,9 @@ def display_ventilation_infiltration_tab():
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,11 +698,11 @@ def display_ventilation_infiltration_tab():
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,79 +710,74 @@ def display_ventilation_infiltration_tab():
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
789
- import uuid
790
-
791
  st.subheader("Schedules Editor")
792
-
793
  DEFAULT_STATE = {
794
  "name": "",
795
  "description": "",
@@ -799,16 +787,20 @@ def display_schedules_tab():
799
  "is_edit": False,
800
  "original_name": ""
801
  }
802
-
803
  # Initialize schedule_editor and schedule_action if not present
804
  if "schedule_editor" not in st.session_state:
805
  st.session_state.schedule_editor = DEFAULT_STATE.copy()
806
  if "schedule_action" not in st.session_state:
807
  st.session_state.schedule_action = {"action": None, "id": None}
808
-
 
 
 
 
809
  editor_state = st.session_state.schedule_editor
810
  schedules = st.session_state.project_data["internal_loads"]["schedules"]
811
-
812
  # Handle template change
813
  template_options = list(DEFAULT_SCHEDULE_TEMPLATES.keys())
814
  selected_template = st.selectbox(
@@ -818,9 +810,11 @@ def display_schedules_tab():
818
  if editor_state.get("template") in template_options else 0,
819
  help="Choose a base schedule to prefill values."
820
  )
821
-
822
  # Update sliders only if template changes and not in edit mode
823
- if selected_template != editor_state.get("template", "None") and not editor_state.get("is_edit"):
 
 
824
  st.session_state.schedule_editor["template"] = selected_template
825
  if selected_template != "None":
826
  tpl = DEFAULT_SCHEDULE_TEMPLATES[selected_template]
@@ -833,14 +827,15 @@ def display_schedules_tab():
833
  for hour in range(24):
834
  st.session_state[f"weekday_slider_{hour}_value"] = 0.0
835
  st.session_state[f"weekend_slider_{hour}_value"] = 0.0
 
836
  st.session_state.module_rerun_flags["internal_loads"] = True
837
  st.rerun()
838
-
839
  # UI FORM for name/description and actions
840
- with st.form("schedule_form"):
841
  name = st.text_input("Schedule Name", value=editor_state.get("name", ""))
842
  description = st.text_area("Description", value=editor_state.get("description", ""))
843
-
844
  # SLIDERS LAYOUT
845
  st.markdown("### Schedule Sliders")
846
  col_hour, col_wd, col_we = st.columns([0.4, 2.0, 2.0])
@@ -850,7 +845,7 @@ def display_schedules_tab():
850
  st.markdown("<div style='text-align:center; font-weight:bold; font-size:18px;'>Weekday</div>", unsafe_allow_html=True)
851
  with col_we:
852
  st.markdown("<div style='text-align:center; font-weight:bold; font-size:18px;'>Weekend</div>", unsafe_allow_html=True)
853
-
854
  hide_elements = """
855
  <style>
856
  div[data-testid="stSliderTickBarMin"],
@@ -860,10 +855,10 @@ def display_schedules_tab():
860
  </style>
861
  """
862
  st.markdown(hide_elements, unsafe_allow_html=True)
863
-
864
  weekday_values = []
865
  weekend_values = []
866
-
867
  for hour in range(24):
868
  col_hour, col_wd, col_we = st.columns([0.4, 2.0, 2.0])
869
  with col_hour:
@@ -892,7 +887,7 @@ def display_schedules_tab():
892
  format=None
893
  )
894
  weekend_values.append(val)
895
-
896
  # Action Buttons
897
  col1, col2, col3 = st.columns(3)
898
  with col1:
@@ -902,7 +897,7 @@ def display_schedules_tab():
902
  edit_submitted = st.form_submit_button("Edit Selected Schedule")
903
  with col3:
904
  delete_submitted = st.form_submit_button("Delete Selected Schedule")
905
-
906
  # Saved Schedules Selector
907
  st.markdown("### Saved Schedules")
908
  saved_schedule = st.selectbox(
@@ -911,383 +906,409 @@ def display_schedules_tab():
911
  index=list(schedules.keys()).index(editor_state.get("name", list(schedules.keys())[0])) if editor_state.get("name") in schedules and schedules else 0,
912
  help="Select a saved schedule to edit or delete."
913
  )
914
-
915
  # Save logic
916
- if submitted:
917
- action_id = str(uuid.uuid4())
918
- if st.session_state.schedule_action.get("id") != action_id:
919
- st.session_state.schedule_action = {"action": "save", "id": action_id}
920
- if not name.strip():
921
- st.error("Schedule name is required.")
922
- elif name in schedules and not editor_state.get("is_edit"):
923
- st.error("A schedule with this name already exists.")
924
- else:
925
- schedules[name] = {
926
- "description": description,
927
- "weekday": weekday_values,
928
- "weekend": weekend_values
929
- }
930
- if editor_state.get("is_edit") and editor_state.get("original_name") and editor_state.get("original_name") != name:
931
- if editor_state["original_name"] in schedules:
932
- del schedules[editor_state["original_name"]]
933
- st.session_state.schedule_editor = DEFAULT_STATE.copy()
934
- for hour in range(24):
935
- st.session_state[f"weekday_slider_{hour}_value"] = 0.0
936
- st.session_state[f"weekend_slider_{hour}_value"] = 0.0
937
- st.success(f"Schedule '{name}' saved successfully.")
938
- st.session_state.schedule_action = {"action": None, "id": None}
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)")
 
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
  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
 
 
80
  with col1:
81
  if st.button("Back to Building Components", key="back_to_components"):
82
  st.session_state.current_page = "Building Components"
83
+ st.session_state.module_rerun_flags["internal_loads"] = True
84
  st.rerun()
85
 
86
  with col2:
87
  if st.button("Continue to HVAC Loads", key="continue_to_hvac_loads"):
88
  st.session_state.current_page = "HVAC Loads"
89
+ st.session_state.module_rerun_flags["internal_loads"] = True
90
  st.rerun()
91
 
92
  def initialize_internal_loads():
 
220
  st.error("Group name is required.")
221
  st.session_state.people_action = {"action": None, "id": None}
222
  return
 
223
  existing_names = [group["name"] for group in people_groups if not (is_edit and group["name"] == editor_state.get("name"))]
224
  if name.strip() in existing_names:
225
  st.error("Group name must be unique.")
 
260
  st.session_state.people_action = {"action": None, "id": None}
261
  st.session_state.module_rerun_flags["internal_loads"] = True
262
 
 
263
  elif refresh and current_action["action"] != "refresh":
264
  new_action_id = str(uuid.uuid4())
265
  st.session_state.people_action = {"action": "refresh", "id": new_action_id}
 
394
  st.error("Radiative and convective fractions must sum to 1.0")
395
  st.session_state.lighting_action = {"action": None, "id": None}
396
  return
 
397
  existing_names = [system["name"] for system in lighting_systems if not (is_edit and system["name"] == editor_state.get("name"))]
398
  if name.strip() in existing_names:
399
  st.error("System name must be unique.")
 
427
  st.session_state.lighting_action = {"action": None, "id": None}
428
  st.session_state.module_rerun_flags["internal_loads"] = True
429
 
 
430
  elif refresh and current_action["action"] != "refresh":
431
  new_action_id = str(uuid.uuid4())
432
  st.session_state.lighting_action = {"action": "refresh", "id": new_action_id}
 
463
  action_id = current_action.get("id", None)
464
 
465
  # Get building type for default values
466
+ building_type = st.session_state.project_data["building_info"].get("building_type")
467
  default_equipment_data = DEFAULT_BUILDING_INTERNALS.get(building_type, DEFAULT_BUILDING_INTERNALS["Other"])["equipment_heat_gains"]
468
 
469
  # Display the editor form
 
488
  help="Floor area served by this equipment."
489
  )
490
 
491
+ # Heat gains
492
+ st.write("**Heat Gains (W/m虏):**")
493
+ col_sens, col_lat = st.columns(2)
 
494
 
495
+ with col_sens:
496
+ sensible_gain = st.number_input(
497
  "Sensible Heat Gain",
498
  min_value=0.0,
499
  max_value=200.0,
 
502
  help="Sensible heat gain in watts per square meter."
503
  )
504
 
505
+ with col_lat:
506
+ latent_gain = st.number_input(
507
  "Latent Heat Gain",
508
  min_value=0.0,
509
  max_value=200.0,
 
514
 
515
  # Heat distribution
516
  st.write("**Heat Distribution:**")
517
+ col_rad, col_conv = st.columns(2)
 
518
 
519
+ with col_rad:
520
+ radiative_fraction = st.number_input(
 
 
 
521
  "Radiative Fraction",
522
+ min_value=0.0,
523
+ max_value=1.0,
524
+ value=float(editor_state.get("radiative_fraction", 0.5)),
525
  format="%.2f",
526
  help="Fraction of sensible heat released as radiation."
527
  )
528
 
529
  with col_conv:
530
  convective_fraction = st.number_input(
531
+ "Convective Fraction",
532
  min_value=0.0,
533
  max_value=1.0,
534
  value=float(editor_state.get("convective_fraction", 0.5)),
 
537
  )
538
 
539
  # Schedule selection
540
+ available_schedules = list(st.session_state.project_data["internal_loads"]["schedules"].keys())
541
+ schedule = st.selectbox(
542
+ "Schedule",
543
  available_schedules,
544
  index=available_schedules.index(editor_state.get("schedule", available_schedules[0])) if editor_state.get("schedule") in available_schedules else 0,
545
+ help="Select the equipment schedule."
546
  )
547
 
548
+ # Zone assignment
549
  zone = st.text_input(
550
  "Zone",
551
  value=editor_state.get("zone", "Whole Building"),
 
553
  )
554
 
555
  # Submit buttons
556
+ col1, col2 = st.columns(2)
557
  with col1:
558
+ submit_label = "Update" if is_edit else "Add"
559
+ submit = st.form_submit_button(f"{submit_label} Equipment")
560
 
561
  with col2:
562
+ refresh = st.form_submit_button("Refresh")
563
 
564
  # Handle form submission
565
  if submit and current_action["action"] != "submit":
566
+ new_action_id = str(uuid.uuid4())
567
+ st.session_state.equipment_action = {"action": "submit", "id": new_action_id}
568
 
569
  # Validate inputs
570
  if not name.strip():
 
575
  st.error("Radiative and convective fractions must sum to 1.0")
576
  st.session_state.equipment_action = {"action": None, "id": None}
577
  return
 
578
  existing_names = [system["name"] for system in equipment_systems if not (is_edit and system["name"] == editor_state.get("name"))]
579
  if name.strip() in existing_names:
580
+ st.error("Equipment name must be unique.")
581
  st.session_state.equipment_action = {"action": None, "id": None}
582
  return
583
+
584
  # Create equipment data
585
+ equipment_data = {
586
  "id": str(uuid.uuid4()),
587
  "name": name.strip(),
588
  "area": area,
589
+ "sensible_gain": sensible_gain,
590
  "latent_gain": latent_gain,
591
  "total_sensible_power": area * sensible_gain,
592
  "total_latent_power": area * latent_gain,
593
  "radiative_fraction": radiative_fraction,
594
  "convective_fraction": convective_fraction,
595
  "schedule": schedule,
596
+ "zone": zone
 
597
  }
598
 
599
  # Handle edit mode
600
  if is_edit:
601
+ index = editor_state.get("index", 0)
602
+ st.session_state.project_data["internal_loads"]["equipment"][index] = equipment_data
603
+ st.success(f"Equipment '{name}' updated!")
604
  else:
605
  st.session_state.project_data["internal_loads"]["equipment"].append(equipment_data)
606
  st.success(f"Equipment '{name}' added!")
607
+
608
+ # Clear editor and action states
609
+ st.session_state.equipment_editor = {}
610
  st.session_state.equipment_action = {"action": None, "id": None}
611
  st.session_state.module_rerun_flags["internal_loads"] = True
612
+
613
  elif refresh and current_action["action"] != "refresh":
614
+ new_action_id = str(uuid.uuid4())
615
+ st.session_state.equipment_action = {"action": "refresh", "id": new_action_id}
616
+ st.session_state.equipment_editor = {}
617
+ st.session_state.equipment_action = {"action": None, "id": None}
618
+ st.session_state.module_rerun_flags["internal_loads"] = True
619
 
620
  def display_ventilation_infiltration_tab():
621
  """Display the ventilation/infiltration tab content with a two-column layout and fixed button logic."""
622
+ # Get ventilation/infiltration from session state
623
+ vent_inf_systems = st.session_state.project_data["internal_loads"]["ventilation_infiltration"]
624
 
625
+ # Split the display into two columns
626
+ col1, col2 = st.columns([3, 2])
627
 
628
  with col1:
629
  st.subheader("Saved Ventilation & Infiltration")
630
+ if vent_inf_systems:
631
  display_ventilation_infiltration_table(vent_inf_systems)
632
  else:
633
+ st.write("No ventilation or infiltration systems defined.")
634
+
635
  with col2:
636
  st.subheader("Ventilation/Infiltration Editor/Creator")
637
 
638
+ # Initialize editor and action states
639
+ if "vent_inf_editor" not in st.session_state:
640
+ st.session_state.vent_inf_editor = {}
641
+ if "vent_inf_action" not in st.session_state:
642
+ st.session_state.vent_inf_action = {"action": None, "id": None}
643
 
644
+ # Check if an action is pending
645
+ current_action = st.session_state.vent_inf_action
646
+ action_id = current_action.get("id", None)
647
 
648
  # Get building type for default values
649
+ building_type = st.session_state.project_data["building_info"].get("building_type")
650
+ default_building_data = DEFAULT_BUILDING_INTERNALS.get(building_type, DEFAULT_BUILDING_INTERNALS["Other"])
651
 
652
  # Display the editor form
653
+ with st.form("vent_inf_editor_form", clear_on_submit=True):
654
+ editor_state = st.session_state.get("vent_inf_editor", {})
655
+ is_edit = editor_state.get("is_edit", False)
 
656
 
657
  # System name
658
+ name = st.text_input(
659
  "System Name",
660
  value=editor_state.get("name", ""),
661
  help="Enter a unique name for this ventilation/infiltration system."
662
  )
663
 
664
  # System type
665
+ system_type = st.selectbox(
666
  "System Type",
667
  ["Ventilation", "Infiltration"],
668
+ index=["Ventilation", "Infiltration"].index(editor_state.get("system_type", "Ventilation")) if editor_state.get("system_type") in ["Ventilation", "Infiltration"] else 0,
669
+ help="Select whether this is ventilation or infiltration."
670
  )
671
 
672
  # Area
673
+ area = st.number_input(
674
  "Area (m虏)",
675
  min_value=1.0,
676
+ max_value=100000.0,
677
+ value=float(editor_state.get("area", st.session_state.project_data["building_info"].get("floor_area", 100.0))),
 
678
  format="%.2f",
679
+ help="Floor area served by this system."
680
  )
681
 
682
  if system_type == "Ventilation":
683
+ ventilation_rate = st.number_input(
 
684
  "Ventilation Rate (L/s路m虏)",
685
  min_value=0.1,
686
  max_value=50.0,
 
688
  format="%.2f",
689
  help="Ventilation rate in liters per second per square meter."
690
  )
691
+ air_change_rate = 0.0
692
  else:
693
+ air_change_rate = st.number_input(
 
694
  "Air Change Rate (ACH)",
695
  min_value=0.0,
696
  max_value=10.0,
 
698
  format="%.2f",
699
  help="Air change rate in air changes per hour."
700
  )
701
+ ventilation_rate = 0.0
702
+
703
  # Schedule selection
704
+ available_schedules = list(st.session_state.project_data["internal_loads"]["schedules"].keys())
705
+ schedule = st.selectbox(
706
  "Schedule",
707
  available_schedules,
708
  index=available_schedules.index(editor_state.get("schedule", available_schedules[0])) if editor_state.get("schedule") in available_schedules else 0,
 
710
  )
711
 
712
  # Zone assignment
713
+ zone = st.text_input(
714
  "Zone",
715
  value=editor_state.get("zone", "Whole Building"),
716
  help="Zone or area where this system operates."
717
  )
718
 
719
  # Submit buttons
720
+ col1, col2 = st.columns(2)
721
+ with col1:
722
+ submit_label = "Update" if is_edit else "Add"
723
+ submit = st.form_submit_button(f"{submit_label} System")
 
 
 
724
 
725
+ with col2:
726
+ refresh = st.form_submit_button("Refresh")
727
+
728
  # Handle form submission
729
  if submit and current_action["action"] != "submit":
730
+ new_action_id = str(uuid.uuid4())
731
+ st.session_state.vent_inf_action = {"action": "submit", "id": new_action_id}
732
 
733
  # Validate inputs
734
  if not name.strip():
735
  st.error("System name is required.")
736
+ st.session_state.vent_inf_action = {"action": None, "id": None}
737
  return
738
+ existing_names = [system["name"] for system in vent_inf_systems if not (is_edit and system["name"] == editor_state.get("name"))]
739
+ if name.strip() in existing_names:
 
740
  st.error("System name must be unique.")
741
+ st.session_state.vent_inf_action = {"action": None, "id": None}
742
  return
743
+
744
+ # Create ventilation/infiltration data
745
+ vent_inf_data = {
746
+ "id": str(uuid.uuid4()),
747
  "name": name.strip(),
748
  "system_type": system_type,
749
  "area": area,
750
  "ventilation_rate": ventilation_rate,
751
  "air_change_rate": air_change_rate,
752
  "schedule": schedule,
753
+ "zone": zone
 
754
  }
755
+
756
  # Handle edit mode
757
  if is_edit:
758
+ index = editor_state.get("index", 0)
759
+ st.session_state.project_data["internal_loads"]["ventilation_infiltration"][index] = vent_inf_data
 
760
  st.success(f"{system_type} system '{name}' updated!")
761
  else:
762
+ st.session_state.project_data["internal_loads"]["ventilation_infiltration"].append(vent_inf_data)
763
  st.success(f"{system_type} system '{name}' added!")
 
 
 
 
 
764
 
765
+ # Clear editor and action states
766
+ st.session_state.vent_inf_editor = {}
767
+ st.session_state.vent_inf_action = {"action": None, "id": None}
768
+ st.session_state.module_rerun_flags["internal_loads"] = True
769
+
770
  elif refresh and current_action["action"] != "refresh":
771
+ new_action_id = str(uuid.uuid4())
772
+ st.session_state.vent_inf_action = {"action": "refresh", "id": new_action_id}
773
+ st.session_state.vent_inf_editor = {}
774
+ st.session_state.vent_inf_action = {"action": None, "id": None}
775
+ st.session_state.module_rerun_flags["internal_loads"] = True
776
 
777
  def display_schedules_tab():
778
+ """Display the schedules tab content with fixed button logic."""
 
 
779
  st.subheader("Schedules Editor")
780
+
781
  DEFAULT_STATE = {
782
  "name": "",
783
  "description": "",
 
787
  "is_edit": False,
788
  "original_name": ""
789
  }
790
+
791
  # Initialize schedule_editor and schedule_action if not present
792
  if "schedule_editor" not in st.session_state:
793
  st.session_state.schedule_editor = DEFAULT_STATE.copy()
794
  if "schedule_action" not in st.session_state:
795
  st.session_state.schedule_action = {"action": None, "id": None}
796
+
797
+ # Check if an action is pending
798
+ current_action = st.session_state.schedule_action
799
+ action_id = current_action.get("id", None)
800
+
801
  editor_state = st.session_state.schedule_editor
802
  schedules = st.session_state.project_data["internal_loads"]["schedules"]
803
+
804
  # Handle template change
805
  template_options = list(DEFAULT_SCHEDULE_TEMPLATES.keys())
806
  selected_template = st.selectbox(
 
810
  if editor_state.get("template") in template_options else 0,
811
  help="Choose a base schedule to prefill values."
812
  )
813
+
814
  # Update sliders only if template changes and not in edit mode
815
+ if selected_template != editor_state.get("template", "None") and not editor_state.get("is_edit") and current_action["action"] != "template_change":
816
+ new_action_id = str(uuid.uuid4())
817
+ st.session_state.schedule_action = {"action": "template_change", "id": new_action_id}
818
  st.session_state.schedule_editor["template"] = selected_template
819
  if selected_template != "None":
820
  tpl = DEFAULT_SCHEDULE_TEMPLATES[selected_template]
 
827
  for hour in range(24):
828
  st.session_state[f"weekday_slider_{hour}_value"] = 0.0
829
  st.session_state[f"weekend_slider_{hour}_value"] = 0.0
830
+ st.session_state.schedule_action = {"action": None, "id": None}
831
  st.session_state.module_rerun_flags["internal_loads"] = True
832
  st.rerun()
833
+
834
  # UI FORM for name/description and actions
835
+ with st.form("schedule_form", clear_on_submit=True):
836
  name = st.text_input("Schedule Name", value=editor_state.get("name", ""))
837
  description = st.text_area("Description", value=editor_state.get("description", ""))
838
+
839
  # SLIDERS LAYOUT
840
  st.markdown("### Schedule Sliders")
841
  col_hour, col_wd, col_we = st.columns([0.4, 2.0, 2.0])
 
845
  st.markdown("<div style='text-align:center; font-weight:bold; font-size:18px;'>Weekday</div>", unsafe_allow_html=True)
846
  with col_we:
847
  st.markdown("<div style='text-align:center; font-weight:bold; font-size:18px;'>Weekend</div>", unsafe_allow_html=True)
848
+
849
  hide_elements = """
850
  <style>
851
  div[data-testid="stSliderTickBarMin"],
 
855
  </style>
856
  """
857
  st.markdown(hide_elements, unsafe_allow_html=True)
858
+
859
  weekday_values = []
860
  weekend_values = []
861
+
862
  for hour in range(24):
863
  col_hour, col_wd, col_we = st.columns([0.4, 2.0, 2.0])
864
  with col_hour:
 
887
  format=None
888
  )
889
  weekend_values.append(val)
890
+
891
  # Action Buttons
892
  col1, col2, col3 = st.columns(3)
893
  with col1:
 
897
  edit_submitted = st.form_submit_button("Edit Selected Schedule")
898
  with col3:
899
  delete_submitted = st.form_submit_button("Delete Selected Schedule")
900
+
901
  # Saved Schedules Selector
902
  st.markdown("### Saved Schedules")
903
  saved_schedule = st.selectbox(
 
906
  index=list(schedules.keys()).index(editor_state.get("name", list(schedules.keys())[0])) if editor_state.get("name") in schedules and schedules else 0,
907
  help="Select a saved schedule to edit or delete."
908
  )
909
+
910
  # Save logic
911
+ if submitted and current_action["action"] != "submit":
912
+ new_action_id = str(uuid.uuid4())
913
+ st.session_state.schedule_action = {"action": "submit", "id": new_action_id}
914
+ if not name.strip():
915
+ st.error("Schedule name is required.")
916
+ st.session_state.schedule_action = {"action": None, "id": None}
917
+ return
918
+ if name in schedules and not editor_state.get("is_edit"):
919
+ st.error("A schedule with this name already exists.")
920
+ st.session_state.schedule_action = {"action": None, "id": None}
921
+ return
922
+ schedules[name] = {
923
+ "description": description,
924
+ "weekday": weekday_values,
925
+ "weekend": weekend_values
926
+ }
927
+ if editor_state.get("is_edit") and editor_state.get("original_name") and editor_state.get("original_name") != name:
928
+ if editor_state["original_name"] in schedules:
929
+ del schedules[editor_state["original_name"]]
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 '{name}' saved successfully!")
935
+ st.session_state.schedule_action = {"action": None, "id": None}
936
+ st.session_state.module_rerun_flags["internal_loads"] = True
937
+
938
+ # Edit logic
939
+ elif edit_submitted and saved_schedule and current_action["action"] != "edit":
940
+ new_action_id = str(uuid.uuid4())
941
+ st.session_state.schedule_action = {"action": "edit", "id": new_action_id}
942
+ if saved_schedule in DEFAULT_SCHEDULE_TEMPLATES:
943
+ st.error("Default schedules cannot be edited.")
944
+ st.session_state.schedule_action = {"action": None, "id": None}
945
+ return
946
+ schedule_data = schedules[saved_schedule]
947
+ st.session_state.schedule_editor = {
948
+ "is_edit": True,
949
+ "original_name": saved_schedule,
950
+ "name": saved_schedule,
951
+ "description": schedule_data.get("description", ""),
952
+ "weekday": schedule_data.get("weekday", [0.0] * 24),
953
+ "weekend": schedule_data.get("weekend", [0.0] * 24),
954
+ "template": "None"
955
+ }
956
+ for hour in range(24):
957
+ st.session_state[f"weekday_slider_{hour}_value"] = schedule_data.get("weekday", [0.0] * 24)[hour]
958
+ st.session_state[f"weekend_slider_{hour}_value"] = schedule_data.get("weekend", [0.0] * 24)[hour]
959
+ st.session_state.schedule_action = {"action": None, "id": None}
960
+ st.session_state.module_rerun_flags["internal_loads"] = True
961
+
962
+ # Delete logic
963
+ elif delete_submitted and saved_schedule and current_action["action"] != "delete":
964
+ new_action_id = str(uuid.uuid4())
965
+ st.session_state.schedule_action = {"action": "delete", "id": new_action_id}
966
+ if saved_schedule in DEFAULT_SCHEDULE_TEMPLATES:
967
+ st.error("Default schedules cannot be deleted.")
968
+ st.session_state.schedule_action = {"action": None, "id": None}
969
+ return
970
+ if is_schedule_in_use(saved_schedule):
971
+ st.error(f"Schedule '{saved_schedule}' is in use and cannot be deleted.")
972
+ st.session_state.schedule_action = {"action": None, "id": None}
973
+ return
974
+ del schedules[saved_schedule]
975
+ st.session_state.schedule_editor = DEFAULT_STATE.copy()
976
+ for hour in range(24):
977
+ st.session_state[f"weekday_slider_{hour}_value"] = 0.0
978
+ st.session_state[f"weekend_slider_{hour}_value"] = 0.0
979
+ st.success(f"Schedule '{saved_schedule}' deleted!")
980
+ st.session_state.schedule_action = {"action": None, "id": None}
981
+ st.session_state.module_rerun_flags["internal_loads"] = True
982
 
983
+ def is_schedule_in_use(schedule_name: str) -> bool:
984
  """
985
+ Check if a schedule is in use by any people, lighting, equipment, or ventilation/infiltration systems.
986
+
987
  Args:
988
+ schedule_name: Name of the schedule to check.
 
 
989
 
990
  Returns:
991
+ bool: True if the schedule is in use, False otherwise.
992
  """
993
+ internal_loads = st.session_state.project_data["internal_loads"]
994
+ for group in internal_loads.get("people", []):
995
+ if group.get("schedule") == schedule_name:
996
  return True
997
+ for system in internal_loads.get("lighting", []):
998
+ if system.get("schedule") == schedule_name:
999
  return True
1000
+ for system in internal_loads.get("equipment", []):
1001
+ if system.get("schedule") == schedule_name:
1002
  return True
1003
+ for system in internal_loads.get("ventilation_infiltration", []):
1004
+ if system.get("schedule") == schedule_name:
1005
  return True
1006
  return False
1007
 
1008
  def display_people_table(people_groups: List[Dict[str, Any]]):
1009
+ """Display people groups in a table format with fixed edit/delete button logic."""
1010
+ # Create column headers
1011
+ cols = st.columns([2, 1, 1, 1, 1, 1, 1])
1012
+ cols[0].write("**Name**")
1013
+ cols[1].write("**Number**")
1014
+ cols[2].write("**Sensible (W)**")
1015
+ cols[3].write("**Latent (W)**")
1016
+ cols[4].write("**Zone**")
1017
+ cols[5].write("**Edit**")
1018
+ cols[6].write("**Delete**")
1019
+
1020
+ # Check if an action is pending
1021
+ current_action = st.session_state.people_action
1022
+ action_id = current_action.get("id", None)
1023
+
1024
+ # Display each group
1025
+ for idx, group in enumerate(people_groups):
1026
+ cols = st.columns([2, 1, 1, 1, 1, 1, 1])
1027
+ cols[0].write(group["name"])
1028
+ cols[1].write(str(group.get("num_people", 0)))
1029
+ cols[2].write(f"{group.get('total_sensible_heat', 0):.1f}")
1030
+ cols[3].write(f"{group.get('total_latent_heat', 0):.1f}")
1031
+ cols[4].write(group.get("zone", "Unknown"))
1032
 
1033
  # Edit button
1034
+ edit_key = f"edit_people_{group['id']}_{idx}"
1035
+ with cols[5].container():
1036
+ if st.button("Edit", key=edit_key) and current_action["action"] != "edit":
1037
+ new_action_id = str(uuid.uuid4())
1038
+ st.session_state.people_action = {"action": "edit", "id": new_action_id}
1039
+ st.session_state.people_editor = {
1040
+ "index": idx,
1041
+ "name": group.get("name", ""),
1042
+ "num_people": group.get("num_people", 10),
1043
+ "activity_level": group.get("activity_level", list(PEOPLE_ACTIVITY_LEVELS.keys())[0]),
1044
+ "clo_summer": group.get("clo_summer", 0.5),
1045
+ "clo_winter": group.get("clo_winter", 1.0),
1046
+ "schedule": group.get("schedule", "Continuous"),
1047
+ "zone": group.get("zone", "Whole Building"),
1048
  "is_edit": True
1049
  }
1050
+ st.session_state.people_action = {"action": None, "id": None}
1051
+ st.session_state.module_rerun_flags["internal_loads"] = True
1052
+
1053
  # Delete button
1054
+ delete_key = f"delete_people_{group['id']}_{idx}"
1055
+ with cols[6].container():
1056
+ if st.button("Delete", key=delete_key) and current_action["action"] != "delete":
1057
+ new_action_id = str(uuid.uuid4())
1058
+ st.session_state.people_action = {"action": "delete", "id": new_action_id}
1059
+ st.session_state.project_data["internal_loads"]["people"].pop(idx)
1060
+ st.success(f"People group '{group['name']}' deleted!")
1061
+ st.session_state.people_action = {"action": None, "id": None}
1062
+ st.session_state.module_rerun_flags["internal_loads"] = True
 
 
 
 
 
 
 
 
 
 
 
 
 
1063
 
1064
+ def display_lighting_table(lighting_systems: List[Dict[str, Any]]):
1065
+ """Display lighting systems in a table format with fixed edit/delete button logic."""
1066
+ # Create column headers
1067
+ cols = st.columns([2, 1, 1, 1, 1, 1, 1])
1068
+ cols[0].write("**Name**")
1069
+ cols[1].write("**Area (m虏)**")
1070
+ cols[2].write("**LPD (W/m虏)**")
1071
+ cols[3].write("**Total Power (W)**")
1072
+ cols[4].write("**Zone**")
1073
+ cols[5].write("**Edit**")
1074
+ cols[6].write("**Delete**")
1075
+
1076
+ # Check if an action is pending
1077
+ current_action = st.session_state.lighting_action
1078
+ action_id = current_action.get("id", None)
1079
+
1080
+ # Display each system
1081
+ for idx, system in enumerate(lighting_systems):
1082
+ cols = st.columns([2, 1, 1, 1, 1, 1, 1])
1083
+ cols[0].write(system["name"])
1084
+ cols[1].write(f"{system.get('area', 0):.1f}")
1085
+ cols[2].write(f"{system.get('lpd', 0):.2f}")
1086
+ cols[3].write(f"{system.get('total_power', 0):.1f}")
1087
+ cols[4].write(system.get("zone", "Unknown"))
1088
+
1089
+ # Edit button
1090
+ edit_key = f"edit_lighting_{system['id']}_{idx}"
1091
+ with cols[5].container():
1092
+ if st.button("Edit", key=edit_key) and current_action["action"] != "edit":
1093
+ new_action_id = str(uuid.uuid4())
1094
+ st.session_state.lighting_action = {"action": "edit", "id": new_action_id}
1095
+ st.session_state.lighting_editor = {
1096
+ "index": idx,
1097
+ "name": system.get("name", ""),
1098
+ "area": system.get("area", 100.0),
1099
+ "lpd": system.get("lpd", 12.0),
1100
+ "radiative_fraction": system.get("radiative_fraction", 0.4),
1101
+ "convective_fraction": system.get("convective_fraction", 0.6),
1102
+ "schedule": system.get("schedule", "Continuous"),
1103
+ "zone": system.get("zone", "Whole Building"),
1104
  "is_edit": True
1105
+ }
1106
+ st.session_state.lighting_action = {"action": None, "id": None}
1107
+ st.session_state.module_rerun_flags["internal_loads"] = True
1108
 
1109
  # Delete button
1110
+ delete_key = f"delete_lighting_{system['id']}_{idx}"
1111
+ with cols[6].container():
1112
+ if st.button("Delete", key=delete_key) and current_action["action"] != "delete":
1113
+ new_action_id = str(uuid.uuid4())
1114
+ st.session_state.lighting_action = {"action": "delete", "id": new_action_id}
1115
+ st.session_state.project_data["internal_loads"]["lighting"].pop(idx)
1116
+ st.success(f"Lighting system '{system['name']}' deleted!")
1117
+ st.session_state.lighting_action = {"action": None, "id": None}
1118
+ st.session_state.module_rerun_flags["internal_loads"] = True
1119
 
1120
+ def display_equipment_table(equipment_systems: List[Dict[str, Any]]):
1121
+ """Display equipment systems in a table format with fixed edit/delete button logic."""
1122
+ # Create column headers
1123
+ cols = st.columns([2, 1, 1, 1, 1, 1, 1])
1124
+ cols[0].write("**Name**")
1125
+ cols[1].write("**Area (m虏)**")
1126
+ cols[2].write("**Sensible (W)**")
1127
+ cols[3].write("**Latent (W)**")
1128
+ cols[4].write("**Zone**")
1129
+ cols[5].write("**Edit**")
1130
+ cols[6].write("**Delete**")
 
 
1131
 
1132
+ # Check if an action is pending
1133
+ current_action = st.session_state.equipment_action
1134
+ action_id = current_action.get("id", None)
1135
+
1136
+ # Display each system
1137
+ for idx, system in enumerate(equipment_systems):
1138
+ cols = st.columns([2, 1, 1, 1, 1, 1, 1])
1139
+ cols[0].write(system["name"])
1140
+ cols[1].write(f"{system.get('area', 0):.1f}")
1141
+ cols[2].write(f"{system.get('total_sensible_power', 0):.1f}")
1142
+ cols[3].write(f"{system.get('total_latent_power', 0):.1f}")
1143
+ cols[4].write(system.get("zone", "Unknown"))
1144
+
1145
+ # Edit button
1146
+ edit_key = f"edit_equipment_{system['id']}_{idx}"
1147
+ with cols[5].container():
1148
+ if st.button("Edit", key=edit_key) and current_action["action"] != "edit":
1149
+ new_action_id = str(uuid.uuid4())
1150
+ st.session_state.equipment_action = {"action": "edit", "id": new_action_id}
1151
+ st.session_state.equipment_editor = {
1152
+ "index": idx,
1153
+ "name": system.get("name", ""),
1154
+ "area": system.get("area", 100.0),
1155
+ "sensible_gain": system.get("sensible_gain", 10.0),
1156
+ "latent_gain": system.get("latent_gain", 2.0),
1157
+ "radiative_fraction": system.get("radiative_fraction", 0.5),
1158
+ "convective_fraction": system.get("convective_fraction", 0.5),
1159
+ "schedule": system.get("schedule", "Continuous"),
1160
+ "zone": system.get("zone", "Whole Building"),
1161
+ "is_edit": True
1162
+ }
1163
+ st.session_state.equipment_action = {"action": None, "id": None}
1164
+ st.session_state.module_rerun_flags["internal_loads"] = True
1165
+
1166
+ # Delete button
1167
+ delete_key = f"delete_equipment_{system['id']}_{idx}"
1168
+ with cols[6].container():
1169
+ if st.button("Delete", key=delete_key) and current_action["action"] != "delete":
1170
+ new_action_id = str(uuid.uuid4())
1171
+ st.session_state.equipment_action = {"action": "delete", "id": new_action_id}
1172
+ st.session_state.project_data["internal_loads"]["equipment"].pop(idx)
1173
+ st.success(f"Equipment '{system['name']}' deleted!")
1174
+ st.session_state.equipment_action = {"action": None, "id": None}
1175
+ st.session_state.module_rerun_flags["internal_loads"] = True
 
 
 
 
 
 
 
 
 
 
 
 
1176
 
1177
+ def display_ventilation_infiltration_table(vent_inf_systems: List[Dict[str, Any]]):
1178
+ """Display ventilation/infiltration systems in a table format with fixed edit/delete button logic."""
1179
+ # Create column headers
1180
+ cols = st.columns([2, 1, 1, 1, 1, 1, 1])
1181
+ cols[0].write("**Name**")
1182
+ cols[1].write("**Type**")
1183
+ cols[2].write("**Area (m虏)**")
1184
+ cols[3].write("**Rate**")
1185
+ cols[4].write("**Zone**")
1186
+ cols[5].write("**Edit**")
1187
+ cols[6].write("**Delete**")
1188
+
1189
+ # Check if an action is pending
1190
+ current_action = st.session_state.vent_inf_action
1191
+ action_id = current_action.get("id", None)
1192
+
1193
+ # Display each system
1194
+ for idx, system in enumerate(vent_inf_systems):
1195
+ cols = st.columns([2, 1, 1, 1, 1, 1, 1])
1196
+ cols[0].write(system["name"])
1197
+ cols[1].write(system.get("system_type", "Unknown"))
1198
+ cols[2].write(f"{system.get('area', 0):.1f}")
1199
+ 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"
1200
+ cols[3].write(rate_info)
1201
+ cols[4].write(system.get("zone", "Unknown"))
1202
+
1203
+ # Edit button
1204
+ edit_key = f"edit_vent_inf_{system['id']}_{idx}"
1205
+ with cols[5].container():
1206
+ if st.button("Edit", key=edit_key) and current_action["action"] != "edit":
1207
+ new_action_id = str(uuid.uuid4())
1208
+ st.session_state.vent_inf_action = {"action": "edit", "id": new_action_id}
1209
+ st.session_state.vent_inf_editor = {
1210
+ "index": idx,
1211
+ "name": system.get("name", ""),
1212
+ "system_type": system.get("system_type", "Ventilation"),
1213
+ "area": system.get("area", 100.0),
1214
+ "ventilation_rate": system.get("ventilation_rate", 10.0),
1215
+ "air_change_rate": system.get("air_change_rate", 1.0),
1216
+ "schedule": system.get("schedule", "Continuous"),
1217
+ "zone": system.get("zone", "Whole Building"),
1218
+ "is_edit": True
1219
+ }
1220
+ st.session_state.vent_inf_action = {"action": None, "id": None}
1221
+ st.session_state.module_rerun_flags["internal_loads"] = True
1222
+
1223
+ # Delete button
1224
+ delete_key = f"delete_vent_inf_{system['id']}_{idx}"
1225
+ with cols[6].container():
1226
+ if st.button("Delete", key=delete_key) and current_action["action"] != "delete":
1227
+ new_action_id = str(uuid.uuid4())
1228
+ st.session_state.vent_inf_action = {"action": "delete", "id": new_action_id}
1229
+ st.session_state.project_data["internal_loads"]["ventilation_infiltration"].pop(idx)
1230
+ st.success(f"{system.get('system_type', 'System')} '{system['name']}' deleted!")
1231
+ st.session_state.vent_inf_action = {"action": None, "id": None}
1232
+ st.session_state.module_rerun_flags["internal_loads"] = True
1233
 
1234
+ def display_schedules_table(schedules: Dict[str, Any]):
1235
+ """Display schedules in a table format with fixed edit/delete button logic."""
1236
+ if not schedules:
1237
+ return
 
 
1238
 
1239
  # Create table data
1240
+ table_data = []
1241
+ for name, schedule in schedules.items():
1242
+ weekday_peak = max(schedule.get("weekday", [0]))
1243
+ weekend_peak = max(schedule.get("weekend", [0]))
1244
 
1245
+ table_data.append({
1246
  "Name": name,
1247
+ "Description": schedule.get("description", "No description"),
1248
+ "Weekday Peak": f"{weekday_peak:.2f}",
1249
+ "Weekend Peak": f"{weekend_peak:.2f}",
1250
  "Actions": name
1251
  })
1252
 
1253
+ if table_data:
1254
+ df = pd.DataFrame(table_data)
1255
 
1256
  # Display table
1257
  st.dataframe(df.drop('Actions', axis=1), use_container_width=True)
1258
 
1259
+ # Check if an action is pending
1260
+ current_action = st.session_state.schedule_action
1261
+ action_id = current_action.get("id", None)
1262
+
1263
  # Display action buttons
1264
  st.write("**Actions:**")
1265
+ for i, row in enumerate(table_data):
1266
+ schedule_name = row["Actions"]
1267
 
1268
+ col1, col2, col3, col4 = st.columns([2, 1, 1, 1])
1269
 
1270
  with col1:
1271
  st.write(f"{i+1}. {schedule_name}")
1272
 
1273
  with col2:
1274
+ view_key = f"view_schedule_{schedule_name}_{i}"
1275
+ if st.button("View", key=view_key) and current_action["action"] != "view":
1276
+ new_action_id = str(uuid.uuid4())
1277
+ st.session_state.schedule_action = {"action": "view", "id": new_action_id}
1278
+ schedule_data = schedules[schedule_name]
1279
  display_schedule_chart(schedule_name, schedule_data)
1280
+ st.session_state.schedule_action = {"action": None, "id": None}
1281
+ st.session_state.module_rerun_flags["internal_loads"] = True
1282
 
1283
  with col3:
1284
+ edit_key = f"edit_schedule_{schedule_name}_{i}"
1285
+ if st.button("Edit", key=edit_key) and current_action["action"] != "edit":
1286
+ new_action_id = str(uuid.uuid4())
1287
+ st.session_state.schedule_action = {"action": "edit", "id": new_action_id}
1288
+ schedule_data = schedules[schedule_name]
1289
+ st.session_state.schedule_editor = {
1290
  "is_edit": True,
1291
  "original_name": schedule_name,
1292
  "name": schedule_name,
1293
+ "description": schedule_data.get("description", ""),
1294
+ "weekday": schedule_data.get("weekday", [0.0] * 24),
1295
+ "weekend": schedule_data.get("weekend", [0.0] * 24)
1296
  }
1297
+ st.session_state.schedule_action = {"action": None, "id": None}
1298
+ st.session_state.module_rerun_flags["internal_loads"] = True
1299
 
1300
  with col4:
1301
+ if schedule_name not in DEFAULT_SCHEDULE_TEMPLATES:
1302
  if is_schedule_in_use(schedule_name):
1303
  st.write("(In Use)")
1304
  else:
1305
+ delete_key = f"delete_schedule_{schedule_name}_{i}"
1306
+ if st.button("Delete", key=delete_key) and current_action["action"] != "delete":
1307
+ new_action_id = str(uuid.uuid4())
1308
+ st.session_state.schedule_action = {"action": "delete", "id": new_action_id}
1309
+ del schedules[schedule_name]
1310
+ st.success(f"Schedule '{schedule_name}' deleted!")
1311
+ st.session_state.schedule_action = {"action": None, "id": None}
1312
+ st.session_state.module_rerun_flags["internal_loads"] = True
1313
  else:
1314
  st.write("(Default)")