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

Update app/internal_loads.py

Browse files
Files changed (1) hide show
  1. app/internal_loads.py +367 -337
app/internal_loads.py CHANGED
@@ -104,8 +104,10 @@ def initialize_internal_loads():
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,21 +120,25 @@ def display_people_tab():
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,6 +147,7 @@ def display_people_tab():
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,6 +155,7 @@ def display_people_tab():
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,6 +179,7 @@ def display_people_tab():
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,12 +188,14 @@ def display_people_tab():
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,64 +204,64 @@ def display_people_tab():
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,24 +274,29 @@ def display_lighting_tab():
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,6 +306,7 @@ def display_lighting_tab():
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,6 +316,7 @@ def display_lighting_tab():
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,6 +340,7 @@ def display_lighting_tab():
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,12 +349,14 @@ def display_lighting_tab():
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,59 +365,60 @@ def display_lighting_tab():
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,24 +431,29 @@ def display_equipment_tab():
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,6 +463,7 @@ def display_equipment_tab():
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,6 +487,7 @@ def display_equipment_tab():
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,6 +511,7 @@ def display_equipment_tab():
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,12 +520,14 @@ def display_equipment_tab():
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,61 +536,62 @@ def display_equipment_tab():
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,24 +604,29 @@ def display_ventilation_infiltration_tab():
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,6 +634,7 @@ def display_ventilation_infiltration_tab():
596
  help="Select whether this is ventilation or infiltration."
597
  )
598
 
 
599
  area = st.number_input(
600
  "Area (m²)",
601
  min_value=1.0,
@@ -606,6 +645,7 @@ def display_ventilation_infiltration_tab():
606
  )
607
 
608
  if system_type == "Ventilation":
 
609
  ventilation_rate = st.number_input(
610
  "Ventilation Rate (L/s·m²)",
611
  min_value=0.1,
@@ -616,6 +656,7 @@ def display_ventilation_infiltration_tab():
616
  )
617
  air_change_rate = 0.0
618
  else:
 
