545 lines
14 KiB
Markdown
545 lines
14 KiB
Markdown
# PX360 Journey & Survey Engine
|
|
|
|
## Overview
|
|
|
|
The Journey & Survey Engine is the core of PX360's patient experience tracking system. It implements an event-driven architecture where external systems (HIS, Lab, Radiology, Pharmacy) send integration events that trigger journey stage completions, which in turn automatically send stage-specific surveys to patients.
|
|
|
|
## Architecture
|
|
|
|
### Components
|
|
|
|
1. **Journey Templates** - Define the stages for each journey type (EMS/Inpatient/OPD)
|
|
2. **Journey Instances** - Track actual patient encounters
|
|
3. **Stage Templates** - Define individual stages with trigger events
|
|
4. **Stage Instances** - Track completion of each stage
|
|
5. **Inbound Events** - Integration events from external systems
|
|
6. **Survey Templates** - Define surveys for each stage
|
|
7. **Survey Instances** - Actual surveys sent to patients
|
|
|
|
### Flow Diagram
|
|
|
|
```
|
|
External System (HIS/Lab/etc.)
|
|
↓
|
|
POST /api/integrations/events/
|
|
↓
|
|
InboundEvent (status: PENDING)
|
|
↓
|
|
Celery Task: process_inbound_event
|
|
↓
|
|
Find Journey Instance (by encounter_id)
|
|
↓
|
|
Find Matching Stage (by trigger_event_code)
|
|
↓
|
|
Complete Stage → Update timestamps
|
|
↓
|
|
If auto_send_survey=True
|
|
↓
|
|
Create SurveyInstance
|
|
↓
|
|
Send Survey (SMS/WhatsApp/Email)
|
|
↓
|
|
Patient Completes Survey
|
|
↓
|
|
If score < threshold → Create PXAction
|
|
```
|
|
|
|
## Journey Types
|
|
|
|
### 1. EMS (Emergency Medical Services)
|
|
Tracks emergency patient pathway from arrival to discharge.
|
|
|
|
**Example Stages:**
|
|
- Triage
|
|
- Emergency Physician Consultation
|
|
- Diagnostic Tests
|
|
- Treatment
|
|
- Discharge
|
|
|
|
### 2. Inpatient
|
|
Tracks admitted patient journey from admission to discharge.
|
|
|
|
**Example Stages:**
|
|
- Admission
|
|
- Ward Assignment
|
|
- Physician Rounds
|
|
- Nursing Care
|
|
- Diagnostic Services
|
|
- Treatment
|
|
- Discharge
|
|
|
|
### 3. OPD (Outpatient Department)
|
|
Tracks outpatient visit from registration to checkout.
|
|
|
|
**Example Stages:**
|
|
- Registration
|
|
- MD Consultation
|
|
- Lab Tests
|
|
- Radiology
|
|
- Pharmacy
|
|
- Checkout
|
|
|
|
## Integration Event Contracts
|
|
|
|
### Event Structure
|
|
|
|
All integration events must follow this structure:
|
|
|
|
```json
|
|
{
|
|
"source_system": "his|lab|radiology|pharmacy|moh|chi|other",
|
|
"event_code": "EVENT_CODE",
|
|
"encounter_id": "UNIQUE_ENCOUNTER_ID",
|
|
"patient_identifier": "MRN_OR_PATIENT_ID",
|
|
"payload_json": {
|
|
"physician_license": "PHY001",
|
|
"department_code": "DEPT_OPD",
|
|
"timestamp": "2025-12-14T10:30:00Z",
|
|
"additional_data": {}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Standard Event Codes
|
|
|
|
#### OPD Events
|
|
- `OPD_REGISTRATION_COMPLETED` - Patient registered
|
|
- `OPD_VISIT_COMPLETED` - MD consultation completed
|
|
- `OPD_CHECKOUT_COMPLETED` - Patient checked out
|
|
|
|
#### Lab Events
|
|
- `LAB_ORDER_CREATED` - Lab order placed
|
|
- `LAB_SAMPLE_COLLECTED` - Sample collected
|
|
- `LAB_ORDER_COMPLETED` - Results ready
|
|
|
|
#### Radiology Events
|
|
- `RADIOLOGY_ORDER_CREATED` - Radiology order placed
|
|
- `RADIOLOGY_EXAM_COMPLETED` - Exam performed
|
|
- `RADIOLOGY_REPORT_FINALIZED` - Report signed off
|
|
|
|
#### Pharmacy Events
|
|
- `PHARMACY_ORDER_RECEIVED` - Prescription received
|
|
- `PHARMACY_DISPENSED` - Medication dispensed
|
|
|
|
#### Inpatient Events
|
|
- `ADMISSION_COMPLETED` - Patient admitted
|
|
- `WARD_TRANSFER` - Patient transferred to ward
|
|
- `DISCHARGE_COMPLETED` - Patient discharged
|
|
|
|
#### EMS Events
|
|
- `EMS_ARRIVAL` - Patient arrived via EMS
|
|
- `TRIAGE_COMPLETED` - Triage assessment done
|
|
- `ER_PHYSICIAN_CONSULT` - ER physician saw patient
|
|
|
|
### Example Event Payloads
|
|
|
|
#### OPD Visit Completed
|
|
```json
|
|
{
|
|
"source_system": "his",
|
|
"event_code": "OPD_VISIT_COMPLETED",
|
|
"encounter_id": "ENC20251214001",
|
|
"patient_identifier": "MRN123456",
|
|
"payload_json": {
|
|
"physician_license": "PHY001",
|
|
"department_code": "DEPT_OPD",
|
|
"visit_date": "2025-12-14",
|
|
"visit_time": "10:30:00",
|
|
"chief_complaint": "Follow-up",
|
|
"diagnosis_codes": ["Z00.00"],
|
|
"timestamp": "2025-12-14T10:45:00Z"
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Lab Order Completed
|
|
```json
|
|
{
|
|
"source_system": "lab",
|
|
"event_code": "LAB_ORDER_COMPLETED",
|
|
"encounter_id": "ENC20251214001",
|
|
"patient_identifier": "MRN123456",
|
|
"payload_json": {
|
|
"department_code": "DEPT_LAB",
|
|
"order_id": "LAB789",
|
|
"test_codes": ["CBC", "BMP"],
|
|
"collection_time": "2025-12-14T11:00:00Z",
|
|
"result_time": "2025-12-14T14:30:00Z",
|
|
"timestamp": "2025-12-14T14:30:00Z"
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Pharmacy Dispensed
|
|
```json
|
|
{
|
|
"source_system": "pharmacy",
|
|
"event_code": "PHARMACY_DISPENSED",
|
|
"encounter_id": "ENC20251214001",
|
|
"patient_identifier": "MRN123456",
|
|
"payload_json": {
|
|
"department_code": "DEPT_PHARMACY",
|
|
"prescription_id": "RX456",
|
|
"medications": [
|
|
{"name": "Medication A", "quantity": 30},
|
|
{"name": "Medication B", "quantity": 60}
|
|
],
|
|
"dispensed_time": "2025-12-14T15:00:00Z",
|
|
"timestamp": "2025-12-14T15:00:00Z"
|
|
}
|
|
}
|
|
```
|
|
|
|
## Journey Configuration
|
|
|
|
### Creating a Journey Template
|
|
|
|
**Step 1: Create Journey Template**
|
|
```python
|
|
from apps.journeys.models import PatientJourneyTemplate, JourneyType
|
|
from apps.organizations.models import Hospital
|
|
|
|
hospital = Hospital.objects.get(code='HOSP001')
|
|
|
|
journey_template = PatientJourneyTemplate.objects.create(
|
|
name='Standard OPD Journey',
|
|
name_ar='رحلة العيادات الخارجية القياسية',
|
|
journey_type=JourneyType.OPD,
|
|
hospital=hospital,
|
|
is_active=True,
|
|
is_default=True,
|
|
description='Standard outpatient journey with MD, Lab, Radiology, and Pharmacy stages'
|
|
)
|
|
```
|
|
|
|
**Step 2: Create Stage Templates**
|
|
```python
|
|
from apps.journeys.models import PatientJourneyStageTemplate
|
|
from apps.surveys.models import SurveyTemplate
|
|
|
|
# Get survey templates
|
|
md_survey = SurveyTemplate.objects.get(name='MD Consultation Survey')
|
|
lab_survey = SurveyTemplate.objects.get(name='Lab Experience Survey')
|
|
|
|
# Stage 1: MD Consultation
|
|
PatientJourneyStageTemplate.objects.create(
|
|
journey_template=journey_template,
|
|
name='MD Consultation',
|
|
name_ar='استشارة الطبيب',
|
|
code='OPD_MD_CONSULT',
|
|
order=1,
|
|
trigger_event_code='OPD_VISIT_COMPLETED',
|
|
survey_template=md_survey,
|
|
auto_send_survey=True,
|
|
survey_delay_hours=2, # Send 2 hours after visit
|
|
requires_physician=True,
|
|
requires_department=True,
|
|
is_optional=False,
|
|
is_active=True
|
|
)
|
|
|
|
# Stage 2: Lab
|
|
PatientJourneyStageTemplate.objects.create(
|
|
journey_template=journey_template,
|
|
name='Laboratory',
|
|
name_ar='المختبر',
|
|
code='LAB',
|
|
order=2,
|
|
trigger_event_code='LAB_ORDER_COMPLETED',
|
|
survey_template=lab_survey,
|
|
auto_send_survey=True,
|
|
survey_delay_hours=1,
|
|
requires_physician=False,
|
|
requires_department=True,
|
|
is_optional=True, # Not all patients need lab
|
|
is_active=True
|
|
)
|
|
|
|
# Add more stages as needed...
|
|
```
|
|
|
|
### Creating a Journey Instance
|
|
|
|
**When a patient encounter starts:**
|
|
```python
|
|
from apps.journeys.models import PatientJourneyInstance
|
|
from apps.organizations.models import Patient
|
|
|
|
patient = Patient.objects.get(mrn='MRN123456')
|
|
|
|
journey_instance = PatientJourneyInstance.objects.create(
|
|
journey_template=journey_template,
|
|
patient=patient,
|
|
encounter_id='ENC20251214001',
|
|
hospital=hospital,
|
|
department=opd_department
|
|
)
|
|
|
|
# Stage instances are automatically created for all active stages
|
|
```
|
|
|
|
## Event Processing
|
|
|
|
### Processing Logic
|
|
|
|
When an event is received:
|
|
|
|
1. **Event Receipt**
|
|
- POST to `/api/integrations/events/`
|
|
- Event stored with `status=PENDING`
|
|
- Audit log created
|
|
- Celery task queued
|
|
|
|
2. **Event Processing** (Celery Task)
|
|
- Find journey instance by `encounter_id`
|
|
- Find matching stage where:
|
|
- `stage_template.trigger_event_code == event.event_code`
|
|
- `stage_instance.status IN [PENDING, IN_PROGRESS]`
|
|
- Extract physician and department from payload
|
|
- Complete the stage:
|
|
- Set `status=COMPLETED`
|
|
- Set `completed_at=now()`
|
|
- Attach `physician`, `department`, `event`
|
|
- Store event payload in metadata
|
|
|
|
3. **Survey Triggering**
|
|
- If `stage_template.auto_send_survey=True`
|
|
- If `stage_template.survey_template` exists
|
|
- Create `SurveyInstance`
|
|
- Queue survey send task with delay
|
|
|
|
4. **Journey Completion**
|
|
- Check if all required stages completed
|
|
- If yes, mark journey as `COMPLETED`
|
|
|
|
### Error Handling
|
|
|
|
**Event Not Found:**
|
|
- Status: `IGNORED`
|
|
- Reason: "No journey instance found for encounter {encounter_id}"
|
|
|
|
**Stage Not Found:**
|
|
- Status: `IGNORED`
|
|
- Reason: "No pending stage found with trigger {event_code}"
|
|
|
|
**Processing Error:**
|
|
- Status: `FAILED`
|
|
- Error message stored
|
|
- Retry 3 times with exponential backoff
|
|
- Can be manually reprocessed by PX Admin
|
|
|
|
## API Endpoints
|
|
|
|
### Journey Templates
|
|
```
|
|
GET /api/journeys/templates/ # List templates
|
|
POST /api/journeys/templates/ # Create template
|
|
GET /api/journeys/templates/{id}/ # Get template
|
|
PUT /api/journeys/templates/{id}/ # Update template
|
|
DELETE /api/journeys/templates/{id}/ # Delete template
|
|
```
|
|
|
|
### Journey Instances
|
|
```
|
|
GET /api/journeys/instances/ # List instances
|
|
POST /api/journeys/instances/ # Create instance
|
|
GET /api/journeys/instances/{id}/ # Get instance
|
|
GET /api/journeys/instances/{id}/progress/ # Get progress summary
|
|
```
|
|
|
|
### Integration Events
|
|
```
|
|
POST /api/integrations/events/ # Create event (external systems)
|
|
POST /api/integrations/events/bulk_create/ # Bulk create events
|
|
GET /api/integrations/events/ # List events (PX Admin)
|
|
GET /api/integrations/events/{id}/ # Get event details
|
|
POST /api/integrations/events/{id}/reprocess/ # Reprocess failed event
|
|
```
|
|
|
|
## Testing
|
|
|
|
### Test Scenario: OPD Journey
|
|
|
|
**1. Create Journey Instance**
|
|
```bash
|
|
curl -X POST http://localhost:8000/api/journeys/instances/ \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"journey_template": "TEMPLATE_UUID",
|
|
"patient": "PATIENT_UUID",
|
|
"encounter_id": "ENC20251214001",
|
|
"hospital": "HOSPITAL_UUID"
|
|
}'
|
|
```
|
|
|
|
**2. Send MD Visit Completed Event**
|
|
```bash
|
|
curl -X POST http://localhost:8000/api/integrations/events/ \
|
|
-H "Authorization: Bearer YOUR_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"source_system": "his",
|
|
"event_code": "OPD_VISIT_COMPLETED",
|
|
"encounter_id": "ENC20251214001",
|
|
"patient_identifier": "MRN123456",
|
|
"payload_json": {
|
|
"physician_license": "PHY001",
|
|
"department_code": "DEPT_OPD",
|
|
"timestamp": "2025-12-14T10:45:00Z"
|
|
}
|
|
}'
|
|
```
|
|
|
|
**3. Verify Stage Completion**
|
|
```bash
|
|
curl http://localhost:8000/api/journeys/instances/JOURNEY_UUID/progress/ \
|
|
-H "Authorization: Bearer YOUR_TOKEN"
|
|
```
|
|
|
|
**Expected Response:**
|
|
```json
|
|
{
|
|
"journey_id": "...",
|
|
"encounter_id": "ENC20251214001",
|
|
"patient": "John Doe",
|
|
"journey_type": "opd",
|
|
"status": "active",
|
|
"completion_percentage": 25,
|
|
"is_complete": false,
|
|
"stages": [
|
|
{
|
|
"name": "MD Consultation",
|
|
"order": 1,
|
|
"status": "completed",
|
|
"completed_at": "2025-12-14T10:45:00Z",
|
|
"survey_sent": true
|
|
},
|
|
{
|
|
"name": "Laboratory",
|
|
"order": 2,
|
|
"status": "pending",
|
|
"completed_at": null,
|
|
"survey_sent": false
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
## Configuration Best Practices
|
|
|
|
### 1. Survey Delay
|
|
- **Immediate (0 hours)**: For time-sensitive feedback (ER, urgent care)
|
|
- **2-4 hours**: For outpatient visits (allows patient to leave facility)
|
|
- **24 hours**: For inpatient daily surveys
|
|
- **48-72 hours**: For discharge surveys
|
|
|
|
### 2. Optional vs Required Stages
|
|
- **Required**: Core pathway stages (MD consultation, admission, discharge)
|
|
- **Optional**: Ancillary services (lab, radiology, pharmacy) - not all patients need these
|
|
|
|
### 3. Trigger Event Codes
|
|
- Use consistent naming: `{SERVICE}_{ACTION}_COMPLETED`
|
|
- Examples: `OPD_VISIT_COMPLETED`, `LAB_ORDER_COMPLETED`
|
|
- Document all codes in integration contracts
|
|
|
|
### 4. Survey Templates
|
|
- Create specific surveys for each stage
|
|
- Keep surveys short (5-7 questions max)
|
|
- Use NPS or Likert scales
|
|
- Include open-ended feedback field
|
|
|
|
## Monitoring & Troubleshooting
|
|
|
|
### Check Event Processing Status
|
|
```bash
|
|
# List pending events
|
|
curl http://localhost:8000/api/integrations/events/?status=pending \
|
|
-H "Authorization: Bearer YOUR_TOKEN"
|
|
|
|
# List failed events
|
|
curl http://localhost:8000/api/integrations/events/?status=failed \
|
|
-H "Authorization: Bearer YOUR_TOKEN"
|
|
```
|
|
|
|
### Reprocess Failed Event
|
|
```bash
|
|
curl -X POST http://localhost:8000/api/integrations/events/EVENT_UUID/reprocess/ \
|
|
-H "Authorization: Bearer YOUR_TOKEN"
|
|
```
|
|
|
|
### View Journey Progress
|
|
```bash
|
|
curl http://localhost:8000/api/journeys/instances/?encounter_id=ENC123 \
|
|
-H "Authorization: Bearer YOUR_TOKEN"
|
|
```
|
|
|
|
## Audit Trail
|
|
|
|
All events are logged:
|
|
- Event receipt → `integration_event`
|
|
- Stage completion → `stage_completed`
|
|
- Journey completion → `journey_completed`
|
|
- Survey sent → `survey_sent`
|
|
|
|
View audit logs in Django admin or via API.
|
|
|
|
## Performance Considerations
|
|
|
|
### Database Indexes
|
|
- `encounter_id` - Fast journey lookup
|
|
- `trigger_event_code` - Fast stage matching
|
|
- `status` + `received_at` - Efficient pending event queries
|
|
|
|
### Celery Configuration
|
|
- Events processed asynchronously
|
|
- Retry logic with exponential backoff
|
|
- Max 100 events per batch in periodic task
|
|
- Separate queue for event processing (optional)
|
|
|
|
### Optimization Tips
|
|
1. Use `select_related` for foreign keys
|
|
2. Use `prefetch_related` for reverse relations
|
|
3. Batch process events in periodic task
|
|
4. Monitor Celery queue length
|
|
5. Scale Celery workers as needed
|
|
|
|
## Security
|
|
|
|
### API Authentication
|
|
- JWT tokens for internal systems
|
|
- API keys for external systems (TODO: implement)
|
|
- Rate limiting on event endpoint (TODO: implement)
|
|
|
|
### Data Validation
|
|
- All events validated before storage
|
|
- Payload JSON validated
|
|
- Encounter ID format validation
|
|
- Source system whitelist
|
|
|
|
## Troubleshooting
|
|
|
|
### Event Not Processing
|
|
1. Check event status in admin
|
|
2. Check Celery worker logs
|
|
3. Verify journey instance exists
|
|
4. Verify stage template has matching trigger code
|
|
5. Check stage instance status (must be PENDING or IN_PROGRESS)
|
|
|
|
### Survey Not Sent
|
|
1. Verify `auto_send_survey=True` on stage template
|
|
2. Verify `survey_template` is set
|
|
3. Check survey instance created
|
|
4. Check notification logs (Phase 4)
|
|
|
|
### Stage Not Completing
|
|
1. Verify event code matches `trigger_event_code`
|
|
2. Check stage instance status
|
|
3. Verify physician/department codes if required
|
|
4. Check event processing logs
|
|
|
|
---
|
|
|
|
**For API documentation, see:** `/api/docs/`
|
|
**For implementation status, see:** `IMPLEMENTATION_STATUS.md`
|