HH/apps/physicians/views.py
2025-12-29 18:36:06 +03:00

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 Physician
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 = Physician.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(
physician=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(
physician=physician,
year=prev_year,
month=prev_month
).first()
# Get year-to-date stats
ytd_ratings = PhysicianMonthlyRating.objects.filter(
physician=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(
physician=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(
physician=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 = ['physician', 'year', 'month', 'physician__hospital', 'physician__department']
search_fields = ['physician__first_name', 'physician__last_name', 'physician__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(
'physician',
'physician__hospital',
'physician__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(physician__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('physician', 'physician__hospital', 'physician__department')
# Apply RBAC filters
user = request.user
if not user.is_px_admin() and user.hospital:
queryset = queryset.filter(physician__hospital=user.hospital)
# Apply filters
if hospital_id:
queryset = queryset.filter(physician__hospital_id=hospital_id)
if department_id:
queryset = queryset.filter(physician__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(
physician=rating.physician,
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.physician.id,
'physician_name': rating.physician.get_full_name(),
'physician_license': rating.physician.license_number,
'specialization': rating.physician.specialization,
'department_name': rating.physician.department.name if rating.physician.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(physician__hospital=user.hospital)
# Apply filters
if hospital_id:
queryset = queryset.filter(physician__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']
}
})