619
  air_change_rate = st.number_input(
620
  "Air Change Rate (ACH)",
621
  min_value=0.0,
@@ -626,6 +667,7 @@ def display_ventilation_infiltration_tab():
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,12 +676,14 @@ def display_ventilation_infiltration_tab():
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,52 +692,54 @@ def display_ventilation_infiltration_tab():
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 = {
@@ -706,6 +752,7 @@ def display_schedules_tab():
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:
@@ -714,6 +761,7 @@ def display_schedules_tab():
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",
@@ -723,30 +771,29 @@ def display_schedules_tab():
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:
@@ -798,6 +845,7 @@ def display_schedules_tab():
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"
@@ -807,6 +855,7 @@ def display_schedules_tab():
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",
@@ -815,6 +864,7 @@ def display_schedules_tab():
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:
@@ -841,6 +891,7 @@ def display_schedules_tab():
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:
@@ -865,6 +916,7 @@ def display_schedules_tab():
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:
@@ -911,6 +963,7 @@ def is_schedule_in_use(schedule_name: str) -> bool:
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,6 +973,7 @@ def display_people_table(people_groups: List[Dict[str, Any]]):
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,39 +982,36 @@ def display_people_table(people_groups: List[Dict[str, Any]]):
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,6 +1021,7 @@ def display_lighting_table(lighting_systems: List[Dict[str, Any]]):
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,39 +1030,36 @@ def display_lighting_table(lighting_systems: List[Dict[str, Any]]):
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,6 +1069,7 @@ def display_equipment_table(equipment_systems: List[Dict[str, Any]]):
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,40 +1078,37 @@ def display_equipment_table(equipment_systems: List[Dict[str, Any]]):
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,6 +1118,7 @@ def display_ventilation_infiltration_table(vent_inf_systems: List[Dict[str, Any]
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,42 +1128,39 @@ def display_ventilation_infiltration_table(vent_inf_systems: List[Dict[str, Any]
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,8 +1176,11 @@ def display_schedules_table(schedules: Dict[str, Any]):
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"]
@@ -1144,37 +1192,22 @@ def display_schedules_table(schedules: Dict[str, Any]):
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:
@@ -1182,12 +1215,9 @@ def display_schedules_table(schedules: Dict[str, Any]):
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)")
 
104
 
105
  def display_people_tab():
106
  """Display the people tab content with a two-column layout similar to components.py."""
107
+ # Get people from session state
108
  people_groups = st.session_state.project_data["internal_loads"]["people"]
109
 
110
+ # Split the display into two columns
111
  col1, col2 = st.columns([3, 2])
112
 
113
  with col1:
 
120
  with col2:
121
  st.subheader("People Group Editor/Creator")
122
 
123
+ # Initialize editor and action states
124
  if "people_editor" not in st.session_state:
125
  st.session_state.people_editor = {}
126
  if "people_action" not in st.session_state:
127
  st.session_state.people_action = {"action": None, "id": None}
128
 
129
+ # Display the editor form
130
  with st.form("people_editor_form", clear_on_submit=True):
131
  editor_state = st.session_state.get("people_editor", {})
132
  is_edit = editor_state.get("is_edit", False)
133
 
134
+ # Group name
135
  name = st.text_input(
136
  "Group Name",
137
  value=editor_state.get("name", ""),
138
  help="Enter a unique name for this people group."
139
  )
140
 
141
+ # Number of people
142
  num_people = st.number_input(
143
  "Number of People",
144
  min_value=1,
 
147
  help="Number of people in this group."
148
  )
149
 
150
+ # Activity level
151
  activity_level = st.selectbox(
152
  "Activity Level",
153
  list(PEOPLE_ACTIVITY_LEVELS.keys()),
 
155
  help="Select the activity level for this group."
156
  )
157
 
158
+ # Clothing insulation
159
  st.write("**Clothing Insulation:**")
160
  col_summer, col_winter = st.columns(2)
161
 
 
179
  help="Clothing insulation for winter conditions."
180
  )
181
 
182
+ # Schedule selection
183
  available_schedules = list(st.session_state.project_data["internal_loads"]["schedules"].keys())
184
  schedule = st.selectbox(
185
  "Schedule",
 
188
  help="Select the occupancy schedule for this group."
189
  )
190
 
191
+ # Zone assignment
192
  zone = st.text_input(
193
  "Zone",
194
  value=editor_state.get("zone", "Whole Building"),
195
  help="Zone or area where this people group is located."
196
  )
197
 
198
+ # Submit buttons
199
  col1, col2 = st.columns(2)
200
  with col1:
201
  submit_label = "Update" if is_edit else "Add"
 
204
  with col2:
205
  refresh = st.form_submit_button("Refresh")
206
 
207
+ # Handle form submission
208
+ if submit:
209
+ # Validate inputs
210
+ if not name.strip():
211
+ st.error("Group name is required.")
212
+ return
213
+ # Check for unique name
214
+ existing_names = [group["name"] for group in people_groups if not (is_edit and group["name"] == editor_state.get("name"))]
215
+ if name.strip() in existing_names:
216
+ st.error("Group name must be unique.")
217
+ return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
 
219
+ # Get activity data
220
+ activity_data = PEOPLE_ACTIVITY_LEVELS[activity_level]
221
+
222
+ # Create people group data
223
+ people_data = {
224
+ "id": str(uuid.uuid4()),
225
+ "name": name.strip(),
226
+ "num_people": num_people,
227
+ "activity_level": activity_level,
228
+ "activity_data": activity_data,
229
+ "clo_summer": clo_summer,
230
+ "clo_winter": clo_winter,
231
+ "schedule": schedule,
232
+ "zone": zone,
233
+ "sensible_heat_per_person": (activity_data["sensible_min_w"] + activity_data["sensible_max_w"]) / 2,
234
+ "latent_heat_per_person": (activity_data["latent_min_w"] + activity_data["latent_max_w"]) / 2,
235
+ "total_sensible_heat": num_people * (activity_data["sensible_min_w"] + activity_data["sensible_max_w"]) / 2,
236
+ "total_latent_heat": num_people * (activity_data["latent_min_w"] + activity_data["latent_max_w"]) / 2
237
+ }
238
+
239
+ # Handle edit mode
240
+ if is_edit:
241
+ index = editor_state.get("index", 0)
242
+ st.session_state.project_data["internal_loads"]["people"][index] = people_data
243
+ st.success(f"People group '{name}' updated!")
244
+ else:
245
+ st.session_state.project_data["internal_loads"]["people"].append(people_data)
246
+ st.success(f"People group '{name}' added!")
247
+
248
+ # Clear editor state
249
+ st.session_state.people_editor = {}
250
+ st.session_state.people_action = {"action": "save", "id": str(uuid.uuid4())}
251
+ st.session_state.module_rerun_flags["internal_loads"] = True
252
+
253
+ elif refresh:
254
+ # Clear editor state
255
+ st.session_state.people_editor = {}
256
+ st.session_state.people_action = {"action": "refresh", "id": str(uuid.uuid4())}
257
+ st.session_state.module_rerun_flags["internal_loads"] = True
258
 
259
  def display_lighting_tab():
260
  """Display the lighting tab content with a two-column layout similar to components.py."""
261
+ # Get lighting from session state
262
  lighting_systems = st.session_state.project_data["internal_loads"]["lighting"]
263
 
264
+ # Split the display into two columns
265
  col1, col2 = st.columns([3, 2])
266
 
267
  with col1:
 
274
  with col2:
275
  st.subheader("Lighting System Editor/Creator")
276
 
277
+ # Initialize editor and action states
278
  if "lighting_editor" not in st.session_state:
279
  st.session_state.lighting_editor = {}
280
  if "lighting_action" not in st.session_state:
281
  st.session_state.lighting_action = {"action": None, "id": None}
282
 
283
+ # Get building type for default values
284
  building_type = st.session_state.project_data["building_info"].get("building_type")
285
  default_lighting_density = DEFAULT_BUILDING_INTERNALS.get(building_type, DEFAULT_BUILDING_INTERNALS["Other"])["lighting_density"]
286
 
287
+ # Display the editor form
288
  with st.form("lighting_editor_form", clear_on_submit=True):
289
  editor_state = st.session_state.get("lighting_editor", {})
290
  is_edit = editor_state.get("is_edit", False)
291
 
292
+ # System name
293
  name = st.text_input(
294
  "System Name",
295
  value=editor_state.get("name", ""),
296
  help="Enter a unique name for this lighting system."
297
  )
298
 
299
+ # Area
300
  area = st.number_input(
301
  "Area (m²)",
302
  min_value=1.0,
 
306
  help="Floor area served by this lighting system."
307
  )
308
 
309
+ # Lighting power density
310
  lpd = st.number_input(
311
  "Lighting Power Density (W/m²)",
312
  min_value=0.1,
 
316
  help="Lighting power density in watts per square meter."
317
  )
318
 
319
+ # Heat fractions
320
  st.write("**Heat Distribution:**")
321
  col_rad, col_conv = st.columns(2)
322
 
 
340
  help="Fraction of heat released as convection."
341
  )
342
 
343
+ # Schedule selection
344
  available_schedules = list(st.session_state.project_data["internal_loads"]["schedules"].keys())
345
  schedule = st.selectbox(
346
  "Schedule",
 
349
  help="Select the lighting schedule for this system."
350
  )
351
 
352
+ # Zone assignment
353
  zone = st.text_input(
354
  "Zone",
355
  value=editor_state.get("zone", "Whole Building"),
356
  help="Zone or area where this lighting system is located."
357
  )
358
 
359
+ # Submit buttons
360
  col1, col2 = st.columns(2)
361
  with col1:
362
  submit_label = "Update" if is_edit else "Add"
 
365
  with col2:
366
  refresh = st.form_submit_button("Refresh")
367
 
368
+ # Handle form submission
369
+ if submit:
370
+ # Validate inputs
371
+ if not name.strip():
372
+ st.error("System name is required.")
373
+ return
374
+ if abs(radiative_fraction + convective_fraction - 1.0) > 0.01:
375
+ st.error("Radiative and convective fractions must sum to 1.0")
376
+ return
377
+ # Check for unique name
378
+ existing_names = [system["name"] for system in lighting_systems if not (is_edit and system["name"] == editor_state.get("name"))]
379
+ if name.strip() in existing_names:
380
+ st.error("System name must be unique.")
381
+ return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
382
 
383
+ # Create lighting system data
384
+ lighting_data = {
385
+ "id": str(uuid.uuid4()),
386
+ "name": name.strip(),
387
+ "area": area,
388
+ "lpd": lpd,
389
+ "total_power": area * lpd,
390
+ "radiative_fraction": radiative_fraction,
391
+ "convective_fraction": convective_fraction,
392
+ "schedule": schedule,
393
+ "zone": zone
394
+ }
395
+
396
+ # Handle edit mode
397
+ if is_edit:
398
+ index = editor_state.get("index", 0)
399
+ st.session_state.project_data["internal_loads"]["lighting"][index] = lighting_data
400
+ st.success(f"Lighting system '{name}' updated!")
401
+ else:
402
+ st.session_state.project_data["internal_loads"]["lighting"].append(lighting_data)
403
+ st.success(f"Lighting system '{name}' added!")
404
+
405
+ # Clear editor state
406
+ st.session_state.lighting_editor = {}
407
+ st.session_state.lighting_action = {"action": "save", "id": str(uuid.uuid4())}
408
+ st.session_state.module_rerun_flags["internal_loads"] = True
409
+
410
+ elif refresh:
411
+ # Clear editor state
412
+ st.session_state.lighting_editor = {}
413
+ st.session_state.lighting_action = {"action": "refresh", "id": str(uuid.uuid4())}
414
+ st.session_state.module_rerun_flags["internal_loads"] = True
415
 
416
  def display_equipment_tab():
417
  """Display the equipment tab content with a two-column layout similar to components.py."""
418
+ # Get equipment from session state
419
  equipment_systems = st.session_state.project_data["internal_loads"]["equipment"]
420
 
421
+ # Split the display into two columns
422
  col1, col2 = st.columns([3, 2])
423
 
424
  with col1:
 
431
  with col2:
432
  st.subheader("Equipment Editor/Creator")
433
 
434
+ # Initialize editor and action states
435
  if "equipment_editor" not in st.session_state:
436
  st.session_state.equipment_editor = {}
437
  if "equipment_action" not in st.session_state:
438
  st.session_state.equipment_action = {"action": None, "id": None}
439
 
440
+ # Get building type for default values
441
  building_type = st.session_state.project_data["building_info"].get("building_type")
442
  default_equipment_data = DEFAULT_BUILDING_INTERNALS.get(building_type, DEFAULT_BUILDING_INTERNALS["Other"])["equipment_heat_gains"]
443
 
444
+ # Display the editor form
445
  with st.form("equipment_editor_form", clear_on_submit=True):
446
  editor_state = st.session_state.get("equipment_editor", {})
447
  is_edit = editor_state.get("is_edit", False)
448
 
449
+ # Equipment name
450
  name = st.text_input(
451
  "Equipment Name",
452
  value=editor_state.get("name", ""),
453
  help="Enter a unique name for this equipment."
454
  )
455
 
456
+ # Area
457
  area = st.number_input(
458
  "Area (m²)",
459
  min_value=1.0,
 
463
  help="Floor area served by this equipment."
464
  )
465
 
466
+ # Heat gains
467
  st.write("**Heat Gains (W/m²):**")
468
  col_sens, col_lat = st.columns(2)
469
 
 
487
  help="Latent heat gain in watts per square meter."
488
  )
489
 
490
+ # Heat distribution
491
  st.write("**Heat Distribution:**")
492
  col_rad, col_conv = st.columns(2)
493
 
 
511
  help="Fraction of sensible heat released as convection."
512
  )
513
 
514
+ # Schedule selection
515
  available_schedules = list(st.session_state.project_data["internal_loads"]["schedules"].keys())
516
  schedule = st.selectbox(
517
  "Schedule",
 
520
  help="Select the equipment schedule."
521
  )
522
 
523
+ # Zone assignment
524
  zone = st.text_input(
525
  "Zone",
526
  value=editor_state.get("zone", "Whole Building"),
527
  help="Zone or area where this equipment is located."
528
  )
529
 
530
+ # Submit buttons
531
  col1, col2 = st.columns(2)
532
  with col1:
533
  submit_label = "Update" if is_edit else "Add"
 
536
  with col2:
537
  refresh = st.form_submit_button("Refresh")
538
 
539
+ # Handle form submission
540
+ if submit:
541
+ # Validate inputs
542
+ if not name.strip():
543
+ st.error("Equipment name is required.")
544
+ return
545
+ if abs(radiative_fraction + convective_fraction - 1.0) > 0.01:
546
+ st.error("Radiative and convective fractions must sum to 1.0")
547
+ return
548
+ # Check for unique name
549
+ existing_names = [system["name"] for system in equipment_systems if not (is_edit and system["name"] == editor_state.get("name"))]
550
+ if name.strip() in existing_names:
551
+ st.error("Equipment name must be unique.")
552
+ return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
553
 
554
+ # Create equipment data
555
+ equipment_data = {
556
+ "id": str(uuid.uuid4()),
557
+ "name": name.strip(),
558
+ "area": area,
559
+ "sensible_gain": sensible_gain,
560
+ "latent_gain": latent_gain,
561
+ "total_sensible_power": area * sensible_gain,
562
+ "total_latent_power": area * latent_gain,
563
+ "radiative_fraction": radiative_fraction,
564
+ "convective_fraction": convective_fraction,
565
+ "schedule": schedule,
566
+ "zone": zone
567
+ }
568
+
569
+ # Handle edit mode
570
+ if is_edit:
571
+ index = editor_state.get("index", 0)
572
+ st.session_state.project_data["internal_loads"]["equipment"][index] = equipment_data
573
+ st.success(f"Equipment '{name}' updated!")
574
+ else:
575
+ st.session_state.project_data["internal_loads"]["equipment"].append(equipment_data)
576
+ st.success(f"Equipment '{name}' added!")
577
+
578
+ # Clear editor state
579
+ st.session_state.equipment_editor = {}
580
+ st.session_state.equipment_action = {"action": "save", "id": str(uuid.uuid4())}
581
+ st.session_state.module_rerun_flags["internal_loads"] = True
582
+
583
+ elif refresh:
584
+ # Clear editor state
585
+ st.session_state.equipment_editor = {}
586
+ st.session_state.equipment_action = {"action": "refresh", "id": str(uuid.uuid4())}
587
+ st.session_state.module_rerun_flags["internal_loads"] = True
588
 
589
  def display_ventilation_infiltration_tab():
590
  """Display the ventilation/infiltration tab content with a two-column layout similar to components.py."""
591
+ # Get ventilation/infiltration from session state
592
  vent_inf_systems = st.session_state.project_data["internal_loads"]["ventilation_infiltration"]
593
 
594
+ # Split the display into two columns
595
  col1, col2 = st.columns([3, 2])
596
 
597
  with col1:
 
604
  with col2:
605
  st.subheader("Ventilation/Infiltration Editor/Creator")
606
 
607
+ # Initialize editor and action states
608
  if "vent_inf_editor" not in st.session_state:
609
  st.session_state.vent_inf_editor = {}
610
  if "vent_inf_action" not in st.session_state:
611
  st.session_state.vent_inf_action = {"action": None, "id": None}
612
 
613
+ # Get building type for default values
614
  building_type = st.session_state.project_data["building_info"].get("building_type")
615
  default_building_data = DEFAULT_BUILDING_INTERNALS.get(building_type, DEFAULT_BUILDING_INTERNALS["Other"])
616
 
617
+ # Display the editor form
618
  with st.form("vent_inf_editor_form", clear_on_submit=True):
619
  editor_state = st.session_state.get("vent_inf_editor", {})
620
  is_edit = editor_state.get("is_edit", False)
621
 
622
+ # System name
623
  name = st.text_input(
624
  "System Name",
625
  value=editor_state.get("name", ""),
626
  help="Enter a unique name for this ventilation/infiltration system."
627
  )
628
 
629
+ # System type
630
  system_type = st.selectbox(
631
  "System Type",
632
  ["Ventilation", "Infiltration"],
 
634
  help="Select whether this is ventilation or infiltration."
635
  )
636
 
637
+ # Area
638
  area = st.number_input(
639
  "Area (m²)",
640
  min_value=1.0,
 
645
  )
646
 
647
  if system_type == "Ventilation":
648
+ # Ventilation rate
649
  ventilation_rate = st.number_input(
650
  "Ventilation Rate (L/s·m²)",
651
  min_value=0.1,
 
656
  )
657
  air_change_rate = 0.0
658
  else:
659
+ # Air change rate for infiltration
660
  air_change_rate = st.number_input(
661
  "Air Change Rate (ACH)",
662
  min_value=0.0,
 
667
  )
668
  ventilation_rate = 0.0
669
 
670
+ # Schedule selection
671
  available_schedules = list(st.session_state.project_data["internal_loads"]["schedules"].keys())
672
  schedule = st.selectbox(
673
  "Schedule",
 
676
  help="Select the operation schedule for this system."
677
  )
678
 
679
+ # Zone assignment
680
  zone = st.text_input(
681
  "Zone",
682
  value=editor_state.get("zone", "Whole Building"),
683
  help="Zone or area where this system operates."
684
  )
685
 
686
+ # Submit buttons
687
  col1, col2 = st.columns(2)
688
  with col1:
689
  submit_label = "Update" if is_edit else "Add"
 
692
  with col2:
693
  refresh = st.form_submit_button("Refresh")
694
 
695
+ # Handle form submission
696
+ if submit:
697
+ # Validate inputs
698
+ if not name.strip():
699
+ st.error("System name is required.")
700
+ return
701
+ # Check for unique name
702
+ existing_names = [system["name"] for system in vent_inf_systems if not (is_edit and system["name"] == editor_state.get("name"))]
703
+ if name.strip() in existing_names:
704
+ st.error("System name must be unique.")
705
+ return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
706
 
707
+ # Create ventilation/infiltration data
708
+ vent_inf_data = {
709
+ "id": str(uuid.uuid4()),
710
+ "name": name.strip(),
711
+ "system_type": system_type,
712
+ "area": area,
713
+ "ventilation_rate": ventilation_rate,
714
+ "air_change_rate": air_change_rate,
715
+ "schedule": schedule,
716
+ "zone": zone
717
+ }
718
+
719
+ # Handle edit mode
720
+ if is_edit:
721
+ index = editor_state.get("index", 0)
722
+ st.session_state.project_data["internal_loads"]["ventilation_infiltration"][index] = vent_inf_data
723
+ st.success(f"{system_type} system '{name}' updated!")
724
+ else:
725
+ st.session_state.project_data["internal_loads"]["ventilation_infiltration"].append(vent_inf_data)
726
+ st.success(f"{system_type} system '{name}' added!")
727
+
728
+ # Clear editor state
729
+ st.session_state.vent_inf_editor = {}
730
+ st.session_state.vent_inf_action = {"action": "save", "id": str(uuid.uuid4())}
731
+ st.session_state.module_rerun_flags["internal_loads"] = True
732
+
733
+ elif refresh:
734
+ # Clear editor state
735
+ st.session_state.vent_inf_editor = {}
736
+ st.session_state.vent_inf_action = {"action": "refresh", "id": str(uuid.uuid4())}
737
+ st.session_state.module_rerun_flags["internal_loads"] = True
738
 
739
  def display_schedules_tab():
740
+ import streamlit as st
741
+ import uuid
742
+
743
  st.subheader("Schedules Editor")
744
 
745
  DEFAULT_STATE = {
 
752
  "original_name": ""
753
  }
754
 
755
+ # Initialize schedule_editor and schedule_action if not present
756
  if "schedule_editor" not in st.session_state:
757
  st.session_state.schedule_editor = DEFAULT_STATE.copy()
758
  if "schedule_action" not in st.session_state:
 
761
  editor_state = st.session_state.schedule_editor
762
  schedules = st.session_state.project_data["internal_loads"]["schedules"]
763
 
764
+ # Handle template change
765
  template_options = list(DEFAULT_SCHEDULE_TEMPLATES.keys())
766
  selected_template = st.selectbox(
767
  "Select Template",
 
771
  help="Choose a base schedule to prefill values."
772
  )
773
 
774
+ # Update sliders only if template changes and not in edit mode
775
  if selected_template != editor_state.get("template", "None") and not editor_state.get("is_edit"):
776
+ st.session_state.schedule_editor["template"] = selected_template
777
+ if selected_template != "None":
778
+ tpl = DEFAULT_SCHEDULE_TEMPLATES[selected_template]
779
+ st.session_state.schedule_editor["weekday"] = tpl["weekday"]
780
+ st.session_state.schedule_editor["weekend"] = tpl["weekend"]
781
+ for hour in range(24):
782
+ st.session_state[f"weekday_slider_{hour}_value"] = tpl["weekday"][hour]
783
+ st.session_state[f"weekend_slider_{hour}_value"] = tpl["weekend"][hour]
784
+ else:
785
+ for hour in range(24):
786
+ st.session_state[f"weekday_slider_{hour}_value"] = 0.0
787
+ st.session_state[f"weekend_slider_{hour}_value"] = 0.0
788
+ st.session_state.module_rerun_flags["internal_loads"] = True
789
+ st.rerun()
 
 
 
 
790
 
791
+ # UI FORM for name/description and actions
792
  with st.form("schedule_form"):
793
  name = st.text_input("Schedule Name", value=editor_state.get("name", ""))
794
  description = st.text_area("Description", value=editor_state.get("description", ""))
795
 
796
+ # SLIDERS LAYOUT
797
  st.markdown("### Schedule Sliders")
798
  col_hour, col_wd, col_we = st.columns([0.4, 2.0, 2.0])
799
  with col_hour:
 
845
  )
