diff --git a/apps/physicians/ui_views.py b/apps/physicians/ui_views.py index 7ddb5c7..3912802 100644 --- a/apps/physicians/ui_views.py +++ b/apps/physicians/ui_views.py @@ -418,3 +418,191 @@ def ratings_list(request): } return render(request, 'physicians/ratings_list.html', context) + + +@login_required +def specialization_overview(request): + """ + Specialization overview - aggregated ratings by specialization. + + Features: + - Average rating per specialization + - Total physicians per specialization + - Total surveys per specialization + - Drill-down to physicians in specialization + """ + # 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') + + # Base queryset + queryset = PhysicianMonthlyRating.objects.filter( + year=year, + month=month + ).select_related('physician', 'physician__hospital', 'physician__department') + + # Apply RBAC filters + user = request.user + if not user.is_px_admin() and user.hospital: + queryset = queryset.filter(physician__hospital=user.hospital) + + # Apply filters + if hospital_filter: + queryset = queryset.filter(physician__hospital_id=hospital_filter) + + # Aggregate by specialization + from django.db.models import Avg, Count, Sum + + specialization_data = {} + for rating in queryset: + spec = rating.physician.specialization + if spec not in specialization_data: + specialization_data[spec] = { + 'specialization': spec, + 'physicians': [], + 'total_physicians': 0, + 'total_surveys': 0, + 'total_positive': 0, + 'total_neutral': 0, + 'total_negative': 0, + 'ratings_sum': 0, + } + + specialization_data[spec]['physicians'].append(rating) + specialization_data[spec]['total_physicians'] += 1 + specialization_data[spec]['total_surveys'] += rating.total_surveys + specialization_data[spec]['total_positive'] += rating.positive_count + specialization_data[spec]['total_neutral'] += rating.neutral_count + specialization_data[spec]['total_negative'] += rating.negative_count + specialization_data[spec]['ratings_sum'] += float(rating.average_rating) + + # Calculate averages + specializations = [] + for spec, data in specialization_data.items(): + avg_rating = data['ratings_sum'] / data['total_physicians'] if data['total_physicians'] > 0 else 0 + specializations.append({ + 'specialization': spec, + 'total_physicians': data['total_physicians'], + 'average_rating': round(avg_rating, 2), + 'total_surveys': data['total_surveys'], + 'positive_count': data['total_positive'], + 'neutral_count': data['total_neutral'], + 'negative_count': data['total_negative'], + 'physicians': sorted(data['physicians'], key=lambda x: x.average_rating, reverse=True) + }) + + # Sort by average rating + specializations.sort(key=lambda x: x['average_rating'], reverse=True) + + # Get filter options + hospitals = Hospital.objects.filter(status='active') + if not user.is_px_admin() and user.hospital: + hospitals = hospitals.filter(id=user.hospital.id) + + context = { + 'specializations': specializations, + 'year': year, + 'month': month, + 'hospitals': hospitals, + 'filters': request.GET, + } + + return render(request, 'physicians/specialization_overview.html', context) + + +@login_required +def department_overview(request): + """ + Department overview - aggregated ratings by department. + + Features: + - Average rating per department + - Total physicians per department + - Total surveys per department + - Drill-down to physicians in department + """ + # 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') + + # Base queryset + queryset = PhysicianMonthlyRating.objects.filter( + year=year, + month=month + ).select_related('physician', 'physician__hospital', 'physician__department') + + # Apply RBAC filters + user = request.user + if not user.is_px_admin() and user.hospital: + queryset = queryset.filter(physician__hospital=user.hospital) + + # Apply filters + if hospital_filter: + queryset = queryset.filter(physician__hospital_id=hospital_filter) + + # Aggregate by department + from django.db.models import Avg, Count, Sum + + department_data = {} + for rating in queryset: + dept = rating.physician.department + if not dept: + continue + + dept_key = str(dept.id) + if dept_key not in department_data: + department_data[dept_key] = { + 'department': dept, + 'physicians': [], + 'total_physicians': 0, + 'total_surveys': 0, + 'total_positive': 0, + 'total_neutral': 0, + 'total_negative': 0, + 'ratings_sum': 0, + } + + department_data[dept_key]['physicians'].append(rating) + department_data[dept_key]['total_physicians'] += 1 + department_data[dept_key]['total_surveys'] += rating.total_surveys + department_data[dept_key]['total_positive'] += rating.positive_count + department_data[dept_key]['total_neutral'] += rating.neutral_count + department_data[dept_key]['total_negative'] += rating.negative_count + department_data[dept_key]['ratings_sum'] += float(rating.average_rating) + + # Calculate averages + departments = [] + for dept_key, data in department_data.items(): + avg_rating = data['ratings_sum'] / data['total_physicians'] if data['total_physicians'] > 0 else 0 + departments.append({ + 'department': data['department'], + 'total_physicians': data['total_physicians'], + 'average_rating': round(avg_rating, 2), + 'total_surveys': data['total_surveys'], + 'positive_count': data['total_positive'], + 'neutral_count': data['total_neutral'], + 'negative_count': data['total_negative'], + 'physicians': sorted(data['physicians'], key=lambda x: x.average_rating, reverse=True) + }) + + # Sort by average rating + departments.sort(key=lambda x: x['average_rating'], reverse=True) + + # Get filter options + hospitals = Hospital.objects.filter(status='active') + if not user.is_px_admin() and user.hospital: + hospitals = hospitals.filter(id=user.hospital.id) + + context = { + 'departments': departments, + 'year': year, + 'month': month, + 'hospitals': hospitals, + 'filters': request.GET, + } + + return render(request, 'physicians/department_overview.html', context) diff --git a/apps/physicians/urls.py b/apps/physicians/urls.py index 7bb16ff..024f635 100644 --- a/apps/physicians/urls.py +++ b/apps/physicians/urls.py @@ -15,6 +15,10 @@ router.register(r'api/physicians/ratings', views.PhysicianMonthlyRatingViewSet, # UI URL patterns urlpatterns = [ + # Overview pages + path('overview/specialization/', ui_views.specialization_overview, name='specialization_overview'), + path('overview/department/', ui_views.department_overview, name='department_overview'), + # Physician management path('', ui_views.physician_list, name='physician_list'), path('/', ui_views.physician_detail, name='physician_detail'), diff --git a/templates/physicians/department_overview.html b/templates/physicians/department_overview.html new file mode 100644 index 0000000..0f2066b --- /dev/null +++ b/templates/physicians/department_overview.html @@ -0,0 +1,177 @@ +{% extends "layouts/base.html" %} +{% load i18n %} +{% load static %} + +{% block title %}{% trans "Department Overview" %} - {% trans "Physicians" %} - PX360{% endblock %} + +{% block content %} +
+ +
+
+ +

