HH/apps/physicians/ui_views.py

609 lines
20 KiB
Python

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