Kulcare Docs
DeveloperCare plansCare planFuture state

Multi-Practitioner Documents Implementation Plan

Status: Planned Priority: High Dependencies: Episode of Care (recommended but not required)

Overview

Replace the doctor-centric Prescription model with a flexible EncounterDocument system that supports practitioner-specific documentation types.

Problem Statement

Current Limitation: All encounters use the Prescription model, which is designed for doctor workflows.

Impact:

  • Therapists/Psychologists cannot document: session notes, mood assessments, safety plans
  • Physiotherapists cannot document: ROM measurements, exercise prescriptions, functional assessments
  • Dietitians cannot document: meal plans, nutritional assessments, dietary recommendations
  • Only one document type per encounter (1:1 mapping)

Solution: EncounterDocument Model

Key Architecture Decisions

  1. Flexible 1:N documents per encounter - Not rigid 1:1 mapping
  2. Document types defined in data - Registry-driven, not code-driven
  3. Document lifecycle - draft -> signed -> locked -> amended
  4. Granular visibility - Per-document visibility controls
  5. Multi-practitioner support - EncounterParticipant for co-managed encounters

Data Model

EncounterDocument Table

create_table :encounter_documents do |t|
  t.references :encounter, null: false, foreign_key: true
  t.references :author, null: false, foreign_key: { to_table: :users }
  t.references :cosigner, foreign_key: { to_table: :users }

  # Document type (from registry)
  t.string :doc_type, null: false
  # Options: prescription | therapy_session | physio_session | nutrition_consultation

  # Lifecycle
  t.string :status, default: 'draft'
  # Options: draft | pending_cosign | signed | locked | amended

  # Content storage
  t.jsonb :data, default: {}          # Structured document content
  t.text :narrative_summary           # AI-generated or manual summary

  # Visibility
  t.string :visibility, default: 'care_team'
  # Options: private | care_team | patient_visible

  # Signing
  t.datetime :signed_at
  t.string :signature_type            # manual | digital
  t.datetime :locked_at

  # Amendment tracking
  t.references :parent_document, foreign_key: { to_table: :encounter_documents }
  t.text :amendment_reason

  t.timestamps
end

add_index :encounter_documents, [:encounter_id, :doc_type]
add_index :encounter_documents, :status

EncounterParticipant Table

create_table :encounter_participants do |t|
  t.references :encounter, null: false, foreign_key: true
  t.references :user, null: false, foreign_key: true  # The practitioner

  t.string :role       # primary | consultant | supervisor | assistant
  t.string :status     # invited | accepted | declined | completed
  t.datetime :joined_at
  t.datetime :left_at
  t.text :notes

  t.timestamps
end

add_index :encounter_participants, [:encounter_id, :user_id], unique: true

Model Associations

class Encounter < ApplicationRecord
  has_many :encounter_documents, dependent: :destroy
  has_many :encounter_participants, dependent: :destroy
  has_many :participants, through: :encounter_participants, source: :user

  # Existing associations
  belongs_to :patient
  belongs_to :clinic
  belongs_to :episode_of_care, optional: true
  belongs_to :practitioner, polymorphic: true  # Primary practitioner
end

class EncounterDocument < ApplicationRecord
  belongs_to :encounter
  belongs_to :author, class_name: 'User'
  belongs_to :cosigner, class_name: 'User', optional: true
  belongs_to :parent_document, class_name: 'EncounterDocument', optional: true

  has_many :amendments, class_name: 'EncounterDocument', foreign_key: :parent_document_id

  enum status: {
    draft: 'draft',
    pending_cosign: 'pending_cosign',
    signed: 'signed',
    locked: 'locked',
    amended: 'amended'
  }

  validates :doc_type, presence: true,
    inclusion: { in: DocumentTypeRegistry.valid_types }
end

Document Type Registry

Document types are defined as data, not hardcoded:

# Backend document type registry
class DocumentTypeRegistry
  TYPES = {
    'prescription' => {
      practitioner_types: ['doctor'],
      schema: PrescriptionSchema,
      requires_cosign: false,
      pdf_template: 'prescription'
    },
    'therapy_session' => {
      practitioner_types: ['therapist', 'psychologist'],
      schema: TherapySessionSchema,
      requires_cosign: false,
      pdf_template: 'therapy_session',
      safety_alerts: true
    },
    'physio_session' => {
      practitioner_types: ['physiotherapist'],
      schema: PhysioSessionSchema,
      requires_cosign: false,
      pdf_template: 'physio_session'
    },
    'nutrition_consultation' => {
      practitioner_types: ['dietitian'],
      schema: NutritionConsultationSchema,
      requires_cosign: false,
      pdf_template: 'nutrition_consultation'
    }
  }.freeze

  def self.valid_types
    TYPES.keys
  end

  def self.types_for_practitioner(practitioner_type)
    TYPES.select { |_, v| v[:practitioner_types].include?(practitioner_type) }.keys
  end
