""" Referrals Celery tasks for background processing. This module contains tasks for referral notifications, reminders, and statistics updates. """ import logging from datetime import datetime, timedelta from typing import Dict from celery import shared_task from django.db.models import Count, Q from django.utils import timezone from core.tasks import create_notification_task, send_email_task from referrals.models import Referral logger = logging.getLogger(__name__) @shared_task(bind=True, max_retries=3) def send_referral_notification(self, referral_id: str) -> bool: """ Send referral notification to receiving clinic. Args: referral_id: UUID of the referral Returns: bool: True if notification was sent successfully """ try: referral = Referral.objects.select_related( 'patient', 'from_clinic', 'to_clinic', 'from_provider', 'to_provider' ).get(id=referral_id) # Prepare notification message message = ( f"New Referral Received\n\n" f"Patient: {referral.patient.full_name_en} (MRN: {referral.patient.mrn})\n" f"From: {referral.from_clinic.name_en} - {referral.from_provider.get_full_name()}\n" f"Reason: {referral.reason}\n" f"Urgency: {referral.get_urgency_display()}\n" f"Type: {referral.get_referral_type_display()}\n\n" f"Please review and accept/reject this referral." ) # Notify receiving provider if specified if referral.to_provider: create_notification_task.delay( user_id=str(referral.to_provider.id), title="New Referral Received", message=message, notification_type='INFO', related_object_type='referral', related_object_id=str(referral.id), ) # Send email if referral.to_provider.email: send_email_task.delay( subject="New Referral Received", message=message, recipient_list=[referral.to_provider.email], ) logger.info(f"Referral notification sent: {referral_id}") return True except Referral.DoesNotExist: logger.error(f"Referral {referral_id} not found") return False except Exception as exc: logger.error(f"Failed to send referral notification for {referral_id}: {exc}") raise self.retry(exc=exc, countdown=300) @shared_task def check_pending_referrals() -> int: """ Check for pending referrals and send reminders. This task runs daily at 10:00 AM to check for referrals that have been pending for more than 24 hours. Returns: int: Number of reminders sent """ # Get referrals pending for more than 24 hours cutoff_time = timezone.now() - timedelta(hours=24) pending_referrals = Referral.objects.filter( status='PENDING', created_at__lt=cutoff_time ).select_related('patient', 'from_clinic', 'to_clinic', 'to_provider') count = 0 for referral in pending_referrals: # Send reminder send_referral_reminder.delay(str(referral.id)) count += 1 if count > 0: logger.info(f"Sent {count} referral reminders") return count @shared_task(bind=True, max_retries=3) def send_referral_reminder(self, referral_id: str) -> bool: """ Send reminder for pending referral. Args: referral_id: UUID of the referral Returns: bool: True if reminder was sent successfully """ try: referral = Referral.objects.select_related( 'patient', 'from_clinic', 'to_clinic', 'to_provider' ).get(id=referral_id) if referral.status != 'PENDING': logger.info(f"Skipping reminder for referral {referral_id} - status: {referral.status}") return False hours_pending = (timezone.now() - referral.created_at).total_seconds() / 3600 message = ( f"Referral Reminder\n\n" f"A referral has been pending for {int(hours_pending)} hours.\n\n" f"Patient: {referral.patient.full_name_en} (MRN: {referral.patient.mrn})\n" f"From: {referral.from_clinic.name_en}\n" f"Reason: {referral.reason}\n" f"Urgency: {referral.get_urgency_display()}\n\n" f"Please review and respond to this referral." ) # Notify receiving provider if referral.to_provider: create_notification_task.delay( user_id=str(referral.to_provider.id), title="Referral Reminder", message=message, notification_type='WARNING', related_object_type='referral', related_object_id=str(referral.id), ) # Send email if referral.to_provider.email: send_email_task.delay( subject="Referral Reminder - Action Required", message=message, recipient_list=[referral.to_provider.email], ) logger.info(f"Referral reminder sent: {referral_id}") return True except Referral.DoesNotExist: logger.error(f"Referral {referral_id} not found") return False except Exception as exc: logger.error(f"Failed to send referral reminder for {referral_id}: {exc}") raise self.retry(exc=exc, countdown=300) @shared_task def update_referral_statistics() -> Dict: """ Update referral statistics for reporting. Returns: dict: Referral statistics """ # Calculate statistics stats = { 'total': Referral.objects.count(), 'by_status': { 'pending': Referral.objects.filter(status='PENDING').count(), 'accepted': Referral.objects.filter(status='ACCEPTED').count(), 'rejected': Referral.objects.filter(status='REJECTED').count(), 'completed': Referral.objects.filter(status='COMPLETED').count(), 'cancelled': Referral.objects.filter(status='CANCELLED').count(), }, 'by_urgency': { 'routine': Referral.objects.filter(urgency='ROUTINE').count(), 'urgent': Referral.objects.filter(urgency='URGENT').count(), 'emergency': Referral.objects.filter(urgency='EMERGENCY').count(), }, 'by_type': { 'internal': Referral.objects.filter(referral_type='INTERNAL').count(), 'external': Referral.objects.filter(referral_type='EXTERNAL').count(), }, 'pending_over_24h': Referral.objects.filter( status='PENDING', created_at__lt=timezone.now() - timedelta(hours=24) ).count(), } logger.info(f"Referral statistics updated: {stats}") return stats @shared_task def send_referral_statistics(period: str = 'weekly') -> Dict: """ Send referral statistics report. Args: period: Report period ('weekly', 'monthly') Returns: dict: Statistics sent """ # Determine date range if period == 'weekly': start_date = timezone.now() - timedelta(days=7) elif period == 'monthly': start_date = timezone.now() - timedelta(days=30) else: start_date = timezone.now() - timedelta(days=7) # Get referrals for the period referrals = Referral.objects.filter(created_at__gte=start_date) # Calculate statistics stats = { 'period': period, 'start_date': str(start_date.date()), 'end_date': str(timezone.now().date()), 'total_referrals': referrals.count(), 'by_status': { 'pending': referrals.filter(status='PENDING').count(), 'accepted': referrals.filter(status='ACCEPTED').count(), 'rejected': referrals.filter(status='REJECTED').count(), 'completed': referrals.filter(status='COMPLETED').count(), }, 'by_urgency': { 'routine': referrals.filter(urgency='ROUTINE').count(), 'urgent': referrals.filter(urgency='URGENT').count(), 'emergency': referrals.filter(urgency='EMERGENCY').count(), }, 'acceptance_rate': 0, } # Calculate acceptance rate total_responded = referrals.filter(status__in=['ACCEPTED', 'REJECTED']).count() if total_responded > 0: accepted = referrals.filter(status='ACCEPTED').count() stats['acceptance_rate'] = round((accepted / total_responded) * 100, 2) logger.info(f"Referral statistics report generated for {period}: {stats}") # TODO: Send report via email to management return stats