""" Feedback models - Patient feedback and suggestions management This module implements the feedback management system that: - Tracks patient feedback (compliments, suggestions, general feedback) - Manages feedback workflow (submitted → reviewed → acknowledged → closed) - Maintains feedback responses and timeline - Supports attachments and ratings """ from django.conf import settings from django.db import models from django.utils import timezone from apps.core.models import PriorityChoices, TimeStampedModel, UUIDModel class FeedbackType(models.TextChoices): """Feedback type choices""" COMPLIMENT = 'compliment', 'Compliment' SUGGESTION = 'suggestion', 'Suggestion' GENERAL = 'general', 'General Feedback' INQUIRY = 'inquiry', 'Inquiry' SATISFACTION_CHECK = 'satisfaction_check', 'Satisfaction Check' class FeedbackStatus(models.TextChoices): """Feedback status choices""" SUBMITTED = 'submitted', 'Submitted' REVIEWED = 'reviewed', 'Reviewed' ACKNOWLEDGED = 'acknowledged', 'Acknowledged' CLOSED = 'closed', 'Closed' class FeedbackCategory(models.TextChoices): """Feedback category choices""" CLINICAL_CARE = 'clinical_care', 'Clinical Care' STAFF_SERVICE = 'staff_service', 'Staff Service' FACILITY = 'facility', 'Facility & Environment' COMMUNICATION = 'communication', 'Communication' APPOINTMENT = 'appointment', 'Appointment & Scheduling' BILLING = 'billing', 'Billing & Insurance' FOOD_SERVICE = 'food_service', 'Food Service' CLEANLINESS = 'cleanliness', 'Cleanliness' TECHNOLOGY = 'technology', 'Technology & Systems' OTHER = 'other', 'Other' class SentimentChoices(models.TextChoices): """Sentiment analysis choices""" POSITIVE = 'positive', 'Positive' NEUTRAL = 'neutral', 'Neutral' NEGATIVE = 'negative', 'Negative' class Feedback(UUIDModel, TimeStampedModel): """ Feedback model for patient feedback, compliments, and suggestions. Workflow: 1. SUBMITTED - Feedback received 2. REVIEWED - Being reviewed by staff 3. ACKNOWLEDGED - Response provided 4. CLOSED - Feedback closed """ # Patient and encounter information patient = models.ForeignKey( 'organizations.Patient', on_delete=models.CASCADE, related_name='feedbacks', null=True, blank=True, help_text="Patient who provided feedback (optional for anonymous feedback)" ) # Anonymous feedback support is_anonymous = models.BooleanField(default=False) contact_name = models.CharField(max_length=200, blank=True) contact_email = models.EmailField(blank=True) contact_phone = models.CharField(max_length=20, blank=True) encounter_id = models.CharField( max_length=100, blank=True, db_index=True, help_text="Related encounter ID if applicable" ) # Survey linkage (for satisfaction checks after negative surveys) related_survey = models.ForeignKey( 'surveys.SurveyInstance', on_delete=models.SET_NULL, null=True, blank=True, related_name='follow_up_feedbacks', help_text="Survey that triggered this satisfaction check feedback" ) # Organization hospital = models.ForeignKey( 'organizations.Hospital', on_delete=models.CASCADE, related_name='feedbacks' ) department = models.ForeignKey( 'organizations.Department', on_delete=models.SET_NULL, null=True, blank=True, related_name='feedbacks' ) staff = models.ForeignKey( 'organizations.Staff', on_delete=models.SET_NULL, null=True, blank=True, related_name='feedbacks', help_text="Staff member being mentioned in feedback" ) # Feedback details feedback_type = models.CharField( max_length=20, choices=FeedbackType.choices, default=FeedbackType.GENERAL, db_index=True ) title = models.CharField(max_length=500) message = models.TextField(help_text="Feedback message") # Classification category = models.CharField( max_length=50, choices=FeedbackCategory.choices, db_index=True ) subcategory = models.CharField(max_length=100, blank=True) # Rating (1-5 stars) rating = models.IntegerField( null=True, blank=True, help_text="Rating from 1 to 5 stars" ) # Priority priority = models.CharField( max_length=20, choices=PriorityChoices.choices, default=PriorityChoices.MEDIUM, db_index=True ) # Sentiment analysis sentiment = models.CharField( max_length=20, choices=SentimentChoices.choices, default=SentimentChoices.NEUTRAL, db_index=True, help_text="Sentiment analysis result" ) sentiment_score = models.FloatField( null=True, blank=True, help_text="Sentiment score from -1 (negative) to 1 (positive)" ) # Status and workflow status = models.CharField( max_length=20, choices=FeedbackStatus.choices, default=FeedbackStatus.SUBMITTED, db_index=True ) # Assignment assigned_to = models.ForeignKey( 'accounts.User', on_delete=models.SET_NULL, null=True, blank=True, related_name='assigned_feedbacks' ) assigned_at = models.DateTimeField(null=True, blank=True) # Review tracking reviewed_at = models.DateTimeField(null=True, blank=True) reviewed_by = models.ForeignKey( 'accounts.User', on_delete=models.SET_NULL, null=True, blank=True, related_name='reviewed_feedbacks' ) # Acknowledgment acknowledged_at = models.DateTimeField(null=True, blank=True) acknowledged_by = models.ForeignKey( 'accounts.User', on_delete=models.SET_NULL, null=True, blank=True, related_name='acknowledged_feedbacks' ) # Closure closed_at = models.DateTimeField(null=True, blank=True) closed_by = models.ForeignKey( 'accounts.User', on_delete=models.SET_NULL, null=True, blank=True, related_name='closed_feedbacks' ) # Flags is_featured = models.BooleanField( default=False, help_text="Feature this feedback (e.g., for testimonials)" ) is_public = models.BooleanField( default=False, help_text="Make this feedback public" ) requires_follow_up = models.BooleanField(default=False) # Metadata source = models.CharField( max_length=50, default='web', help_text="Source of feedback (web, mobile, kiosk, etc.)" ) metadata = models.JSONField(default=dict, blank=True) # Soft delete is_deleted = models.BooleanField(default=False, db_index=True) deleted_at = models.DateTimeField(null=True, blank=True) deleted_by = models.ForeignKey( 'accounts.User', on_delete=models.SET_NULL, null=True, blank=True, related_name='deleted_feedbacks' ) class Meta: ordering = ['-created_at'] indexes = [ models.Index(fields=['status', '-created_at']), models.Index(fields=['hospital', 'status', '-created_at']), models.Index(fields=['feedback_type', '-created_at']), models.Index(fields=['sentiment', '-created_at']), models.Index(fields=['is_deleted', '-created_at']), ] verbose_name_plural = 'Feedback' def __str__(self): if self.patient: return f"{self.title} - {self.patient.get_full_name()} ({self.feedback_type})" return f"{self.title} - Anonymous ({self.feedback_type})" def get_contact_name(self): """Get contact name (patient or anonymous)""" if self.patient: return self.patient.get_full_name() return self.contact_name or "Anonymous" def soft_delete(self, user=None): """Soft delete feedback""" self.is_deleted = True self.deleted_at = timezone.now() self.deleted_by = user self.save(update_fields=['is_deleted', 'deleted_at', 'deleted_by']) class FeedbackAttachment(UUIDModel, TimeStampedModel): """Feedback attachment (images, documents, etc.)""" feedback = models.ForeignKey( Feedback, on_delete=models.CASCADE, related_name='attachments' ) file = models.FileField(upload_to='feedback/%Y/%m/%d/') filename = models.CharField(max_length=500) file_type = models.CharField(max_length=100, blank=True) file_size = models.IntegerField(help_text="File size in bytes") uploaded_by = models.ForeignKey( 'accounts.User', on_delete=models.SET_NULL, null=True, blank=True, related_name='feedback_attachments' ) description = models.TextField(blank=True) class Meta: ordering = ['-created_at'] def __str__(self): return f"{self.feedback} - {self.filename}" class FeedbackResponse(UUIDModel, TimeStampedModel): """ Feedback response/timeline entry. Tracks all responses, status changes, and communications. """ feedback = models.ForeignKey( Feedback, on_delete=models.CASCADE, related_name='responses' ) # Response details response_type = models.CharField( max_length=50, choices=[ ('status_change', 'Status Change'), ('assignment', 'Assignment'), ('note', 'Internal Note'), ('response', 'Response to Patient'), ('acknowledgment', 'Acknowledgment'), ], db_index=True ) message = models.TextField() # User who made the response created_by = models.ForeignKey( 'accounts.User', on_delete=models.SET_NULL, null=True, related_name='feedback_responses' ) # Status change tracking old_status = models.CharField(max_length=20, blank=True) new_status = models.CharField(max_length=20, blank=True) # Visibility is_internal = models.BooleanField( default=False, help_text="Internal note (not visible to patient)" ) # Metadata metadata = models.JSONField(default=dict, blank=True) class Meta: ordering = ['-created_at'] indexes = [ models.Index(fields=['feedback', '-created_at']), ] def __str__(self): return f"{self.feedback} - {self.response_type} - {self.created_at.strftime('%Y-%m-%d %H:%M')}"