""" 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, Sum from django.http import JsonResponse 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 - only show staff marked as physicians # Include both: staff with physician=True (from rating imports) OR staff_type='physician' queryset = ( Staff.objects.filter(Q(physician=True) | Q(staff_type=Staff.StaffType.PHYSICIAN)) .select_related("hospital", "department") .distinct() ) # 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) 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, "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 physician_ratings_dashboard(request): """ Physician ratings dashboard - Main analytics view with charts. Features: - Statistics cards - Rating trend over 6 months - Rating distribution - Department comparison - Sentiment analysis - Top physicians table """ 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") # Get available years (2024 to current year) current_year = now.year years = list(range(2024, current_year + 1)) years.reverse() # Most recent first context = { "years": years, "filters": request.GET, } return render(request, "physicians/physician_ratings_dashboard.html", context) @login_required def physician_ratings_dashboard_api(request): """ API endpoint for physician ratings dashboard data. Returns JSON data for all dashboard charts and statistics. """ try: 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") # Base queryset - only include staff marked as physicians queryset = PhysicianMonthlyRating.objects.filter(staff__physician=True).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) # Filter for selected period current_period = queryset.filter(year=year, month=month) # 1. Statistics stats = current_period.aggregate( total_physicians=Count("id", distinct=True), average_rating=Avg("average_rating"), total_surveys=Sum("total_surveys"), ) excellent_count = current_period.filter(average_rating__gte=4.5).count() # 2. Rating Trend (last 6 months) trend_data = [] for i in range(5, -1, -1): m = month - i y = year if m <= 0: m += 12 y -= 1 period_data = queryset.filter(year=y, month=m).aggregate( avg=Avg("average_rating"), surveys=Sum("total_surveys") ) trend_data.append( { "period": f"{y}-{m:02d}", "average_rating": float(period_data["avg"] or 0), "total_surveys": period_data["surveys"] or 0, } ) # 3. Rating Distribution excellent = current_period.filter(average_rating__gte=4.5).count() good = current_period.filter(average_rating__gte=3.5, average_rating__lt=4.5).count() average = current_period.filter(average_rating__gte=2.5, average_rating__lt=3.5).count() poor = current_period.filter(average_rating__lt=2.5).count() distribution = {"excellent": excellent, "good": good, "average": average, "poor": poor} # 4. Department Comparison (top 10) dept_data = ( current_period.values("staff__department__name") .annotate( average_rating=Avg("average_rating"), total_surveys=Sum("total_surveys"), physician_count=Count("id", distinct=True), ) .filter(staff__department__isnull=False) .order_by("-average_rating")[:10] ) departments = [ { "name": item["staff__department__name"] or "Unknown", "average_rating": float(item["average_rating"] or 0), "total_surveys": item["total_surveys"] or 0, } for item in dept_data ] # 5. Sentiment Analysis sentiment = current_period.aggregate( positive=Sum("positive_count"), neutral=Sum("neutral_count"), negative=Sum("negative_count") ) total_sentiment = (sentiment["positive"] or 0) + (sentiment["neutral"] or 0) + (sentiment["negative"] or 0) if total_sentiment > 0: sentiment_pct = { "positive": ((sentiment["positive"] or 0) / total_sentiment) * 100, "neutral": ((sentiment["neutral"] or 0) / total_sentiment) * 100, "negative": ((sentiment["negative"] or 0) / total_sentiment) * 100, } else: sentiment_pct = {"positive": 0, "neutral": 0, "negative": 0} # 6. Top 10 Physicians top_physicians = current_period.select_related("staff", "staff__hospital", "staff__department").order_by( "-average_rating", "-total_surveys" )[:10] physicians_list = [ { "id": rating.staff.id, "name": rating.staff.get_full_name(), "license_number": rating.staff.license_number, "specialization": rating.staff.specialization or "-", "department": rating.staff.department.name if rating.staff.department else "-", "hospital": rating.staff.hospital.name if rating.staff.hospital else "-", "rating": float(rating.average_rating), "surveys": rating.total_surveys, } for rating in top_physicians ] return JsonResponse( { "statistics": { "total_physicians": stats["total_physicians"] or 0, "average_rating": float(stats["average_rating"] or 0), "total_surveys": stats["total_surveys"] or 0, "excellent_count": excellent_count, }, "trend": trend_data, "distribution": distribution, "departments": departments, "sentiment": sentiment_pct, "top_physicians": physicians_list, } ) except Exception as e: import traceback return JsonResponse({"error": str(e), "traceback": traceback.format_exc()}, status=500) @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 - only include staff marked as physicians queryset = PhysicianMonthlyRating.objects.filter(staff__physician=True).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) 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, "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 - only include staff marked as physicians queryset = PhysicianMonthlyRating.objects.filter(year=year, month=month, staff__physician=True).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 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) context = { "specializations": specializations, "year": year, "month": month, "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 - only include staff marked as physicians queryset = PhysicianMonthlyRating.objects.filter(year=year, month=month, staff__physician=True).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 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) context = { "departments": departments, "year": year, "month": month, "filters": request.GET, } return render(request, "physicians/department_overview.html", context)