846
  weekend_values.append(val)
847
 
848
+ # Action Buttons
849
  col1, col2, col3 = st.columns(3)
850
  with col1:
851
  submit_label = "Update Schedule" if editor_state.get("is_edit") else "Save Schedule"
 
855
  with col3:
856
  delete_submitted = st.form_submit_button("Delete Selected Schedule")
857
 
858
+ # Saved Schedules Selector
859
  st.markdown("### Saved Schedules")
860
  saved_schedule = st.selectbox(
861
  "Select Saved Schedule",
 
864
  help="Select a saved schedule to edit or delete."
865
  )
866
 
867
+ # Save logic
868
  if submitted:
869
  action_id = str(uuid.uuid4())
870
  if st.session_state.schedule_action.get("id") != action_id:
 
891
  st.session_state.module_rerun_flags["internal_loads"] = True
892
  st.rerun()
893
 
894
+ # Edit logic
895
  if edit_submitted and saved_schedule:
896
  action_id = str(uuid.uuid4())
897
  if st.session_state.schedule_action.get("id") != action_id:
 
916
  st.session_state.module_rerun_flags["internal_loads"] = True
917
  st.rerun()
918
 
919
+ # Delete logic
920
  if delete_submitted and saved_schedule:
921
  action_id = str(uuid.uuid4())
