mabuseif commited on
Commit
54ad746
·
verified ·
1 Parent(s): 23f3437

Update app/internal_loads.py

Browse files
Files changed (1) hide show
  1. app/internal_loads.py +440 -561
app/internal_loads.py CHANGED
@@ -80,13 +80,11 @@ def display_internal_loads_page():
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():
@@ -105,11 +103,9 @@ def initialize_internal_loads():
105
  }
106
 
107
  def display_people_tab():
108
- """Display the people tab content with a two-column layout and fixed button logic."""
109
- # Get people from session state
110
  people_groups = st.session_state.project_data["internal_loads"]["people"]
111
 
112
- # Split the display into two columns
113
  col1, col2 = st.columns([3, 2])
114
 
115
  with col1:
@@ -122,29 +118,21 @@ def display_people_tab():
122
  with col2:
123
  st.subheader("People Group Editor/Creator")
124
 
125
- # Initialize editor and action states
126
  if "people_editor" not in st.session_state:
127
  st.session_state.people_editor = {}
128
  if "people_action" not in st.session_state:
129
  st.session_state.people_action = {"action": None, "id": None}
130
 
131
- # Check if an action is pending
132
- current_action = st.session_state.people_action
133
- action_id = current_action.get("id", None)
134
-
135
- # Display the editor form
136
  with st.form("people_editor_form", clear_on_submit=True):
137
  editor_state = st.session_state.get("people_editor", {})
138
  is_edit = editor_state.get("is_edit", False)
139
 
140
- # Group name
141
  name = st.text_input(
142
  "Group Name",
143
  value=editor_state.get("name", ""),
144
  help="Enter a unique name for this people group."
145
  )
146
 
147
- # Number of people
148
  num_people = st.number_input(
149
  "Number of People",
150
  min_value=1,
@@ -153,7 +141,6 @@ def display_people_tab():
153
  help="Number of people in this group."
154
  )
155
 
156
- # Activity level
157
  activity_level = st.selectbox(
158
  "Activity Level",
159
  list(PEOPLE_ACTIVITY_LEVELS.keys()),
@@ -161,7 +148,6 @@ def display_people_tab():
161
  help="Select the activity level for this group."
162
  )
163
 
164
- # Clothing insulation
165
  st.write("**Clothing Insulation:**")
166
  col_summer, col_winter = st.columns(2)
167
 
@@ -185,7 +171,6 @@ def display_people_tab():
185
  help="Clothing insulation for winter conditions."
186
  )
187
 
188
- # Schedule selection
189
  available_schedules = list(st.session_state.project_data["internal_loads"]["schedules"].keys())
190
  schedule = st.selectbox(
191
  "Schedule",
@@ -194,14 +179,12 @@ def display_people_tab():
194
  help="Select the occupancy schedule for this group."
195
  )
196
 
197
- # Zone assignment
198
  zone = st.text_input(
199
  "Zone",
200
  value=editor_state.get("zone", "Whole Building"),
201
  help="Zone or area where this people group is located."
202
  )
203
 
204
- # Submit buttons
205
  col1, col2 = st.columns(2)
206
  with col1:
207
  submit_label = "Update" if is_edit else "Add"
@@ -210,69 +193,64 @@ def display_people_tab():
210
  with col2:
211
  refresh = st.form_submit_button("Refresh")
212
 
213
- # Handle form submission
214
- if submit and current_action["action"] != "submit":
215
- new_action_id = str(uuid.uuid4())
216
- st.session_state.people_action = {"action": "submit", "id": new_action_id}
217
-
218
- # Validate inputs
219
- if not name.strip():
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.")
226
- st.session_state.people_action = {"action": None, "id": None}
227
- return
228
-
229
- # Get activity data
230
- activity_data = PEOPLE_ACTIVITY_LEVELS[activity_level]
231
-
232
- # Create people group data
233
- people_data = {
234
- "id": str(uuid.uuid4()),
235
- "name": name.strip(),
236
- "num_people": num_people,
237
- "activity_level": activity_level,
238
- "activity_data": activity_data,
239
- "clo_summer": clo_summer,
240
- "clo_winter": clo_winter,
241
- "schedule": schedule,
242
- "zone": zone,
243
- "sensible_heat_per_person": (activity_data["sensible_min_w"] + activity_data["sensible_max_w"]) / 2,
244
- "latent_heat_per_person": (activity_data["latent_min_w"] + activity_data["latent_max_w"]) / 2,
245
- "total_sensible_heat": num_people * (activity_data["sensible_min_w"] + activity_data["sensible_max_w"]) / 2,
246
- "total_latent_heat": num_people * (activity_data["latent_min_w"] + activity_data["latent_max_w"]) / 2
247
- }
248
-
249
- # Handle edit mode
250
- if is_edit:
251
- index = editor_state.get("index", 0)
252
- st.session_state.project_data["internal_loads"]["people"][index] = people_data
253
- st.success(f"People group '{name}' updated!")
254
- else:
255
- st.session_state.project_data["internal_loads"]["people"].append(people_data)
256
- st.success(f"People group '{name}' added!")
 
257
 
258
- # Clear editor and action states
259
- st.session_state.people_editor = {}
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}
266
- st.session_state.people_editor = {}
267
- st.session_state.people_action = {"action": None, "id": None}
268
- st.session_state.module_rerun_flags["internal_loads"] = True
269
 
270
  def display_lighting_tab():
271
- """Display the lighting tab content with a two-column layout and fixed button logic."""
272
- # Get lighting from session state
273
  lighting_systems = st.session_state.project_data["internal_loads"]["lighting"]
274
 
275
- # Split the display into two columns
276
  col1, col2 = st.columns([3, 2])
277
 
278
  with col1:
@@ -285,33 +263,24 @@ def display_lighting_tab():
285
  with col2:
286
  st.subheader("Lighting System Editor/Creator")
287
 
288
- # Initialize editor and action states
289
  if "lighting_editor" not in st.session_state:
290
  st.session_state.lighting_editor = {}
291
  if "lighting_action" not in st.session_state:
292
  st.session_state.lighting_action = {"action": None, "id": None}
293
 
294
- # Check if an action is pending
295
- current_action = st.session_state.lighting_action
296
- action_id = current_action.get("id", None)
297
-
298
- # Get building type for default values
299
  building_type = st.session_state.project_data["building_info"].get("building_type")
300
  default_lighting_density = DEFAULT_BUILDING_INTERNALS.get(building_type, DEFAULT_BUILDING_INTERNALS["Other"])["lighting_density"]
301
 
302
- # Display the editor form
303
  with st.form("lighting_editor_form", clear_on_submit=True):
304
  editor_state = st.session_state.get("lighting_editor", {})
305
  is_edit = editor_state.get("is_edit", False)
306
 
307
- # System name
308
  name = st.text_input(
309
  "System Name",
310
  value=editor_state.get("name", ""),
311
  help="Enter a unique name for this lighting system."
312
  )
313
 
314
- # Area
315
  area = st.number_input(
316
  "Area (m²)",
317
  min_value=1.0,
@@ -321,7 +290,6 @@ def display_lighting_tab():
321
  help="Floor area served by this lighting system."
322
  )