+ + {% trans "Department Overview" %} +

+

{% trans "Performance by department for" %} {{ year }}-{{ month|stringformat:"02d" }}

+
+
+ + {% trans "Specialization View" %} + + + {% trans "Back to Physicians" %} + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+
+ + + {% if departments %} + {% for dept_data in departments %} +
+
+
+
+
+ + {{ dept_data.department.name }} +
+ {{ dept_data.department.hospital.name }} +
+
+
+
+ {{ dept_data.average_rating|floatformat:2 }} +
{% trans "Avg Rating" %} +
+
+ {{ dept_data.total_physicians }} +
{% trans "Physicians" %} +
+
+ {{ dept_data.total_surveys }} +
{% trans "Surveys" %} +
+
+
+ {{ dept_data.positive_count }} + {{ dept_data.neutral_count }} + {{ dept_data.negative_count }} +
+ {% trans "Sentiment" %} +
+
+
+
+
+
+
+ + + + + + + + + + + + + + {% for rating in dept_data.physicians %} + + + + + + + + + + {% endfor %} + +
{% trans "Rank" %}{% trans "Physician" %}{% trans "Specialization" %}{% trans "Rating" %}{% trans "Surveys" %}{% trans "Dept Rank" %}{% trans "Actions" %}
+ {% if forloop.counter <= 3 %} + #{{ forloop.counter }} + {% else %} + #{{ forloop.counter }} + {% endif %} + + {{ rating.physician.get_full_name }}
+ {{ rating.physician.license_number }} +
{{ rating.physician.specialization }} + {{ rating.average_rating|floatformat:2 }} + + {{ rating.total_surveys }} + + {% if rating.department_rank %} + #{{ rating.department_rank }} + {% else %} + - + {% endif %} + + + + +
+
+
+
+ {% endfor %} + {% else %} +
+
+ +

{% trans "No department data available for this period" %}

+
+
+ {% endif %} +
+{% endblock %} diff --git a/templates/physicians/physician_list.html b/templates/physicians/physician_list.html index 3d792d1..0c183e5 100644 --- a/templates/physicians/physician_list.html +++ b/templates/physicians/physician_list.html @@ -15,6 +15,14 @@

{% trans "Manage physician profiles and performance" %}

+ {% trans "Leaderboard" %} diff --git a/templates/physicians/specialization_overview.html b/templates/physicians/specialization_overview.html new file mode 100644 index 0000000..53b2476 --- /dev/null +++ b/templates/physicians/specialization_overview.html @@ -0,0 +1,176 @@ +{% extends "layouts/base.html" %} +{% load i18n %} +{% load static %} + +{% block title %}{% trans "Specialization Overview" %} - {% trans "Physicians" %} - PX360{% endblock %} + +{% block content %} +
+ +
+
+ +

+ + {% trans "Specialization Overview" %} +

+

{% trans "Performance by medical specialization for" %} {{ year }}-{{ month|stringformat:"02d" }}

+
+
+ + {% trans "Department View" %} + + + {% trans "Back to Physicians" %} + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+
+ + + {% if specializations %} + {% for spec_data in specializations %} +
+
+
+
+
+ + {{ spec_data.specialization }} +
+
+
+
+
+ {{ spec_data.average_rating|floatformat:2 }} +
{% trans "Avg Rating" %} +
+
+ {{ spec_data.total_physicians }} +
{% trans "Physicians" %} +
+
+ {{ spec_data.total_surveys }} +
{% trans "Surveys" %} +
+
+
+ {{ spec_data.positive_count }} + {{ spec_data.neutral_count }} + {{ spec_data.negative_count }} +
+ {% trans "Sentiment" %} +
+
+
+
+
+
+
+ + + + + + + + + + + + + + {% for rating in spec_data.physicians %} + + + + + + + + + + {% endfor %} + +
{% trans "Rank" %}{% trans "Physician" %}{% trans "Department" %}{% trans "Hospital" %}{% trans "Rating" %}{% trans "Surveys" %}{% trans "Actions" %}
+ {% if forloop.counter <= 3 %} + #{{ forloop.counter }} + {% else %} + #{{ forloop.counter }} + {% endif %} + + {{ rating.physician.get_full_name }}
+ {{ rating.physician.license_number }} +
+ {% if rating.physician.department %} + {{ rating.physician.department.name }} + {% else %} + - + {% endif %} + {{ rating.physician.hospital.name }} + {{ rating.average_rating|floatformat:2 }} + + {{ rating.total_surveys }} + + + + +
+
+
+
+ {% endfor %} + {% else %} +
+
+ +

{% trans "No specialization data available for this period" %}

+
+
+ {% endif %} +
+{% endblock %}