922
  if st.session_state.schedule_action.get("id") != action_id:
 
963
 
964
  def display_people_table(people_groups: List[Dict[str, Any]]):
965
  """Display people groups in a table format with edit/delete buttons."""
966
+ # Create column headers
967
  cols = st.columns([2, 1, 1, 1, 1, 1, 1])
968
  cols[0].write("**Name**")
969
  cols[1].write("**Number**")
 
973
  cols[5].write("**Edit**")
974
  cols[6].write("**Delete**")
975
 
976
+ # Display each group
977
  for idx, group in enumerate(people_groups):
978
  cols = st.columns([2, 1, 1, 1, 1, 1, 1])
979
  cols[0].write(group["name"])
 
982
  cols[3].write(f"{group.get('total_latent_heat', 0):.1f}")
983
  cols[4].write(group.get("zone", "Unknown"))
984
 
985
+ # Edit button
986
  edit_key = f"edit_people_{group['id']}_{idx}"
987
  with cols[5].container():
988
  if st.button("Edit", key=edit_key):
989
+ st.session_state.people_editor = {
990
+ "index": idx,
991
+ "name": group.get("name", ""),
992
+ "num_people": group.get("num_people", 10),
993
+ "activity_level": group.get("activity_level", list(PEOPLE_ACTIVITY_LEVELS.keys())[0]),
994
+ "clo_summer": group.get("clo_summer", 0.5),
995
+ "clo_winter": group.get("clo_winter", 1.0),
996
+ "schedule": group.get("schedule", "Continuous"),
997
+ "zone": group.get("zone", "Whole Building"),
998
+ "is_edit": True
999
+ }
1000
+ st.session_state.people_action = {"action": "edit", "id": str(uuid.uuid4())}
1001
+ st.session_state.module_rerun_flags["internal_loads"] = True
 
 
 
