290 lines
9.8 KiB
Python
290 lines
9.8 KiB
Python
"""
|
|
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')
|