Kulcare Docs
DeveloperEncounters

EncounterPage Functional Workflow Documentation

Purpose: Complete workflow breakdown of the EncounterPage focused on user-facing behavior, data flow, and functional logic.

Scope: The EncounterPage is a multi-specialty clinical documentation interface enabling healthcare practitioners to document patient encounters across 35+ clinical domains.


1. Page Purpose & Entry Point

What the Page Does

The EncounterPage is the primary clinical documentation interface for healthcare encounters. It allows a practitioner (doctor, physiotherapist, dietitian, etc.) to document a patient visit by filling out specialty-specific forms organized into tabs, saving the data, and optionally finalizing the encounter.

How a User Arrives at This Page

A user navigates to this page by clicking into an existing encounter from the dashboard or queue. The page requires:

  • Encounter ID: Identifies which clinical encounter is being documented
  • Patient ID: Identifies the patient (may come from URL params, query params, or the encounter itself)
  • Appointment ID: Optional, enables AI Scribe integration when present

Entry URL pattern:

/encounter/:encounterId?patientId=X&appointmentId=Y&practitionerType=Z

2. Practitioner Type & Tab Configuration

How the System Determines What Tabs to Show

On page load, the system reads the practitionerType from three sources in priority order:

  1. URL query parameter practitionerType
  2. The practitioner_type field stored on the encounter record
  3. Default fallback: "doctor"

Using this practitioner type, the system fetches an Encounter Type Configuration — a set of rules that determines:

  • Which tabs are visible
  • Which tab is shown by default
  • Whether prescriptions and investigations tabs are enabled

Tab Visibility Rules

Each specialty domain has its own set of tabs. However, two tabs have additional conditional logic:

  • Medications tab: Hidden if encounterTypeConfig.has_prescriptions === false
  • Investigations tab: Hidden if encounterTypeConfig.has_investigations === false

3. Data Loading & Initialization Flow

Step-by-Step Load Sequence

1. Page URL is parsed for encounterId, patientId, appointmentId, practitionerType
2. Practitioner type is resolved from URL → encounter data → default
3. Encounter Type Config is fetched for the practitioner type
4. Encounter metadata is fetched from the clinical API (status, start time, practitioner, facility)
5. Patient details are fetched from the clinical API (name, age, gender, allergies, conditions)
6. All clinical data is fetched from the clinical API (vitals, observations, medications, etc.)
7. Clinical data is "seeded" into local state — the page becomes interactive

Data Seeding Behavior

The system uses a one-time seed pattern:

  1. While clinical data is loading, local state remains at defaults
  2. Once loading completes, if any clinical data exists, local state is initialized with it
  3. A hasSeededFromAPI flag prevents re-seeding on subsequent renders
  4. Even if no data exists, the seed still runs (with empty arrays) to mark initialization complete

Patient Data Normalization

Patient data from the API has two possible sources and gets normalized:

  • From the patient API: Full demographics (name, age, gender, phone, email, MRN, allergies, conditions)
  • From the encounter's embedded patient object: Fallback if patient API fails (display name, MRN only)

The system prioritizes patient API data and falls back to encounter-embedded data if the patient fetch fails.


4. Tabs & Sections

Complete Tab Inventory

The page contains 35+ tabs organized into clinical specialty domains:

Universal / Core Tabs (shown to all practitioners)

Tab IDNamePurpose
vitalsVitalsRecord vital signs (BP, heart rate, temperature, weight, etc.)
observationsObservationsClinical findings, symptoms, assessments
medicationsMedicationsPrescribe medications with drug interaction checking
investigationsInvestigationsOrder lab tests, imaging, referrals
adviceAdviceProvide patient advice and schedule follow-ups

Dental

Tab IDNamePurpose
dentalDentalTooth-by-tooth findings and treatment planning

Mental Health

Tab IDNamePurpose
clinical-historyClinical HistoryPsychiatric history, substance use, working diagnoses
session-notesSession NotesSession narrative and therapist notes
assessmentsAssessmentsStandardized mental health assessments
treatment-planTreatment PlanTreatment goals and objectives
homeworkHomeworkPatient assignments between sessions

Physiotherapy

Tab IDNamePurpose
assessmentAssessmentPain, range of motion, muscle strength, functional status
exercisesExercisesExercise prescriptions
progressProgressProgress notes and tracking entries