1002
 
1003
+ # Delete button
1004
  delete_key = f"delete_people_{group['id']}_{idx}"
1005
  with cols[6].container():
1006
  if st.button("Delete", key=delete_key):
1007
+ st.session_state.project_data["internal_loads"]["people"].pop(idx)
1008
+ st.success(f"People group '{group['name']}' deleted!")
1009
+ st.session_state.people_action = {"action": "delete", "id": str(uuid.uuid4())}
1010
+ st.session_state.module_rerun_flags["internal_loads"] = True
 
 
 
1011
 
1012
  def display_lighting_table(lighting_systems: List[Dict[str, Any]]):
1013
  """Display lighting systems in a table format with edit/delete buttons."""
1014
+ # Create column headers
1015
  cols = st.columns([2, 1, 1, 1, 1, 1, 1])
1016
  cols[0].write("**Name**")
1017
  cols[1].write("**Area (m²)**")
 
1021
  cols[5].write("**Edit**")
1022
  cols[6].write("**Delete**")
1023
 
1024
+ # Display each system
1025
  for idx, system in enumerate(lighting_systems):
1026
  cols = st.columns([2, 1, 1, 1, 1, 1, 1])
1027
  cols[0].write(system["name"])
 
1030
  cols[3].write(f"{system.get('total_power', 0):.1f}")
