""" Simulator Console UI views - Server-rendered templates for viewing simulator logs """ from django.contrib import messages from django.contrib.auth.decorators import login_required from django.core.paginator import Paginator from django.db.models import Q, Count, Avg from django.shortcuts import get_object_or_404, render from django.views.decorators.http import require_http_methods from .models import HISRequestLog @login_required def simulator_logs_list(request): """ Simulator logs list view with filters and statistics. Features: - Server-side pagination - Filters (channel, status, date range, hospital) - Search (request ID, patient MRN, recipient) - Statistics dashboard - Channel breakdown """ # Base queryset with optimizations queryset = HISRequestLog.objects.all() # Apply filters channel_filter = request.GET.get('channel') if channel_filter: queryset = queryset.filter(channel=channel_filter) status_filter = request.GET.get('status') if status_filter: queryset = queryset.filter(status=status_filter) hospital_filter = request.GET.get('hospital') if hospital_filter: queryset = queryset.filter(hospital_code=hospital_filter) visit_type_filter = request.GET.get('visit_type') if visit_type_filter: queryset = queryset.filter(visit_type=visit_type_filter) # Search search_query = request.GET.get('search') if search_query: queryset = queryset.filter( Q(request_id__icontains=search_query) | Q(patient_id__icontains=search_query) | Q(journey_id__icontains=search_query) | Q(survey_id__icontains=search_query) | Q(recipient__icontains=search_query) | Q(subject__icontains=search_query) ) # Date range date_from = request.GET.get('date_from') if date_from: queryset = queryset.filter(timestamp__gte=date_from) date_to = request.GET.get('date_to') if date_to: queryset = queryset.filter(timestamp__lte=date_to) # Ordering order_by = request.GET.get('order_by', '-timestamp') queryset = queryset.order_by(order_by) # Pagination page_size = int(request.GET.get('page_size', 25)) paginator = Paginator(queryset, page_size) page_number = request.GET.get('page', 1) page_obj = paginator.get_page(page_number) # Get base queryset for statistics (without pagination) stats_queryset = HISRequestLog.objects.all() # Apply same filters to stats if channel_filter: stats_queryset = stats_queryset.filter(channel=channel_filter) if status_filter: stats_queryset = stats_queryset.filter(status=status_filter) if hospital_filter: stats_queryset = stats_queryset.filter(hospital_code=hospital_filter) if visit_type_filter: stats_queryset = stats_queryset.filter(visit_type=visit_type_filter) if search_query: stats_queryset = stats_queryset.filter( Q(request_id__icontains=search_query) | Q(patient_id__icontains=search_query) | Q(journey_id__icontains=search_query) | Q(survey_id__icontains=search_query) | Q(recipient__icontains=search_query) | Q(subject__icontains=search_query) ) if date_from: stats_queryset = stats_queryset.filter(timestamp__gte=date_from) if date_to: stats_queryset = stats_queryset.filter(timestamp__lte=date_to) # Statistics total_count = stats_queryset.count() success_count = stats_queryset.filter(status__in=['success', 'sent']).count() failed_count = stats_queryset.filter(status='failed').count() partial_count = stats_queryset.filter(status='partial').count() # Channel breakdown channel_stats = stats_queryset.values('channel').annotate( count=Count('request_id') ).order_by('-count') # Status breakdown status_stats = stats_queryset.values('status').annotate( count=Count('request_id') ).order_by('-count') # Hospital breakdown (for HIS events) hospital_stats = stats_queryset.values('hospital_code').annotate( count=Count('request_id') ).exclude(hospital_code__isnull=True).order_by('-count') # Visit type breakdown (for HIS events) visit_type_stats = stats_queryset.values('visit_type').annotate( count=Count('request_id') ).exclude(visit_type__isnull=True).order_by('-count') # Average processing time processing_stats = stats_queryset.filter( processing_time_ms__isnull=False ).aggregate( avg_time=Avg('processing_time_ms') ) avg_processing_time = processing_stats['avg_time'] or 0 # Survey invitations sent survey_sent_count = stats_queryset.filter( channel='his_event', status='success' ).count() # Detailed channel counts email_count = stats_queryset.filter(channel='email').count() sms_count = stats_queryset.filter(channel='sms').count() his_event_count = stats_queryset.filter(channel='his_event').count() stats = { 'total': total_count, 'success': success_count, 'failed': failed_count, 'partial': partial_count, 'success_rate': round((success_count / total_count * 100) if total_count > 0 else 0, 1), 'avg_processing_time': round(avg_processing_time, 2), 'survey_sent': survey_sent_count, 'channels': { 'email': email_count, 'sms': sms_count, 'his_event': his_event_count, } } context = { 'page_obj': page_obj, 'logs': page_obj.object_list, 'stats': stats, 'channel_stats': list(channel_stats), 'status_stats': list(status_stats), 'hospital_stats': list(hospital_stats), 'visit_type_stats': list(visit_type_stats), 'filters': request.GET, } return render(request, 'simulator/log_list.html', context) @login_required def simulator_log_detail(request, pk): """ Simulator log detail view. Features: - Full request payload - Full response data - Processing details - Related object links - Error details if applicable """ log_entry = get_object_or_404(HISRequestLog, pk=pk) # Parse JSON for better display import json # Format payload payload_formatted = json.dumps(log_entry.payload, indent=2) # Format response data response_formatted = None if log_entry.response_data: response_formatted = json.dumps(log_entry.response_data, indent=2) # Get related objects if available related_objects = {} if log_entry.patient_id: from apps.organizations.models import Patient try: patient = Patient.objects.filter(mrn=log_entry.patient_id).first() if patient: related_objects['patient'] = { 'id': str(patient.id), 'mrn': patient.mrn, 'name': patient.get_full_name(), 'phone': patient.phone, 'email': patient.email, } except Exception: pass if log_entry.journey_id: from apps.journeys.models import PatientJourneyInstance try: journey = PatientJourneyInstance.objects.filter( encounter_id=log_entry.journey_id ).select_related('journey_template', 'patient').first() if journey: related_objects['journey'] = { 'id': str(journey.id), 'encounter_id': journey.encounter_id, 'patient_name': journey.patient.get_full_name() if journey.patient else None, 'journey_type': journey.journey_template.journey_type if journey.journey_template else None, 'status': journey.status, } except Exception: pass if log_entry.survey_id: from apps.surveys.models import SurveyInstance try: survey = SurveyInstance.objects.filter( id=log_entry.survey_id ).select_related('survey_template', 'patient').first() if survey: related_objects['survey'] = { 'id': str(survey.id), 'status': survey.status, 'patient_name': survey.patient.get_full_name() if survey.patient else None, 'survey_type': survey.survey_template.survey_type if survey.survey_template else None, 'total_score': float(survey.total_score) if survey.total_score else None, } except Exception: pass context = { 'log': log_entry, 'payload_formatted': payload_formatted, 'response_formatted': response_formatted, 'related_objects': related_objects, } return render(request, 'simulator/log_detail.html', context) @login_required @require_http_methods(["POST"]) def simulator_clear_logs(request): """ Clear all simulator logs (admin only). This is a destructive operation that removes all logged simulator requests. """ # Check if user is admin if not (request.user.is_superuser or request.user.is_px_admin()): messages.error(request, "You don't have permission to clear simulator logs.") return redirect('simulator:logs_list') try: count = HISRequestLog.objects.count() HISRequestLog.objects.all().delete() messages.success( request, f"Successfully cleared {count} simulator log entries." ) except Exception as e: messages.error(request, f"Error clearing logs: {str(e)}") return redirect('simulator:logs_list')