327 lines
12 KiB
Python
327 lines
12 KiB
Python
"""
|
|
Physicians API views and viewsets
|
|
"""
|
|
from django.db.models import Avg, Count, Q
|
|
from rest_framework import status, viewsets
|
|
from rest_framework.decorators import action
|
|
from rest_framework.permissions import IsAuthenticated
|
|
from rest_framework.response import Response
|
|
|
|
from apps.accounts.permissions import IsPXAdminOrHospitalAdmin
|
|
from apps.organizations.models import Staff
|
|
|
|
from .models import PhysicianMonthlyRating
|
|
from .serializers import (
|
|
PhysicianLeaderboardSerializer,
|
|
PhysicianMonthlyRatingSerializer,
|
|
PhysicianPerformanceSerializer,
|
|
PhysicianSerializer,
|
|
)
|
|
|
|
|
|
class PhysicianViewSet(viewsets.ReadOnlyModelViewSet):
|
|
"""
|
|
ViewSet for Physicians.
|
|
|
|
Permissions:
|
|
- All authenticated users can view physicians
|
|
- Filtered by hospital based on user role
|
|
"""
|
|
queryset = Staff.objects.all()
|
|
serializer_class = PhysicianSerializer
|
|
permission_classes = [IsAuthenticated]
|
|
filterset_fields = ['hospital', 'department', 'specialization', 'status']
|
|
search_fields = ['first_name', 'last_name', 'license_number', 'specialization']
|
|
ordering_fields = ['last_name', 'first_name', 'specialization', 'created_at']
|
|
ordering = ['last_name', 'first_name']
|
|
|
|
def get_queryset(self):
|
|
"""Filter physicians based on user role"""
|
|
queryset = super().get_queryset().select_related('hospital', 'department')
|
|
user = self.request.user
|
|
|
|
# PX Admins see all physicians
|
|
if user.is_px_admin():
|
|
return queryset
|
|
|
|
# Hospital Admins and staff see physicians for their hospital
|
|
if user.hospital:
|
|
return queryset.filter(hospital=user.hospital)
|
|
|
|
return queryset.none()
|
|
|
|
@action(detail=True, methods=['get'])
|
|
def performance(self, request, pk=None):
|
|
"""
|
|
Get physician performance summary.
|
|
|
|
GET /api/physicians/{id}/performance/
|
|
|
|
Returns:
|
|
- Current month rating
|
|
- Previous month rating
|
|
- Year-to-date average
|
|
- Best/worst months
|
|
- Trend analysis
|
|
"""
|
|
physician = self.get_object()
|
|
|
|
from django.utils import timezone
|
|
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 best and worst months (last 12 months)
|
|
last_12_months = PhysicianMonthlyRating.objects.filter(
|
|
staff=physician
|
|
).order_by('-year', '-month')[:12]
|
|
|
|
best_month = last_12_months.order_by('-average_rating').first()
|
|
worst_month = last_12_months.order_by('average_rating').first()
|
|
|
|
# Determine trend
|
|
trend = 'stable'
|
|
if current_month_rating and previous_month_rating:
|
|
if current_month_rating.average_rating > previous_month_rating.average_rating:
|
|
trend = 'improving'
|
|
elif current_month_rating.average_rating < previous_month_rating.average_rating:
|
|
trend = 'declining'
|
|
|
|
# Build response
|
|
data = {
|
|
'physician': PhysicianSerializer(physician).data,
|
|
'current_month_rating': PhysicianMonthlyRatingSerializer(current_month_rating).data if current_month_rating else None,
|
|
'previous_month_rating': PhysicianMonthlyRatingSerializer(previous_month_rating).data if previous_month_rating else None,
|
|
'year_to_date_average': ytd_stats['avg_rating'],
|
|
'total_surveys_ytd': ytd_stats['total_surveys'],
|
|
'best_month': PhysicianMonthlyRatingSerializer(best_month).data if best_month else None,
|
|
'worst_month': PhysicianMonthlyRatingSerializer(worst_month).data if worst_month else None,
|
|
'trend': trend
|
|
}
|
|
|
|
serializer = PhysicianPerformanceSerializer(data)
|
|
return Response(serializer.data)
|
|
|
|
@action(detail=True, methods=['get'])
|
|
def ratings_history(self, request, pk=None):
|
|
"""
|
|
Get physician ratings history.
|
|
|
|
GET /api/physicians/{id}/ratings_history/?months=12
|
|
|
|
Returns monthly ratings for the specified number of months.
|
|
"""
|
|
physician = self.get_object()
|
|
months = int(request.query_params.get('months', 12))
|
|
|
|
ratings = PhysicianMonthlyRating.objects.filter(
|
|
staff=physician
|
|
).order_by('-year', '-month')[:months]
|
|
|
|
serializer = PhysicianMonthlyRatingSerializer(ratings, many=True)
|
|
return Response(serializer.data)
|
|
|
|
|
|
class PhysicianMonthlyRatingViewSet(viewsets.ReadOnlyModelViewSet):
|
|
"""
|
|
ViewSet for Physician Monthly Ratings.
|
|
|
|
Permissions:
|
|
- All authenticated users can view ratings
|
|
- Filtered by hospital based on user role
|
|
"""
|
|
queryset = PhysicianMonthlyRating.objects.all()
|
|
serializer_class = PhysicianMonthlyRatingSerializer
|
|
permission_classes = [IsAuthenticated]
|
|
filterset_fields = ['staff', 'year', 'month', 'staff__hospital', 'staff__department']
|
|
search_fields = ['staff__first_name', 'staff__last_name', 'staff__license_number']
|
|
ordering_fields = ['year', 'month', 'average_rating', 'total_surveys', 'hospital_rank', 'department_rank']
|
|
ordering = ['-year', '-month', '-average_rating']
|
|
|
|
def get_queryset(self):
|
|
"""Filter ratings based on user role"""
|
|
queryset = super().get_queryset().select_related(
|
|
'staff',
|
|
'staff__hospital',
|
|
'staff__department'
|
|
)
|
|
user = self.request.user
|
|
|
|
# PX Admins see all ratings
|
|
if user.is_px_admin():
|
|
return queryset
|
|
|
|
# Hospital Admins and staff see ratings for their hospital
|
|
if user.hospital:
|
|
return queryset.filter(staff__hospital=user.hospital)
|
|
|
|
return queryset.none()
|
|
|
|
@action(detail=False, methods=['get'])
|
|
def leaderboard(self, request):
|
|
"""
|
|
Get physician leaderboard.
|
|
|
|
GET /api/physicians/ratings/leaderboard/?year=2024&month=12&hospital={id}&department={id}&limit=10
|
|
|
|
Returns top-rated physicians for the specified period.
|
|
"""
|
|
from django.utils import timezone
|
|
|
|
# Get parameters
|
|
year = int(request.query_params.get('year', timezone.now().year))
|
|
month = int(request.query_params.get('month', timezone.now().month))
|
|
hospital_id = request.query_params.get('hospital')
|
|
department_id = request.query_params.get('department')
|
|
limit = int(request.query_params.get('limit', 10))
|
|
|
|
# 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_id:
|
|
queryset = queryset.filter(staff__hospital_id=hospital_id)
|
|
|
|
if department_id:
|
|
queryset = queryset.filter(staff__department_id=department_id)
|
|
|
|
# Order by rating and limit
|
|
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 data
|
|
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'
|
|
if prev_rating:
|
|
if rating.average_rating > prev_rating.average_rating:
|
|
trend = 'up'
|
|
elif rating.average_rating < prev_rating.average_rating:
|
|
trend = 'down'
|
|
|
|
leaderboard.append({
|
|
'physician_id': rating.staff.id,
|
|
'physician_name': rating.staff.get_full_name(),
|
|
'physician_license': rating.staff.license_number,
|
|
'specialization': rating.staff.specialization,
|
|
'department_name': rating.staff.department.name if rating.staff.department else '',
|
|
'average_rating': rating.average_rating,
|
|
'total_surveys': rating.total_surveys,
|
|
'rank': rank,
|
|
'trend': trend
|
|
})
|
|
|
|
serializer = PhysicianLeaderboardSerializer(leaderboard, many=True)
|
|
return Response(serializer.data)
|
|
|
|
@action(detail=False, methods=['get'])
|
|
def statistics(self, request):
|
|
"""
|
|
Get physician rating statistics.
|
|
|
|
GET /api/physicians/ratings/statistics/?year=2024&month=12&hospital={id}
|
|
|
|
Returns aggregate statistics for the specified period.
|
|
"""
|
|
from django.utils import timezone
|
|
|
|
# Get parameters
|
|
year = int(request.query_params.get('year', timezone.now().year))
|
|
month = int(request.query_params.get('month', timezone.now().month))
|
|
hospital_id = request.query_params.get('hospital')
|
|
|
|
# Build queryset
|
|
queryset = PhysicianMonthlyRating.objects.filter(
|
|
year=year,
|
|
month=month
|
|
)
|
|
|
|
# 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_id:
|
|
queryset = queryset.filter(staff__hospital_id=hospital_id)
|
|
|
|
# Calculate statistics
|
|
stats = queryset.aggregate(
|
|
total_physicians=Count('id'),
|
|
average_rating=Avg('average_rating'),
|
|
total_surveys=Count('total_surveys'),
|
|
total_positive=Count('positive_count'),
|
|
total_neutral=Count('neutral_count'),
|
|
total_negative=Count('negative_count')
|
|
)
|
|
|
|
# Get distribution
|
|
excellent = queryset.filter(average_rating__gte=4.5).count()
|
|
good = queryset.filter(average_rating__gte=3.5, average_rating__lt=4.5).count()
|
|
average = queryset.filter(average_rating__gte=2.5, average_rating__lt=3.5).count()
|
|
poor = queryset.filter(average_rating__lt=2.5).count()
|
|
|
|
return Response({
|
|
'year': year,
|
|
'month': month,
|
|
'total_physicians': stats['total_physicians'],
|
|
'average_rating': stats['average_rating'],
|
|
'total_surveys': stats['total_surveys'],
|
|
'distribution': {
|
|
'excellent': excellent, # 4.5+
|
|
'good': good, # 3.5-4.5
|
|
'average': average, # 2.5-3.5
|
|
'poor': poor # <2.5
|
|
},
|
|
'sentiment': {
|
|
'positive': stats['total_positive'],
|
|
'neutral': stats['total_neutral'],
|
|
'negative': stats['total_negative']
|
|
}
|
|
})
|