""" Defect Codes - Layer 7 of IOM Hierarchical classification system for infrastructure defects """ from typing import Dict, List, Optional, Tuple from enum import Enum class DefectCategory(str, Enum): """Top-level defect categories""" SURFACE = "surface" STRUCTURAL = "structural" COMPONENT = "component" BLOCKAGE = "blockage" ENVIRONMENTAL = "environmental" CORROSION = "corrosion" SAFETY = "safety" FUNCTIONAL = "functional" class DefectSeverity(str, Enum): """Defect severity levels""" COSMETIC = "cosmetic" # Purely visual MINOR = "minor" # Minor issue, no immediate action MODERATE = "moderate" # Requires attention within 12 months MAJOR = "major" # Requires attention within 3 months CRITICAL = "critical" # Immediate action required class DefectCode: """Represents a defect code with metadata""" def __init__( self, code: str, name_sv: str, name_en: str, category: DefectCategory, severity: DefectSeverity, description_sv: str = "", description_en: str = "", parent_code: Optional[str] = None, ai_keywords: Optional[List[str]] = None ): self.code = code self.name_sv = name_sv self.name_en = name_en self.category = category self.severity = severity self.description_sv = description_sv self.description_en = description_en self.parent_code = parent_code self.ai_keywords = ai_keywords or [] def get_name(self, lang: str = "sv") -> str: """Get name in specified language""" return self.name_en if lang == "en" else self.name_sv def get_description(self, lang: str = "sv") -> str: """Get description in specified language""" return self.description_en if lang == "en" else self.description_sv def to_dict(self, lang: str = "sv") -> Dict: """Convert to dictionary""" return { "code": self.code, "name": self.get_name(lang), "description": self.get_description(lang), "category": self.category.value, "severity": self.severity.value, "parent_code": self.parent_code, "ai_keywords": self.ai_keywords } class DefectRegistry: """Registry of all defect codes""" def __init__(self): self._codes: Dict[str, DefectCode] = {} self._init_codes() def _init_codes(self): """Initialize all defect codes""" # 1000 - Corrosion self._add(DefectCode( "1000", "Korrosion", "Corrosion", DefectCategory.CORROSION, DefectSeverity.MODERATE, "Korrosion på metalliska material", "Corrosion on metallic materials", ai_keywords=["rust", "corrosion", "oxidation", "pit"] )) self._add(DefectCode( "1100", "Ytrost", "Surface Rust", DefectCategory.CORROSION, DefectSeverity.MINOR, "Ytlig rost på yta", "Surface rust on surface", parent_code="1000", ai_keywords=["surface rust", "light rust", "orange"] )) self._add(DefectCode( "1200", "Genomrost", "Deep Corrosion", DefectCategory.CORROSION, DefectSeverity.MAJOR, "Genomgående korrosion som påverkar struktur", "Deep corrosion affecting structure", parent_code="1000", ai_keywords=["deep rust", "structural rust", "hole"] )) self._add(DefectCode( "1250", "Galvanisk korrosion", "Galvanic Corrosion", DefectCategory.CORROSION, DefectSeverity.MODERATE, "Korrosion mellan olika metaller", "Corrosion between dissimilar metals", parent_code="1000", ai_keywords=["galvanic", "dissimilar metal"] )) # 1300 - Concrete Cracks self._add(DefectCode( "1300", "Betongspricka", "Concrete Crack", DefectCategory.STRUCTURAL, DefectSeverity.MODERATE, "Spricka i betongkonstruktion", "Crack in concrete structure", ai_keywords=["crack", "fissure", "split"] )) self._add(DefectCode( "1310", "Plastisk krympning", "Plastic Shrinkage", DefectCategory.STRUCTURAL, DefectSeverity.MINOR, "Spricka från plastisk krympning", "Crack from plastic shrinkage", parent_code="1300", ai_keywords=["shrinkage crack", "surface crack"] )) self._add(DefectCode( "1320", "Temperaturspricka", "Temperature Crack", DefectCategory.STRUCTURAL, DefectSeverity.MINOR, "Spricka från temperaturförändring", "Crack from temperature change", parent_code="1300", ai_keywords=["temperature crack", "thermal crack"] )) self._add(DefectCode( "1330", "Lastspricka", "Load Crack", DefectCategory.STRUCTURAL, DefectSeverity.MAJOR, "Spricka från överbelastning", "Crack from overload", parent_code="1300", ai_keywords=["load crack", "structural crack", "wide crack"] )) # 1400 - Impact Damage self._add(DefectCode( "1400", "Påkörningsskada", "Impact Damage", DefectCategory.STRUCTURAL, DefectSeverity.MAJOR, "Skada från påkörning eller stöt", "Damage from impact or collision", ai_keywords=["impact", "collision", "dent", "damage"] )) self._add(DefectCode( "1410", "Ytskada", "Surface Damage", DefectCategory.SURFACE, DefectSeverity.MINOR, "Ytlig skada på yta", "Superficial damage to surface", parent_code="1400", ai_keywords=["scratch", "scuff", "minor damage"] )) self._add(DefectCode( "1420", "Deformation", "Deformation", DefectCategory.STRUCTURAL, DefectSeverity.MODERATE, "Permanent deformation", "Permanent deformation", parent_code="1400", ai_keywords=["bent", "deformed", "twisted"] )) self._add(DefectCode( "1430", "Genomskjutning", "Penetration", DefectCategory.STRUCTURAL, DefectSeverity.CRITICAL, "Genomskjutning av konstruktion", "Penetration of structure", parent_code="1400", ai_keywords=["hole", "penetration", "puncture"] )) # 1500 - Missing Component self._add(DefectCode( "1500", "Saknad komponent", "Missing Component", DefectCategory.COMPONENT, DefectSeverity.MODERATE, "Komponent saknas", "Component is missing", ai_keywords=["missing", "absent", "gone"] )) self._add(DefectCode( "1510", "Stöld", "Theft", DefectCategory.COMPONENT, DefectSeverity.MAJOR, "Komponent stulen", "Component stolen", parent_code="1500", ai_keywords=["theft", "stolen", "removed"] )) self._add(DefectCode( "1520", "Ej installerad", "Not Installed", DefectCategory.COMPONENT, DefectSeverity.MINOR, "Komponent ej installerad", "Component not installed", parent_code="1500", ai_keywords=["not installed", "missing installation"] )) self._add(DefectCode( "1530", "Avlägsnad", "Removed", DefectCategory.COMPONENT, DefectSeverity.MINOR, "Komponent avlägsnad", "Component removed", parent_code="1500", ai_keywords=["removed", "taken away"] )) # 1600 - Deformation/Settlement self._add(DefectCode( "1600", "Lutning / Sättning", "Tilt / Settlement", DefectCategory.STRUCTURAL, DefectSeverity.MODERATE, "Lutning eller sättning", "Tilt or settlement", ai_keywords=["tilt", "lean", "settlement", "sinking"] )) self._add(DefectCode( "1610", "Sättning", "Settlement", DefectCategory.STRUCTURAL, DefectSeverity.MAJOR, "Sättning av fundament", "Foundation settlement", parent_code="1600", ai_keywords=["settlement", "sinking", "subsidence"] )) self._add(DefectCode( "1620", "Böjning", "Bending", DefectCategory.STRUCTURAL, DefectSeverity.MODERATE, "Böjning av konstruktion", "Bending of structure", parent_code="1600", ai_keywords=["bending", "sagging", "deflection"] )) self._add(DefectCode( "1630", "Vridning", "Torsion", DefectCategory.STRUCTURAL, DefectSeverity.MODERATE, "Vridning av konstruktion", "Torsion of structure", parent_code="1600", ai_keywords=["twist", "torsion", "warp"] )) # 1700 - Vibration self._add(DefectCode( "1700", "Vibration", "Vibration", DefectCategory.FUNCTIONAL, DefectSeverity.MODERATE, "Oönskad vibration", "Unwanted vibration", ai_keywords=["vibration", "shaking", "oscillation"] )) self._add(DefectCode( "1710", "Mekanisk vibration", "Mechanical Vibration", DefectCategory.FUNCTIONAL, DefectSeverity.MODERATE, "Vibration från mekanisk källa", "Vibration from mechanical source", parent_code="1700", ai_keywords=["mechanical vibration", "machinery"] )) self._add(DefectCode( "1720", "Aerodynamisk vibration", "Aerodynamic Vibration", DefectCategory.FUNCTIONAL, DefectSeverity.MINOR, "Vibration från vind", "Vibration from wind", parent_code="1700", ai_keywords=["wind vibration", "aerodynamic", "flutter"] )) # 1800 - Vegetation self._add(DefectCode( "1800", "Vegetation", "Vegetation", DefectCategory.ENVIRONMENTAL, DefectSeverity.MINOR, "Oönskad vegetation", "Unwanted vegetation", ai_keywords=["vegetation", "plant", "tree", "weed"] )) self._add(DefectCode( "1810", "Blockerande vegetation", "Blocking Vegetation", DefectCategory.ENVIRONMENTAL, DefectSeverity.MODERATE, "Vegetation som blockerar sikt eller tillgång", "Vegetation blocking view or access", parent_code="1800", ai_keywords=["blocking", "overgrown", "obscured"] )) self._add(DefectCode( "1820", "Rotinträngning", "Root Intrusion", DefectCategory.STRUCTURAL, DefectSeverity.MAJOR, "Rötter som tränger in i struktur", "Roots intruding into structure", parent_code="1800", ai_keywords=["root", "intrusion", "crack with root"] )) self._add(DefectCode( "1830", "Fuktbevarande vegetation", "Moisture-Retaining Vegetation", DefectCategory.ENVIRONMENTAL, DefectSeverity.MINOR, "Vegetation som bevarar fukt", "Vegetation retaining moisture", parent_code="1800", ai_keywords=["moss", "lichen", "moisture"] )) # 1900 - Blockage self._add(DefectCode( "1900", "Blockering", "Blockage", DefectCategory.BLOCKAGE, DefectSeverity.MODERATE, "Blockering av funktion eller tillgång", "Blockage of function or access", ai_keywords=["blocked", "obstructed", "clogged"] )) self._add(DefectCode( "1910", "Fysisk blockering", "Physical Blockage", DefectCategory.BLOCKAGE, DefectSeverity.MODERATE, "Fysisk blockering", "Physical blockage", parent_code="1900", ai_keywords=["physical block", "debris", "trash"] )) self._add(DefectCode( "1920", "Digital blockering", "Digital Blockage", DefectCategory.FUNCTIONAL, DefectSeverity.MINOR, "Digital eller elektronisk blockering", "Digital or electronic blockage", parent_code="1900", ai_keywords=["digital", "electronic", "signal"] )) self._add(DefectCode( "1930", "Organisk blockering", "Organic Blockage", DefectCategory.BLOCKAGE, DefectSeverity.MODERATE, "Organisk blockering (löv, grenar)", "Organic blockage (leaves, branches)", parent_code="1900", ai_keywords=["organic", "leaves", "branches"] )) # 2000 - Surface Issues (Phase 1 focus) self._add(DefectCode( "2000", "Ytskada", "Surface Issue", DefectCategory.SURFACE, DefectSeverity.MINOR, "Ytskada eller nedsmutsning", "Surface damage or dirt", ai_keywords=["surface", "dirt", "stain"] )) self._add(DefectCode( "2100", "Nedsmutsning", "Dirt Accumulation", DefectCategory.SURFACE, DefectSeverity.COSMETIC, "Nedsmutsning på yta", "Dirt accumulation on surface", parent_code="2000", ai_keywords=["dirty", "dirt", "grime", "dust", "mud"] )) self._add(DefectCode( "2200", "Färgförändring", "Color Change", DefectCategory.SURFACE, DefectSeverity.COSMETIC, "Färgförändring eller blekning", "Color change or fading", parent_code="2000", ai_keywords=["faded", "discoloration", "color change", "bleached"] )) self._add(DefectCode( "2300", "Ytskada", "Surface Damage", DefectCategory.SURFACE, DefectSeverity.MINOR, "Skada på yta", "Damage to surface", parent_code="2000", ai_keywords=["damage", "scratch", "chip", "peel"] )) self._add(DefectCode( "2400", "Klotter", "Graffiti", DefectCategory.SURFACE, DefectSeverity.COSMETIC, "Klotter eller målning", "Graffiti or painting", parent_code="2000", ai_keywords=["graffiti", "paint", "tag", "vandalism"] )) # 3000 - Structural Issues self._add(DefectCode( "3000", "Strukturell skada", "Structural Damage", DefectCategory.STRUCTURAL, DefectSeverity.MAJOR, "Strukturell skada", "Structural damage", ai_keywords=["structural", "damage", "crack", "break"] )) self._add(DefectCode( "3100", "Spricka", "Crack", DefectCategory.STRUCTURAL, DefectSeverity.MODERATE, "Spricka i material", "Crack in material", parent_code="3000", ai_keywords=["crack", "fissure", "fracture"] )) self._add(DefectCode( "3200", "Deformation", "Deformation", DefectCategory.STRUCTURAL, DefectSeverity.MODERATE, "Permanent deformation", "Permanent deformation", parent_code="3000", ai_keywords=["deformed", "bent", "warped"] )) self._add(DefectCode( "3300", "Materialförlust", "Material Loss", DefectCategory.STRUCTURAL, DefectSeverity.MAJOR, "Förlust av material", "Loss of material", parent_code="3000", ai_keywords=["missing material", "erosion", "wear"] )) # 4000 - Component Issues self._add(DefectCode( "4000", "Komponentskada", "Component Issue", DefectCategory.COMPONENT, DefectSeverity.MODERATE, "Problem med komponent", "Component issue", ai_keywords=["component", "part", "piece"] )) self._add(DefectCode( "4100", "Saknad del", "Missing Part", DefectCategory.COMPONENT, DefectSeverity.MODERATE, "Del saknas", "Part missing", parent_code="4000", ai_keywords=["missing", "absent", "gone"] )) self._add(DefectCode( "4200", "Trasig del", "Broken Part", DefectCategory.COMPONENT, DefectSeverity.MODERATE, "Del är trasig", "Part broken", parent_code="4000", ai_keywords=["broken", "damaged", "fractured"] )) self._add(DefectCode( "4300", "Lossnad del", "Loose Part", DefectCategory.COMPONENT, DefectSeverity.MINOR, "Del är lossnad", "Part loose", parent_code="4000", ai_keywords=["loose", "detached", "hanging"] )) # 5000 - Blockage Issues self._add(DefectCode( "5000", "Blockering", "Blockage", DefectCategory.BLOCKAGE, DefectSeverity.MODERATE, "Blockering", "Blockage", ai_keywords=["blocked", "obstructed"] )) self._add(DefectCode( "5100", "Fysisk blockering", "Physical Blockage", DefectCategory.BLOCKAGE, DefectSeverity.MODERATE, "Fysisk blockering", "Physical blockage", parent_code="5000", ai_keywords=["physical", "block", "debris"] )) self._add(DefectCode( "5200", "Synlig blockering", "Visible Blockage", DefectCategory.BLOCKAGE, DefectSeverity.MINOR, "Synlig blockering", "Visible blockage", parent_code="5000", ai_keywords=["visible", "block", "obscured"] )) # 6000 - Environmental Issues self._add(DefectCode( "6000", "Miljöskada", "Environmental Damage", DefectCategory.ENVIRONMENTAL, DefectSeverity.MINOR, "Skada från miljöfaktorer", "Damage from environmental factors", ai_keywords=["environmental", "weather", "water"] )) self._add(DefectCode( "6100", "Vegetation", "Vegetation", DefectCategory.ENVIRONMENTAL, DefectSeverity.MINOR, "Oönskad vegetation", "Unwanted vegetation", parent_code="6000", ai_keywords=["vegetation", "plant", "tree"] )) self._add(DefectCode( "6200", "Vattenskada", "Water Damage", DefectCategory.ENVIRONMENTAL, DefectSeverity.MODERATE, "Skada från vatten", "Water damage", parent_code="6000", ai_keywords=["water", "moisture", "leak", "flood"] )) self._add(DefectCode( "6300", "Isskada", "Ice Damage", DefectCategory.ENVIRONMENTAL, DefectSeverity.MODERATE, "Skada från is eller frost", "Damage from ice or frost", parent_code="6000", ai_keywords=["ice", "frost", "freeze"] )) def _add(self, code: DefectCode): """Add defect code to registry""" self._codes[code.code] = code def get(self, code: str) -> Optional[DefectCode]: """Get defect code by code string""" return self._codes.get(code) def get_by_category(self, category: DefectCategory) -> List[DefectCode]: """Get all codes in a category""" return [c for c in self._codes.values() if c.category == category] def get_by_severity(self, severity: DefectSeverity) -> List[DefectCode]: """Get all codes with a severity level""" return [c for c in self._codes.values() if c.severity == severity] def search(self, query: str, lang: str = "sv") -> List[DefectCode]: """Search codes by name or description""" query = query.lower() results = [] for code in self._codes.values(): name = code.get_name(lang).lower() desc = code.get_description(lang).lower() if query in name or query in desc or query in code.code: results.append(code) return results def get_children(self, parent_code: str) -> List[DefectCode]: """Get child codes for a parent code""" return [c for c in self._codes.values() if c.parent_code == parent_code] def get_top_level(self) -> List[DefectCode]: """Get all top-level codes (no parent)""" return [c for c in self._codes.values() if c.parent_code is None] def get_ai_suggestions(self, keywords: List[str]) -> List[Tuple[DefectCode, float]]: """ Get defect code suggestions based on AI keywords Returns: List of (DefectCode, confidence) tuples """ suggestions = [] for code in self._codes.values(): matches = sum(1 for kw in keywords if kw.lower() in [k.lower() for k in code.ai_keywords]) if matches > 0: confidence = matches / len(keywords) suggestions.append((code, confidence)) # Sort by confidence suggestions.sort(key=lambda x: x[1], reverse=True) return suggestions def list_all(self) -> List[Dict]: """List all defect codes""" return [code.to_dict() for code in self._codes.values()] def list_categories(self) -> List[Dict]: """List all categories with counts""" categories = {} for code in self._codes.values(): cat = code.category.value if cat not in categories: categories[cat] = 0 categories[cat] += 1 return [ {"category": cat, "count": count} for cat, count in sorted(categories.items()) ] # Singleton instance defect_registry = DefectRegistry() def get_defect_code(code: str) -> Optional[DefectCode]: """Convenience function""" return defect_registry.get(code) def search_defects(query: str, lang: str = "sv") -> List[DefectCode]: """Convenience function""" return defect_registry.search(query, lang) def suggest_defect_codes(keywords: List[str]) -> List[Tuple[DefectCode, float]]: """Convenience function""" return defect_registry.get_ai_suggestions(keywords) if __name__ == '__main__': # Example usage registry = DefectRegistry() print("=== All Defect Codes ===") for code in registry.list_all()[:5]: print(f"{code['code']}: {code['name']} ({code['category']})") print("\n=== Search for 'crack' ===") results = registry.search("spricka") for code in results[:5]: print(f"{code.code}: {code.name_sv}") print("\n=== AI Suggestions for ['rust', 'corrosion'] ===") suggestions = registry.get_ai_suggestions(["rust", "corrosion"]) for code, confidence in suggestions[:5]: print(f"{code.code}: {code.name_en} (confidence: {confidence:.2f})") print("\n=== Categories ===") for cat in registry.list_categories(): print(f"{cat['category']}: {cat['count']} codes")