""" Physicians Console UI views - Server-rendered templates for physician management """ from django.contrib.auth.decorators import login_required from django.core.paginator import Paginator from django.db.models import Avg, Count, Q from django.shortcuts import get_object_or_404, render from django.utils import timezone from apps.organizations.models import Department, Hospital, Staff from .models import PhysicianMonthlyRating @login_required def physician_list(request): """ Physicians list view with filters. Features: - Server-side pagination - Filters (hospital, department, specialization, status) - Search by name or license number - Current month rating display """ # Base queryset with optimizations queryset = Staff.objects.select_related('hospital', 'department') # Apply RBAC filters user = request.user if user.is_px_admin(): pass # See all elif user.hospital: queryset = queryset.filter(hospital=user.hospital) else: queryset = queryset.none() # Apply filters hospital_filter = request.GET.get('hospital') if hospital_filter: queryset = queryset.filter(hospital_id=hospital_filter) department_filter = request.GET.get('department') if department_filter: queryset = queryset.filter(department_id=department_filter) specialization_filter = request.GET.get('specialization') if specialization_filter: queryset = queryset.filter(specialization__icontains=specialization_filter) status_filter = request.GET.get('status', 'active') if status_filter: queryset = queryset.filter(status=status_filter) # Search search_query = request.GET.get('search') if search_query: queryset = queryset.filter( Q(first_name__icontains=search_query) | Q(last_name__icontains=search_query) | Q(license_number__icontains=search_query) | Q(specialization__icontains=search_query) ) # Ordering order_by = request.GET.get('order_by', 'last_name') 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 current month ratings for displayed physicians now = timezone.now() physician_ids = [p.id for p in page_obj.object_list] current_ratings = PhysicianMonthlyRating.objects.filter( staff_id__in=physician_ids, year=now.year, month=now.month ).select_related('staff') # Create rating lookup ratings_dict = {r.staff_id: r for r in current_ratings} # Attach ratings to physicians for physician in page_obj.object_list: physician.current_rating = ratings_dict.get(physician.id) # Get filter options hospitals = Hospital.objects.filter(status='active') if not user.is_px_admin() and user.hospital: hospitals = hospitals.filter(id=user.hospital.id) departments = Department.objects.filter(status='active') if not user.is_px_admin() and user.hospital: departments = departments.filter(hospital=user.hospital) # Get unique specializations specializations = Staff.objects.values_list('specialization', flat=True).distinct().order_by('specialization') # Statistics stats = { 'total': queryset.count(), 'active': queryset.filter(status='active').count(), } context = { 'page_obj': page_obj, 'physicians': page_obj.object_list, 'stats': stats, 'hospitals': hospitals, 'departments': departments, 'specializations': specializations, 'filters': request.GET, 'current_year': now.year, 'current_month': now.month, } return render(request, 'physicians/physician_list.html', context) @login_required def physician_detail(request, pk): """ Physician detail view with performance metrics. Features: - Full physician details - Current month rating - Year-to-date performance - Monthly ratings history (last 12 months) - Performance trends """ physician = get_object_or_404( Staff.objects.select_related('hospital', 'department'), pk=pk ) # Check permission user = request.user if not user.is_px_admin() and user.hospital: if physician.hospital != user.hospital: from django.http import Http404 raise Http404("Physician not found") now = timezone.now() current_year = now.year current_month = now.month # Get current month rating current_month_rating = PhysicianMonthlyRating.objects.filter( staff=physician, year=current_year, month=current_month ).first() # Get previous month rating prev_month = current_month - 1 if current_month > 1 else 12 prev_year = current_year if current_month > 1 else current_year - 1 previous_month_rating = PhysicianMonthlyRating.objects.filter( staff=physician, year=prev_year, month=prev_month ).first() # Get year-to-date stats ytd_ratings = PhysicianMonthlyRating.objects.filter( staff=physician, year=current_year ) ytd_stats = ytd_ratings.aggregate( avg_rating=Avg('average_rating'), total_surveys=Count('id') ) # Get last 12 months ratings ratings_history = PhysicianMonthlyRating.objects.filter( staff=physician ).order_by('-year', '-month')[:12] # Get best and worst months from all ratings (not just last 12 months) all_ratings = PhysicianMonthlyRating.objects.filter(staff=physician) best_month = all_ratings.order_by('-average_rating').first() worst_month = all_ratings.order_by('average_rating').first() # Determine trend trend = 'stable' trend_percentage = 0 if current_month_rating and previous_month_rating: diff = float(current_month_rating.average_rating - previous_month_rating.average_rating) if previous_month_rating.average_rating > 0: trend_percentage = (diff / float(previous_month_rating.average_rating)) * 100 if diff > 0.1: trend = 'improving' elif diff < -0.1: trend = 'declining' context = { 'physician': physician, 'current_month_rating': current_month_rating, 'previous_month_rating': previous_month_rating, 'ytd_average': ytd_stats['avg_rating'], 'ytd_surveys': ytd_stats['total_surveys'], 'ratings_history': ratings_history, 'best_month': best_month, 'worst_month': worst_month, 'trend': trend, 'trend_percentage': abs(trend_percentage), 'current_year': current_year, 'current_month': current_month, } return render(request, 'physicians/physician_detail.html', context) @login_required def leaderboard(request): """ Physician leaderboard view. Features: - Top-rated physicians for selected period - Filters (hospital, department, month/year) - Ranking with trends - Performance distribution """ # Get parameters now = timezone.now() year = int(request.GET.get('year', now.year)) month = int(request.GET.get('month', now.month)) hospital_filter = request.GET.get('hospital') department_filter = request.GET.get('department') limit = int(request.GET.get('limit', 20)) # Build queryset queryset = PhysicianMonthlyRating.objects.filter( year=year, month=month ).select_related('staff', 'staff__hospital', 'staff__department') # Apply RBAC filters user = request.user if not user.is_px_admin() and user.hospital: queryset = queryset.filter(staff__hospital=user.hospital) # Apply filters if hospital_filter: queryset = queryset.filter(staff__hospital_id=hospital_filter) if department_filter: queryset = queryset.filter(staff__department_id=department_filter) # Order by rating queryset = queryset.order_by('-average_rating')[:limit] # Get previous month for trend prev_month = month - 1 if month > 1 else 12 prev_year = year if month > 1 else year - 1 # Build leaderboard with trends leaderboard = [] for rank, rating in enumerate(queryset, start=1): # Get previous month rating for trend prev_rating = PhysicianMonthlyRating.objects.filter( staff=rating.staff, year=prev_year, month=prev_month ).first() trend = 'stable' trend_value = 0 if prev_rating: diff = float(rating.average_rating - prev_rating.average_rating) trend_value = diff if diff > 0.1: trend = 'up' elif diff < -0.1: trend = 'down' leaderboard.append({ 'rank': rank, 'rating': rating, 'physician': rating.staff, 'trend': trend, 'trend_value': trend_value, 'prev_rating': prev_rating }) # Get filter options hospitals = Hospital.objects.filter(status='active') if not user.is_px_admin() and user.hospital: hospitals = hospitals.filter(id=user.hospital.id) departments = Department.objects.filter(status='active') if not user.is_px_admin() and user.hospital: departments = departments.filter(hospital=user.hospital) # Calculate statistics all_ratings = PhysicianMonthlyRating.objects.filter(year=year, month=month) if not user.is_px_admin() and user.hospital: all_ratings = all_ratings.filter(staff__hospital=user.hospital) stats = all_ratings.aggregate( total_physicians=Count('id'), average_rating=Avg('average_rating'), total_surveys=Count('total_surveys') ) # Distribution excellent = all_ratings.filter(average_rating__gte=4.5).count() good = all_ratings.filter(average_rating__gte=3.5, average_rating__lt=4.5).count() average = all_ratings.filter(average_rating__gte=2.5, average_rating__lt=3.5).count() poor = all_ratings.filter(average_rating__lt=2.5).count() context = { 'leaderboard': leaderboard, 'year': year, 'month': month, 'hospitals': hospitals, 'departments': departments, 'filters': request.GET, 'stats': stats, 'distribution': { 'excellent': excellent, 'good': good, 'average': average, 'poor': poor } } return render(request, 'physicians/leaderboard.html', context) @login_required def ratings_list(request): """ Monthly ratings list view with filters. Features: - All monthly ratings - Filters (physician, hospital, department, year, month) - Search by physician name - Pagination """ # Base queryset queryset = PhysicianMonthlyRating.objects.select_related( 'staff', 'staff__hospital', 'staff__department' ) # Apply RBAC filters user = request.user if not user.is_px_admin() and user.hospital: queryset = queryset.filter(staff__hospital=user.hospital) # Apply filters physician_filter = request.GET.get('physician') if physician_filter: queryset = queryset.filter(staff_id=physician_filter) hospital_filter = request.GET.get('hospital') if hospital_filter: queryset = queryset.filter(staff__hospital_id=hospital_filter) department_filter = request.GET.get('department') if department_filter: queryset = queryset.filter(staff__department_id=department_filter) year_filter = request.GET.get('year') if year_filter: queryset = queryset.filter(year=int(year_filter)) month_filter = request.GET.get('month') if month_filter: queryset = queryset.filter(month=int(month_filter)) # Search search_query = request.GET.get('search') if search_query: queryset = queryset.filter( Q(staff__first_name__icontains=search_query) | Q(staff__last_name__icontains=search_query) | Q(staff__license_number__icontains=search_query) ) # Ordering order_by = request.GET.get('order_by', '-year,-month,-average_rating') queryset = queryset.order_by(*order_by.split(',')) # 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 filter options hospitals = Hospital.objects.filter(status='active') if not user.is_px_admin() and user.hospital: hospitals = hospitals.filter(id=user.hospital.id) departments = Department.objects.filter(status='active') if not user.is_px_admin() and user.hospital: departments = departments.filter(hospital=user.hospital) # Get available years years = PhysicianMonthlyRating.objects.values_list('year', flat=True).distinct().order_by('-year') context = { 'page_obj': page_obj, 'ratings': page_obj.object_list, 'hospitals': hospitals, 'departments': departments, 'years': years, 'filters': request.GET, } return render(request, 'physicians/ratings_list.html', context) @login_required def specialization_overview(request): """ Specialization overview - aggregated ratings by specialization. Features: - Average rating per specialization - Total physicians per specialization - Total surveys per specialization - Drill-down to physicians in specialization """ # Get parameters now = timezone.now() year = int(request.GET.get('year', now.year)) month = int(request.GET.get('month', now.month)) hospital_filter = request.GET.get('hospital') # Base queryset queryset = PhysicianMonthlyRating.objects.filter( year=year, month=month ).select_related('staff', 'staff__hospital', 'staff__department') # Apply RBAC filters user = request.user if not user.is_px_admin() and user.hospital: queryset = queryset.filter(staff__hospital=user.hospital) # Apply filters if hospital_filter: queryset = queryset.filter(staff__hospital_id=hospital_filter) # Aggregate by specialization from django.db.models import Avg, Count, Sum specialization_data = {} for rating in queryset: spec = rating.staff.specialization if spec not in specialization_data: specialization_data[spec] = { 'specialization': spec, 'physicians': [], 'total_physicians': 0, 'total_surveys': 0, 'total_positive': 0, 'total_neutral': 0, 'total_negative': 0, 'ratings_sum': 0, } specialization_data[spec]['physicians'].append(rating) specialization_data[spec]['total_physicians'] += 1 specialization_data[spec]['total_surveys'] += rating.total_surveys specialization_data[spec]['total_positive'] += rating.positive_count specialization_data[spec]['total_neutral'] += rating.neutral_count specialization_data[spec]['total_negative'] += rating.negative_count specialization_data[spec]['ratings_sum'] += float(rating.average_rating) # Calculate averages specializations = [] for spec, data in specialization_data.items(): avg_rating = data['ratings_sum'] / data['total_physicians'] if data['total_physicians'] > 0 else 0 specializations.append({ 'specialization': spec, 'total_physicians': data['total_physicians'], 'average_rating': round(avg_rating, 2), 'total_surveys': data['total_surveys'], 'positive_count': data['total_positive'], 'neutral_count': data['total_neutral'], 'negative_count': data['total_negative'], 'physicians': sorted(data['physicians'], key=lambda x: x.average_rating, reverse=True) }) # Sort by average rating specializations.sort(key=lambda x: x['average_rating'], reverse=True) # Get filter options hospitals = Hospital.objects.filter(status='active') if not user.is_px_admin() and user.hospital: hospitals = hospitals.filter(id=user.hospital.id) context = { 'specializations': specializations, 'year': year, 'month': month, 'hospitals': hospitals, 'filters': request.GET, } return render(request, 'physicians/specialization_overview.html', context) @login_required def department_overview(request): """ Department overview - aggregated ratings by department. Features: - Average rating per department - Total physicians per department - Total surveys per department - Drill-down to physicians in department """ # Get parameters now = timezone.now() year = int(request.GET.get('year', now.year)) month = int(request.GET.get('month', now.month)) hospital_filter = request.GET.get('hospital') # Base queryset queryset = PhysicianMonthlyRating.objects.filter( year=year, month=month ).select_related('staff', 'staff__hospital', 'staff__department') # Apply RBAC filters user = request.user if not user.is_px_admin() and user.hospital: queryset = queryset.filter(staff__hospital=user.hospital) # Apply filters if hospital_filter: queryset = queryset.filter(staff__hospital_id=hospital_filter) # Aggregate by department from django.db.models import Avg, Count, Sum department_data = {} for rating in queryset: dept = rating.staff.department if not dept: continue dept_key = str(dept.id) if dept_key not in department_data: department_data[dept_key] = { 'department': dept, 'physicians': [], 'total_physicians': 0, 'total_surveys': 0, 'total_positive': 0, 'total_neutral': 0, 'total_negative': 0, 'ratings_sum': 0, } department_data[dept_key]['physicians'].append(rating) department_data[dept_key]['total_physicians'] += 1 department_data[dept_key]['total_surveys'] += rating.total_surveys department_data[dept_key]['total_positive'] += rating.positive_count department_data[dept_key]['total_neutral'] += rating.neutral_count department_data[dept_key]['total_negative'] += rating.negative_count department_data[dept_key]['ratings_sum'] += float(rating.average_rating) # Calculate averages departments = [] for dept_key, data in department_data.items(): avg_rating = data['ratings_sum'] / data['total_physicians'] if data['total_physicians'] > 0 else 0 departments.append({ 'department': data['department'], 'total_physicians': data['total_physicians'], 'average_rating': round(avg_rating, 2), 'total_surveys': data['total_surveys'], 'positive_count': data['total_positive'], 'neutral_count': data['total_neutral'], 'negative_count': data['total_negative'], 'physicians': sorted(data['physicians'], key=lambda x: x.average_rating, reverse=True) }) # Sort by average rating departments.sort(key=lambda x: x['average_rating'], reverse=True) # Get filter options hospitals = Hospital.objects.filter(status='active') if not user.is_px_admin() and user.hospital: hospitals = hospitals.filter(id=user.hospital.id) context = { 'departments': departments, 'year': year, 'month': month, 'hospitals': hospitals, 'filters': request.GET, } return render(request, 'physicians/department_overview.html', context)