Files
speckle-embodied-carbon-cal…/src/services/carbon_calculator.py
T
2025-02-24 16:45:44 +01:00

120 lines
4.8 KiB
Python

from typing import Dict, Optional
from src.domain.carbon.emission_factor_registry import EmissionFactorRegistry
from src.domain.types import BuildingElement, CarbonResult, Material, MaterialType
class CarbonCalculator:
"""Calculates embodied carbon for building elements."""
def __init__(
self,
steel_database: str,
timber_database: str,
concrete_database: Optional[str] = None,
):
# Store database selections
self._steel_database = steel_database
self._timber_database = timber_database
self._concrete_database = concrete_database
# Initialize registry
self._registry = EmissionFactorRegistry()
# Cache common material factors to avoid repeated lookups
self._steel_factors_cache = {}
self._timber_factors_cache = {}
self._concrete_factors_cache = {}
def calculate_carbon(self, element: BuildingElement) -> Dict[str, CarbonResult]:
"""Calculate carbon emissions for an element's materials."""
results = {}
print(f"Calculating carbon for {len(element.materials)} materials")
for material in element.materials:
print(f" Material: {material.properties.name}, type: {material.type}")
print(f" Structural asset: {material.properties.structural_asset}")
print(f" Volume: {material.properties.volume}, Mass: {material.mass}")
try:
result = self._calculate_material_carbon(material)
results[material.properties.name] = result
except Exception as e:
# Log error but continue with other materials
print(
f"Error calculating carbon for {material.properties.name}: {str(e)}"
)
return results
def _calculate_material_carbon(self, material: Material) -> CarbonResult:
"""Calculate carbon emissions for a single material."""
print(f"Calculating for material type: {material.type}")
if material.type == MaterialType.METAL:
return self._calculate_metal_carbon(material)
elif material.type == MaterialType.WOOD:
return self._calculate_wood_carbon(material)
elif material.type == MaterialType.CONCRETE:
return self._calculate_concrete_carbon(material)
else:
raise ValueError(f"Unsupported material type: {material.type}")
def _calculate_metal_carbon(self, material: Material) -> CarbonResult:
"""Calculate carbon emissions for metal."""
if not material.mass:
raise ValueError("Mass required for metal carbon calculation")
# Get factor from cache or registry
if material.grade not in self._steel_factors_cache:
print(f"Metal: {material.grade}{self._steel_database}")
factor = self._registry.get_steel_factor(
material.grade, self._steel_database
)
print(f"Factor found: {factor}")
if not factor:
raise ValueError(
f"No emission factor found for metal grade: {material.grade}"
)
self._steel_factors_cache[material.grade] = factor
factor = self._steel_factors_cache[material.grade]
return CarbonResult(
factor=factor.value,
total_carbon=material.mass * factor.value,
category="Metal",
)
def _calculate_wood_carbon(self, material: Material) -> CarbonResult:
"""Calculate carbon emissions for wood."""
structural_asset = material.properties.structural_asset
# Get factor from cache or registry
if structural_asset not in self._timber_factors_cache:
print(
f"Wood: {material.properties.structural_asset}{self._timber_database}"
)
factor = self._registry.get_timber_factor(
structural_asset, self._timber_database
)
print(f"Factor found: {factor}")
if not factor:
raise ValueError(
f"No emission factor found for wood type: {structural_asset}"
)
self._timber_factors_cache[structural_asset] = factor
factor = self._timber_factors_cache[structural_asset]
return CarbonResult(
factor=factor.value,
total_carbon=material.properties.volume * factor.value,
category="Wood",
)
def _calculate_concrete_carbon(self, material: Material) -> CarbonResult:
"""Calculate carbon emissions for concrete."""
# TODO: Implement concrete-specific calculation when concrete database is added
return CarbonResult(
factor=0.0, # Placeholder
total_carbon=0.0, # Placeholder
category="Concrete",
)