from rest_framework import viewsets, permissions, status from rest_framework.decorators import action from rest_framework.response import Response from django_filters.rest_framework import DjangoFilterBackend from rest_framework import filters from django.db.models import Q, Count, Avg, F from django.utils import timezone from datetime import timedelta from ..models import LabTest, LabOrder, Specimen, LabResult, QualityControl, ReferenceRange from .serializers import ( LabTestSerializer, LabOrderSerializer, SpecimenSerializer, LabResultSerializer, QualityControlSerializer, ReferenceRangeSerializer, LabStatsSerializer, SpecimenCollectionSerializer, ResultEntrySerializer, ResultVerificationSerializer ) from core.utils import AuditLogger class BaseViewSet(viewsets.ModelViewSet): """Base ViewSet with common functionality""" permission_classes = [permissions.IsAuthenticated] filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] def get_queryset(self): # Filter by tenant if user has one if hasattr(self.request.user, 'tenant') and self.request.user.tenant: return self.queryset.filter(tenant=self.request.user.tenant) return self.queryset def perform_create(self, serializer): if hasattr(self.request.user, 'tenant'): serializer.save(tenant=self.request.user.tenant) else: serializer.save() class LabTestViewSet(BaseViewSet): """ViewSet for LabTest model""" queryset = LabTest.objects.all() serializer_class = LabTestSerializer filterset_fields = [ 'category', 'specimen_type', 'requires_fasting', 'is_active' ] search_fields = [ 'code', 'name', 'description', 'cpt_code', 'loinc_code' ] ordering_fields = ['name', 'code', 'category', 'turnaround_time_hours'] ordering = ['name'] @action(detail=False, methods=['get']) def by_category(self, request): """Get tests grouped by category""" category = request.query_params.get('category') if category: queryset = self.get_queryset().filter(category=category, is_active=True) else: queryset = self.get_queryset().filter(is_active=True) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @action(detail=False, methods=['get']) def search_by_code(self, request): """Search tests by code (CPT or LOINC)""" code = request.query_params.get('code') if not code: return Response({'error': 'Code parameter required'}, status=status.HTTP_400_BAD_REQUEST) queryset = self.get_queryset().filter( Q(code__icontains=code) | Q(cpt_code__icontains=code) | Q(loinc_code__icontains=code), is_active=True ) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @action(detail=True, methods=['get']) def reference_ranges(self, request, pk=None): """Get reference ranges for a test""" test = self.get_object() ranges = ReferenceRange.objects.filter(lab_test=test, is_active=True) serializer = ReferenceRangeSerializer(ranges, many=True) return Response(serializer.data) class LabOrderViewSet(BaseViewSet): """ViewSet for LabOrder model""" queryset = LabOrder.objects.all() serializer_class = LabOrderSerializer filterset_fields = [ 'status', 'priority', 'patient', 'ordering_provider', 'fasting_required' ] search_fields = [ 'order_number', 'patient__first_name', 'patient__last_name', 'patient__mrn', 'ordering_provider__first_name', 'ordering_provider__last_name' ] ordering_fields = ['order_date', 'collection_scheduled_date', 'priority'] ordering = ['-order_date'] @action(detail=False, methods=['get']) def pending(self, request): """Get pending orders""" queryset = self.get_queryset().filter(status='PENDING') serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @action(detail=False, methods=['get']) def scheduled_today(self, request): """Get orders scheduled for collection today""" today = timezone.now().date() queryset = self.get_queryset().filter( collection_scheduled_date__date=today, status__in=['PENDING', 'SCHEDULED'] ) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @action(detail=True, methods=['post']) def schedule_collection(self, request, pk=None): """Schedule collection for an order""" order = self.get_object() collection_date = request.data.get('collection_date') special_instructions = request.data.get('special_instructions', '') if not collection_date: return Response( {'error': 'Collection date is required'}, status=status.HTTP_400_BAD_REQUEST ) order.collection_scheduled_date = collection_date order.status = 'SCHEDULED' order.special_instructions = special_instructions order.save() # Log the action AuditLogger.log_action( user=request.user, action='ORDER_SCHEDULED', model='LabOrder', object_id=str(order.order_id), details={ 'order_number': order.order_number, 'collection_date': collection_date } ) return Response({'message': 'Collection scheduled successfully'}) @action(detail=True, methods=['post']) def cancel(self, request, pk=None): """Cancel an order""" order = self.get_object() reason = request.data.get('reason', '') order.status = 'CANCELLED' order.notes = f"Cancelled: {reason}" order.save() # Log the action AuditLogger.log_action( user=request.user, action='ORDER_CANCELLED', model='LabOrder', object_id=str(order.order_id), details={ 'order_number': order.order_number, 'reason': reason } ) return Response({'message': 'Order cancelled successfully'}) class SpecimenViewSet(BaseViewSet): """ViewSet for Specimen model""" queryset = Specimen.objects.all() serializer_class = SpecimenSerializer filterset_fields = [ 'status', 'specimen_type', 'lab_order', 'collected_by', 'received_by' ] search_fields = [ 'specimen_number', 'lab_order__order_number', 'lab_order__patient__first_name', 'lab_order__patient__last_name', 'lab_order__patient__mrn' ] ordering_fields = ['collection_date', 'received_date', 'processing_started_date'] ordering = ['-collection_date'] @action(detail=False, methods=['post']) def collect(self, request): """Collect a specimen""" serializer = SpecimenCollectionSerializer(data=request.data) if serializer.is_valid(): order = LabOrder.objects.get(id=serializer.validated_data['order_id']) # Create specimen specimen = Specimen.objects.create( lab_order=order, specimen_type=serializer.validated_data['specimen_type'], collection_date=timezone.now().date(), collection_time=timezone.now().time(), collected_by=request.user, collection_site=serializer.validated_data['collection_site'], collection_method=serializer.validated_data['collection_method'], container_type=serializer.validated_data['container_type'], volume_collected=serializer.validated_data['volume_collected'], status='COLLECTED', comments=serializer.validated_data.get('comments', ''), tenant=getattr(request.user, 'tenant', None) ) # Update order status order.status = 'COLLECTED' order.collection_date = timezone.now() order.collected_by = request.user order.save() # Log the action AuditLogger.log_action( user=request.user, action='SPECIMEN_COLLECTED', model='Specimen', object_id=str(specimen.specimen_id), details={ 'specimen_number': specimen.specimen_number, 'order_number': order.order_number } ) return Response({ 'message': 'Specimen collected successfully', 'specimen': SpecimenSerializer(specimen).data }) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @action(detail=True, methods=['post']) def receive(self, request, pk=None): """Receive a specimen in the lab""" specimen = self.get_object() specimen.status = 'RECEIVED' specimen.received_date = timezone.now() specimen.received_by = request.user specimen.save() # Log the action AuditLogger.log_action( user=request.user, action='SPECIMEN_RECEIVED', model='Specimen', object_id=str(specimen.specimen_id), details={'specimen_number': specimen.specimen_number} ) return Response({'message': 'Specimen received successfully'}) @action(detail=True, methods=['post']) def start_processing(self, request, pk=None): """Start processing a specimen""" specimen = self.get_object() specimen.status = 'PROCESSING' specimen.processing_started_date = timezone.now() specimen.processed_by = request.user specimen.save() # Log the action AuditLogger.log_action( user=request.user, action='SPECIMEN_PROCESSING_STARTED', model='Specimen', object_id=str(specimen.specimen_id), details={'specimen_number': specimen.specimen_number} ) return Response({'message': 'Processing started successfully'}) @action(detail=True, methods=['post']) def reject(self, request, pk=None): """Reject a specimen""" specimen = self.get_object() reason = request.data.get('reason', '') specimen.status = 'REJECTED' specimen.rejection_reason = reason specimen.rejected_by = request.user specimen.rejected_date = timezone.now() specimen.save() # Log the action AuditLogger.log_action( user=request.user, action='SPECIMEN_REJECTED', model='Specimen', object_id=str(specimen.specimen_id), details={ 'specimen_number': specimen.specimen_number, 'reason': reason } ) return Response({'message': 'Specimen rejected successfully'}) class LabResultViewSet(BaseViewSet): """ViewSet for LabResult model""" queryset = LabResult.objects.all() serializer_class = LabResultSerializer filterset_fields = [ 'status', 'result_status', 'lab_test', 'lab_order', 'critical_flag', 'abnormal_flag', 'performed_by', 'verified_by' ] search_fields = [ 'lab_order__order_number', 'lab_order__patient__first_name', 'lab_order__patient__last_name', 'lab_test__name', 'specimen__specimen_number' ] ordering_fields = ['performed_date', 'verified_date'] ordering = ['-performed_date'] @action(detail=False, methods=['post']) def enter_result(self, request): """Enter a lab result""" serializer = ResultEntrySerializer(data=request.data) if serializer.is_valid(): specimen = Specimen.objects.get(id=serializer.validated_data['specimen_id']) lab_test = LabTest.objects.get(id=serializer.validated_data['lab_test_id']) # Create result result = LabResult.objects.create( lab_order=specimen.lab_order, lab_test=lab_test, specimen=specimen, result_value=serializer.validated_data.get('result_value', ''), result_text=serializer.validated_data.get('result_text', ''), abnormal_flag=serializer.validated_data.get('abnormal_flag', ''), critical_flag=serializer.validated_data.get('critical_flag', False), status='PENDING_VERIFICATION', performed_date=timezone.now(), performed_by=request.user, instrument_id=serializer.validated_data.get('instrument_id', ''), method_used=serializer.validated_data.get('method_used', ''), comments=serializer.validated_data.get('comments', ''), tenant=getattr(request.user, 'tenant', None) ) # Log the action AuditLogger.log_action( user=request.user, action='RESULT_ENTERED', model='LabResult', object_id=str(result.result_id), details={ 'test_name': lab_test.name, 'specimen_number': specimen.specimen_number } ) return Response({ 'message': 'Result entered successfully', 'result': LabResultSerializer(result).data }) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @action(detail=False, methods=['get']) def pending_verification(self, request): """Get results pending verification""" queryset = self.get_queryset().filter(status='PENDING_VERIFICATION') serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @action(detail=False, methods=['get']) def critical_results(self, request): """Get critical results""" queryset = self.get_queryset().filter(critical_flag=True, status='VERIFIED') serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @action(detail=True, methods=['post']) def verify(self, request, pk=None): """Verify a lab result""" result = self.get_object() comments = request.data.get('verification_comments', '') result.status = 'VERIFIED' result.verified_date = timezone.now() result.verified_by = request.user if comments: result.comments = f"{result.comments}\nVerification: {comments}" result.save() # Log the action AuditLogger.log_action( user=request.user, action='RESULT_VERIFIED', model='LabResult', object_id=str(result.result_id), details={ 'test_name': result.lab_test.name, 'specimen_number': result.specimen.specimen_number } ) return Response({'message': 'Result verified successfully'}) class QualityControlViewSet(BaseViewSet): """ViewSet for QualityControl model""" queryset = QualityControl.objects.all() serializer_class = QualityControlSerializer filterset_fields = ['lab_test', 'control_type', 'status', 'performed_by'] search_fields = ['lab_test__name', 'control_lot', 'instrument_id'] ordering_fields = ['run_date'] ordering = ['-run_date'] @action(detail=False, methods=['get']) def failed_controls(self, request): """Get failed quality controls""" queryset = self.get_queryset().filter(status='FAILED') serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @action(detail=False, methods=['get']) def daily_summary(self, request): """Get daily QC summary""" date = request.query_params.get('date', timezone.now().date()) queryset = self.get_queryset().filter(run_date__date=date) total = queryset.count() passed = queryset.filter(status='PASSED').count() failed = queryset.filter(status='FAILED').count() return Response({ 'date': date, 'total_controls': total, 'passed': passed, 'failed': failed, 'pass_rate': (passed / total * 100) if total > 0 else 0 }) class ReferenceRangeViewSet(BaseViewSet): """ViewSet for ReferenceRange model""" queryset = ReferenceRange.objects.all() serializer_class = ReferenceRangeSerializer filterset_fields = ['lab_test', 'gender', 'is_active'] search_fields = ['lab_test__name', 'population'] ordering_fields = ['effective_date', 'age_min'] ordering = ['lab_test__name', 'age_min'] class LabStatsViewSet(viewsets.ViewSet): """ViewSet for laboratory statistics""" permission_classes = [permissions.IsAuthenticated] @action(detail=False, methods=['get']) def dashboard(self, request): """Get laboratory dashboard statistics""" tenant_filter = {} if hasattr(request.user, 'tenant') and request.user.tenant: tenant_filter['tenant'] = request.user.tenant today = timezone.now().date() # Order statistics orders = LabOrder.objects.filter(**tenant_filter) total_orders = orders.count() pending_orders = orders.filter(status='PENDING').count() completed_today = orders.filter( status='COMPLETED', collection_date__date=today ).count() # Specimen statistics specimens = Specimen.objects.filter(**tenant_filter) specimens_collected = specimens.filter( collection_date=today ).count() # Result statistics results = LabResult.objects.filter(**tenant_filter) results_pending = results.filter(status='PENDING_VERIFICATION').count() critical_results = results.filter( critical_flag=True, status='VERIFIED', verified_date__date=today ).count() # Turnaround time completed_results = results.filter( status='VERIFIED', verified_date__date=today ) turnaround_times = [] for result in completed_results: if result.lab_order.collection_date and result.verified_date: delta = result.verified_date - result.lab_order.collection_date turnaround_times.append(delta.total_seconds() / 3600) # hours turnaround_time_avg = sum(turnaround_times) / len(turnaround_times) if turnaround_times else 0 # QC pass rate qc_today = QualityControl.objects.filter( run_date__date=today, **tenant_filter ) qc_total = qc_today.count() qc_passed = qc_today.filter(status='PASSED').count() qc_pass_rate = (qc_passed / qc_total * 100) if qc_total > 0 else 100 # Order types breakdown order_types = orders.values('status').annotate( count=Count('id') ).order_by('-count') stats = { 'total_orders': total_orders, 'pending_orders': pending_orders, 'completed_today': completed_today, 'specimens_collected': specimens_collected, 'results_pending': results_pending, 'critical_results': critical_results, 'turnaround_time_avg': round(turnaround_time_avg, 2), 'qc_pass_rate': round(qc_pass_rate, 2), 'order_types': {item['status']: item['count'] for item in order_types}, 'status_breakdown': dict(order_types.values_list('status', 'count')) } serializer = LabStatsSerializer(stats) return Response(serializer.data)