mabuseif commited on
Commit
d56a18a
·
verified ·
1 Parent(s): cd0c130

Update app/component_selection.py

Browse files
Files changed (1) hide show
  1. app/component_selection.py +275 -39
app/component_selection.py CHANGED
@@ -1,6 +1,7 @@
1
  """
2
  HVAC Component Selection Module
3
  Provides UI for selecting building components in the HVAC Load Calculator.
 
4
  """
5
 
6
  import streamlit as st
@@ -12,19 +13,150 @@ from dataclasses import dataclass, field
12
  from enum import Enum
13
  from typing import Dict, List, Any, Optional
14
 
15
- # Imports from your project structure
16
- from data.building_components import Wall, Roof, Floor, Window, Door, Orientation, ComponentType
17
- from data.reference_data import reference_data
18
- from utils.component_library import ComponentLibrary
19
- from utils.u_value_calculator import UValueCalculator
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
- # --- Shading System (New Addition) ---
22
  class ShadingType(Enum):
23
  NONE = "None"
24
  INTERNAL = "Internal"
25
  EXTERNAL = "External"
26
  BETWEEN_GLASS = "Between-glass"
27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  @dataclass
29
  class ShadingDevice:
30
  id: str
@@ -79,12 +211,125 @@ class ShadingSystem:
79
 
80
  shading_system = ShadingSystem()
81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  # --- Component Selection Interface ---
83
  class ComponentSelectionInterface:
84
  def __init__(self):
85
- self.component_library = ComponentLibrary()
86
- self.u_value_calculator = UValueCalculator()
87
  self.shading_system = shading_system
 
88
 
89
  def display_component_selection(self, session_state: Any) -> None:
90
  st.title("Building Components")
@@ -140,9 +385,9 @@ class ComponentSelectionInterface:
140
  name = st.text_input("Name", f"New {type_name.capitalize()}")
141
  area = st.number_input("Area (m²)", min_value=0.0, value=1.0, step=0.1)
142
  if component_type not in [ComponentType.ROOF, ComponentType.FLOOR]:
143
- orientation = st.selectbox("Orientation", [o.name for o in Orientation], index=0)
144
  else:
145
- orientation = Orientation.HORIZONTAL.name
146
 
147
  with col2:
148
  selection_method = st.radio(f"{type_name.capitalize()} Selection Method", ["Select from Presets", "Custom Properties"])
@@ -206,36 +451,36 @@ class ComponentSelectionInterface:
206
  component = self.component_library.get_component(component_id)
207
  new_component = component.__class__(
208
  id=str(uuid.uuid4()), name=name, area=area,
209
- orientation=Orientation[orientation], **vars(component)
210
  )
211
  del new_component.__dict__["id"]
212
  else:
213
  if component_type == ComponentType.WALL:
214
  new_component = Wall(
215
  id=str(uuid.uuid4()), name=name, u_value=u_value, area=area,
216
- orientation=Orientation[orientation], wall_type=wall_type, wall_group=wall_group
217
  )
218
  elif component_type == ComponentType.ROOF:
219
  new_component = Roof(
220
  id=str(uuid.uuid4()), name=name, u_value=u_value, area=area,
221
- orientation=Orientation[orientation], roof_type=roof_type, roof_group=roof_group
222
  )
223
  elif component_type == ComponentType.FLOOR:
224
  new_component = Floor(
225
  id=str(uuid.uuid4()), name=name, u_value=u_value, area=area,
226
- orientation=Orientation[orientation], floor_type=floor_type
227
  )
228
  elif component_type == ComponentType.WINDOW:
229
  new_component = Window(
230
  id=str(uuid.uuid4()), name=name, u_value=u_value, area=area,
231
- orientation=Orientation[orientation], shgc=shgc, vt=vt, window_type=window_type,
232
  glazing_layers=glazing_layers, gas_fill=gas_fill, low_e_coating=low_e_coating,
233
  shading_device_id=shading_options[shading_device]
234
  )
235
  elif component_type == ComponentType.DOOR:
236
  new_component = Door(
237
  id=str(uuid.uuid4()), name=name, u_value=u_value, area=area,
238
- orientation=Orientation[orientation], door_type=door_type
239
  )
240
  self.component_library.add_component(new_component)
241
  session_state.components[type_name + 's'].append(new_component)
