HH/SOURCE_BASED_SLA_IMPLEMENTATION.md

476 lines
15 KiB
Markdown

# 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:
```python
# 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:
```python
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:
```python
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:
```python
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:
```python
@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:
```python
@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:
```python
# 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:
```bash
python manage.py setup_source_based_sla
```
### 6. Admin Interface
Updated `apps/complaints/admin.py` to display new fields in the SLA configuration interface:
```python
@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
1. **Ministry of Health**
- Source Type: External
- English Name: Ministry of Health
- Arabic Name: وزارة الصحة
2. **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:
1. SLA configuration existence for different sources
2. Helper method functionality
3. Complaint.get_sla_config() method
4. SLA due date calculation
5. Reminder timing calculation
6. Fallback mechanism
Run tests with:
```bash
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_before` fields are preserved for configurations not using source-based timing
- The `complaint_created_at` parameter in helper methods is optional for flexibility
## Files Modified
1. `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()
2. `apps/complaints/migrations/0005_add_source_based_sla_fields.py`
- Database migration for new fields
3. `apps/complaints/tasks.py`
- Updated send_sla_reminders() for source-based timing
- Updated escalate_overdue_complaints() for source-based timing
4. `apps/complaints/admin.py`
- Updated ComplaintSLAConfigAdmin to display new fields
5. `apps/complaints/management/commands/setup_source_based_sla.py` (NEW)
- Management command to set up source-based SLA configs
6. `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:
```python
# 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:
1. Django Admin
2. Complaint SLA Configurations
3. View configurations by hospital, source, severity, and priority
### Modifying SLA Timings
Administrators can modify SLA timings through the Django Admin interface:
1. Navigate to Complaint SLA Configurations
2. Select the configuration to modify
3. Update timing values:
- `first_reminder_hours_after`: Hours after creation to send 1st reminder
- `second_reminder_hours_after`: Hours after creation to send 2nd reminder
- `escalation_hours_after`: Hours after creation to escalate
## Future Enhancements
Potential improvements for future consideration:
1. **Email Templates**: Create specific email templates for each source type
2. **Reporting**: Add source-based SLA compliance reporting
3. **Notifications**: Add in-app notifications in addition to emails
4. **Customizable Timings**: Allow hospital-specific overrides
5. **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.