Kulcare Docs
DeveloperCare plansCare planArchive

Encounter-Prescription Integration Plan

Overview

Integrate the Encounter feature with the existing Prescription flow without code duplication. The Encounter model will own Prescription, enabling clinical documentation for both scheduled appointments and walk-in patients.

Implementation Status

PhaseComponentStatus
Phase 1Backend Foundation✅ Complete
Phase 2Frontend Service Layer✅ Complete
Phase 3UI Integration✅ Complete

Key Decisions

  1. Ownership Model: Encounter OWNS Prescription (parent-child relationship)
  2. Trigger Mechanism: Both auto-create on check-in AND manual walk-in creation
  3. Backend Approach: New Encounter model with has_one :prescription
  4. Practitioner Flexibility: Supports doctors, physiotherapists, psychologists, and other healthcare practitioners
  5. Clinic Independence: Supports teleconsults outside clinic facilities (clinic_id optional)

Architecture

1. Data Model

Appointment (administrative/scheduling)
    |
    v
Encounter (clinical event - owns prescription)
    |
    v
Prescription (clinical documentation)

Relationship Chain:

  • Appointment optionally has_one :encounter
  • Encounter has_one :prescription
  • Prescription.event polymorphic association supports both Appointment and Encounter

Encounter Flexibility:

  • clinic_id - Optional (null for teleconsults outside clinic)
  • doctor_id - Optional (null for non-doctor practitioners like physiotherapists)
  • practitioner_type / practitioner_id - Polymorphic reference supporting multiple practitioner types

2. Hook Composition Pattern

Create useEncounterPrescription that composes existing hooks:

// File: frontend/apps/cms/src/features/encounter/hooks/useEncounterPrescription.ts

export function useEncounterPrescription(encounterId: string) {
  // Compose encounter hook (metadata, status, timing)
  const encounter = useEncounter(encounterId)

  // Compose prescription hook (clinical documentation)
  const prescription = usePrescription({
    eventType: 'Encounter',
    eventId: encounterId
  })

  return {
    // Encounter state
    encounter: encounter.data,
    encounterLoading: encounter.isLoading,
    updateEncounter: encounter.update,

    // Prescription state (delegated)
    prescription: prescription.prescription,
    prescriptionLoading: prescription.isLoading,
    sections: prescription.sections,

    // Unified actions
    save: async () => {
      await encounter.save()
      await prescription.save()
    },
    finalize: async () => {
      await prescription.finalize()
      await encounter.complete()
    }
  }
}

3. Route Structure

/cms/encounters                     -> EncounterListPage (walk-ins + appointment-linked)
/cms/encounters/new                 -> NewEncounterPage (walk-in flow)

/cms/appointments/:appointmentId/encounter -> AppointmentEncounterBridge (creates/fetches encounter, redirects to prescription)

# After encounter creation, navigation goes directly to PrescriptionPage:
/cms/prescription/:appointmentId/:patientId/encounter?did=:doctorId -> PrescriptionPage

Note: There is no separate encounter detail/edit page. After creating or retrieving an encounter, the system navigates directly to the existing PrescriptionPage, which handles all clinical documentation.

4. Bridge Component

// File: frontend/apps/cms/src/features/encounter/components/AppointmentEncounterBridge.tsx

export function AppointmentEncounterBridge() {
  const { appointmentId } = useParams()
  const { data: encounter, isLoading } = useGetOrCreateEncounterByAppointment(appointmentId)

  if (isLoading) return <LoadingState />

  // Redirect directly to prescription page with encounter context
  return <Navigate
    to={`/cms/prescription/${encounter.appointmentId}/${encounter.patientId}/encounter?did=${doctorId}`}
    replace
  />
}

5. Prescription Hook Modifications

Modify usePrescription to accept polymorphic event reference:

// File: frontend/apps/cms/src/features/prescription/hooks/usePrescription.ts

interface UsePrescriptionOptions {
  eventType: 'Appointment' | 'Encounter'
  eventId: string
}

export function usePrescription({ eventType, eventId }: UsePrescriptionOptions) {
  // Use eventType + eventId as query key
  const queryKey = ['prescription', eventType, eventId]

  // API calls use polymorphic endpoint
  // GET /prescriptions?event_type=Encounter&event_id=123
  // POST /prescriptions { event_type: 'Encounter', event_id: '123', ... }
}

