190 lines
7.8 KiB
Python
190 lines
7.8 KiB
Python
"""
|
|
Dashboard views - PX Command Center and analytics dashboards
|
|
"""
|
|
from datetime import timedelta
|
|
|
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
from django.db.models import Avg, Count, Q
|
|
from django.utils import timezone
|
|
from django.views.generic import TemplateView
|
|
|
|
|
|
class CommandCenterView(LoginRequiredMixin, TemplateView):
|
|
"""
|
|
PX Command Center Dashboard - Real-time control panel.
|
|
|
|
Shows:
|
|
- Top KPI cards (complaints, actions, surveys, etc.)
|
|
- Charts (trends, satisfaction, leaderboards)
|
|
- Live feed (latest complaints, actions, events)
|
|
- Filters (date range, hospital, department)
|
|
"""
|
|
template_name = 'dashboard/command_center.html'
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
user = self.request.user
|
|
|
|
# Import models
|
|
from apps.complaints.models import Complaint
|
|
from apps.px_action_center.models import PXAction
|
|
from apps.surveys.models import SurveyInstance
|
|
from apps.social.models import SocialMention
|
|
from apps.callcenter.models import CallCenterInteraction
|
|
from apps.integrations.models import InboundEvent
|
|
from apps.physicians.models import PhysicianMonthlyRating
|
|
from apps.organizations.models import Physician
|
|
|
|
# Date filters
|
|
now = timezone.now()
|
|
last_24h = now - timedelta(hours=24)
|
|
last_7d = now - timedelta(days=7)
|
|
last_30d = now - timedelta(days=30)
|
|
|
|
# Base querysets (filtered by user role)
|
|
if user.is_px_admin():
|
|
complaints_qs = Complaint.objects.all()
|
|
actions_qs = PXAction.objects.all()
|
|
surveys_qs = SurveyInstance.objects.all()
|
|
social_qs = SocialMention.objects.all()
|
|
calls_qs = CallCenterInteraction.objects.all()
|
|
elif user.is_hospital_admin() and user.hospital:
|
|
complaints_qs = Complaint.objects.filter(hospital=user.hospital)
|
|
actions_qs = PXAction.objects.filter(hospital=user.hospital)
|
|
surveys_qs = SurveyInstance.objects.filter(survey_template__hospital=user.hospital)
|
|
social_qs = SocialMention.objects.filter(hospital=user.hospital)
|
|
calls_qs = CallCenterInteraction.objects.filter(hospital=user.hospital)
|
|
elif user.is_department_manager() and user.department:
|
|
complaints_qs = Complaint.objects.filter(department=user.department)
|
|
actions_qs = PXAction.objects.filter(department=user.department)
|
|
surveys_qs = SurveyInstance.objects.filter(journey_stage_instance__department=user.department)
|
|
social_qs = SocialMention.objects.filter(department=user.department)
|
|
calls_qs = CallCenterInteraction.objects.filter(department=user.department)
|
|
else:
|
|
complaints_qs = Complaint.objects.none()
|
|
actions_qs = PXAction.objects.none()
|
|
surveys_qs = SurveyInstance.objects.none()
|
|
social_qs = SocialMention.objects.none()
|
|
calls_qs = CallCenterInteraction.objects.none()
|
|
|
|
# Top KPI Stats
|
|
context['stats'] = [
|
|
{
|
|
'label': 'Active Complaints',
|
|
'value': complaints_qs.filter(status__in=['open', 'in_progress']).count(),
|
|
'icon': 'exclamation-triangle',
|
|
'color': 'danger'
|
|
},
|
|
{
|
|
'label': 'Overdue Complaints',
|
|
'value': complaints_qs.filter(is_overdue=True, status__in=['open', 'in_progress']).count(),
|
|
'icon': 'clock-history',
|
|
'color': 'warning'
|
|
},
|
|
{
|
|
'label': 'Open PX Actions',
|
|
'value': actions_qs.filter(status__in=['open', 'in_progress']).count(),
|
|
'icon': 'clipboard-check',
|
|
'color': 'primary'
|
|
},
|
|
{
|
|
'label': 'Overdue Actions',
|
|
'value': actions_qs.filter(is_overdue=True, status__in=['open', 'in_progress']).count(),
|
|
'icon': 'alarm',
|
|
'color': 'danger'
|
|
},
|
|
{
|
|
'label': 'Negative Surveys (24h)',
|
|
'value': surveys_qs.filter(is_negative=True, completed_at__gte=last_24h).count(),
|
|
'icon': 'emoji-frown',
|
|
'color': 'warning'
|
|
},
|
|
{
|
|
'label': 'Negative Social Mentions',
|
|
'value': social_qs.filter(sentiment='negative', posted_at__gte=last_7d).count(),
|
|
'icon': 'chat-dots',
|
|
'color': 'danger'
|
|
},
|
|
{
|
|
'label': 'Low Call Center Ratings',
|
|
'value': calls_qs.filter(is_low_rating=True, call_started_at__gte=last_7d).count(),
|
|
'icon': 'telephone',
|
|
'color': 'warning'
|
|
},
|
|
{
|
|
'label': 'Avg Survey Score',
|
|
'value': f"{surveys_qs.filter(completed_at__gte=last_30d).aggregate(Avg('total_score'))['total_score__avg'] or 0:.1f}",
|
|
'icon': 'star',
|
|
'color': 'success'
|
|
},
|
|
]
|
|
|
|
# Latest high severity complaints
|
|
context['latest_complaints'] = complaints_qs.filter(
|
|
severity__in=['high', 'critical']
|
|
).select_related('patient', 'hospital', 'department').order_by('-created_at')[:5]
|
|
|
|
# Latest escalated actions
|
|
context['latest_actions'] = actions_qs.filter(
|
|
escalation_level__gt=0
|
|
).select_related('hospital', 'assigned_to').order_by('-escalated_at')[:5]
|
|
|
|
# Latest integration events
|
|
context['latest_events'] = InboundEvent.objects.filter(
|
|
status='processed'
|
|
).select_related().order_by('-processed_at')[:10]
|
|
|
|
# Physician ratings data
|
|
current_month_ratings = PhysicianMonthlyRating.objects.filter(
|
|
year=now.year,
|
|
month=now.month
|
|
).select_related('physician', 'physician__hospital', 'physician__department')
|
|
|
|
# Filter by user role
|
|
if user.is_hospital_admin() and user.hospital:
|
|
current_month_ratings = current_month_ratings.filter(physician__hospital=user.hospital)
|
|
elif user.is_department_manager() and user.department:
|
|
current_month_ratings = current_month_ratings.filter(physician__department=user.department)
|
|
|
|
# Top 5 physicians this month
|
|
context['top_physicians'] = current_month_ratings.order_by('-average_rating')[:5]
|
|
|
|
# Physician stats
|
|
physician_stats = current_month_ratings.aggregate(
|
|
total_physicians=Count('id'),
|
|
avg_rating=Avg('average_rating'),
|
|
total_surveys=Count('total_surveys')
|
|
)
|
|
context['physician_stats'] = physician_stats
|
|
|
|
# Chart data (simplified for now)
|
|
import json
|
|
context['chart_data'] = {
|
|
'complaints_trend': json.dumps(self.get_complaints_trend(complaints_qs, last_30d)),
|
|
'survey_satisfaction': self.get_survey_satisfaction(surveys_qs, last_30d),
|
|
}
|
|
|
|
return context
|
|
|
|
def get_complaints_trend(self, queryset, start_date):
|
|
"""Get complaints trend data for chart"""
|
|
# Group by day for last 30 days
|
|
data = []
|
|
for i in range(30):
|
|
date = start_date + timedelta(days=i)
|
|
count = queryset.filter(
|
|
created_at__date=date.date()
|
|
).count()
|
|
data.append({
|
|
'date': date.strftime('%Y-%m-%d'),
|
|
'count': count
|
|
})
|
|
return data
|
|
|
|
def get_survey_satisfaction(self, queryset, start_date):
|
|
"""Get survey satisfaction averages"""
|
|
return queryset.filter(
|
|
completed_at__gte=start_date,
|
|
total_score__isnull=False
|
|
).aggregate(Avg('total_score'))['total_score__avg'] or 0
|