1031
  cols[4].write(system.get("zone", "Unknown"))
1032
 
1033
+ # Edit button
1034
  edit_key = f"edit_lighting_{system['id']}_{idx}"
1035
  with cols[5].container():
1036
  if st.button("Edit", key=edit_key):
1037
+ st.session_state.lighting_editor = {
1038
+ "index": idx,
1039
+ "name": system.get("name", ""),
1040
+ "area": system.get("area", 100.0),
1041
+ "lpd": system.get("lpd", 12.0),
1042
+ "radiative_fraction": system.get("radiative_fraction", 0.4),
1043
+ "convective_fraction": system.get("convective_fraction", 0.6),
1044
+ "schedule": system.get("schedule", "Continuous"),
1045
+ "zone": system.get("zone", "Whole Building"),
1046
+ "is_edit": True
1047
+ }
1048
+ st.session_state.lighting_action = {"action": "edit", "id": str(uuid.uuid4())}
1049
+ st.session_state.module_rerun_flags["internal_loads"] = True
 
 
 
1050
 
1051
+ # Delete button
1052
  delete_key = f"delete_lighting_{system['id']}_{idx}"
1053
  with cols[6].container():
1054
  if st.button("Delete", key=delete_key):
1055
+ st.session_state.project_data["internal_loads"]["lighting"].pop(idx)
1056
+ st.success(f"Lighting system '{system['name']}' deleted!")
1057
+ st.session_state.lighting_action = {"action": "delete", "id": str(uuid.uuid4())}
1058
+ st.session_state.module_rerun_flags["internal_loads"] = True
 
 
 