API Endpoints

Backend Endpoints (Implemented)

# Encounter endpoints - ✅ IMPLEMENTED
GET    /api/v1/encounters                              # List encounters (with filters)
POST   /api/v1/encounters                              # Create walk-in encounter
GET    /api/v1/encounters/:id                          # Get encounter details
PATCH  /api/v1/encounters/:id                          # Update encounter
DELETE /api/v1/encounters/:id                          # Soft delete encounter

# Encounter actions - ✅ IMPLEMENTED
POST   /api/v1/encounters/:id/complete                 # Complete/finalize encounter

# Appointment-Encounter bridge - ✅ IMPLEMENTED
GET    /api/v1/encounters/for_appointment/:appointment_id  # Get or create encounter for appointment

Request/Response Examples (Tested)

Create Walk-in Encounter (Doctor-based):

POST /api/v1/encounters
{
  "patient_id": 123,
  "clinic_id": 456,
  "encounter_details": {
    "doctor_id": 789,
    "visit_type": "in_person",
    "urgency": "routine",
    "chief_complaint": "Headache and fever"
  }
}

Create Teleconsult Encounter (Outside clinic):

POST /api/v1/encounters
{
  "patient_id": 123,
  "encounter_details": {
    "doctor_id": 789,
    "visit_type": "teleconsultation",
    "chief_complaint": "Follow-up consultation"
  }
}

Create Non-Doctor Encounter (Physiotherapy):

POST /api/v1/encounters
{
  "patient_id": 123,
  "clinic_id": 456,
  "encounter_details": {
    "practitioner_type": "physiotherapist",
    "visit_type": "in_person",
    "chief_complaint": "Lower back pain rehabilitation"
  }
}

Get/Create Encounter for Appointment:

GET /api/v1/encounters/for_appointment/789

Response:
{
  "data": {
    "type": "encounter",
    "id": 123,
    "attributes": {
      "status": "in_progress",
      "visit_type": "in_person",
      "urgency": "routine",
      "practitioner_type": "doctor",
      "chief_complaint": "Scheduled appointment",
      "started_at": "2024-12-23T10:30:00Z",
      "ended_at": null
    },
    "relationships": {
      "patient": { ... },
      "doctor": { ... },
      "clinic": { ... },
      "appointment": { ... }
    }
  }
}

List Encounters with Filters:

GET /api/v1/encounters?status=in_progress&clinic_id=456&page=1&per_page=20

Response:
{
  "data": [...],
  "meta": {
    "current_page": 1,
    "total_pages": 5,
    "total_count": 100
  }
}

Complete Encounter:

POST /api/v1/encounters/123/complete
{
  "notes": "Patient treated successfully"
}

Response:
{
  "data": {
    "id": 123,
    "attributes": {
      "status": "completed",
      "ended_at": "2024-12-23T11:00:00Z"
    }
  }
}

Implementation Phases

Phase 1: Backend Foundation ✅ COMPLETE

TaskStatusFile
Create Encounter model with migrations✅parts/clinic/migrate/20251223145803_create_encounters.rb
Create Encounter model with validations✅parts/clinic/models/encounter.rb
Add has_one :encounter to Appointment✅parts/clinic/models/appointment.rb
Create Encounter serializer✅parts/clinic/serializers/encounter_serializer.rb
Create Encounter controller✅parts/clinic/controllers/encounters_controller.rb
Create Encounter policy✅parts/clinic/policies/encounters_controller_policy.rb
Create Encounter routes✅parts/clinic/routes.rb
Create interactors (7 total)✅parts/clinic/interactors/encounter_actions/
Create factory for testing✅parts/clinic/spec/factories/encounters.rb
Create model spec✅parts/clinic/spec/models/encounter_spec.rb

Interactors Created:

  • EncounterActions::Create - Create new encounter with validation
  • EncounterActions::Index - List encounters with filters and pagination
  • EncounterActions::Show - Get single encounter details
  • EncounterActions::Update - Update encounter fields
  • EncounterActions::Delete - Soft delete encounter
  • EncounterActions::Complete - Complete/finalize encounter
  • EncounterActions::GetOrCreateForAppointment - Bridge for appointment-based encounters

