"""Exterior/interior part classification for assembly review. Classifies parts based on name-pattern matching against configurable exterior patterns. Everything that does not match is classified as interior. """ from __future__ import annotations import logging from dataclasses import dataclass from config.settings import SplitterSettings from src.loader.assembly_tree import AssemblyNode logger = logging.getLogger(__name__) @dataclass class ClassificationResult: """Result of exterior/interior classification for a part.""" node_id: str name: str classification: str # "exterior" or "interior" score: int # 1 = matched exterior pattern, 0 = no match reasons: list[str] def classify_parts( root: AssemblyNode, settings: SplitterSettings | None = None, ) -> list[ClassificationResult]: """Classify all leaf parts as exterior or interior. Uses simple case-insensitive substring matching against the configured exterior_patterns. Any part whose name matches at least one pattern is classified as "exterior"; everything else is "interior". Args: root: Assembly tree root node. settings: Splitter settings with exterior_patterns. Returns: List of ClassificationResult for each leaf part. """ if settings is None: settings = SplitterSettings() patterns = [p.lower() for p in settings.exterior_patterns] results: list[ClassificationResult] = [] for leaf in root.iter_leaves(): if leaf.shape is None or leaf.shape.IsNull(): continue name_lower = leaf.name.lower() matched = [p for p in patterns if p in name_lower] if matched: classification = "exterior" score = 1 reasons = [f"name matches exterior pattern(s): {', '.join(matched)}"] else: classification = "interior" score = 0 reasons = ["no exterior pattern matched"] leaf.classification = classification result = ClassificationResult( node_id=leaf.id, name=leaf.name, classification=classification, score=score, reasons=reasons, ) results.append(result) logger.debug("Part '%s': -> %s (%s)", leaf.name, classification, "; ".join(reasons)) ext_count = sum(1 for r in results if r.classification == "exterior") int_count = sum(1 for r in results if r.classification == "interior") logger.info("Classification: %d exterior, %d interior", ext_count, int_count) return results