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
|
Analytics Console UI views
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
@ -25,14 +26,14 @@ import json
|
|||||||
def serialize_queryset_values(queryset):
|
def serialize_queryset_values(queryset):
|
||||||
"""Properly serialize QuerySet values to JSON string."""
|
"""Properly serialize QuerySet values to JSON string."""
|
||||||
if queryset is None:
|
if queryset is None:
|
||||||
return '[]'
|
return "[]"
|
||||||
data = list(queryset)
|
data = list(queryset)
|
||||||
result = []
|
result = []
|
||||||
for item in data:
|
for item in data:
|
||||||
row = {}
|
row = {}
|
||||||
for key, value in item.items():
|
for key, value in item.items():
|
||||||
# Convert UUID to string
|
# Convert UUID to string
|
||||||
if hasattr(value, 'hex'): # UUID object
|
if hasattr(value, "hex"): # UUID object
|
||||||
row[key] = str(value)
|
row[key] = str(value)
|
||||||
# Convert Python None to JavaScript null
|
# Convert Python None to JavaScript null
|
||||||
elif value is None:
|
elif value is None:
|
||||||
@ -64,7 +65,7 @@ def analytics_dashboard(request):
|
|||||||
user = request.user
|
user = request.user
|
||||||
|
|
||||||
# Get hospital filter
|
# Get hospital filter
|
||||||
hospital_filter = request.GET.get('hospital')
|
hospital_filter = request.GET.get("hospital")
|
||||||
if hospital_filter:
|
if hospital_filter:
|
||||||
hospital = Hospital.objects.filter(id=hospital_filter).first()
|
hospital = Hospital.objects.filter(id=hospital_filter).first()
|
||||||
elif user.hospital:
|
elif user.hospital:
|
||||||
@ -75,7 +76,7 @@ def analytics_dashboard(request):
|
|||||||
# Base querysets
|
# Base querysets
|
||||||
complaints_queryset = Complaint.objects.all()
|
complaints_queryset = Complaint.objects.all()
|
||||||
actions_queryset = PXAction.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()
|
feedback_queryset = Feedback.objects.all()
|
||||||
|
|
||||||
if hospital:
|
if hospital:
|
||||||
@ -86,44 +87,56 @@ def analytics_dashboard(request):
|
|||||||
|
|
||||||
# ============ COMPLAINTS KPIs ============
|
# ============ COMPLAINTS KPIs ============
|
||||||
total_complaints = complaints_queryset.count()
|
total_complaints = complaints_queryset.count()
|
||||||
open_complaints = complaints_queryset.filter(status='open').count()
|
open_complaints = complaints_queryset.filter(status="open").count()
|
||||||
in_progress_complaints = complaints_queryset.filter(status='in_progress').count()
|
in_progress_complaints = complaints_queryset.filter(status="in_progress").count()
|
||||||
resolved_complaints = complaints_queryset.filter(status='resolved').count()
|
resolved_complaints = complaints_queryset.filter(status="resolved").count()
|
||||||
closed_complaints = complaints_queryset.filter(status='closed').count()
|
closed_complaints = complaints_queryset.filter(status="closed").count()
|
||||||
overdue_complaints = complaints_queryset.filter(is_overdue=True).count()
|
overdue_complaints = complaints_queryset.filter(is_overdue=True).count()
|
||||||
|
|
||||||
# Complaint sources
|
# 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)
|
# 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)
|
# 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
|
# 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
|
||||||
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 ============
|
# ============ ACTIONS KPIs ============
|
||||||
total_actions = actions_queryset.count()
|
total_actions = actions_queryset.count()
|
||||||
open_actions = actions_queryset.filter(status='open').count()
|
open_actions = actions_queryset.filter(status="open").count()
|
||||||
in_progress_actions = actions_queryset.filter(status='in_progress').count()
|
in_progress_actions = actions_queryset.filter(status="in_progress").count()
|
||||||
approved_actions = actions_queryset.filter(status='approved').count()
|
approved_actions = actions_queryset.filter(status="approved").count()
|
||||||
closed_actions = actions_queryset.filter(status='closed').count()
|
closed_actions = actions_queryset.filter(status="closed").count()
|
||||||
overdue_actions = actions_queryset.filter(is_overdue=True).count()
|
overdue_actions = actions_queryset.filter(is_overdue=True).count()
|
||||||
|
|
||||||
# Action sources
|
# 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
|
||||||
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 ============
|
# ============ SURVEYS KPIs ============
|
||||||
total_surveys = surveys_queryset.count()
|
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()
|
negative_surveys = surveys_queryset.filter(is_negative=True).count()
|
||||||
|
|
||||||
# Survey completion rate
|
# Survey completion rate
|
||||||
@ -131,72 +144,71 @@ def analytics_dashboard(request):
|
|||||||
if hospital:
|
if hospital:
|
||||||
all_surveys = all_surveys.filter(survey_template__hospital=hospital)
|
all_surveys = all_surveys.filter(survey_template__hospital=hospital)
|
||||||
total_sent = all_surveys.count()
|
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
|
completion_rate = (completed_surveys / total_sent * 100) if total_sent > 0 else 0
|
||||||
|
|
||||||
# Survey types
|
# 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 ============
|
# ============ FEEDBACK KPIs ============
|
||||||
total_feedback = feedback_queryset.count()
|
total_feedback = feedback_queryset.count()
|
||||||
compliments = feedback_queryset.filter(feedback_type='compliment').count()
|
compliments = feedback_queryset.filter(feedback_type="compliment").count()
|
||||||
suggestions = feedback_queryset.filter(feedback_type='suggestion').count()
|
suggestions = feedback_queryset.filter(feedback_type="suggestion").count()
|
||||||
|
|
||||||
# Sentiment analysis
|
# 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_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
|
# 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) ============
|
# ============ TRENDS (Last 30 days) ============
|
||||||
thirty_days_ago = timezone.now() - timedelta(days=30)
|
thirty_days_ago = timezone.now() - timedelta(days=30)
|
||||||
|
|
||||||
# Complaint trends
|
# Complaint trends
|
||||||
complaint_trend = complaints_queryset.filter(
|
complaint_trend = (
|
||||||
created_at__gte=thirty_days_ago
|
complaints_queryset.filter(created_at__gte=thirty_days_ago)
|
||||||
).annotate(
|
.annotate(day=TruncDate("created_at"))
|
||||||
day=TruncDate('created_at')
|
.values("day")
|
||||||
).values('day').annotate(count=Count('id')).order_by('day')
|
.annotate(count=Count("id"))
|
||||||
|
.order_by("day")
|
||||||
|
)
|
||||||
|
|
||||||
# Survey score trend
|
# Survey score trend
|
||||||
survey_score_trend = surveys_queryset.filter(
|
survey_score_trend = (
|
||||||
completed_at__gte=thirty_days_ago
|
surveys_queryset.filter(completed_at__gte=thirty_days_ago)
|
||||||
).annotate(
|
.annotate(day=TruncDate("completed_at"))
|
||||||
day=TruncDate('completed_at')
|
.values("day")
|
||||||
).values('day').annotate(avg_score=Avg('total_score')).order_by('day')
|
.annotate(avg_score=Avg("total_score"))
|
||||||
|
.order_by("day")
|
||||||
|
)
|
||||||
|
|
||||||
# ============ DEPARTMENT RANKINGS ============
|
# ============ DEPARTMENT RANKINGS ============
|
||||||
department_rankings = Department.objects.filter(
|
department_rankings = (
|
||||||
status='active'
|
Department.objects.filter(status="active")
|
||||||
).annotate(
|
.annotate(
|
||||||
avg_score=Avg(
|
avg_score=Avg(
|
||||||
'journey_instances__surveys__total_score',
|
"journey_instances__surveys__total_score", filter=Q(journey_instances__surveys__status="completed")
|
||||||
filter=Q(journey_instances__surveys__status='completed')
|
|
||||||
),
|
),
|
||||||
survey_count=Count(
|
survey_count=Count("journey_instances__surveys", filter=Q(journey_instances__surveys__status="completed")),
|
||||||
'journey_instances__surveys',
|
complaint_count=Count("complaints"),
|
||||||
filter=Q(journey_instances__surveys__status='completed')
|
action_count=Count("px_actions"),
|
||||||
),
|
)
|
||||||
complaint_count=Count('complaints'),
|
.filter(survey_count__gt=0)
|
||||||
action_count=Count('px_actions')
|
.order_by("-avg_score")[:7]
|
||||||
).filter(
|
)
|
||||||
survey_count__gt=0
|
|
||||||
).order_by('-avg_score')[:7]
|
|
||||||
|
|
||||||
# ============ TIME-BASED CALCULATIONS ============
|
# ============ TIME-BASED CALCULATIONS ============
|
||||||
# Average resolution time (complaints)
|
# Average resolution time (complaints)
|
||||||
resolved_with_time = complaints_queryset.filter(
|
resolved_with_time = complaints_queryset.filter(
|
||||||
status__in=['resolved', 'closed'],
|
status__in=["resolved", "closed"], resolved_at__isnull=False, created_at__isnull=False
|
||||||
resolved_at__isnull=False,
|
|
||||||
created_at__isnull=False
|
|
||||||
)
|
)
|
||||||
if resolved_with_time.exists():
|
if resolved_with_time.exists():
|
||||||
avg_resolution_hours = resolved_with_time.annotate(
|
avg_resolution_hours = resolved_with_time.annotate(
|
||||||
resolution_time=F('resolved_at') - F('created_at')
|
resolution_time=F("resolved_at") - F("created_at")
|
||||||
).aggregate(avg=Avg('resolution_time'))['avg']
|
).aggregate(avg=Avg("resolution_time"))["avg"]
|
||||||
if avg_resolution_hours:
|
if avg_resolution_hours:
|
||||||
avg_resolution_hours = avg_resolution_hours.total_seconds() / 3600
|
avg_resolution_hours = avg_resolution_hours.total_seconds() / 3600
|
||||||
else:
|
else:
|
||||||
@ -206,14 +218,12 @@ def analytics_dashboard(request):
|
|||||||
|
|
||||||
# Average action completion time
|
# Average action completion time
|
||||||
closed_actions_with_time = actions_queryset.filter(
|
closed_actions_with_time = actions_queryset.filter(
|
||||||
status='closed',
|
status="closed", closed_at__isnull=False, created_at__isnull=False
|
||||||
closed_at__isnull=False,
|
|
||||||
created_at__isnull=False
|
|
||||||
)
|
)
|
||||||
if closed_actions_with_time.exists():
|
if closed_actions_with_time.exists():
|
||||||
avg_action_days = closed_actions_with_time.annotate(
|
avg_action_days = closed_actions_with_time.annotate(completion_time=F("closed_at") - F("created_at")).aggregate(
|
||||||
completion_time=F('closed_at') - F('created_at')
|
avg=Avg("completion_time")
|
||||||
).aggregate(avg=Avg('completion_time'))['avg']
|
)["avg"]
|
||||||
if avg_action_days:
|
if avg_action_days:
|
||||||
avg_action_days = avg_action_days.days
|
avg_action_days = avg_action_days.days
|
||||||
else:
|
else:
|
||||||
@ -224,17 +234,13 @@ def analytics_dashboard(request):
|
|||||||
# ============ SLA COMPLIANCE ============
|
# ============ SLA COMPLIANCE ============
|
||||||
total_with_sla = complaints_queryset.filter(due_at__isnull=False).count()
|
total_with_sla = complaints_queryset.filter(due_at__isnull=False).count()
|
||||||
resolved_within_sla = complaints_queryset.filter(
|
resolved_within_sla = complaints_queryset.filter(
|
||||||
status__in=['resolved', 'closed'],
|
status__in=["resolved", "closed"], resolved_at__lte=F("due_at")
|
||||||
resolved_at__lte=F('due_at')
|
|
||||||
).count()
|
).count()
|
||||||
sla_compliance = (resolved_within_sla / total_with_sla * 100) if total_with_sla > 0 else 0
|
sla_compliance = (resolved_within_sla / total_with_sla * 100) if total_with_sla > 0 else 0
|
||||||
|
|
||||||
# ============ NPS CALCULATION ============
|
# ============ NPS CALCULATION ============
|
||||||
# NPS = % Promoters (9-10) - % Detractors (0-6)
|
# NPS = % Promoters (9-10) - % Detractors (0-6)
|
||||||
nps_surveys = surveys_queryset.filter(
|
nps_surveys = surveys_queryset.filter(survey_template__survey_type="nps", total_score__isnull=False)
|
||||||
survey_template__survey_type='nps',
|
|
||||||
total_score__isnull=False
|
|
||||||
)
|
|
||||||
if nps_surveys.exists():
|
if nps_surveys.exists():
|
||||||
promoters = nps_surveys.filter(total_score__gte=9).count()
|
promoters = nps_surveys.filter(total_score__gte=9).count()
|
||||||
detractors = nps_surveys.filter(total_score__lte=6).count()
|
detractors = nps_surveys.filter(total_score__lte=6).count()
|
||||||
@ -243,76 +249,52 @@ def analytics_dashboard(request):
|
|||||||
else:
|
else:
|
||||||
nps_score = 0
|
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 = {
|
kpis = {
|
||||||
# Complaints
|
"total_complaints": total_complaints,
|
||||||
'total_complaints': total_complaints,
|
"open_complaints": open_complaints,
|
||||||
'open_complaints': open_complaints,
|
"in_progress_complaints": in_progress_complaints,
|
||||||
'in_progress_complaints': in_progress_complaints,
|
"resolved_complaints": resolved_complaints,
|
||||||
'resolved_complaints': resolved_complaints,
|
"closed_complaints": closed_complaints,
|
||||||
'closed_complaints': closed_complaints,
|
"overdue_complaints": overdue_complaints,
|
||||||
'overdue_complaints': overdue_complaints,
|
"avg_resolution_hours": round(avg_resolution_hours, 1),
|
||||||
'avg_resolution_hours': round(avg_resolution_hours, 1),
|
"sla_compliance": round(sla_compliance, 1),
|
||||||
'sla_compliance': round(sla_compliance, 1),
|
"total_actions": total_actions,
|
||||||
|
"open_actions": open_actions,
|
||||||
# Actions
|
"in_progress_actions": in_progress_actions,
|
||||||
'total_actions': total_actions,
|
"approved_actions": approved_actions,
|
||||||
'open_actions': open_actions,
|
"closed_actions": closed_actions,
|
||||||
'in_progress_actions': in_progress_actions,
|
"overdue_actions": overdue_actions,
|
||||||
'approved_actions': approved_actions,
|
"avg_action_days": round(avg_action_days, 1),
|
||||||
'closed_actions': closed_actions,
|
"total_surveys": total_surveys,
|
||||||
'overdue_actions': overdue_actions,
|
"avg_survey_score": round(avg_survey_score, 2),
|
||||||
'avg_action_days': round(avg_action_days, 1),
|
"nps_score": round(nps_score, 1),
|
||||||
|
"negative_surveys": negative_surveys,
|
||||||
# Surveys
|
"completion_rate": round(completion_rate, 1),
|
||||||
'total_surveys': total_surveys,
|
"total_feedback": total_feedback,
|
||||||
'avg_survey_score': round(avg_survey_score, 2),
|
"compliments": compliments,
|
||||||
'nps_score': round(nps_score, 1),
|
"suggestions": suggestions,
|
||||||
'negative_surveys': negative_surveys,
|
"avg_rating": round(avg_rating, 2),
|
||||||
'completion_rate': round(completion_rate, 1),
|
|
||||||
|
|
||||||
# Feedback
|
|
||||||
'total_feedback': total_feedback,
|
|
||||||
'compliments': compliments,
|
|
||||||
'suggestions': suggestions,
|
|
||||||
'avg_rating': round(avg_rating, 2),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'kpis': kpis,
|
"kpis": kpis,
|
||||||
'hospitals': hospitals,
|
"selected_hospital": hospital,
|
||||||
'selected_hospital': hospital,
|
"complaint_sources": serialize_queryset_values(complaint_sources),
|
||||||
|
"top_domains": serialize_queryset_values(top_domains),
|
||||||
# Complaint analytics - serialize properly for JSON
|
"top_categories": serialize_queryset_values(top_categories),
|
||||||
'complaint_sources': serialize_queryset_values(complaint_sources),
|
"severity_breakdown": serialize_queryset_values(severity_breakdown),
|
||||||
'top_domains': serialize_queryset_values(top_domains),
|
"status_breakdown": serialize_queryset_values(status_breakdown),
|
||||||
'top_categories': serialize_queryset_values(top_categories),
|
"complaint_trend": serialize_queryset_values(complaint_trend),
|
||||||
'severity_breakdown': serialize_queryset_values(severity_breakdown),
|
"action_sources": serialize_queryset_values(action_sources),
|
||||||
'status_breakdown': serialize_queryset_values(status_breakdown),
|
"action_categories": serialize_queryset_values(action_categories),
|
||||||
'complaint_trend': serialize_queryset_values(complaint_trend),
|
"survey_types": serialize_queryset_values(survey_types),
|
||||||
|
"survey_score_trend": serialize_queryset_values(survey_score_trend),
|
||||||
# Action analytics
|
"sentiment_breakdown": serialize_queryset_values(sentiment_breakdown),
|
||||||
'action_sources': serialize_queryset_values(action_sources),
|
"feedback_categories": serialize_queryset_values(feedback_categories),
|
||||||
'action_categories': serialize_queryset_values(action_categories),
|
"department_rankings": department_rankings,
|
||||||
|
|
||||||
# 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,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, 'analytics/dashboard.html', context)
|
return render(request, "analytics/dashboard.html", context)
|
||||||
|
|
||||||
|
|
||||||
@block_source_user
|
@block_source_user
|
||||||
@ -322,32 +304,32 @@ def kpi_list(request):
|
|||||||
queryset = KPI.objects.all()
|
queryset = KPI.objects.all()
|
||||||
|
|
||||||
# Apply filters
|
# Apply filters
|
||||||
category_filter = request.GET.get('category')
|
category_filter = request.GET.get("category")
|
||||||
if category_filter:
|
if category_filter:
|
||||||
queryset = queryset.filter(category=category_filter)
|
queryset = queryset.filter(category=category_filter)
|
||||||
|
|
||||||
is_active = request.GET.get('is_active')
|
is_active = request.GET.get("is_active")
|
||||||
if is_active == 'true':
|
if is_active == "true":
|
||||||
queryset = queryset.filter(is_active=True)
|
queryset = queryset.filter(is_active=True)
|
||||||
elif is_active == 'false':
|
elif is_active == "false":
|
||||||
queryset = queryset.filter(is_active=False)
|
queryset = queryset.filter(is_active=False)
|
||||||
|
|
||||||
# Ordering
|
# Ordering
|
||||||
queryset = queryset.order_by('category', 'name')
|
queryset = queryset.order_by("category", "name")
|
||||||
|
|
||||||
# Pagination
|
# Pagination
|
||||||
page_size = int(request.GET.get('page_size', 25))
|
page_size = int(request.GET.get("page_size", 25))
|
||||||
paginator = Paginator(queryset, page_size)
|
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)
|
page_obj = paginator.get_page(page_number)
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'page_obj': page_obj,
|
"page_obj": page_obj,
|
||||||
'kpis': page_obj.object_list,
|
"kpis": page_obj.object_list,
|
||||||
'filters': request.GET,
|
"filters": request.GET,
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, 'analytics/kpi_list.html', context)
|
return render(request, "analytics/kpi_list.html", context)
|
||||||
|
|
||||||
|
|
||||||
@block_source_user
|
@block_source_user
|
||||||
@ -366,50 +348,49 @@ def command_center(request):
|
|||||||
|
|
||||||
# Get filter parameters
|
# Get filter parameters
|
||||||
filters = {
|
filters = {
|
||||||
'date_range': request.GET.get('date_range', '30d'),
|
"date_range": request.GET.get("date_range", "30d"),
|
||||||
'hospital': request.GET.get('hospital', ''),
|
"hospital": request.GET.get("hospital", ""),
|
||||||
'department': request.GET.get('department', ''),
|
"department": request.GET.get("department", ""),
|
||||||
'kpi_category': request.GET.get('kpi_category', ''),
|
"kpi_category": request.GET.get("kpi_category", ""),
|
||||||
'custom_start': request.GET.get('custom_start', ''),
|
"custom_start": request.GET.get("custom_start", ""),
|
||||||
'custom_end': request.GET.get('custom_end', ''),
|
"custom_end": request.GET.get("custom_end", ""),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get hospitals for filter
|
# 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:
|
if not user.is_px_admin() and user.hospital:
|
||||||
hospitals = hospitals.filter(id=user.hospital.id)
|
hospitals = hospitals.filter(id=user.hospital.id)
|
||||||
|
|
||||||
# Get departments for filter
|
# Get departments for filter
|
||||||
departments = Department.objects.filter(status='active')
|
departments = Department.objects.filter(status="active")
|
||||||
if filters.get('hospital'):
|
if filters.get("hospital"):
|
||||||
departments = departments.filter(hospital_id=filters['hospital'])
|
departments = departments.filter(hospital_id=filters["hospital"])
|
||||||
elif not user.is_px_admin() and user.hospital:
|
elif not user.is_px_admin() and user.hospital:
|
||||||
departments = departments.filter(hospital=user.hospital)
|
departments = departments.filter(hospital=user.hospital)
|
||||||
|
|
||||||
# Get initial KPIs
|
# Get initial KPIs
|
||||||
custom_start = None
|
custom_start = None
|
||||||
custom_end = None
|
custom_end = None
|
||||||
if filters['custom_start'] and filters['custom_end']:
|
if filters["custom_start"] and filters["custom_end"]:
|
||||||
custom_start = datetime.strptime(filters['custom_start'], '%Y-%m-%d')
|
custom_start = datetime.strptime(filters["custom_start"], "%Y-%m-%d")
|
||||||
custom_end = datetime.strptime(filters['custom_end'], '%Y-%m-%d')
|
custom_end = datetime.strptime(filters["custom_end"], "%Y-%m-%d")
|
||||||
|
|
||||||
kpis = UnifiedAnalyticsService.get_all_kpis(
|
kpis = UnifiedAnalyticsService.get_all_kpis(
|
||||||
user=user,
|
user=user,
|
||||||
date_range=filters['date_range'],
|
date_range=filters["date_range"],
|
||||||
hospital_id=filters['hospital'] if filters['hospital'] else None,
|
hospital_id=filters["hospital"] if filters["hospital"] else None,
|
||||||
department_id=filters['department'] if filters['department'] else None,
|
department_id=filters["department"] if filters["department"] else None,
|
||||||
custom_start=custom_start,
|
custom_start=custom_start,
|
||||||
custom_end=custom_end
|
custom_end=custom_end,
|
||||||
)
|
)
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'filters': filters,
|
"filters": filters,
|
||||||
'hospitals': hospitals,
|
"departments": departments,
|
||||||
'departments': departments,
|
"kpis": kpis,
|
||||||
'kpis': kpis,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, 'analytics/command_center.html', context)
|
return render(request, "analytics/command_center.html", context)
|
||||||
|
|
||||||
|
|
||||||
@block_source_user
|
@block_source_user
|
||||||
@ -421,26 +402,26 @@ def command_center_api(request):
|
|||||||
Returns JSON data for KPIs, charts, and tables based on filters.
|
Returns JSON data for KPIs, charts, and tables based on filters.
|
||||||
Used by JavaScript to dynamically update dashboard.
|
Used by JavaScript to dynamically update dashboard.
|
||||||
"""
|
"""
|
||||||
if request.method != 'GET':
|
if request.method != "GET":
|
||||||
return JsonResponse({'error': 'Only GET requests allowed'}, status=405)
|
return JsonResponse({"error": "Only GET requests allowed"}, status=405)
|
||||||
|
|
||||||
user = request.user
|
user = request.user
|
||||||
|
|
||||||
# Get filter parameters
|
# Get filter parameters
|
||||||
date_range = request.GET.get('date_range', '30d')
|
date_range = request.GET.get("date_range", "30d")
|
||||||
hospital_id = request.GET.get('hospital')
|
hospital_id = request.GET.get("hospital")
|
||||||
department_id = request.GET.get('department')
|
department_id = request.GET.get("department")
|
||||||
kpi_category = request.GET.get('kpi_category')
|
kpi_category = request.GET.get("kpi_category")
|
||||||
custom_start_str = request.GET.get('custom_start')
|
custom_start_str = request.GET.get("custom_start")
|
||||||
custom_end_str = request.GET.get('custom_end')
|
custom_end_str = request.GET.get("custom_end")
|
||||||
|
|
||||||
# Parse custom dates
|
# Parse custom dates
|
||||||
custom_start = None
|
custom_start = None
|
||||||
custom_end = None
|
custom_end = None
|
||||||
if custom_start_str and custom_end_str:
|
if custom_start_str and custom_end_str:
|
||||||
try:
|
try:
|
||||||
custom_start = datetime.strptime(custom_start_str, '%Y-%m-%d')
|
custom_start = datetime.strptime(custom_start_str, "%Y-%m-%d")
|
||||||
custom_end = datetime.strptime(custom_end_str, '%Y-%m-%d')
|
custom_end = datetime.strptime(custom_end_str, "%Y-%m-%d")
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -458,49 +439,60 @@ def command_center_api(request):
|
|||||||
department_id=department_id,
|
department_id=department_id,
|
||||||
kpi_category=kpi_category,
|
kpi_category=kpi_category,
|
||||||
custom_start=custom_start,
|
custom_start=custom_start,
|
||||||
custom_end=custom_end
|
custom_end=custom_end,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Ensure numeric KPIs are proper Python types for JSON serialization
|
# Ensure numeric KPIs are proper Python types for JSON serialization
|
||||||
numeric_kpis = [
|
numeric_kpis = [
|
||||||
'total_complaints', 'open_complaints', 'overdue_complaints',
|
"total_complaints",
|
||||||
'high_severity_complaints', 'resolved_complaints',
|
"open_complaints",
|
||||||
'total_actions', 'open_actions', 'overdue_actions', 'escalated_actions', 'resolved_actions',
|
"overdue_complaints",
|
||||||
'total_surveys', 'negative_surveys', 'avg_survey_score',
|
"high_severity_complaints",
|
||||||
'negative_social_mentions', 'low_call_ratings', 'total_sentiment_analyses'
|
"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:
|
for key in numeric_kpis:
|
||||||
if key in kpis:
|
if key in kpis:
|
||||||
value = kpis[key]
|
value = kpis[key]
|
||||||
if value is None:
|
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)):
|
elif isinstance(value, (int, float)):
|
||||||
# Already a number - ensure floats for specific fields
|
# Already a number - ensure floats for specific fields
|
||||||
if key == 'avg_survey_score':
|
if key == "avg_survey_score":
|
||||||
kpis[key] = float(value)
|
kpis[key] = float(value)
|
||||||
else:
|
else:
|
||||||
# Try to convert to number
|
# Try to convert to number
|
||||||
try:
|
try:
|
||||||
kpis[key] = float(value)
|
kpis[key] = float(value)
|
||||||
except (ValueError, TypeError):
|
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
|
# Handle nested trend data
|
||||||
if 'complaints_trend' in kpis and isinstance(kpis['complaints_trend'], dict):
|
if "complaints_trend" in kpis and isinstance(kpis["complaints_trend"], dict):
|
||||||
trend = kpis['complaints_trend']
|
trend = kpis["complaints_trend"]
|
||||||
trend['current'] = int(trend.get('current', 0))
|
trend["current"] = int(trend.get("current", 0))
|
||||||
trend['previous'] = int(trend.get('previous', 0))
|
trend["previous"] = int(trend.get("previous", 0))
|
||||||
trend['percentage_change'] = float(trend.get('percentage_change', 0))
|
trend["percentage_change"] = float(trend.get("percentage_change", 0))
|
||||||
|
|
||||||
# Get chart data
|
# Get chart data
|
||||||
chart_types = [
|
chart_types = [
|
||||||
'complaints_trend',
|
"complaints_trend",
|
||||||
'complaints_by_category',
|
"complaints_by_category",
|
||||||
'survey_satisfaction_trend',
|
"survey_satisfaction_trend",
|
||||||
'survey_distribution',
|
"survey_distribution",
|
||||||
'department_performance',
|
"department_performance",
|
||||||
'physician_leaderboard'
|
"physician_leaderboard",
|
||||||
]
|
]
|
||||||
|
|
||||||
charts = {}
|
charts = {}
|
||||||
@ -512,7 +504,7 @@ def command_center_api(request):
|
|||||||
hospital_id=hospital_id,
|
hospital_id=hospital_id,
|
||||||
department_id=department_id,
|
department_id=department_id,
|
||||||
custom_start=custom_start,
|
custom_start=custom_start,
|
||||||
custom_end=custom_end
|
custom_end=custom_end,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get table data
|
# Get table data
|
||||||
@ -531,45 +523,41 @@ def command_center_api(request):
|
|||||||
if user.is_department_manager() and user.department:
|
if user.is_department_manager() and user.department:
|
||||||
complaints_qs = complaints_qs.filter(department=user.department)
|
complaints_qs = complaints_qs.filter(department=user.department)
|
||||||
|
|
||||||
tables['overdue_complaints'] = list(
|
tables["overdue_complaints"] = list(
|
||||||
complaints_qs.select_related('hospital', 'department', 'patient', 'source')
|
complaints_qs.select_related("hospital", "department", "patient", "source")
|
||||||
.order_by('due_at')[:20]
|
.order_by("due_at")[:20]
|
||||||
.values(
|
.values(
|
||||||
'id',
|
"id",
|
||||||
'title',
|
"title",
|
||||||
'severity',
|
"severity",
|
||||||
'due_at',
|
"due_at",
|
||||||
'complaint_source_type',
|
"complaint_source_type",
|
||||||
hospital_name=F('hospital__name'),
|
hospital_name=F("hospital__name"),
|
||||||
department_name=F('department__name'),
|
department_name=F("department__name"),
|
||||||
patient_full_name=Concat('patient__first_name', Value(' '), 'patient__last_name'),
|
patient_full_name=Concat("patient__first_name", Value(" "), "patient__last_name"),
|
||||||
source_name=F('source__name_en'),
|
source_name=F("source__name_en"),
|
||||||
assigned_to_full_name=Concat('assigned_to__first_name', Value(' '), 'assigned_to__last_name')
|
assigned_to_full_name=Concat("assigned_to__first_name", Value(" "), "assigned_to__last_name"),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Physician leaderboard table
|
# Physician leaderboard table
|
||||||
physician_data = charts.get('physician_leaderboard', {}).get('metadata', [])
|
physician_data = charts.get("physician_leaderboard", {}).get("metadata", [])
|
||||||
tables['physician_leaderboard'] = [
|
tables["physician_leaderboard"] = [
|
||||||
{
|
{
|
||||||
'physician_id': p['physician_id'],
|
"physician_id": p["physician_id"],
|
||||||
'name': p['name'],
|
"name": p["name"],
|
||||||
'specialization': p['specialization'],
|
"specialization": p["specialization"],
|
||||||
'department': p['department'],
|
"department": p["department"],
|
||||||
'rating': float(p['rating']) if p['rating'] is not None else 0.0,
|
"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,
|
"surveys": int(p["surveys"]) if p["surveys"] is not None else 0,
|
||||||
'positive': int(p['positive']) if p['positive'] 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,
|
"neutral": int(p["neutral"]) if p["neutral"] is not None else 0,
|
||||||
'negative': int(p['negative']) if p['negative'] is not None else 0
|
"negative": int(p["negative"]) if p["negative"] is not None else 0,
|
||||||
}
|
}
|
||||||
for p in physician_data
|
for p in physician_data
|
||||||
]
|
]
|
||||||
|
|
||||||
return JsonResponse({
|
return JsonResponse({"kpis": kpis, "charts": charts, "tables": tables})
|
||||||
'kpis': kpis,
|
|
||||||
'charts': charts,
|
|
||||||
'tables': tables
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@block_source_user
|
@block_source_user
|
||||||
@ -584,26 +572,26 @@ def export_command_center(request, export_format):
|
|||||||
Returns:
|
Returns:
|
||||||
HttpResponse with file download
|
HttpResponse with file download
|
||||||
"""
|
"""
|
||||||
if export_format not in ['excel', 'pdf']:
|
if export_format not in ["excel", "pdf"]:
|
||||||
return JsonResponse({'error': 'Invalid export format'}, status=400)
|
return JsonResponse({"error": "Invalid export format"}, status=400)
|
||||||
|
|
||||||
user = request.user
|
user = request.user
|
||||||
|
|
||||||
# Get filter parameters
|
# Get filter parameters
|
||||||
date_range = request.GET.get('date_range', '30d')
|
date_range = request.GET.get("date_range", "30d")
|
||||||
hospital_id = request.GET.get('hospital')
|
hospital_id = request.GET.get("hospital")
|
||||||
department_id = request.GET.get('department')
|
department_id = request.GET.get("department")
|
||||||
kpi_category = request.GET.get('kpi_category')
|
kpi_category = request.GET.get("kpi_category")
|
||||||
custom_start_str = request.GET.get('custom_start')
|
custom_start_str = request.GET.get("custom_start")
|
||||||
custom_end_str = request.GET.get('custom_end')
|
custom_end_str = request.GET.get("custom_end")
|
||||||
|
|
||||||
# Parse custom dates
|
# Parse custom dates
|
||||||
custom_start = None
|
custom_start = None
|
||||||
custom_end = None
|
custom_end = None
|
||||||
if custom_start_str and custom_end_str:
|
if custom_start_str and custom_end_str:
|
||||||
try:
|
try:
|
||||||
custom_start = datetime.strptime(custom_start_str, '%Y-%m-%d')
|
custom_start = datetime.strptime(custom_start_str, "%Y-%m-%d")
|
||||||
custom_end = datetime.strptime(custom_end_str, '%Y-%m-%d')
|
custom_end = datetime.strptime(custom_end_str, "%Y-%m-%d")
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -619,16 +607,16 @@ def export_command_center(request, export_format):
|
|||||||
department_id=department_id,
|
department_id=department_id,
|
||||||
kpi_category=kpi_category,
|
kpi_category=kpi_category,
|
||||||
custom_start=custom_start,
|
custom_start=custom_start,
|
||||||
custom_end=custom_end
|
custom_end=custom_end,
|
||||||
)
|
)
|
||||||
|
|
||||||
chart_types = [
|
chart_types = [
|
||||||
'complaints_trend',
|
"complaints_trend",
|
||||||
'complaints_by_category',
|
"complaints_by_category",
|
||||||
'survey_satisfaction_trend',
|
"survey_satisfaction_trend",
|
||||||
'survey_distribution',
|
"survey_distribution",
|
||||||
'department_performance',
|
"department_performance",
|
||||||
'physician_leaderboard'
|
"physician_leaderboard",
|
||||||
]
|
]
|
||||||
|
|
||||||
charts = {}
|
charts = {}
|
||||||
@ -640,7 +628,7 @@ def export_command_center(request, export_format):
|
|||||||
hospital_id=hospital_id,
|
hospital_id=hospital_id,
|
||||||
department_id=department_id,
|
department_id=department_id,
|
||||||
custom_start=custom_start,
|
custom_start=custom_start,
|
||||||
custom_end=custom_end
|
custom_end=custom_end,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get table data
|
# Get table data
|
||||||
@ -658,59 +646,46 @@ def export_command_center(request, export_format):
|
|||||||
if user.is_department_manager() and user.department:
|
if user.is_department_manager() and user.department:
|
||||||
complaints_qs = complaints_qs.filter(department=user.department)
|
complaints_qs = complaints_qs.filter(department=user.department)
|
||||||
|
|
||||||
tables['overdue_complaints'] = {
|
tables["overdue_complaints"] = {
|
||||||
'headers': ['ID', 'Title', 'Patient', 'Severity', 'Hospital', 'Department', 'Due Date'],
|
"headers": ["ID", "Title", "Patient", "Severity", "Hospital", "Department", "Due Date"],
|
||||||
'rows': list(
|
"rows": list(
|
||||||
complaints_qs.select_related('hospital', 'department', 'patient')
|
complaints_qs.select_related("hospital", "department", "patient")
|
||||||
.order_by('due_at')[:100]
|
.order_by("due_at")[:100]
|
||||||
.annotate(
|
.annotate(
|
||||||
patient_full_name=Concat('patient__first_name', Value(' '), 'patient__last_name'),
|
patient_full_name=Concat("patient__first_name", Value(" "), "patient__last_name"),
|
||||||
hospital_name=F('hospital__name'),
|
hospital_name=F("hospital__name"),
|
||||||
department_name=F('department__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 leaderboard
|
||||||
physician_data = charts.get('physician_leaderboard', {}).get('metadata', [])
|
physician_data = charts.get("physician_leaderboard", {}).get("metadata", [])
|
||||||
tables['physician_leaderboard'] = {
|
tables["physician_leaderboard"] = {
|
||||||
'headers': ['Name', 'Specialization', 'Department', 'Rating', 'Surveys', 'Positive', 'Neutral', 'Negative'],
|
"headers": ["Name", "Specialization", "Department", "Rating", "Surveys", "Positive", "Neutral", "Negative"],
|
||||||
'rows': [
|
"rows": [
|
||||||
[
|
[
|
||||||
p['name'],
|
p["name"],
|
||||||
p['specialization'],
|
p["specialization"],
|
||||||
p['department'],
|
p["department"],
|
||||||
str(p['rating']),
|
str(p["rating"]),
|
||||||
str(p['surveys']),
|
str(p["surveys"]),
|
||||||
str(p['positive']),
|
str(p["positive"]),
|
||||||
str(p['neutral']),
|
str(p["neutral"]),
|
||||||
str(p['negative'])
|
str(p["negative"]),
|
||||||
]
|
]
|
||||||
for p in physician_data
|
for p in physician_data
|
||||||
]
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
# Prepare export data
|
# Prepare export data
|
||||||
export_data = ExportService.prepare_dashboard_data(
|
export_data = ExportService.prepare_dashboard_data(user=user, kpis=kpis, charts=charts, tables=tables)
|
||||||
user=user,
|
|
||||||
kpis=kpis,
|
|
||||||
charts=charts,
|
|
||||||
tables=tables
|
|
||||||
)
|
|
||||||
|
|
||||||
# Export based on format
|
# Export based on format
|
||||||
if export_format == 'excel':
|
if export_format == "excel":
|
||||||
return ExportService.export_to_excel(export_data)
|
return ExportService.export_to_excel(export_data)
|
||||||
elif export_format == 'pdf':
|
elif export_format == "pdf":
|
||||||
return ExportService.export_to_pdf(export_data)
|
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_number = request.GET.get('page', 1)
|
||||||
page_obj = paginator.get_page(page_number)
|
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:
|
if not user.is_px_admin() and user.hospital:
|
||||||
@ -172,7 +168,6 @@ def appreciation_list(request):
|
|||||||
'page_obj': page_obj,
|
'page_obj': page_obj,
|
||||||
'appreciations': page_obj.object_list,
|
'appreciations': page_obj.object_list,
|
||||||
'stats': stats,
|
'stats': stats,
|
||||||
'hospitals': hospitals,
|
|
||||||
'departments': departments,
|
'departments': departments,
|
||||||
'categories': categories,
|
'categories': categories,
|
||||||
'status_choices': AppreciationStatus.choices,
|
'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))
|
categories = categories.filter(Q(hospital_id=request.user.hospital.id) | Q(hospital__isnull=True))
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'hospitals': hospitals,
|
|
||||||
'categories': categories,
|
'categories': categories,
|
||||||
'visibility_choices': AppreciationVisibility.choices,
|
'visibility_choices': AppreciationVisibility.choices,
|
||||||
}
|
}
|
||||||
@ -410,10 +404,6 @@ def leaderboard_view(request):
|
|||||||
page_number = request.GET.get('page', 1)
|
page_number = request.GET.get('page', 1)
|
||||||
page_obj = paginator.get_page(page_number)
|
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:
|
if not user.is_px_admin() and user.hospital:
|
||||||
@ -426,7 +416,6 @@ def leaderboard_view(request):
|
|||||||
context = {
|
context = {
|
||||||
'page_obj': page_obj,
|
'page_obj': page_obj,
|
||||||
'leaderboard': page_obj.object_list,
|
'leaderboard': page_obj.object_list,
|
||||||
'hospitals': hospitals,
|
|
||||||
'departments': departments,
|
'departments': departments,
|
||||||
'months': months,
|
'months': months,
|
||||||
'years': years,
|
'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')
|
).distinct().select_related('hospital', 'department')
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'hospitals': hospitals,
|
|
||||||
'departments': departments,
|
'departments': departments,
|
||||||
'staff_list': staff_queryset,
|
'staff_list': staff_queryset,
|
||||||
'selected_hospital_id': hospital_id,
|
'selected_hospital_id': hospital_id,
|
||||||
|
|||||||
@ -148,10 +148,6 @@ def feedback_list(request):
|
|||||||
page_number = request.GET.get('page', 1)
|
page_number = request.GET.get('page', 1)
|
||||||
page_obj = paginator.get_page(page_number)
|
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:
|
if not user.is_px_admin() and user.hospital:
|
||||||
@ -179,7 +175,6 @@ def feedback_list(request):
|
|||||||
'page_obj': page_obj,
|
'page_obj': page_obj,
|
||||||
'feedbacks': page_obj.object_list,
|
'feedbacks': page_obj.object_list,
|
||||||
'stats': stats,
|
'stats': stats,
|
||||||
'hospitals': hospitals,
|
|
||||||
'departments': departments,
|
'departments': departments,
|
||||||
'assignable_users': assignable_users,
|
'assignable_users': assignable_users,
|
||||||
'status_choices': FeedbackStatus.choices,
|
'status_choices': FeedbackStatus.choices,
|
||||||
|
|||||||
@ -105,10 +105,6 @@ def journey_instance_list(request):
|
|||||||
page_number = request.GET.get('page', 1)
|
page_number = request.GET.get('page', 1)
|
||||||
page_obj = paginator.get_page(page_number)
|
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:
|
if not user.is_px_admin() and user.hospital:
|
||||||
@ -125,7 +121,6 @@ def journey_instance_list(request):
|
|||||||
'page_obj': page_obj,
|
'page_obj': page_obj,
|
||||||
'journeys': page_obj.object_list,
|
'journeys': page_obj.object_list,
|
||||||
'stats': stats,
|
'stats': stats,
|
||||||
'hospitals': hospitals,
|
|
||||||
'departments': departments,
|
'departments': departments,
|
||||||
'filters': request.GET,
|
'filters': request.GET,
|
||||||
}
|
}
|
||||||
@ -221,15 +216,10 @@ def journey_template_list(request):
|
|||||||
page_number = request.GET.get('page', 1)
|
page_number = request.GET.get('page', 1)
|
||||||
page_obj = paginator.get_page(page_number)
|
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 = {
|
context = {
|
||||||
'page_obj': page_obj,
|
'page_obj': page_obj,
|
||||||
'templates': page_obj.object_list,
|
'templates': page_obj.object_list,
|
||||||
'hospitals': hospitals,
|
|
||||||
'filters': request.GET,
|
'filters': request.GET,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Physicians Console UI views - Server-rendered templates for physician management
|
Physicians Console UI views - Server-rendered templates for physician management
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.db.models import Avg, Count, Q, Sum
|
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
|
# Base queryset with optimizations - only show staff marked as physicians
|
||||||
# Include both: staff with physician=True (from rating imports) OR staff_type='physician'
|
# Include both: staff with physician=True (from rating imports) OR staff_type='physician'
|
||||||
queryset = Staff.objects.filter(
|
queryset = (
|
||||||
Q(physician=True) | Q(staff_type=Staff.StaffType.PHYSICIAN)
|
Staff.objects.filter(Q(physician=True) | Q(staff_type=Staff.StaffType.PHYSICIAN))
|
||||||
).select_related('hospital', 'department').distinct()
|
.select_related("hospital", "department")
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
|
|
||||||
# Apply RBAC filters
|
# Apply RBAC filters
|
||||||
user = request.user
|
user = request.user
|
||||||
@ -40,50 +43,48 @@ def physician_list(request):
|
|||||||
queryset = queryset.none()
|
queryset = queryset.none()
|
||||||
|
|
||||||
# Apply filters
|
# Apply filters
|
||||||
hospital_filter = request.GET.get('hospital')
|
hospital_filter = request.GET.get("hospital")
|
||||||
if hospital_filter:
|
if hospital_filter:
|
||||||
queryset = queryset.filter(hospital_id=hospital_filter)
|
queryset = queryset.filter(hospital_id=hospital_filter)
|
||||||
|
|
||||||
department_filter = request.GET.get('department')
|
department_filter = request.GET.get("department")
|
||||||
if department_filter:
|
if department_filter:
|
||||||
queryset = queryset.filter(department_id=department_filter)
|
queryset = queryset.filter(department_id=department_filter)
|
||||||
|
|
||||||
specialization_filter = request.GET.get('specialization')
|
specialization_filter = request.GET.get("specialization")
|
||||||
if specialization_filter:
|
if specialization_filter:
|
||||||
queryset = queryset.filter(specialization__icontains=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:
|
if status_filter:
|
||||||
queryset = queryset.filter(status=status_filter)
|
queryset = queryset.filter(status=status_filter)
|
||||||
|
|
||||||
# Search
|
# Search
|
||||||
search_query = request.GET.get('search')
|
search_query = request.GET.get("search")
|
||||||
if search_query:
|
if search_query:
|
||||||
queryset = queryset.filter(
|
queryset = queryset.filter(
|
||||||
Q(first_name__icontains=search_query) |
|
Q(first_name__icontains=search_query)
|
||||||
Q(last_name__icontains=search_query) |
|
| Q(last_name__icontains=search_query)
|
||||||
Q(license_number__icontains=search_query) |
|
| Q(license_number__icontains=search_query)
|
||||||
Q(specialization__icontains=search_query)
|
| Q(specialization__icontains=search_query)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Ordering
|
# 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)
|
queryset = queryset.order_by(order_by)
|
||||||
|
|
||||||
# Pagination
|
# Pagination
|
||||||
page_size = int(request.GET.get('page_size', 25))
|
page_size = int(request.GET.get("page_size", 25))
|
||||||
paginator = Paginator(queryset, page_size)
|
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)
|
page_obj = paginator.get_page(page_number)
|
||||||
|
|
||||||
# Get current month ratings for displayed physicians
|
# Get current month ratings for displayed physicians
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
physician_ids = [p.id for p in page_obj.object_list]
|
physician_ids = [p.id for p in page_obj.object_list]
|
||||||
current_ratings = PhysicianMonthlyRating.objects.filter(
|
current_ratings = PhysicianMonthlyRating.objects.filter(
|
||||||
staff_id__in=physician_ids,
|
staff_id__in=physician_ids, year=now.year, month=now.month
|
||||||
year=now.year,
|
).select_related("staff")
|
||||||
month=now.month
|
|
||||||
).select_related('staff')
|
|
||||||
|
|
||||||
# Create rating lookup
|
# Create rating lookup
|
||||||
ratings_dict = {r.staff_id: r for r in current_ratings}
|
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:
|
for physician in page_obj.object_list:
|
||||||
physician.current_rating = ratings_dict.get(physician.id)
|
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')
|
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')
|
|
||||||
if not user.is_px_admin() and user.hospital:
|
if not user.is_px_admin() and user.hospital:
|
||||||
departments = departments.filter(hospital=user.hospital)
|
departments = departments.filter(hospital=user.hospital)
|
||||||
|
|
||||||
@ -312,9 +104,7 @@ def leaderboard(request):
|
|||||||
all_ratings = all_ratings.filter(staff__hospital=user.hospital)
|
all_ratings = all_ratings.filter(staff__hospital=user.hospital)
|
||||||
|
|
||||||
stats = all_ratings.aggregate(
|
stats = all_ratings.aggregate(
|
||||||
total_physicians=Count('id'),
|
total_physicians=Count("id"), average_rating=Avg("average_rating"), total_surveys=Count("total_surveys")
|
||||||
average_rating=Avg('average_rating'),
|
|
||||||
total_surveys=Count('total_surveys')
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Distribution
|
# Distribution
|
||||||
@ -324,22 +114,17 @@ def leaderboard(request):
|
|||||||
poor = all_ratings.filter(average_rating__lt=2.5).count()
|
poor = all_ratings.filter(average_rating__lt=2.5).count()
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'leaderboard': leaderboard,
|
"leaderboard": leaderboard,
|
||||||
'year': year,
|
"year": year,
|
||||||
'month': month,
|
"month": month,
|
||||||
'hospitals': hospitals,
|
"hospitals": hospitals,
|
||||||
'departments': departments,
|
"departments": departments,
|
||||||
'filters': request.GET,
|
"filters": request.GET,
|
||||||
'stats': stats,
|
"stats": stats,
|
||||||
'distribution': {
|
"distribution": {"excellent": excellent, "good": good, "average": average, "poor": poor},
|
||||||
'excellent': excellent,
|
|
||||||
'good': good,
|
|
||||||
'average': average,
|
|
||||||
'poor': poor
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, 'physicians/leaderboard.html', context)
|
return render(request, "physicians/leaderboard.html", context)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@ -356,18 +141,11 @@ def physician_ratings_dashboard(request):
|
|||||||
- Top physicians table
|
- Top physicians table
|
||||||
"""
|
"""
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
year = int(request.GET.get('year', now.year))
|
year = int(request.GET.get("year", now.year))
|
||||||
month = int(request.GET.get('month', now.month))
|
month = int(request.GET.get("month", now.month))
|
||||||
hospital_filter = request.GET.get('hospital')
|
hospital_filter = request.GET.get("hospital")
|
||||||
department_filter = request.GET.get('department')
|
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)
|
departments = departments.filter(hospital=user.hospital)
|
||||||
|
|
||||||
# Get available years (2024 to current year)
|
# Get available years (2024 to current year)
|
||||||
@ -376,13 +154,13 @@ def physician_ratings_dashboard(request):
|
|||||||
years.reverse() # Most recent first
|
years.reverse() # Most recent first
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'years': years,
|
"years": years,
|
||||||
'hospitals': hospitals,
|
"hospitals": hospitals,
|
||||||
'departments': departments,
|
"departments": departments,
|
||||||
'filters': request.GET,
|
"filters": request.GET,
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, 'physicians/physician_ratings_dashboard.html', context)
|
return render(request, "physicians/physician_ratings_dashboard.html", context)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@ -394,15 +172,15 @@ def physician_ratings_dashboard_api(request):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
year = int(request.GET.get('year', now.year))
|
year = int(request.GET.get("year", now.year))
|
||||||
month = int(request.GET.get('month', now.month))
|
month = int(request.GET.get("month", now.month))
|
||||||
hospital_filter = request.GET.get('hospital')
|
hospital_filter = request.GET.get("hospital")
|
||||||
department_filter = request.GET.get('department')
|
department_filter = request.GET.get("department")
|
||||||
|
|
||||||
# Base queryset - only include staff marked as physicians
|
# Base queryset - only include staff marked as physicians
|
||||||
queryset = PhysicianMonthlyRating.objects.filter(
|
queryset = PhysicianMonthlyRating.objects.filter(staff__physician=True).select_related(
|
||||||
staff__physician=True
|
"staff", "staff__hospital", "staff__department"
|
||||||
).select_related('staff', 'staff__hospital', 'staff__department')
|
)
|
||||||
|
|
||||||
# Apply RBAC filters
|
# Apply RBAC filters
|
||||||
user = request.user
|
user = request.user
|
||||||
@ -420,9 +198,9 @@ def physician_ratings_dashboard_api(request):
|
|||||||
|
|
||||||
# 1. Statistics
|
# 1. Statistics
|
||||||
stats = current_period.aggregate(
|
stats = current_period.aggregate(
|
||||||
total_physicians=Count('id', distinct=True),
|
total_physicians=Count("id", distinct=True),
|
||||||
average_rating=Avg('average_rating'),
|
average_rating=Avg("average_rating"),
|
||||||
total_surveys=Sum('total_surveys')
|
total_surveys=Sum("total_surveys"),
|
||||||
)
|
)
|
||||||
|
|
||||||
excellent_count = current_period.filter(average_rating__gte=4.5).count()
|
excellent_count = current_period.filter(average_rating__gte=4.5).count()
|
||||||
@ -437,15 +215,16 @@ def physician_ratings_dashboard_api(request):
|
|||||||
y -= 1
|
y -= 1
|
||||||
|
|
||||||
period_data = queryset.filter(year=y, month=m).aggregate(
|
period_data = queryset.filter(year=y, month=m).aggregate(
|
||||||
avg=Avg('average_rating'),
|
avg=Avg("average_rating"), surveys=Sum("total_surveys")
|
||||||
surveys=Sum('total_surveys')
|
|
||||||
)
|
)
|
||||||
|
|
||||||
trend_data.append({
|
trend_data.append(
|
||||||
'period': f'{y}-{m:02d}',
|
{
|
||||||
'average_rating': float(period_data['avg'] or 0),
|
"period": f"{y}-{m:02d}",
|
||||||
'total_surveys': period_data['surveys'] or 0
|
"average_rating": float(period_data["avg"] or 0),
|
||||||
})
|
"total_surveys": period_data["surveys"] or 0,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# 3. Rating Distribution
|
# 3. Rating Distribution
|
||||||
excellent = current_period.filter(average_rating__gte=4.5).count()
|
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()
|
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()
|
poor = current_period.filter(average_rating__lt=2.5).count()
|
||||||
|
|
||||||
distribution = {
|
distribution = {"excellent": excellent, "good": good, "average": average, "poor": poor}
|
||||||
'excellent': excellent,
|
|
||||||
'good': good,
|
|
||||||
'average': average,
|
|
||||||
'poor': poor
|
|
||||||
}
|
|
||||||
|
|
||||||
# 4. Department Comparison (top 10)
|
# 4. Department Comparison (top 10)
|
||||||
dept_data = current_period.values('staff__department__name').annotate(
|
dept_data = (
|
||||||
average_rating=Avg('average_rating'),
|
current_period.values("staff__department__name")
|
||||||
total_surveys=Sum('total_surveys'),
|
.annotate(
|
||||||
physician_count=Count('id', distinct=True)
|
average_rating=Avg("average_rating"),
|
||||||
).filter(staff__department__isnull=False).order_by('-average_rating')[:10]
|
total_surveys=Sum("total_surveys"),
|
||||||
|
physician_count=Count("id", distinct=True),
|
||||||
|
)
|
||||||
|
.filter(staff__department__isnull=False)
|
||||||
|
.order_by("-average_rating")[:10]
|
||||||
|
)
|
||||||
|
|
||||||
departments = [
|
departments = [
|
||||||
{
|
{
|
||||||
'name': item['staff__department__name'] or 'Unknown',
|
"name": item["staff__department__name"] or "Unknown",
|
||||||
'average_rating': float(item['average_rating'] or 0),
|
"average_rating": float(item["average_rating"] or 0),
|
||||||
'total_surveys': item['total_surveys'] or 0
|
"total_surveys": item["total_surveys"] or 0,
|
||||||
}
|
}
|
||||||
for item in dept_data
|
for item in dept_data
|
||||||
]
|
]
|
||||||
|
|
||||||
# 5. Sentiment Analysis
|
# 5. Sentiment Analysis
|
||||||
sentiment = current_period.aggregate(
|
sentiment = current_period.aggregate(
|
||||||
positive=Sum('positive_count'),
|
positive=Sum("positive_count"), neutral=Sum("neutral_count"), negative=Sum("negative_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:
|
if total_sentiment > 0:
|
||||||
sentiment_pct = {
|
sentiment_pct = {
|
||||||
'positive': ((sentiment['positive'] or 0) / total_sentiment) * 100,
|
"positive": ((sentiment["positive"] or 0) / total_sentiment) * 100,
|
||||||
'neutral': ((sentiment['neutral'] or 0) / total_sentiment) * 100,
|
"neutral": ((sentiment["neutral"] or 0) / total_sentiment) * 100,
|
||||||
'negative': ((sentiment['negative'] or 0) / total_sentiment) * 100
|
"negative": ((sentiment["negative"] or 0) / total_sentiment) * 100,
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
sentiment_pct = {'positive': 0, 'neutral': 0, 'negative': 0}
|
sentiment_pct = {"positive": 0, "neutral": 0, "negative": 0}
|
||||||
|
|
||||||
# 6. Top 10 Physicians
|
# 6. Top 10 Physicians
|
||||||
top_physicians = current_period.select_related(
|
top_physicians = current_period.select_related("staff", "staff__hospital", "staff__department").order_by(
|
||||||
'staff', 'staff__hospital', 'staff__department'
|
"-average_rating", "-total_surveys"
|
||||||
).order_by('-average_rating', '-total_surveys')[:10]
|
)[:10]
|
||||||
|
|
||||||
physicians_list = [
|
physicians_list = [
|
||||||
{
|
{
|
||||||
'id': rating.staff.id,
|
"id": rating.staff.id,
|
||||||
'name': rating.staff.get_full_name(),
|
"name": rating.staff.get_full_name(),
|
||||||
'license_number': rating.staff.license_number,
|
"license_number": rating.staff.license_number,
|
||||||
'specialization': rating.staff.specialization or '-',
|
"specialization": rating.staff.specialization or "-",
|
||||||
'department': rating.staff.department.name if rating.staff.department else '-',
|
"department": rating.staff.department.name if rating.staff.department else "-",
|
||||||
'hospital': rating.staff.hospital.name if rating.staff.hospital else '-',
|
"hospital": rating.staff.hospital.name if rating.staff.hospital else "-",
|
||||||
'rating': float(rating.average_rating),
|
"rating": float(rating.average_rating),
|
||||||
'surveys': rating.total_surveys
|
"surveys": rating.total_surveys,
|
||||||
}
|
}
|
||||||
for rating in top_physicians
|
for rating in top_physicians
|
||||||
]
|
]
|
||||||
|
|
||||||
return JsonResponse({
|
return JsonResponse(
|
||||||
'statistics': {
|
{
|
||||||
'total_physicians': stats['total_physicians'] or 0,
|
"statistics": {
|
||||||
'average_rating': float(stats['average_rating'] or 0),
|
"total_physicians": stats["total_physicians"] or 0,
|
||||||
'total_surveys': stats['total_surveys'] or 0,
|
"average_rating": float(stats["average_rating"] or 0),
|
||||||
'excellent_count': excellent_count
|
"total_surveys": stats["total_surveys"] or 0,
|
||||||
|
"excellent_count": excellent_count,
|
||||||
},
|
},
|
||||||
'trend': trend_data,
|
"trend": trend_data,
|
||||||
'distribution': distribution,
|
"distribution": distribution,
|
||||||
'departments': departments,
|
"departments": departments,
|
||||||
'sentiment': sentiment_pct,
|
"sentiment": sentiment_pct,
|
||||||
'top_physicians': physicians_list
|
"top_physicians": physicians_list,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
import traceback
|
||||||
return JsonResponse({
|
|
||||||
'error': str(e),
|
return JsonResponse({"error": str(e), "traceback": traceback.format_exc()}, status=500)
|
||||||
'traceback': traceback.format_exc()
|
|
||||||
}, status=500)
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@ -546,9 +323,9 @@ def ratings_list(request):
|
|||||||
- Pagination
|
- Pagination
|
||||||
"""
|
"""
|
||||||
# Base queryset - only include staff marked as physicians
|
# Base queryset - only include staff marked as physicians
|
||||||
queryset = PhysicianMonthlyRating.objects.filter(
|
queryset = PhysicianMonthlyRating.objects.filter(staff__physician=True).select_related(
|
||||||
staff__physician=True
|
"staff", "staff__hospital", "staff__department"
|
||||||
).select_related('staff', 'staff__hospital', 'staff__department')
|
)
|
||||||
|
|
||||||
# Apply RBAC filters
|
# Apply RBAC filters
|
||||||
user = request.user
|
user = request.user
|
||||||
@ -556,67 +333,63 @@ def ratings_list(request):
|
|||||||
queryset = queryset.filter(staff__hospital=user.hospital)
|
queryset = queryset.filter(staff__hospital=user.hospital)
|
||||||
|
|
||||||
# Apply filters
|
# Apply filters
|
||||||
physician_filter = request.GET.get('physician')
|
physician_filter = request.GET.get("physician")
|
||||||
if physician_filter:
|
if physician_filter:
|
||||||
queryset = queryset.filter(staff_id=physician_filter)
|
queryset = queryset.filter(staff_id=physician_filter)
|
||||||
|
|
||||||
hospital_filter = request.GET.get('hospital')
|
hospital_filter = request.GET.get("hospital")
|
||||||
if hospital_filter:
|
if hospital_filter:
|
||||||
queryset = queryset.filter(staff__hospital_id=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:
|
if department_filter:
|
||||||
queryset = queryset.filter(staff__department_id=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:
|
if year_filter:
|
||||||
queryset = queryset.filter(year=int(year_filter))
|
queryset = queryset.filter(year=int(year_filter))
|
||||||
|
|
||||||
month_filter = request.GET.get('month')
|
month_filter = request.GET.get("month")
|
||||||
if month_filter:
|
if month_filter:
|
||||||
queryset = queryset.filter(month=int(month_filter))
|
queryset = queryset.filter(month=int(month_filter))
|
||||||
|
|
||||||
# Search
|
# Search
|
||||||
search_query = request.GET.get('search')
|
search_query = request.GET.get("search")
|
||||||
if search_query:
|
if search_query:
|
||||||
queryset = queryset.filter(
|
queryset = queryset.filter(
|
||||||
Q(staff__first_name__icontains=search_query) |
|
Q(staff__first_name__icontains=search_query)
|
||||||
Q(staff__last_name__icontains=search_query) |
|
| Q(staff__last_name__icontains=search_query)
|
||||||
Q(staff__license_number__icontains=search_query)
|
| Q(staff__license_number__icontains=search_query)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Ordering
|
# Ordering
|
||||||
order_by = request.GET.get('order_by', '-year,-month,-average_rating')
|
order_by = request.GET.get("order_by", "-year,-month,-average_rating")
|
||||||
queryset = queryset.order_by(*order_by.split(','))
|
queryset = queryset.order_by(*order_by.split(","))
|
||||||
|
|
||||||
# Pagination
|
# Pagination
|
||||||
page_size = int(request.GET.get('page_size', 25))
|
page_size = int(request.GET.get("page_size", 25))
|
||||||
paginator = Paginator(queryset, page_size)
|
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)
|
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:
|
if not user.is_px_admin() and user.hospital:
|
||||||
departments = departments.filter(hospital=user.hospital)
|
departments = departments.filter(hospital=user.hospital)
|
||||||
|
|
||||||
# Get available years
|
# 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 = {
|
context = {
|
||||||
'page_obj': page_obj,
|
"page_obj": page_obj,
|
||||||
'ratings': page_obj.object_list,
|
"ratings": page_obj.object_list,
|
||||||
'hospitals': hospitals,
|
"hospitals": hospitals,
|
||||||
'departments': departments,
|
"departments": departments,
|
||||||
'years': years,
|
"years": years,
|
||||||
'filters': request.GET,
|
"filters": request.GET,
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, 'physicians/ratings_list.html', context)
|
return render(request, "physicians/ratings_list.html", context)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@ -632,16 +405,14 @@ def specialization_overview(request):
|
|||||||
"""
|
"""
|
||||||
# Get parameters
|
# Get parameters
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
year = int(request.GET.get('year', now.year))
|
year = int(request.GET.get("year", now.year))
|
||||||
month = int(request.GET.get('month', now.month))
|
month = int(request.GET.get("month", now.month))
|
||||||
hospital_filter = request.GET.get('hospital')
|
hospital_filter = request.GET.get("hospital")
|
||||||
|
|
||||||
# Base queryset - only include staff marked as physicians
|
# Base queryset - only include staff marked as physicians
|
||||||
queryset = PhysicianMonthlyRating.objects.filter(
|
queryset = PhysicianMonthlyRating.objects.filter(year=year, month=month, staff__physician=True).select_related(
|
||||||
year=year,
|
"staff", "staff__hospital", "staff__department"
|
||||||
month=month,
|
)
|
||||||
staff__physician=True
|
|
||||||
).select_related('staff', 'staff__hospital', 'staff__department')
|
|
||||||
|
|
||||||
# Apply RBAC filters
|
# Apply RBAC filters
|
||||||
user = request.user
|
user = request.user
|
||||||
@ -655,60 +426,57 @@ def specialization_overview(request):
|
|||||||
# Aggregate by specialization
|
# Aggregate by specialization
|
||||||
specialization_data = {}
|
specialization_data = {}
|
||||||
for rating in queryset:
|
for rating in queryset:
|
||||||
|
|
||||||
spec = rating.staff.specialization
|
spec = rating.staff.specialization
|
||||||
if spec not in specialization_data:
|
if spec not in specialization_data:
|
||||||
specialization_data[spec] = {
|
specialization_data[spec] = {
|
||||||
'specialization': spec,
|
"specialization": spec,
|
||||||
'physicians': [],
|
"physicians": [],
|
||||||
'total_physicians': 0,
|
"total_physicians": 0,
|
||||||
'total_surveys': 0,
|
"total_surveys": 0,
|
||||||
'total_positive': 0,
|
"total_positive": 0,
|
||||||
'total_neutral': 0,
|
"total_neutral": 0,
|
||||||
'total_negative': 0,
|
"total_negative": 0,
|
||||||
'ratings_sum': 0,
|
"ratings_sum": 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
specialization_data[spec]['physicians'].append(rating)
|
specialization_data[spec]["physicians"].append(rating)
|
||||||
specialization_data[spec]['total_physicians'] += 1
|
specialization_data[spec]["total_physicians"] += 1
|
||||||
specialization_data[spec]['total_surveys'] += rating.total_surveys
|
specialization_data[spec]["total_surveys"] += rating.total_surveys
|
||||||
specialization_data[spec]['total_positive'] += rating.positive_count
|
specialization_data[spec]["total_positive"] += rating.positive_count
|
||||||
specialization_data[spec]['total_neutral'] += rating.neutral_count
|
specialization_data[spec]["total_neutral"] += rating.neutral_count
|
||||||
specialization_data[spec]['total_negative'] += rating.negative_count
|
specialization_data[spec]["total_negative"] += rating.negative_count
|
||||||
specialization_data[spec]['ratings_sum'] += float(rating.average_rating)
|
specialization_data[spec]["ratings_sum"] += float(rating.average_rating)
|
||||||
|
|
||||||
# Calculate averages
|
# Calculate averages
|
||||||
specializations = []
|
specializations = []
|
||||||
for spec, data in specialization_data.items():
|
for spec, data in specialization_data.items():
|
||||||
avg_rating = data['ratings_sum'] / data['total_physicians'] if data['total_physicians'] > 0 else 0
|
avg_rating = data["ratings_sum"] / data["total_physicians"] if data["total_physicians"] > 0 else 0
|
||||||
specializations.append({
|
specializations.append(
|
||||||
'specialization': spec,
|
{
|
||||||
'total_physicians': data['total_physicians'],
|
"specialization": spec,
|
||||||
'average_rating': round(avg_rating, 2),
|
"total_physicians": data["total_physicians"],
|
||||||
'total_surveys': data['total_surveys'],
|
"average_rating": round(avg_rating, 2),
|
||||||
'positive_count': data['total_positive'],
|
"total_surveys": data["total_surveys"],
|
||||||
'neutral_count': data['total_neutral'],
|
"positive_count": data["total_positive"],
|
||||||
'negative_count': data['total_negative'],
|
"neutral_count": data["total_neutral"],
|
||||||
'physicians': sorted(data['physicians'], key=lambda x: x.average_rating, reverse=True)
|
"negative_count": data["total_negative"],
|
||||||
})
|
"physicians": sorted(data["physicians"], key=lambda x: x.average_rating, reverse=True),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# Sort by average rating
|
# 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 = {
|
context = {
|
||||||
'specializations': specializations,
|
"specializations": specializations,
|
||||||
'year': year,
|
"year": year,
|
||||||
'month': month,
|
"month": month,
|
||||||
'hospitals': hospitals,
|
"hospitals": hospitals,
|
||||||
'filters': request.GET,
|
"filters": request.GET,
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, 'physicians/specialization_overview.html', context)
|
return render(request, "physicians/specialization_overview.html", context)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@ -724,16 +492,14 @@ def department_overview(request):
|
|||||||
"""
|
"""
|
||||||
# Get parameters
|
# Get parameters
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
year = int(request.GET.get('year', now.year))
|
year = int(request.GET.get("year", now.year))
|
||||||
month = int(request.GET.get('month', now.month))
|
month = int(request.GET.get("month", now.month))
|
||||||
hospital_filter = request.GET.get('hospital')
|
hospital_filter = request.GET.get("hospital")
|
||||||
|
|
||||||
# Base queryset - only include staff marked as physicians
|
# Base queryset - only include staff marked as physicians
|
||||||
queryset = PhysicianMonthlyRating.objects.filter(
|
queryset = PhysicianMonthlyRating.objects.filter(year=year, month=month, staff__physician=True).select_related(
|
||||||
year=year,
|
"staff", "staff__hospital", "staff__department"
|
||||||
month=month,
|
)
|
||||||
staff__physician=True
|
|
||||||
).select_related('staff', 'staff__hospital', 'staff__department')
|
|
||||||
|
|
||||||
# Apply RBAC filters
|
# Apply RBAC filters
|
||||||
user = request.user
|
user = request.user
|
||||||
@ -754,53 +520,51 @@ def department_overview(request):
|
|||||||
dept_key = str(dept.id)
|
dept_key = str(dept.id)
|
||||||
if dept_key not in department_data:
|
if dept_key not in department_data:
|
||||||
department_data[dept_key] = {
|
department_data[dept_key] = {
|
||||||
'department': dept,
|
"department": dept,
|
||||||
'physicians': [],
|
"physicians": [],
|
||||||
'total_physicians': 0,
|
"total_physicians": 0,
|
||||||
'total_surveys': 0,
|
"total_surveys": 0,
|
||||||
'total_positive': 0,
|
"total_positive": 0,
|
||||||
'total_neutral': 0,
|
"total_neutral": 0,
|
||||||
'total_negative': 0,
|
"total_negative": 0,
|
||||||
'ratings_sum': 0,
|
"ratings_sum": 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
department_data[dept_key]['physicians'].append(rating)
|
department_data[dept_key]["physicians"].append(rating)
|
||||||
department_data[dept_key]['total_physicians'] += 1
|
department_data[dept_key]["total_physicians"] += 1
|
||||||
department_data[dept_key]['total_surveys'] += rating.total_surveys
|
department_data[dept_key]["total_surveys"] += rating.total_surveys
|
||||||
department_data[dept_key]['total_positive'] += rating.positive_count
|
department_data[dept_key]["total_positive"] += rating.positive_count
|
||||||
department_data[dept_key]['total_neutral'] += rating.neutral_count
|
department_data[dept_key]["total_neutral"] += rating.neutral_count
|
||||||
department_data[dept_key]['total_negative'] += rating.negative_count
|
department_data[dept_key]["total_negative"] += rating.negative_count
|
||||||
department_data[dept_key]['ratings_sum'] += float(rating.average_rating)
|
department_data[dept_key]["ratings_sum"] += float(rating.average_rating)
|
||||||
|
|
||||||
# Calculate averages
|
# Calculate averages
|
||||||
departments = []
|
departments = []
|
||||||
for dept_key, data in department_data.items():
|
for dept_key, data in department_data.items():
|
||||||
avg_rating = data['ratings_sum'] / data['total_physicians'] if data['total_physicians'] > 0 else 0
|
avg_rating = data["ratings_sum"] / data["total_physicians"] if data["total_physicians"] > 0 else 0
|
||||||
departments.append({
|
departments.append(
|
||||||
'department': data['department'],
|
{
|
||||||
'total_physicians': data['total_physicians'],
|
"department": data["department"],
|
||||||
'average_rating': round(avg_rating, 2),
|
"total_physicians": data["total_physicians"],
|
||||||
'total_surveys': data['total_surveys'],
|
"average_rating": round(avg_rating, 2),
|
||||||
'positive_count': data['total_positive'],
|
"total_surveys": data["total_surveys"],
|
||||||
'neutral_count': data['total_neutral'],
|
"positive_count": data["total_positive"],
|
||||||
'negative_count': data['total_negative'],
|
"neutral_count": data["total_neutral"],
|
||||||
'physicians': sorted(data['physicians'], key=lambda x: x.average_rating, reverse=True)
|
"negative_count": data["total_negative"],
|
||||||
})
|
"physicians": sorted(data["physicians"], key=lambda x: x.average_rating, reverse=True),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# Sort by average rating
|
# 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 = {
|
context = {
|
||||||
'departments': departments,
|
"departments": departments,
|
||||||
'year': year,
|
"year": year,
|
||||||
'month': month,
|
"month": month,
|
||||||
'hospitals': hospitals,
|
"hospitals": hospitals,
|
||||||
'filters': request.GET,
|
"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 = {
|
context = {
|
||||||
'page_obj': page_obj,
|
'page_obj': page_obj,
|
||||||
'projects': page_obj.object_list,
|
'projects': page_obj.object_list,
|
||||||
'hospitals': hospitals,
|
|
||||||
'stats': stats,
|
'stats': stats,
|
||||||
'filters': request.GET,
|
'filters': request.GET,
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user