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
This commit is contained in:
parent
c16e410fdd
commit
b57ce661bb
@ -1,6 +1,7 @@
|
||||
"""
|
||||
Analytics Console UI views
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
@ -25,14 +26,14 @@ import json
|
||||
def serialize_queryset_values(queryset):
|
||||
"""Properly serialize QuerySet values to JSON string."""
|
||||
if queryset is None:
|
||||
return '[]'
|
||||
return "[]"
|
||||
data = list(queryset)
|
||||
result = []
|
||||
for item in data:
|
||||
row = {}
|
||||
for key, value in item.items():
|
||||
# Convert UUID to string
|
||||
if hasattr(value, 'hex'): # UUID object
|
||||
if hasattr(value, "hex"): # UUID object
|
||||
row[key] = str(value)
|
||||
# Convert Python None to JavaScript null
|
||||
elif value is None:
|
||||
@ -60,11 +61,11 @@ def analytics_dashboard(request):
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
from django.db.models.functions import TruncDate, TruncMonth
|
||||
|
||||
|
||||
user = request.user
|
||||
|
||||
# Get hospital filter
|
||||
hospital_filter = request.GET.get('hospital')
|
||||
hospital_filter = request.GET.get("hospital")
|
||||
if hospital_filter:
|
||||
hospital = Hospital.objects.filter(id=hospital_filter).first()
|
||||
elif user.hospital:
|
||||
@ -75,7 +76,7 @@ def analytics_dashboard(request):
|
||||
# Base querysets
|
||||
complaints_queryset = Complaint.objects.all()
|
||||
actions_queryset = PXAction.objects.all()
|
||||
surveys_queryset = SurveyInstance.objects.filter(status='completed')
|
||||
surveys_queryset = SurveyInstance.objects.filter(status="completed")
|
||||
feedback_queryset = Feedback.objects.all()
|
||||
|
||||
if hospital:
|
||||
@ -86,117 +87,128 @@ def analytics_dashboard(request):
|
||||
|
||||
# ============ COMPLAINTS KPIs ============
|
||||
total_complaints = complaints_queryset.count()
|
||||
open_complaints = complaints_queryset.filter(status='open').count()
|
||||
in_progress_complaints = complaints_queryset.filter(status='in_progress').count()
|
||||
resolved_complaints = complaints_queryset.filter(status='resolved').count()
|
||||
closed_complaints = complaints_queryset.filter(status='closed').count()
|
||||
open_complaints = complaints_queryset.filter(status="open").count()
|
||||
in_progress_complaints = complaints_queryset.filter(status="in_progress").count()
|
||||
resolved_complaints = complaints_queryset.filter(status="resolved").count()
|
||||
closed_complaints = complaints_queryset.filter(status="closed").count()
|
||||
overdue_complaints = complaints_queryset.filter(is_overdue=True).count()
|
||||
|
||||
|
||||
# Complaint sources
|
||||
complaint_sources = complaints_queryset.values('source').annotate(count=Count('id')).order_by('-count')[:6]
|
||||
complaint_sources = complaints_queryset.values("source").annotate(count=Count("id")).order_by("-count")[:6]
|
||||
|
||||
# Complaint domains (Level 1)
|
||||
top_domains = complaints_queryset.filter(domain__isnull=False).values('domain__name_en').annotate(count=Count('id')).order_by('-count')[:5]
|
||||
top_domains = (
|
||||
complaints_queryset.filter(domain__isnull=False)
|
||||
.values("domain__name_en")
|
||||
.annotate(count=Count("id"))
|
||||
.order_by("-count")[:5]
|
||||
)
|
||||
|
||||
# Complaint categories (Level 2)
|
||||
top_categories = complaints_queryset.filter(category__isnull=False).values('category__name_en').annotate(count=Count('id')).order_by('-count')[:5]
|
||||
top_categories = (
|
||||
complaints_queryset.filter(category__isnull=False)
|
||||
.values("category__name_en")
|
||||
.annotate(count=Count("id"))
|
||||
.order_by("-count")[:5]
|
||||
)
|
||||
|
||||
# Complaint severity
|
||||
severity_breakdown = complaints_queryset.values('severity').annotate(count=Count('id')).order_by('-count')
|
||||
|
||||
severity_breakdown = complaints_queryset.values("severity").annotate(count=Count("id")).order_by("-count")
|
||||
|
||||
# Status breakdown
|
||||
status_breakdown = complaints_queryset.values('status').annotate(count=Count('id')).order_by('-count')
|
||||
status_breakdown = complaints_queryset.values("status").annotate(count=Count("id")).order_by("-count")
|
||||
|
||||
# ============ ACTIONS KPIs ============
|
||||
total_actions = actions_queryset.count()
|
||||
open_actions = actions_queryset.filter(status='open').count()
|
||||
in_progress_actions = actions_queryset.filter(status='in_progress').count()
|
||||
approved_actions = actions_queryset.filter(status='approved').count()
|
||||
closed_actions = actions_queryset.filter(status='closed').count()
|
||||
open_actions = actions_queryset.filter(status="open").count()
|
||||
in_progress_actions = actions_queryset.filter(status="in_progress").count()
|
||||
approved_actions = actions_queryset.filter(status="approved").count()
|
||||
closed_actions = actions_queryset.filter(status="closed").count()
|
||||
overdue_actions = actions_queryset.filter(is_overdue=True).count()
|
||||
|
||||
|
||||
# Action sources
|
||||
action_sources = actions_queryset.values('source_type').annotate(count=Count('id')).order_by('-count')[:6]
|
||||
|
||||
action_sources = actions_queryset.values("source_type").annotate(count=Count("id")).order_by("-count")[:6]
|
||||
|
||||
# Action categories
|
||||
action_categories = actions_queryset.exclude(category='').values('category').annotate(count=Count('id')).order_by('-count')[:5]
|
||||
action_categories = (
|
||||
actions_queryset.exclude(category="").values("category").annotate(count=Count("id")).order_by("-count")[:5]
|
||||
)
|
||||
|
||||
# ============ SURVEYS KPIs ============
|
||||
total_surveys = surveys_queryset.count()
|
||||
avg_survey_score = surveys_queryset.aggregate(avg=Avg('total_score'))['avg'] or 0
|
||||
avg_survey_score = surveys_queryset.aggregate(avg=Avg("total_score"))["avg"] or 0
|
||||
negative_surveys = surveys_queryset.filter(is_negative=True).count()
|
||||
|
||||
|
||||
# Survey completion rate
|
||||
all_surveys = SurveyInstance.objects.all()
|
||||
if hospital:
|
||||
all_surveys = all_surveys.filter(survey_template__hospital=hospital)
|
||||
total_sent = all_surveys.count()
|
||||
completed_surveys = all_surveys.filter(status='completed').count()
|
||||
completed_surveys = all_surveys.filter(status="completed").count()
|
||||
completion_rate = (completed_surveys / total_sent * 100) if total_sent > 0 else 0
|
||||
|
||||
|
||||
# Survey types
|
||||
survey_types = all_surveys.values('survey_template__survey_type').annotate(count=Count('id')).order_by('-count')[:5]
|
||||
survey_types = all_surveys.values("survey_template__survey_type").annotate(count=Count("id")).order_by("-count")[:5]
|
||||
|
||||
# ============ FEEDBACK KPIs ============
|
||||
total_feedback = feedback_queryset.count()
|
||||
compliments = feedback_queryset.filter(feedback_type='compliment').count()
|
||||
suggestions = feedback_queryset.filter(feedback_type='suggestion').count()
|
||||
|
||||
compliments = feedback_queryset.filter(feedback_type="compliment").count()
|
||||
suggestions = feedback_queryset.filter(feedback_type="suggestion").count()
|
||||
|
||||
# Sentiment analysis
|
||||
sentiment_breakdown = feedback_queryset.values('sentiment').annotate(count=Count('id')).order_by('-count')
|
||||
|
||||
sentiment_breakdown = feedback_queryset.values("sentiment").annotate(count=Count("id")).order_by("-count")
|
||||
|
||||
# Feedback categories
|
||||
feedback_categories = feedback_queryset.values('category').annotate(count=Count('id')).order_by('-count')[:5]
|
||||
|
||||
feedback_categories = feedback_queryset.values("category").annotate(count=Count("id")).order_by("-count")[:5]
|
||||
|
||||
# Average rating
|
||||
avg_rating = feedback_queryset.filter(rating__isnull=False).aggregate(avg=Avg('rating'))['avg'] or 0
|
||||
avg_rating = feedback_queryset.filter(rating__isnull=False).aggregate(avg=Avg("rating"))["avg"] or 0
|
||||
|
||||
# ============ TRENDS (Last 30 days) ============
|
||||
thirty_days_ago = timezone.now() - timedelta(days=30)
|
||||
|
||||
|
||||
# Complaint trends
|
||||
complaint_trend = complaints_queryset.filter(
|
||||
created_at__gte=thirty_days_ago
|
||||
).annotate(
|
||||
day=TruncDate('created_at')
|
||||
).values('day').annotate(count=Count('id')).order_by('day')
|
||||
|
||||
complaint_trend = (
|
||||
complaints_queryset.filter(created_at__gte=thirty_days_ago)
|
||||
.annotate(day=TruncDate("created_at"))
|
||||
.values("day")
|
||||
.annotate(count=Count("id"))
|
||||
.order_by("day")
|
||||
)
|
||||
|
||||
# Survey score trend
|
||||
survey_score_trend = surveys_queryset.filter(
|
||||
completed_at__gte=thirty_days_ago
|
||||
).annotate(
|
||||
day=TruncDate('completed_at')
|
||||
).values('day').annotate(avg_score=Avg('total_score')).order_by('day')
|
||||
survey_score_trend = (
|
||||
surveys_queryset.filter(completed_at__gte=thirty_days_ago)
|
||||
.annotate(day=TruncDate("completed_at"))
|
||||
.values("day")
|
||||
.annotate(avg_score=Avg("total_score"))
|
||||
.order_by("day")
|
||||
)
|
||||
|
||||
# ============ DEPARTMENT RANKINGS ============
|
||||
department_rankings = Department.objects.filter(
|
||||
status='active'
|
||||
).annotate(
|
||||
avg_score=Avg(
|
||||
'journey_instances__surveys__total_score',
|
||||
filter=Q(journey_instances__surveys__status='completed')
|
||||
),
|
||||
survey_count=Count(
|
||||
'journey_instances__surveys',
|
||||
filter=Q(journey_instances__surveys__status='completed')
|
||||
),
|
||||
complaint_count=Count('complaints'),
|
||||
action_count=Count('px_actions')
|
||||
).filter(
|
||||
survey_count__gt=0
|
||||
).order_by('-avg_score')[:7]
|
||||
department_rankings = (
|
||||
Department.objects.filter(status="active")
|
||||
.annotate(
|
||||
avg_score=Avg(
|
||||
"journey_instances__surveys__total_score", filter=Q(journey_instances__surveys__status="completed")
|
||||
),
|
||||
survey_count=Count("journey_instances__surveys", filter=Q(journey_instances__surveys__status="completed")),
|
||||
complaint_count=Count("complaints"),
|
||||
action_count=Count("px_actions"),
|
||||
)
|
||||
.filter(survey_count__gt=0)
|
||||
.order_by("-avg_score")[:7]
|
||||
)
|
||||
|
||||
# ============ TIME-BASED CALCULATIONS ============
|
||||
# Average resolution time (complaints)
|
||||
resolved_with_time = complaints_queryset.filter(
|
||||
status__in=['resolved', 'closed'],
|
||||
resolved_at__isnull=False,
|
||||
created_at__isnull=False
|
||||
status__in=["resolved", "closed"], resolved_at__isnull=False, created_at__isnull=False
|
||||
)
|
||||
if resolved_with_time.exists():
|
||||
avg_resolution_hours = resolved_with_time.annotate(
|
||||
resolution_time=F('resolved_at') - F('created_at')
|
||||
).aggregate(avg=Avg('resolution_time'))['avg']
|
||||
resolution_time=F("resolved_at") - F("created_at")
|
||||
).aggregate(avg=Avg("resolution_time"))["avg"]
|
||||
if avg_resolution_hours:
|
||||
avg_resolution_hours = avg_resolution_hours.total_seconds() / 3600
|
||||
else:
|
||||
@ -206,14 +218,12 @@ def analytics_dashboard(request):
|
||||
|
||||
# Average action completion time
|
||||
closed_actions_with_time = actions_queryset.filter(
|
||||
status='closed',
|
||||
closed_at__isnull=False,
|
||||
created_at__isnull=False
|
||||
status="closed", closed_at__isnull=False, created_at__isnull=False
|
||||
)
|
||||
if closed_actions_with_time.exists():
|
||||
avg_action_days = closed_actions_with_time.annotate(
|
||||
completion_time=F('closed_at') - F('created_at')
|
||||
).aggregate(avg=Avg('completion_time'))['avg']
|
||||
avg_action_days = closed_actions_with_time.annotate(completion_time=F("closed_at") - F("created_at")).aggregate(
|
||||
avg=Avg("completion_time")
|
||||
)["avg"]
|
||||
if avg_action_days:
|
||||
avg_action_days = avg_action_days.days
|
||||
else:
|
||||
@ -224,17 +234,13 @@ def analytics_dashboard(request):
|
||||
# ============ SLA COMPLIANCE ============
|
||||
total_with_sla = complaints_queryset.filter(due_at__isnull=False).count()
|
||||
resolved_within_sla = complaints_queryset.filter(
|
||||
status__in=['resolved', 'closed'],
|
||||
resolved_at__lte=F('due_at')
|
||||
status__in=["resolved", "closed"], resolved_at__lte=F("due_at")
|
||||
).count()
|
||||
sla_compliance = (resolved_within_sla / total_with_sla * 100) if total_with_sla > 0 else 0
|
||||
|
||||
# ============ NPS CALCULATION ============
|
||||
# NPS = % Promoters (9-10) - % Detractors (0-6)
|
||||
nps_surveys = surveys_queryset.filter(
|
||||
survey_template__survey_type='nps',
|
||||
total_score__isnull=False
|
||||
)
|
||||
nps_surveys = surveys_queryset.filter(survey_template__survey_type="nps", total_score__isnull=False)
|
||||
if nps_surveys.exists():
|
||||
promoters = nps_surveys.filter(total_score__gte=9).count()
|
||||
detractors = nps_surveys.filter(total_score__lte=6).count()
|
||||
@ -243,76 +249,52 @@ def analytics_dashboard(request):
|
||||
else:
|
||||
nps_score = 0
|
||||
|
||||
# Get hospitals for filter
|
||||
hospitals = Hospital.objects.filter(status='active')
|
||||
if not user.is_px_admin() and user.hospital:
|
||||
hospitals = hospitals.filter(id=user.hospital.id)
|
||||
|
||||
# Build comprehensive KPI data
|
||||
kpis = {
|
||||
# Complaints
|
||||
'total_complaints': total_complaints,
|
||||
'open_complaints': open_complaints,
|
||||
'in_progress_complaints': in_progress_complaints,
|
||||
'resolved_complaints': resolved_complaints,
|
||||
'closed_complaints': closed_complaints,
|
||||
'overdue_complaints': overdue_complaints,
|
||||
'avg_resolution_hours': round(avg_resolution_hours, 1),
|
||||
'sla_compliance': round(sla_compliance, 1),
|
||||
|
||||
# Actions
|
||||
'total_actions': total_actions,
|
||||
'open_actions': open_actions,
|
||||
'in_progress_actions': in_progress_actions,
|
||||
'approved_actions': approved_actions,
|
||||
'closed_actions': closed_actions,
|
||||
'overdue_actions': overdue_actions,
|
||||
'avg_action_days': round(avg_action_days, 1),
|
||||
|
||||
# Surveys
|
||||
'total_surveys': total_surveys,
|
||||
'avg_survey_score': round(avg_survey_score, 2),
|
||||
'nps_score': round(nps_score, 1),
|
||||
'negative_surveys': negative_surveys,
|
||||
'completion_rate': round(completion_rate, 1),
|
||||
|
||||
# Feedback
|
||||
'total_feedback': total_feedback,
|
||||
'compliments': compliments,
|
||||
'suggestions': suggestions,
|
||||
'avg_rating': round(avg_rating, 2),
|
||||
"total_complaints": total_complaints,
|
||||
"open_complaints": open_complaints,
|
||||
"in_progress_complaints": in_progress_complaints,
|
||||
"resolved_complaints": resolved_complaints,
|
||||
"closed_complaints": closed_complaints,
|
||||
"overdue_complaints": overdue_complaints,
|
||||
"avg_resolution_hours": round(avg_resolution_hours, 1),
|
||||
"sla_compliance": round(sla_compliance, 1),
|
||||
"total_actions": total_actions,
|
||||
"open_actions": open_actions,
|
||||
"in_progress_actions": in_progress_actions,
|
||||
"approved_actions": approved_actions,
|
||||
"closed_actions": closed_actions,
|
||||
"overdue_actions": overdue_actions,
|
||||
"avg_action_days": round(avg_action_days, 1),
|
||||
"total_surveys": total_surveys,
|
||||
"avg_survey_score": round(avg_survey_score, 2),
|
||||
"nps_score": round(nps_score, 1),
|
||||
"negative_surveys": negative_surveys,
|
||||
"completion_rate": round(completion_rate, 1),
|
||||
"total_feedback": total_feedback,
|
||||
"compliments": compliments,
|
||||
"suggestions": suggestions,
|
||||
"avg_rating": round(avg_rating, 2),
|
||||
}
|
||||
|
||||
context = {
|
||||
'kpis': kpis,
|
||||
'hospitals': hospitals,
|
||||
'selected_hospital': hospital,
|
||||
|
||||
# Complaint analytics - serialize properly for JSON
|
||||
'complaint_sources': serialize_queryset_values(complaint_sources),
|
||||
'top_domains': serialize_queryset_values(top_domains),
|
||||
'top_categories': serialize_queryset_values(top_categories),
|
||||
'severity_breakdown': serialize_queryset_values(severity_breakdown),
|
||||
'status_breakdown': serialize_queryset_values(status_breakdown),
|
||||
'complaint_trend': serialize_queryset_values(complaint_trend),
|
||||
|
||||
# Action analytics
|
||||
'action_sources': serialize_queryset_values(action_sources),
|
||||
'action_categories': serialize_queryset_values(action_categories),
|
||||
|
||||
# Survey analytics
|
||||
'survey_types': serialize_queryset_values(survey_types),
|
||||
'survey_score_trend': serialize_queryset_values(survey_score_trend),
|
||||
|
||||
# Feedback analytics
|
||||
'sentiment_breakdown': serialize_queryset_values(sentiment_breakdown),
|
||||
'feedback_categories': serialize_queryset_values(feedback_categories),
|
||||
|
||||
# Department rankings
|
||||
'department_rankings': department_rankings,
|
||||
"kpis": kpis,
|
||||
"selected_hospital": hospital,
|
||||
"complaint_sources": serialize_queryset_values(complaint_sources),
|
||||
"top_domains": serialize_queryset_values(top_domains),
|
||||
"top_categories": serialize_queryset_values(top_categories),
|
||||
"severity_breakdown": serialize_queryset_values(severity_breakdown),
|
||||
"status_breakdown": serialize_queryset_values(status_breakdown),
|
||||
"complaint_trend": serialize_queryset_values(complaint_trend),
|
||||
"action_sources": serialize_queryset_values(action_sources),
|
||||
"action_categories": serialize_queryset_values(action_categories),
|
||||
"survey_types": serialize_queryset_values(survey_types),
|
||||
"survey_score_trend": serialize_queryset_values(survey_score_trend),
|
||||
"sentiment_breakdown": serialize_queryset_values(sentiment_breakdown),
|
||||
"feedback_categories": serialize_queryset_values(feedback_categories),
|
||||
"department_rankings": department_rankings,
|
||||
}
|
||||
|
||||
return render(request, 'analytics/dashboard.html', context)
|
||||
return render(request, "analytics/dashboard.html", context)
|
||||
|
||||
|
||||
@block_source_user
|
||||
@ -320,34 +302,34 @@ def analytics_dashboard(request):
|
||||
def kpi_list(request):
|
||||
"""KPI definitions list view"""
|
||||
queryset = KPI.objects.all()
|
||||
|
||||
|
||||
# Apply filters
|
||||
category_filter = request.GET.get('category')
|
||||
category_filter = request.GET.get("category")
|
||||
if category_filter:
|
||||
queryset = queryset.filter(category=category_filter)
|
||||
|
||||
is_active = request.GET.get('is_active')
|
||||
if is_active == 'true':
|
||||
|
||||
is_active = request.GET.get("is_active")
|
||||
if is_active == "true":
|
||||
queryset = queryset.filter(is_active=True)
|
||||
elif is_active == 'false':
|
||||
elif is_active == "false":
|
||||
queryset = queryset.filter(is_active=False)
|
||||
|
||||
|
||||
# Ordering
|
||||
queryset = queryset.order_by('category', 'name')
|
||||
|
||||
queryset = queryset.order_by("category", "name")
|
||||
|
||||
# Pagination
|
||||
page_size = int(request.GET.get('page_size', 25))
|
||||
page_size = int(request.GET.get("page_size", 25))
|
||||
paginator = Paginator(queryset, page_size)
|
||||
page_number = request.GET.get('page', 1)
|
||||
page_number = request.GET.get("page", 1)
|
||||
page_obj = paginator.get_page(page_number)
|
||||
|
||||
|
||||
context = {
|
||||
'page_obj': page_obj,
|
||||
'kpis': page_obj.object_list,
|
||||
'filters': request.GET,
|
||||
"page_obj": page_obj,
|
||||
"kpis": page_obj.object_list,
|
||||
"filters": request.GET,
|
||||
}
|
||||
|
||||
return render(request, 'analytics/kpi_list.html', context)
|
||||
|
||||
return render(request, "analytics/kpi_list.html", context)
|
||||
|
||||
|
||||
@block_source_user
|
||||
@ -355,7 +337,7 @@ def kpi_list(request):
|
||||
def command_center(request):
|
||||
"""
|
||||
PX Command Center - Unified Dashboard
|
||||
|
||||
|
||||
Comprehensive dashboard showing all PX360 metrics:
|
||||
- Complaints, Surveys, Actions KPIs
|
||||
- Interactive charts with ApexCharts
|
||||
@ -363,53 +345,52 @@ def command_center(request):
|
||||
- Export to Excel/PDF
|
||||
"""
|
||||
user = request.user
|
||||
|
||||
|
||||
# Get filter parameters
|
||||
filters = {
|
||||
'date_range': request.GET.get('date_range', '30d'),
|
||||
'hospital': request.GET.get('hospital', ''),
|
||||
'department': request.GET.get('department', ''),
|
||||
'kpi_category': request.GET.get('kpi_category', ''),
|
||||
'custom_start': request.GET.get('custom_start', ''),
|
||||
'custom_end': request.GET.get('custom_end', ''),
|
||||
"date_range": request.GET.get("date_range", "30d"),
|
||||
"hospital": request.GET.get("hospital", ""),
|
||||
"department": request.GET.get("department", ""),
|
||||
"kpi_category": request.GET.get("kpi_category", ""),
|
||||
"custom_start": request.GET.get("custom_start", ""),
|
||||
"custom_end": request.GET.get("custom_end", ""),
|
||||
}
|
||||
|
||||
|
||||
# Get hospitals for filter
|
||||
hospitals = Hospital.objects.filter(status='active')
|
||||
hospitals = Hospital.objects.filter(status="active")
|
||||
if not user.is_px_admin() and user.hospital:
|
||||
hospitals = hospitals.filter(id=user.hospital.id)
|
||||
|
||||
|
||||
# Get departments for filter
|
||||
departments = Department.objects.filter(status='active')
|
||||
if filters.get('hospital'):
|
||||
departments = departments.filter(hospital_id=filters['hospital'])
|
||||
departments = Department.objects.filter(status="active")
|
||||
if filters.get("hospital"):
|
||||
departments = departments.filter(hospital_id=filters["hospital"])
|
||||
elif not user.is_px_admin() and user.hospital:
|
||||
departments = departments.filter(hospital=user.hospital)
|
||||
|
||||
|
||||
# Get initial KPIs
|
||||
custom_start = None
|
||||
custom_end = None
|
||||
if filters['custom_start'] and filters['custom_end']:
|
||||
custom_start = datetime.strptime(filters['custom_start'], '%Y-%m-%d')
|
||||
custom_end = datetime.strptime(filters['custom_end'], '%Y-%m-%d')
|
||||
|
||||
if filters["custom_start"] and filters["custom_end"]:
|
||||
custom_start = datetime.strptime(filters["custom_start"], "%Y-%m-%d")
|
||||
custom_end = datetime.strptime(filters["custom_end"], "%Y-%m-%d")
|
||||
|
||||
kpis = UnifiedAnalyticsService.get_all_kpis(
|
||||
user=user,
|
||||
date_range=filters['date_range'],
|
||||
hospital_id=filters['hospital'] if filters['hospital'] else None,
|
||||
department_id=filters['department'] if filters['department'] else None,
|
||||
date_range=filters["date_range"],
|
||||
hospital_id=filters["hospital"] if filters["hospital"] else None,
|
||||
department_id=filters["department"] if filters["department"] else None,
|
||||
custom_start=custom_start,
|
||||
custom_end=custom_end
|
||||
custom_end=custom_end,
|
||||
)
|
||||
|
||||
|
||||
context = {
|
||||
'filters': filters,
|
||||
'hospitals': hospitals,
|
||||
'departments': departments,
|
||||
'kpis': kpis,
|
||||
"filters": filters,
|
||||
"departments": departments,
|
||||
"kpis": kpis,
|
||||
}
|
||||
|
||||
return render(request, 'analytics/command_center.html', context)
|
||||
|
||||
return render(request, "analytics/command_center.html", context)
|
||||
|
||||
|
||||
@block_source_user
|
||||
@ -417,39 +398,39 @@ def command_center(request):
|
||||
def command_center_api(request):
|
||||
"""
|
||||
API endpoint for Command Center data
|
||||
|
||||
|
||||
Returns JSON data for KPIs, charts, and tables based on filters.
|
||||
Used by JavaScript to dynamically update dashboard.
|
||||
"""
|
||||
if request.method != 'GET':
|
||||
return JsonResponse({'error': 'Only GET requests allowed'}, status=405)
|
||||
|
||||
if request.method != "GET":
|
||||
return JsonResponse({"error": "Only GET requests allowed"}, status=405)
|
||||
|
||||
user = request.user
|
||||
|
||||
|
||||
# Get filter parameters
|
||||
date_range = request.GET.get('date_range', '30d')
|
||||
hospital_id = request.GET.get('hospital')
|
||||
department_id = request.GET.get('department')
|
||||
kpi_category = request.GET.get('kpi_category')
|
||||
custom_start_str = request.GET.get('custom_start')
|
||||
custom_end_str = request.GET.get('custom_end')
|
||||
|
||||
date_range = request.GET.get("date_range", "30d")
|
||||
hospital_id = request.GET.get("hospital")
|
||||
department_id = request.GET.get("department")
|
||||
kpi_category = request.GET.get("kpi_category")
|
||||
custom_start_str = request.GET.get("custom_start")
|
||||
custom_end_str = request.GET.get("custom_end")
|
||||
|
||||
# Parse custom dates
|
||||
custom_start = None
|
||||
custom_end = None
|
||||
if custom_start_str and custom_end_str:
|
||||
try:
|
||||
custom_start = datetime.strptime(custom_start_str, '%Y-%m-%d')
|
||||
custom_end = datetime.strptime(custom_end_str, '%Y-%m-%d')
|
||||
custom_start = datetime.strptime(custom_start_str, "%Y-%m-%d")
|
||||
custom_end = datetime.strptime(custom_end_str, "%Y-%m-%d")
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
# Handle hospital_id (can be integer or UUID string)
|
||||
hospital_id = hospital_id if hospital_id else None
|
||||
|
||||
|
||||
# Handle department_id (UUID string)
|
||||
department_id = department_id if department_id else None
|
||||
|
||||
|
||||
# Get KPIs
|
||||
kpis = UnifiedAnalyticsService.get_all_kpis(
|
||||
user=user,
|
||||
@ -458,51 +439,62 @@ def command_center_api(request):
|
||||
department_id=department_id,
|
||||
kpi_category=kpi_category,
|
||||
custom_start=custom_start,
|
||||
custom_end=custom_end
|
||||
custom_end=custom_end,
|
||||
)
|
||||
|
||||
|
||||
# Ensure numeric KPIs are proper Python types for JSON serialization
|
||||
numeric_kpis = [
|
||||
'total_complaints', 'open_complaints', 'overdue_complaints',
|
||||
'high_severity_complaints', 'resolved_complaints',
|
||||
'total_actions', 'open_actions', 'overdue_actions', 'escalated_actions', 'resolved_actions',
|
||||
'total_surveys', 'negative_surveys', 'avg_survey_score',
|
||||
'negative_social_mentions', 'low_call_ratings', 'total_sentiment_analyses'
|
||||
"total_complaints",
|
||||
"open_complaints",
|
||||
"overdue_complaints",
|
||||
"high_severity_complaints",
|
||||
"resolved_complaints",
|
||||
"total_actions",
|
||||
"open_actions",
|
||||
"overdue_actions",
|
||||
"escalated_actions",
|
||||
"resolved_actions",
|
||||
"total_surveys",
|
||||
"negative_surveys",
|
||||
"avg_survey_score",
|
||||
"negative_social_mentions",
|
||||
"low_call_ratings",
|
||||
"total_sentiment_analyses",
|
||||
]
|
||||
|
||||
|
||||
for key in numeric_kpis:
|
||||
if key in kpis:
|
||||
value = kpis[key]
|
||||
if value is None:
|
||||
kpis[key] = 0.0 if key == 'avg_survey_score' else 0
|
||||
kpis[key] = 0.0 if key == "avg_survey_score" else 0
|
||||
elif isinstance(value, (int, float)):
|
||||
# Already a number - ensure floats for specific fields
|
||||
if key == 'avg_survey_score':
|
||||
if key == "avg_survey_score":
|
||||
kpis[key] = float(value)
|
||||
else:
|
||||
# Try to convert to number
|
||||
try:
|
||||
kpis[key] = float(value)
|
||||
except (ValueError, TypeError):
|
||||
kpis[key] = 0.0 if key == 'avg_survey_score' else 0
|
||||
|
||||
kpis[key] = 0.0 if key == "avg_survey_score" else 0
|
||||
|
||||
# Handle nested trend data
|
||||
if 'complaints_trend' in kpis and isinstance(kpis['complaints_trend'], dict):
|
||||
trend = kpis['complaints_trend']
|
||||
trend['current'] = int(trend.get('current', 0))
|
||||
trend['previous'] = int(trend.get('previous', 0))
|
||||
trend['percentage_change'] = float(trend.get('percentage_change', 0))
|
||||
|
||||
if "complaints_trend" in kpis and isinstance(kpis["complaints_trend"], dict):
|
||||
trend = kpis["complaints_trend"]
|
||||
trend["current"] = int(trend.get("current", 0))
|
||||
trend["previous"] = int(trend.get("previous", 0))
|
||||
trend["percentage_change"] = float(trend.get("percentage_change", 0))
|
||||
|
||||
# Get chart data
|
||||
chart_types = [
|
||||
'complaints_trend',
|
||||
'complaints_by_category',
|
||||
'survey_satisfaction_trend',
|
||||
'survey_distribution',
|
||||
'department_performance',
|
||||
'physician_leaderboard'
|
||||
"complaints_trend",
|
||||
"complaints_by_category",
|
||||
"survey_satisfaction_trend",
|
||||
"survey_distribution",
|
||||
"department_performance",
|
||||
"physician_leaderboard",
|
||||
]
|
||||
|
||||
|
||||
charts = {}
|
||||
for chart_type in chart_types:
|
||||
charts[chart_type] = UnifiedAnalyticsService.get_chart_data(
|
||||
@ -512,64 +504,60 @@ def command_center_api(request):
|
||||
hospital_id=hospital_id,
|
||||
department_id=department_id,
|
||||
custom_start=custom_start,
|
||||
custom_end=custom_end
|
||||
custom_end=custom_end,
|
||||
)
|
||||
|
||||
|
||||
# Get table data
|
||||
tables = {}
|
||||
|
||||
|
||||
# Overdue complaints table
|
||||
complaints_qs = Complaint.objects.filter(is_overdue=True)
|
||||
if hospital_id:
|
||||
complaints_qs = complaints_qs.filter(hospital_id=hospital_id)
|
||||
if department_id:
|
||||
complaints_qs = complaints_qs.filter(department_id=department_id)
|
||||
|
||||
|
||||
# Apply role-based filtering
|
||||
if not user.is_px_admin() and user.hospital:
|
||||
complaints_qs = complaints_qs.filter(hospital=user.hospital)
|
||||
if user.is_department_manager() and user.department:
|
||||
complaints_qs = complaints_qs.filter(department=user.department)
|
||||
|
||||
tables['overdue_complaints'] = list(
|
||||
complaints_qs.select_related('hospital', 'department', 'patient', 'source')
|
||||
.order_by('due_at')[:20]
|
||||
|
||||
tables["overdue_complaints"] = list(
|
||||
complaints_qs.select_related("hospital", "department", "patient", "source")
|
||||
.order_by("due_at")[:20]
|
||||
.values(
|
||||
'id',
|
||||
'title',
|
||||
'severity',
|
||||
'due_at',
|
||||
'complaint_source_type',
|
||||
hospital_name=F('hospital__name'),
|
||||
department_name=F('department__name'),
|
||||
patient_full_name=Concat('patient__first_name', Value(' '), 'patient__last_name'),
|
||||
source_name=F('source__name_en'),
|
||||
assigned_to_full_name=Concat('assigned_to__first_name', Value(' '), 'assigned_to__last_name')
|
||||
"id",
|
||||
"title",
|
||||
"severity",
|
||||
"due_at",
|
||||
"complaint_source_type",
|
||||
hospital_name=F("hospital__name"),
|
||||
department_name=F("department__name"),
|
||||
patient_full_name=Concat("patient__first_name", Value(" "), "patient__last_name"),
|
||||
source_name=F("source__name_en"),
|
||||
assigned_to_full_name=Concat("assigned_to__first_name", Value(" "), "assigned_to__last_name"),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# Physician leaderboard table
|
||||
physician_data = charts.get('physician_leaderboard', {}).get('metadata', [])
|
||||
tables['physician_leaderboard'] = [
|
||||
physician_data = charts.get("physician_leaderboard", {}).get("metadata", [])
|
||||
tables["physician_leaderboard"] = [
|
||||
{
|
||||
'physician_id': p['physician_id'],
|
||||
'name': p['name'],
|
||||
'specialization': p['specialization'],
|
||||
'department': p['department'],
|
||||
'rating': float(p['rating']) if p['rating'] is not None else 0.0,
|
||||
'surveys': int(p['surveys']) if p['surveys'] is not None else 0,
|
||||
'positive': int(p['positive']) if p['positive'] is not None else 0,
|
||||
'neutral': int(p['neutral']) if p['neutral'] is not None else 0,
|
||||
'negative': int(p['negative']) if p['negative'] is not None else 0
|
||||
"physician_id": p["physician_id"],
|
||||
"name": p["name"],
|
||||
"specialization": p["specialization"],
|
||||
"department": p["department"],
|
||||
"rating": float(p["rating"]) if p["rating"] is not None else 0.0,
|
||||
"surveys": int(p["surveys"]) if p["surveys"] is not None else 0,
|
||||
"positive": int(p["positive"]) if p["positive"] is not None else 0,
|
||||
"neutral": int(p["neutral"]) if p["neutral"] is not None else 0,
|
||||
"negative": int(p["negative"]) if p["negative"] is not None else 0,
|
||||
}
|
||||
for p in physician_data
|
||||
]
|
||||
|
||||
return JsonResponse({
|
||||
'kpis': kpis,
|
||||
'charts': charts,
|
||||
'tables': tables
|
||||
})
|
||||
|
||||
return JsonResponse({"kpis": kpis, "charts": charts, "tables": tables})
|
||||
|
||||
|
||||
@block_source_user
|
||||
@ -577,40 +565,40 @@ def command_center_api(request):
|
||||
def export_command_center(request, export_format):
|
||||
"""
|
||||
Export Command Center data to Excel or PDF
|
||||
|
||||
|
||||
Args:
|
||||
export_format: 'excel' or 'pdf'
|
||||
|
||||
|
||||
Returns:
|
||||
HttpResponse with file download
|
||||
"""
|
||||
if export_format not in ['excel', 'pdf']:
|
||||
return JsonResponse({'error': 'Invalid export format'}, status=400)
|
||||
|
||||
if export_format not in ["excel", "pdf"]:
|
||||
return JsonResponse({"error": "Invalid export format"}, status=400)
|
||||
|
||||
user = request.user
|
||||
|
||||
|
||||
# Get filter parameters
|
||||
date_range = request.GET.get('date_range', '30d')
|
||||
hospital_id = request.GET.get('hospital')
|
||||
department_id = request.GET.get('department')
|
||||
kpi_category = request.GET.get('kpi_category')
|
||||
custom_start_str = request.GET.get('custom_start')
|
||||
custom_end_str = request.GET.get('custom_end')
|
||||
|
||||
date_range = request.GET.get("date_range", "30d")
|
||||
hospital_id = request.GET.get("hospital")
|
||||
department_id = request.GET.get("department")
|
||||
kpi_category = request.GET.get("kpi_category")
|
||||
custom_start_str = request.GET.get("custom_start")
|
||||
custom_end_str = request.GET.get("custom_end")
|
||||
|
||||
# Parse custom dates
|
||||
custom_start = None
|
||||
custom_end = None
|
||||
if custom_start_str and custom_end_str:
|
||||
try:
|
||||
custom_start = datetime.strptime(custom_start_str, '%Y-%m-%d')
|
||||
custom_end = datetime.strptime(custom_end_str, '%Y-%m-%d')
|
||||
custom_start = datetime.strptime(custom_start_str, "%Y-%m-%d")
|
||||
custom_end = datetime.strptime(custom_end_str, "%Y-%m-%d")
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
# Handle hospital_id and department_id (can be integer or UUID string)
|
||||
hospital_id = hospital_id if hospital_id else None
|
||||
department_id = department_id if department_id else None
|
||||
|
||||
|
||||
# Get all data
|
||||
kpis = UnifiedAnalyticsService.get_all_kpis(
|
||||
user=user,
|
||||
@ -619,18 +607,18 @@ def export_command_center(request, export_format):
|
||||
department_id=department_id,
|
||||
kpi_category=kpi_category,
|
||||
custom_start=custom_start,
|
||||
custom_end=custom_end
|
||||
custom_end=custom_end,
|
||||
)
|
||||
|
||||
|
||||
chart_types = [
|
||||
'complaints_trend',
|
||||
'complaints_by_category',
|
||||
'survey_satisfaction_trend',
|
||||
'survey_distribution',
|
||||
'department_performance',
|
||||
'physician_leaderboard'
|
||||
"complaints_trend",
|
||||
"complaints_by_category",
|
||||
"survey_satisfaction_trend",
|
||||
"survey_distribution",
|
||||
"department_performance",
|
||||
"physician_leaderboard",
|
||||
]
|
||||
|
||||
|
||||
charts = {}
|
||||
for chart_type in chart_types:
|
||||
charts[chart_type] = UnifiedAnalyticsService.get_chart_data(
|
||||
@ -640,77 +628,64 @@ def export_command_center(request, export_format):
|
||||
hospital_id=hospital_id,
|
||||
department_id=department_id,
|
||||
custom_start=custom_start,
|
||||
custom_end=custom_end
|
||||
custom_end=custom_end,
|
||||
)
|
||||
|
||||
|
||||
# Get table data
|
||||
tables = {}
|
||||
|
||||
|
||||
# Overdue complaints
|
||||
complaints_qs = Complaint.objects.filter(is_overdue=True)
|
||||
if hospital_id:
|
||||
complaints_qs = complaints_qs.filter(hospital_id=hospital_id)
|
||||
if department_id:
|
||||
complaints_qs = complaints_qs.filter(department_id=department_id)
|
||||
|
||||
|
||||
if not user.is_px_admin() and user.hospital:
|
||||
complaints_qs = complaints_qs.filter(hospital=user.hospital)
|
||||
if user.is_department_manager() and user.department:
|
||||
complaints_qs = complaints_qs.filter(department=user.department)
|
||||
|
||||
tables['overdue_complaints'] = {
|
||||
'headers': ['ID', 'Title', 'Patient', 'Severity', 'Hospital', 'Department', 'Due Date'],
|
||||
'rows': list(
|
||||
complaints_qs.select_related('hospital', 'department', 'patient')
|
||||
.order_by('due_at')[:100]
|
||||
|
||||
tables["overdue_complaints"] = {
|
||||
"headers": ["ID", "Title", "Patient", "Severity", "Hospital", "Department", "Due Date"],
|
||||
"rows": list(
|
||||
complaints_qs.select_related("hospital", "department", "patient")
|
||||
.order_by("due_at")[:100]
|
||||
.annotate(
|
||||
patient_full_name=Concat('patient__first_name', Value(' '), 'patient__last_name'),
|
||||
hospital_name=F('hospital__name'),
|
||||
department_name=F('department__name')
|
||||
patient_full_name=Concat("patient__first_name", Value(" "), "patient__last_name"),
|
||||
hospital_name=F("hospital__name"),
|
||||
department_name=F("department__name"),
|
||||
)
|
||||
.values_list(
|
||||
'id',
|
||||
'title',
|
||||
'patient_full_name',
|
||||
'severity',
|
||||
'hospital_name',
|
||||
'department_name',
|
||||
'due_at'
|
||||
)
|
||||
)
|
||||
.values_list("id", "title", "patient_full_name", "severity", "hospital_name", "department_name", "due_at")
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
# Physician leaderboard
|
||||
physician_data = charts.get('physician_leaderboard', {}).get('metadata', [])
|
||||
tables['physician_leaderboard'] = {
|
||||
'headers': ['Name', 'Specialization', 'Department', 'Rating', 'Surveys', 'Positive', 'Neutral', 'Negative'],
|
||||
'rows': [
|
||||
physician_data = charts.get("physician_leaderboard", {}).get("metadata", [])
|
||||
tables["physician_leaderboard"] = {
|
||||
"headers": ["Name", "Specialization", "Department", "Rating", "Surveys", "Positive", "Neutral", "Negative"],
|
||||
"rows": [
|
||||
[
|
||||
p['name'],
|
||||
p['specialization'],
|
||||
p['department'],
|
||||
str(p['rating']),
|
||||
str(p['surveys']),
|
||||
str(p['positive']),
|
||||
str(p['neutral']),
|
||||
str(p['negative'])
|
||||
p["name"],
|
||||
p["specialization"],
|
||||
p["department"],
|
||||
str(p["rating"]),
|
||||
str(p["surveys"]),
|
||||
str(p["positive"]),
|
||||
str(p["neutral"]),
|
||||
str(p["negative"]),
|
||||
]
|
||||
for p in physician_data
|
||||
]
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
# Prepare export data
|
||||
export_data = ExportService.prepare_dashboard_data(
|
||||
user=user,
|
||||
kpis=kpis,
|
||||
charts=charts,
|
||||
tables=tables
|
||||
)
|
||||
|
||||
export_data = ExportService.prepare_dashboard_data(user=user, kpis=kpis, charts=charts, tables=tables)
|
||||
|
||||
# Export based on format
|
||||
if export_format == 'excel':
|
||||
if export_format == "excel":
|
||||
return ExportService.export_to_excel(export_data)
|
||||
elif export_format == 'pdf':
|
||||
elif export_format == "pdf":
|
||||
return ExportService.export_to_pdf(export_data)
|
||||
|
||||
return JsonResponse({'error': 'Export failed'}, status=500)
|
||||
|
||||
return JsonResponse({"error": "Export failed"}, status=500)
|
||||
|
||||
@ -140,10 +140,6 @@ def appreciation_list(request):
|
||||
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:
|
||||
@ -172,7 +168,6 @@ def appreciation_list(request):
|
||||
'page_obj': page_obj,
|
||||
'appreciations': page_obj.object_list,
|
||||
'stats': stats,
|
||||
'hospitals': hospitals,
|
||||
'departments': departments,
|
||||
'categories': categories,
|
||||
'status_choices': AppreciationStatus.choices,
|
||||
@ -334,7 +329,6 @@ def appreciation_send(request):
|
||||
categories = categories.filter(Q(hospital_id=request.user.hospital.id) | Q(hospital__isnull=True))
|
||||
|
||||
context = {
|
||||
'hospitals': hospitals,
|
||||
'categories': categories,
|
||||
'visibility_choices': AppreciationVisibility.choices,
|
||||
}
|
||||
@ -410,10 +404,6 @@ def leaderboard_view(request):
|
||||
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:
|
||||
@ -426,7 +416,6 @@ def leaderboard_view(request):
|
||||
context = {
|
||||
'page_obj': page_obj,
|
||||
'leaderboard': page_obj.object_list,
|
||||
'hospitals': hospitals,
|
||||
'departments': departments,
|
||||
'months': months,
|
||||
'years': years,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1083,7 +1083,6 @@ def admin_evaluation(request):
|
||||
).distinct().select_related('hospital', 'department')
|
||||
|
||||
context = {
|
||||
'hospitals': hospitals,
|
||||
'departments': departments,
|
||||
'staff_list': staff_queryset,
|
||||
'selected_hospital_id': hospital_id,
|
||||
|
||||
@ -148,10 +148,6 @@ def feedback_list(request):
|
||||
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:
|
||||
@ -179,7 +175,6 @@ def feedback_list(request):
|
||||
'page_obj': page_obj,
|
||||
'feedbacks': page_obj.object_list,
|
||||
'stats': stats,
|
||||
'hospitals': hospitals,
|
||||
'departments': departments,
|
||||
'assignable_users': assignable_users,
|
||||
'status_choices': FeedbackStatus.choices,
|
||||
|
||||
@ -105,10 +105,6 @@ def journey_instance_list(request):
|
||||
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:
|
||||
@ -125,7 +121,6 @@ def journey_instance_list(request):
|
||||
'page_obj': page_obj,
|
||||
'journeys': page_obj.object_list,
|
||||
'stats': stats,
|
||||
'hospitals': hospitals,
|
||||
'departments': departments,
|
||||
'filters': request.GET,
|
||||
}
|
||||
@ -221,15 +216,10 @@ def journey_template_list(request):
|
||||
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)
|
||||
|
||||
context = {
|
||||
'page_obj': page_obj,
|
||||
'templates': page_obj.object_list,
|
||||
'hospitals': hospitals,
|
||||
'filters': request.GET,
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
"""
|
||||
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
|
||||
@ -26,9 +27,11 @@ def physician_list(request):
|
||||
"""
|
||||
# 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()
|
||||
queryset = (
|
||||
Staff.objects.filter(Q(physician=True) | Q(staff_type=Staff.StaffType.PHYSICIAN))
|
||||
.select_related("hospital", "department")
|
||||
.distinct()
|
||||
)
|
||||
|
||||
# Apply RBAC filters
|
||||
user = request.user
|
||||
@ -40,50 +43,48 @@ def physician_list(request):
|
||||
queryset = queryset.none()
|
||||
|
||||
# Apply filters
|
||||
hospital_filter = request.GET.get('hospital')
|
||||
hospital_filter = request.GET.get("hospital")
|
||||
if hospital_filter:
|
||||
queryset = queryset.filter(hospital_id=hospital_filter)
|
||||
|
||||
department_filter = request.GET.get('department')
|
||||
department_filter = request.GET.get("department")
|
||||
if department_filter:
|
||||
queryset = queryset.filter(department_id=department_filter)
|
||||
|
||||
specialization_filter = request.GET.get('specialization')
|
||||
specialization_filter = request.GET.get("specialization")
|
||||
if specialization_filter:
|
||||
queryset = queryset.filter(specialization__icontains=specialization_filter)
|
||||
|
||||
status_filter = request.GET.get('status', 'active')
|
||||
status_filter = request.GET.get("status", "active")
|
||||
if status_filter:
|
||||
queryset = queryset.filter(status=status_filter)
|
||||
|
||||
# Search
|
||||
search_query = request.GET.get('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)
|
||||
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')
|
||||
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))
|
||||
page_size = int(request.GET.get("page_size", 25))
|
||||
paginator = Paginator(queryset, page_size)
|
||||
page_number = request.GET.get('page', 1)
|
||||
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')
|
||||
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}
|
||||
@ -92,217 +93,8 @@ def physician_list(request):
|
||||
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 (only from physicians)
|
||||
specializations = Staff.objects.filter(
|
||||
Q(physician=True) | Q(staff_type=Staff.StaffType.PHYSICIAN)
|
||||
).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 - 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)
|
||||
|
||||
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')
|
||||
departments = Department.objects.filter(status="active")
|
||||
if not user.is_px_admin() and user.hospital:
|
||||
departments = departments.filter(hospital=user.hospital)
|
||||
|
||||
@ -312,9 +104,7 @@ def leaderboard(request):
|
||||
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')
|
||||
total_physicians=Count("id"), average_rating=Avg("average_rating"), total_surveys=Count("total_surveys")
|
||||
)
|
||||
|
||||
# Distribution
|
||||
@ -324,22 +114,17 @@ def leaderboard(request):
|
||||
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
|
||||
}
|
||||
"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)
|
||||
return render(request, "physicians/leaderboard.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
@ -356,18 +141,11 @@ def physician_ratings_dashboard(request):
|
||||
- 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')
|
||||
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 filter options
|
||||
user = request.user
|
||||
hospitals = Hospital.objects.filter(status='active')
|
||||
departments = Department.objects.filter(status='active')
|
||||
|
||||
if not user.is_px_admin() and user.hospital:
|
||||
hospitals = hospitals.filter(id=user.hospital.id)
|
||||
departments = departments.filter(hospital=user.hospital)
|
||||
|
||||
# Get available years (2024 to current year)
|
||||
@ -376,13 +154,13 @@ def physician_ratings_dashboard(request):
|
||||
years.reverse() # Most recent first
|
||||
|
||||
context = {
|
||||
'years': years,
|
||||
'hospitals': hospitals,
|
||||
'departments': departments,
|
||||
'filters': request.GET,
|
||||
"years": years,
|
||||
"hospitals": hospitals,
|
||||
"departments": departments,
|
||||
"filters": request.GET,
|
||||
}
|
||||
|
||||
return render(request, 'physicians/physician_ratings_dashboard.html', context)
|
||||
return render(request, "physicians/physician_ratings_dashboard.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
@ -394,15 +172,15 @@ def physician_ratings_dashboard_api(request):
|
||||
"""
|
||||
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')
|
||||
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')
|
||||
queryset = PhysicianMonthlyRating.objects.filter(staff__physician=True).select_related(
|
||||
"staff", "staff__hospital", "staff__department"
|
||||
)
|
||||
|
||||
# Apply RBAC filters
|
||||
user = request.user
|
||||
@ -420,9 +198,9 @@ def physician_ratings_dashboard_api(request):
|
||||
|
||||
# 1. Statistics
|
||||
stats = current_period.aggregate(
|
||||
total_physicians=Count('id', distinct=True),
|
||||
average_rating=Avg('average_rating'),
|
||||
total_surveys=Sum('total_surveys')
|
||||
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()
|
||||
@ -437,15 +215,16 @@ def physician_ratings_dashboard_api(request):
|
||||
y -= 1
|
||||
|
||||
period_data = queryset.filter(year=y, month=m).aggregate(
|
||||
avg=Avg('average_rating'),
|
||||
surveys=Sum('total_surveys')
|
||||
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
|
||||
})
|
||||
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()
|
||||
@ -453,85 +232,83 @@ def physician_ratings_dashboard_api(request):
|
||||
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
|
||||
}
|
||||
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]
|
||||
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
|
||||
"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')
|
||||
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)
|
||||
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
|
||||
"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}
|
||||
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]
|
||||
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
|
||||
"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
|
||||
})
|
||||
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)
|
||||
|
||||
return JsonResponse({"error": str(e), "traceback": traceback.format_exc()}, status=500)
|
||||
|
||||
|
||||
@login_required
|
||||
@ -546,9 +323,9 @@ def ratings_list(request):
|
||||
- Pagination
|
||||
"""
|
||||
# Base queryset - only include staff marked as physicians
|
||||
queryset = PhysicianMonthlyRating.objects.filter(
|
||||
staff__physician=True
|
||||
).select_related('staff', 'staff__hospital', 'staff__department')
|
||||
queryset = PhysicianMonthlyRating.objects.filter(staff__physician=True).select_related(
|
||||
"staff", "staff__hospital", "staff__department"
|
||||
)
|
||||
|
||||
# Apply RBAC filters
|
||||
user = request.user
|
||||
@ -556,67 +333,63 @@ def ratings_list(request):
|
||||
queryset = queryset.filter(staff__hospital=user.hospital)
|
||||
|
||||
# Apply filters
|
||||
physician_filter = request.GET.get('physician')
|
||||
physician_filter = request.GET.get("physician")
|
||||
if physician_filter:
|
||||
queryset = queryset.filter(staff_id=physician_filter)
|
||||
|
||||
hospital_filter = request.GET.get('hospital')
|
||||
hospital_filter = request.GET.get("hospital")
|
||||
if hospital_filter:
|
||||
queryset = queryset.filter(staff__hospital_id=hospital_filter)
|
||||
|
||||
department_filter = request.GET.get('department')
|
||||
department_filter = request.GET.get("department")
|
||||
if department_filter:
|
||||
queryset = queryset.filter(staff__department_id=department_filter)
|
||||
|
||||
year_filter = request.GET.get('year')
|
||||
year_filter = request.GET.get("year")
|
||||
if year_filter:
|
||||
queryset = queryset.filter(year=int(year_filter))
|
||||
|
||||
month_filter = request.GET.get('month')
|
||||
month_filter = request.GET.get("month")
|
||||
if month_filter:
|
||||
queryset = queryset.filter(month=int(month_filter))
|
||||
|
||||
# Search
|
||||
search_query = request.GET.get('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)
|
||||
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(','))
|
||||
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))
|
||||
page_size = int(request.GET.get("page_size", 25))
|
||||
paginator = Paginator(queryset, page_size)
|
||||
page_number = request.GET.get('page', 1)
|
||||
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')
|
||||
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')
|
||||
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,
|
||||
"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)
|
||||
return render(request, "physicians/ratings_list.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
@ -632,16 +405,14 @@ def specialization_overview(request):
|
||||
"""
|
||||
# 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')
|
||||
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')
|
||||
queryset = PhysicianMonthlyRating.objects.filter(year=year, month=month, staff__physician=True).select_related(
|
||||
"staff", "staff__hospital", "staff__department"
|
||||
)
|
||||
|
||||
# Apply RBAC filters
|
||||
user = request.user
|
||||
@ -655,60 +426,57 @@ def specialization_overview(request):
|
||||
# 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": 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)
|
||||
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)
|
||||
})
|
||||
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)
|
||||
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,
|
||||
"specializations": specializations,
|
||||
"year": year,
|
||||
"month": month,
|
||||
"hospitals": hospitals,
|
||||
"filters": request.GET,
|
||||
}
|
||||
|
||||
return render(request, 'physicians/specialization_overview.html', context)
|
||||
return render(request, "physicians/specialization_overview.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
@ -724,16 +492,14 @@ def department_overview(request):
|
||||
"""
|
||||
# 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')
|
||||
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')
|
||||
queryset = PhysicianMonthlyRating.objects.filter(year=year, month=month, staff__physician=True).select_related(
|
||||
"staff", "staff__hospital", "staff__department"
|
||||
)
|
||||
|
||||
# Apply RBAC filters
|
||||
user = request.user
|
||||
@ -754,53 +520,51 @@ def department_overview(request):
|
||||
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": 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)
|
||||
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)
|
||||
})
|
||||
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)
|
||||
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,
|
||||
"departments": departments,
|
||||
"year": year,
|
||||
"month": month,
|
||||
"hospitals": hospitals,
|
||||
"filters": request.GET,
|
||||
}
|
||||
|
||||
return render(request, 'physicians/department_overview.html', context)
|
||||
return render(request, "physicians/department_overview.html", context)
|
||||
|
||||
@ -83,7 +83,6 @@ def project_list(request):
|
||||
context = {
|
||||
'page_obj': page_obj,
|
||||
'projects': page_obj.object_list,
|
||||
'hospitals': hospitals,
|
||||
'stats': stats,
|
||||
'filters': request.GET,
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user