@@ -244,11 +489,11 @@ class ComponentSelectionInterface:
244
  except ValueError as e:
245
  st.error(f"Error: {str(e)}")
246
 
247
- def _display_components_table(self, session_state: Any, component_type: ComponentType, components: List[Any]) -> None:
248
  type_name = component_type.value.lower()
249
  data = []
250
  for comp in components:
251
- row = {"Name": comp.name, "Area (m²)": comp.area, "U-Value (W/m²·K)": comp.u_value, "Orientation": comp.orientation.name, "ID": comp.id}
252
  if component_type == ComponentType.WALL:
253
  row.update({"Wall Type": comp.wall_type, "Wall Group": comp.wall_group})
254
  elif component_type == ComponentType.ROOF:
@@ -299,10 +544,10 @@ class ComponentSelectionInterface:
299
  name = st.text_input("Name", value=component.name)
300
  area = st.number_input("Area (m²)", min_value=0.0, value=component.area, step=0.1)
301
  if component_type not in [ComponentType.ROOF, ComponentType.FLOOR]:
302
- orientation = st.selectbox("Orientation", [o.name for o in Orientation],
303
- index=[o.name for o in Orientation].index(component.orientation.name))
304
  else:
305
- orientation = Orientation.HORIZONTAL.name
306
 
307
  with col2:
308
  u_value = st.number_input("U-Value (W/m²·K)", min_value=0.0, value=component.u_value, step=0.01)
@@ -347,7 +592,7 @@ class ComponentSelectionInterface:
347
  component.name = name
348
  component.area = area
349
  component.u_value = u_value
350
- component.orientation = Orientation[orientation]
351
  if component_type == ComponentType.WALL:
352
  component.wall_type = wall_type
353
  component.wall_group = wall_group
@@ -397,15 +642,11 @@ class ComponentSelectionInterface:
397
  for i, l in enumerate(session_state.material_layers)]
398
  st.dataframe(pd.DataFrame(layer_data), use_container_width=True)
399
 
400
- r_outside = 0.04 # m²·K/W
401
- r_inside = 0.13 # m²·K/W
402
- r_layers = sum([l["thickness"] / 1000 / l["conductivity"] for l in session_state.material_layers])
403
- r_total = r_outside + r_layers + r_inside
404
- u_value = 1 / r_total if r_total > 0 else 0
405
 
406
  col1, col2, col3 = st.columns(3)
407
  with col1:
408
- st.metric("Total R-Value", f"{r_total:.3f} m²·K/W")
409
  with col2:
410
  st.metric("U-Value", f"{u_value:.3f} W/m²·K")
411
  with col3:
@@ -456,16 +697,12 @@ class ComponentSelectionInterface:
456
 
457
  def _save_components(self, session_state: Any) -> None:
458
  components_dict = {
459
- "walls": [c.__dict__ for c in session_state.components["walls"]],
460
- "roofs": [c.__dict__ for c in session_state.components["roofs"]],
461
- "floors": [c.__dict__ for c in session_state.components["floors"]],
462
- "windows": [c.__dict__ for c in session_state.components["windows"]],
463
- "doors": [c.__dict__ for c in session_state.components["doors"]]
464
  }
465
- for type_name in components_dict:
466
- for comp in components_dict[type_name]:
467
- comp["orientation"] = comp["orientation"].name
468
- comp["component_type"] = comp["component_type"].name
469
  file_path = "components_export.json"
470
  with open(file_path, 'w') as f:
471
  json.dump(components_dict, f, indent=4)
@@ -478,8 +715,7 @@ class ComponentSelectionInterface:
478
  session_state.components = {'walls': [], 'roofs': [], 'floors': [], 'windows': [], 'doors': []}
479
  for type_name in data:
480
  for comp_dict in data[type_name]:
481
- comp_dict["orientation"] = Orientation[comp_dict["orientation"]]
482
- comp_dict["component_type"] = ComponentType[comp_dict["component_type"]]
483
  if type_name == "walls":
484
  comp = Wall(**comp_dict)
485
  elif type_name == "roofs":
 
1
  """
2
  HVAC Component Selection Module
3
  Provides UI for selecting building components in the HVAC Load Calculator.
4
+ All dependencies are included within this file for standalone operation.
5
  """
6
 
7
  import streamlit as st
 
13
  from enum import Enum
