Updates
This commit is contained in:
1
ETB-API/incident_intelligence/views/__init__.py
Normal file
1
ETB-API/incident_intelligence/views/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Views for incident intelligence
|
||||
Binary file not shown.
Binary file not shown.
457
ETB-API/incident_intelligence/views/incident.py
Normal file
457
ETB-API/incident_intelligence/views/incident.py
Normal file
@@ -0,0 +1,457 @@
|
||||
"""
|
||||
Views for incident intelligence API endpoints
|
||||
"""
|
||||
from rest_framework import viewsets, status, filters
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from django.db.models import Q, Count, Avg, DurationField, F
|
||||
from django.db.models.functions import Extract
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from ..models import (
|
||||
Incident, IncidentClassification, SeveritySuggestion, IncidentCorrelation,
|
||||
DuplicationDetection, IncidentPattern, AIProcessingLog
|
||||
)
|
||||
from ..serializers.incident import (
|
||||
IncidentSerializer, IncidentCreateSerializer, IncidentUpdateSerializer,
|
||||
IncidentAnalysisSerializer, IncidentSearchSerializer, IncidentStatsSerializer,
|
||||
IncidentCorrelationSerializer, DuplicationDetectionSerializer, IncidentPatternSerializer
|
||||
)
|
||||
from ..ai.classification import IncidentClassifier, SeverityAnalyzer
|
||||
from ..ai.correlation import IncidentCorrelationEngine
|
||||
from ..ai.duplication import DuplicationDetector
|
||||
from ..security import IncidentAccessControl, log_incident_operation, get_user_accessible_incidents
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IncidentViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
ViewSet for managing incidents with AI intelligence
|
||||
"""
|
||||
queryset = Incident.objects.all()
|
||||
permission_classes = [IsAuthenticated]
|
||||
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
|
||||
filterset_fields = ['status', 'severity', 'category', 'assigned_to', 'reporter', 'ai_processed', 'is_duplicate']
|
||||
search_fields = ['title', 'description', 'free_text']
|
||||
ordering_fields = ['created_at', 'updated_at', 'severity', 'status']
|
||||
ordering = ['-created_at']
|
||||
|
||||
def get_serializer_class(self):
|
||||
"""Return appropriate serializer based on action"""
|
||||
if self.action == 'create':
|
||||
return IncidentCreateSerializer
|
||||
elif self.action in ['update', 'partial_update']:
|
||||
return IncidentUpdateSerializer
|
||||
elif self.action == 'analysis':
|
||||
return IncidentAnalysisSerializer
|
||||
else:
|
||||
return IncidentSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
"""Filter queryset based on user permissions and search parameters"""
|
||||
# Start with user-accessible incidents
|
||||
queryset = get_user_accessible_incidents(self.request.user)
|
||||
|
||||
# Apply search filters
|
||||
search_serializer = IncidentSearchSerializer(data=self.request.query_params)
|
||||
if search_serializer.is_valid():
|
||||
params = search_serializer.validated_data
|
||||
|
||||
if params.get('query'):
|
||||
queryset = queryset.filter(
|
||||
Q(title__icontains=params['query']) |
|
||||
Q(description__icontains=params['query']) |
|
||||
Q(free_text__icontains=params['query'])
|
||||
)
|
||||
|
||||
if params.get('category'):
|
||||
queryset = queryset.filter(category=params['category'])
|
||||
|
||||
if params.get('severity'):
|
||||
queryset = queryset.filter(severity=params['severity'])
|
||||
|
||||
if params.get('status'):
|
||||
queryset = queryset.filter(status=params['status'])
|
||||
|
||||
if params.get('assigned_to'):
|
||||
queryset = queryset.filter(assigned_to_id=params['assigned_to'])
|
||||
|
||||
if params.get('reporter'):
|
||||
queryset = queryset.filter(reporter_id=params['reporter'])
|
||||
|
||||
if params.get('date_from'):
|
||||
queryset = queryset.filter(created_at__gte=params['date_from'])
|
||||
|
||||
if params.get('date_to'):
|
||||
queryset = queryset.filter(created_at__lte=params['date_to'])
|
||||
|
||||
if params.get('has_ai_analysis') is not None:
|
||||
queryset = queryset.filter(ai_processed=params['has_ai_analysis'])
|
||||
|
||||
if params.get('is_duplicate') is not None:
|
||||
queryset = queryset.filter(is_duplicate=params['is_duplicate'])
|
||||
|
||||
return queryset
|
||||
|
||||
def perform_create(self, serializer):
|
||||
"""Create incident and trigger AI processing"""
|
||||
# Check create permissions
|
||||
IncidentAccessControl.check_incident_access(self.request.user, None, 'create')
|
||||
|
||||
incident = serializer.save()
|
||||
|
||||
# Log the creation
|
||||
log_incident_operation(self.request.user, incident, 'create')
|
||||
|
||||
# Trigger AI processing
|
||||
try:
|
||||
from ..tasks import process_incident_ai
|
||||
process_incident_ai.delay(incident.id)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to trigger AI processing for incident {incident.id}: {e}")
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def analyze(self, request, pk=None):
|
||||
"""Trigger AI analysis for a specific incident"""
|
||||
incident = self.get_object()
|
||||
|
||||
# Check analyze permissions
|
||||
IncidentAccessControl.check_incident_access(request.user, incident, 'analyze')
|
||||
|
||||
try:
|
||||
from ..tasks import process_incident_ai
|
||||
process_incident_ai.delay(incident.id)
|
||||
|
||||
# Log the analysis trigger
|
||||
log_incident_operation(request.user, incident, 'analyze')
|
||||
|
||||
return Response({
|
||||
'message': 'AI analysis triggered successfully',
|
||||
'incident_id': str(incident.id)
|
||||
}, status=status.HTTP_202_ACCEPTED)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to trigger AI analysis for incident {incident.id}: {e}")
|
||||
return Response({
|
||||
'error': 'Failed to trigger AI analysis',
|
||||
'details': str(e)
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
@action(detail=True, methods=['get'])
|
||||
def analysis(self, request, pk=None):
|
||||
"""Get comprehensive AI analysis for an incident"""
|
||||
incident = self.get_object()
|
||||
serializer = self.get_serializer(incident)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=True, methods=['get'])
|
||||
def correlations(self, request, pk=None):
|
||||
"""Get correlations for a specific incident"""
|
||||
incident = self.get_object()
|
||||
|
||||
# Get correlations where this incident is primary
|
||||
primary_correlations = incident.correlations_as_primary.all()
|
||||
|
||||
# Get correlations where this incident is related
|
||||
related_correlations = incident.correlations_as_related.all()
|
||||
|
||||
# Combine and serialize
|
||||
all_correlations = list(primary_correlations) + list(related_correlations)
|
||||
serializer = IncidentCorrelationSerializer(all_correlations, many=True)
|
||||
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=True, methods=['get'])
|
||||
def duplicates(self, request, pk=None):
|
||||
"""Get potential duplicates for a specific incident"""
|
||||
incident = self.get_object()
|
||||
|
||||
# Get duplications where this incident is incident_a
|
||||
duplications_a = incident.duplication_as_a.all()
|
||||
|
||||
# Get duplications where this incident is incident_b
|
||||
duplications_b = incident.duplication_as_b.all()
|
||||
|
||||
# Combine and serialize
|
||||
all_duplications = list(duplications_a) + list(duplications_b)
|
||||
serializer = DuplicationDetectionSerializer(all_duplications, many=True)
|
||||
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=True, methods=['get'])
|
||||
def patterns(self, request, pk=None):
|
||||
"""Get patterns associated with a specific incident"""
|
||||
incident = self.get_object()
|
||||
patterns = incident.patterns.all()
|
||||
serializer = IncidentPatternSerializer(patterns, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def stats(self, request):
|
||||
"""Get incident statistics"""
|
||||
queryset = self.get_queryset()
|
||||
|
||||
# Basic counts
|
||||
total_incidents = queryset.count()
|
||||
open_incidents = queryset.filter(status__in=['OPEN', 'IN_PROGRESS']).count()
|
||||
resolved_incidents = queryset.filter(status__in=['RESOLVED', 'CLOSED']).count()
|
||||
|
||||
# Severity counts
|
||||
critical_incidents = queryset.filter(severity='CRITICAL').count()
|
||||
high_incidents = queryset.filter(severity='HIGH').count()
|
||||
medium_incidents = queryset.filter(severity='MEDIUM').count()
|
||||
low_incidents = queryset.filter(severity='LOW').count()
|
||||
|
||||
# Average resolution time
|
||||
resolved_with_time = queryset.filter(
|
||||
status__in=['RESOLVED', 'CLOSED'],
|
||||
resolved_at__isnull=False
|
||||
).annotate(
|
||||
resolution_time=F('resolved_at') - F('created_at')
|
||||
)
|
||||
|
||||
avg_resolution_time = resolved_with_time.aggregate(
|
||||
avg_time=Avg('resolution_time')
|
||||
)['avg_time']
|
||||
|
||||
# Category distribution
|
||||
incidents_by_category = dict(
|
||||
queryset.values('category').annotate(count=Count('id')).values_list('category', 'count')
|
||||
)
|
||||
|
||||
# Severity distribution
|
||||
incidents_by_severity = dict(
|
||||
queryset.values('severity').annotate(count=Count('id')).values_list('severity', 'count')
|
||||
)
|
||||
|
||||
# Status distribution
|
||||
incidents_by_status = dict(
|
||||
queryset.values('status').annotate(count=Count('id')).values_list('status', 'count')
|
||||
)
|
||||
|
||||
# AI processing stats
|
||||
ai_processed_count = queryset.filter(ai_processed=True).count()
|
||||
duplicate_count = queryset.filter(is_duplicate=True).count()
|
||||
correlation_count = IncidentCorrelation.objects.count()
|
||||
pattern_count = IncidentPattern.objects.count()
|
||||
|
||||
stats_data = {
|
||||
'total_incidents': total_incidents,
|
||||
'open_incidents': open_incidents,
|
||||
'resolved_incidents': resolved_incidents,
|
||||
'critical_incidents': critical_incidents,
|
||||
'high_incidents': high_incidents,
|
||||
'medium_incidents': medium_incidents,
|
||||
'low_incidents': low_incidents,
|
||||
'average_resolution_time': avg_resolution_time,
|
||||
'incidents_by_category': incidents_by_category,
|
||||
'incidents_by_severity': incidents_by_severity,
|
||||
'incidents_by_status': incidents_by_status,
|
||||
'ai_processed_count': ai_processed_count,
|
||||
'duplicate_count': duplicate_count,
|
||||
'correlation_count': correlation_count,
|
||||
'pattern_count': pattern_count
|
||||
}
|
||||
|
||||
serializer = IncidentStatsSerializer(stats_data)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=False, methods=['post'])
|
||||
def batch_analyze(self, request):
|
||||
"""Trigger AI analysis for multiple incidents"""
|
||||
incident_ids = request.data.get('incident_ids', [])
|
||||
|
||||
if not incident_ids:
|
||||
return Response({
|
||||
'error': 'No incident IDs provided'
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
from ..tasks import batch_process_incidents_ai
|
||||
batch_process_incidents_ai.delay(incident_ids)
|
||||
|
||||
return Response({
|
||||
'message': f'AI analysis triggered for {len(incident_ids)} incidents',
|
||||
'incident_ids': incident_ids
|
||||
}, status=status.HTTP_202_ACCEPTED)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to trigger batch AI analysis: {e}")
|
||||
return Response({
|
||||
'error': 'Failed to trigger batch AI analysis',
|
||||
'details': str(e)
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
@action(detail=False, methods=['post'])
|
||||
def detect_duplicates(self, request):
|
||||
"""Detect duplicates across all incidents"""
|
||||
try:
|
||||
from ..tasks import detect_all_duplicates
|
||||
detect_all_duplicates.delay()
|
||||
|
||||
return Response({
|
||||
'message': 'Duplicate detection process started'
|
||||
}, status=status.HTTP_202_ACCEPTED)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to trigger duplicate detection: {e}")
|
||||
return Response({
|
||||
'error': 'Failed to trigger duplicate detection',
|
||||
'details': str(e)
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
@action(detail=False, methods=['post'])
|
||||
def correlate_incidents(self, request):
|
||||
"""Run correlation analysis on all incidents"""
|
||||
try:
|
||||
from ..tasks import correlate_all_incidents
|
||||
correlate_all_incidents.delay()
|
||||
|
||||
return Response({
|
||||
'message': 'Correlation analysis process started'
|
||||
}, status=status.HTTP_202_ACCEPTED)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to trigger correlation analysis: {e}")
|
||||
return Response({
|
||||
'error': 'Failed to trigger correlation analysis',
|
||||
'details': str(e)
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
|
||||
class IncidentCorrelationViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""
|
||||
ViewSet for viewing incident correlations
|
||||
"""
|
||||
queryset = IncidentCorrelation.objects.all()
|
||||
serializer_class = IncidentCorrelationSerializer
|
||||
permission_classes = [IsAuthenticated]
|
||||
filter_backends = [DjangoFilterBackend, filters.OrderingFilter]
|
||||
filterset_fields = ['correlation_type', 'correlation_strength', 'is_problem_indicator']
|
||||
ordering_fields = ['confidence_score', 'created_at']
|
||||
ordering = ['-confidence_score']
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def problem_indicators(self, request):
|
||||
"""Get correlations that indicate larger problems"""
|
||||
queryset = self.get_queryset().filter(is_problem_indicator=True)
|
||||
serializer = self.get_serializer(queryset, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
class DuplicationDetectionViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""
|
||||
ViewSet for viewing duplication detection results
|
||||
"""
|
||||
queryset = DuplicationDetection.objects.all()
|
||||
serializer_class = DuplicationDetectionSerializer
|
||||
permission_classes = [IsAuthenticated]
|
||||
filter_backends = [DjangoFilterBackend, filters.OrderingFilter]
|
||||
filterset_fields = ['duplication_type', 'status', 'recommended_action']
|
||||
ordering_fields = ['confidence_score', 'similarity_score', 'created_at']
|
||||
ordering = ['-confidence_score']
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def approve_merge(self, request, pk=None):
|
||||
"""Approve merging of duplicate incidents"""
|
||||
duplication = self.get_object()
|
||||
|
||||
if duplication.status != 'DETECTED':
|
||||
return Response({
|
||||
'error': 'Only detected duplications can be approved for merging'
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
# Mark as reviewed and approved
|
||||
duplication.status = 'REVIEWED'
|
||||
duplication.reviewed_by = request.user
|
||||
duplication.reviewed_at = timezone.now()
|
||||
duplication.save()
|
||||
|
||||
# Trigger merge process
|
||||
from ..tasks import merge_duplicate_incidents
|
||||
merge_duplicate_incidents.delay(duplication.id)
|
||||
|
||||
return Response({
|
||||
'message': 'Merge approved and queued for processing'
|
||||
}, status=status.HTTP_200_OK)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to approve merge for duplication {duplication.id}: {e}")
|
||||
return Response({
|
||||
'error': 'Failed to approve merge',
|
||||
'details': str(e)
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def reject_merge(self, request, pk=None):
|
||||
"""Reject merging of duplicate incidents"""
|
||||
duplication = self.get_object()
|
||||
|
||||
if duplication.status != 'DETECTED':
|
||||
return Response({
|
||||
'error': 'Only detected duplications can be rejected'
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
duplication.status = 'REJECTED'
|
||||
duplication.reviewed_by = request.user
|
||||
duplication.reviewed_at = timezone.now()
|
||||
duplication.save()
|
||||
|
||||
return Response({
|
||||
'message': 'Merge rejected'
|
||||
}, status=status.HTTP_200_OK)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to reject merge for duplication {duplication.id}: {e}")
|
||||
return Response({
|
||||
'error': 'Failed to reject merge',
|
||||
'details': str(e)
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
|
||||
class IncidentPatternViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""
|
||||
ViewSet for viewing incident patterns
|
||||
"""
|
||||
queryset = IncidentPattern.objects.all()
|
||||
serializer_class = IncidentPatternSerializer
|
||||
permission_classes = [IsAuthenticated]
|
||||
filter_backends = [DjangoFilterBackend, filters.OrderingFilter]
|
||||
filterset_fields = ['pattern_type', 'is_active', 'is_resolved']
|
||||
ordering_fields = ['confidence_score', 'incident_count', 'created_at']
|
||||
ordering = ['-confidence_score']
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def active_patterns(self, request):
|
||||
"""Get active patterns that need attention"""
|
||||
queryset = self.get_queryset().filter(is_active=True, is_resolved=False)
|
||||
serializer = self.get_serializer(queryset, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def resolve_pattern(self, request, pk=None):
|
||||
"""Mark a pattern as resolved"""
|
||||
pattern = self.get_object()
|
||||
|
||||
try:
|
||||
pattern.is_resolved = True
|
||||
pattern.is_active = False
|
||||
pattern.save()
|
||||
|
||||
return Response({
|
||||
'message': 'Pattern marked as resolved'
|
||||
}, status=status.HTTP_200_OK)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to resolve pattern {pattern.id}: {e}")
|
||||
return Response({
|
||||
'error': 'Failed to resolve pattern',
|
||||
'details': str(e)
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
Reference in New Issue
Block a user