""" Complaints Celery tasks This module contains tasks for: - Checking overdue complaints - Sending SLA reminders - Triggering resolution satisfaction surveys - Creating PX actions from complaints """ import logging from celery import shared_task from django.db import transaction from django.utils import timezone logger = logging.getLogger(__name__) @shared_task def check_overdue_complaints(): """ Periodic task to check for overdue complaints. Runs every 15 minutes (configured in config/celery.py). Updates is_overdue flag for complaints past their SLA deadline. """ from apps.complaints.models import Complaint, ComplaintStatus # Get active complaints (not closed or cancelled) active_complaints = Complaint.objects.filter( status__in=[ComplaintStatus.OPEN, ComplaintStatus.IN_PROGRESS, ComplaintStatus.RESOLVED] ).select_related('hospital', 'patient') overdue_count = 0 for complaint in active_complaints: if complaint.check_overdue(): overdue_count += 1 logger.warning( f"Complaint {complaint.id} is overdue: {complaint.title} " f"(due: {complaint.due_at})" ) # TODO: Trigger escalation (Phase 6) # from apps.px_action_center.tasks import escalate_complaint # escalate_complaint.delay(str(complaint.id)) if overdue_count > 0: logger.info(f"Found {overdue_count} overdue complaints") return {'overdue_count': overdue_count} @shared_task def send_complaint_resolution_survey(complaint_id): """ Send resolution satisfaction survey when complaint is closed. This task is triggered when a complaint status changes to CLOSED. Args: complaint_id: UUID of the Complaint Returns: dict: Result with survey_instance_id """ from apps.complaints.models import Complaint from apps.core.services import create_audit_log from apps.surveys.models import SurveyInstance, SurveyTemplate try: complaint = Complaint.objects.select_related( 'patient', 'hospital' ).get(id=complaint_id) # Check if survey already sent if complaint.resolution_survey: logger.info(f"Resolution survey already sent for complaint {complaint_id}") return {'status': 'skipped', 'reason': 'already_sent'} # Get resolution satisfaction survey template try: survey_template = SurveyTemplate.objects.get( hospital=complaint.hospital, survey_type='complaint_resolution', is_active=True ) except SurveyTemplate.DoesNotExist: logger.warning( f"No resolution satisfaction survey template found for hospital {complaint.hospital.name}" ) return {'status': 'skipped', 'reason': 'no_template'} # Create survey instance with transaction.atomic(): survey_instance = SurveyInstance.objects.create( survey_template=survey_template, patient=complaint.patient, encounter_id=complaint.encounter_id, delivery_channel='sms', # Default recipient_phone=complaint.patient.phone, recipient_email=complaint.patient.email, metadata={ 'complaint_id': str(complaint.id), 'complaint_title': complaint.title } ) # Link survey to complaint complaint.resolution_survey = survey_instance complaint.resolution_survey_sent_at = timezone.now() complaint.save(update_fields=['resolution_survey', 'resolution_survey_sent_at']) # Send survey from apps.notifications.services import NotificationService notification_log = NotificationService.send_survey_invitation( survey_instance=survey_instance, language='en' # TODO: Get from patient preference ) # Update survey status survey_instance.status = 'active' survey_instance.sent_at = timezone.now() survey_instance.save(update_fields=['status', 'sent_at']) # Log audit event create_audit_log( event_type='survey_sent', description=f"Resolution satisfaction survey sent for complaint: {complaint.title}", content_object=survey_instance, metadata={ 'complaint_id': str(complaint.id), 'survey_template': survey_template.name } ) logger.info( f"Resolution satisfaction survey sent for complaint {complaint.id}" ) return { 'status': 'sent', 'survey_instance_id': str(survey_instance.id), 'notification_log_id': str(notification_log.id) } except Complaint.DoesNotExist: error_msg = f"Complaint {complaint_id} not found" logger.error(error_msg) return {'status': 'error', 'reason': error_msg} except Exception as e: error_msg = f"Error sending resolution survey: {str(e)}" logger.error(error_msg, exc_info=True) return {'status': 'error', 'reason': error_msg} @shared_task def create_action_from_complaint(complaint_id): """ Create PX Action from complaint (if configured). This task is triggered when a complaint is created, if the hospital configuration requires automatic action creation. Args: complaint_id: UUID of the Complaint Returns: dict: Result with action_id """ # TODO: Implement in Phase 6 logger.info(f"Should create PX Action from complaint {complaint_id}") return {'status': 'pending_phase_6'}