14
  from typing import Dict, List, Any, Optional
15
 
16
+ # --- Enums ---
17
+ class Orientation(Enum):
18
+ NORTH = "North"
19
+ NORTHEAST = "Northeast"
20
+ EAST = "East"
21
+ SOUTHEAST = "Southeast"
22
+ SOUTH = "South"
23
+ SOUTHWEST = "Southwest"
24
+ WEST = "West"
25
+ NORTHWEST = "Northwest"
26
+ HORIZONTAL = "Horizontal"
27
+ NOT_APPLICABLE = "N/A"
28
+
29
+ class ComponentType(Enum):
30
+ WALL = "Wall"
31
+ ROOF = "Roof"
32
+ FLOOR = "Floor"
33
+ WINDOW = "Window"
34
+ DOOR = "Door"
35
 
 
36
  class ShadingType(Enum):
37
  NONE = "None"
38
  INTERNAL = "Internal"
39
  EXTERNAL = "External"
40
  BETWEEN_GLASS = "Between-glass"
41
 
42
+ # --- Data Models ---
43
+ @dataclass
44
+ class MaterialLayer:
45
+ name: str
46
+ thickness: float # in mm
47
+ conductivity: float # W/(m·K)
48
+
49
+ @dataclass
50
+ class BuildingComponent:
51
+ id: str = field(default_factory=lambda: str(uuid.uuid4()))
52
+ name: str = "Unnamed Component"
53
+ component_type: ComponentType = ComponentType.WALL
54
+ u_value: float = 0.0 # W/(m²·K)
55
+ area: float = 0.0 # m²
56
+ orientation: Orientation = Orientation.NOT_APPLICABLE
57
+ material_layers: List[MaterialLayer] = field(default_factory=list)
58
+
59
+ def __post_init__(self):
60
+ if self.area < 0:
61
+ raise ValueError("Area cannot be negative")
62
+ if self.u_value < 0:
63
+ raise ValueError("U-value cannot be negative")
64
+
65
+ def to_dict(self) -> dict:
66
+ return {
67
+ "id": self.id,
68
+ "name": self.name,
69
+ "component_type": self.component_type.value,
70
+ "u_value": self.u_value,
71
+ "area": self.area,
72
+ "orientation": self.orientation.value,
73
+ "material_layers": [{"name": l.name, "thickness": l.thickness, "conductivity": l.conductivity} for l in self.material_layers]
74
+ }
75
+
76
+ @dataclass
77
+ class Wall(BuildingComponent):
78
+ wall_type: str = "Brick"
79
+ wall_group: str = "B"
80
+
81
+ def __post_init__(self):
82
+ super().__post_init__()
83
+ self.component_type = ComponentType.WALL
84
+
85
+ def to_dict(self) -> dict:
86
+ base_dict = super().to_dict()
87
+ base_dict.update({"wall_type": self.wall_type, "wall_group": self.wall_group})
88
+ return base_dict
89
+
90
+ @dataclass
91
+ class Roof(BuildingComponent):
92
+ roof_type: str = "Concrete"
93
+ roof_group: str = "C"
94
+
95
+ def __post_init__(self):
96
+ super().__post_init__()
97
+ self.component_type = ComponentType.ROOF
98
+ self.orientation = Orientation.HORIZONTAL
99
+
100
+ def to_dict(self) -> dict:
101
+ base_dict = super().to_dict()
102
+ base_dict.update({"roof_type": self.roof_type, "roof_group": self.roof_group})
103
+ return base_dict
104
+
105
+ @dataclass
106
+ class Floor(BuildingComponent):
107
+ floor_type: str = "Concrete"
108
+
109
+ def __post_init__(self):
110
+ super().__post_init__()
111
+ self.component_type = ComponentType.FLOOR
112
+ self.orientation = Orientation.HORIZONTAL
113
+
114
+ def to_dict(self) -> dict:
115
+ base_dict = super().to_dict()
116
+ base_dict.update({"floor_type": self.floor_type})
117
+ return base_dict
118
+
119
+ @dataclass
120
+ class Window(BuildingComponent):
121
+ shgc: float = 0.7
122
+ vt: float = 0.7
123
+ window_type: str = "Double Glazed"
124
+ glazing_layers: int = 2
125
+ gas_fill: str = "Air"
126
+ low_e_coating: bool = False
127
+ shading_device_id: str = "preset_none"
128
+
129
+ def __post_init__(self):
130
+ super().__post_init__()
131
+ self.component_type = ComponentType.WINDOW
132
+ if not 0 <= self.shgc <= 1:
133
+ raise ValueError("SHGC must be between 0 and 1")
134
+ if not 0 <= self.vt <= 1:
135
+ raise ValueError("VT must be between 0 and 1")
136
+
137
+ def to_dict(self) -> dict:
138
+ base_dict = super().to_dict()
139
+ base_dict.update({
140
+ "shgc": self.shgc, "vt": self.vt, "window_type": self.window_type,
141
+ "glazing_layers": self.glazing_layers, "gas_fill": self.gas_fill,
142
+ "low_e_coating": self.low_e_coating, "shading_device_id": self.shading_device_id
143
+ })
144
+ return base_dict
145
+
146
+ @dataclass
147
+ class Door(BuildingComponent):
148
+ door_type: str = "Solid Wood"
149
+
150
+ def __post_init__(self):
151
+ super().__post_init__()
152
+ self.component_type = ComponentType.DOOR
153
+
154
+ def to_dict(self) -> dict:
155
+ base_dict = super().to_dict()
156
+ base_dict.update({"door_type": self.door_type})
157
+ return base_dict
158
+
159
+ # --- Shading System ---
160
  @dataclass
