HH/apps/simulator/ui_views.py

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')