Dermatology

Tab IDNamePurpose
skin-examinationSkin ExaminationLesion mapping and dermatological findings
proceduresProceduresDermatological procedures performed
photo-documentationPhoto DocumentationClinical photography upload

Aesthetic Medicine

Tab IDNamePurpose
skin-assessmentSkin AssessmentFitzpatrick type, skin concerns, analysis
cosmetic-proceduresCosmetic ProceduresAesthetic procedures (Botox, fillers, etc.)
consentConsentPatient consent management for procedures

Cardiology

Tab IDNamePurpose
cardiac-examinationCardiac ExamHeart sounds, murmurs, rhythm examination
ecg-interpretationECG InterpretationECG findings and interpretation
cardiac-riskCardiac RiskCardiovascular risk assessment

Cardiac Surgery

Tab IDNamePurpose
surgical-proceduresSurgical ProceduresCardiac surgery procedures
post-op-assessmentPost-Op AssessmentPost-operative wound status, complications

Dietitian

Tab IDNamePurpose
nutritional-assessmentNutritional AssessmentDietary assessment and restrictions
meal-planMeal PlanStructured meal planning
goalsGoalsNutrition goals
dietitian-progressProgressDietitian progress tracking

Ophthalmology

Tab IDNamePurpose
eye-examinationEye ExaminationVisual acuity, fundoscopy findings
vision-assessmentVision AssessmentComprehensive vision testing

ENT

Tab IDNamePurpose
ent-examinationENT ExaminationEar, nose, throat examination
hearing-assessmentHearing AssessmentAudiometry and hearing evaluation

Orthopedic

Tab IDNamePurpose
orthopedic-examinationOrthopedic ExamJoint-by-joint musculoskeletal examination
orthopedic-proceduresProceduresOrthopedic procedures

Gynecology

Tab IDNamePurpose
gynec-examinationGynec ExamGynecological examination findings
obstetric-assessmentObstetric AssessmentPregnancy and obstetric assessment

Pediatric

Tab IDNamePurpose
pediatric-examinationPediatric ExamPediatric milestones and development
immunizationImmunizationVaccination record
growth-assessmentGrowth AssessmentGrowth percentiles and tracking

Pulmonology

Tab IDNamePurpose
pulmonary-examinationPulmonary ExamRespiratory examination findings
respiratory-assessmentRespiratory AssessmentPFT results and respiratory evaluation

5. Forms & Data Entry

Form Data Model

Each tab manages a specific domain object stored in the single clinicalData local state:

