15 KiB
Source-Based SLA Implementation - Complete Summary
Overview
This document summarizes the implementation of source-based Service Level Agreement (SLA) functionality for the complaint management system. The system now supports different SLA timelines, reminder schedules, and escalation rules based on the complaint source (MOH, CCHI, Patient/Family).
Implementation Date
February 4, 2026
Requirements
External Complaints
Ministry of Health Complaints (Within 24 Hours)
- Send the 1st reminder email after 12 hours from the initial email
- Send the second reminder email after 18 hours from the first reminder (30 hours total)
- Escalate the complaint to the manager or director of the department after 24 hours if no response has been received
Council of Cooperative Health Insurance (CCHI) Complaints (Within 48 Hours)
- Send the 1st reminder email after 24 hours from the initial email
- Send the second reminder email after 36 hours from the first reminder (60 hours total)
- Escalate the complaint to the manager or director of the department after 48 hours if no response has been received
Internal Complaints (Within 72 Hours)
Patients, Relatives Complaints
- Send the 1st reminder email after 24 hours from the initial email
- Send the second reminder email after 48 hours from the first reminder (72 hours total)
- Escalate the complaint to the manager or director of the department after 72 hours if no response has been received
Technical Implementation
1. Database Model Changes
Extended ComplaintSLAConfig Model
Added three new fields to support source-based timing:
# apps/complaints/models.py
class ComplaintSLAConfig(UUIDModel, TimeStampedModel):
# ... existing fields ...
# Source-based reminder timing (from complaint creation)
first_reminder_hours_after = models.IntegerField(
default=0,
help_text="Send 1st reminder X hours after complaint creation"
)
second_reminder_hours_after = models.IntegerField(
default=0,
help_text="Send 2nd reminder X hours after complaint creation"
)
escalation_hours_after = models.IntegerField(
default=0,
help_text="Escalate complaint X hours after creation if unresolved"
)
Helper Methods
Added helper methods to calculate timing based on configuration:
def get_first_reminder_hours_after(self, complaint_created_at=None):
"""Calculate first reminder timing based on config."""
if self.first_reminder_hours_after > 0:
return self.first_reminder_hours_after
else:
return max(0, self.sla_hours - self.reminder_hours_before)
def get_second_reminder_hours_after(self, complaint_created_at=None):
"""Calculate second reminder timing based on config."""
if self.second_reminder_hours_after > 0:
return self.second_reminder_hours_after
elif self.second_reminder_enabled:
return max(0, self.sla_hours - self.second_reminder_hours_before)
else:
return 0
def get_escalation_hours_after(self, complaint_created_at=None):
"""Calculate escalation timing based on config."""
if self.escalation_hours_after > 0:
return self.escalation_hours_after
else:
return None # Use standard overdue logic
2. SLA Calculation Logic
Updated Complaint.calculate_sla_due_date() to prioritize source-based configs:
def calculate_sla_due_date(self):
"""
Calculate SLA due date based on source, severity, and hospital configuration.
Priority order:
1. Source-based config (MOH, CHI, Internal)
2. Severity/priority-based config
3. Settings defaults
"""
# Try source-based SLA config first
if self.source:
try:
sla_config = ComplaintSLAConfig.objects.get(
hospital=self.hospital,
source=self.source,
is_active=True
)
sla_hours = sla_config.sla_hours
return timezone.now() + timedelta(hours=sla_hours)
except ComplaintSLAConfig.DoesNotExist:
pass # Fall through to next option
# Try severity/priority-based config
# ... fallback logic ...
Added Complaint.get_sla_config() method to retrieve the applicable configuration:
def get_sla_config(self):
"""
Get the SLA config for this complaint.
Returns the source-based or severity/priority-based config that applies.
"""
if self.source:
try:
return ComplaintSLAConfig.objects.get(
hospital=self.hospital,
source=self.source,
is_active=True
)
except ComplaintSLAConfig.DoesNotExist:
pass
# Fallback to severity/priority-based config
# ...
3. Task Updates
SLA Reminder Task (apps/complaints/tasks.py)
Updated send_sla_reminders() to use source-based timing:
@shared_task
def send_sla_reminders():
"""
Send SLA reminder emails for complaints approaching deadlines.
Uses source-based timing for reminder scheduling.
"""
now = timezone.now()
# Get all open complaints
complaints = Complaint.objects.filter(
status__in=[ComplaintStatus.OPEN, ComplaintStatus.IN_PROGRESS],
is_overdue=False
)
for complaint in complaints:
sla_config = complaint.get_sla_config()
if sla_config:
# First reminder
first_reminder_hours = sla_config.get_first_reminder_hours_after()
if first_reminder_hours:
first_reminder_time = complaint.created_at + timedelta(hours=first_reminder_hours)
if (now >= first_reminder_time and
not complaint.reminder_sent_at and
complaint.status == ComplaintStatus.OPEN):
# Send first reminder
...
# Second reminder
second_reminder_hours = sla_config.get_second_reminder_hours_after()
if second_reminder_hours:
second_reminder_time = complaint.created_at + timedelta(hours=second_reminder_hours)
if (now >= second_reminder_time and
not complaint.second_reminder_sent_at):
# Send second reminder
...
Escalation Task
Updated escalation logic to use source-based timing:
@shared_task
def escalate_overdue_complaints():
"""
Escalate overdue complaints based on source-based timing.
"""
now = timezone.now()
complaints = Complaint.objects.filter(
status__in=[ComplaintStatus.OPEN, ComplaintStatus.IN_PROGRESS],
is_overdue=False
)
for complaint in complaints:
sla_config = complaint.get_sla_config()
if sla_config:
escalation_hours = sla_config.get_escalation_hours_after()
if escalation_hours:
escalation_time = complaint.created_at + timedelta(hours=escalation_hours)
if now >= escalation_time and not complaint.escalated_at:
# Escalate complaint
...
4. Database Migration
Created migration 0005_add_source_based_sla_fields.py to add new fields:
# Generated migration
operations = [
migrations.AddField(
model_name='complaintslaconfig',
name='first_reminder_hours_after',
field=models.IntegerField(default=0, help_text='Send 1st reminder X hours after complaint creation'),
),
migrations.AddField(
model_name='complaintslaconfig',
name='second_reminder_hours_after',
field=models.IntegerField(default=0, help_text='Send 2nd reminder X hours after complaint creation'),
),
migrations.AddField(
model_name='complaintslaconfig',
name='escalation_hours_after',
field=models.IntegerField(default=0, help_text='Escalate complaint X hours after creation if unresolved'),
),
]
5. Management Command
Created apps/complaints/management/commands/setup_source_based_sla.py to:
- Create MOH source if not exists
- Create CCHI source if not exists
- Create SLA configurations for all hospitals
- Set up correct timing values for each source
Command usage:
python manage.py setup_source_based_sla
6. Admin Interface
Updated apps/complaints/admin.py to display new fields in the SLA configuration interface:
@admin.register(ComplaintSLAConfig)
class ComplaintSLAConfigAdmin(admin.ModelAdmin):
list_display = [
'hospital', 'source', 'severity', 'priority', 'sla_hours',
'first_reminder_hours_after', 'second_reminder_hours_after',
'escalation_hours_after', 'is_active'
]
fieldsets = (
('Configuration', {
'fields': ('hospital', 'source', 'severity', 'priority', 'sla_hours')
}),
('Source-Based Timing', {
'fields': (
'first_reminder_hours_after',
'second_reminder_hours_after',
'escalation_hours_after'
)
}),
('Legacy Timing', {
'fields': (
'reminder_hours_before',
'second_reminder_enabled',
'second_reminder_hours_before',
'thank_you_email_enabled'
),
'classes': ('collapse',)
}),
('Status', {
'fields': ('is_active',)
}),
)
Configuration Data
PX Sources Created
-
Ministry of Health
- Source Type: External
- English Name: Ministry of Health
- Arabic Name: وزارة الصحة
-
Council of Cooperative Health Insurance
- Source Type: External
- English Name: Council of Cooperative Health Insurance
- Arabic Name: مجلس الضمان الصحي التعاوني
SLA Configurations Created
For each hospital (12 total), the following configurations were created:
1. Ministry of Health (External)
- SLA Hours: 24
- 1st Reminder: 12 hours after creation
- 2nd Reminder: 30 hours after creation (12 + 18)
- Escalation: 24 hours after creation
2. Council of Cooperative Health Insurance (External)
- SLA Hours: 48
- 1st Reminder: 24 hours after creation
- 2nd Reminder: 60 hours after creation (24 + 36)
- Escalation: 48 hours after creation
3. Patient (Internal)
- SLA Hours: 72
- 1st Reminder: 24 hours after creation
- 2nd Reminder: 72 hours after creation (24 + 48)
- Escalation: 72 hours after creation
4. Family Member (Internal)
- SLA Hours: 72
- 1st Reminder: 24 hours after creation
- 2nd Reminder: 72 hours after creation (24 + 48)
- Escalation: 72 hours after creation
Verification
Test Results
All verification tests passed successfully:
✓ MOH Config: SLA=24h, 1st Rem=12h, 2nd Rem=30h, Esc=24h
✓ CCHI Config: SLA=48h, 1st Rem=24h, 2nd Rem=60h, Esc=48h
✓ Patient Config: SLA=72h, 1st Rem=24h, 2nd Rem=72h, Esc=72h
✓ MOH Config methods: 1st Rem=12h, 2nd Rem=30h, Esc=24h
✓ MOH complaint returns source-based config (SLA=24h)
✓ 1st reminder: 12h after creation
✓ 2nd reminder: 30h after creation
✓ Escalation: 24h after creation
Test Script
Created comprehensive test script at test_source_based_sla.py that verifies:
- SLA configuration existence for different sources
- Helper method functionality
- Complaint.get_sla_config() method
- SLA due date calculation
- Reminder timing calculation
- Fallback mechanism
Run tests with:
python test_source_based_sla.py
Backward Compatibility
The implementation maintains backward compatibility with existing severity/priority-based SLA configurations:
- Source-based configs take precedence over severity/priority configs
- If no source-based config exists, the system falls back to severity/priority configs
- Legacy
reminder_hours_beforefields are preserved for configurations not using source-based timing - The
complaint_created_atparameter in helper methods is optional for flexibility
Files Modified
-
apps/complaints/models.py- Added source-based fields to ComplaintSLAConfig
- Added helper methods for timing calculation
- Updated Complaint.calculate_sla_due_date()
- Added Complaint.get_sla_config()
-
apps/complaints/migrations/0005_add_source_based_sla_fields.py- Database migration for new fields
-
apps/complaints/tasks.py- Updated send_sla_reminders() for source-based timing
- Updated escalate_overdue_complaints() for source-based timing
-
apps/complaints/admin.py- Updated ComplaintSLAConfigAdmin to display new fields
-
apps/complaints/management/commands/setup_source_based_sla.py(NEW)- Management command to set up source-based SLA configs
-
test_source_based_sla.py(NEW)- Comprehensive test suite for verification
Usage
Creating a Complaint with Source-Based SLA
When creating a complaint, the system automatically selects the appropriate SLA configuration based on the complaint source:
# MOH complaint
complaint = Complaint(
hospital=hospital,
source=PXSource.objects.get(name_en='Ministry of Health'),
title='Complaint from MOH',
description='...',
status='open'
)
complaint.save() # SLA automatically set to 24h
# Patient complaint
complaint = Complaint(
hospital=hospital,
source=PXSource.objects.get(name_en='Patient'),
title='Patient complaint',
description='...',
status='open'
)
complaint.save() # SLA automatically set to 72h
Viewing SLA Configuration in Admin
Navigate to:
- Django Admin
- Complaint SLA Configurations
- View configurations by hospital, source, severity, and priority
Modifying SLA Timings
Administrators can modify SLA timings through the Django Admin interface:
- Navigate to Complaint SLA Configurations
- Select the configuration to modify
- Update timing values:
first_reminder_hours_after: Hours after creation to send 1st remindersecond_reminder_hours_after: Hours after creation to send 2nd reminderescalation_hours_after: Hours after creation to escalate
Future Enhancements
Potential improvements for future consideration:
- Email Templates: Create specific email templates for each source type
- Reporting: Add source-based SLA compliance reporting
- Notifications: Add in-app notifications in addition to emails
- Customizable Timings: Allow hospital-specific overrides
- Analytics Dashboard: Track SLA performance by source
Summary
The source-based SLA implementation has been successfully completed with:
- ✓ Extended database model with new timing fields
- ✓ Updated SLA calculation logic to prioritize source-based configs
- ✓ Modified reminder and escalation tasks to use source-based timing
- ✓ Created PX sources for MOH and CCHI
- ✓ Set up SLA configurations for all 12 hospitals
- ✓ Updated admin interface
- ✓ Created comprehensive test suite
- ✓ All verification tests passing
The system now correctly implements the specified requirements for external (MOH, CCHI) and internal (Patient/Family) complaint SLA timelines, reminders, and escalation rules.