161
  class ShadingDevice:
162
  id: str
 
211
 
212
  shading_system = ShadingSystem()
213
 
214
+ # --- Reference Data ---
215
+ class ReferenceData:
216
+ def __init__(self):
217
+ self.data = {
218
+ "materials": {
219
+ "mat_001": {"name": "Concrete", "conductivity": 1.4},
220
+ "mat_002": {"name": "Insulation", "conductivity": 0.04},
221
+ "mat_003": {"name": "Brick", "conductivity": 0.8},
222
+ "mat_004": {"name": "Glass", "conductivity": 1.0},
223
+ "mat_005": {"name": "Wood", "conductivity": 0.15}
224
+ },
225
+ "wall_types": {
226
+ "preset_wall_001": {
227
+ "id": "preset_wall_001", "name": "Standard Brick Wall", "component_type": "Wall",
228
+ "u_value": 0.5, "area": 0.0, "orientation": "North", "wall_type": "Brick", "wall_group": "B"
229
+ }
230
+ },
231
+ "roof_types": {
232
+ "preset_roof_001": {
233
+ "id": "preset_roof_001", "name": "Concrete Roof", "component_type": "Roof",
234
+ "u_value": 0.3, "area": 0.0, "orientation": "Horizontal", "roof_type": "Concrete", "roof_group": "C"
235
+ }
236
+ },
237
+ "floor_types": {
238
+ "preset_floor_001": {
239
+ "id": "preset_floor_001", "name": "Concrete Floor", "component_type": "Floor",
240
+ "u_value": 0.4, "area": 0.0, "orientation": "Horizontal", "floor_type": "Concrete"
241
+ }
242
+ },
243
+ "window_types": {
244
+ "preset_window_001": {
245
+ "id": "preset_window_001", "name": "Double Glazed Window", "component_type": "Window",
246
+ "u_value": 2.8, "area": 0.0, "orientation": "North", "shgc": 0.7, "vt": 0.8,
247
+ "window_type": "Double Glazed", "glazing_layers": 2, "gas_fill": "Air", "low_e_coating": False,
248
+ "shading_device_id": "preset_none"
249
+ }
250
+ },
251
+ "door_types": {
252
+ "preset_door_001": {
253
+ "id": "preset_door_001", "name": "Solid Wood Door", "component_type": "Door",
254
+ "u_value": 2.0, "area": 0.0, "orientation": "North", "door_type": "Solid Wood"
255
+ }
256
+ }
257
+ }
258
+
259
+ def get_materials(self) -> List[Dict[str, Any]]:
260
+ return list(self.data["materials"].values())
261
+
262
+ def get_component_data(self, component_type: str) -> Dict[str, Any]:
263
+ return self.data.get(f"{component_type.lower()}_types", {})
264
+
265
+ reference_data = ReferenceData()
266
+
267
+ # --- Component Library ---
268
+ class ComponentLibrary:
269
+ def __init__(self):
270
+ self.components = {}
271
+ self._load_preset_components()
272
+
273
+ def _load_preset_components(self):
274
+ for type_key in ["wall_types", "roof_types", "floor_types", "window_types", "door_types"]:
275
+ for component_data in reference_data.data[type_key].values():
276
+ component_data["orientation"] = Orientation(component_data["orientation"])
277
+ if component_data["component_type"] == ComponentType.WALL.value:
278
+ component = Wall(**component_data)
279
+ elif component_data["component_type"] == ComponentType.ROOF.value:
280
+ component = Roof(**component_data)
281
+ elif component_data["component_type"] == ComponentType.FLOOR.value:
282
+ component = Floor(**component_data)
283
+ elif component_data["component_type"] == ComponentType.WINDOW.value:
284
+ component = Window(**component_data)
285
+ elif component_data["component_type"] == ComponentType.DOOR.value:
286
+ component = Door(**component_data)
287
+ self.components[component.id] = component
288
+
289
+ def get_component(self, component_id: str) -> BuildingComponent:
290
+ return self.components.get(component_id)
291
+
292
+ def get_preset_components_by_type(self, component_type: ComponentType) -> List[BuildingComponent]:
293
+ return [comp for comp in self.components.values() if comp.component_type == component_type and comp.id.startswith("preset_")]
294
+
295
+ def add_component(self, component: BuildingComponent):
296
+ self.components[component.id] = component
297
+
298
+ def update_component(self, component_id: str, component: BuildingComponent):
299
+ self.components[component_id] = component
300
+
301
+ def remove_component(self, component_id: str):
302
+ if component_id.startswith("preset_"):
303
+ raise ValueError("Cannot remove preset components")
304
+ if component_id in self.components:
305
+ del self.components[component_id]
306
+
307
+ component_library = ComponentLibrary()
308
+
309
+ # --- U-Value Calculator ---
310
+ class UValueCalculator:
311
+ def __init__(self):
312
+ self.materials = reference_data.get_materials()
313
+
314
+ def get_preset_materials(self) -> List[Dict[str, Any]]:
315
+ return self.materials
316
+
317
+ def calculate_u_value(self, layers: List[Dict[str, float]]) -> float:
318
+ r_outside = 0.04 # m²·K/W
319
+ r_inside = 0.13 # m��·K/W
320
+ r_layers = sum(layer["thickness"] / 1000 / layer["conductivity"] for layer in layers)
321
+ r_total = r_outside + r_layers + r_inside
322
+ return 1 / r_total if r_total > 0 else 0
323
+
324
+ u_value_calculator = UValueCalculator()
325
+
326
  # --- Component Selection Interface ---
