Updates
This commit is contained in:
714
ETB-API/analytics_predictive_insights/views/analytics.py
Normal file
714
ETB-API/analytics_predictive_insights/views/analytics.py
Normal file
@@ -0,0 +1,714 @@
|
||||
"""
|
||||
Analytics & Predictive Insights views for Enterprise Incident Management API
|
||||
Implements comprehensive analytics endpoints for KPIs, predictive insights, and dashboards
|
||||
"""
|
||||
from rest_framework import viewsets, status, permissions
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.pagination import PageNumberPagination
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from django_filters import rest_framework as filters
|
||||
from django.db.models import Q, Avg, Count, Sum, Max, Min
|
||||
from django.utils import timezone
|
||||
from datetime import datetime, timedelta
|
||||
from decimal import Decimal
|
||||
|
||||
from ..models import (
|
||||
KPIMetric, KPIMeasurement, IncidentRecurrenceAnalysis, PredictiveModel,
|
||||
AnomalyDetection, CostImpactAnalysis, DashboardConfiguration,
|
||||
HeatmapData, PredictiveInsight
|
||||
)
|
||||
from ..serializers.analytics import (
|
||||
KPIMetricSerializer, KPIMeasurementSerializer, IncidentRecurrenceAnalysisSerializer,
|
||||
PredictiveModelSerializer, AnomalyDetectionSerializer, CostImpactAnalysisSerializer,
|
||||
DashboardConfigurationSerializer, HeatmapDataSerializer, PredictiveInsightSerializer,
|
||||
KPISummarySerializer, AnomalySummarySerializer, CostSummarySerializer,
|
||||
PredictiveInsightSummarySerializer, DashboardDataSerializer
|
||||
)
|
||||
|
||||
|
||||
class StandardResultsSetPagination(PageNumberPagination):
|
||||
"""Standard pagination for analytics endpoints"""
|
||||
page_size = 20
|
||||
page_size_query_param = 'page_size'
|
||||
max_page_size = 100
|
||||
|
||||
|
||||
class KPIMetricFilter(filters.FilterSet):
|
||||
"""Filter for KPI metrics"""
|
||||
|
||||
metric_type = filters.ChoiceFilter(choices=KPIMetric.METRIC_TYPES)
|
||||
is_active = filters.BooleanFilter()
|
||||
is_system_metric = filters.BooleanFilter()
|
||||
created_after = filters.DateTimeFilter(field_name='created_at', lookup_expr='gte')
|
||||
created_before = filters.DateTimeFilter(field_name='created_at', lookup_expr='lte')
|
||||
|
||||
class Meta:
|
||||
model = KPIMetric
|
||||
fields = ['metric_type', 'is_active', 'is_system_metric', 'created_after', 'created_before']
|
||||
|
||||
|
||||
class KPIMetricViewSet(viewsets.ModelViewSet):
|
||||
"""ViewSet for KPI metrics management"""
|
||||
|
||||
queryset = KPIMetric.objects.all()
|
||||
serializer_class = KPIMetricSerializer
|
||||
pagination_class = StandardResultsSetPagination
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
filterset_class = KPIMetricFilter
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
"""Filter queryset based on user permissions"""
|
||||
queryset = super().get_queryset()
|
||||
|
||||
# Add any additional filtering based on user permissions
|
||||
if not self.request.user.is_staff:
|
||||
# Non-staff users can only see active metrics
|
||||
queryset = queryset.filter(is_active=True)
|
||||
|
||||
return queryset.order_by('-created_at')
|
||||
|
||||
@action(detail=True, methods=['get'])
|
||||
def measurements(self, request, pk=None):
|
||||
"""Get measurements for a specific KPI metric"""
|
||||
metric = self.get_object()
|
||||
measurements = metric.measurements.all().order_by('-calculated_at')
|
||||
|
||||
# Apply date filtering if provided
|
||||
start_date = request.query_params.get('start_date')
|
||||
end_date = request.query_params.get('end_date')
|
||||
|
||||
if start_date:
|
||||
measurements = measurements.filter(measurement_period_start__gte=start_date)
|
||||
if end_date:
|
||||
measurements = measurements.filter(measurement_period_end__lte=end_date)
|
||||
|
||||
# Paginate results
|
||||
paginator = StandardResultsSetPagination()
|
||||
page = paginator.paginate_queryset(measurements, request)
|
||||
|
||||
if page is not None:
|
||||
serializer = KPIMeasurementSerializer(page, many=True)
|
||||
return paginator.get_paginated_response(serializer.data)
|
||||
|
||||
serializer = KPIMeasurementSerializer(measurements, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def summary(self, request):
|
||||
"""Get summary of all KPI metrics"""
|
||||
metrics = self.get_queryset()
|
||||
|
||||
# Get latest measurements for each metric
|
||||
summaries = []
|
||||
for metric in metrics:
|
||||
latest_measurement = metric.measurements.first()
|
||||
if latest_measurement:
|
||||
# Calculate trend (simplified - compare with previous measurement)
|
||||
previous_measurement = metric.measurements.all()[1:2].first()
|
||||
trend = 'stable'
|
||||
trend_percentage = Decimal('0.00')
|
||||
|
||||
if previous_measurement:
|
||||
if latest_measurement.value > previous_measurement.value:
|
||||
trend = 'up'
|
||||
trend_percentage = ((latest_measurement.value - previous_measurement.value) / previous_measurement.value) * 100
|
||||
elif latest_measurement.value < previous_measurement.value:
|
||||
trend = 'down'
|
||||
trend_percentage = ((previous_measurement.value - latest_measurement.value) / previous_measurement.value) * 100
|
||||
|
||||
summary_data = {
|
||||
'metric_type': metric.metric_type,
|
||||
'metric_name': metric.name,
|
||||
'current_value': latest_measurement.value,
|
||||
'unit': latest_measurement.unit,
|
||||
'trend': trend,
|
||||
'trend_percentage': trend_percentage,
|
||||
'period_start': latest_measurement.measurement_period_start,
|
||||
'period_end': latest_measurement.measurement_period_end,
|
||||
'incident_count': latest_measurement.incident_count,
|
||||
'target_value': None, # Could be added to metric model
|
||||
'target_met': True # Could be calculated based on target
|
||||
}
|
||||
summaries.append(summary_data)
|
||||
|
||||
serializer = KPISummarySerializer(summaries, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
class KPIMeasurementViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""ViewSet for KPI measurements (read-only)"""
|
||||
|
||||
queryset = KPIMeasurement.objects.all()
|
||||
serializer_class = KPIMeasurementSerializer
|
||||
pagination_class = StandardResultsSetPagination
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
"""Filter queryset based on query parameters"""
|
||||
queryset = super().get_queryset()
|
||||
|
||||
# Filter by metric
|
||||
metric_id = self.request.query_params.get('metric_id')
|
||||
if metric_id:
|
||||
queryset = queryset.filter(metric_id=metric_id)
|
||||
|
||||
# Filter by date range
|
||||
start_date = self.request.query_params.get('start_date')
|
||||
end_date = self.request.query_params.get('end_date')
|
||||
|
||||
if start_date:
|
||||
queryset = queryset.filter(measurement_period_start__gte=start_date)
|
||||
if end_date:
|
||||
queryset = queryset.filter(measurement_period_end__lte=end_date)
|
||||
|
||||
return queryset.order_by('-calculated_at')
|
||||
|
||||
|
||||
class IncidentRecurrenceAnalysisViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""ViewSet for incident recurrence analysis"""
|
||||
|
||||
queryset = IncidentRecurrenceAnalysis.objects.all()
|
||||
serializer_class = IncidentRecurrenceAnalysisSerializer
|
||||
pagination_class = StandardResultsSetPagination
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
"""Filter queryset based on query parameters"""
|
||||
queryset = super().get_queryset()
|
||||
|
||||
# Filter by recurrence type
|
||||
recurrence_type = self.request.query_params.get('recurrence_type')
|
||||
if recurrence_type:
|
||||
queryset = queryset.filter(recurrence_type=recurrence_type)
|
||||
|
||||
# Filter by confidence score
|
||||
min_confidence = self.request.query_params.get('min_confidence')
|
||||
if min_confidence:
|
||||
queryset = queryset.filter(confidence_score__gte=float(min_confidence))
|
||||
|
||||
# Filter by resolution status
|
||||
is_resolved = self.request.query_params.get('is_resolved')
|
||||
if is_resolved is not None:
|
||||
queryset = queryset.filter(is_resolved=is_resolved.lower() == 'true')
|
||||
|
||||
return queryset.order_by('-confidence_score', '-created_at')
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def unresolved(self, request):
|
||||
"""Get unresolved recurrence analyses"""
|
||||
queryset = self.get_queryset().filter(is_resolved=False)
|
||||
|
||||
paginator = StandardResultsSetPagination()
|
||||
page = paginator.paginate_queryset(queryset, request)
|
||||
|
||||
if page is not None:
|
||||
serializer = self.get_serializer(page, many=True)
|
||||
return paginator.get_paginated_response(serializer.data)
|
||||
|
||||
serializer = self.get_serializer(queryset, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
class PredictiveModelViewSet(viewsets.ModelViewSet):
|
||||
"""ViewSet for predictive models management"""
|
||||
|
||||
queryset = PredictiveModel.objects.all()
|
||||
serializer_class = PredictiveModelSerializer
|
||||
pagination_class = StandardResultsSetPagination
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
"""Filter queryset based on user permissions"""
|
||||
queryset = super().get_queryset()
|
||||
|
||||
# Filter by model type
|
||||
model_type = self.request.query_params.get('model_type')
|
||||
if model_type:
|
||||
queryset = queryset.filter(model_type=model_type)
|
||||
|
||||
# Filter by status
|
||||
status_filter = self.request.query_params.get('status')
|
||||
if status_filter:
|
||||
queryset = queryset.filter(status=status_filter)
|
||||
|
||||
return queryset.order_by('-created_at')
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def train(self, request, pk=None):
|
||||
"""Trigger model training"""
|
||||
model = self.get_object()
|
||||
|
||||
# Update model status to training
|
||||
model.status = 'TRAINING'
|
||||
model.save()
|
||||
|
||||
# Here you would typically trigger the actual training process
|
||||
# For now, we'll just return a success response
|
||||
|
||||
return Response({
|
||||
'message': f'Training started for model {model.name}',
|
||||
'model_id': str(model.id),
|
||||
'status': model.status
|
||||
}, status=status.HTTP_202_ACCEPTED)
|
||||
|
||||
@action(detail=True, methods=['get'])
|
||||
def performance(self, request, pk=None):
|
||||
"""Get model performance metrics"""
|
||||
model = self.get_object()
|
||||
|
||||
performance_data = {
|
||||
'accuracy': model.accuracy_score,
|
||||
'precision': model.precision_score,
|
||||
'recall': model.recall_score,
|
||||
'f1_score': model.f1_score,
|
||||
'training_samples': model.training_samples_count,
|
||||
'last_trained': model.last_trained_at,
|
||||
'training_duration': model.training_duration_seconds,
|
||||
'insight_count': model.insights.count(),
|
||||
'anomaly_detection_count': model.anomaly_detections.count()
|
||||
}
|
||||
|
||||
return Response(performance_data)
|
||||
|
||||
|
||||
class AnomalyDetectionViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""ViewSet for anomaly detection results"""
|
||||
|
||||
queryset = AnomalyDetection.objects.all()
|
||||
serializer_class = AnomalyDetectionSerializer
|
||||
pagination_class = StandardResultsSetPagination
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
"""Filter queryset based on query parameters"""
|
||||
queryset = super().get_queryset()
|
||||
|
||||
# Filter by anomaly type
|
||||
anomaly_type = self.request.query_params.get('anomaly_type')
|
||||
if anomaly_type:
|
||||
queryset = queryset.filter(anomaly_type=anomaly_type)
|
||||
|
||||
# Filter by severity
|
||||
severity = self.request.query_params.get('severity')
|
||||
if severity:
|
||||
queryset = queryset.filter(severity=severity)
|
||||
|
||||
# Filter by status
|
||||
status_filter = self.request.query_params.get('status')
|
||||
if status_filter:
|
||||
queryset = queryset.filter(status=status_filter)
|
||||
|
||||
# Filter by date range
|
||||
start_date = self.request.query_params.get('start_date')
|
||||
end_date = self.request.query_params.get('end_date')
|
||||
|
||||
if start_date:
|
||||
queryset = queryset.filter(detected_at__gte=start_date)
|
||||
if end_date:
|
||||
queryset = queryset.filter(detected_at__lte=end_date)
|
||||
|
||||
return queryset.order_by('-detected_at')
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def summary(self, request):
|
||||
"""Get anomaly detection summary"""
|
||||
queryset = self.get_queryset()
|
||||
|
||||
# Calculate summary statistics
|
||||
total_anomalies = queryset.count()
|
||||
critical_anomalies = queryset.filter(severity='CRITICAL').count()
|
||||
high_anomalies = queryset.filter(severity='HIGH').count()
|
||||
medium_anomalies = queryset.filter(severity='MEDIUM').count()
|
||||
low_anomalies = queryset.filter(severity='LOW').count()
|
||||
unresolved_anomalies = queryset.filter(status__in=['DETECTED', 'INVESTIGATING']).count()
|
||||
|
||||
# Calculate false positive rate (simplified)
|
||||
false_positives = queryset.filter(status='FALSE_POSITIVE').count()
|
||||
false_positive_rate = (false_positives / total_anomalies * 100) if total_anomalies > 0 else 0
|
||||
|
||||
# Calculate average resolution time
|
||||
resolved_anomalies = queryset.filter(status='RESOLVED', resolved_at__isnull=False)
|
||||
if resolved_anomalies.exists():
|
||||
avg_resolution_time = resolved_anomalies.aggregate(
|
||||
avg_time=Avg('resolved_at' - 'detected_at')
|
||||
)['avg_time']
|
||||
else:
|
||||
avg_resolution_time = None
|
||||
|
||||
summary_data = {
|
||||
'total_anomalies': total_anomalies,
|
||||
'critical_anomalies': critical_anomalies,
|
||||
'high_anomalies': high_anomalies,
|
||||
'medium_anomalies': medium_anomalies,
|
||||
'low_anomalies': low_anomalies,
|
||||
'unresolved_anomalies': unresolved_anomalies,
|
||||
'false_positive_rate': Decimal(str(false_positive_rate)),
|
||||
'average_resolution_time': avg_resolution_time
|
||||
}
|
||||
|
||||
serializer = AnomalySummarySerializer(summary_data)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def acknowledge(self, request, pk=None):
|
||||
"""Acknowledge an anomaly detection"""
|
||||
anomaly = self.get_object()
|
||||
|
||||
if anomaly.status == 'DETECTED':
|
||||
anomaly.status = 'INVESTIGATING'
|
||||
anomaly.save()
|
||||
|
||||
return Response({
|
||||
'message': 'Anomaly acknowledged and moved to investigating status',
|
||||
'anomaly_id': str(anomaly.id),
|
||||
'status': anomaly.status
|
||||
})
|
||||
|
||||
return Response({
|
||||
'error': 'Anomaly is not in DETECTED status'
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def resolve(self, request, pk=None):
|
||||
"""Resolve an anomaly detection"""
|
||||
anomaly = self.get_object()
|
||||
|
||||
if anomaly.status in ['DETECTED', 'INVESTIGATING', 'CONFIRMED']:
|
||||
anomaly.status = 'RESOLVED'
|
||||
anomaly.resolved_at = timezone.now()
|
||||
anomaly.resolved_by = request.user
|
||||
anomaly.save()
|
||||
|
||||
return Response({
|
||||
'message': 'Anomaly resolved',
|
||||
'anomaly_id': str(anomaly.id),
|
||||
'status': anomaly.status,
|
||||
'resolved_at': anomaly.resolved_at
|
||||
})
|
||||
|
||||
return Response({
|
||||
'error': 'Anomaly cannot be resolved in current status'
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class CostImpactAnalysisViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""ViewSet for cost impact analysis"""
|
||||
|
||||
queryset = CostImpactAnalysis.objects.all()
|
||||
serializer_class = CostImpactAnalysisSerializer
|
||||
pagination_class = StandardResultsSetPagination
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
"""Filter queryset based on query parameters"""
|
||||
queryset = super().get_queryset()
|
||||
|
||||
# Filter by cost type
|
||||
cost_type = self.request.query_params.get('cost_type')
|
||||
if cost_type:
|
||||
queryset = queryset.filter(cost_type=cost_type)
|
||||
|
||||
# Filter by validation status
|
||||
is_validated = self.request.query_params.get('is_validated')
|
||||
if is_validated is not None:
|
||||
queryset = queryset.filter(is_validated=is_validated.lower() == 'true')
|
||||
|
||||
# Filter by date range
|
||||
start_date = self.request.query_params.get('start_date')
|
||||
end_date = self.request.query_params.get('end_date')
|
||||
|
||||
if start_date:
|
||||
queryset = queryset.filter(created_at__gte=start_date)
|
||||
if end_date:
|
||||
queryset = queryset.filter(created_at__lte=end_date)
|
||||
|
||||
return queryset.order_by('-created_at')
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def summary(self, request):
|
||||
"""Get cost impact summary"""
|
||||
queryset = self.get_queryset()
|
||||
|
||||
# Calculate summary statistics
|
||||
total_cost = queryset.aggregate(total=Sum('cost_amount'))['total'] or Decimal('0')
|
||||
downtime_cost = queryset.filter(cost_type='DOWNTIME').aggregate(total=Sum('cost_amount'))['total'] or Decimal('0')
|
||||
lost_revenue = queryset.filter(cost_type='LOST_REVENUE').aggregate(total=Sum('cost_amount'))['total'] or Decimal('0')
|
||||
penalty_cost = queryset.filter(cost_type='PENALTY').aggregate(total=Sum('cost_amount'))['total'] or Decimal('0')
|
||||
resource_cost = queryset.filter(cost_type='RESOURCE_COST').aggregate(total=Sum('cost_amount'))['total'] or Decimal('0')
|
||||
|
||||
total_downtime_hours = queryset.aggregate(total=Sum('downtime_hours'))['total'] or Decimal('0')
|
||||
total_affected_users = queryset.aggregate(total=Sum('affected_users'))['total'] or 0
|
||||
|
||||
# Calculate derived metrics
|
||||
cost_per_hour = (total_cost / total_downtime_hours) if total_downtime_hours > 0 else Decimal('0')
|
||||
cost_per_user = (total_cost / total_affected_users) if total_affected_users > 0 else Decimal('0')
|
||||
|
||||
summary_data = {
|
||||
'total_cost': total_cost,
|
||||
'currency': 'USD',
|
||||
'downtime_cost': downtime_cost,
|
||||
'lost_revenue': lost_revenue,
|
||||
'penalty_cost': penalty_cost,
|
||||
'resource_cost': resource_cost,
|
||||
'total_downtime_hours': total_downtime_hours,
|
||||
'total_affected_users': total_affected_users,
|
||||
'cost_per_hour': cost_per_hour,
|
||||
'cost_per_user': cost_per_user
|
||||
}
|
||||
|
||||
serializer = CostSummarySerializer(summary_data)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
class DashboardConfigurationViewSet(viewsets.ModelViewSet):
|
||||
"""ViewSet for dashboard configurations"""
|
||||
|
||||
queryset = DashboardConfiguration.objects.all()
|
||||
serializer_class = DashboardConfigurationSerializer
|
||||
pagination_class = StandardResultsSetPagination
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
"""Filter queryset based on user permissions"""
|
||||
queryset = super().get_queryset()
|
||||
|
||||
# Filter by dashboard type
|
||||
dashboard_type = self.request.query_params.get('dashboard_type')
|
||||
if dashboard_type:
|
||||
queryset = queryset.filter(dashboard_type=dashboard_type)
|
||||
|
||||
# Filter by active status
|
||||
is_active = self.request.query_params.get('is_active')
|
||||
if is_active is not None:
|
||||
queryset = queryset.filter(is_active=is_active.lower() == 'true')
|
||||
|
||||
# Filter by public dashboards or user's accessible dashboards
|
||||
if not self.request.user.is_staff:
|
||||
queryset = queryset.filter(
|
||||
Q(is_public=True) | Q(allowed_users=self.request.user)
|
||||
)
|
||||
|
||||
return queryset.order_by('name')
|
||||
|
||||
@action(detail=True, methods=['get'])
|
||||
def data(self, request, pk=None):
|
||||
"""Get dashboard data"""
|
||||
dashboard = self.get_object()
|
||||
|
||||
# Check if user has access to this dashboard
|
||||
if not dashboard.is_public and self.request.user not in dashboard.allowed_users.all():
|
||||
return Response({
|
||||
'error': 'Access denied to this dashboard'
|
||||
}, status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
# Get KPI summary
|
||||
kpi_metrics = KPIMetric.objects.filter(is_active=True)
|
||||
kpi_summaries = []
|
||||
for metric in kpi_metrics:
|
||||
latest_measurement = metric.measurements.first()
|
||||
if latest_measurement:
|
||||
kpi_summaries.append({
|
||||
'metric_type': metric.metric_type,
|
||||
'metric_name': metric.name,
|
||||
'current_value': latest_measurement.value,
|
||||
'unit': latest_measurement.unit,
|
||||
'trend': 'stable', # Simplified
|
||||
'trend_percentage': Decimal('0.00'),
|
||||
'period_start': latest_measurement.measurement_period_start,
|
||||
'period_end': latest_measurement.measurement_period_end,
|
||||
'incident_count': latest_measurement.incident_count,
|
||||
'target_value': None,
|
||||
'target_met': True
|
||||
})
|
||||
|
||||
# Get anomaly summary
|
||||
anomalies = AnomalyDetection.objects.all()
|
||||
anomaly_summary = {
|
||||
'total_anomalies': anomalies.count(),
|
||||
'critical_anomalies': anomalies.filter(severity='CRITICAL').count(),
|
||||
'high_anomalies': anomalies.filter(severity='HIGH').count(),
|
||||
'medium_anomalies': anomalies.filter(severity='MEDIUM').count(),
|
||||
'low_anomalies': anomalies.filter(severity='LOW').count(),
|
||||
'unresolved_anomalies': anomalies.filter(status__in=['DETECTED', 'INVESTIGATING']).count(),
|
||||
'false_positive_rate': Decimal('0.00'), # Simplified
|
||||
'average_resolution_time': None
|
||||
}
|
||||
|
||||
# Get cost summary
|
||||
cost_analyses = CostImpactAnalysis.objects.all()
|
||||
cost_summary = {
|
||||
'total_cost': cost_analyses.aggregate(total=Sum('cost_amount'))['total'] or Decimal('0'),
|
||||
'currency': 'USD',
|
||||
'downtime_cost': cost_analyses.filter(cost_type='DOWNTIME').aggregate(total=Sum('cost_amount'))['total'] or Decimal('0'),
|
||||
'lost_revenue': cost_analyses.filter(cost_type='LOST_REVENUE').aggregate(total=Sum('cost_amount'))['total'] or Decimal('0'),
|
||||
'penalty_cost': cost_analyses.filter(cost_type='PENALTY').aggregate(total=Sum('cost_amount'))['total'] or Decimal('0'),
|
||||
'resource_cost': cost_analyses.filter(cost_type='RESOURCE_COST').aggregate(total=Sum('cost_amount'))['total'] or Decimal('0'),
|
||||
'total_downtime_hours': cost_analyses.aggregate(total=Sum('downtime_hours'))['total'] or Decimal('0'),
|
||||
'total_affected_users': cost_analyses.aggregate(total=Sum('affected_users'))['total'] or 0,
|
||||
'cost_per_hour': Decimal('0.00'),
|
||||
'cost_per_user': Decimal('0.00')
|
||||
}
|
||||
|
||||
# Get insight summary
|
||||
insights = PredictiveInsight.objects.all()
|
||||
insight_summary = {
|
||||
'total_insights': insights.count(),
|
||||
'high_confidence_insights': insights.filter(confidence_level='HIGH').count(),
|
||||
'medium_confidence_insights': insights.filter(confidence_level='MEDIUM').count(),
|
||||
'low_confidence_insights': insights.filter(confidence_level='LOW').count(),
|
||||
'acknowledged_insights': insights.filter(is_acknowledged=True).count(),
|
||||
'validated_insights': insights.filter(is_validated=True).count(),
|
||||
'expired_insights': insights.filter(expires_at__lt=timezone.now()).count(),
|
||||
'average_accuracy': Decimal('0.00'),
|
||||
'active_models': PredictiveModel.objects.filter(status='ACTIVE').count()
|
||||
}
|
||||
|
||||
# Get recent data
|
||||
recent_anomalies = anomalies.order_by('-detected_at')[:5]
|
||||
recent_insights = insights.order_by('-generated_at')[:5]
|
||||
heatmap_data = HeatmapData.objects.all()[:3]
|
||||
|
||||
dashboard_data = {
|
||||
'kpi_summary': kpi_summaries,
|
||||
'anomaly_summary': anomaly_summary,
|
||||
'cost_summary': cost_summary,
|
||||
'insight_summary': insight_summary,
|
||||
'recent_anomalies': AnomalyDetectionSerializer(recent_anomalies, many=True).data,
|
||||
'recent_insights': PredictiveInsightSerializer(recent_insights, many=True).data,
|
||||
'heatmap_data': HeatmapDataSerializer(heatmap_data, many=True).data,
|
||||
'last_updated': timezone.now()
|
||||
}
|
||||
|
||||
serializer = DashboardDataSerializer(dashboard_data)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
class HeatmapDataViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""ViewSet for heatmap data"""
|
||||
|
||||
queryset = HeatmapData.objects.all()
|
||||
serializer_class = HeatmapDataSerializer
|
||||
pagination_class = StandardResultsSetPagination
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
"""Filter queryset based on query parameters"""
|
||||
queryset = super().get_queryset()
|
||||
|
||||
# Filter by heatmap type
|
||||
heatmap_type = self.request.query_params.get('heatmap_type')
|
||||
if heatmap_type:
|
||||
queryset = queryset.filter(heatmap_type=heatmap_type)
|
||||
|
||||
# Filter by time granularity
|
||||
time_granularity = self.request.query_params.get('time_granularity')
|
||||
if time_granularity:
|
||||
queryset = queryset.filter(time_granularity=time_granularity)
|
||||
|
||||
return queryset.order_by('-created_at')
|
||||
|
||||
|
||||
class PredictiveInsightViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""ViewSet for predictive insights"""
|
||||
|
||||
queryset = PredictiveInsight.objects.all()
|
||||
serializer_class = PredictiveInsightSerializer
|
||||
pagination_class = StandardResultsSetPagination
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
"""Filter queryset based on query parameters"""
|
||||
queryset = super().get_queryset()
|
||||
|
||||
# Filter by insight type
|
||||
insight_type = self.request.query_params.get('insight_type')
|
||||
if insight_type:
|
||||
queryset = queryset.filter(insight_type=insight_type)
|
||||
|
||||
# Filter by confidence level
|
||||
confidence_level = self.request.query_params.get('confidence_level')
|
||||
if confidence_level:
|
||||
queryset = queryset.filter(confidence_level=confidence_level)
|
||||
|
||||
# Filter by acknowledgment status
|
||||
is_acknowledged = self.request.query_params.get('is_acknowledged')
|
||||
if is_acknowledged is not None:
|
||||
queryset = queryset.filter(is_acknowledged=is_acknowledged.lower() == 'true')
|
||||
|
||||
# Filter by validation status
|
||||
is_validated = self.request.query_params.get('is_validated')
|
||||
if is_validated is not None:
|
||||
queryset = queryset.filter(is_validated=is_validated.lower() == 'true')
|
||||
|
||||
# Filter by expiry
|
||||
include_expired = self.request.query_params.get('include_expired', 'false')
|
||||
if include_expired.lower() != 'true':
|
||||
queryset = queryset.filter(expires_at__gt=timezone.now())
|
||||
|
||||
return queryset.order_by('-generated_at')
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def acknowledge(self, request, pk=None):
|
||||
"""Acknowledge a predictive insight"""
|
||||
insight = self.get_object()
|
||||
|
||||
if not insight.is_acknowledged:
|
||||
insight.is_acknowledged = True
|
||||
insight.acknowledged_by = request.user
|
||||
insight.acknowledged_at = timezone.now()
|
||||
insight.save()
|
||||
|
||||
return Response({
|
||||
'message': 'Insight acknowledged',
|
||||
'insight_id': str(insight.id),
|
||||
'acknowledged_at': insight.acknowledged_at
|
||||
})
|
||||
|
||||
return Response({
|
||||
'error': 'Insight is already acknowledged'
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def summary(self, request):
|
||||
"""Get predictive insight summary"""
|
||||
queryset = self.get_queryset()
|
||||
|
||||
# Calculate summary statistics
|
||||
total_insights = queryset.count()
|
||||
high_confidence_insights = queryset.filter(confidence_level='HIGH').count()
|
||||
medium_confidence_insights = queryset.filter(confidence_level='MEDIUM').count()
|
||||
low_confidence_insights = queryset.filter(confidence_level='LOW').count()
|
||||
acknowledged_insights = queryset.filter(is_acknowledged=True).count()
|
||||
validated_insights = queryset.filter(is_validated=True).count()
|
||||
expired_insights = queryset.filter(expires_at__lt=timezone.now()).count()
|
||||
|
||||
# Calculate average accuracy
|
||||
validated_insights_with_accuracy = queryset.filter(
|
||||
is_validated=True,
|
||||
validation_accuracy__isnull=False
|
||||
)
|
||||
if validated_insights_with_accuracy.exists():
|
||||
avg_accuracy = validated_insights_with_accuracy.aggregate(
|
||||
avg=Avg('validation_accuracy')
|
||||
)['avg']
|
||||
else:
|
||||
avg_accuracy = None
|
||||
|
||||
active_models = PredictiveModel.objects.filter(status='ACTIVE').count()
|
||||
|
||||
summary_data = {
|
||||
'total_insights': total_insights,
|
||||
'high_confidence_insights': high_confidence_insights,
|
||||
'medium_confidence_insights': medium_confidence_insights,
|
||||
'low_confidence_insights': low_confidence_insights,
|
||||
'acknowledged_insights': acknowledged_insights,
|
||||
'validated_insights': validated_insights,
|
||||
'expired_insights': expired_insights,
|
||||
'average_accuracy': avg_accuracy,
|
||||
'active_models': active_models
|
||||
}
|
||||
|
||||
serializer = PredictiveInsightSummarySerializer(summary_data)
|
||||
return Response(serializer.data)
|
||||
Reference in New Issue
Block a user