HH/apps/observations/tasks.py
2026-03-28 14:03:56 +03:00

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,
}