mabuseif commited on
Commit
266fe45
verified
1 Parent(s): 297bc15

Update app/construction.py

Browse files
Files changed (1) hide show
  1. app/construction.py +379 -594
app/construction.py CHANGED
@@ -18,6 +18,9 @@ import logging
18
  import uuid
19
  from typing import Dict, List, Any, Optional, Tuple, Union
20
 
 
 
 
21
  # Configure logging
22
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
23
  logger = logging.getLogger(__name__)
@@ -25,169 +28,13 @@ logger = logging.getLogger(__name__)
25
  # Define constants
26
  CONSTRUCTION_TYPES = ["Wall", "Roof", "Floor"]
27
 
28
- # Default library constructions
29
- DEFAULT_CONSTRUCTIONS = {
30
- "Brick Cavity Wall": {
31
- "type": "Wall",
32
- "layers": [
33
- {"material": "Brick", "thickness": 0.1},
34
- {"material": "Air Gap", "thickness": 0.05},
35
- {"material": "Mineral Wool", "thickness": 0.1},
36
- {"material": "Concrete Block", "thickness": 0.1},
37
- {"material": "Gypsum Board", "thickness": 0.0125}
38
- ],
39
- "u_value": 0.35, # W/m虏路K
40
- "r_value": 2.86, # m虏路K/W
41
- "thermal_mass": 220.0, # kJ/m虏路K
42
- "embodied_carbon": 120.0, # kg CO2e/m虏
43
- "cost": 180.0 # USD/m虏
44
- },
45
- "Insulated Concrete Wall": {
46
- "type": "Wall",
47
- "layers": [
48
- {"material": "Concrete", "thickness": 0.15},
49
- {"material": "EPS Insulation", "thickness": 0.1},
50
- {"material": "Gypsum Board", "thickness": 0.0125}
51
- ],
52
- "u_value": 0.32,
53
- "r_value": 3.13,
54
- "thermal_mass": 360.0,
55
- "embodied_carbon": 95.0,
56
- "cost": 150.0
57
- },
58
- "Timber Frame Wall": {
59
- "type": "Wall",
60
- "layers": [
61
- {"material": "Wood (Pine)", "thickness": 0.02},
62
- {"material": "Glass Fiber Insulation", "thickness": 0.15},
63
- {"material": "Plywood", "thickness": 0.012},
64
- {"material": "Gypsum Board", "thickness": 0.0125}
65
- ],
66
- "u_value": 0.25,
67
- "r_value": 4.0,
68
- "thermal_mass": 45.0,
69
- "embodied_carbon": 35.0,
70
- "cost": 120.0
71
- },
72
- "Concrete Flat Roof": {
73
- "type": "Roof",
74
- "layers": [
75
- {"material": "Concrete", "thickness": 0.15},
76
- {"material": "Polyurethane Insulation", "thickness": 0.15},
77
- {"material": "Waterproofing Membrane", "thickness": 0.005}
78
- ],
79
- "u_value": 0.18,
80
- "r_value": 5.56,
81
- "thermal_mass": 360.0,
82
- "embodied_carbon": 110.0,
83
- "cost": 200.0
84
- },
85
- "Timber Pitched Roof": {
86
- "type": "Roof",
87
- "layers": [
88
- {"material": "Roof Tiles", "thickness": 0.02},
89
- {"material": "Waterproofing Membrane", "thickness": 0.002},
90
- {"material": "Wood (Pine)", "thickness": 0.025},
91
- {"material": "Glass Fiber Insulation", "thickness": 0.2},
92
- {"material": "Gypsum Board", "thickness": 0.0125}
93
- ],
94
- "u_value": 0.16,
95
- "r_value": 6.25,
96
- "thermal_mass": 40.0,
97
- "embodied_carbon": 45.0,
98
- "cost": 160.0
99
- },
100
- "Concrete Floor Slab": {
101
- "type": "Floor",
102
- "layers": [
103
- {"material": "Ceramic Tile", "thickness": 0.01},
104
- {"material": "Concrete", "thickness": 0.1},
105
- {"material": "EPS Insulation", "thickness": 0.1},
106
- {"material": "Damp Proof Membrane", "thickness": 0.002},
107
- {"material": "Gravel", "thickness": 0.15}
108
- ],
109
- "u_value": 0.25,
110
- "r_value": 4.0,
111
- "thermal_mass": 240.0,
112
- "embodied_carbon": 85.0,
113
- "cost": 140.0
114
- },
115
- "Timber Floor": {
116
- "type": "Floor",
117
- "layers": [
118
- {"material": "Wood (Pine)", "thickness": 0.02},
119
- {"material": "Air Gap", "thickness": 0.05},
120
- {"material": "Glass Fiber Insulation", "thickness": 0.15},
121
- {"material": "Plywood", "thickness": 0.018}
122
- ],
123
- "u_value": 0.22,
124
- "r_value": 4.55,
125
- "thermal_mass": 30.0,
126
- "embodied_carbon": 25.0,
127
- "cost": 110.0
128
- }
129
- }
130
-
131
- # Additional materials needed for constructions but not in material library
132
- ADDITIONAL_MATERIALS = {
133
- "Air Gap": {
134
- "category": "Sub-Structural Materials",
135
- "thermal_conductivity": 0.026,
136
- "density": 1.2,
137
- "specific_heat": 1000.0,
138
- "thickness_range": [0.01, 0.1],
139
- "default_thickness": 0.05,
140
- "embodied_carbon": 0.0,
141
- "cost": 0.0
142
- },
143
- "Waterproofing Membrane": {
144
- "category": "Finishing Materials",
145
- "thermal_conductivity": 0.17,
146
- "density": 1100.0,
147
- "specific_heat": 1000.0,
148
- "thickness_range": [0.001, 0.01],
149
- "default_thickness": 0.002,
150
- "embodied_carbon": 4.2,
151
- "cost": 15.0
152
- },
153
- "Damp Proof Membrane": {
154
- "category": "Sub-Structural Materials",
155
- "thermal_conductivity": 0.17,
156
- "density": 1100.0,
157
- "specific_heat": 1000.0,
158
- "thickness_range": [0.001, 0.005],
159
- "default_thickness": 0.002,
160
- "embodied_carbon": 4.0,
161
- "cost": 12.0
162
- },
163
- "Roof Tiles": {
164
- "category": "Finishing Materials",
165
- "thermal_conductivity": 1.0,
166
- "density": 1900.0,
167
- "specific_heat": 800.0,
168
- "thickness_range": [0.01, 0.03],
169
- "default_thickness": 0.02,
170
- "embodied_carbon": 110.0,
171
- "cost": 75.0
172
- },
173
- "Gravel": {
174
- "category": "Sub-Structural Materials",
175
- "thermal_conductivity": 0.7,
176
- "density": 1800.0,
177
- "specific_heat": 840.0,
178
- "thickness_range": [0.05, 0.3],
179
- "default_thickness": 0.15,
180
- "embodied_carbon": 1.5,
181
- "cost": 30.0
182
- }
183
- }
184
-
185
  def display_construction_page():