327
  class ComponentSelectionInterface:
328
  def __init__(self):
329
+ self.component_library = component_library
330
+ self.u_value_calculator = u_value_calculator
331
  self.shading_system = shading_system
332
+ self.reference_data = reference_data
333
 
334
  def display_component_selection(self, session_state: Any) -> None:
335
  st.title("Building Components")
 
385
  name = st.text_input("Name", f"New {type_name.capitalize()}")
386
  area = st.number_input("Area (m²)", min_value=0.0, value=1.0, step=0.1)
387
  if component_type not in [ComponentType.ROOF, ComponentType.FLOOR]:
388
+ orientation = st.selectbox("Orientation", [o.value for o in Orientation], index=0)
389
  else:
390
+ orientation = Orientation.HORIZONTAL.value
391
 
392
  with col2:
393
  selection_method = st.radio(f"{type_name.capitalize()} Selection Method", ["Select from Presets", "Custom Properties"])
 
451
  component = self.component_library.get_component(component_id)
452
  new_component = component.__class__(
453
  id=str(uuid.uuid4()), name=name, area=area,
454
+ orientation=Orientation(orientation), **component.__dict__
455
  )
456
  del new_component.__dict__["id"]
457
  else:
458
  if component_type == ComponentType.WALL:
459
  new_component = Wall(
460
  id=str(uuid.uuid4()), name=name, u_value=u_value, area=area,
461
+ orientation=Orientation(orientation), wall_type=wall_type, wall_group=wall_group
462
  )
463
  elif component_type == ComponentType.ROOF:
464
  new_component = Roof(
465
  id=str(uuid.uuid4()), name=name, u_value=u_value, area=area,
466
+ orientation=Orientation(orientation), roof_type=roof_type, roof_group=roof_group
467
  )
