feat: better logging for matches
This commit is contained in:
@@ -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} kgCO₂e\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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user