Phase 2: Frontend Service Layer ✅ COMPLETE

TaskStatusFile
Create comprehensive TypeScript types✅features/encounter/types/index.ts
Create API service with CRUD operations✅features/encounter/services/encounterApi.ts
Create EncounterAutoSave class for debouncing✅features/encounter/services/encounterApi.ts
Create transformation utilities✅transformToListItem, transformToEnhancedEncounter
Create React Query hooks✅features/encounter/hooks/useEncounter.ts
Create useEncounterPrescription composition hook✅features/encounter/hooks/useEncounterPrescription.ts
Export barrel files✅services/index.ts, hooks/index.ts, index.ts

Implemented Hooks:

  • useEncounter - Fetch single encounter with transformation
  • useEncounterList - List encounters with filtering
  • useCreateEncounter - Create with cache invalidation
  • useUpdateEncounter - Update with optimistic updates
  • useDeleteEncounter - Soft delete with cache removal
  • useFinalizeEncounter - Complete/finalize encounter
  • useGetOrCreateEncounterByAppointment - Bridge for appointment-based encounters
  • useEncounterAutoSave - Debounced auto-save with cache updates
  • usePatientContext - Patient context for encounter
  • usePreviousPrescription - Copy previous functionality
  • useEncounterTimeline - Patient encounter timeline
  • usePrintPrescription - Print prescription mutation
  • useSendPrescription - Send prescription to patient

Key Features:

  • All hooks use string IDs externally, convert to numbers for API calls
  • Optimistic updates for responsive UI
  • Proper cache invalidation on mutations
  • Composition pattern in useEncounterPrescription for unified encounter+prescription management

Phase 3: UI Integration ✅ COMPLETE

TaskStatusNotes
Create AppointmentEncounterBridge component✅Redirects to PrescriptionPage
Update routing in CMS app✅Routes configured
Create EncounterListPage for walk-ins✅Displays queue with active encounters
Create NewEncounterPage for walk-in flow✅Creates encounter and navigates to prescription
Navigate to PrescriptionPage with encounter context✅All flows go to existing prescription page

Key Decision: Rather than creating a separate EncounterEditPage, all encounter flows navigate directly to the existing PrescriptionPage. This avoids code duplication and leverages the mature prescription workflow.

Files Created/Modified

Backend Files (Implemented)

New Files:

  • parts/clinic/migrate/20251223145803_create_encounters.rb - Migration
  • parts/clinic/models/encounter.rb - Model
  • parts/clinic/serializers/encounter_serializer.rb - Serializer
  • parts/clinic/controllers/encounters_controller.rb - Controller
  • parts/clinic/policies/encounters_controller_policy.rb - Policy
  • parts/clinic/interactors/encounter_actions/create.rb - Create interactor
  • parts/clinic/interactors/encounter_actions/index.rb - Index interactor
  • parts/clinic/interactors/encounter_actions/show.rb - Show interactor
  • parts/clinic/interactors/encounter_actions/update.rb - Update interactor
  • parts/clinic/interactors/encounter_actions/delete.rb - Delete interactor
  • parts/clinic/interactors/encounter_actions/complete.rb - Complete interactor
  • parts/clinic/interactors/encounter_actions/get_or_create_for_appointment.rb - Bridge interactor
  • parts/clinic/spec/factories/encounters.rb - Factory
  • parts/clinic/spec/models/encounter_spec.rb - Model spec

Modified Files:

  • parts/clinic/models/appointment.rb - Added has_one :encounter
  • parts/clinic/routes.rb - Added encounter routes

Frontend Files (All Complete)

Service Layer ✅:

  • frontend/apps/cms/src/features/encounter/types/index.ts - Comprehensive TypeScript definitions
  • frontend/apps/cms/src/features/encounter/services/encounterApi.ts - API service with CRUD operations
  • frontend/apps/cms/src/features/encounter/services/index.ts - Service barrel export
  • frontend/apps/cms/src/features/encounter/hooks/useEncounter.ts - React Query hooks
  • frontend/apps/cms/src/features/encounter/hooks/useEncounterPrescription.ts - Composition hook
  • frontend/apps/cms/src/features/encounter/hooks/index.ts - Hooks barrel export
  • frontend/apps/cms/src/features/encounter/index.ts - Feature barrel export