1059
 
1060
  def display_equipment_table(equipment_systems: List[Dict[str, Any]]):
1061
  """Display equipment systems in a table format with edit/delete buttons."""
1062
+ # Create column headers
1063
  cols = st.columns([2, 1, 1, 1, 1, 1, 1])
1064
  cols[0].write("**Name**")
1065
  cols[1].write("**Area (m²)**")
 
1069
  cols[5].write("**Edit**")
1070
  cols[6].write("**Delete**")
1071
 
1072
+ # Display each system
1073
  for idx, system in enumerate(equipment_systems):
1074
  cols = st.columns([2, 1, 1, 1, 1, 1, 1])
1075
  cols[0].write(system["name"])
 
1078
  cols[3].write(f"{system.get('total_latent_power', 0):.1f}")
1079
  cols[4].write(system.get("zone", "Unknown"))
1080
 
1081
+ # Edit button
1082
  edit_key = f"edit_equipment_{system['id']}_{idx}"
1083
  with cols[5].container():
1084
  if st.button("Edit", key=edit_key):
1085
+ st.session_state.equipment_editor = {
1086
+ "index": idx,
1087
+ "name": system.get("name", ""),
1088
+ "area": system.get("area", 100.0),
1089
+ "sensible_gain": system.get("sensible_gain", 10.0),
1090
+ "latent_gain": system.get("latent_gain", 2.0),
1091
+ "radiative_fraction": system.get("radiative_fraction", 0.5),
1092
+ "convective_fraction": system.get("convective_fraction", 0.5),
1093
+ "schedule": system.get("schedule", "Continuous"),
1094
+ "zone": system.get("zone", "Whole Building"),
1095
+ "is_edit": True
1096
+ }
1097
+ st.session_state.equipment_action = {"action": "edit", "id": str(uuid.uuid4())}
1098
+ st.session_state.module_rerun_flags["internal_loads"] = True
 
 
 
1099
 
1100
+ # Delete button
1101
  delete_key = f"delete_equipment_{system['id']}_{idx}"
1102
  with cols[6].container():
1103
  if st.button("Delete", key=delete_key):