Domain ObjectStored AsDefault Value
VitalsclinicalData.vitalsEmpty array
ObservationsclinicalData.observationsEmpty array
Chief Complaint DurationclinicalData.chiefComplaintDurationUndefined
MedicationsclinicalData.medicationsEmpty array
InvestigationsclinicalData.investigationsEmpty array
AdviceclinicalData.adviceEmpty array
Follow-upsclinicalData.followUpsEmpty array
Dental FindingsclinicalData.dentalFindingsEmpty array
Dental TreatmentsclinicalData.dentalTreatmentsEmpty array
Session NoteclinicalData.sessionNoteEmpty object {}
AssessmentsclinicalData.assessmentsEmpty array
Treatment GoalsclinicalData.treatmentGoalsEmpty array
Homework AssignmentsclinicalData.homeworkAssignmentsEmpty array
Physio AssessmentclinicalData.physioAssessmentStructured default with 4 empty arrays
ExercisesclinicalData.exercisesEmpty array
Progress EntriesclinicalData.progressEntriesEmpty array
Clinical HistoryclinicalData.clinicalHistoryObject with 5 empty arrays
Skin ExaminationclinicalData.skinExaminationObject with empty lesions array
Derm ProceduresclinicalData.dermProceduresEmpty array
Photo DocumentationclinicalData.photoDocumentationObject with empty photos array
Skin AssessmentclinicalData.skinAssessmentObject with empty skinConcerns array
Cosmetic ProceduresclinicalData.cosmeticProceduresEmpty array
Consent ManagementclinicalData.consentManagementObject with empty records array
Cardiac ExaminationclinicalData.cardiacExaminationStructured default
ECG InterpretationclinicalData.ecgInterpretationStructured default
Cardiac Risk AssessmentclinicalData.cardiacRiskAssessmentObject with empty riskFactors array
Cardiac ProceduresclinicalData.cardiacProceduresEmpty array
Surgical ProceduresclinicalData.surgicalProceduresEmpty array
Post-Op AssessmentclinicalData.postOpAssessmentStructured default
Nutritional AssessmentclinicalData.nutritionalAssessmentObject with empty dietaryRestrictions array
Meal PlanclinicalData.mealPlanObject with empty meals array
Nutrition GoalsclinicalData.nutritionGoalsEmpty array
Dietitian ProgressclinicalData.dietitianProgressEntriesEmpty array
Eye ExaminationclinicalData.eyeExaminationEmpty object {}
Vision AssessmentclinicalData.visionAssessmentEmpty object {}
Eye ProceduresclinicalData.eyeProceduresEmpty array
ENT ExaminationclinicalData.entExaminationEmpty object {}
Hearing AssessmentclinicalData.hearingAssessmentEmpty object {}
ENT ProceduresclinicalData.entProceduresEmpty array
Orthopedic ExaminationclinicalData.orthopedicExaminationObject with empty joints array
Orthopedic ProceduresclinicalData.orthopedicProceduresEmpty array
Gynec ExaminationclinicalData.gynecExaminationEmpty object {}
Obstetric AssessmentclinicalData.obstetricAssessmentEmpty object {}
Gynec ProceduresclinicalData.gynecProceduresEmpty array
Pediatric ExaminationclinicalData.pediatricExaminationObject with empty milestones array
Growth AssessmentclinicalData.growthAssessmentObject with empty currentPercentiles
Immunization RecordclinicalData.immunizationRecordObject with empty vaccinations array
Pediatric ProceduresclinicalData.pediatricProceduresEmpty array
Pulmonary ExaminationclinicalData.pulmonaryExaminationEmpty object {}
Respiratory AssessmentclinicalData.respiratoryAssessmentEmpty object {}
Pulmonary ProceduresclinicalData.pulmonaryProceduresEmpty array

Form Initialization

Each tab receives its data from local state and renders a form. When the user makes changes:

  1. The tab calls its onChange handler (e.g., onVitalsChange, onMedicationsChange)
  2. The handler updates the corresponding field in clinicalData
  3. The handler sets hasUnsavedChanges = true

Form Validation

No explicit validation is performed by the EncounterPage itself. Validation rules are implemented within individual tab components. The page trusts the tab-level components to handle field-level validation.

Form Reset Behavior

There is no form reset functionality on the page. The data persists in local state until:

  • The user navigates away (data is lost if not saved)
  • The page is reloaded (data re-seeds from API)

6. APIs & Data Flow

All API Calls Made by This Page

CallTriggerPurpose
useClinicalEncounter(encounterId)Page loadFetch encounter metadata (status, times, practitioner, facility)
useClinicalPatient(patientId)Page loadFetch patient demographics and medical info
useEncounterTypeConfig(practitionerType)Page loadFetch tab visibility rules
useEncounterClinicalData(...)Page load (conditional)Fetches all clinical data for active tabs
saveBatch(clinicalData, tabIds)Manual save actionPersists all clinical data to the API
completeEncounter()Finalize actionTransitions encounter status to completed
refetchEncounter()After finalizeRe-fetch encounter to show updated status

Clinical Data Fetching Strategy

The system uses a conditional query strategy:

  1. On page load, the list of active tab IDs is derived from encounterTypeConfig
  2. useEncounterClinicalData fires queries for all clinical data domains
  3. Only tabs that are active receive their data; inactive tabs are not queried
  4. Queries run in parallel when possible

Batch Save Flow

When the user clicks Save:

1. User clicks "Save" button
2. Button shows loading spinner (isSaving = true)
3. System identifies which tabs are active for the current practitioner type
4. All clinical data is sent to the batch save endpoint via saveBatch()
5. Batch endpoint persists vitals, observations, medications, investigations, etc.
6. If investigations were saved, the response contains server-assigned UUIDs
7. Local investigation state is updated with these UUIDs (prevents stale deletes)
8. hasUnsavedChanges = false
9. Success alert shown for 3 seconds
10. If error: error alert shown for 5 seconds

Encounter Finalization Flow

When the user clicks "Finalize":