UI Files ✅:

  • frontend/apps/cms/src/features/encounter/pages/EncounterListPage.tsx - Queue with active encounters
  • frontend/apps/cms/src/features/encounter/pages/NewEncounterPage.tsx - Walk-in encounter creation
  • frontend/apps/cms/src/features/encounter/pages/index.ts - Pages barrel export
  • frontend/apps/cms/src/features/encounter/components/AppointmentEncounterBridge.tsx - Appointment-to-prescription bridge
  • frontend/apps/cms/src/features/encounter/components/index.ts - Components barrel export

Note: EncounterEditPage was intentionally not created. All encounter editing flows navigate directly to the existing PrescriptionPage.

Modified Files:

  • frontend/apps/cms/src/features/prescription/hooks/usePrescription.ts - Already supports polymorphic events
  • frontend/apps/cms/src/App.tsx - Routes fully configured

Encounter Model Details

Constants

STATUSES = %w[scheduled in_progress paused completed cancelled]
VISIT_TYPES = %w[in_person teleconsultation follow_up home_visit]
URGENCY_LEVELS = %w[routine urgent emergency]
PRACTITIONER_TYPES = %w[doctor physiotherapist psychologist therapist nurse dietitian other]

Scopes

  • not_deleted - Exclude soft-deleted encounters
  • active - Encounters with status in_progress or paused
  • completed - Encounters with status completed
  • for_clinic(clinic_id) - Filter by clinic
  • for_patient(patient_id) - Filter by patient
  • for_doctor(doctor_id) - Filter by doctor
  • by_status(status) - Filter by status
  • by_visit_type(visit_type) - Filter by visit type
  • recent - Order by started_at descending

Instance Methods

  • active? - Check if encounter is active
  • completed? - Check if encounter is completed
  • walk_in? - Check if encounter is a walk-in (no appointment)
  • teleconsultation? - Check if visit type is teleconsultation
  • duration_minutes - Calculate duration in minutes
  • complete! - Mark encounter as completed
  • cancel! - Mark encounter as cancelled
  • pause! - Mark encounter as paused
  • resume! - Resume a paused encounter
  • soft_delete! - Soft delete the encounter

Benefits

  1. No Code Duplication: Prescription logic reused via composition
  2. Clean Separation: Encounter handles metadata, Prescription handles clinical docs
  3. Flexible Triggers: Supports both scheduled appointments and walk-ins
  4. Backward Compatible: Existing appointment-prescription flow continues to work
  5. Future-Proof: Encounter can be extended for additional clinical contexts
  6. Practitioner Flexible: Supports various healthcare practitioner types beyond doctors
  7. Location Independent: Supports both clinic-based and remote teleconsultation encounters

Testing Strategy

  1. Unit Tests: Test useEncounterPrescription composition
  2. Integration Tests: Test appointment-to-encounter flow
  3. E2E Tests: Test complete walk-in and appointment encounter flows
  4. Migration Tests: Verify existing prescriptions still work
  5. Backend Tests: Model specs, interactor specs (completed)

Completed Steps ✅

  1. Frontend Types: Comprehensive TypeScript definitions for all encounter entities
  2. Frontend API Service: encounterApi.ts with all CRUD operations and transformations
  3. Auto-Save Manager: EncounterAutoSave class with debounced updates
  4. React Query Hooks: All hooks created with optimistic updates and cache management
  5. Composition Hook: useEncounterPrescription for unified encounter+prescription management

Completed Implementation

All three phases are now complete:

  1. Backend Foundation ✅ - Full Encounter model with API endpoints and interactors
  2. Frontend Service Layer ✅ - API service, React Query hooks, and composition patterns
  3. UI Integration ✅ - Pages, components, and routing to PrescriptionPage

Architecture Decision

Rather than duplicating clinical documentation UI in a separate EncounterEditPage, the system navigates directly to the existing PrescriptionPage after encounter creation. This approach:

  • Eliminates code duplication
  • Leverages the mature, tested prescription workflow
  • Simplifies maintenance
  • Provides a consistent user experience

Future Improvements

  1. Testing: Add unit tests for hooks and integration tests for flows
  2. Fix TypeScript Errors: Address pre-existing type errors in related components
  3. Enhanced Walk-in Flow: Add more patient context during walk-in encounter creation

On this page