2025-08-12 13:33:25 +03:00

534 lines
20 KiB
Python

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)