Spaces:
Sleeping
Sleeping
Update app/construction.py
Browse files- app/construction.py +37 -67
app/construction.py
CHANGED
@@ -20,15 +20,12 @@ from typing import Dict, List, Any, Optional, Tuple, Union
|
|
20 |
|
21 |
# Import the centralized data module and materials library
|
22 |
from app.m_c_data import get_default_constructions
|
23 |
-
from app.materials_library import get_available_materials, Material
|
24 |
|
25 |
# Configure logging
|
26 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
27 |
logger = logging.getLogger(__name__)
|
28 |
|
29 |
-
# Define constants
|
30 |
-
CONSTRUCTION_TYPES = ["Wall", "Roof", "Floor"]
|
31 |
-
|
32 |
# Surface resistances (EN ISO 6946 or ASHRAE)
|
33 |
R_SI = 0.12 # Internal surface resistance (m²·K/W)
|
34 |
R_SE = 0.04 # External surface resistance (m²·K/W)
|
@@ -42,10 +39,9 @@ class ConstructionLibrary:
|
|
42 |
data = [
|
43 |
{
|
44 |
"Name": name,
|
45 |
-
"Component Type": props["type"],
|
46 |
"U-Value (W/m²·K)": props.get("u_value", 0.0),
|
47 |
"R-Value (m²·K/W)": props.get("r_value", 0.0),
|
48 |
-
"Thermal Mass
|
49 |
"Embodied Carbon (kgCO₂e/m²)": props.get("embodied_carbon", 0.0),
|
50 |
"Cost (USD/m²)": props.get("cost", 0.0),
|
51 |
"Layers": len(props.get("layers", []))
|
@@ -162,10 +158,7 @@ def display_construction_page():
|
|
162 |
# Display project constructions DataFrame
|
163 |
st.subheader("Project Constructions")
|
164 |
try:
|
165 |
-
construction_filter = st.session_state.get("construction_filter", "All")
|
166 |
construction_df = construction_library.to_dataframe(st.session_state.project_data["constructions"]["project"])
|
167 |
-
if construction_filter != "All":
|
168 |
-
construction_df = construction_df[construction_df["Component Type"] == construction_filter]
|
169 |
if not construction_df.empty:
|
170 |
st.dataframe(construction_df, use_container_width=True)
|
171 |
else:
|
@@ -199,43 +192,20 @@ def initialize_construction():
|
|
199 |
|
200 |
def display_constructions_tables(construction_library: ConstructionLibrary):
|
201 |
"""Display library and project constructions tables."""
|
202 |
-
# Component type filter
|
203 |
-
filter_options = ["All"] + CONSTRUCTION_TYPES
|
204 |
-
construction_filter = st.selectbox("Filter by Component Type", filter_options, key="construction_filter")
|
205 |
-
|
206 |
st.subheader("Library Constructions")
|
207 |
with st.container():
|
208 |
-
library_constructions = list(construction_library.library_constructions.items())
|
209 |
-
|
210 |
-
library_constructions = [(name, c) for name, c in library_constructions if c["type"] == construction_filter]
|
211 |
-
cols = st.columns([2, 1, 1, 1, 1])
|
212 |
cols[0].write("**Name**")
|
213 |
-
cols[1].write("**Thermal Mass**")
|
214 |
cols[2].write("**U-Value (W/m²·K)**")
|
215 |
-
cols[3].write("**
|
216 |
-
|
217 |
-
|
218 |
-
cols
|
219 |
-
cols[
|
220 |
-
cols[1].write(f"{construction.get('thermal_mass', 0.0):.1f}")
|
221 |
cols[2].write(f"{construction.get('u_value', 0.0):.3f}")
|
222 |
-
if cols[3].button("
|
223 |
-
if st.session_state.get("rerun_trigger") != f"preview_cons_{name}":
|
224 |
-
st.session_state.rerun_trigger = f"preview_cons_{name}"
|
225 |
-
st.session_state.construction_editor = {
|
226 |
-
"name": name,
|
227 |
-
"component_type": construction["type"],
|
228 |
-
"layers": [{"material_name": layer["material"], "thickness": layer["thickness"]} for layer in construction["layers"]],
|
229 |
-
"is_edit": False
|
230 |
-
}
|
231 |
-
st.session_state.construction_form_state = {
|
232 |
-
"name": name,
|
233 |
-
"component_type": construction["type"],
|
234 |
-
"num_layers": len(construction["layers"]),
|
235 |
-
"layers": [{"material_name": layer["material"], "thickness": layer["thickness"]} for layer in construction["layers"]]
|
236 |
-
}
|
237 |
-
st.session_state.construction_rerun_pending = True
|
238 |
-
if cols[4].button("Copy", key=f"copy_lib_cons_{name}"):
|
239 |
new_name = f"{name}_Project"
|
240 |
counter = 1
|
241 |
while new_name in st.session_state.project_data["constructions"]["project"] or new_name in construction_library.library_constructions:
|
@@ -257,9 +227,13 @@ def display_constructions_tables(construction_library: ConstructionLibrary):
|
|
257 |
while new_mat_name in st.session_state.project_data["materials"]["project"] or new_mat_name in st.session_state.project_data["materials"]["library"]:
|
258 |
new_mat_name = f"{material_name}_Project_{counter}"
|
259 |
counter += 1
|
|
|
|
|
|
|
|
|
260 |
new_material = Material(
|
261 |
name=new_mat_name,
|
262 |
-
category=MaterialCategory
|
263 |
conductivity=library_material["thermal_conductivity"],
|
264 |
density=library_material["density"],
|
265 |
specific_heat=library_material["specific_heat"],
|
@@ -280,11 +254,11 @@ def display_constructions_tables(construction_library: ConstructionLibrary):
|
|
280 |
properties = calculate_construction_properties(new_layers, get_available_materials())
|
281 |
new_construction = {
|
282 |
"name": new_name,
|
283 |
-
"type": construction["type"],
|
284 |
"layers": new_layers,
|
285 |
"u_value": properties["u_value"],
|
286 |
"r_value": properties["r_value"],
|
287 |
"thermal_mass": properties["thermal_mass"],
|
|
|
288 |
"embodied_carbon": properties["embodied_carbon"],
|
289 |
"cost": properties["cost"],
|
290 |
"is_library": False
|
@@ -298,34 +272,30 @@ def display_constructions_tables(construction_library: ConstructionLibrary):
|
|
298 |
|
299 |
st.subheader("Project Constructions")
|
300 |
with st.container():
|
301 |
-
project_constructions = list(st.session_state.project_data["constructions"]["project"].items())
|
302 |
-
if construction_filter != "All":
|
303 |
-
project_constructions = [(name, c) for name, c in project_constructions if c["type"] == construction_filter]
|
304 |
if project_constructions:
|
305 |
cols = st.columns([2, 1, 1, 1, 1])
|
306 |
cols[0].write("**Name**")
|
307 |
-
cols[1].write("**Thermal Mass**")
|
308 |
cols[2].write("**U-Value (W/m²·K)**")
|
309 |
cols[3].write("**Edit**")
|
310 |
cols[4].write("**Delete**")
|
311 |
-
for name, construction in project_constructions:
|
312 |
cols = st.columns([2, 1, 1, 1, 1])
|
313 |
-
cols[0].write(name)
|
314 |
-
cols[1].write(
|
315 |
cols[2].write(f"{construction.get('u_value', 0.0):.3f}")
|
316 |
if cols[3].button("Edit", key=f"edit_proj_cons_{name}"):
|
317 |
if st.session_state.get("rerun_trigger") != f"edit_cons_{name}":
|
318 |
st.session_state.rerun_trigger = f"edit_cons_{name}"
|
319 |
st.session_state.construction_editor = {
|
320 |
"name": name,
|
321 |
-
"component_type": construction["type"],
|
322 |
"layers": [{"material_name": layer["material"], "thickness": layer["thickness"]} for layer in construction["layers"]],
|
323 |
"is_edit": True,
|
324 |
"original_name": name
|
325 |
}
|
326 |
st.session_state.construction_form_state = {
|
327 |
"name": name,
|
328 |
-
"component_type": construction["type"],
|
329 |
"num_layers": len(construction["layers"]),
|
330 |
"layers": [{"material_name": layer["material"], "thickness": layer["thickness"]} for layer in construction["layers"]]
|
331 |
}
|
@@ -363,7 +333,6 @@ def display_construction_editor(construction_library: ConstructionLibrary):
|
|
363 |
editor_state = st.session_state.get("construction_editor", {})
|
364 |
form_state = st.session_state.get("construction_form_state", {
|
365 |
"name": "",
|
366 |
-
"component_type": "Wall",
|
367 |
"num_layers": 1,
|
368 |
"layers": [{"material_name": material_names[0] if material_names else "", "thickness": 0.1}]
|
369 |
})
|
@@ -375,13 +344,6 @@ def display_construction_editor(construction_library: ConstructionLibrary):
|
|
375 |
help="Unique construction identifier",
|
376 |
key="construction_name_input"
|
377 |
)
|
378 |
-
component_type = st.selectbox(
|
379 |
-
"Component Type",
|
380 |
-
CONSTRUCTION_TYPES,
|
381 |
-
index=CONSTRUCTION_TYPES.index(form_state.get("component_type", editor_state.get("component_type", "Wall"))),
|
382 |
-
help="Building component type",
|
383 |
-
key="construction_component_type_input"
|
384 |
-
)
|
385 |
num_layers = st.number_input(
|
386 |
"Number of Layers",
|
387 |
min_value=1,
|
@@ -484,11 +446,11 @@ def display_construction_editor(construction_library: ConstructionLibrary):
|
|
484 |
properties = calculate_construction_properties(valid_layers, material_objects)
|
485 |
new_construction = {
|
486 |
"name": name,
|
487 |
-
"type": component_type,
|
488 |
"layers": valid_layers,
|
489 |
"u_value": properties["u_value"],
|
490 |
"r_value": properties["r_value"],
|
491 |
"thermal_mass": properties["thermal_mass"],
|
|
|
492 |
"embodied_carbon": properties["embodied_carbon"],
|
493 |
"cost": properties["cost"],
|
494 |
"is_library": False
|
@@ -504,7 +466,6 @@ def display_construction_editor(construction_library: ConstructionLibrary):
|
|
504 |
st.session_state.construction_editor = {}
|
505 |
st.session_state.construction_form_state = {
|
506 |
"name": "",
|
507 |
-
"component_type": "Wall",
|
508 |
"num_layers": 1,
|
509 |
"layers": [{"material_name": default_material, "thickness": 0.1}]
|
510 |
}
|
@@ -521,7 +482,7 @@ def calculate_construction_properties(layers: List[Dict[str, Any]], materials: D
|
|
521 |
Calculate the thermal properties of a construction based on its layers.
|
522 |
"""
|
523 |
r_value = R_SI # Internal surface resistance
|
524 |
-
thermal_mass = 0.0 #
|
525 |
embodied_carbon = 0.0 # kg CO₂e/m²
|
526 |
cost = 0.0 # $/m²
|
527 |
|
@@ -534,7 +495,7 @@ def calculate_construction_properties(layers: List[Dict[str, Any]], materials: D
|
|
534 |
if material.conductivity > 0:
|
535 |
r_value += thickness / material.conductivity
|
536 |
# Thermal mass
|
537 |
-
thermal_mass += material.density * material.specific_heat * thickness
|
538 |
# Embodied carbon
|
539 |
embodied_carbon += material.embodied_carbon * material.density * thickness
|
540 |
# Cost
|
@@ -543,10 +504,19 @@ def calculate_construction_properties(layers: List[Dict[str, Any]], materials: D
|
|
543 |
r_value += R_SE # External surface resistance
|
544 |
u_value = 1.0 / r_value if r_value > 0 else 0.0
|
545 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
546 |
return {
|
547 |
"u_value": u_value,
|
548 |
"r_value": r_value,
|
549 |
"thermal_mass": thermal_mass,
|
|
|
550 |
"embodied_carbon": embodied_carbon,
|
551 |
"cost": cost
|
552 |
}
|
@@ -569,7 +539,7 @@ def display_construction_help():
|
|
569 |
st.markdown("""
|
570 |
### Construction Library Help
|
571 |
|
572 |
-
This section allows you to create and manage multi-layer constructions for
|
573 |
|
574 |
**Key Concepts:**
|
575 |
|
@@ -577,14 +547,14 @@ def display_construction_help():
|
|
577 |
* **Layers**: Individual material layers that make up a construction, defined from outside to inside.
|
578 |
* **U-Value**: Overall heat transfer coefficient (W/m²·K), lower is better for insulation.
|
579 |
* **R-Value**: Thermal resistance (m²·K/W), higher is better for insulation.
|
580 |
-
* **Thermal Mass**: Heat storage capacity (
|
581 |
|
582 |
**Workflow:**
|
583 |
|
584 |
1. Create new constructions or add existing ones from the library to your project.
|
585 |
2. Define layers for each construction, selecting materials and specifying thicknesses.
|
586 |
3. Calculate thermal properties to evaluate performance.
|
587 |
-
4. Use constructions in the Building Components section to define
|
588 |
|
589 |
**Tips:**
|
590 |
|
|
|
20 |
|
21 |
# Import the centralized data module and materials library
|
22 |
from app.m_c_data import get_default_constructions
|
23 |
+
from app.materials_library import get_available_materials, Material, MaterialCategory
|
24 |
|
25 |
# Configure logging
|
26 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
27 |
logger = logging.getLogger(__name__)
|
28 |
|
|
|
|
|
|
|
29 |
# Surface resistances (EN ISO 6946 or ASHRAE)
|
30 |
R_SI = 0.12 # Internal surface resistance (m²·K/W)
|
31 |
R_SE = 0.04 # External surface resistance (m²·K/W)
|
|
|
39 |
data = [
|
40 |
{
|
41 |
"Name": name,
|
|
|
42 |
"U-Value (W/m²·K)": props.get("u_value", 0.0),
|
43 |
"R-Value (m²·K/W)": props.get("r_value", 0.0),
|
44 |
+
"Thermal Mass Category": props.get("thermal_mass_category", "Low"),
|
45 |
"Embodied Carbon (kgCO₂e/m²)": props.get("embodied_carbon", 0.0),
|
46 |
"Cost (USD/m²)": props.get("cost", 0.0),
|
47 |
"Layers": len(props.get("layers", []))
|
|
|
158 |
# Display project constructions DataFrame
|
159 |
st.subheader("Project Constructions")
|
160 |
try:
|
|
|
161 |
construction_df = construction_library.to_dataframe(st.session_state.project_data["constructions"]["project"])
|
|
|
|
|
162 |
if not construction_df.empty:
|
163 |
st.dataframe(construction_df, use_container_width=True)
|
164 |
else:
|
|
|
192 |
|
193 |
def display_constructions_tables(construction_library: ConstructionLibrary):
|
194 |
"""Display library and project constructions tables."""
|
|
|
|
|
|
|
|
|
195 |
st.subheader("Library Constructions")
|
196 |
with st.container():
|
197 |
+
library_constructions = list(construction_library.library_constructions.items())
|
198 |
+
cols = st.columns([2, 1, 1, 1])
|
|
|
|
|
199 |
cols[0].write("**Name**")
|
200 |
+
cols[1].write("**Thermal Mass Category**")
|
201 |
cols[2].write("**U-Value (W/m²·K)**")
|
202 |
+
cols[3].write("**Copy**")
|
203 |
+
for name, construction in library_constructions:
|
204 |
+
cols = st.columns([2, 1, 1, 1])
|
205 |
+
cols[0].write(name)
|
206 |
+
cols[1].write(construction.get("thermal_mass_category", "Low"))
|
|
|
207 |
cols[2].write(f"{construction.get('u_value', 0.0):.3f}")
|
208 |
+
if cols[3].button("Copy", key=f"copy_lib_cons_{name}"):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
209 |
new_name = f"{name}_Project"
|
210 |
counter = 1
|
211 |
while new_name in st.session_state.project_data["constructions"]["project"] or new_name in construction_library.library_constructions:
|
|
|
227 |
while new_mat_name in st.session_state.project_data["materials"]["project"] or new_mat_name in st.session_state.project_data["materials"]["library"]:
|
228 |
new_mat_name = f"{material_name}_Project_{counter}"
|
229 |
counter += 1
|
230 |
+
category_str = library_material["category"].upper().replace("-", "_")
|
231 |
+
if category_str not in MaterialCategory.__members__:
|
232 |
+
st.error(f"Invalid category for material {material_name}: {library_material['category']}")
|
233 |
+
break
|
234 |
new_material = Material(
|
235 |
name=new_mat_name,
|
236 |
+
category=MaterialCategory[category_str],
|
237 |
conductivity=library_material["thermal_conductivity"],
|
238 |
density=library_material["density"],
|
239 |
specific_heat=library_material["specific_heat"],
|
|
|
254 |
properties = calculate_construction_properties(new_layers, get_available_materials())
|
255 |
new_construction = {
|
256 |
"name": new_name,
|
|
|
257 |
"layers": new_layers,
|
258 |
"u_value": properties["u_value"],
|
259 |
"r_value": properties["r_value"],
|
260 |
"thermal_mass": properties["thermal_mass"],
|
261 |
+
"thermal_mass_category": properties["thermal_mass_category"],
|
262 |
"embodied_carbon": properties["embodied_carbon"],
|
263 |
"cost": properties["cost"],
|
264 |
"is_library": False
|
|
|
272 |
|
273 |
st.subheader("Project Constructions")
|
274 |
with st.container():
|
275 |
+
project_constructions = list(st.session_state.project_data["constructions"]["project"].items())
|
|
|
|
|
276 |
if project_constructions:
|
277 |
cols = st.columns([2, 1, 1, 1, 1])
|
278 |
cols[0].write("**Name**")
|
279 |
+
cols[1].write("**Thermal Mass Category**")
|
280 |
cols[2].write("**U-Value (W/m²·K)**")
|
281 |
cols[3].write("**Edit**")
|
282 |
cols[4].write("**Delete**")
|
283 |
+
for name, construction in project_constructions:
|
284 |
cols = st.columns([2, 1, 1, 1, 1])
|
285 |
+
cols[0].write(name)
|
286 |
+
cols[1].write(construction.get("thermal_mass_category", "Low"))
|
287 |
cols[2].write(f"{construction.get('u_value', 0.0):.3f}")
|
288 |
if cols[3].button("Edit", key=f"edit_proj_cons_{name}"):
|
289 |
if st.session_state.get("rerun_trigger") != f"edit_cons_{name}":
|
290 |
st.session_state.rerun_trigger = f"edit_cons_{name}"
|
291 |
st.session_state.construction_editor = {
|
292 |
"name": name,
|
|
|
293 |
"layers": [{"material_name": layer["material"], "thickness": layer["thickness"]} for layer in construction["layers"]],
|
294 |
"is_edit": True,
|
295 |
"original_name": name
|
296 |
}
|
297 |
st.session_state.construction_form_state = {
|
298 |
"name": name,
|
|
|
299 |
"num_layers": len(construction["layers"]),
|
300 |
"layers": [{"material_name": layer["material"], "thickness": layer["thickness"]} for layer in construction["layers"]]
|
301 |
}
|
|
|
333 |
editor_state = st.session_state.get("construction_editor", {})
|
334 |
form_state = st.session_state.get("construction_form_state", {
|
335 |
"name": "",
|
|
|
336 |
"num_layers": 1,
|
337 |
"layers": [{"material_name": material_names[0] if material_names else "", "thickness": 0.1}]
|
338 |
})
|
|
|
344 |
help="Unique construction identifier",
|
345 |
key="construction_name_input"
|
346 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
347 |
num_layers = st.number_input(
|
348 |
"Number of Layers",
|
349 |
min_value=1,
|
|
|
446 |
properties = calculate_construction_properties(valid_layers, material_objects)
|
447 |
new_construction = {
|
448 |
"name": name,
|
|
|
449 |
"layers": valid_layers,
|
450 |
"u_value": properties["u_value"],
|
451 |
"r_value": properties["r_value"],
|
452 |
"thermal_mass": properties["thermal_mass"],
|
453 |
+
"thermal_mass_category": properties["thermal_mass_category"],
|
454 |
"embodied_carbon": properties["embodied_carbon"],
|
455 |
"cost": properties["cost"],
|
456 |
"is_library": False
|
|
|
466 |
st.session_state.construction_editor = {}
|
467 |
st.session_state.construction_form_state = {
|
468 |
"name": "",
|
|
|
469 |
"num_layers": 1,
|
470 |
"layers": [{"material_name": default_material, "thickness": 0.1}]
|
471 |
}
|
|
|
482 |
Calculate the thermal properties of a construction based on its layers.
|
483 |
"""
|
484 |
r_value = R_SI # Internal surface resistance
|
485 |
+
thermal_mass = 0.0 # J/m²·K
|
486 |
embodied_carbon = 0.0 # kg CO₂e/m²
|
487 |
cost = 0.0 # $/m²
|
488 |
|
|
|
495 |
if material.conductivity > 0:
|
496 |
r_value += thickness / material.conductivity
|
497 |
# Thermal mass
|
498 |
+
thermal_mass += material.density * material.specific_heat * thickness
|
499 |
# Embodied carbon
|
500 |
embodied_carbon += material.embodied_carbon * material.density * thickness
|
501 |
# Cost
|
|
|
504 |
r_value += R_SE # External surface resistance
|
505 |
u_value = 1.0 / r_value if r_value > 0 else 0.0
|
506 |
|
507 |
+
# Categorize thermal mass
|
508 |
+
if thermal_mass < 30000:
|
509 |
+
thermal_mass_category = "Low"
|
510 |
+
elif 30000 <= thermal_mass <= 90000:
|
511 |
+
thermal_mass_category = "Medium"
|
512 |
+
else:
|
513 |
+
thermal_mass_category = "High"
|
514 |
+
|
515 |
return {
|
516 |
"u_value": u_value,
|
517 |
"r_value": r_value,
|
518 |
"thermal_mass": thermal_mass,
|
519 |
+
"thermal_mass_category": thermal_mass_category,
|
520 |
"embodied_carbon": embodied_carbon,
|
521 |
"cost": cost
|
522 |
}
|
|
|
539 |
st.markdown("""
|
540 |
### Construction Library Help
|
541 |
|
542 |
+
This section allows you to create and manage multi-layer constructions for building envelope components.
|
543 |
|
544 |
**Key Concepts:**
|
545 |
|
|
|
547 |
* **Layers**: Individual material layers that make up a construction, defined from outside to inside.
|
548 |
* **U-Value**: Overall heat transfer coefficient (W/m²·K), lower is better for insulation.
|
549 |
* **R-Value**: Thermal resistance (m²·K/W), higher is better for insulation.
|
550 |
+
* **Thermal Mass Category**: Heat storage capacity, categorized as Low (< 30,000 J/m²·K), Medium (30,000–90,000 J/m²·K), or High (> 90,000 J/m²·K).
|
551 |
|
552 |
**Workflow:**
|
553 |
|
554 |
1. Create new constructions or add existing ones from the library to your project.
|
555 |
2. Define layers for each construction, selecting materials and specifying thicknesses.
|
556 |
3. Calculate thermal properties to evaluate performance.
|
557 |
+
4. Use constructions in the Building Components section to define building elements.
|
558 |
|
559 |
**Tips:**
|
560 |
|