""" Error handling utilities for CAE Mesh Generator """ import logging import traceback from datetime import datetime from functools import wraps from flask import jsonify from typing import Dict, Any, Optional # Configure logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler('app.log'), logging.StreamHandler() ] ) logger = logging.getLogger(__name__) class MeshGeneratorError(Exception): """Base exception class for mesh generator errors""" def __init__(self, message: str, error_code: str = None, details: Dict = None): self.message = message self.error_code = error_code or 'GENERAL_ERROR' self.details = details or {} super().__init__(self.message) class FileUploadError(MeshGeneratorError): """Exception for file upload related errors""" def __init__(self, message: str, details: Dict = None): super().__init__(message, 'FILE_UPLOAD_ERROR', details) class ANSYSError(MeshGeneratorError): """Exception for ANSYS related errors""" def __init__(self, message: str, details: Dict = None, diagnosis=None): super().__init__(message, 'ANSYS_ERROR', details) self.diagnosis = diagnosis # ErrorDiagnosis object from ANSYSErrorHandler class MeshGenerationError(MeshGeneratorError): """Exception for mesh generation related errors""" def __init__(self, message: str, details: Dict = None): super().__init__(message, 'MESH_GENERATION_ERROR', details) class ValidationError(MeshGeneratorError): """Exception for validation related errors""" def __init__(self, message: str, details: Dict = None): super().__init__(message, 'VALIDATION_ERROR', details) def handle_api_error(func): """Decorator for handling API errors""" @wraps(func) def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except MeshGeneratorError as e: logger.error(f"API Error in {func.__name__}: {e.message}", extra={ 'error_code': e.error_code, 'details': e.details }) return jsonify({ 'success': False, 'error': e.message, 'error_code': e.error_code, 'details': e.details }), 400 except Exception as e: logger.error(f"Unexpected error in {func.__name__}: {str(e)}", extra={ 'traceback': traceback.format_exc() }) return jsonify({ 'success': False, 'error': 'An unexpected error occurred. Please try again.', 'error_code': 'INTERNAL_ERROR' }), 500 return wrapper def handle_ansys_error(func): """Decorator for handling ANSYS-specific errors with intelligent diagnosis""" @wraps(func) def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except ImportError as e: logger.warning(f"ANSYS not available: {str(e)}") raise ANSYSError( "ANSYS Mechanical is not available. Please check your installation.", details={'import_error': str(e)} ) except Exception as e: error_msg = str(e) # Use intelligent ANSYS error analysis try: from backend.pymechanical.ansys_error_handler import ANSYSErrorHandler, ErrorContext # Initialize error handler (could be cached for performance) error_handler = ANSYSErrorHandler() # Create error context from function context context = ErrorContext( operation_type=func.__name__, timestamp=datetime.now() ) # Analyze the error diagnosis = error_handler.analyze_error(error_msg, context) # Create enhanced error with diagnosis user_message = f"{diagnosis.title}: {diagnosis.description}" details = { 'original_error': error_msg, 'error_category': diagnosis.category.value, 'severity': diagnosis.severity.value, 'solutions': diagnosis.immediate_solutions, 'estimated_fix_time': diagnosis.estimated_fix_time, 'recovery_possible': diagnosis.recovery_possible, 'confidence_level': diagnosis.confidence_level } logger.error(f"ANSYS error in {func.__name__}: {error_msg} (Category: {diagnosis.category.value}, Severity: {diagnosis.severity.value})") raise ANSYSError(user_message, details=details, diagnosis=diagnosis) except ImportError: # Fallback to basic error handling if error handler not available logger.warning("ANSYS error handler not available, using basic error handling") # Basic ANSYS error patterns and user-friendly messages if 'license' in error_msg.lower(): user_message = "ANSYS license error. Please check your license status." elif 'connection' in error_msg.lower(): user_message = "Cannot connect to ANSYS. Please ensure ANSYS is properly installed." elif 'geometry' in error_msg.lower(): user_message = "Geometry processing error. Please check your STEP file." elif 'mesh' in error_msg.lower(): user_message = "Mesh generation failed. The geometry may be too complex or contain errors." else: user_message = f"ANSYS processing error: {error_msg}" logger.error(f"ANSYS error in {func.__name__}: {error_msg}") raise ANSYSError(user_message, details={'original_error': error_msg}) except Exception as analysis_error: # Fallback if error analysis fails logger.warning(f"Error analysis failed: {str(analysis_error)}, using basic error handling") user_message = f"ANSYS processing error: {error_msg}" logger.error(f"ANSYS error in {func.__name__}: {error_msg}") raise ANSYSError(user_message, details={'original_error': error_msg, 'analysis_error': str(analysis_error)}) return wrapper def validate_file_upload(file) -> None: """Validate uploaded file""" if not file: raise FileUploadError("No file provided") if file.filename == '': raise FileUploadError("No file selected") # Check file extension allowed_extensions = {'.step', '.stp'} file_ext = '.' + file.filename.rsplit('.', 1)[-1].lower() if file_ext not in allowed_extensions: raise FileUploadError( f"Invalid file format. Only STEP files (.step, .stp) are supported.", details={'provided_extension': file_ext, 'allowed_extensions': list(allowed_extensions)} ) # Check file size (this is also handled by Flask, but we add explicit validation) if hasattr(file, 'content_length') and file.content_length: max_size = 100 * 1024 * 1024 # 100MB if file.content_length > max_size: raise FileUploadError( f"File too large. Maximum size is 100MB.", details={'file_size': file.content_length, 'max_size': max_size} ) def validate_mesh_parameters(params: Dict[str, Any]) -> None: """Validate mesh generation parameters""" if not isinstance(params, dict): raise ValidationError("Parameters must be a dictionary") # Validate element size if provided if 'element_size' in params: element_size = params['element_size'] if not isinstance(element_size, (int, float)) or element_size <= 0: raise ValidationError( "Element size must be a positive number", details={'provided_value': element_size} ) # Validate refinement settings if provided if 'refinement_regions' in params: regions = params['refinement_regions'] if not isinstance(regions, list): raise ValidationError("Refinement regions must be a list") for i, region in enumerate(regions): if not isinstance(region, dict): raise ValidationError( f"Refinement region {i} must be a dictionary", details={'region_index': i} ) class ErrorReporter: """Class for collecting and reporting errors""" def __init__(self): self.errors = [] self.warnings = [] def add_error(self, message: str, error_code: str = None, details: Dict = None): """Add an error to the report""" self.errors.append({ 'message': message, 'error_code': error_code or 'GENERAL_ERROR', 'details': details or {}, 'timestamp': self._get_timestamp() }) logger.error(f"Error reported: {message}", extra={'error_code': error_code, 'details': details}) def add_warning(self, message: str, details: Dict = None): """Add a warning to the report""" self.warnings.append({ 'message': message, 'details': details or {}, 'timestamp': self._get_timestamp() }) logger.warning(f"Warning reported: {message}", extra={'details': details}) def has_errors(self) -> bool: """Check if there are any errors""" return len(self.errors) > 0 def has_warnings(self) -> bool: """Check if there are any warnings""" return len(self.warnings) > 0 def get_report(self) -> Dict[str, Any]: """Get the complete error report""" return { 'errors': self.errors, 'warnings': self.warnings, 'error_count': len(self.errors), 'warning_count': len(self.warnings) } def clear(self): """Clear all errors and warnings""" self.errors.clear() self.warnings.clear() def _get_timestamp(self) -> str: """Get current timestamp""" from datetime import datetime return datetime.now().isoformat() def create_error_response(error: Exception, status_code: int = 500) -> tuple: """Create a standardized error response""" if isinstance(error, MeshGeneratorError): return jsonify({ 'success': False, 'error': error.message, 'error_code': error.error_code, 'details': error.details }), status_code else: logger.error(f"Unexpected error: {str(error)}", extra={'traceback': traceback.format_exc()}) return jsonify({ 'success': False, 'error': 'An unexpected error occurred', 'error_code': 'INTERNAL_ERROR' }), status_code def log_processing_step(step_name: str, status: str, details: Dict = None): """Log processing step with standardized format""" logger.info(f"Processing step: {step_name} - {status}", extra={ 'step': step_name, 'status': status, 'details': details or {} }) # Global error reporter instance error_reporter = ErrorReporter()