#!/usr/bin/env python """ Comprehensive SLA System Test This script tests the complete SLA (Service Level Agreement) system: 1. SLA Configuration 2. SLA Deadline Calculation 3. Overdue Detection 4. Escalation Rules 5. SLA Reminders 6. Explanation SLA """ import os import django from datetime import timedelta # Setup Django os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.dev') django.setup() from apps.complaints.models import ( Complaint, ComplaintSLAConfig, EscalationRule, ComplaintThreshold, ExplanationSLAConfig, ComplaintStatus ) from apps.organizations.models import Hospital, Department, Staff, Patient from apps.accounts.models import User from django.utils import timezone def setup_test_data(): """Setup test data for SLA testing.""" print("\n" + "=" * 80) print("๐Ÿ”ง SETTING UP TEST DATA") print("=" * 80) # Get or create hospital hospital = Hospital.objects.first() if not hospital: print("โŒ No hospital found. Please run seed commands first.") return None, None, None print(f"โœ… Hospital: {hospital.name}") # Get department department = Department.objects.filter(hospital=hospital).first() if department: print(f"โœ… Department: {department.name}") else: print("โš ๏ธ No department found") # Get user for assignment user = User.objects.filter(hospital=hospital).first() if user: print(f"โœ… User: {user.get_full_name()}") else: print("โš ๏ธ No user found") return hospital, department, user def test_sla_configuration(hospital): """Test SLA configuration creation and retrieval.""" print("\n" + "=" * 80) print("๐Ÿ“‹ TEST 1: SLA CONFIGURATION") print("=" * 80) # Clean up existing configs ComplaintSLAConfig.objects.filter(hospital=hospital).delete() # Create SLA configs for different severities and priorities sla_configs = [ # Low severity { 'severity': 'low', 'priority': 'low', 'sla_hours': 72, 'reminder_hours_before': 24, }, { 'severity': 'low', 'priority': 'medium', 'sla_hours': 48, 'reminder_hours_before': 24, }, { 'severity': 'low', 'priority': 'high', 'sla_hours': 24, 'reminder_hours_before': 12, }, # Medium severity { 'severity': 'medium', 'priority': 'low', 'sla_hours': 48, 'reminder_hours_before': 24, }, { 'severity': 'medium', 'priority': 'medium', 'sla_hours': 24, 'reminder_hours_before': 12, }, { 'severity': 'medium', 'priority': 'high', 'sla_hours': 12, 'reminder_hours_before': 6, }, # High severity { 'severity': 'high', 'priority': 'low', 'sla_hours': 24, 'reminder_hours_before': 12, }, { 'severity': 'high', 'priority': 'medium', 'sla_hours': 12, 'reminder_hours_before': 6, }, { 'severity': 'high', 'priority': 'high', 'sla_hours': 6, 'reminder_hours_before': 3, }, # Critical severity { 'severity': 'critical', 'priority': 'low', 'sla_hours': 12, 'reminder_hours_before': 6, }, { 'severity': 'critical', 'priority': 'medium', 'sla_hours': 6, 'reminder_hours_before': 3, }, { 'severity': 'critical', 'priority': 'high', 'sla_hours': 4, 'reminder_hours_before': 2, 'second_reminder_enabled': True, 'second_reminder_hours_before': 1, }, ] created_count = 0 for config in sla_configs: sla_config, created = ComplaintSLAConfig.objects.get_or_create( hospital=hospital, severity=config['severity'], priority=config['priority'], defaults=config ) if created: created_count += 1 print(f" โœ… Created: {sla_config.severity}/{sla_config.priority} - {sla_config.sla_hours}h") else: print(f" โ„น๏ธ Existing: {sla_config.severity}/{sla_config.priority} - {sla_config.sla_hours}h") print(f"\n๐Ÿ“Š Total SLA configs: {ComplaintSLAConfig.objects.filter(hospital=hospital).count()}") print(f"๐Ÿ“ Created: {created_count} new configs") return True def test_sla_deadline_calculation(hospital, department, user): """Test SLA deadline calculation for complaints.""" print("\n" + "=" * 80) print("โฐ TEST 2: SLA DEADLINE CALCULATION") print("=" * 80) test_cases = [ {'severity': 'low', 'priority': 'low', 'expected_hours': 72}, {'severity': 'medium', 'priority': 'medium', 'expected_hours': 24}, {'severity': 'high', 'priority': 'high', 'expected_hours': 6}, {'severity': 'critical', 'priority': 'high', 'expected_hours': 4}, ] for i, test_case in enumerate(test_cases, 1): print(f"\n๐Ÿ“ Test case {i}: {test_case['severity']}/{test_case['priority']}") # Get SLA config try: sla_config = ComplaintSLAConfig.objects.get( hospital=hospital, severity=test_case['severity'], priority=test_case['priority'], is_active=True ) sla_hours = sla_config.sla_hours except ComplaintSLAConfig.DoesNotExist: sla_hours = test_case['expected_hours'] # Fallback print(f" SLA hours: {sla_hours}h") # Create complaint complaint = Complaint.objects.create( hospital=hospital, department=department, title=f"Test SLA Complaint {i}", description=f"Testing SLA deadline calculation for {test_case['severity']}/{test_case['priority']}", severity=test_case['severity'], priority=test_case['priority'], status=ComplaintStatus.OPEN, assigned_to=user ) # Check deadline deadline = complaint.due_at now = timezone.now() hours_until_deadline = (deadline - now).total_seconds() / 3600 print(f" Created at: {complaint.created_at.strftime('%Y-%m-%d %H:%M:%S')}") print(f" Due at: {deadline.strftime('%Y-%m-%d %H:%M:%S')}") print(f" Hours until deadline: {hours_until_deadline:.2f}h") print(f" Expected: {test_case['expected_hours']}h") # Verify deadline is correct (within 1 minute tolerance) expected_deadline = complaint.created_at + timedelta(hours=sla_hours) time_diff = abs((deadline - expected_deadline).total_seconds()) if time_diff < 60: # Within 1 minute print(f" โœ… Deadline calculation CORRECT") else: print(f" โŒ Deadline calculation INCORRECT (diff: {time_diff:.2f}s)") # Summary total_complaints = Complaint.objects.filter(title__contains="Test SLA Complaint").count() print(f"\n๐Ÿ“Š Total test complaints created: {total_complaints}") return True def test_overdue_detection(): """Test overdue complaint detection.""" print("\n" + "=" * 80) print("โš ๏ธ TEST 3: OVERDETECTION") print("=" * 80) # Get all open complaints open_complaints = Complaint.objects.filter( status=ComplaintStatus.OPEN, title__contains="Test SLA Complaint" ) print(f"๐Ÿ“Š Open complaints: {open_complaints.count()}") overdue_count = 0 not_overdue_count = 0 for complaint in open_complaints: is_overdue = complaint.check_overdue() hours_until = (complaint.due_at - timezone.now()).total_seconds() / 3600 if is_overdue: overdue_count += 1 print(f"\n โŒ OVERDUE: {complaint.title}") print(f" Due at: {complaint.due_at.strftime('%Y-%m-%d %H:%M:%S')}") print(f" Hours overdue: {-hours_until:.2f}h") else: not_overdue_count += 1 print(f"\n โœ… NOT OVERDUE: {complaint.title}") print(f" Due at: {complaint.due_at.strftime('%Y-%m-%d %H:%M:%S')}") print(f" Hours remaining: {hours_until:.2f}h") print(f"\n๐Ÿ“Š Summary: {overdue_count} overdue, {not_overdue_count} not overdue") return True def test_escalation_rules(hospital, user): """Test escalation rule configuration and application.""" print("\n" + "=" * 80) print("๐Ÿ“ˆ TEST 4: ESCALATION RULES") print("=" * 80) # Clean up existing rules EscalationRule.objects.filter(hospital=hospital).delete() # Create escalation rules escalation_rules = [ { 'name': 'Level 1 - Department Manager', 'description': 'Escalate to department manager', 'escalation_level': 1, 'max_escalation_level': 3, 'trigger_hours_overdue': 0, # Immediate 'escalate_to_role': 'department_manager', 'severity_filter': 'high', 'order': 1, }, { 'name': 'Level 2 - Hospital Admin', 'description': 'Escalate to hospital admin', 'escalation_level': 2, 'max_escalation_level': 3, 'trigger_hours_overdue': 4, # 4 hours after overdue 'escalate_to_role': 'hospital_admin', 'severity_filter': 'high', 'order': 2, }, { 'name': 'Level 3 - CEO', 'description': 'Escalate to CEO', 'escalation_level': 3, 'max_escalation_level': 3, 'trigger_hours_overdue': 8, # 8 hours after overdue 'escalate_to_role': 'ceo', 'severity_filter': 'critical', 'order': 3, }, ] created_count = 0 for rule_data in escalation_rules: rule, created = EscalationRule.objects.get_or_create( hospital=hospital, name=rule_data['name'], defaults=rule_data ) if created: created_count += 1 print(f" โœ… Created: {rule.name} (Level {rule.escalation_level})") print(f" Role: {rule.escalate_to_role}") print(f" Trigger: {rule.trigger_hours_overdue}h after overdue") else: print(f" โ„น๏ธ Existing: {rule.name}") print(f"\n๐Ÿ“Š Total escalation rules: {EscalationRule.objects.filter(hospital=hospital).count()}") print(f"๐Ÿ“ Created: {created_count} new rules") # Test escalation logic on a complaint print("\n๐Ÿงช Testing escalation on a high-severity complaint...") # Create a high-severity complaint complaint = Complaint.objects.create( hospital=hospital, title="Test Escalation Complaint", description="Testing escalation rules", severity='high', priority='high', status=ComplaintStatus.OPEN, assigned_to=user ) # Manually set it to be overdue complaint.due_at = timezone.now() - timedelta(hours=2) # 2 hours ago complaint.save(update_fields=['due_at']) print(f" Complaint: {complaint.title}") print(f" Severity: {complaint.severity}") print(f" Due at: {complaint.due_at.strftime('%Y-%m-%d %H:%M:%S')}") print(f" Hours overdue: 2h") # Check applicable rules applicable_rules = EscalationRule.objects.filter( hospital=hospital, is_active=True, trigger_on_overdue=True, escalation_level=1, severity_filter=complaint.severity ).order_by('order') print(f"\n Applicable rules: {applicable_rules.count()}") for rule in applicable_rules: print(f" - {rule.name}") print(f" Level: {rule.escalation_level}") print(f" Trigger: {rule.trigger_hours_overdue}h after overdue") print(f" Should trigger: {2 >= rule.trigger_hours_overdue}") return True def test_explanation_sla(hospital): """Test explanation SLA configuration.""" print("\n" + "=" * 80) print("๐Ÿ’ฌ TEST 5: EXPLANATION SLA") print("=" * 80) # Clean up existing configs ExplanationSLAConfig.objects.filter(hospital=hospital).delete() # Create explanation SLA config config_data = { 'response_hours': 48, 'reminder_hours_before': 12, 'auto_escalate_enabled': True, 'escalation_hours_overdue': 24, 'max_escalation_levels': 3, } sla_config, created = ExplanationSLAConfig.objects.get_or_create( hospital=hospital, defaults=config_data ) if created: print(f" โœ… Created Explanation SLA config") print(f" Response time: {sla_config.response_hours}h") print(f" Reminder: {sla_config.reminder_hours_before}h before deadline") print(f" Auto-escalate: {sla_config.auto_escalate_enabled}") print(f" Escalate after: {sla_config.escalation_hours_overdue}h overdue") print(f" Max levels: {sla_config.max_escalation_levels}") else: print(f" โ„น๏ธ Existing Explanation SLA config found") return True def test_sla_threshold(hospital): """Test complaint threshold configuration.""" print("\n" + "=" * 80) print("๐ŸŽฏ TEST 6: COMPLAINT THRESHOLDS") print("=" * 80) # Clean up existing thresholds ComplaintThreshold.objects.filter(hospital=hospital).delete() # Create thresholds thresholds = [ { 'threshold_type': 'resolution_survey_score', 'threshold_value': 50, 'comparison_operator': 'lt', # Less than 50% 'action_type': 'create_px_action', }, ] created_count = 0 for threshold_data in thresholds: threshold, created = ComplaintThreshold.objects.get_or_create( hospital=hospital, threshold_type=threshold_data['threshold_type'], defaults=threshold_data ) if created: created_count += 1 print(f" โœ… Created threshold") print(f" Type: {threshold.threshold_type}") print(f" Condition: {threshold.comparison_operator} {threshold.threshold_value}") print(f" Action: {threshold.action_type}") # Test threshold checking test_values = [30, 50, 70] for value in test_values: result = threshold.check_threshold(value) print(f" Value {value}: {'โœ… BREACHED' if result else 'โŒ NOT breached'}") else: print(f" โ„น๏ธ Existing threshold found") print(f"\n๐Ÿ“Š Total thresholds: {ComplaintThreshold.objects.filter(hospital=hospital).count()}") return True def test_sla_summary(): """Print SLA system summary.""" print("\n" + "=" * 80) print("๐Ÿ“Š SLA SYSTEM SUMMARY") print("=" * 80) hospital = Hospital.objects.first() if not hospital: return # Count configurations sla_configs = ComplaintSLAConfig.objects.filter(hospital=hospital, is_active=True) escalation_rules = EscalationRule.objects.filter(hospital=hospital, is_active=True) thresholds = ComplaintThreshold.objects.filter(hospital=hospital, is_active=True) explanation_configs = ExplanationSLAConfig.objects.filter(hospital=hospital, is_active=True) print(f"\n Hospital: {hospital.name}") print(f"\n SLA Configurations: {sla_configs.count()}") for config in sla_configs: print(f" - {config.severity}/{config.priority}: {config.sla_hours}h") print(f"\n Escalation Rules: {escalation_rules.count()}") for rule in escalation_rules: print(f" - {rule.name} (Level {rule.escalation_level})") print(f"\n Thresholds: {thresholds.count()}") for threshold in thresholds: print(f" - {threshold.threshold_type}: {threshold.comparison_operator} {threshold.threshold_value}") print(f"\n Explanation SLA: {explanation_configs.count()}") for config in explanation_configs: print(f" - {config.response_hours}h to respond") # Count complaints by status print(f"\n Complaints by status:") complaints = Complaint.objects.filter(hospital=hospital) for status in ComplaintStatus.choices: count = complaints.filter(status=status[0]).count() print(f" - {status[1]}: {count}") # Count overdue complaints overdue_count = complaints.filter(is_overdue=True).count() print(f"\n Overdue complaints: {overdue_count}") return True def main(): """Main test function.""" print("\n" + "=" * 80) print("๐Ÿš€ COMPREHENSIVE SLA SYSTEM TEST") print("=" * 80) print(f"๐Ÿ“… Started at: {timezone.now().strftime('%Y-%m-%d %H:%M:%S')}") # Setup test data hospital, department, user = setup_test_data() if not hospital: return # Run tests try: # Test 1: SLA Configuration test_sla_configuration(hospital) # Test 2: SLA Deadline Calculation test_sla_deadline_calculation(hospital, department, user) # Test 3: Overdue Detection test_overdue_detection() # Test 4: Escalation Rules test_escalation_rules(hospital, user) # Test 5: Explanation SLA test_explanation_sla(hospital) # Test 6: SLA Thresholds test_sla_threshold(hospital) # Summary test_sla_summary() print("\n" + "=" * 80) print("โœ… ALL TESTS COMPLETED") print("=" * 80) print(f"๐Ÿ“… Completed at: {timezone.now().strftime('%Y-%m-%d %H:%M:%S')}") except Exception as e: print(f"\nโŒ ERROR: {str(e)}") import traceback traceback.print_exc() if __name__ == '__main__': main()