186
  """
187
  Display the construction page.
188
  This is the main function called by main.py when the Construction page is selected.
189
  """
190
  st.title("Construction Library")
 
191
 
192
  # Display help information in an expandable section
193
  with st.expander("Help & Information"):
@@ -196,23 +43,24 @@ def display_construction_page():
196
  # Initialize constructions in session state if not present
197
  initialize_constructions()
198
 
199
- # Create columns for library and project constructions
200
- col1, col2 = st.columns(2)
 
 
 
 
 
 
 
 
201
 
202
- # Library Constructions
203
  with col1:
204
- st.subheader("Library Constructions")
205
- display_library_constructions()
206
 
207
- # Project Constructions
208
  with col2:
209
- st.subheader("Project Constructions")
210
- display_project_constructions()
211
-
212
- # Construction Editor
213
- st.markdown("---")
214
- st.subheader("Construction Editor")
215
- display_construction_editor()
216
 
217
  # Navigation buttons
218
  col1, col2 = st.columns(2)
@@ -237,162 +85,135 @@ def initialize_constructions():
237
 
238
  # Initialize library constructions if empty
239
  if not st.session_state.project_data["constructions"]["library"]:
240
- st.session_state.project_data["constructions"]["library"] = DEFAULT_CONSTRUCTIONS.copy()
241
-
242
- # Add additional materials to library if not present
243
- for material_name, material_data in ADDITIONAL_MATERIALS.items():
244
- if material_name not in st.session_state.project_data["materials"]["library"]:
245
- st.session_state.project_data["materials"]["library"][material_name] = material_data
246
 
247
  # Initialize construction editor state
248
  if "construction_editor" not in st.session_state:
249
- st.session_state.construction_editor = {
250
- "name": "",
251
- "type": CONSTRUCTION_TYPES[0],
252
- "layers": [],
253
- "edit_mode": False,
254
- "original_name": ""
255
- }
256
 
257
- def display_library_constructions():
258
- """Display the library constructions section."""
259
  # Filter options
260
- type_filter = st.selectbox(
261
- "Filter by Type",
262
- ["All"] + CONSTRUCTION_TYPES,
263
- key="library_construction_type_filter"
264
- )
265
-
266
- # Get library constructions
267
- library_constructions = st.session_state.project_data["constructions"]["library"]
268
-
269
- # Apply filter
270
- if type_filter != "All":
271
- filtered_constructions = {
272
- name: props for name, props in library_constructions.items()
273
- if props["type"] == type_filter
274
- }
275
- else:
276
- filtered_constructions = library_constructions
277
 
