HH/apps/dashboard/views.py
2025-12-29 18:36:06 +03:00

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