import logging import traceback import numpy as np from typing import Any, Dict, Optional, List, Callable from functools import wraps logger = logging.getLogger(__name__) class EvaluationExceptionHandler: """평가 시스템 전용 예외 처리기""" @staticmethod def handle_metric_calculation_error(metric_name: str, default_value: float = 0.0): """메트릭 계산 오류 처리 데코레이터""" def decorator(func): @wraps(func) def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except ZeroDivisionError as e: logger.error(f"Zero division error in {metric_name}: {str(e)}") return default_value except ValueError as e: logger.error(f"Value error in {metric_name}: {str(e)}") return default_value except KeyError as e: logger.error(f"Key error in {metric_name}: {str(e)}") return default_value except MemoryError as e: logger.error(f"Memory error in {metric_name}: {str(e)}") return default_value except Exception as e: logger.error(f"Unexpected error in {metric_name}: {str(e)}") logger.debug(traceback.format_exc()) return default_value return wrapper return decorator @staticmethod def validate_metric_result(metric_name: str, result: Any, expected_range: tuple) -> bool: """메트릭 결과 검증""" if not isinstance(result, (int, float)): logger.warning(f"{metric_name}: Invalid result type {type(result)}") return False # 무한대 값 검증 if np.isinf(result): logger.warning(f"{metric_name}: Result is infinite: {result}") return False # NaN 값 검증 if np.isnan(result): logger.warning(f"{metric_name}: Result is NaN: {result}") return False if not (expected_range[0] <= result <= expected_range[1]): logger.warning(f"{metric_name}: Result {result} out of expected range {expected_range}") return False return True @staticmethod def handle_vertices_key_error(mesh_data: Dict, fallback_vertices: List[List[float]] = None) -> Dict: """vertices 키 누락 오류 처리""" if 'vertices' not in mesh_data: logger.warning("vertices 키가 누락되었습니다. 대체 데이터 생성 중...") if fallback_vertices is None: # 기본 정육면체 vertices 생성 fallback_vertices = [ [0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0], # 하단 [0, 0, 1], [1, 0, 1], [1, 1, 1], [0, 1, 1] # 상단 ] mesh_data['vertices'] = fallback_vertices # faces도 생성 if 'faces' not in mesh_data: mesh_data['faces'] = [ [0, 1, 2], [0, 2, 3], # 하단 [4, 7, 6], [4, 6, 5], # 상단 [0, 4, 5], [0, 5, 1], # 앞면 [2, 6, 7], [2, 7, 3], # 뒷면 [0, 3, 7], [0, 7, 4], # 왼쪽 [1, 5, 6], [1, 6, 2] # 오른쪽 ] return mesh_data @staticmethod def handle_infinite_values(value: float, metric_name: str, max_value: float = 1000.0) -> float: """무한대 값 처리""" if np.isinf(value): if value > 0: # 양의 무한대 logger.warning(f"{metric_name}: 양의 무한대 값 감지, {max_value}로 대체") return max_value else: # 음의 무한대 logger.warning(f"{metric_name}: 음의 무한대 값 감지, 0으로 대체") return 0.0 if np.isnan(value): logger.warning(f"{metric_name}: NaN 값 감지, 0으로 대체") return 0.0 return value @staticmethod def safe_divide(numerator: float, denominator: float, default_value: float = 0.0) -> float: """안전한 나눗셈""" if denominator == 0 or np.isclose(denominator, 0): logger.warning(f"0으로 나누기 시도: {numerator} / {denominator}") return default_value result = numerator / denominator # 무한대 값 검사 if np.isinf(result): logger.warning(f"나눗셈 결과가 무한대: {numerator} / {denominator}") return default_value return result @staticmethod def validate_mesh_data(mesh_data: Dict) -> bool: """메시 데이터 검증""" required_keys = ['vertices', 'faces'] for key in required_keys: if key not in mesh_data: logger.error(f"메시 데이터에 필수 키 '{key}'가 누락되었습니다") return False if not mesh_data[key]: logger.error(f"메시 데이터의 '{key}'가 비어있습니다") return False # vertices 검증 vertices = mesh_data['vertices'] if not isinstance(vertices, (list, np.ndarray)): logger.error("vertices는 리스트 또는 numpy 배열이어야 합니다") return False if len(vertices) == 0: logger.error("vertices가 비어있습니다") return False # faces 검증 faces = mesh_data['faces'] if not isinstance(faces, (list, np.ndarray)): logger.error("faces는 리스트 또는 numpy 배열이어야 합니다") return False if len(faces) == 0: logger.error("faces가 비어있습니다") return False return True @staticmethod def create_robust_error_handler(metric_name: str, default_value: float = 0.0) -> Callable: """견고한 오류 처리기 생성""" def error_handler(func): @wraps(func) def wrapper(*args, **kwargs): try: result = func(*args, **kwargs) # 결과 검증 if result is None: logger.warning(f"{metric_name}: 결과가 None입니다") return default_value if isinstance(result, float): if np.isinf(result): logger.warning(f"{metric_name}: 결과가 무한대입니다") return default_value if np.isnan(result): logger.warning(f"{metric_name}: 결과가 NaN입니다") return default_value return result except KeyError as e: logger.error(f"{metric_name}: 키 오류 - {str(e)}") return default_value except ValueError as e: logger.error(f"{metric_name}: 값 오류 - {str(e)}") return default_value except MemoryError as e: logger.error(f"{metric_name}: 메모리 오류 - {str(e)}") return default_value except Exception as e: logger.error(f"{metric_name}: 예상치 못한 오류 - {str(e)}") logger.debug(traceback.format_exc()) return default_value return wrapper return error_handler