end

Document Type Schemas

Therapy Session Schema

TherapySessionSchema = {
  session_number: :integer,
  session_type: [:individual, :group, :family, :couples],
  mood_assessment: {
    patient_reported_mood: :integer,  # 1-10
    clinician_observed: :string,
    mood_trend: [:improving, :stable, :declining]
  },
  session_notes: :text,
  interventions_used: [:cbt, :dbt, :emdr, :mindfulness, :other],
  homework_assigned: :text,
  homework_completed: :boolean,
  risk_assessment: {
    suicidal_ideation: [:none, :passive, :active_no_plan, :active_with_plan],
    self_harm: [:none, :urges, :recent, :current],
    safety_plan_reviewed: :boolean
  },
  next_session_focus: :text
}

Physio Session Schema

PhysioSessionSchema = {
  session_number: :integer,
  subjective: :text,  # Patient-reported progress/concerns
  objective: {
    pain_level: :integer,  # 0-10
    rom_measurements: [
      { joint: :string, movement: :string, degrees: :integer }
    ],
    strength_tests: [
      { muscle_group: :string, grade: :integer }  # 0-5 scale
    ],
    functional_tests: :text
  },
  assessment: :text,
  plan: {
    exercises_prescribed: [
      { name: :string, sets: :integer, reps: :integer, notes: :string }
    ],
    modalities_used: [:ultrasound, :tens, :heat, :ice, :manual_therapy],
    home_program: :text,
    precautions: :text
  },
  progress_toward_goals: :text
}

Nutrition Consultation Schema

NutritionConsultationSchema = {
  consultation_type: [:initial, :follow_up, :review],
  anthropometrics: {
    weight: :decimal,
    bmi: :decimal,
    waist_circumference: :decimal,
    body_fat_percentage: :decimal
  },
  dietary_assessment: {
    current_intake: :text,
    food_preferences: :text,
    allergies_intolerances: [:string],
    supplements: [:string]
  },
  nutritional_diagnosis: :text,
  meal_plan: {
    calories: :integer,
    macros: { protein: :integer, carbs: :integer, fat: :integer },
    meal_timing: :text,
    specific_recommendations: :text
  },
  goals: [:string],
  follow_up_plan: :text
}

API Endpoints

# Encounter documents
GET    /api/v1/encounters/:encounter_id/documents
POST   /api/v1/encounters/:encounter_id/documents
GET    /api/v1/encounter_documents/:id
PATCH  /api/v1/encounter_documents/:id
DELETE /api/v1/encounter_documents/:id

# Document lifecycle
POST   /api/v1/encounter_documents/:id/sign
POST   /api/v1/encounter_documents/:id/lock
POST   /api/v1/encounter_documents/:id/amend

# Encounter participants
GET    /api/v1/encounters/:encounter_id/participants
POST   /api/v1/encounters/:encounter_id/participants
PATCH  /api/v1/encounter_participants/:id
DELETE /api/v1/encounter_participants/:id

# Document type registry
GET    /api/v1/document_types
GET    /api/v1/document_types/:type/schema

Frontend Components

New Components

ComponentPurpose
DocumentTypeSelectorChoose document type based on practitioner
TherapySessionFormTherapy-specific documentation form
PhysioSessionFormPhysio-specific documentation form
NutritionConsultationFormNutrition-specific documentation form
DocumentTimelineAll documents for an encounter
ParticipantManagerManage encounter participants
SafetyAlertBannerRisk alerts for therapy sessions

Updated Components

ComponentUpdate
EncounterPageRender practitioner-specific forms
EncounterHeaderShow document count, participant count
PrescriptionPageBecomes one document type among many

Implementation Steps

Phase 1: Database & Models

  1. Create EncounterDocument migration
  2. Create EncounterParticipant migration
  3. Create DocumentTypeRegistry
  4. Create document type schemas

Phase 2: Backend API

  1. Create EncounterDocumentsController
  2. Create EncounterParticipantsController
  3. Implement document lifecycle methods
  4. Create serializers for document types

Phase 3: Frontend - Therapy

  1. Build TherapySessionForm component
  2. Implement safety alert system
  3. Add therapy-specific PDF generation

Phase 4: Frontend - Physio & Nutrition

  1. Build PhysioSessionForm component
  2. Build NutritionConsultationForm component
  3. Add respective PDF templates

Phase 5: Migration

  1. Create migration script for existing Prescriptions
  2. Validate data integrity
  3. Update reporting queries

Backward Compatibility

  • Existing Prescription data: Migrated to EncounterDocument with doc_type='prescription'
  • Doctor workflow: Unchanged - Prescription becomes default document type for doctors
  • API compatibility: Existing prescription APIs continue to work
  • PDF generation: Existing prescription PDFs unchanged

On this page