1. User clicks "Finalize" button
2. Button shows loading spinner (isCompleting = true)
3. completeEncounter() is called
4. Encounter status transitions to "completed" on the server
5. Encounter is re-fetched to show updated status
6. Success alert shown for 3 seconds
7. Page remains viewable but in read-only mode
8. If error: error alert shown for 5 seconds

Status Mapping

The clinical API uses different status strings than the internal model:

Clinical API StatusInternal EncounterStatus
in_progressin-progress
finishedcompleted
cancelledcancelled
entered_in_errorcancelled
(anything else)draft

Error Handling

  • Encounter not found: Shows error state with "Encounter Not Found" message and "Back to Dashboard" button
  • Patient not found: Same error state
  • Save failure: Error alert with "Failed to save clinical data", auto-dismisses after 5 seconds
  • Finalize failure: Error alert with "Failed to complete encounter", auto-dismisses after 5 seconds

Loading States

  • While any of (encounter loading, patient loading, clinical data loading): Full-page skeleton shown
  • During save: Save button shows spinner, page remains interactive
  • During finalize: Finalize button shows spinner, page remains interactive

7. User Workflow

Complete End-to-End Journey

┌─────────────────────────────────────────────────────────────┐
│ STEP 1: Page Load                                           │
├─────────────────────────────────────────────────────────────┤
│ User arrives from dashboard/queue with encounterId           │
│                                                             │
│ System parses URL → encounterId, patientId, appointmentId    │
│ System determines practitioner type (URL → encounter → default)
│ System fetches: encounter metadata, patient details,         │
│   encounter type config, all clinical data                   │
│                                                             │
│ Loading state: Full-page skeleton                           │
│                                                             │
│ Data arrives → Clinical data seeded into local state        │
│ Default tab set from encounter type config                  │
│ Page becomes interactive                                     │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ STEP 2: Review Patient Context                              │
├─────────────────────────────────────────────────────────────┤
│ Patient header shows: name, age, gender, phone              │
│ Medical context: allergies (highlighted), diseases           │
│ Encounter context: status badge, start time, practitioner    │
│                                                             │
│ User can toggle AI Scribe sidebar (if appointmentId exists) │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ STEP 3: Navigate Tabs & Fill Clinical Data                  │
├─────────────────────────────────────────────────────────────┤
│ User clicks through tabs (Vitals → Observations → etc.)    │
│ Each tab pre-populated with seeded data                     │
│                                                             │
│ On every change:                                            │
│   → Local state updated                                     │
│   → hasUnsavedChanges = true                                │
│   → Unsaved indicator appears below tabs                    │
│                                                             │
│ Tab-specific behavior:                                       │
│   • Vitals: Age/gender-aware reference ranges               │
│   • Observations: AI suggestions integration                │
│   • Medications: Allergy alerts, drug interaction checking  │
│   • Investigations: Lab/imaging/referral categorization    │
│   • Dental: Tooth chart selection, ADA codes                │
│   • Clinical History: Psychiatric history structured input   │
│   • Physical exams: Structured findings by system          │
│   • Procedures: Procedure codes, dates, notes               │
│   • Photo Documentation: File upload                        │
│   • Consent: Procedure consent tracking                    │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ STEP 4: AI Scribe (Optional)                                │
├─────────────────────────────────────────────────────────────┤
│ If appointmentId exists and AI Scribe is enabled:           │
│   User opens AI Scribe sidebar                              │
│   Live transcription of consultation                        │
│   AI generates suggestions:                                  │
│     • Medication suggestions → click to add to Medications tab
│     • Test suggestions → click to add to Investigations tab │
│     • Diagnosis suggestions → appended to Observations tab   │
│                                                             │
│ Accept: Applies suggestion, marks as accepted                │
│ Reject: Dismisses suggestion, marks as rejected              │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ STEP 5: Save Clinical Data                                  │
├─────────────────────────────────────────────────────────────┤
│ User clicks "Save" button                                   │
│                                                             │
│ Save button: Always visible, enabled even with no changes    │
│ Spinner shown while saving                                  │
│                                                             │
│ Success: Green alert "Encounter saved successfully"         │
│          hasUnsavedChanges = false                          │
│                                                             │
│ Error: Red alert "Failed to save clinical data"             │
│        Data remains in local state, user can retry          │
│                                                             │
│ Note: User should save before navigating away —            │
│       unsaved local state is lost on navigation             │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ STEP 6: Finalize Encounter (Optional)                       │
├─────────────────────────────────────────────────────────────┤
│ User clicks "Finalize" button                               │
│                                                             │
│ Only shown for practitioners with appropriate permissions    │
│                                                             │
│ On finalize:                                                │
│   → Encounter status → "completed" on server                 │
│   → Encounter re-fetched to sync status                      │
│   → "Finalized" badge shown in header                      │
│   → Further edits may be restricted depending on settings   │
│                                                             │
│ Note: Finalization is a one-way transition (to some states) │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ STEP 7: Navigate Away                                       │
├─────────────────────────────────────────────────────────────┤
│ User clicks "Cancel" or browser back                        │
│                                                             │
│ If hasUnsavedChanges = true:                                │
│   → No confirmation dialog is shown                         │
│   → Data is simply lost                                     │
│   → No warning to user                                      │
│                                                             │
│ If hasUnsavedChanges = false:                               │
│   → Clean navigation to dashboard                           │
└─────────────────────────────────────────────────────────────┘

