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
- Flexible 1:N documents per encounter - Not rigid 1:1 mapping
- Document types defined in data - Registry-driven, not code-driven
- Document lifecycle - draft -> signed -> locked -> amended
- Granular visibility - Per-document visibility controls
- 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, :statusEncounterParticipant 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: trueModel 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 }
endDocument 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
endDocument 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/schemaFrontend Components
New Components
| Component | Purpose |
|---|---|
DocumentTypeSelector | Choose document type based on practitioner |
TherapySessionForm | Therapy-specific documentation form |
PhysioSessionForm | Physio-specific documentation form |
NutritionConsultationForm | Nutrition-specific documentation form |
DocumentTimeline | All documents for an encounter |
ParticipantManager | Manage encounter participants |
SafetyAlertBanner | Risk alerts for therapy sessions |
Updated Components
| Component | Update |
|---|---|
EncounterPage | Render practitioner-specific forms |
EncounterHeader | Show document count, participant count |
PrescriptionPage | Becomes one document type among many |
Implementation Steps
Phase 1: Database & Models
- Create EncounterDocument migration
- Create EncounterParticipant migration
- Create DocumentTypeRegistry
- Create document type schemas
Phase 2: Backend API
- Create EncounterDocumentsController
- Create EncounterParticipantsController
- Implement document lifecycle methods
- Create serializers for document types
Phase 3: Frontend - Therapy
- Build TherapySessionForm component
- Implement safety alert system
- Add therapy-specific PDF generation
Phase 4: Frontend - Physio & Nutrition
- Build PhysioSessionForm component
- Build NutritionConsultationForm component
- Add respective PDF templates
Phase 5: Migration
- Create migration script for existing Prescriptions
- Validate data integrity
- 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