HH/docs/JOURNEY_ENGINE.md
2025-12-24 12:42:31 +03:00

14 KiB

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:

{
  "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

{
  "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

{
  "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

{
  "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

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

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:

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

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

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

curl http://localhost:8000/api/journeys/instances/JOURNEY_UUID/progress/ \
  -H "Authorization: Bearer YOUR_TOKEN"

Expected Response:

{
  "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

# 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

curl -X POST http://localhost:8000/api/integrations/events/EVENT_UUID/reprocess/ \
  -H "Authorization: Bearer YOUR_TOKEN"

View Journey Progress

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