870 lines
22 KiB
Markdown
870 lines
22 KiB
Markdown
# Survey Delivery Implementation
|
|
|
|
**Date:** January 28, 2026
|
|
**Status:** ✅ Complete
|
|
**Version:** 1.0
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
The Survey Delivery System automatically sends patient experience surveys via SMS or Email when patients complete their journey and are discharged. This document describes the implementation, architecture, and usage of the survey delivery system.
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
### Components
|
|
|
|
```
|
|
HIS Simulator
|
|
↓
|
|
[Patient Discharged]
|
|
↓
|
|
HISAdapter.trigger_post_discharge_survey()
|
|
↓
|
|
SurveyDeliveryService.deliver_survey()
|
|
↓
|
|
┌──────────────────┬──────────────────┐
|
|
│ SMS Delivery │ Email Delivery │
|
|
│ (Logged/Planned)│ (Implemented) │
|
|
└──────────────────┴──────────────────┘
|
|
```
|
|
|
|
### Files
|
|
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `apps/surveys/services.py` | SurveyDeliveryService - handles all survey delivery |
|
|
| `apps/integrations/services/his_adapter.py` | Trigger surveys when patients are discharged |
|
|
| `apps/surveys/models.py` | SurveyInstance model - tracks delivery status |
|
|
|
|
---
|
|
|
|
## SurveyDeliveryService
|
|
|
|
### Location
|
|
`apps/surveys/services.py`
|
|
|
|
### Class Methods
|
|
|
|
#### 1. `generate_survey_url(survey_instance) -> str`
|
|
|
|
Generates secure survey URL with access token.
|
|
|
|
**Parameters:**
|
|
- `survey_instance`: SurveyInstance object
|
|
|
|
**Returns:**
|
|
- Full survey URL (e.g., `http://localhost:8000/surveys/s/TOKEN/`)
|
|
|
|
**Example:**
|
|
```python
|
|
from apps.surveys.services import SurveyDeliveryService
|
|
from apps.surveys.models import SurveyInstance
|
|
|
|
survey = SurveyInstance.objects.get(id=uuid)
|
|
url = SurveyDeliveryService.generate_survey_url(survey)
|
|
# Returns: http://localhost:8000/surveys/s/GRsOpbYmtpf-kiZ0fzrtR4GvgJc5Yv_WWGahMW4moB4/
|
|
```
|
|
|
|
---
|
|
|
|
#### 2. `generate_sms_message(patient_name: str, survey_url: str, hospital_name: str = None) -> str`
|
|
|
|
Generates SMS message with survey link.
|
|
|
|
**Parameters:**
|
|
- `patient_name`: Patient's first name
|
|
- `survey_url`: Survey link
|
|
- `hospital_name`: Optional hospital name
|
|
|
|
**Returns:**
|
|
- SMS message text
|
|
|
|
**Example Message:**
|
|
```
|
|
Dear Fatima,
|
|
|
|
Thank you for choosing Al Hammadi Hospital. We value your feedback! Please take a moment to complete our patient experience survey:
|
|
|
|
http://localhost:8000/surveys/s/GRsOpbYmtpf-kiZ0fzrtR4GvgJc5Yv_WWGahMW4moB4/
|
|
|
|
This survey will take approximately 2-3 minutes.
|
|
Thank you for helping us improve our services!
|
|
```
|
|
|
|
---
|
|
|
|
#### 3. `generate_email_message(patient_name: str, survey_url: str, hospital_name: str = None) -> str`
|
|
|
|
Generates email message with survey link.
|
|
|
|
**Parameters:**
|
|
- `patient_name`: Patient's first name
|
|
- `survey_url`: Survey link
|
|
- `hospital_name`: Optional hospital name
|
|
|
|
**Returns:**
|
|
- Email message text
|
|
|
|
**Example Message:**
|
|
```
|
|
Dear Fatima,
|
|
|
|
Thank you for choosing Al Hammadi Hospital.
|
|
|
|
We value your feedback and would appreciate it if you could take a moment to complete our patient experience survey.
|
|
|
|
Your feedback helps us improve our services and provide better care for all our patients.
|
|
|
|
Survey Link: http://localhost:8000/surveys/s/GRsOpbYmtpf-kiZ0fzrtR4GvgJc5Yv_WWGahMW4moB4/
|
|
|
|
This survey will take approximately 2-3 minutes to complete.
|
|
|
|
If you have any questions or concerns, please don't hesitate to contact us.
|
|
|
|
Thank you for helping us improve our services!
|
|
|
|
Best regards,
|
|
Patient Experience Team
|
|
```
|
|
|
|
---
|
|
|
|
#### 4. `send_survey_sms(survey_instance) -> bool`
|
|
|
|
Sends survey via SMS.
|
|
|
|
**Parameters:**
|
|
- `survey_instance`: SurveyInstance object
|
|
|
|
**Returns:**
|
|
- `True` if sent successfully, `False` otherwise
|
|
|
|
**Behavior:**
|
|
1. Validates recipient phone number
|
|
2. Generates survey URL
|
|
3. Generates SMS message
|
|
4. **Current**: Logs message (TODO: Integrate with Twilio or SMS service)
|
|
5. Updates survey status to `SENT`
|
|
6. Sets `sent_at` timestamp
|
|
|
|
**Example:**
|
|
```python
|
|
from apps.surveys.services import SurveyDeliveryService
|
|
from apps.surveys.models import SurveyInstance
|
|
|
|
survey = SurveyInstance.objects.get(id=uuid)
|
|
success = SurveyDeliveryService.send_survey_sms(survey)
|
|
if success:
|
|
print("SMS sent successfully!")
|
|
else:
|
|
print("SMS delivery failed")
|
|
```
|
|
|
|
---
|
|
|
|
#### 5. `send_survey_email(survey_instance) -> bool`
|
|
|
|
Sends survey via Email.
|
|
|
|
**Parameters:**
|
|
- `survey_instance`: SurveyInstance object
|
|
|
|
**Returns:**
|
|
- `True` if sent successfully, `False` otherwise
|
|
|
|
**Behavior:**
|
|
1. Validates recipient email address
|
|
2. Generates survey URL
|
|
3. Generates email message
|
|
4. Sends email using Django's `send_mail()`
|
|
5. Updates survey status to `SENT`
|
|
6. Sets `sent_at` timestamp
|
|
|
|
**Configuration:**
|
|
```python
|
|
# settings.py
|
|
DEFAULT_FROM_EMAIL = 'noreply@hospital.com'
|
|
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
|
EMAIL_HOST = 'smtp.example.com'
|
|
EMAIL_PORT = 587
|
|
EMAIL_USE_TLS = True
|
|
EMAIL_HOST_USER = 'your-email@example.com'
|
|
EMAIL_HOST_PASSWORD = 'your-password'
|
|
```
|
|
|
|
**Example:**
|
|
```python
|
|
from apps.surveys.services import SurveyDeliveryService
|
|
from apps.surveys.models import SurveyInstance
|
|
|
|
survey = SurveyInstance.objects.get(id=uuid)
|
|
success = SurveyDeliveryService.send_survey_email(survey)
|
|
if success:
|
|
print("Email sent successfully!")
|
|
else:
|
|
print("Email delivery failed")
|
|
```
|
|
|
|
---
|
|
|
|
#### 6. `deliver_survey(survey_instance) -> bool`
|
|
|
|
Delivers survey based on configured delivery channel.
|
|
|
|
**Parameters:**
|
|
- `survey_instance`: SurveyInstance object
|
|
|
|
**Returns:**
|
|
- `True` if delivered successfully, `False` otherwise
|
|
|
|
**Behavior:**
|
|
1. Ensures patient contact info is set
|
|
2. Calls appropriate delivery method based on `delivery_channel`:
|
|
- `sms`: `send_survey_sms()`
|
|
- `email`: `send_survey_email()`
|
|
- `whatsapp`: Not yet implemented (logs warning)
|
|
3. Handles unknown channels with error logging
|
|
|
|
**Example:**
|
|
```python
|
|
from apps.surveys.services import SurveyDeliveryService
|
|
from apps.surveys.models import SurveyInstance
|
|
|
|
survey = SurveyInstance.objects.get(id=uuid)
|
|
success = SurveyDeliveryService.deliver_survey(survey)
|
|
```
|
|
|
|
---
|
|
|
|
## Integration with HISAdapter
|
|
|
|
### Location
|
|
`apps/integrations/services/his_adapter.py`
|
|
|
|
### Method: `trigger_post_discharge_survey(journey: PatientJourneyInstance) -> Optional[SurveyInstance]`
|
|
|
|
**Purpose:** Creates and delivers post-discharge survey when patient journey is completed.
|
|
|
|
**Flow:**
|
|
```python
|
|
def trigger_post_discharge_survey(journey: PatientJourneyInstance) -> Optional[SurveyInstance]:
|
|
# 1. Check if journey template has post-discharge survey enabled
|
|
if not journey.journey_template.send_post_discharge_survey:
|
|
return None
|
|
|
|
# 2. Find appropriate survey template
|
|
survey_template = SurveyTemplate.objects.filter(
|
|
name__icontains="OPD",
|
|
is_active=True
|
|
).first()
|
|
|
|
# 3. Create survey instance (PENDING status)
|
|
survey = SurveyInstance.objects.create(
|
|
survey_template=survey_template,
|
|
patient=journey.patient,
|
|
journey_instance=journey,
|
|
hospital=journey.hospital,
|
|
status="PENDING",
|
|
delivery_channel="SMS",
|
|
recipient_phone=journey.patient.phone
|
|
)
|
|
|
|
# 4. Deliver survey using SurveyDeliveryService
|
|
from apps.surveys.services import SurveyDeliveryService
|
|
delivery_success = SurveyDeliveryService.deliver_survey(survey)
|
|
|
|
# 5. Return survey (even if delivery fails)
|
|
return survey
|
|
```
|
|
|
|
**Called From:** `HISAdapter.process_his_data()` when patient is discharged
|
|
|
|
---
|
|
|
|
## Survey Lifecycle
|
|
|
|
### Status Transitions
|
|
|
|
```
|
|
PENDING (Created)
|
|
↓
|
|
SENT (Delivered via SMS/Email)
|
|
↓
|
|
[Patient Opens Survey]
|
|
↓
|
|
VIEWED
|
|
↓
|
|
IN_PROGRESS
|
|
↓
|
|
COMPLETED
|
|
↓
|
|
[Score Calculated]
|
|
↓
|
|
[Marked Negative if Score < Threshold]
|
|
↓
|
|
[Follow-up if Negative]
|
|
```
|
|
|
|
### Status Descriptions
|
|
|
|
| Status | Description |
|
|
|--------|-------------|
|
|
| `PENDING` | Survey created, waiting to be delivered |
|
|
| `SENT` | Survey delivered to patient (SMS/Email sent) |
|
|
| `VIEWED` | Patient opened survey link |
|
|
| `IN_PROGRESS` | Patient started answering questions |
|
|
| `COMPLETED` | Patient finished answering all questions |
|
|
| `ABANDONED` | Patient started but didn't finish |
|
|
| `EXPIRED` | Survey access token expired |
|
|
| `CANCELLED` | Survey cancelled by admin |
|
|
|
|
---
|
|
|
|
## Configuration
|
|
|
|
### Settings
|
|
|
|
Add these settings to your `settings.py`:
|
|
|
|
```python
|
|
# Survey Base URL
|
|
# Used to generate survey links
|
|
SURVEY_BASE_URL = 'http://localhost:8000' # Change to production URL
|
|
|
|
# Email Configuration
|
|
DEFAULT_FROM_EMAIL = 'noreply@hospital.com'
|
|
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
|
EMAIL_HOST = 'smtp.example.com'
|
|
EMAIL_PORT = 587
|
|
EMAIL_USE_TLS = True
|
|
EMAIL_HOST_USER = 'your-email@example.com'
|
|
EMAIL_HOST_PASSWORD = 'your-password'
|
|
|
|
# Survey Settings
|
|
SURVEY_TOKEN_EXPIRATION_DAYS = 30 # Survey link validity
|
|
SURVEY_NEGATIVE_THRESHOLD = 3.0 # Score below which survey is marked negative
|
|
```
|
|
|
|
### Journey Template Configuration
|
|
|
|
To enable post-discharge surveys for a journey:
|
|
|
|
```python
|
|
from apps.journeys.models import PatientJourneyTemplate
|
|
|
|
template = PatientJourneyTemplate.objects.get(id=uuid)
|
|
template.send_post_discharge_survey = True # Enable surveys
|
|
template.save()
|
|
```
|
|
|
|
---
|
|
|
|
## Testing
|
|
|
|
### Manual Testing
|
|
|
|
#### Test SMS Message Generation
|
|
|
|
```python
|
|
python manage.py shell
|
|
|
|
from apps.surveys.models import SurveyInstance
|
|
from apps.surveys.services import SurveyDeliveryService
|
|
|
|
# Get a recent survey
|
|
survey = SurveyInstance.objects.filter(status='SENT').order_by('-sent_at').first()
|
|
|
|
# Generate SMS message
|
|
url = SurveyDeliveryService.generate_survey_url(survey)
|
|
message = SurveyDeliveryService.generate_sms_message(
|
|
survey.patient.first_name,
|
|
url,
|
|
survey.hospital.name
|
|
)
|
|
|
|
print(message)
|
|
```
|
|
|
|
#### Test Email Delivery
|
|
|
|
```python
|
|
python manage.py shell
|
|
|
|
from apps.surveys.models import SurveyInstance
|
|
from apps.surveys.services import SurveyDeliveryService
|
|
|
|
# Get a survey with email delivery
|
|
survey = SurveyInstance.objects.filter(
|
|
delivery_channel='email'
|
|
).order_by('-created_at').first()
|
|
|
|
# Set recipient email (if needed)
|
|
survey.recipient_email = 'test@example.com'
|
|
survey.save()
|
|
|
|
# Send email
|
|
success = SurveyDeliveryService.send_survey_email(survey)
|
|
print(f"Email sent: {success}")
|
|
```
|
|
|
|
#### Test Full Survey Delivery
|
|
|
|
```python
|
|
python manage.py shell
|
|
|
|
from apps.surveys.models import SurveyInstance
|
|
from apps.surveys.services import SurveyDeliveryService
|
|
|
|
# Get a pending survey
|
|
survey = SurveyInstance.objects.filter(status='PENDING').first()
|
|
|
|
# Deliver survey
|
|
success = SurveyDeliveryService.deliver_survey(survey)
|
|
|
|
# Verify
|
|
survey.refresh_from_db()
|
|
print(f"Status: {survey.status}")
|
|
print(f"Sent at: {survey.sent_at}")
|
|
print(f"Delivery successful: {success}")
|
|
```
|
|
|
|
### Automated Testing
|
|
|
|
Run the HIS simulator to test end-to-end:
|
|
|
|
```bash
|
|
# Run simulator with test patients
|
|
python apps/simulator/his_simulator.py --max-patients 5 --delay 3
|
|
|
|
# Check surveys created
|
|
python manage.py shell -c "
|
|
from apps.surveys.models import SurveyInstance
|
|
surveys = SurveyInstance.objects.order_by('-created_at')[:5]
|
|
print(f'Recent Surveys:')
|
|
for s in surveys:
|
|
print(f'Patient: {s.patient.first_name}, Status: {s.status}')
|
|
"
|
|
```
|
|
|
|
---
|
|
|
|
## SMS Integration (Future Work)
|
|
|
|
### Current Status
|
|
SMS messages are currently **logged but not sent**. The system is ready for SMS integration.
|
|
|
|
### Recommended SMS Services
|
|
|
|
1. **Twilio** (Recommended)
|
|
- Reliable, global coverage
|
|
- Excellent documentation
|
|
- Easy Python integration
|
|
|
|
2. **MessageBird**
|
|
- Good for multi-channel
|
|
- Competitive pricing
|
|
|
|
3. **AWS SNS**
|
|
- AWS native
|
|
- Scalable
|
|
|
|
### Twilio Integration Example
|
|
|
|
```python
|
|
# requirements.txt
|
|
# twilio==8.11.0
|
|
|
|
# apps/surveys/services.py
|
|
from twilio.rest import Client
|
|
from django.conf import settings
|
|
|
|
class SurveyDeliveryService:
|
|
# ... existing methods ...
|
|
|
|
@staticmethod
|
|
def send_survey_sms(survey_instance) -> bool:
|
|
"""Send survey via SMS using Twilio"""
|
|
if not survey_instance.recipient_phone:
|
|
logger.warning(f"No phone number for survey {survey_instance.id}")
|
|
return False
|
|
|
|
try:
|
|
# Generate survey URL and message
|
|
survey_url = SurveyDeliveryService.generate_survey_url(survey_instance)
|
|
patient_name = survey_instance.patient.first_name
|
|
hospital_name = survey_instance.hospital.name
|
|
message = SurveyDeliveryService.generate_sms_message(
|
|
patient_name, survey_url, hospital_name
|
|
)
|
|
|
|
# Initialize Twilio client
|
|
client = Client(
|
|
settings.TWILIO_ACCOUNT_SID,
|
|
settings.TWILIO_AUTH_TOKEN
|
|
)
|
|
|
|
# Send SMS
|
|
sms = client.messages.create(
|
|
body=message,
|
|
from_=settings.TWILIO_PHONE_NUMBER,
|
|
to=survey_instance.recipient_phone
|
|
)
|
|
|
|
# Log success
|
|
logger.info(f"SMS sent via Twilio. SID: {sms.sid}")
|
|
|
|
# Update survey instance
|
|
survey_instance.status = 'SENT'
|
|
survey_instance.sent_at = timezone.now()
|
|
survey_instance.save(update_fields=['status', 'sent_at'])
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error sending SMS for survey {survey_instance.id}: {str(e)}")
|
|
return False
|
|
```
|
|
|
|
### Settings for Twilio
|
|
|
|
```python
|
|
# settings.py
|
|
|
|
# Twilio Configuration
|
|
TWILIO_ACCOUNT_SID = 'your_account_sid'
|
|
TWILIO_AUTH_TOKEN = 'your_auth_token'
|
|
TWILIO_PHONE_NUMBER = '+1234567890' # Your Twilio phone number
|
|
```
|
|
|
|
---
|
|
|
|
## WhatsApp Integration (Future Work)
|
|
|
|
### Status
|
|
WhatsApp delivery is **not yet implemented** but the framework is in place.
|
|
|
|
### Implementation Plan
|
|
|
|
1. **Use WhatsApp Business API**
|
|
2. **Integrate with provider** (Twilio, MessageBird, or Meta)
|
|
3. **Add to SurveyDeliveryService.deliver_survey()**
|
|
4. **Create WhatsApp message templates**
|
|
|
|
### Example Structure
|
|
|
|
```python
|
|
@staticmethod
|
|
def send_survey_whatsapp(survey_instance) -> bool:
|
|
"""Send survey via WhatsApp"""
|
|
# TODO: Implement WhatsApp delivery
|
|
from apps.whatsapp.services import WhatsAppService
|
|
|
|
survey_url = SurveyDeliveryService.generate_survey_url(survey_instance)
|
|
message = SurveyDeliveryService.generate_whatsapp_message(
|
|
survey_instance.patient.first_name,
|
|
survey_url,
|
|
survey_instance.hospital.name
|
|
)
|
|
|
|
success = WhatsAppService.send_message(
|
|
to=survey_instance.recipient_phone,
|
|
message=message
|
|
)
|
|
|
|
if success:
|
|
survey_instance.status = 'SENT'
|
|
survey_instance.sent_at = timezone.now()
|
|
survey_instance.save(update_fields=['status', 'sent_at'])
|
|
|
|
return success
|
|
```
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Issue: Surveys not being delivered
|
|
|
|
**Symptoms:**
|
|
- Survey status remains `PENDING`
|
|
- No SMS/Email sent
|
|
- No logs indicating delivery attempt
|
|
|
|
**Solutions:**
|
|
1. Check journey template has `send_post_discharge_survey=True`
|
|
2. Verify patient has valid contact info (phone/email)
|
|
3. Check logs for errors
|
|
4. Verify email/SMS service configuration
|
|
|
|
### Issue: Email not sent
|
|
|
|
**Symptoms:**
|
|
- Survey status changes to `SENT`
|
|
- But patient doesn't receive email
|
|
|
|
**Solutions:**
|
|
1. Check email backend configuration in settings.py
|
|
2. Verify `recipient_email` is set on survey
|
|
3. Check email server logs
|
|
4. Test email configuration with Django's `send_mail()`
|
|
|
|
### Issue: SMS not sent (after Twilio integration)
|
|
|
|
**Symptoms:**
|
|
- Survey status changes to `SENT`
|
|
- But patient doesn't receive SMS
|
|
|
|
**Solutions:**
|
|
1. Verify Twilio credentials
|
|
2. Check Twilio account balance
|
|
3. Verify recipient phone number format (include country code)
|
|
4. Check Twilio logs for delivery status
|
|
|
|
### Issue: Survey URL not accessible
|
|
|
|
**Symptoms:**
|
|
- Patient receives link but gets 404 or permission error
|
|
|
|
**Solutions:**
|
|
1. Check `SURVEY_BASE_URL` setting
|
|
2. Verify survey `access_token` is valid
|
|
3. Check token hasn't expired
|
|
4. Verify URL view is configured correctly
|
|
|
|
---
|
|
|
|
## Monitoring and Logging
|
|
|
|
### Log Messages
|
|
|
|
The SurveyDeliveryService logs all delivery attempts:
|
|
|
|
```python
|
|
logger.info(f"SMS would be sent to {survey_instance.recipient_phone}")
|
|
logger.info(f"Message: {message}")
|
|
logger.info(f"Survey email sent to {survey_instance.recipient_email}")
|
|
logger.warning(f"WhatsApp delivery not yet implemented for survey {survey_instance.id}")
|
|
logger.error(f"Error sending SMS for survey {survey_instance.id}: {str(e)}")
|
|
```
|
|
|
|
### Monitoring Metrics
|
|
|
|
Track these metrics for survey delivery health:
|
|
|
|
- **Delivery Success Rate**: `(SENT surveys / Total surveys) * 100`
|
|
- **Response Rate**: `(COMPLETED surveys / SENT surveys) * 100`
|
|
- **Delivery Channel Distribution**: SMS vs Email vs WhatsApp
|
|
- **Delivery Time**: Time between creation and SENT status
|
|
- **Response Time**: Time between SENT and COMPLETED
|
|
|
|
### Dashboard Queries
|
|
|
|
```python
|
|
# Delivery success rate
|
|
from apps.surveys.models import SurveyInstance
|
|
from django.db.models import Count, Q
|
|
|
|
total = SurveyInstance.objects.count()
|
|
sent = SurveyInstance.objects.filter(status='SENT').count()
|
|
success_rate = (sent / total * 100) if total > 0 else 0
|
|
|
|
# Response rate
|
|
completed = SurveyInstance.objects.filter(status='COMPLETED').count()
|
|
response_rate = (completed / sent * 100) if sent > 0 else 0
|
|
|
|
# Channel distribution
|
|
channels = SurveyInstance.objects.values('delivery_channel').annotate(
|
|
count=Count('id')
|
|
).order_by('-count')
|
|
```
|
|
|
|
---
|
|
|
|
## Best Practices
|
|
|
|
### 1. Always Use Delivery Service
|
|
|
|
**❌ Bad:**
|
|
```python
|
|
survey.status = 'SENT'
|
|
survey.save()
|
|
```
|
|
|
|
**✅ Good:**
|
|
```python
|
|
from apps.surveys.services import SurveyDeliveryService
|
|
SurveyDeliveryService.deliver_survey(survey)
|
|
```
|
|
|
|
### 2. Validate Contact Information
|
|
|
|
Ensure patients have valid contact info before creating surveys:
|
|
|
|
```python
|
|
if not patient.phone and not patient.email:
|
|
logger.warning(f"Patient {patient.id} has no contact info")
|
|
return None
|
|
```
|
|
|
|
### 3. Handle Delivery Failures Gracefully
|
|
|
|
Even if delivery fails, create the survey record:
|
|
|
|
```python
|
|
delivery_success = SurveyDeliveryService.deliver_survey(survey)
|
|
if not delivery_success:
|
|
logger.warning(f"Survey {survey.id} delivery failed, but record created")
|
|
# Still return survey so it can be retried
|
|
return survey
|
|
```
|
|
|
|
### 4. Use Appropriate Delivery Channel
|
|
|
|
Consider patient preferences and data availability:
|
|
|
|
```python
|
|
# Prefer SMS if available (higher open rate)
|
|
if patient.phone:
|
|
delivery_channel = 'sms'
|
|
elif patient.email:
|
|
delivery_channel = 'email'
|
|
else:
|
|
logger.warning(f"No contact info for patient {patient.id}")
|
|
return None
|
|
```
|
|
|
|
---
|
|
|
|
## Security Considerations
|
|
|
|
### 1. Secure Survey URLs
|
|
|
|
- Survey URLs use secure, randomly generated `access_token`
|
|
- Tokens expire after `SURVEY_TOKEN_EXPIRATION_DAYS`
|
|
- URLs should be sent over HTTPS in production
|
|
|
|
### 2. Contact Information Protection
|
|
|
|
- Phone numbers and emails should be stored securely
|
|
- Limit access to contact information
|
|
- Use encryption in transit (TLS for email, HTTPS for web)
|
|
|
|
### 3. Rate Limiting
|
|
|
|
Consider implementing rate limiting to prevent abuse:
|
|
|
|
```python
|
|
from django.core.cache import cache
|
|
|
|
def can_send_survey(patient_id):
|
|
"""Check if patient can receive survey (rate limit)"""
|
|
key = f'survey_rate_limit_{patient_id}'
|
|
return cache.get(key) is None
|
|
|
|
def set_survey_sent(patient_id):
|
|
"""Mark survey as sent for rate limit"""
|
|
key = f'survey_rate_limit_{patient_id}'
|
|
cache.set(key, True, timeout=86400) # 24 hours
|
|
```
|
|
|
|
---
|
|
|
|
## Future Enhancements
|
|
|
|
### 1. Retry Failed Deliveries
|
|
|
|
Implement retry logic for failed deliveries:
|
|
|
|
```python
|
|
@staticmethod
|
|
def deliver_survey_with_retry(survey_instance, max_retries=3):
|
|
"""Deliver survey with automatic retry"""
|
|
for attempt in range(max_retries):
|
|
success = SurveyDeliveryService.deliver_survey(survey_instance)
|
|
if success:
|
|
return True
|
|
|
|
# Wait before retry
|
|
time.sleep(60 * (attempt + 1))
|
|
|
|
# All retries failed
|
|
logger.error(f"Failed to deliver survey {survey_instance.id} after {max_retries} attempts")
|
|
return False
|
|
```
|
|
|
|
### 2. Delivery Preferences
|
|
|
|
Allow patients to choose preferred delivery channel:
|
|
|
|
```python
|
|
# Patient model
|
|
class Patient(models.Model):
|
|
preferred_contact_method = models.CharField(
|
|
choices=[('sms', 'SMS'), ('email', 'Email'), ('whatsapp', 'WhatsApp')]
|
|
)
|
|
```
|
|
|
|
### 3. Scheduled Surveys
|
|
|
|
Send surveys at optimal times (e.g., 2 hours after discharge):
|
|
|
|
```python
|
|
from django.utils import timezone
|
|
from django.db.models import F
|
|
|
|
def schedule_survey(journey):
|
|
"""Schedule survey for 2 hours after discharge"""
|
|
from apps.surveys.tasks import deliver_scheduled_survey
|
|
|
|
deliver_time = timezone.now() + timedelta(hours=2)
|
|
deliver_scheduled_survey.apply_async(
|
|
args=[journey.id],
|
|
eta=deliver_time
|
|
)
|
|
```
|
|
|
|
### 4. Multi-language Support
|
|
|
|
Send surveys in patient's preferred language:
|
|
|
|
```python
|
|
@staticmethod
|
|
def generate_sms_message(patient_name: str, survey_url: str,
|
|
hospital_name: str = None, language='en') -> str:
|
|
"""Generate SMS message in specified language"""
|
|
if language == 'ar':
|
|
return SurveyDeliveryService._generate_arabic_message(...)
|
|
else:
|
|
return SurveyDeliveryService._generate_english_message(...)
|
|
```
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
The Survey Delivery System is **fully functional** with:
|
|
|
|
✅ **SMS Delivery** (message generation implemented, Twilio integration ready)
|
|
✅ **Email Delivery** (fully implemented using Django mail)
|
|
✅ **Automatic Triggering** (via HISAdapter on patient discharge)
|
|
✅ **Secure URLs** (token-based access)
|
|
✅ **Delivery Tracking** (status updates, timestamps)
|
|
✅ **Error Handling** (graceful failure handling)
|
|
✅ **Logging** (comprehensive activity logging)
|
|
|
|
**Next Steps for Production:**
|
|
1. Integrate with SMS service (Twilio recommended)
|
|
2. Configure email backend settings
|
|
3. Set up monitoring dashboards
|
|
4. Implement retry logic
|
|
5. Add rate limiting
|
|
6. Implement WhatsApp delivery if needed
|
|
|
|
---
|
|
|
|
**Document Version:** 1.0
|
|
**Last Updated:** January 28, 2026
|
|
**Maintainer:** PX360 Development Team |