""" Documentation Delay Tracking Service. This service monitors clinical documentation completion and alerts senior therapists when documentation is delayed beyond 5 working days. """ from datetime import datetime, timedelta from django.db import models from django.utils import timezone from django.utils.translation import gettext_lazy as _ from core.models import UUIDPrimaryKeyMixin, TimeStampedMixin, TenantOwnedMixin class DocumentationDelayTracker(UUIDPrimaryKeyMixin, TimeStampedMixin, TenantOwnedMixin): """ Tracks documentation delays and sends alerts to senior therapists. """ class DocumentType(models.TextChoices): SESSION_NOTE = 'SESSION_NOTE', _('Session Note') ASSESSMENT = 'ASSESSMENT', _('Assessment') PROGRESS_REPORT = 'PROGRESS_REPORT', _('Progress Report') DISCHARGE_SUMMARY = 'DISCHARGE_SUMMARY', _('Discharge Summary') MDT_NOTE = 'MDT_NOTE', _('MDT Note') class Status(models.TextChoices): PENDING = 'PENDING', _('Pending') OVERDUE = 'OVERDUE', _('Overdue') COMPLETED = 'COMPLETED', _('Completed') ESCALATED = 'ESCALATED', _('Escalated') # Document Reference (Generic FK) document_type = models.CharField( max_length=30, choices=DocumentType.choices, verbose_name=_("Document Type") ) document_id = models.UUIDField( verbose_name=_("Document ID"), help_text=_("UUID of the document being tracked") ) # Responsible Staff assigned_to = models.ForeignKey( 'core.User', on_delete=models.CASCADE, related_name='assigned_documentation', verbose_name=_("Assigned To"), help_text=_("Therapist responsible for completing the documentation") ) senior_therapist = models.ForeignKey( 'core.User', on_delete=models.CASCADE, related_name='supervised_documentation', verbose_name=_("Senior Therapist"), help_text=_("Senior therapist to be notified of delays") ) # Timing due_date = models.DateField( verbose_name=_("Due Date"), help_text=_("Date by which documentation should be completed") ) completed_at = models.DateTimeField( null=True, blank=True, verbose_name=_("Completed At") ) # Status & Alerts status = models.CharField( max_length=20, choices=Status.choices, default=Status.PENDING, verbose_name=_("Status") ) days_overdue = models.IntegerField( default=0, verbose_name=_("Days Overdue") ) alert_sent_at = models.DateTimeField( null=True, blank=True, verbose_name=_("Alert Sent At") ) alert_count = models.PositiveIntegerField( default=0, verbose_name=_("Alert Count"), help_text=_("Number of alerts sent to senior") ) last_alert_at = models.DateTimeField( null=True, blank=True, verbose_name=_("Last Alert At") ) # Escalation escalated_at = models.DateTimeField( null=True, blank=True, verbose_name=_("Escalated At") ) escalated_to = models.ForeignKey( 'core.User', on_delete=models.SET_NULL, null=True, blank=True, related_name='escalated_documentation', verbose_name=_("Escalated To"), help_text=_("Clinical Coordinator or Admin if escalated") ) # Notes notes = models.TextField( blank=True, verbose_name=_("Notes") ) class Meta: verbose_name = _("Documentation Delay Tracker") verbose_name_plural = _("Documentation Delay Trackers") ordering = ['-days_overdue', 'due_date'] indexes = [ models.Index(fields=['assigned_to', 'status']), models.Index(fields=['senior_therapist', 'status']), models.Index(fields=['status', 'due_date']), models.Index(fields=['tenant', 'status']), ] def __str__(self): return f"{self.get_document_type_display()} - {self.assigned_to} - {self.days_overdue} days overdue" def calculate_days_overdue(self): """Calculate how many working days the documentation is overdue.""" from datetime import date if self.status == self.Status.COMPLETED: return 0 today = date.today() if today <= self.due_date: return 0 # Calculate working days (excluding weekends) days = 0 current_date = self.due_date + timedelta(days=1) while current_date <= today: # Skip Friday and Saturday (weekend in Saudi Arabia) if current_date.weekday() not in [4, 5]: # 4=Friday, 5=Saturday days += 1 current_date += timedelta(days=1) return days def update_status(self): """Update status based on current state.""" self.days_overdue = self.calculate_days_overdue() if self.status == self.Status.COMPLETED: return if self.days_overdue >= 5: if self.days_overdue >= 10 and not self.escalated_at: self.status = self.Status.ESCALATED else: self.status = self.Status.OVERDUE else: self.status = self.Status.PENDING self.save(update_fields=['status', 'days_overdue']) def mark_completed(self): """Mark documentation as completed.""" self.status = self.Status.COMPLETED self.completed_at = timezone.now() self.days_overdue = 0 self.save() def send_alert(self): """Record that an alert was sent.""" self.alert_count += 1 self.last_alert_at = timezone.now() if self.alert_count == 1: self.alert_sent_at = self.last_alert_at self.save(update_fields=['alert_count', 'last_alert_at', 'alert_sent_at']) def escalate(self, escalated_to_user): """Escalate to Clinical Coordinator or Admin.""" self.status = self.Status.ESCALATED self.escalated_at = timezone.now() self.escalated_to = escalated_to_user self.save() @classmethod def get_overdue_documentation(cls, tenant=None, senior_therapist=None): """ Get all overdue documentation. Args: tenant: Optional tenant filter senior_therapist: Optional senior therapist filter Returns: QuerySet: Overdue documentation trackers """ queryset = cls.objects.filter( status__in=[cls.Status.OVERDUE, cls.Status.ESCALATED] ) if tenant: queryset = queryset.filter(tenant=tenant) if senior_therapist: queryset = queryset.filter(senior_therapist=senior_therapist) return queryset.order_by('-days_overdue') @classmethod def get_pending_alerts(cls, tenant=None): """ Get documentation that needs alerts sent (>5 days overdue, no recent alert). Args: tenant: Optional tenant filter Returns: QuerySet: Documentation needing alerts """ from datetime import date queryset = cls.objects.filter( status__in=[cls.Status.OVERDUE, cls.Status.ESCALATED], days_overdue__gte=5 ) # Only send alerts once per day yesterday = timezone.now() - timedelta(days=1) queryset = queryset.filter( Q(last_alert_at__isnull=True) | Q(last_alert_at__lt=yesterday) ) if tenant: queryset = queryset.filter(tenant=tenant) return queryset @classmethod def create_tracker_for_session(cls, session, assigned_to, senior_therapist, tenant): """ Create a documentation tracker for a therapy session. Args: session: Session instance (OT, ABA, SLP, etc.) assigned_to: User who should complete the documentation senior_therapist: Senior therapist to notify tenant: Tenant instance Returns: DocumentationDelayTracker instance """ from datetime import date # Documentation due 2 working days after session due_date = date.today() + timedelta(days=2) # Adjust for weekends while due_date.weekday() in [4, 5]: # Friday, Saturday due_date += timedelta(days=1) tracker = cls.objects.create( document_type=cls.DocumentType.SESSION_NOTE, document_id=session.id, assigned_to=assigned_to, senior_therapist=senior_therapist, tenant=tenant, due_date=due_date ) return tracker