323
 
324
- # Lighting power density
325
  lpd = st.number_input(
326
  "Lighting Power Density (W/m²)",
327
  min_value=0.1,
@@ -331,7 +299,6 @@ def display_lighting_tab():
331
  help="Lighting power density in watts per square meter."
332
  )
333
 
334
- # Heat fractions
335
  st.write("**Heat Distribution:**")
336
  col_rad, col_conv = st.columns(2)
337
 
@@ -355,7 +322,6 @@ def display_lighting_tab():
355
  help="Fraction of heat released as convection."
356
  )
357
 
358
- # Schedule selection
359
  available_schedules = list(st.session_state.project_data["internal_loads"]["schedules"].keys())
360
  schedule = st.selectbox(
361
  "Schedule",
@@ -364,14 +330,12 @@ def display_lighting_tab():
364
  help="Select the lighting schedule for this system."
365
  )
366
 
367
- # Zone assignment
368
  zone = st.text_input(
369
  "Zone",
370
  value=editor_state.get("zone", "Whole Building"),
371
  help="Zone or area where this lighting system is located."
372
  )
373
 
374
- # Submit buttons
375
  col1, col2 = st.columns(2)
376
  with col1:
377
  submit_label = "Update" if is_edit else "Add"
@@ -380,66 +344,59 @@ def display_lighting_tab():
380
  with col2:
381
  refresh = st.form_submit_button("Refresh")
382
 
383
- # Handle form submission
384
- if submit and current_action["action"] != "submit":
385
- new_action_id = str(uuid.uuid4())
386
- st.session_state.lighting_action = {"action": "submit", "id": new_action_id}
387
-
388
- # Validate inputs
389
- if not name.strip():
390
- st.error("System name is required.")
391
- st.session_state.lighting_action = {"action": None, "id": None}
392
- return
393
- if abs(radiative_fraction + convective_fraction - 1.0) > 0.01:
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.")
400
- st.session_state.lighting_action = {"action": None, "id": None}
401
- return
402
-
403
- # Create lighting system data
404
- lighting_data = {
405
- "id": str(uuid.uuid4()),
406
- "name": name.strip(),
407
- "area": area,
408
- "lpd": lpd,
409
- "total_power": area * lpd,
410
- "radiative_fraction": radiative_fraction,
411
- "convective_fraction": convective_fraction,
412
- "schedule": schedule,
413
- "zone": zone
414
- }
415
-
416
- # Handle edit mode
417
- if is_edit:
418
- index = editor_state.get("index", 0)
419
- st.session_state.project_data["internal_loads"]["lighting"][index] = lighting_data
420
- st.success(f"Lighting system '{name}' updated!")
421
- else:
422
- st.session_state.project_data["internal_loads"]["lighting"].append(lighting_data)
423
- st.success(f"Lighting system '{name}' added!")
424
 
425
- # Clear editor and action states
426
- st.session_state.lighting_editor = {}
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}
433
- st.session_state.lighting_editor = {}
434
- st.session_state.lighting_action = {"action": None, "id": None}
435
- st.session_state.module_rerun_flags["internal_loads"] = True
436
 
437
  def display_equipment_tab():
438
- """Display the equipment tab content with a two-column layout and fixed button logic."""
439
- # Get equipment from session state
440
  equipment_systems = st.session_state.project_data["internal_loads"]["equipment"]
441
 
442
- # Split the display into two columns
443
  col1, col2 = st.columns([3, 2])
444
 
445
  with col1:
@@ -452,33 +409,24 @@ def display_equipment_tab():
452
  with col2:
453
  st.subheader("Equipment Editor/Creator")
454
 
455
- # Initialize editor and action states
456
  if "equipment_editor" not in st.session_state:
457
  st.session_state.equipment_editor = {}
458
  if "equipment_action" not in st.session_state:
459
  st.session_state.equipment_action = {"action": None, "id": None}
460
 
461
- # Check if an action is pending
462
- current_action = st.session_state.equipment_action
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
470
  with st.form("equipment_editor_form", clear_on_submit=True):
471
  editor_state = st.session_state.get("equipment_editor", {})
472
  is_edit = editor_state.get("is_edit", False)
473
 
474
- # Equipment name
475
  name = st.text_input(
476
  "Equipment Name",
477
  value=editor_state.get("name", ""),
478
  help="Enter a unique name for this equipment."
479
  )
480
 
481
- # Area
482
  area = st.number_input(
483
  "Area (m²)",
484
  min_value=1.0,
@@ -488,7 +436,6 @@ def display_equipment_tab():
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
 
@@ -512,7 +459,6 @@ def display_equipment_tab():
512
  help="Latent heat gain in watts per square meter."
513
  )
514
 
515
- # Heat distribution
516
  st.write("**Heat Distribution:**")
517
  col_rad, col_conv = st.columns(2)
518
 
@@ -536,7 +482,6 @@ def display_equipment_tab():
536
  help="Fraction of sensible heat released as convection."
537
  )
538
 
539
- # Schedule selection
540
  available_schedules = list(st.session_state.project_data["internal_loads"]["schedules"].keys())
541
  schedule = st.selectbox(
542
  "Schedule",
@@ -545,14 +490,12 @@ def display_equipment_tab():
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"),
552
  help="Zone or area where this equipment is located."
553
  )
554
 
555
- # Submit buttons
556
  col1, col2 = st.columns(2)
557
  with col1:
558
  submit_label = "Update" if is_edit else "Add"
@@ -561,68 +504,61 @@ def display_equipment_tab():
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():
571
- st.error("Equipment name is required.")
572
- st.session_state.equipment_action = {"action": None, "id": None}
573
- return
574
- if abs(radiative_fraction + convective_fraction - 1.0) > 0.01:
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:
@@ -635,33 +571,24 @@ def display_ventilation_infiltration_tab():
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"],
@@ -669,7 +596,6 @@ def display_ventilation_infiltration_tab():
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,
@@ -700,7 +626,6 @@ def display_ventilation_infiltration_tab():
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",
@@ -709,14 +634,12 @@ def display_ventilation_infiltration_tab():
709
  help="Select the operation schedule for this system."
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"
@@ -725,59 +648,54 @@ def display_ventilation_infiltration_tab():
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,21 +705,15 @@ def display_schedules_tab():
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(
807
  "Select Template",
@@ -810,33 +722,31 @@ def display_schedules_tab():
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]
821
- st.session_state.schedule_editor["weekday"] = tpl["weekday"]
822
- st.session_state.schedule_editor["weekend"] = tpl["weekend"]
823
- for hour in range(24):
824
- st.session_state[f"weekday_slider_{hour}_value"] = tpl["weekday"][hour]
825
- st.session_state[f"weekend_slider_{hour}_value"] = tpl["weekend"][hour]
826
- else:
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])
842
  with col_hour:
@@ -845,7 +755,7 @@ def display_schedules_tab():
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,10 +765,10 @@ def display_schedules_tab():
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,8 +797,7 @@ def display_schedules_tab():
887
  format=None
