131 lines
4.9 KiB
Python
131 lines
4.9 KiB
Python
"""
|
|
Observations Celery tasks - SLA tracking and notifications.
|
|
|
|
This module implements tasks for:
|
|
- Checking overdue observations
|
|
- Sending SLA reminder emails
|
|
- Escalation handling
|
|
"""
|
|
|
|
import logging
|
|
|
|
from celery import shared_task
|
|
from django.utils import timezone
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@shared_task
|
|
def check_overdue_observations():
|
|
"""
|
|
Periodic task to check for overdue observations.
|
|
|
|
Runs every 15 minutes (configured in config/celery.py).
|
|
Updates is_overdue flag and sets breached_at for observations past their SLA deadline.
|
|
"""
|
|
from apps.observations.models import Observation, ObservationStatus
|
|
|
|
active_statuses = [
|
|
ObservationStatus.NEW,
|
|
ObservationStatus.TRIAGED,
|
|
ObservationStatus.ASSIGNED,
|
|
ObservationStatus.IN_PROGRESS,
|
|
]
|
|
active_observations = Observation.objects.filter(
|
|
status__in=active_statuses,
|
|
due_at__isnull=False,
|
|
).select_related("hospital", "assigned_department")
|
|
|
|
overdue_count = 0
|
|
|
|
for observation in active_observations:
|
|
if observation.check_overdue():
|
|
overdue_count += 1
|
|
logger.warning(
|
|
f"Observation {observation.id} is overdue: {observation.tracking_code} (due: {observation.due_at})"
|
|
)
|
|
|
|
if overdue_count > 0:
|
|
logger.info(f"Found {overdue_count} overdue observations")
|
|
|
|
return {"overdue_count": overdue_count}
|
|
|
|
|
|
@shared_task
|
|
def send_observation_sla_reminders():
|
|
"""
|
|
Periodic task to send SLA reminder emails for observations approaching deadline.
|
|
|
|
Runs every hour (configured in config/celery.py).
|
|
"""
|
|
from apps.observations.models import Observation, ObservationStatus, ObservationSLAConfig
|
|
from apps.notifications.services import NotificationService
|
|
|
|
now = timezone.now()
|
|
active_statuses = [
|
|
ObservationStatus.NEW,
|
|
ObservationStatus.TRIAGED,
|
|
ObservationStatus.ASSIGNED,
|
|
ObservationStatus.IN_PROGRESS,
|
|
]
|
|
|
|
active_observations = Observation.objects.filter(
|
|
status__in=active_statuses,
|
|
due_at__isnull=False,
|
|
is_overdue=False,
|
|
).select_related("hospital", "assigned_to")
|
|
|
|
first_reminder_count = 0
|
|
second_reminder_count = 0
|
|
|
|
for observation in active_observations:
|
|
config = observation.get_sla_config()
|
|
if not config:
|
|
continue
|
|
|
|
first_reminder_hours = config.get_first_reminder_hours_after()
|
|
second_reminder_hours = config.get_second_reminder_hours_after()
|
|
|
|
if first_reminder_hours > 0 and observation.reminder_sent_at is None:
|
|
hours_since_creation = (now - observation.created_at).total_seconds() / 3600
|
|
if hours_since_creation >= first_reminder_hours:
|
|
if observation.assigned_to and observation.assigned_to.email:
|
|
try:
|
|
NotificationService.send_email(
|
|
email=observation.assigned_to.email,
|
|
subject=f"SLA Reminder - Observation {observation.tracking_code}",
|
|
message=f"Observation '{observation.title or observation.description[:50]}' is due at {observation.due_at}. Please take action.",
|
|
related_object=observation,
|
|
)
|
|
observation.reminder_sent_at = now
|
|
observation.save(update_fields=["reminder_sent_at"])
|
|
first_reminder_count += 1
|
|
except Exception as e:
|
|
logger.error(f"Failed to send observation reminder: {e}")
|
|
|
|
if second_reminder_hours > 0 and observation.second_reminder_sent_at is None:
|
|
hours_since_creation = (now - observation.created_at).total_seconds() / 3600
|
|
if hours_since_creation >= second_reminder_hours:
|
|
if observation.assigned_to and observation.assigned_to.email:
|
|
try:
|
|
NotificationService.send_email(
|
|
email=observation.assigned_to.email,
|
|
subject=f"URGENT: SLA Reminder - Observation {observation.tracking_code}",
|
|
message=f"Observation '{observation.title or observation.description[:50]}' is due at {observation.due_at}. URGENT action required.",
|
|
related_object=observation,
|
|
)
|
|
observation.second_reminder_sent_at = now
|
|
observation.save(update_fields=["second_reminder_sent_at"])
|
|
second_reminder_count += 1
|
|
except Exception as e:
|
|
logger.error(f"Failed to send second observation reminder: {e}")
|
|
|
|
logger.info(
|
|
f"Sent {first_reminder_count} first reminders and {second_reminder_count} second reminders for observations"
|
|
)
|
|
|
|
return {
|
|
"first_reminder_count": first_reminder_count,
|
|
"second_reminder_count": second_reminder_count,
|
|
}
|