468
  elif component_type == ComponentType.FLOOR:
469
  new_component = Floor(
470
  id=str(uuid.uuid4()), name=name, u_value=u_value, area=area,
471
+ orientation=Orientation(orientation), floor_type=floor_type
472
  )
473
  elif component_type == ComponentType.WINDOW:
474
  new_component = Window(
475
  id=str(uuid.uuid4()), name=name, u_value=u_value, area=area,
476
+ orientation=Orientation(orientation), shgc=shgc, vt=vt, window_type=window_type,
477
  glazing_layers=glazing_layers, gas_fill=gas_fill, low_e_coating=low_e_coating,
478
  shading_device_id=shading_options[shading_device]
479
  )
480
  elif component_type == ComponentType.DOOR:
481
  new_component = Door(
482
  id=str(uuid.uuid4()), name=name, u_value=u_value, area=area,
483
+ orientation=Orientation(orientation), door_type=door_type
484
  )
485
  self.component_library.add_component(new_component)
486
  session_state.components[type_name + 's'].append(new_component)
 
489
  except ValueError as e:
490
  st.error(f"Error: {str(e)}")
491
 
492
+ def _display_components_table(self, session_state: Any, component_type: ComponentType, components: List[BuildingComponent]) -> None:
493
  type_name = component_type.value.lower()
494
  data = []
495
  for comp in components:
496
+ row = {"Name": comp.name, "Area (m²)": comp.area, "U-Value (W/m²·K)": comp.u_value, "Orientation": comp.orientation.value, "ID": comp.id}
497
  if component_type == ComponentType.WALL:
498
  row.update({"Wall Type": comp.wall_type, "Wall Group": comp.wall_group})
499
  elif component_type == ComponentType.ROOF:
 
544
  name = st.text_input("Name", value=component.name)
545
  area = st.number_input("Area (m²)", min_value=0.0, value=component.area, step=0.1)
546
  if component_type not in [ComponentType.ROOF, ComponentType.FLOOR]:
547
+ orientation = st.selectbox("Orientation", [o.value for o in Orientation],
548
+ index=[o.value for o in Orientation].index(component.orientation.value))
549
  else:
550
+ orientation = Orientation.HORIZONTAL.value
551
 
552
  with col2:
553
  u_value = st.number_input("U-Value (W/m²·K)", min_value=0.0, value=component.u_value, step=0.01)
 
592
  component.name = name
593
  component.area = area
594
  component.u_value = u_value
595
+ component.orientation = Orientation(orientation)
596
  if component_type == ComponentType.WALL:
597
  component.wall_type = wall_type
598
  component.wall_group = wall_group
 
642
  for i, l in enumerate(session_state.material_layers)]
643
  st.dataframe(pd.DataFrame(layer_data), use_container_width=True)
644
 
645
+ u_value = self.u_value_calculator.calculate_u_value(session_state.material_layers)
 
 
 
 
646
 
647
  col1, col2, col3 = st.columns(3)
648
  with col1:
649
+ st.metric("Total R-Value", f"{1/u_value if u_value > 0 else 0:.3f} m²·K/W")
650
  with col2:
651
  st.metric("U-Value", f"{u_value:.3f} W/m²·K")
652
  with col3:
 
697
 
698
  def _save_components(self, session_state: Any) -> None:
699
  components_dict = {
700
+ "walls": [c.to_dict() for c in session_state.components["walls"]],
701
+ "roofs": [c.to_dict() for c in session_state.components["roofs"]],
702
+ "floors": [c.to_dict() for c in session_state.components["floors"]],
703
+ "windows": [c.to_dict() for c in session_state.components["windows"]],
704
+ "doors": [c.to_dict() for c in session_state.components["doors"]]
705
  }
 
 
 
 
706
  file_path = "components_export.json"
707
  with open(file_path, 'w') as f:
708
  json.dump(components_dict, f, indent=4)
 
715
  session_state.components = {'walls': [], 'roofs': [], 'floors': [], 'windows': [], 'doors': []}
716
  for type_name in data:
717
  for comp_dict in data[type_name]:
718
+ comp_dict["orientation"] = Orientation(comp_dict["orientation"])
 
719
  if type_name == "walls":
720
  comp = Wall(**comp_dict)
721
  elif type_name == "roofs":