888
  )
889
  weekend_values.append(val)
890
-
891
- # Action Buttons
892
  col1, col2, col3 = st.columns(3)
893
  with col1:
894
  submit_label = "Update Schedule" if editor_state.get("is_edit") else "Save Schedule"
@@ -897,8 +806,7 @@ def display_schedules_tab():
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(
904
  "Select Saved Schedule",
@@ -906,79 +814,75 @@ def display_schedules_tab():
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
  """
@@ -1006,8 +910,7 @@ def is_schedule_in_use(schedule_name: str) -> bool:
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**")
@@ -1017,11 +920,6 @@ def display_people_table(people_groups: List[Dict[str, Any]]):
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"])
@@ -1030,40 +928,39 @@ def display_people_table(people_groups: List[Dict[str, Any]]):
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²)**")
@@ -1073,11 +970,6 @@ def display_lighting_table(lighting_systems: List[Dict[str, Any]]):
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"])
@@ -1086,40 +978,39 @@ def display_lighting_table(lighting_systems: List[Dict[str, Any]]):
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²)**")
@@ -1129,11 +1020,6 @@ def display_equipment_table(equipment_systems: List[Dict[str, Any]]):
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"])
@@ -1142,41 +1028,40 @@ def display_equipment_table(equipment_systems: List[Dict[str, Any]]):
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**")
@@ -1186,11 +1071,6 @@ def display_ventilation_infiltration_table(vent_inf_systems: List[Dict[str, Any]
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"])
@@ -1200,43 +1080,42 @@ def display_ventilation_infiltration_table(vent_inf_systems: List[Dict[str, Any]
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]))
@@ -1252,15 +1131,8 @@ def display_schedules_table(schedules: Dict[str, Any]):
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"]
@@ -1271,44 +1143,51 @@ def display_schedules_table(schedules: Dict[str, Any]):
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)")
 
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.rerun()
84
 
85
  with col2:
86
  if st.button("Continue to HVAC Loads", key="continue_to_hvac_loads"):
87
  st.session_state.current_page = "HVAC Loads"
 
88
  st.rerun()
89
 
90
  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
  people_groups = st.session_state.project_data["internal_loads"]["people"]
108
 
 
109
  col1, col2 = st.columns([3, 2])
110
 
111
  with col1:
 
118
  with col2:
119
  st.subheader("People Group Editor/Creator")
120
 
 
121
  if "people_editor" not in st.session_state:
122
  st.session_state.people_editor = {}
123
  if "people_action" not in st.session_state:
124
  st.session_state.people_action = {"action": None, "id": None}
125
 
 
 
 
 
 
126
  with st.form("people_editor_form", clear_on_submit=True):
127
  editor_state = st.session_state.get("people_editor", {})
128
  is_edit = editor_state.get("is_edit", False)
129
 
 
130
  name = st.text_input(
131
  "Group Name",
132
  value=editor_state.get("name", ""),
133
  help="Enter a unique name for this people group."
134
  )
135
 
 
136
  num_people = st.number_input(
137
  "Number of People",
138
  min_value=1,
 
141
  help="Number of people in this group."
142
  )
143
 
 
144
  activity_level = st.selectbox(
145
  "Activity Level",
146
  list(PEOPLE_ACTIVITY_LEVELS.keys()),
 
148
  help="Select the activity level for this group."
149
  )
150
 
 
151
  st.write("**Clothing Insulation:**")
152
  col_summer, col_winter = st.columns(2)
153
 
 
171
  help="Clothing insulation for winter conditions."
172
  )
173
 
 
174
  available_schedules = list(st.session_state.project_data["internal_loads"]["schedules"].keys())
175
  schedule = st.selectbox(
176
  "Schedule",
 
179
  help="Select the occupancy schedule for this group."
180
  )
181
 
 
182
  zone = st.text_input(
183
  "Zone",
184
  value=editor_state.get("zone", "Whole Building"),
185
  help="Zone or area where this people group is located."
186
  )
187
 
 
188
  col1, col2 = st.columns(2)
189
  with col1:
190
  submit_label = "Update" if is_edit else "Add"
 
193
  with col2:
194
  refresh = st.form_submit_button("Refresh")
195
 
196
+ # Handle form submission
197
+ if submit:
198
+ action_id = str(uuid.uuid4())
199
+ if st.session_state.people_action.get("id") != action_id:
200
+ st.session_state.people_action = {"action": "save", "id": action_id}
201
+
202
+ # Validate inputs
203
+ if not name.strip():
204
+ st.error("Group name is required.")
205
+ return
206
+ existing_names = [group["name"] for group in people_groups if not (is_edit and group["name"] == editor_state.get("name"))]
207
+ if name.strip() in existing_names:
208
+ st.error("Group name must be unique.")
209
+ return
210
+
211
+ activity_data = PEOPLE_ACTIVITY_LEVELS[activity_level]
212
+
213
+ people_data = {
214
+ "id": str(uuid.uuid4()),
215
+ "name": name.strip(),
216
+ "num_people": num_people,
217
+ "activity_level": activity_level,
218
+ "activity_data": activity_data,
219
+ "clo_summer": clo_summer,
220
+ "clo_winter": clo_winter,
221
+ "schedule": schedule,
222
+ "zone": zone,
223
+ "sensible_heat_per_person": (activity_data["sensible_min_w"] + activity_data["sensible_max_w"]) / 2,
224
+ "latent_heat_per_person": (activity_data["latent_min_w"] + activity_data["latent_max_w"]) / 2,
225
+ "total_sensible_heat": num_people * (activity_data["sensible_min_w"] + activity_data["sensible_max_w"]) / 2,
226
+ "total_latent_heat": num_people * (activity_data["latent_min_w"] + activity_data["latent_max_w"]) / 2
227
+ }
228
+
229
+ if is_edit:
230
+ index = editor_state.get("index", 0)
231
+ st.session_state.project_data["internal_loads"]["people"][index] = people_data
232
+ st.success(f"People group '{name}' updated!")
233
+ else:
234
+ st.session_state.project_data["internal_loads"]["people"].append(people_data)
235
+ st.success(f"People group '{name}' added!")
236
+
237
+ st.session_state.people_editor = {}
238
+ st.session_state.people_action = {"action": None, "id": None}
239
+ st.session_state.module_rerun_flags["internal_loads"] = True
240
+ st.rerun()
241
 
242
+ if refresh:
243
+ action_id = str(uuid.uuid4())
244
+ if st.session_state.people_action.get("id") != action_id:
245
+ st.session_state.people_action = {"action": "refresh", "id": action_id}
246
+ st.session_state.people_editor = {}
247
+ st.session_state.module_rerun_flags["internal_loads"] = True
248
+ st.rerun()
 
 
 
 
249
 
250
  def display_lighting_tab():
251
+ """Display the lighting tab content with a two-column layout similar to components.py."""
 
252
  lighting_systems = st.session_state.project_data["internal_loads"]["lighting"]
253
 
 
254
  col1, col2 = st.columns([3, 2])
255
 
256
  with col1:
 
263
  with col2:
264
  st.subheader("Lighting System Editor/Creator")
265
 
 
266
  if "lighting_editor" not in st.session_state:
267
  st.session_state.lighting_editor = {}
268
  if "lighting_action" not in st.session_state:
269
  st.session_state.lighting_action = {"action": None, "id": None}
270
 
 
 
 
 
 
271
  building_type = st.session_state.project_data["building_info"].get("building_type")
272
  default_lighting_density = DEFAULT_BUILDING_INTERNALS.get(building_type, DEFAULT_BUILDING_INTERNALS["Other"])["lighting_density"]
273
 
 
274
  with st.form("lighting_editor_form", clear_on_submit=True):
275
  editor_state = st.session_state.get("lighting_editor", {})
276
  is_edit = editor_state.get("is_edit", False)
277
 
 
278
  name = st.text_input(
279
  "System Name",
280
  value=editor_state.get("name", ""),
281
  help="Enter a unique name for this lighting system."
282
  )
283
 
 
284
  area = st.number_input(
285
  "Area (m²)",
286
  min_value=1.0,
 
290
  help="Floor area served by this lighting system."
291
  )
292
 
 
293
  lpd = st.number_input(
294
  "Lighting Power Density (W/m²)",
295
  min_value=0.1,
 
299
  help="Lighting power density in watts per square meter."
300
  )
301
 
 
302
  st.write("**Heat Distribution:**")
303
  col_rad, col_conv = st.columns(2)
304
 
 
322
  help="Fraction of heat released as convection."
323
  )
324
 
 
325
  available_schedules = list(st.session_state.project_data["internal_loads"]["schedules"].keys())
326
  schedule = st.selectbox(
327
  "Schedule",
 
330
  help="Select the lighting schedule for this system."
331
  )
332
 
 
333
  zone = st.text_input(
334
  "Zone",
335
  value=editor_state.get("zone", "Whole Building"),
336
  help="Zone or area where this lighting system is located."
337
  )
338
 
 
339
  col1, col2 = st.columns(2)
340
  with col1:
341
  submit_label = "Update" if is_edit else "Add"
 
344
  with col2:
345
  refresh = st.form_submit_button("Refresh")
346
 
347
+ if submit:
348
+ action_id = str(uuid.uuid4())
349
+ if st.session_state.lighting_action.get("id") != action_id:
350
+ st.session_state.lighting_action = {"action": "save", "id": action_id}
351
+
352
+ if not name.strip():
353
+ st.error("System name is required.")
354
+ return
355
+ if abs(radiative_fraction + convective_fraction - 1.0) > 0.01:
356
+ st.error("Radiative and convective fractions must sum to 1.0")
357
+ return
358
+ existing_names = [system["name"] for system in lighting_systems if not (is_edit and system["name"] == editor_state.get("name"))]
359
+ if name.strip() in existing_names:
360
+ st.error("System name must be unique.")
361
+ return
362
+
363
+ lighting_data = {
364
+ "id": str(uuid.uuid4()),
365
+ "name": name.strip(),
366
+ "area": area,
367
+ "lpd": lpd,
368
+ "total_power": area * lpd,
369
+ "radiative_fraction": radiative_fraction,
370
+ "convective_fraction": convective_fraction,
371
+ "schedule": schedule,
372
+ "zone": zone
373
+ }
374
+
375
+ if is_edit:
376
+ index = editor_state.get("index", 0)
377
+ st.session_state.project_data["internal_loads"]["lighting"][index] = lighting_data
378
+ st.success(f"Lighting system '{name}' updated!")
379
+ else:
380
+ st.session_state.project_data["internal_loads"]["lighting"].append(lighting_data)
381
+ st.success(f"Lighting system '{name}' added!")
382
+
383
+ st.session_state.lighting_editor = {}
384
+ st.session_state.lighting_action = {"action": None, "id": None}
385
+ st.session_state.module_rerun_flags["internal_loads"] = True
386
+ st.rerun()
 
387
 
388
+ if refresh:
389
+ action_id = str(uuid.uuid4())
390
+ if st.session_state.lighting_action.get("id") != action_id:
391
+ st.session_state.lighting_action = {"action": "refresh", "id": action_id}
392
+ st.session_state.lighting_editor = {}
393
+ st.session_state.module_rerun_flags["internal_loads"] = True
394
+ st.rerun()
 
 
 
 
395
 
396
  def display_equipment_tab():
397
+ """Display the equipment tab content with a two-column layout similar to components.py."""
 
398
  equipment_systems = st.session_state.project_data["internal_loads"]["equipment"]
399
 
 
400
  col1, col2 = st.columns([3, 2])
401
 
402
  with col1:
 
409
  with col2:
410
  st.subheader("Equipment Editor/Creator")
411
 
 
412
  if "equipment_editor" not in st.session_state:
413
  st.session_state.equipment_editor = {}
414
  if "equipment_action" not in st.session_state:
415
  st.session_state.equipment_action = {"action": None, "id": None}
416
 
 
 
 
 
 
417
  building_type = st.session_state.project_data["building_info"].get("building_type")
418
  default_equipment_data = DEFAULT_BUILDING_INTERNALS.get(building_type, DEFAULT_BUILDING_INTERNALS["Other"])["equipment_heat_gains"]
419
 
 
420
  with st.form("equipment_editor_form", clear_on_submit=True):
421
  editor_state = st.session_state.get("equipment_editor", {})
422
  is_edit = editor_state.get("is_edit", False)
423
 
 
424
  name = st.text_input(
425
  "Equipment Name",
426
  value=editor_state.get("name", ""),
427
  help="Enter a unique name for this equipment."
428
  )
429
 
 
430
  area = st.number_input(
431
  "Area (m²)",
432
  min_value=1.0,
 
436
  help="Floor area served by this equipment."
437
  )
438
 
 
439
  st.write("**Heat Gains (W/m²):**")
440
  col_sens, col_lat = st.columns(2)
441
 
 
459
  help="Latent heat gain in watts per square meter."
460
  )
461
 
 
462
  st.write("**Heat Distribution:**")
463
  col_rad, col_conv = st.columns(2)
464
 
 
482
  help="Fraction of sensible heat released as convection."
483
  )
484
 
 
485
  available_schedules = list(st.session_state.project_data["internal_loads"]["schedules"].keys())
486
  schedule = st.selectbox(
487
  "Schedule",
 
490
  help="Select the equipment schedule."
491
  )
492
 
 
493
  zone = st.text_input(
494
  "Zone",
495
  value=editor_state.get("zone", "Whole Building"),
496
  help="Zone or area where this equipment is located."
497
  )
498
 
 
499
  col1, col2 = st.columns(2)
500
  with col1:
501
  submit_label = "Update" if is_edit else "Add"
 
504
  with col2:
505
  refresh = st.form_submit_button("Refresh")
506
 
507
+ if submit:
508
+ action_id = str(uuid.uuid4())
509
+ if st.session_state.equipment_action.get("id") != action_id:
510
+ st.session_state.equipment_action = {"action": "save", "id": action_id}
511
+
512
+ if not name.strip():
513
+ st.error("Equipment name is required.")
514
+ return
515
+ if abs(radiative_fraction + convective_fraction - 1.0) > 0.01:
516
+ st.error("Radiative and convective fractions must sum to 1.0")
517
+ return
518
+ existing_names = [system["name"] for system in equipment_systems if not (is_edit and system["name"] == editor_state.get("name"))]
519
+ if name.strip() in existing_names:
520
+ st.error("Equipment name must be unique.")
521
+ return
522
+
523
+ equipment_data = {
524
+ "id": str(uuid.uuid4()),
525
+ "name": name.strip(),
526
+ "area": area,
527
+ "sensible_gain": sensible_gain,
528
+ "latent_gain": latent_gain,
529
+ "total_sensible_power": area * sensible_gain,
530
+ "total_latent_power": area * latent_gain,
531
+ "radiative_fraction": radiative_fraction,
532
+ "convective_fraction": convective_fraction,
533
+ "schedule": schedule,
534
+ "zone": zone
535
+ }
536
+
537
+ if is_edit:
538
+ index = editor_state.get("index", 0)
539
+ st.session_state.project_data["internal_loads"]["equipment"][index] = equipment_data
540
+ st.success(f"Equipment '{name}' updated!")
541
+ else:
542
+ st.session_state.project_data["internal_loads"]["equipment"].append(equipment_data)
543
+ st.success(f"Equipment '{name}' added!")
544
+
545
+ st.session_state.equipment_editor = {}
546
+ st.session_state.equipment_action = {"action": None, "id": None}
547
+ st.session_state.module_rerun_flags["internal_loads"] = True
548
+ st.rerun()
 
549
 
550
+ if refresh:
551
+ action_id = str(uuid.uuid4())
552
+ if st.session_state.equipment_action.get("id") != action_id:
553
+ st.session_state.equipment_action = {"action": "refresh", "id": action_id}
554
+ st.session_state.equipment_editor = {}
555
+ st.session_state.module_rerun_flags["internal_loads"] = True
556
+ st.rerun()
 
 
 
 
557
 
558
  def display_ventilation_infiltration_tab():
559
+ """Display the ventilation/infiltration tab content with a two-column layout similar to components.py."""
 
560
  vent_inf_systems = st.session_state.project_data["internal_loads"]["ventilation_infiltration"]
561
 
 
562
  col1, col2 = st.columns([3, 2])
563
 
564
  with col1:
 
571
  with col2:
572
  st.subheader("Ventilation/Infiltration Editor/Creator")
573
 
 
574
  if "vent_inf_editor" not in st.session_state:
575
  st.session_state.vent_inf_editor = {}
576
  if "vent_inf_action" not in st.session_state:
577
  st.session_state.vent_inf_action = {"action": None, "id": None}
578
 
 
 
 
 
 
579
  building_type = st.session_state.project_data["building_info"].get("building_type")
580
  default_building_data = DEFAULT_BUILDING_INTERNALS.get(building_type, DEFAULT_BUILDING_INTERNALS["Other"])
581
 
 
582
  with st.form("vent_inf_editor_form", clear_on_submit=True):
583
  editor_state = st.session_state.get("vent_inf_editor", {})
584
  is_edit = editor_state.get("is_edit", False)
585
 
 
586
  name = st.text_input(
587
  "System Name",
588
  value=editor_state.get("name", ""),
589
  help="Enter a unique name for this ventilation/infiltration system."
590
  )
591
 
 
592
  system_type = st.selectbox(
593
  "System Type",
594
  ["Ventilation", "Infiltration"],
 
596
  help="Select whether this is ventilation or infiltration."
597
  )
598
 
 
599
  area = st.number_input(
600
  "Area (m²)",
601
  min_value=1.0,
 
626
  )
627
  ventilation_rate = 0.0
628
 
 
629
  available_schedules = list(st.session_state.project_data["internal_loads"]["schedules"].keys())
630
  schedule = st.selectbox(
631
  "Schedule",
 
634
  help="Select the operation schedule for this system."
635
  )
636
 
 
637
  zone = st.text_input(
638
  "Zone",
639
  value=editor_state.get("zone", "Whole Building"),
640
  help="Zone or area where this system operates."
641
  )
642
 
 
643
  col1, col2 = st.columns(2)
644
  with col1:
645
  submit_label = "Update" if is_edit else "Add"
 
648
  with col2:
649
  refresh = st.form_submit_button("Refresh")
650
 
651
+ if submit:
652
+ action_id = str(uuid.uuid4())
653
+ if st.session_state.vent_inf_action.get("id") != action_id:
654
+ st.session_state.vent_inf_action = {"action": "save", "id": action_id}
655
+
656
+ if not name.strip():
657
+ st.error("System name is required.")
658
+ return
659
+ existing_names = [system["name"] for system in vent_inf_systems if not (is_edit and system["name"] == editor_state.get("name"))]
660
+ if name.strip() in existing_names:
661
+ st.error("System name must be unique.")
662
+ return
663
+
664
+ vent_inf_data = {
665
+ "id": str(uuid.uuid4()),
666
+ "name": name.strip(),
667
+ "system_type": system_type,
668
+ "area": area,
669
+ "ventilation_rate": ventilation_rate,
670
+ "air_change_rate": air_change_rate,
671
+ "schedule": schedule,
672
+ "zone": zone
673
+ }
674
+
675
+ if is_edit:
676
+ index = editor_state.get("index", 0)
677
+ st.session_state.project_data["internal_loads"]["ventilation_infiltration"][index] = vent_inf_data
678
+ st.success(f"{system_type} system '{name}' updated!")
679
+ else:
680
+ st.session_state.project_data["internal_loads"]["ventilation_infiltration"].append(vent_inf_data)
681
+ st.success(f"{system_type} system '{name}' added!")
682
+
683
+ st.session_state.vent_inf_editor = {}
684
+ st.session_state.vent_inf_action = {"action": None, "id": None}
685
+ st.session_state.module_rerun_flags["internal_loads"] = True
686
+ st.rerun()
687
 
688
+ if refresh:
689
+ action_id = str(uuid.uuid4())
690
+ if st.session_state.vent_inf_action.get("id") != action_id:
691
+ st.session_state.vent_inf_action = {"action": "refresh", "id": action_id}
692
+ st.session_state.vent_inf_editor = {}
693
+ st.session_state.module_rerun_flags["internal_loads"] = True
694
+ st.rerun()
 
 
 
 
695
 
696
  def display_schedules_tab():
 
697
  st.subheader("Schedules Editor")
698
+
699
  DEFAULT_STATE = {
700
  "name": "",
701
  "description": "",
 
705
  "is_edit": False,
706
  "original_name": ""
707
  }
708
+
 
709
  if "schedule_editor" not in st.session_state:
710
  st.session_state.schedule_editor = DEFAULT_STATE.copy()
711
  if "schedule_action" not in st.session_state:
712
  st.session_state.schedule_action = {"action": None, "id": None}
713
+
 
 
 
 
714
  editor_state = st.session_state.schedule_editor
715
  schedules = st.session_state.project_data["internal_loads"]["schedules"]
716
+
 
717
  template_options = list(DEFAULT_SCHEDULE_TEMPLATES.keys())
718
  selected_template = st.selectbox(
719
  "Select Template",
 
722
  if editor_state.get("template") in template_options else 0,
723
  help="Choose a base schedule to prefill values."
724
  )
725
+
726
+ if selected_template != editor_state.get("template", "None") and not editor_state.get("is_edit"):
727
+ action_id = str(uuid.uuid4())
728
+ if st.session_state.schedule_action.get("id") != action_id:
729
+ st.session_state.schedule_action = {"action": "template_change", "id": action_id}
730
+ st.session_state.schedule_editor["template"] = selected_template
731
+ if selected_template != "None":
732
+ tpl = DEFAULT_SCHEDULE_TEMPLATES[selected_template]
733
+ st.session_state.schedule_editor["weekday"] = tpl["weekday"]
734
+ st.session_state.schedule_editor["weekend"] = tpl["weekend"]
735
+ for hour in range(24):
736
+ st.session_state[f"weekday_slider_{hour}_value"] = tpl["weekday"][hour]
737
+ st.session_state[f"weekend_slider_{hour}_value"] = tpl["weekend"][hour]
738
+ else:
739
+ for hour in range(24):
740
+ st.session_state[f"weekday_slider_{hour}_value"] = 0.0
741
+ st.session_state[f"weekend_slider_{hour}_value"] = 0.0
742
+ st.session_state.schedule_action = {"action": None, "id": None}
743
+ st.session_state.module_rerun_flags["internal_loads"] = True
744
+ st.rerun()
745
+
746
+ with st.form("schedule_form"):
 
747
  name = st.text_input("Schedule Name", value=editor_state.get("name", ""))
748
  description = st.text_area("Description", value=editor_state.get("description", ""))
749
+
 
750
  st.markdown("### Schedule Sliders")
751
  col_hour, col_wd, col_we = st.columns([0.4, 2.0, 2.0])
752
  with col_hour:
 
755
  st.markdown("<div style='text-align:center; font-weight:bold; font-size:18px;'>Weekday</div>", unsafe_allow_html=True)
756
  with col_we:
757
  st.markdown("<div style='text-align:center; font-weight:bold; font-size:18px;'>Weekend</div>", unsafe_allow_html=True)
758
+
759
  hide_elements = """
760
  <style>
761
  div[data-testid="stSliderTickBarMin"],
 
765
  </style>
766
  """
767
  st.markdown(hide_elements, unsafe_allow_html=True)
768
+
769
  weekday_values = []
770
  weekend_values = []
771
+
772
  for hour in range(24):
773
  col_hour, col_wd, col_we = st.columns([0.4, 2.0, 2.0])
774
  with col_hour:
 
797
  format=None
798
  )
799
  weekend_values.append(val)
800
+
 
801
  col1, col2, col3 = st.columns(3)
802
  with col1:
803
  submit_label = "Update Schedule" if editor_state.get("is_edit") else "Save Schedule"
 
806
  edit_submitted = st.form_submit_button("Edit Selected Schedule")
807
  with col3:
808
  delete_submitted = st.form_submit_button("Delete Selected Schedule")
809
+
 
810
  st.markdown("### Saved Schedules")
811
  saved_schedule = st.selectbox(
812
  "Select Saved Schedule",
 
814
  index=list(schedules.keys()).index(editor_state.get("name", list(schedules.keys())[0])) if editor_state.get("name") in schedules and schedules else 0,
815
  help="Select a saved schedule to edit or delete."
816
  )
817
+
818
+ if submitted:
819
+ action_id = str(uuid.uuid4())
820
+ if st.session_state.schedule_action.get("id") != action_id:
821
+ st.session_state.schedule_action = {"action": "save", "id": action_id}
822
+ if not name.strip():
823
+ st.error("Schedule name is required.")
824
+ elif name in schedules and not editor_state.get("is_edit"):
825
+ st.error("A schedule with this name already exists.")
826
+ else:
827
+ schedules[name] = {
828
+ "description": description,
829
+ "weekday": weekday_values,
830
+ "weekend": weekend_values
831
+ }
832
+ if editor_state.get("is_edit") and editor_state.get("original_name") and editor_state.get("original_name") != name:
833
+ if editor_state["original_name"] in schedules:
834
+ del schedules[editor_state["original_name"]]
835
+ st.session_state.schedule_editor = DEFAULT_STATE.copy()
836
+ for hour in range(24):
837
+ st.session_state[f"weekday_slider_{hour}_value"] = 0.0
838
+ st.session_state[f"weekend_slider_{hour}_value"] = 0.0
839
+ st.success(f"Schedule '{name}' saved successfully.")
840
+ st.session_state.schedule_action = {"action": None, "id": None}
841
+ st.session_state.module_rerun_flags["internal_loads"] = True
842
+ st.rerun()
843
+
844
+ if edit_submitted and saved_schedule:
845
+ action_id = str(uuid.uuid4())
846
+ if st.session_state.schedule_action.get("id") != action_id:
847
+ st.session_state.schedule_action = {"action": "edit", "id": action_id}
848
+ if saved_schedule in DEFAULT_SCHEDULE_TEMPLATES:
849
+ st.error("Default schedules cannot be edited.")
850
+ else:
851
+ schedule_data = schedules[saved_schedule]
852
+ st.session_state.schedule_editor = {
853
+ "is_edit": True,
854
+ "original_name": saved_schedule,
855
+ "name": saved_schedule,
856
+ "description": schedule_data.get("description", ""),
857
+ "weekday": schedule_data.get("weekday", [0.0] * 24),
858
+ "weekend": schedule_data.get("weekend", [0.0] * 24),
859
+ "template": "None"
860
+ }
861
+ for hour in range(24):
862
+ st.session_state[f"weekday_slider_{hour}_value"] = schedule_data.get("weekday", [0.0] * 24)[hour]
863
+ st.session_state[f"weekend_slider_{hour}_value"] = schedule_data.get("weekend", [0.0] * 24)[hour]
864
+ st.session_state.schedule_action = {"action": None, "id": None}
865
+ st.session_state.module_rerun_flags["internal_loads"] = True
866
+ st.rerun()
867
+
868
+ if delete_submitted and saved_schedule:
869
+ action_id = str(uuid.uuid4())
870
+ if st.session_state.schedule_action.get("id") != action_id:
871
+ st.session_state.schedule_action = {"action": "delete", "id": action_id}
872
+ if saved_schedule in DEFAULT_SCHEDULE_TEMPLATES:
873
+ st.error("Default schedules cannot be deleted.")
874
+ elif is_schedule_in_use(saved_schedule):
875
+ st.error(f"Schedule '{saved_schedule}' is in use and cannot be deleted.")
876
+ else:
877
+ del schedules[saved_schedule]
878
+ st.session_state.schedule_editor = DEFAULT_STATE.copy()
879
+ for hour in range(24):
880
+ st.session_state[f"weekday_slider_{hour}_value"] = 0.0
881
+ st.session_state[f"weekend_slider_{hour}_value"] = 0.0
882
+ st.success(f"Schedule '{saved_schedule}' deleted!")
883
+ st.session_state.schedule_action = {"action": None, "id": None}
884
+ st.session_state.module_rerun_flags["internal_loads"] = True
885
+ st.rerun()
 
 
 
 
886
 
887
  def is_schedule_in_use(schedule_name: str) -> bool:
888
  """
 
910
  return False
911
 
912
  def display_people_table(people_groups: List[Dict[str, Any]]):
913
+ """Display people groups in a table format with edit/delete buttons."""
 
914
  cols = st.columns([2, 1, 1, 1, 1, 1, 1])
915
  cols[0].write("**Name**")
916
  cols[1].write("**Number**")
 
920
  cols[5].write("**Edit**")
921
  cols[6].write("**Delete**")
922
 
 
 
 
 
 
923
  for idx, group in enumerate(people_groups):
924
  cols = st.columns([2, 1, 1, 1, 1, 1, 1])
925
  cols[0].write(group["name"])
 
928
  cols[3].write(f"{group.get('total_latent_heat', 0):.1f}")
929
  cols[4].write(group.get("zone", "Unknown"))
930
 
 
931
  edit_key = f"edit_people_{group['id']}_{idx}"
932
  with cols[5].container():
933
+ if st.button("Edit", key=edit_key):
934
+ action_id = str(uuid.uuid4())
935
+ if st.session_state.people_action.get("id") != action_id:
936
+ st.session_state.people_action = {"action": "edit", "id": action_id}
937
+ st.session_state.people_editor = {
938
+ "index": idx,
939
+ "name": group.get("name", ""),
940
+ "num_people": group.get("num_people", 10),
941
+ "activity_level": group.get("activity_level", list(PEOPLE_ACTIVITY_LEVELS.keys())[0]),
942
+ "clo_summer": group.get("clo_summer", 0.5),
943
+ "clo_winter": group.get("clo_winter", 1.0),
944
+ "schedule": group.get("schedule", "Continuous"),
945
+ "zone": group.get("zone", "Whole Building"),
946
+ "is_edit": True
947
+ }
948
+ st.session_state.module_rerun_flags["internal_loads"] = True
949
+ st.rerun()
950
 
 
951
  delete_key = f"delete_people_{group['id']}_{idx}"
952
  with cols[6].container():
953
+ if st.button("Delete", key=delete_key):
954
+ action_id = str(uuid.uuid4())
955
+ if st.session_state.people_action.get("id") != action_id:
956
+ st.session_state.people_action = {"action": "delete", "id": action_id}
957
+ st.session_state.project_data["internal_loads"]["people"].pop(idx)
958
+ st.success(f"People group '{group['name']}' deleted!")
959
+ st.session_state.module_rerun_flags["internal_loads"] = True
960
+ st.rerun()
961
 
962
  def display_lighting_table(lighting_systems: List[Dict[str, Any]]):
963
+ """Display lighting systems in a table format with edit/delete buttons."""
 
964
  cols = st.columns([2, 1, 1, 1, 1, 1, 1])
965
  cols[0].write("**Name**")
966
  cols[1].write("**Area (m²)**")
 
970
  cols[5].write("**Edit**")
971
  cols[6].write("**Delete**")
972
 
 
 
 
 
 
973
  for idx, system in enumerate(lighting_systems):
974
  cols = st.columns([2, 1, 1, 1, 1, 1, 1])
975
  cols[0].write(system["name"])
 
978
  cols[3].write(f"{system.get('total_power', 0):.1f}")
979
  cols[4].write(system.get("zone", "Unknown"))
980
 
 
981
  edit_key = f"edit_lighting_{system['id']}_{idx}"
982
  with cols[5].container():
983
+ if st.button("Edit", key=edit_key):
984
+ action_id = str(uuid.uuid4())
985
+ if st.session_state.lighting_action.get("id") != action_id:
986
+ st.session_state.lighting_action = {"action": "edit", "id": action_id}
987
+ st.session_state.lighting_editor = {
988
+ "index": idx,
989
+ "name": system.get("name", ""),
990
+ "area": system.get("area", 100.0),
991
+ "lpd": system.get("lpd", 12.0),
992
+ "radiative_fraction": system.get("radiative_fraction", 0.4),
993
+ "convective_fraction": system.get("convective_fraction", 0.6),
994
+ "schedule": system.get("schedule", "Continuous"),
995
+ "zone": system.get("zone", "Whole Building"),
996
+ "is_edit": True
997
+ }
998
+ st.session_state.module_rerun_flags["internal_loads"] = True
999
+ st.rerun()
1000
 
 
1001
  delete_key = f"delete_lighting_{system['id']}_{idx}"
1002
  with cols[6].container():
1003
+ if st.button("Delete", key=delete_key):
1004
+ action_id = str(uuid.uuid4())
1005
+ if st.session_state.lighting_action.get("id") != action_id:
1006
+ st.session_state.lighting_action = {"action": "delete", "id": action_id}
1007
+ st.session_state.project_data["internal_loads"]["lighting"].pop(idx)
1008
+ st.success(f"Lighting system '{system['name']}' deleted!")
1009
+ st.session_state.module_rerun_flags["internal_loads"] = True
1010
+ st.rerun()
1011
 
1012
  def display_equipment_table(equipment_systems: List[Dict[str, Any]]):
1013
+ """Display equipment systems in a table format with edit/delete buttons."""
 
1014
  cols = st.columns([2, 1, 1, 1, 1, 1, 1])
1015
  cols[0].write("**Name**")
1016
  cols[1].write("**Area (m²)**")
 
1020
  cols[5].write("**Edit**")
1021
  cols[6].write("**Delete**")
1022
 
 
 
 
 
 
1023
  for idx, system in enumerate(equipment_systems):
1024
  cols = st.columns([2, 1, 1, 1, 1, 1, 1])
1025
  cols[0].write(system["name"])
 
1028
  cols[3].write(f"{system.get('total_latent_power', 0):.1f}")
1029
  cols[4].write(system.get("zone", "Unknown"))
1030
 
 
1031
  edit_key = f"edit_equipment_{system['id']}_{idx}"
1032
  with cols[5].container():
1033
+ if st.button("Edit", key=edit_key):
1034
+ action_id = str(uuid.uuid4())
1035
+ if st.session_state.equipment_action.get("id") != action_id:
1036
+ st.session_state.equipment_action = {"action": "edit", "id": action_id}
1037
+ st.session_state.equipment_editor = {
1038
+ "index": idx,
1039
+ "name": system.get("name", ""),
1040
+ "area": system.get("area", 100.0),
1041
+ "sensible_gain": system.get("sensible_gain", 10.0),
1042
+ "latent_gain": system.get("latent_gain", 2.0),
1043
+ "radiative_fraction": system.get("radiative_fraction", 0.5),
1044
+ "convective_fraction": system.get("convective_fraction", 0.5),
1045
+ "schedule": system.get("schedule", "Continuous"),
1046
+ "zone": system.get("zone", "Whole Building"),
1047
+ "is_edit": True
1048
+ }
1049
+ st.session_state.module_rerun_flags["internal_loads"] = True
1050
+ st.rerun()
1051
 
 
1052
  delete_key = f"delete_equipment_{system['id']}_{idx}"
1053
  with cols[6].container():
1054
+ if st.button("Delete", key=delete_key):
1055
+ action_id = str(uuid.uuid4())
1056
+ if st.session_state.equipment_action.get("id") != action_id:
1057
+ st.session_state.equipment_action = {"action": "delete", "id": action_id}
1058
+ st.session_state.project_data["internal_loads"]["equipment"].pop(idx)
1059
+ st.success(f"Equipment '{system['name']}' deleted!")
1060
+ st.session_state.module_rerun_flags["internal_loads"] = True
1061
+ st.rerun()
1062
 
1063
  def display_ventilation_infiltration_table(vent_inf_systems: List[Dict[str, Any]]):
1064
+ """Display ventilation/infiltration systems in a table format with edit/delete buttons."""
 
1065
  cols = st.columns([2, 1, 1, 1, 1, 1, 1])
1066
  cols[0].write("**Name**")
1067
  cols[1].write("**Type**")
 
1071
  cols[5].write("**Edit**")
1072
  cols[6].write("**Delete**")
1073
 
 
 
 
 
 
1074
  for idx, system in enumerate(vent_inf_systems):
1075
  cols = st.columns([2, 1, 1, 1, 1, 1, 1])
1076
  cols[0].write(system["name"])
 
1080
  cols[3].write(rate_info)
1081
  cols[4].write(system.get("zone", "Unknown"))
1082
 
 
1083
  edit_key = f"edit_vent_inf_{system['id']}_{idx}"
1084
  with cols[5].container():
1085
+ if st.button("Edit", key=edit_key):
1086
+ action_id = str(uuid.uuid4())
1087
+ if st.session_state.vent_inf_action.get("id") != action_id:
1088
+ st.session_state.vent_inf_action = {"action": "edit", "id": action_id}
1089
+ st.session_state.vent_inf_editor = {
1090
+ "index": idx,
1091
+ "name": system.get("name", ""),
1092
+ "system_type": system.get("system_type", "Ventilation"),
1093
+ "area": system.get("area", 100.0),
1094
+ "ventilation_rate": system.get("ventilation_rate", 10.0),
1095
+ "air_change_rate": system.get("air_change_rate", 1.0),
1096
+ "schedule": system.get("schedule", "Continuous"),
1097
+ "zone": system.get("zone", "Whole Building"),
1098
+ "is_edit": True
1099
+ }
1100
+ st.session_state.module_rerun_flags["internal_loads"] = True
1101
+ st.rerun()
1102
 
 
1103
  delete_key = f"delete_vent_inf_{system['id']}_{idx}"
1104
  with cols[6].container():
1105
+ if st.button("Delete", key=delete_key):
1106
+ action_id = str(uuid.uuid4())
1107
+ if st.session_state.vent_inf_action.get("id") != action_id:
1108
+ st.session_state.vent_inf_action = {"action": "delete", "id": action_id}
1109
+ st.session_state.project_data["internal_loads"]["ventilation_infiltration"].pop(idx)
1110
+ st.success(f"{system.get('system_type', 'System')} '{system['name']}' deleted!")
1111
+ st.session_state.module_rerun_flags["internal_loads"] = True
1112
+ st.rerun()
1113
 
1114
  def display_schedules_table(schedules: Dict[str, Any]):
1115
+ """Display schedules in a table format with edit/delete buttons."""
1116
  if not schedules:
1117
  return
1118
 
 
1119
  table_data = []
1120
  for name, schedule in schedules.items():
1121
  weekday_peak = max(schedule.get("weekday", [0]))
 
1131
 
1132
  if table_data:
1133
  df = pd.DataFrame(table_data)
 
 
1134
  st.dataframe(df.drop('Actions', axis=1), use_container_width=True)
1135
 
 
 
 
 
 
1136
  st.write("**Actions:**")
1137
  for i, row in enumerate(table_data):
1138
  schedule_name = row["Actions"]
 
1143
  st.write(f"{i+1}. {schedule_name}")
1144
 
1145
  with col2:
1146
+ if st.button("View", key=f"view_schedule_{schedule_name}_{i}"):
1147
+ action_id = str(uuid.uuid4())
1148
+ if st.session_state.schedule_action.get("id") != action_id:
1149
+ st.session_state.schedule_action = {"action": "view", "id": action_id}
1150
+ schedule_data = schedules[schedule_name]
1151
+ display_schedule_chart(schedule_name, schedule_data)
1152
+ st.session_state.schedule_action = {"action": None, "id": None}
1153
+ st.session_state.module_rerun_flags["internal_loads"] = True
1154
+ st.rerun()
1155
 
1156
  with col3:
1157
+ if st.button("Edit", key=f"edit_schedule_{schedule_name}_{i}"):
1158
+ action_id = str(uuid.uuid4())
1159
+ if st.session_state.schedule_action.get("id") != action_id:
1160
+ st.session_state.schedule_action = {"action": "edit", "id": action_id}
1161
+ if schedule_name in DEFAULT_SCHEDULE_TEMPLATES:
1162
+ st.error("Default schedules cannot be edited.")
1163
+ else:
1164
+ schedule_data = schedules[schedule_name]
1165
+ st.session_state.schedule_editor = {
1166
+ "is_edit": True,
1167
+ "original_name": schedule_name,
1168
+ "name": schedule_name,
1169
+ "description": schedule_data.get("description", ""),
1170
+ "weekday": schedule_data.get("weekday", [0.0] * 24),
1171
+ "weekend": schedule_data.get("weekend", [0.0] * 24)
1172
+ }
1173
+ for hour in range(24):
1174
+ st.session_state[f"weekday_slider_{hour}_value"] = schedule_data.get("weekday", [0.0] * 24)[hour]
1175
+ st.session_state[f"weekend_slider_{hour}_value"] = schedule_data.get("weekend", [0.0] * 24)[hour]
1176
+ st.session_state.module_rerun_flags["internal_loads"] = True
1177
+ st.rerun()
1178
 
1179
  with col4:
1180
  if schedule_name not in DEFAULT_SCHEDULE_TEMPLATES:
1181
  if is_schedule_in_use(schedule_name):
1182
  st.write("(In Use)")
1183
  else:
1184
+ if st.button("Delete", key=f"delete_schedule_{schedule_name}_{i}"):
1185
+ action_id = str(uuid.uuid4())
1186
+ if st.session_state.schedule_action.get("id") != action_id:
1187
+ st.session_state.schedule_action = {"action": "delete", "id": action_id}
1188
+ del schedules[schedule_name]
1189
+ st.success(f"Schedule '{schedule_name}' deleted!")
1190
+ st.session_state.module_rerun_flags["internal_loads"] = True
1191
+ st.rerun()
1192
  else:
1193
  st.write("(Default)")