262 lines
8.7 KiB
Python
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
|