""" Management command to test staff matching functionality in complaints. This command creates a test complaint with 2-3 staff members mentioned and verifies if the AI-based staff matching is working correctly. """ import random import uuid from datetime import datetime from django.core.management.base import BaseCommand, CommandError from django.db import transaction from django.utils import timezone from apps.accounts.models import User from apps.complaints.models import Complaint, ComplaintCategory, ComplaintUpdate from apps.organizations.models import Hospital, Department, Staff from apps.px_sources.models import PXSource from apps.core.ai_service import AIService # English complaint templates with placeholders for staff names ENGLISH_COMPLAINT_TEMPLATES = [ { 'title': 'Issues with multiple staff members', 'description': 'I had a very unpleasant experience during my stay. Nurse {staff1_name} was rude and dismissive when I asked for pain medication. Later, Dr. {staff2_name} did not explain my treatment plan properly and seemed rushed. The third staff member, {staff3_name}, was actually helpful but the overall experience was poor.', 'category': 'staff_behavior', 'severity': 'high', 'priority': 'high' }, { 'title': 'Excellent care from nursing team', 'description': 'I want to commend the excellent care I received. Nurse {staff1_name} was particularly attentive and caring throughout my stay. {staff2_name} also went above and beyond to ensure my comfort. Dr. {staff3_name} was thorough and took time to answer all my questions.', 'category': 'clinical_care', 'severity': 'low', 'priority': 'low' }, { 'title': 'Mixed experience with hospital staff', 'description': 'My experience was mixed. Nurse {staff1_name} was professional and efficient, but {staff2_name} made a medication error that was concerning. Dr. {staff3_name} was helpful in resolving the situation, but the initial error was unacceptable.', 'category': 'clinical_care', 'severity': 'high', 'priority': 'high' } ] # Arabic complaint templates with placeholders for staff names ARABIC_COMPLAINT_TEMPLATES = [ { 'title': 'مشاكل مع عدة موظفين', 'description': 'كانت لدي تجربة غير سارة جداً خلال إقامتي. الممرضة {staff1_name} كانت غير مهذبة ومتجاهلة عندما طلبت دواء للم. لاحقاً، د. {staff2_name} لم يوضح خطة علاجي بشكل صحيح وبدو متسرع. كان الموظف الثالث {staff3_name} مفيداً فعلاً ولكن التجربة العامة كانت سيئة.', 'category': 'staff_behavior', 'severity': 'high', 'priority': 'high' }, { 'title': 'رعاية ممتازة من فريق التمريض', 'description': 'أريد أن أشكر الرعاية الممتازة التي تلقيتها. الممرضة {staff1_name} كانت مهتمة وراعية بشكل خاص طوال إقامتي. {staff2_name} أيضاً بذل ما هو أبعد من المتوقع لضمان راحتي. د. {staff3_name} كان دقيقاً وأخذ وقتاً للإجابة على جميع أسئلتي.', 'category': 'clinical_care', 'severity': 'low', 'priority': 'low' }, { 'title': 'تجربة مختلطة مع موظفي المستشفى', 'description': 'كانت تجربتي مختلطة. الممرضة {staff1_name} كانت مهنية وفعالة، لكن {staff2_name} ارتكب خطأ في الدواء كان مقلقاً. د. {staff3_name} كان مفيداً في حل الموقف، لكن الخطأ الأولي كان غير مقبول.', 'category': 'clinical_care', 'severity': 'high', 'priority': 'high' } ] class Command(BaseCommand): help = 'Test staff matching functionality by creating a complaint with mentioned staff' def add_arguments(self, parser): parser.add_argument( '--hospital-code', type=str, help='Target hospital code (default: first active hospital)' ) parser.add_argument( '--staff-count', type=int, default=3, help='Number of staff to test (2 or 3, default: 3)' ) parser.add_argument( '--language', type=str, default='en', choices=['en', 'ar'], help='Complaint language (en/ar, default: en)' ) parser.add_argument( '--dry-run', action='store_true', help='Preview without creating complaint' ) parser.add_argument( '--template-index', type=int, help='Template index to use (0-2, default: random)' ) def handle(self, *args, **options): hospital_code = options['hospital_code'] staff_count = options['staff_count'] language = options['language'] dry_run = options['dry_run'] template_index = options['template_index'] # Validate staff count if staff_count not in [2, 3]: self.stdout.write(self.style.ERROR("staff-count must be 2 or 3")) return self.stdout.write(f"\n{'='*80}") self.stdout.write("🧪 STAFF MATCHING TEST COMMAND") self.stdout.write(f"{'='*80}\n") # Get hospital if hospital_code: hospital = Hospital.objects.filter(code=hospital_code).first() if not hospital: self.stdout.write( self.style.ERROR(f"Hospital with code '{hospital_code}' not found") ) return else: hospital = Hospital.objects.filter(status='active').first() if not hospital: self.stdout.write( self.style.ERROR("No active hospitals found") ) return self.stdout.write(f"🏥 Hospital: {hospital.name} (Code: {hospital.code})") # Get active staff from hospital all_staff = Staff.objects.filter(hospital=hospital, status='active') if all_staff.count() < staff_count: self.stdout.write( self.style.ERROR( f"Not enough staff found. Found {all_staff.count()}, need {staff_count}" ) ) return # Select random staff selected_staff = random.sample(list(all_staff), staff_count) self.stdout.write(f"\n👥 Selected Staff ({staff_count} members):") for i, staff in enumerate(selected_staff, 1): if language == 'ar' and staff.first_name_ar: name = f"{staff.first_name_ar} {staff.last_name_ar}" name_en = f"{staff.first_name} {staff.last_name}" else: name = f"{staff.first_name} {staff.last_name}" name_en = name self.stdout.write( f" {i}. {name} (EN: {name_en})" ) self.stdout.write(f" ID: {staff.id}") self.stdout.write(f" Job Title: {staff.job_title}") self.stdout.write(f" Department: {staff.department.name if staff.department else 'N/A'}") # Select template templates = ARABIC_COMPLAINT_TEMPLATES if language == 'ar' else ENGLISH_COMPLAINT_TEMPLATES if template_index is not None: if 0 <= template_index < len(templates): template = templates[template_index] else: self.stdout.write( self.style.WARNING(f"Template index {template_index} out of range, using random") ) template = random.choice(templates) else: template = random.choice(templates) # Prepare complaint data complaint_data = self.prepare_complaint( template=template, staff=selected_staff, hospital=hospital, language=language ) self.stdout.write(f"\n📋 Complaint Details:") self.stdout.write(f" Title: {complaint_data['title']}") self.stdout.write(f" Category: {complaint_data['category']}") self.stdout.write(f" Severity: {complaint_data['severity']}") self.stdout.write(f" Priority: {complaint_data['priority']}") self.stdout.write(f"\n Description:") self.stdout.write(f" {complaint_data['description']}") # Test staff matching self.stdout.write(f"\n{'='*80}") self.stdout.write("🔍 STAFF MATCHING TEST") self.stdout.write(f"{'='*80}\n") from apps.complaints.tasks import match_staff_from_name matched_staff = [] unmatched_staff = [] for staff in selected_staff: if language == 'ar' and staff.first_name_ar: name_to_match = f"{staff.first_name_ar} {staff.last_name_ar}" else: name_to_match = f"{staff.first_name} {staff.last_name}" self.stdout.write(f"\n🔎 Testing: '{name_to_match}'") self.stdout.write(f" Staff ID: {staff.id}") # Test matching matches, confidence, method = match_staff_from_name( staff_name=name_to_match, hospital_id=str(hospital.id), department_name=None, return_all=True ) if matches: found = any(m['id'] == str(staff.id) for m in matches) if found: self.stdout.write( self.style.SUCCESS(f" ✅ MATCHED! (confidence: {confidence:.2f}, method: {method})") ) matched_staff.append({ 'staff': staff, 'confidence': confidence, 'method': method }) else: self.stdout.write( self.style.WARNING(f" ⚠️ Found {len(matches)} matches but not the correct one") ) for i, match in enumerate(matches[:3], 1): self.stdout.write(f" {i}. {match['name_en']} (confidence: {match['confidence']:.2f})") unmatched_staff.append(staff) else: self.stdout.write( self.style.ERROR(f" ❌ NO MATCHES (confidence: {confidence:.2f}, method: {method})") ) unmatched_staff.append(staff) # Summary self.stdout.write(f"\n{'='*80}") self.stdout.write("📊 TEST SUMMARY") self.stdout.write(f"{'='*80}\n") self.stdout.write(f"Total staff tested: {len(selected_staff)}") self.stdout.write(f"Matched: {len(matched_staff)}") self.stdout.write(f"Unmatched: {len(unmatched_staff)}") if matched_staff: self.stdout.write(f"\n✅ Matched Staff:") for item in matched_staff: staff = item['staff'] name = f"{staff.first_name} {staff.last_name}" self.stdout.write(f" - {name} (confidence: {item['confidence']:.2f}, method: {item['method']})") if unmatched_staff: self.stdout.write(f"\n❌ Unmatched Staff:") for staff in unmatched_staff: name = f"{staff.first_name} {staff.last_name}" self.stdout.write(f" - {name} (ID: {staff.id})") # Create complaint if not dry run if not dry_run: self.stdout.write(f"\n{'='*80}") self.stdout.write("💾 CREATING COMPLAINT") self.stdout.write(f"{'='*80}\n") try: with transaction.atomic(): # Create complaint complaint = Complaint.objects.create( reference_number=self.generate_reference_number(hospital.code), hospital=hospital, department=selected_staff[0].department if selected_staff[0].department else None, category=complaint_data['category'], title=complaint_data['title'], description=complaint_data['description'], severity=complaint_data['severity'], priority=complaint_data['priority'], source=self.get_source_instance(), status='open', contact_name='Test Patient', contact_phone='+966500000000', contact_email='test@example.com', ) # Create timeline entry ComplaintUpdate.objects.create( complaint=complaint, update_type='status_change', old_status='', new_status='open', message='Complaint created for staff matching test', created_by=None ) self.stdout.write( self.style.SUCCESS(f"✓ Complaint created successfully!") ) self.stdout.write(f" Reference: {complaint.reference_number}") self.stdout.write(f" ID: {complaint.id}") # Trigger AI analysis self.stdout.write(f"\n{'='*80}") self.stdout.write("🤖 AI ANALYSIS") self.stdout.write(f"{'='*80}\n") ai_service = AIService() analysis = ai_service.analyze_complaint( title=complaint.title, description=complaint.description, category=complaint.category.name_en if complaint.category else None, hospital_id=hospital.id ) self.stdout.write(f"AI Analysis Results:") # Display extracted staff names staff_names = analysis.get('staff_names', []) if staff_names: self.stdout.write(f"\n Extracted Staff Names ({len(staff_names)}):") for i, staff_name in enumerate(staff_names, 1): self.stdout.write(f" {i}. {staff_name}") else: self.stdout.write(f" No staff names extracted") # Display primary staff primary_staff = analysis.get('primary_staff_name', '') if primary_staff: self.stdout.write(f"\n Primary Staff: {primary_staff}") # Display classification results self.stdout.write(f"\n Classification:") self.stdout.write(f" - Complaint Type: {analysis.get('complaint_type', 'N/A')}") self.stdout.write(f" - Severity: {analysis.get('severity', 'N/A')}") self.stdout.write(f" - Priority: {analysis.get('priority', 'N/A')}") self.stdout.write(f" - Category: {analysis.get('category', 'N/A')}") self.stdout.write(f" - Subcategory: {analysis.get('subcategory', 'N/A')}") self.stdout.write(f" - Department: {analysis.get('department', 'N/A')}") self.stdout.write(f"\n{'='*80}") self.stdout.write(f"✅ TEST COMPLETED") self.stdout.write(f"{'='*80}\n") except Exception as e: self.stdout.write( self.style.ERROR(f"Error creating complaint: {str(e)}") ) import traceback self.stdout.write(traceback.format_exc()) else: self.stdout.write(f"\n{'='*80}") self.stdout.write(self.style.WARNING("🔍 DRY RUN - No changes made")) self.stdout.write(f"{'='*80}\n") def prepare_complaint(self, template, staff, hospital, language): """Prepare complaint data from template with staff names""" # Get category category = ComplaintCategory.objects.filter( code=template['category'], is_active=True ).first() # Format description with staff names description = template['description'] if len(staff) == 2: description = description.format( staff1_name=self.get_staff_name(staff[0], language), staff2_name=self.get_staff_name(staff[1], language), staff3_name='' ) elif len(staff) == 3: description = description.format( staff1_name=self.get_staff_name(staff[0], language), staff2_name=self.get_staff_name(staff[1], language), staff3_name=self.get_staff_name(staff[2], language) ) return { 'title': template['title'], 'description': description, 'category': category, 'severity': template['severity'], 'priority': template['priority'] } def get_staff_name(self, staff, language): """Get staff name in appropriate language""" if language == 'ar' and staff.first_name_ar: return f"{staff.first_name_ar} {staff.last_name_ar}" else: return f"{staff.first_name} {staff.last_name}" def generate_reference_number(self, hospital_code): """Generate unique complaint reference number""" short_uuid = str(uuid.uuid4())[:8].upper() year = timezone.now().year return f"CMP-{hospital_code}-{year}-{short_uuid}" def get_source_instance(self): """Get PXSource instance""" try: return PXSource.objects.get(name_en='Online Form', is_active=True) except PXSource.DoesNotExist: return PXSource.objects.filter(is_active=True).first()