8. Unsaved Changes Handling

Tracking Unsaved Changes

Every change handler (onVitalsChange, onMedicationsChange, etc.) sets hasUnsavedChanges = true. This flag drives:

  • Visual indicator: Amber badge below tabs showing "Unsaved changes" with pulsing dot
  • Save prompt: None — no browser beforeunload dialog is shown

Key Behavioral Note

There is NO confirmation dialog when navigating away with unsaved changes. The user receives no browser prompt, no modal, no warning. The unsaved data is silently discarded. This is a known gap — the system only shows the visual indicator.


9. AI Scribe Integration

How AI Scribe Works

  1. Sidebar visibility: Controlled by isAIScribeOpen state and settings.showAIScribe
  2. Appointment link: Requires valid appointmentId in URL — without it, sidebar won't render
  3. Live transcription: AIScribeSidebar receives onTranscriptionUpdate callbacks as audio is processed
  4. Suggestion acceptance: When user accepts a suggestion:
    • Medication: New medication added to clinicalData.medications with auto-generated ID
    • Test: Console log (investigation not yet implemented)
    • Diagnosis: Appended to observations with code "Assessment"

10. Role-Based & Conditional Behavior Summary

ConditionBehavior
No appointmentIdAI Scribe sidebar not rendered
settings.showAIScribe = falseAI Scribe sidebar not rendered
encounterTypeConfig.has_prescriptions = falseMedications tab hidden
encounterTypeConfig.has_investigations = falseInvestigations tab hidden
encounter.status = completed"Finalized" badge shown
hasUnsavedChanges = trueAmber unsaved indicator shown
No patient found (API fails)Error page with back button
No encounter found (API fails)Error page with back button
Practitioner type = "doctor" (default)Standard 5 tabs + full specialty set
Other practitioner typesTab set filtered by encounter type config

11. Data Architecture Summary

State Hierarchy

URL Parameters
├── encounterId (required)
├── patientId (optional — fallback to encounter.patient_id)
├── appointmentId (optional — enables AI Scribe)
└── practitionerType (optional — fallback to encounter.practitioner_type → "doctor")

External API Data (fetched on load)
├── Encounter metadata (status, times, practitioner, facility)
├── Patient demographics (name, age, gender, allergies, conditions)
├── Encounter Type Config (tab visibility, defaults)
└── Clinical Data (all domain objects)

Local State (clinicalData)
├── 45+ domain objects managed via individual onChange handlers
├── Seeded once from API, then mutated locally
└── Persisted via batch save

UI State
├── activeTab: which tab is currently shown
├── hasUnsavedChanges: tracks if local edits need saving
├── saveSuccess / saveError: save feedback
├── isCompleting: finalize loading state
├── isAIScribeOpen: sidebar visibility
└── aiSuggestions: list of AI-generated suggestions

Key Data Flow Principle

The EncounterPage follows an Optimistic Local Edit pattern:

  • All edits are applied immediately to local state
  • Local state is the source of truth for rendering
  • A single batch save persists everything to the server
  • After save, server-assigned UUIDs are reconciled back into local state

Document version: 1.0 — Based on EncounterPage.tsx as of 2026-03-24

On this page