AnsysLink/backend/utils/error_handler.py
2025-08-11 13:58:59 +08:00

285 lines
11 KiB
Python

"""
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()