1104
+ st.session_state.project_data["internal_loads"]["equipment"].pop(idx)
1105
+ st.success(f"Equipment '{system['name']}' deleted!")
1106
+ st.session_state.equipment_action = {"action": "delete", "id": str(uuid.uuid4())}
1107
+ st.session_state.module_rerun_flags["internal_loads"] = True
 
 
 
1108
 
1109
  def display_ventilation_infiltration_table(vent_inf_systems: List[Dict[str, Any]]):
1110
  """Display ventilation/infiltration systems in a table format with edit/delete buttons."""
1111
+ # Create column headers
1112
  cols = st.columns([2, 1, 1, 1, 1, 1, 1])
1113
  cols[0].write("**Name**")
1114
  cols[1].write("**Type**")
 
1118
  cols[5].write("**Edit**")
1119
  cols[6].write("**Delete**")
1120
 
1121
+ # Display each system
1122
  for idx, system in enumerate(vent_inf_systems):
1123
  cols = st.columns([2, 1, 1, 1, 1, 1, 1])
1124
  cols[0].write(system["name"])
 
1128
  cols[3].write(rate_info)
1129
  cols[4].write(system.get("zone", "Unknown"))
1130
 
1131
+ # Edit button
1132
  edit_key = f"edit_vent_inf_{system['id']}_{idx}"
1133
  with cols[5].container():
1134
  if st.button("Edit", key=edit_key):
1135
+ st.session_state.vent_inf_editor = {
1136
+ "index": idx,
1137
+ "name": system.get("name", ""),
1138
+ "system_type": system.get("system_type", "Ventilation"),
1139
+ "area": system.get("area", 100.0),
1140
+ "ventilation_rate": system.get("ventilation_rate", 10.0),
1141
+ "air_change_rate": system.get("air_change_rate", 1.0),
1142
+ "schedule": system.get("schedule", "Continuous"),
1143
+ "zone": system.get("zone", "Whole Building"),
1144
+ "is_edit": True
1145
+ }
1146
+ st.session_state.vent_inf_action = {"action": "edit", "id": str(uuid.uuid4())}
1147
+ st.session_state.module_rerun_flags["internal_loads"] = True
 
 
 
1148
 
1149
+ # Delete button
1150
  delete_key = f"delete_vent_inf_{system['id']}_{idx}"
1151
  with cols[6].container():
1152
  if st.button("Delete", key=delete_key):
1153
+ st.session_state.project_data["internal_loads"]["ventilation_infiltration"].pop(idx)
1154
+ st.success(f"{system.get('system_type', 'System')} '{system['name']}' deleted!")
1155
+ st.session_state.vent_inf_action = {"action": "delete", "id": str(uuid.uuid4())}
1156
+ st.session_state.module_rerun_flags["internal_loads"] = True
 
 
 
1157
 
1158
  def display_schedules_table(schedules: Dict[str, Any]):
1159
  """Display schedules in a table format with edit/delete buttons."""
1160
  if not schedules:
1161
  return
1162
 
1163
+ # Create table data
1164
  table_data = []
1165
  for name, schedule in schedules.items():
1166
  weekday_peak = max(schedule.get("weekday", [0]))
 
1176
 
1177
  if table_data:
1178
  df = pd.DataFrame(table_data)
1179
+
1180
+ # Display table
1181
  st.dataframe(df.drop('Actions', axis=1), use_container_width=True)
1182
 
1183
+ # Display action buttons
1184
  st.write("**Actions:**")
1185
  for i, row in enumerate(table_data):
1186
  schedule_name = row["Actions"]
 
1192
 
1193
  with col2:
1194
  if st.button("View", key=f"view_schedule_{schedule_name}_{i}"):
1195
+ schedule_data = schedules[schedule_name]
1196
+ display_schedule_chart(schedule_name, schedule_data)
 
 
 
 
 
 
1197
 
1198
  with col3:
1199
  if st.button("Edit", key=f"edit_schedule_{schedule_name}_{i}"):
1200
+ schedule_data = schedules[schedule_name]
1201
+ st.session_state.schedule_editor = {
1202
+ "is_edit": True,
1203
+ "original_name": schedule_name,
1204
+ "name": schedule_name,
1205
+ "description": schedule_data.get("description", ""),
1206
+ "weekday": schedule_data.get("weekday", [0.0] * 24),
1207
+ "weekend": schedule_data.get("weekend", [0.0] * 24)
1208
+ }
1209
+ st.session_state.module_rerun_flags["internal_loads"] = True
1210
+ st.rerun()
 
 
 
 
 
 
 
 
 
1211
 
1212
  with col4:
1213
  if schedule_name not in DEFAULT_SCHEDULE_TEMPLATES:
 
1215
  st.write("(In Use)")
1216
  else:
1217
  if st.button("Delete", key=f"delete_schedule_{schedule_name}_{i}"):
1218
+ del schedules[schedule_name]
1219
+ st.success(f"Schedule '{schedule_name}' deleted!")
1220
+ st.session_state.module_rerun_flags["internal_loads"] = True
1221
+ st.rerun()
 
 
 
1222
  else:
1223
  st.write("(Default)")