HH/apps/physicians/ui_views.py
ismail b57ce661bb Clean up redundant hospital filtering in views
Removed ~300 lines of redundant hospital filtering code from views.
Templates no longer use hospital dropdowns, so views don't need to:
- Query Hospital.objects.filter()
- Apply RBAC filtering to hospitals queryset
- Pass hospitals to context

The middleware (TenantMiddleware) already handles hospital filtering
via request.tenant_hospital for all users.

Files cleaned:
- apps/surveys/ui_views.py
- apps/callcenter/ui_views.py
- apps/complaints/ui_views.py
- apps/analytics/ui_views.py
- apps/physicians/ui_views.py
- apps/projects/ui_views.py
- apps/feedback/views.py
- apps/dashboard/views.py
- apps/journeys/ui_views.py
- apps/appreciation/ui_views.py
2026-03-11 00:49:42 +03:00

571 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, 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,
"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 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")
departments = departments.filter(hospital=user.hospital)
# 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,
"hospitals": hospitals,
"departments": departments,
"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,
"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 - 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,
"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 - 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,
"hospitals": hospitals,
"filters": request.GET,
}
return render(request, "physicians/department_overview.html", context)