""" 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 from django.utils.translation import gettext_lazy as _ 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