278
- # Display constructions in a table
279
- if filtered_constructions:
280
- # Create a DataFrame for display
281
- data = []
282
- for name, props in filtered_constructions.items():
283
- data.append({
284
- "Name": name,
285
- "Type": props["type"],
286
- "U-Value (W/m虏路K)": props["u_value"],
287
- "R-Value (m虏路K/W)": props["r_value"],
288
- "Thermal Mass (kJ/m虏路K)": props["thermal_mass"],
289
- "Layers": len(props["layers"])
290
- })
291
-
292
- df = pd.DataFrame(data)
293
- st.dataframe(df, use_container_width=True, hide_index=True)
294
-
295
- # Add to project button
296
- selected_construction = st.selectbox(
297
- "Select Construction to Add to Project",
298
- list(filtered_constructions.keys()),
299
- key="library_construction_selector"
300
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
301
 
302
- # Display selected construction details
303
- if selected_construction:
304
- st.subheader(f"Details: {selected_construction}")
305
- display_construction_details(filtered_constructions[selected_construction])
306
-
307
- if st.button("Add to Project", key="add_library_construction_to_project"):
308
- # Check if construction already exists in project
309
- if selected_construction in st.session_state.project_data["constructions"]["project"]:
310
- st.warning(f"Construction '{selected_construction}' already exists in your project.")
311
- else:
312
- # Add to project constructions
313
- st.session_state.project_data["constructions"]["project"][selected_construction] = \
314
- st.session_state.project_data["constructions"]["library"][selected_construction].copy()
315
- st.success(f"Construction '{selected_construction}' added to your project.")
316
- logger.info(f"Added library construction '{selected_construction}' to project")
317
- else:
318
- st.info("No constructions found in the selected type.")
319
-
320
- def display_project_constructions():
321
- """Display the project constructions section."""
322
- # Get project constructions
323
- project_constructions = st.session_state.project_data["constructions"]["project"]
324
-
325
- if project_constructions:
326
- # Create a DataFrame for display
327
- data = []
328
- for name, props in project_constructions.items():
329
- data.append({
330
- "Name": name,
331
- "Type": props["type"],
332
- "U-Value (W/m虏路K)": props["u_value"],
333
- "R-Value (m虏路K/W)": props["r_value"],
334
- "Thermal Mass (kJ/m虏路K)": props["thermal_mass"],
335
- "Layers": len(props["layers"])
336
- })
337
-
338
- df = pd.DataFrame(data)
339
- st.dataframe(df, use_container_width=True, hide_index=True)
 
 
 
 
 
 
 
340
 
341
- # Select construction to view details
 
342
  selected_construction = st.selectbox(
343
  "Select Construction to View Details",
344
- list(project_constructions.keys()),
345
- key="project_construction_selector"
346
  )
347
 
348
- # Display selected construction details
349
  if selected_construction:
350
- st.subheader(f"Details: {selected_construction}")
351
- display_construction_details(project_constructions[selected_construction])
352
-
353
- # Edit and delete options
354
- col1, col2 = st.columns(2)
355
-
356
- with col1:
357
- selected_construction_edit = st.selectbox(
358
- "Select Construction to Edit",
359
- list(project_constructions.keys()),
360
- key="project_construction_edit_selector"
361
- )
362
-
363
- if st.button("Edit Construction", key="edit_project_construction"):
364
- # Load construction data into editor
365
- construction_data = project_constructions[selected_construction_edit]
366
- st.session_state.construction_editor = {
367
- "name": selected_construction_edit,
368
- "type": construction_data["type"],
369
- "layers": construction_data["layers"].copy(),
370
- "edit_mode": True,
371
- "original_name": selected_construction_edit
372
- }
373
- st.success(f"Construction '{selected_construction_edit}' loaded for editing.")
374
- st.rerun()
375
-
376
- with col2:
377
- selected_construction_delete = st.selectbox(
378
- "Select Construction to Delete",
379
- list(project_constructions.keys()),
380
- key="project_construction_delete_selector"
381
- )
382
 
383
- if st.button("Delete Construction", key="delete_project_construction"):
384
- # Check if construction is in use
385
- is_in_use = check_construction_in_use(selected_construction_delete)
386
-
387
- if is_in_use:
388
- st.error(f"Cannot delete construction '{selected_construction_delete}' because it is in use in building components.")
389
- else:
390
- # Delete construction
391
- del st.session_state.project_data["constructions"]["project"][selected_construction_delete]
392
- st.success(f"Construction '{selected_construction_delete}' deleted from your project.")
393
- logger.info(f"Deleted construction '{selected_construction_delete}' from project")
 
 
 
394
  else:
395
- st.info("No constructions in your project. Add constructions from the library or create custom constructions.")
396
 
397
  def display_construction_details(construction: Dict[str, Any]):
398
  """
@@ -406,242 +227,249 @@ def display_construction_details(construction: Dict[str, Any]):
406
 
407
  with col1:
408
  st.write(f"**Type:** {construction['type']}")
409
- st.write(f"**U-Value:** {construction['u_value']} W/m虏路K")
410
 
411
  with col2:
412
- st.write(f"**R-Value:** {construction['r_value']} m虏路K/W")
413
- st.write(f"**Thermal Mass:** {construction['thermal_mass']} kJ/m虏路K")
414
 
415
  with col3:
416
- st.write(f"**Embodied Carbon:** {construction['embodied_carbon']} kg CO鈧俥/m虏")
417
- st.write(f"**Cost:** ${construction['cost']}/m虏")
418
 
419
  # Display layers
420
  st.write("**Layers (from outside to inside):**")
421
 
422
  # Create a DataFrame for layers
423
  layers_data = []
424
- for i, layer in enumerate(construction["layers"]):
425
  layers_data.append({
426
  "Layer": i + 1,
427
- "Material": layer["material"],
428
- "Thickness (m)": layer["thickness"]
429
  })
430
 
431
- layers_df = pd.DataFrame(layers_data)
432
- st.dataframe(layers_df, use_container_width=True, hide_index=True)
 
 
 
433
 
434
  def display_construction_editor():
435
  """Display the construction editor form."""
436
  # Get available materials
437
  available_materials = get_available_materials()
438
 
439
- with st.form("construction_editor_form"):
 
 
 
 
 
440
  # Construction name
441
  name = st.text_input(
442
  "Construction Name",
443
- value=st.session_state.construction_editor["name"],
444
- help="Enter a unique name for the construction."
445
  )
446
 
447
  # Construction type
448
  construction_type = st.selectbox(
449
  "Construction Type",
450
  CONSTRUCTION_TYPES,
451
- index=CONSTRUCTION_TYPES.index(st.session_state.construction_editor["type"]) if st.session_state.construction_editor["type"] in CONSTRUCTION_TYPES else 0,
452
- help="Select the construction type."
453
  )
454
 
455
- # Layers section
456
  st.subheader("Layers (from outside to inside)")
457
 
458
- # Display current layers
459
- if st.session_state.construction_editor["layers"]:
460
- layers_data = []
461
- for i, layer in enumerate(st.session_state.construction_editor["layers"]):
462
- layers_data.append({
463
- "Layer": i + 1,
464
- "Material": layer["material"],
465
- "Thickness (m)": layer["thickness"]
466
- })
467
-
468
- layers_df = pd.DataFrame(layers_data)
469
- st.dataframe(layers_df, use_container_width=True, hide_index=True)
 
 
 
 
 
 
 
470
  else:
471
- st.info("No layers added yet. Use the controls below to add layers.")
472
 
473
- # Layer controls
474
- st.subheader("Add/Edit Layer")
475
 
476
  col1, col2 = st.columns(2)
477
 
478
  with col1:
479
- layer_material = st.selectbox(
480
  "Material",
481
  list(available_materials.keys()),
482
- key="layer_material_selector",
483
- help="Select a material for this layer."
484
  )
485
-
486
- # Get material properties
487
- if layer_material in available_materials:
488
- material_props = available_materials[layer_material]
489
- min_thickness = material_props["thickness_range"][0]
490
- max_thickness = material_props["thickness_range"][1]
491
- default_thickness = material_props["default_thickness"]
492
- else:
493
- min_thickness = 0.001
494
- max_thickness = 0.5
495
- default_thickness = 0.05
496
 
497
  with col2:
498
- layer_thickness = st.number_input(
 
 
 
 
 
 
499
  "Thickness (m)",
500
- min_value=float(min_thickness),
501
- max_value=float(max_thickness),
502
- value=float(default_thickness),
503
  format="%.3f",
504
- help=f"Enter the thickness for this layer (range: {min_thickness}-{max_thickness} m)."
505
- )
506
-
507
- # Layer action buttons
508
- col1, col2, col3 = st.columns(3)
509
-
510
- with col1:
511
- add_layer = st.form_submit_button("Add Layer")
512
-
513
- with col2:
514
- layer_index_to_remove = st.number_input(
515
- "Layer # to Remove",
516
- min_value=1,
517
- max_value=max(1, len(st.session_state.construction_editor["layers"])),
518
- value=len(st.session_state.construction_editor["layers"]) if st.session_state.construction_editor["layers"] else 1,
519
- step=1,
520
- help="Enter the layer number to remove."
521
  )
522
 
523
- with col3:
524
- remove_layer = st.form_submit_button("Remove Layer")
525
 
526
- # Calculate button and results
527
- st.subheader("Thermal Properties")
528
  calculate = st.form_submit_button("Calculate Properties")
529
 
530
- # Form submission buttons
531
- col1, col2, col3 = st.columns(3)
532
 
533
  with col1:
534
- save_button = st.form_submit_button("Save Construction")
 
535
 
536
  with col2:
537
- clear_button = st.form_submit_button("Clear Form")
538
-
539
- with col3:
540
- cancel_button = st.form_submit_button("Cancel Edit")
541
 
542
- # Handle form actions outside the form
543
  if add_layer:
544
- # Add new layer
545
  new_layer = {
546
- "material": layer_material,
547
- "thickness": layer_thickness
548
  }
549
- st.session_state.construction_editor["layers"].append(new_layer)
550
- st.success(f"Layer added: {layer_material} ({layer_thickness} m)")
551
- st.rerun()
552
-
553
- if remove_layer and st.session_state.construction_editor["layers"]:
554
- # Remove layer
555
- if 1 <= layer_index_to_remove <= len(st.session_state.construction_editor["layers"]):
556
- removed_layer = st.session_state.construction_editor["layers"].pop(layer_index_to_remove - 1)
557
- st.success(f"Layer {layer_index_to_remove} removed: {removed_layer['material']} ({removed_layer['thickness']} m)")
558
- st.rerun()
 
 
 
 
 
 
 
 
559
  else:
560
- st.error("Invalid layer index.")
561
-
562
- if calculate:
563
- # Calculate thermal properties
564
- if st.session_state.construction_editor["layers"]:
565
- u_value, r_value, thermal_mass, embodied_carbon, cost = calculate_construction_properties(
566
- st.session_state.construction_editor["layers"],
567
- available_materials
568
- )
569
-
570
- col1, col2, col3 = st.columns(3)
571
 
572
- with col1:
573
- st.metric("U-Value", f"{u_value:.3f} W/m虏路K")
574
- st.metric("R-Value", f"{r_value:.3f} m虏路K/W")
 
 
 
 
 
 
575
 
576
- with col2:
577
- st.metric("Thermal Mass", f"{thermal_mass:.1f} kJ/m虏路K")
578
- st.metric("Total Thickness", f"{sum(layer['thickness'] for layer in st.session_state.construction_editor['layers']):.3f} m")
 
 
 
 
579
 
580
- with col3:
581
- st.metric("Embodied Carbon", f"{embodied_carbon:.1f} kg CO鈧俥/m虏")
582
- st.metric("Cost", f"${cost:.2f}/m虏")
583
- else:
584
- st.warning("Add layers to calculate thermal properties.")
585
 
586
- if save_button:
587
  # Validate inputs
588
- validation_errors = validate_construction(
589
- name, construction_type, st.session_state.construction_editor["layers"],
590
- st.session_state.construction_editor["edit_mode"], st.session_state.construction_editor["original_name"]
591
- )
592
-
593
- if validation_errors:
594
- # Display validation errors
595
- for error in validation_errors:
596
- st.error(error)
597
  else:
598
- # Calculate properties
599
- u_value, r_value, thermal_mass, embodied_carbon, cost = calculate_construction_properties(
600
- st.session_state.construction_editor["layers"],
601
- available_materials
602
- )
 
 
 
 
 
 
603
 
604
  # Create construction data
605
  construction_data = {
606
  "type": construction_type,
607
- "layers": st.session_state.construction_editor["layers"].copy(),
608
- "u_value": u_value,
609
- "r_value": r_value,
610
- "thermal_mass": thermal_mass,
611
- "embodied_carbon": embodied_carbon,
612
- "cost": cost
613
  }
614
 
615
  # Handle edit mode
616
- if st.session_state.construction_editor["edit_mode"]:
617
- original_name = st.session_state.construction_editor["original_name"]
618
-
619
- # If name changed, delete old entry and create new one
620
- if original_name != name:
621
- del st.session_state.project_data["constructions"]["project"][original_name]
622
 
623
- # Update construction
624
- st.session_state.project_data["constructions"]["project"][name] = construction_data
625
- st.success(f"Construction '{name}' updated successfully.")
626
- logger.info(f"Updated construction '{name}' in project")
 
 
 
 
 
 
 
 
 
 
 
 
627
  else:
628
- # Add new construction
629
- st.session_state.project_data["constructions"]["project"][name] = construction_data
630
- st.success(f"Construction '{name}' added to your project.")
631
- logger.info(f"Added new construction '{name}' to project")
632
-
633
- # Reset editor
634
- reset_construction_editor()
635
- st.rerun()
636
-
637
- if clear_button:
638
- reset_construction_editor()
639
- st.rerun()
640
-
641
- if cancel_button and st.session_state.construction_editor["edit_mode"]:
642
- reset_construction_editor()
643
- st.success("Edit cancelled.")
644
- st.rerun()
 
 
645
 
646
  def get_available_materials() -> Dict[str, Any]:
647
  """
@@ -652,115 +480,76 @@ def get_available_materials() -> Dict[str, Any]:
652
  """
653
  available_materials = {}
654
 
655
- # Add library materials
656
- if "materials" in st.session_state.project_data and "library" in st.session_state.project_data["materials"]:
657
- available_materials.update(st.session_state.project_data["materials"]["library"])
658
-
659
- # Add project materials
660
- if "materials" in st.session_state.project_data and "project" in st.session_state.project_data["materials"]:
661
- available_materials.update(st.session_state.project_data["materials"]["project"])
 
 
 
 
 
 
662
 
663
  return available_materials
664
 
665
- def calculate_construction_properties(
666
- layers: List[Dict[str, Any]],
667
- available_materials: Dict[str, Any]
668
- ) -> Tuple[float, float, float, float, float]:
669
  """
670
- Calculate thermal properties of a construction.
671
 
672
  Args:
673
  layers: List of layer dictionaries with material and thickness
674
- available_materials: Dictionary of available materials
675
-
676
  Returns:
677
- Tuple of (u_value, r_value, thermal_mass, embodied_carbon, cost)
678
  """
679
- # Initialize values
680
- r_value_total = 0.0 # m虏路K/W
681
- thermal_mass_total = 0.0 # kJ/m虏路K
682
- embodied_carbon_total = 0.0 # kg CO鈧俥/m虏
683
- cost_total = 0.0 # USD/m虏
684
 
685
- # Add standard surface resistances
686
- r_value_total += 0.13 # Interior surface resistance
687
- r_value_total += 0.04 # Exterior surface resistance
688
 
689
  # Calculate properties for each layer
690
  for layer in layers:
691
- material_name = layer["material"]
692
- thickness = layer["thickness"]
693
 
694
- if material_name in available_materials:
695
- material = available_materials[material_name]
696
 
697
- # Calculate R-value for this layer
698
- r_value_layer = thickness / material["thermal_conductivity"]
699
- r_value_total += r_value_layer
700
 
701
- # Calculate thermal mass for this layer
702
- thermal_mass_layer = material["density"] * material["specific_heat"] * thickness / 1000 # Convert J to kJ
703
- thermal_mass_total += thermal_mass_layer
704
 
705
- # Calculate embodied carbon for this layer
706
- embodied_carbon_layer = material["embodied_carbon"] * thickness
707
- embodied_carbon_total += embodied_carbon_layer
708
 
709
- # Calculate cost for this layer
710
- cost_layer = material["cost"] * thickness
711
- cost_total += cost_layer
712
-
713
- # Calculate U-value from R-value
714
- u_value = 1.0 / r_value_total if r_value_total > 0 else float('inf')
715
-
716
- return u_value, r_value_total, thermal_mass_total, embodied_carbon_total, cost_total
717
-
718
- def validate_construction(
719
- name: str, construction_type: str, layers: List[Dict[str, Any]],
720
- edit_mode: bool, original_name: str
721
- ) -> List[str]:
722
- """
723
- Validate construction inputs.
724
-
725
- Args:
726
- name: Construction name
727
- construction_type: Construction type
728
- layers: List of layer dictionaries
729
- edit_mode: Whether in edit mode
730
- original_name: Original name if in edit mode
731
-
732
- Returns:
733
- List of validation error messages, empty if all inputs are valid
734
- """
735
- errors = []
736
-
737
- # Validate name
738
- if not name or name.strip() == "":
739
- errors.append("Construction name is required.")
740
-
741
- # Check for name uniqueness if not in edit mode or if name changed
742
- if not edit_mode or (edit_mode and name != original_name):
743
- if name in st.session_state.project_data["constructions"]["project"]:
744
- errors.append(f"Construction name '{name}' already exists in your project.")
745
-
746
- # Validate construction type
747
- if construction_type not in CONSTRUCTION_TYPES:
748
- errors.append("Please select a valid construction type.")
749
 
750
- # Validate layers
751
- if not layers:
752
- errors.append("At least one layer is required.")
753
 
754
- return errors
755
-
756
- def reset_construction_editor():
757
- """Reset the construction editor to default values."""
758
- st.session_state.construction_editor = {
759
- "name": "",
760
- "type": CONSTRUCTION_TYPES[0],
761
- "layers": [],
762
- "edit_mode": False,
763
- "original_name": ""
764
  }
765
 
766
  def check_construction_in_use(construction_name: str) -> bool:
@@ -769,55 +558,51 @@ def check_construction_in_use(construction_name: str) -> bool:
769
 
770
  Args:
771
  construction_name: Name of the construction to check
772
-
773
  Returns:
774
  True if the construction is in use, False otherwise
775
  """
776
- # This is a placeholder function that will be implemented when components are added
777
- # For now, we'll assume constructions are not in use
 
 
 
 
 
 
 
 
 
778
  return False
779
 
780
  def display_construction_help():
781
- """Display help information for the construction page."""
 
 
782
  st.markdown("""
783
  ### Construction Library Help
784
 
785
- This section allows you to create and manage multi-layer constructions for your building envelope.
786
 
787
  **Key Concepts:**
788
 
789
- * **Construction**: A multi-layer assembly of materials used for walls, roofs, or floors.
790
  * **Layers**: Individual material layers that make up a construction, defined from outside to inside.
791
- * **U-Value**: Overall heat transfer coefficient (W/m虏路K). Lower values indicate better insulation.
792
- * **R-Value**: Thermal resistance (m虏路K/W). Higher values indicate better insulation.
793
- * **Thermal Mass**: Ability to store heat (kJ/m虏路K). Higher values indicate better heat storage capacity.
794
-
795
- **Library Constructions:**
796
-
797
- The library contains pre-defined constructions with standard thermal properties. You can:
798
- * Browse constructions by type (Wall, Roof, Floor)
799
- * View detailed information about each construction
800
- * Add library constructions to your project
801
 
802
- **Project Constructions:**
803
-
804
- These are constructions you've added to your project from the library or created custom. You can:
805
- * View detailed information about each construction
806
- * Edit existing constructions
807
- * Delete constructions (if not in use)
808
-
809
- **Construction Editor:**
810
 
811
- The editor allows you to create new constructions or modify existing ones:
812
- * Add layers from outside to inside
813
- * Select materials from your material library
814
- * Specify thickness for each layer
815
- * Calculate overall thermal properties
816
 
817
- **Workflow:**
818
 
819
- 1. Browse the library constructions
820
- 2. Add constructions to your project or create custom ones
821
- 3. Edit properties as needed for your specific project
822
- 4. Continue to the Building Components page to use these constructions
823
  """)
 
18
  import uuid
19
  from typing import Dict, List, Any, Optional, Tuple, Union
20
 
21
+ # Import the centralized data module
22
+ from app.m_c_data import get_default_constructions
23
+
24
  # Configure logging
25
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
26
  logger = logging.getLogger(__name__)
 
28
  # Define constants
29
  CONSTRUCTION_TYPES = ["Wall", "Roof", "Floor"]
30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  def display_construction_page():
32
  """
33
  Display the construction page.
34
  This is the main function called by main.py when the Construction page is selected.
35
  """
36
  st.title("Construction Library")
37
+ st.write("Define multi-layer constructions for walls, roofs, and floors based on ASHRAE 2005 Handbook of Fundamentals.")
38
 
39
  # Display help information in an expandable section
40
  with st.expander("Help & Information"):
 
43
  # Initialize constructions in session state if not present
44
  initialize_constructions()
45
 
46
+ # Check if rerun is pending
47
+ if 'construction_rerun_pending' not in st.session_state:
48
+ st.session_state.construction_rerun_pending = False
49
+
50
+ if st.session_state.construction_rerun_pending:
51
+ st.session_state.construction_rerun_pending = False
52
+ st.rerun()
53
+
54
+ # Split the display into two columns
55
+ col1, col2 = st.columns([3, 2])
56
 
 
57
  with col1:
58
+ st.subheader("Saved Constructions")
59
+ display_constructions_table()
60
 
 
61
  with col2:
62
+ st.subheader("Construction Editor/Creator")
63
+ display_construction_editor()
 
 
 
 
 
64
 
65
  # Navigation buttons
66
  col1, col2 = st.columns(2)
 
85
 
86
  # Initialize library constructions if empty
87
  if not st.session_state.project_data["constructions"]["library"]:
88
+ # Get default constructions from the centralized data module
89
+ default_constructions = get_default_constructions()
90
+ st.session_state.project_data["constructions"]["library"] = default_constructions.copy()
 
 
 
91
 
92
  # Initialize construction editor state
93
  if "construction_editor" not in st.session_state:
94
+ st.session_state.construction_editor = {}
95
+
96
+ # Initialize construction action state
97
+ if "construction_action" not in st.session_state:
98
+ st.session_state.construction_action = {"action": None, "id": None}
 
 
99
 
100
+ def display_constructions_table():
101
+ """Display the constructions in a table format."""
102
  # Filter options
103
+ col1, col2 = st.columns(2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
 
105
+ with col1:
106
+ source_filter = st.selectbox(
107
+ "Source",
108
+ ["All", "Library", "Project"],
109
+ key="construction_source_filter"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  )
111
+
112
+ with col2:
113
+ type_filter = st.selectbox(
114
+ "Type",
115
+ ["All"] + CONSTRUCTION_TYPES,
116
+ key="construction_type_filter"
117
+ )
118
+
119
+ # Get constructions based on filters
120
+ constructions = {}
121
+
122
+ if source_filter in ["All", "Library"]:
123
+ for name, props in st.session_state.project_data["constructions"]["library"].items():
124
+ if type_filter == "All" or props["type"] == type_filter:
125
+ constructions[name] = {**props, "source": "Library"}
126
+
127
+ if source_filter in ["All", "Project"]:
128
+ for name, props in st.session_state.project_data["constructions"]["project"].items():
129
+ if type_filter == "All" or props["type"] == type_filter:
130
+ constructions[name] = {**props, "source": "Project"}
131
+
132
+ # Display constructions in a table
133
+ if constructions:
134
+ # Create column headers
135
+ cols = st.columns([3, 1, 1, 1, 1, 1, 1])
136
+ cols[0].write("**Name**")
137
+ cols[1].write("**Type**")
138
+ cols[2].write("**U-Value**")
139
+ cols[3].write("**Layers**")
140
+ cols[4].write("**Source**")
141
+ cols[5].write("**Edit**")
142
+ cols[6].write("**Delete**")
143
 
144
+ # Display each construction
145
+ for idx, (name, props) in enumerate(constructions.items()):
146
+ cols = st.columns([3, 1, 1, 1, 1, 1, 1])
147
+ cols[0].write(name)
148
+ cols[1].write(props["type"])
149
+ cols[2].write(f"{props.get('u_value', 0.0):.3f}")
150
+ cols[3].write(str(len(props.get("layers", []))))
151
+ cols[4].write(props["source"])
152
+
153
+ # Edit button (only for project constructions)
154
+ with cols[5].container():
155
+ if props["source"] == "Project":
156
+ edit_key = f"edit_construction_{name}_{idx}"
157
+ if st.button("Edit", key=edit_key):
158
+ # Load construction data into editor
159
+ st.session_state.construction_editor = {
160
+ "name": name,
161
+ "type": props["type"],
162
+ "layers": props.get("layers", []).copy(),
163
+ "is_edit": True
164
+ }
165
+ st.session_state.construction_action = {"action": "edit", "id": str(uuid.uuid4())}
166
+ st.session_state.construction_rerun_pending = True
167
+ else:
168
+ st.write("N/A")
169
+
170
+ # Delete button (only for project constructions)
171
+ with cols[6].container():
172
+ if props["source"] == "Project":
173
+ delete_key = f"delete_construction_{name}_{idx}"
174
+ if st.button("Delete", key=delete_key):
175
+ # Check if construction is in use
176
+ is_in_use = check_construction_in_use(name)
177
+
178
+ if is_in_use:
179
+ st.error(f"Cannot delete construction '{name}' because it is in use in building components.")
180
+ else:
181
+ # Delete construction
182
+ del st.session_state.project_data["constructions"]["project"][name]
183
+ st.success(f"Construction '{name}' deleted from your project.")
184
+ logger.info(f"Deleted construction '{name}' from project")
185
+ st.session_state.construction_action = {"action": "delete", "id": str(uuid.uuid4())}
186
+ st.session_state.construction_rerun_pending = True
187
+ else:
188
+ st.write("N/A")
189
 
190
+ # Display details of selected construction
191
+ st.subheader("Construction Details")
192
  selected_construction = st.selectbox(
193
  "Select Construction to View Details",
194
+ list(constructions.keys()),
195
+ key="construction_details_selector"
196
  )
197
 
 
198
  if selected_construction:
199
+ display_construction_details(constructions[selected_construction])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
 
201
+ # Add to project button for library constructions
202
+ if constructions[selected_construction]["source"] == "Library":
203
+ if st.button("Add to Project", key="add_to_project"):
204
+ # Check if construction already exists in project
205
+ if selected_construction in st.session_state.project_data["constructions"]["project"]:
206
+ st.warning(f"Construction '{selected_construction}' already exists in your project.")
207
+ else:
208
+ # Add to project constructions
209
+ construction_data = st.session_state.project_data["constructions"]["library"][selected_construction].copy()
210
+ st.session_state.project_data["constructions"]["project"][selected_construction] = construction_data
211
+ st.success(f"Construction '{selected_construction}' added to your project.")
212
+ logger.info(f"Added library construction '{selected_construction}' to project")
213
+ st.session_state.construction_action = {"action": "add", "id": str(uuid.uuid4())}
214
+ st.session_state.construction_rerun_pending = True
215
  else:
216
+ st.info("No constructions found with the selected filters.")
217
 
218
  def display_construction_details(construction: Dict[str, Any]):
219
  """
 
227
 
228
  with col1:
229
  st.write(f"**Type:** {construction['type']}")
230
+ st.write(f"**U-Value:** {construction.get('u_value', 0.0):.3f} W/m虏路K")
231
 
232
  with col2:
233
+ st.write(f"**R-Value:** {construction.get('r_value', 0.0):.3f} m虏路K/W")
234
+ st.write(f"**Thermal Mass:** {construction.get('thermal_mass', 0.0):.1f} kJ/m虏路K")
235
 
236
  with col3:
237
+ st.write(f"**Embodied Carbon:** {construction.get('embodied_carbon', 0.0):.1f} kg CO鈧俥/m虏")
238
+ st.write(f"**Cost:** ${construction.get('cost', 0.0):.2f}/m虏")
239
 
240
  # Display layers
241
  st.write("**Layers (from outside to inside):**")
242
 
243
  # Create a DataFrame for layers
244
  layers_data = []
245
+ for i, layer in enumerate(construction.get("layers", [])):
246
  layers_data.append({
247
  "Layer": i + 1,
248
+ "Material": layer.get("material", ""),
249
+ "Thickness (m)": layer.get("thickness", 0.0)
250
  })
251
 
252
+ if layers_data:
253
+ layers_df = pd.DataFrame(layers_data)
254
+ st.dataframe(layers_df, use_container_width=True, hide_index=True)
255
+ else:
256
+ st.info("No layers defined for this construction.")
257
 
258
  def display_construction_editor():
259
  """Display the construction editor form."""
260
  # Get available materials
261
  available_materials = get_available_materials()
262
 
263
+ # Get editor state
264
+ editor_state = st.session_state.construction_editor
265
+ is_edit = editor_state.get("is_edit", False)
266
+
267
+ # Create the editor form
268
+ with st.form("construction_editor_form", clear_on_submit=True):
269
  # Construction name
270
  name = st.text_input(
271
  "Construction Name",
272
+ value=editor_state.get("name", ""),
273
+ help="Enter a unique name for this construction."
274
  )
275
 
276
  # Construction type
277
  construction_type = st.selectbox(
278
  "Construction Type",
279
  CONSTRUCTION_TYPES,
280
+ index=CONSTRUCTION_TYPES.index(editor_state.get("type", CONSTRUCTION_TYPES[0])) if editor_state.get("type") in CONSTRUCTION_TYPES else 0,
281
+ help="Select the type of construction (wall, roof, or floor)."
282
  )
283
 
284
+ # Layers
285
  st.subheader("Layers (from outside to inside)")
286
 
287
+ # Display existing layers
288
+ layers = editor_state.get("layers", [])
289
+
290
+ if layers:
291
+ for i, layer in enumerate(layers):
292
+ col1, col2, col3 = st.columns([3, 2, 1])
293
+
294
+ with col1:
295
+ st.write(f"**Layer {i+1}:** {layer.get('material', '')}")
296
+
297
+ with col2:
298
+ st.write(f"**Thickness:** {layer.get('thickness', 0.0):.3f} m")
299
+
300
+ with col3:
301
+ if st.form_submit_button(f"Remove Layer {i+1}"):
302
+ layers.pop(i)
303
+ st.session_state.construction_editor["layers"] = layers
304
+ st.session_state.construction_action = {"action": "update_layers", "id": str(uuid.uuid4())}
305
+ st.session_state.construction_rerun_pending = True
306
  else:
307
+ st.info("No layers defined. Add layers below.")
308
 
309
+ # Add new layer
310
+ st.subheader("Add New Layer")
311
 
312
  col1, col2 = st.columns(2)
313
 
314
  with col1:
315
+ new_material = st.selectbox(
316
  "Material",
317
  list(available_materials.keys()),
318
+ help="Select a material for the new layer."
 
319
  )
 
 
 
 
 
 
 
 
 
 
 
320
 
321
  with col2:
322
+ # Get thickness range for selected material
323
+ thickness_range = available_materials[new_material].get("thickness_range", {"min": 0.01, "max": 0.5, "default": 0.1})
324
+ min_thickness = thickness_range.get("min", 0.01)
325
+ max_thickness = thickness_range.get("max", 0.5)
326
+ default_thickness = thickness_range.get("default", 0.1)
327
+
328
+ new_thickness = st.number_input(
329
  "Thickness (m)",
330
+ min_value=min_thickness,
331
+ max_value=max_thickness,
332
+ value=default_thickness,
333
  format="%.3f",
334
+ help=f"Enter the thickness of the layer (between {min_thickness} and {max_thickness} m)."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
  )
336
 
337
+ # Add layer button
338
+ add_layer = st.form_submit_button("Add Layer")
339
 
340
+ # Calculate button
 
341
  calculate = st.form_submit_button("Calculate Properties")
342
 
343
+ # Submit buttons
344
+ col1, col2 = st.columns(2)
345
 
346
  with col1:
347
+ submit_label = "Update Construction" if is_edit else "Create Construction"
348
+ submit = st.form_submit_button(submit_label)
349
 
350
  with col2:
351
+ cancel = st.form_submit_button("Cancel")
 
 
 
352
 
353
+ # Handle form actions
354
  if add_layer:
355
+ # Add new layer to the list
356
  new_layer = {
357
+ "material": new_material,
358
+ "thickness": new_thickness
359
  }
360
+
361
+ layers = editor_state.get("layers", [])
362
+ layers.append(new_layer)
363
+
364
+ # Update editor state
365
+ st.session_state.construction_editor = {
366
+ **editor_state,
367
+ "layers": layers
368
+ }
369
+
370
+ st.success(f"Layer '{new_material}' added to construction.")
371
+ st.session_state.construction_action = {"action": "add_layer", "id": str(uuid.uuid4())}
372
+ st.session_state.construction_rerun_pending = True
373
+
374
+ elif calculate:
375
+ # Calculate construction properties
376
+ if not layers:
377
+ st.error("Cannot calculate properties for a construction with no layers.")
378
  else:
379
+ properties = calculate_construction_properties(layers, available_materials)
 
 
 
 
 
 
 
 
 
 
380
 
381
+ # Update editor state with calculated properties
382
+ st.session_state.construction_editor = {
383
+ **editor_state,
384
+ "u_value": properties["u_value"],
385
+ "r_value": properties["r_value"],
386
+ "thermal_mass": properties["thermal_mass"],
387
+ "embodied_carbon": properties["embodied_carbon"],
388
+ "cost": properties["cost"]
389
+ }
390
 
391
+ # Display calculated properties
392
+ st.success("Properties calculated successfully.")
393
+ st.write(f"**U-Value:** {properties['u_value']:.3f} W/m虏路K")
394
+ st.write(f"**R-Value:** {properties['r_value']:.3f} m虏路K/W")
395
+ st.write(f"**Thermal Mass:** {properties['thermal_mass']:.1f} kJ/m虏路K")
396
+ st.write(f"**Embodied Carbon:** {properties['embodied_carbon']:.1f} kg CO鈧俥/m虏")
397
+ st.write(f"**Cost:** ${properties['cost']:.2f}/m虏")
398
 
399
+ st.session_state.construction_action = {"action": "calculate", "id": str(uuid.uuid4())}
400
+ st.session_state.construction_rerun_pending = True
 
 
 
401
 
402
+ elif submit:
403
  # Validate inputs
404
+ if not name:
405
+ st.error("Construction name is required.")
406
+ elif not layers:
407
+ st.error("At least one layer is required.")
 
 
 
 
 
408
  else:
409
+ # Calculate properties if not already calculated
410
+ if "u_value" not in editor_state:
411
+ properties = calculate_construction_properties(layers, available_materials)
412
+ else:
413
+ properties = {
414
+ "u_value": editor_state.get("u_value", 0.0),
415
+ "r_value": editor_state.get("r_value", 0.0),
416
+ "thermal_mass": editor_state.get("thermal_mass", 0.0),
417
+ "embodied_carbon": editor_state.get("embodied_carbon", 0.0),
418
+ "cost": editor_state.get("cost", 0.0)
419
+ }
420
 
421
  # Create construction data
422
  construction_data = {
423
  "type": construction_type,
424
+ "layers": layers,
425
+ "u_value": properties["u_value"],
426
+ "r_value": properties["r_value"],
427
+ "thermal_mass": properties["thermal_mass"],
428
+ "embodied_carbon": properties["embodied_carbon"],
429
+ "cost": properties["cost"]
430
  }
431
 
432
  # Handle edit mode
433
+ if is_edit:
434
+ # Check if name has changed
435
+ original_name = editor_state.get("name", "")
 
 
 
436
 
437
+ if name != original_name and name in st.session_state.project_data["constructions"]["project"]:
438
+ st.error(f"Construction name '{name}' already exists in your project. Please choose a different name.")
439
+ else:
440
+ # If name changed, delete old entry and add new one
441
+ if name != original_name:
442
+ del st.session_state.project_data["constructions"]["project"][original_name]
443
+
444
+ # Update construction
445
+ st.session_state.project_data["constructions"]["project"][name] = construction_data
446
+ st.success(f"Construction '{name}' updated successfully.")
447
+ logger.info(f"Updated construction '{name}' in project")
448
+
449
+ # Clear editor state
450
+ st.session_state.construction_editor = {}
451
+ st.session_state.construction_action = {"action": "update", "id": str(uuid.uuid4())}
452
+ st.session_state.construction_rerun_pending = True
453
  else:
454
+ # Check if name already exists
455
+ if name in st.session_state.project_data["constructions"]["project"]:
456
+ st.error(f"Construction name '{name}' already exists in your project. Please choose a different name.")
457
+ else:
458
+ # Add new construction
459
+ st.session_state.project_data["constructions"]["project"][name] = construction_data
460
+ st.success(f"Construction '{name}' created successfully.")
461
+ logger.info(f"Created new construction '{name}' in project")
462
+
463
+ # Clear editor state
464
+ st.session_state.construction_editor = {}
465
+ st.session_state.construction_action = {"action": "create", "id": str(uuid.uuid4())}
466
+ st.session_state.construction_rerun_pending = True
467
+
468
+ elif cancel:
469
+ # Clear editor state
470
+ st.session_state.construction_editor = {}
471
+ st.session_state.construction_action = {"action": "cancel", "id": str(uuid.uuid4())}
472
+ st.session_state.construction_rerun_pending = True
473
 
474
  def get_available_materials() -> Dict[str, Any]:
475
  """
 
480
  """
481
  available_materials = {}
482
 
483
+ # Add materials from session state
484
+ if "materials" in st.session_state.project_data:
485
+ # Add library materials
486
+ if "library" in st.session_state.project_data["materials"]:
487
+ for name, material in st.session_state.project_data["materials"]["library"].items():
488
+ if material.get("type") == "opaque":
489
+ available_materials[name] = material
490
+
491
+ # Add project materials
492
+ if "project" in st.session_state.project_data["materials"]:
493
+ for name, material in st.session_state.project_data["materials"]["project"].items():
494
+ if material.get("type") == "opaque":
495
+ available_materials[name] = material
496
 
497
  return available_materials
498
 
499
+ def calculate_construction_properties(layers: List[Dict[str, Any]], materials: Dict[str, Any]) -> Dict[str, float]:
 
 
 
500
  """
501
+ Calculate the thermal properties of a construction based on its layers.
502
 
503
  Args:
504
  layers: List of layer dictionaries with material and thickness
505
+ materials: Dictionary of available materials
506
+
507
  Returns:
508
+ Dictionary of calculated properties
509
  """
510
+ # Initialize properties
511
+ r_value = 0.0 # m虏路K/W
512
+ thermal_mass = 0.0 # kJ/m虏路K
513
+ embodied_carbon = 0.0 # kg CO鈧俥/m虏
514
+ cost = 0.0 # $/m虏
515
 
516
+ # Add surface resistances (inside and outside)
517
+ r_value += 0.17 # m虏路K/W (typical combined surface resistance)
 
518
 
519
  # Calculate properties for each layer
520
  for layer in layers:
521
+ material_name = layer.get("material", "")
522
+ thickness = layer.get("thickness", 0.0)
523
 
524
+ if material_name in materials:
525
+ material = materials[material_name]
526
 
527
+ # Thermal resistance
528
+ if material.get("thermal_conductivity", 0.0) > 0:
529
+ r_value += thickness / material.get("thermal_conductivity", 1.0)
530
 
531
+ # Thermal mass
532
+ thermal_mass += material.get("density", 0.0) * material.get("specific_heat", 0.0) * thickness / 1000.0
 
533
 
534
+ # Embodied carbon
535
+ embodied_carbon += material.get("embodied_carbon", 0.0) * material.get("density", 0.0) * thickness
 
536
 
537
+ # Cost
538
+ material_cost = material.get("cost", {})
539
+ if isinstance(material_cost, dict):
540
+ cost += material_cost.get("material", 0.0) * thickness + material_cost.get("labor", 0.0)
541
+ else:
542
+ cost += material_cost * thickness
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
543
 
544
+ # Calculate U-value
545
+ u_value = 1.0 / r_value if r_value > 0 else 0.0
 
546
 
547
+ return {
548
+ "u_value": u_value,
549
+ "r_value": r_value,
550
+ "thermal_mass": thermal_mass,
551
+ "embodied_carbon": embodied_carbon,
552
+ "cost": cost
 
 
 
 
553
  }
554
 
555
  def check_construction_in_use(construction_name: str) -> bool:
 
558
 
559
  Args:
560
  construction_name: Name of the construction to check
561
+
562
  Returns:
563
  True if the construction is in use, False otherwise
564
  """
565
+ # Check if components exist in session state
566
+ if "components" not in st.session_state.project_data:
567
+ return False
568
+
569
+ # Check walls, roofs, and floors
570
+ for comp_type in ["walls", "roofs", "floors"]:
571
+ if comp_type in st.session_state.project_data["components"]:
572
+ for component in st.session_state.project_data["components"][comp_type]:
573
+ if component.get("construction") == construction_name:
574
+ return True
575
+
576
  return False
577
 
578
  def display_construction_help():
579
+ """
580
+ Display help information for the construction page.
581
+ """
582
  st.markdown("""
583
  ### Construction Library Help
584
 
585
+ This section allows you to create and manage multi-layer constructions for walls, roofs, and floors.
586
 
587
  **Key Concepts:**
588
 
589
+ * **Construction**: A multi-layer assembly of materials used for building envelope components.
590
  * **Layers**: Individual material layers that make up a construction, defined from outside to inside.
591
+ * **U-Value**: Overall heat transfer coefficient (W/m虏路K), lower is better for insulation.
592
+ * **R-Value**: Thermal resistance (m虏路K/W), higher is better for insulation.
593
+ * **Thermal Mass**: Heat storage capacity (kJ/m虏路K), higher values provide better temperature stability.
 
 
 
 
 
 
 
594
 
595
+ **Workflow:**
 
 
 
 
 
 
 
596
 
597
+ 1. Create new constructions or add existing ones from the library to your project.
598
+ 2. Define layers for each construction, selecting materials and specifying thicknesses.
599
+ 3. Calculate thermal properties to evaluate performance.
600
+ 4. Use constructions in the Building Components section to define walls, roofs, and floors.
 
601
 
602
+ **Tips:**
603
 
604
+ * Start with the outermost layer (exterior) and work inward when defining layers.
605
+ * Consider both thermal resistance and thermal mass for optimal performance.
606
+ * Use the Calculate Properties button to evaluate your construction before saving.
607
+ * Library constructions cannot be modified, but you can add them to your project and then edit them.
608
  """)