feat: better logging for matches

This commit is contained in:
Björn Steinhagen
2025-02-24 17:36:02 +01:00
parent 665b26e823
commit e92066a8de
3 changed files with 140 additions and 47 deletions
+67 -16
View File
@@ -67,6 +67,7 @@ class RevitCarbonAnalyzer:
"skipped_elements": [],
"errors": [],
"total_carbon": 0.0,
"missing_factors": {"timber": [], "steel": []},
}
# Process each element
@@ -89,6 +90,22 @@ class RevitCarbonAnalyzer:
}
)
# Get missing factors
missing_timber, missing_steel = self.carbon_calculator.get_missing_factors()
results["missing_factors"]["timber"] = missing_timber
results["missing_factors"]["steel"] = missing_steel
# Log missing factors
if missing_timber:
print(f"Missing timber factors ({len(missing_timber)}):")
for item in missing_timber:
print(f" - {item}")
if missing_steel:
print(f"Missing steel factors ({len(missing_steel)}):")
for item in missing_steel:
print(f" - {item}")
return results
def _process_single_element(self, element: Dict) -> Dict:
@@ -106,17 +123,7 @@ class RevitCarbonAnalyzer:
# Calculate carbon
try:
print(
f"Processing element: {element_id}, category: {processed_element.category.value}"
)
print(
f"Materials: {[m.properties.name for m in processed_element.materials]}"
)
carbon_results = self.carbon_calculator.calculate_carbon(processed_element)
print(f"Carbon results: {carbon_results}")
print(
f"Total carbon: {sum(r.total_carbon for r in carbon_results.values())}"
)
return {
"id": element_id,
"status": "processed",
@@ -157,10 +164,20 @@ def automate_function(
) -> None:
"""Program entry point."""
try:
# Get string values from enums if needed
steel_db = function_inputs.steel_database
timber_db = function_inputs.timber_database
# Ensure we're working with string values
if hasattr(steel_db, "value"):
steel_db = steel_db.value
if hasattr(timber_db, "value"):
timber_db = timber_db.value
# Initialize analyzer
analyzer = RevitCarbonAnalyzer(
steel_database=function_inputs.steel_database,
timber_database=function_inputs.timber_database,
steel_database=steel_db,
timber_database=timber_db,
)
# Get commit root
@@ -183,12 +200,46 @@ def automate_function(
# Process results
_process_automation_results(automate_context, results)
# Mark success
automate_context.mark_run_success(
f"Analysis complete. Processed {len(results['processed_elements'])} elements. "
f"Total carbon: {results['total_carbon']:.2f} kgCO2e"
# Prepare detailed success message
success_message = (
f"🚀 Analysis complete.\n\n\tProcessed:\t\t{len(results['processed_elements'])} elements.\n\t"
f"Total carbon:\t{results['total_carbon']:.2f} kgCOe\n"
)
# Add missing factors to message if any
missing_timber = results["missing_factors"]["timber"]
missing_steel = results["missing_factors"]["steel"]
if missing_timber or missing_steel:
success_message += "\nMissing emission factors detected:\n"
if missing_timber:
success_message += (
f"- Timber ({len(missing_timber)}): {', '.join(missing_timber[:5])}"
)
if len(missing_timber) > 5:
success_message += f" and {len(missing_timber) - 5} more"
success_message += "\n"
if missing_steel:
success_message += (
f"- Steel ({len(missing_steel)}): {', '.join(missing_steel[:5])}"
)
if len(missing_steel) > 5:
success_message += f" and {len(missing_steel) - 5} more"
success_message += "\n"
success_message += "\nThese materials were assigned zero carbon. Consider updating the database."
else:
success_message += (
"\nNOTE: All materials successfully matched with emission factors."
"complete."
)
# Mark success with detailed message
automate_context.mark_run_success(success_message)
except Exception as e:
automate_context.mark_run_failed(f"Analysis failed: {str(e)}")
raise
+35 -12
View File
@@ -53,7 +53,14 @@ class EmissionFactorRegistry:
}
self._steel_aliases = {
"hot rolled": ["hot-rolled", "hot_rolled", "hotrolled", "steel"],
"hot rolled": [
"hot-rolled",
"hot_rolled",
"hotrolled",
"345 MPa",
"350W",
"350W(1)",
],
"hss": ["hollow structural section", "hollow section", "tube"],
"plate": ["flat plate"],
"rebar": ["reinforcing bar", "reinforcement"],
@@ -93,21 +100,41 @@ class EmissionFactorRegistry:
@staticmethod
def _normalize_material_name(name: str, aliases: Dict[str, list]) -> str:
"""Normalize material name using centralized aliases with substring matching"""
name = name.lower()
"""Normalize material name using centralized aliases with enhanced matching.
# Check standard names first
This improved version handles:
- Case insensitivity
- Direct matches (exact)
- Substring matches (contains)
- Special known cases
"""
# Convert to lowercase for case-insensitive comparison
name = name.lower().strip()
# Special case handling
if any(
steel_name in name
for steel_name in ["345 mpa", "350w", "steel 345", "default_steel"]
):
return "Hot Rolled" # Map all these variants to Hot Rolled steel
# Check for direct match with standard names
for standard_name in aliases.keys():
if standard_name in name:
if standard_name.lower() == name:
return standard_name
# Then check aliases
# Check for standard name appearing as substring
for standard_name in aliases.keys():
if standard_name.lower() in name:
return standard_name
# Check aliases
for standard_name, variations in aliases.items():
for variation in variations:
if variation in name:
if variation.lower() == name or variation.lower() in name:
return standard_name
# If no match found, return original
return name
def get_timber_factor(
@@ -127,8 +154,6 @@ class EmissionFactorRegistry:
normalized_name = self._normalize_material_name(
material_name, self._timber_aliases
)
print(f"Looking up '{material_name}' in {database}")
print(f"Normalized name: {normalized_name}")
return db.get_factor(normalized_name)
def get_steel_factor(
@@ -148,8 +173,6 @@ class EmissionFactorRegistry:
normalized_name = self._normalize_material_name(
material_name, self._steel_aliases
)
print(f"Looking up '{material_name}' in {database}")
print(f"Normalized name: {normalized_name}")
return db.get_factor(normalized_name)
def get_concrete_factor(
+38 -19
View File
@@ -1,4 +1,4 @@
from typing import Dict, Optional
from typing import Dict, Optional, Tuple, List
from src.domain.carbon.emission_factor_registry import EmissionFactorRegistry
from src.domain.types import BuildingElement, CarbonResult, Material, MaterialType
@@ -26,20 +26,33 @@ class CarbonCalculator:
self._timber_factors_cache = {}
self._concrete_factors_cache = {}
# TODO: Remove
# Track missing factors
self._missing_timber_factors = set()
self._missing_steel_factors = set()
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
# Track missing factors
if "No emission factor found" in str(e):
if material.type == MaterialType.WOOD:
material_key = (
material.properties.structural_asset
or material.properties.name
)
self._missing_timber_factors.add(material_key)
elif material.type == MaterialType.METAL:
self._missing_steel_factors.add(
material.grade or material.properties.name
)
print(
f"Error calculating carbon for {material.properties.name}: {str(e)}"
)
@@ -48,7 +61,6 @@ class CarbonCalculator:
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:
@@ -65,11 +77,9 @@ class CarbonCalculator:
# 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}"
@@ -85,24 +95,25 @@ class CarbonCalculator:
def _calculate_wood_carbon(self, material: Material) -> CarbonResult:
"""Calculate carbon emissions for wood."""
structural_asset = material.properties.structural_asset
material_name = material.properties.structural_asset
# Use name as a fallback if structural_asset is None
if material_name is None:
# Extract material type from name
material_name = material.properties.name
# 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}"
)
if material_name not in self._timber_factors_cache:
factor = self._registry.get_timber_factor(
structural_asset, self._timber_database
material_name, self._timber_database
)
print(f"Factor found: {factor}")
if not factor:
raise ValueError(
f"No emission factor found for wood type: {structural_asset}"
f"No emission factor found for wood type: {material_name}"
)
self._timber_factors_cache[structural_asset] = factor
self._timber_factors_cache[material_name] = factor
factor = self._timber_factors_cache[structural_asset]
factor = self._timber_factors_cache[material_name]
return CarbonResult(
factor=factor.value,
total_carbon=material.properties.volume * factor.value,
@@ -117,3 +128,11 @@ class CarbonCalculator:
total_carbon=0.0, # Placeholder
category="Concrete",
)
# TODO: Remove
def get_missing_factors(self) -> Tuple[List[str], List[str]]:
"""Return lists of materials that had no emission factor."""
return (
sorted(list(self._missing_timber_factors)),
sorted(list(self._missing_steel_factors)),
)