agdar/referrals/tasks.py
2025-11-02 14:35:35 +03:00

262 lines
8.7 KiB
Python

"""
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