Kulcare Docs
DeveloperCare plansCare planMigration

LHR APIs Implementation Plan v2 - Based on Codebase Analysis

Executive Summary

After deep-diving into the existing codebase, I've identified the current data architecture and patterns. The LHR system is clinic-centric with proper separation between patient-level and clinic-level data. Most CRUD functionality already exists but needs to be exposed through new API endpoints.

Key Findings from Codebase Analysis

Data Architecture

  1. PatientProblem: ✅ FULLY IMPLEMENTED - Clinic-scoped with all required fields
  2. PatientMedicine: ✅ EXISTS - Patient-scoped but linked to prescriptions
  3. MedicationRequest: ✅ FHIR-COMPLIANT - Clinic-scoped with workflow states
  4. ClinicalImpression: ✅ FHIR-COMPLIANT - Clinic-scoped clinical assessments
  5. ServiceRequest: ✅ FHIR-COMPLIANT - Lab/test orders

Authorization Pattern

  • Clinic-based authorization: All LHR operations are clinic-scoped
  • Existing pattern: Clinic interactors call patient interactors with clinic context
  • Policy-based: Uses Pundit for authorization (authorize(patient, :show?))

Current LHR API Status

  • ✅ Read APIs: All 5 LHR endpoints implemented and working
  • ❌ Write APIs: Missing CRUD operations for interactive functionality

Required APIs (NEW)

Based on frontend placeholder actions, we need these NEW endpoints:

Phase 1: Patient Problems CRUD (HIGH PRIORITY)

1.1 Create Patient Problem ⭐ NEEDS IMPLEMENTATION

Endpoint: POST /clinics/:clinic_id/patients/:clinic_patient_id/problems Backend: Patient Problems table is ready - all fields exist

1.2 Update Patient Problem ⭐ NEEDS IMPLEMENTATION

Endpoint: PUT /clinics/:clinic_id/patients/:clinic_patient_id/problems/:problem_id

1.3 Resolve Patient Problem ⭐ NEEDS IMPLEMENTATION

Endpoint: PUT /clinics/:clinic_id/patients/:clinic_patient_id/problems/:problem_id/resolve

1.4 Delete Patient Problem ⭐ NEEDS IMPLEMENTATION

Endpoint: DELETE /clinics/:clinic_id/patients/:clinic_patient_id/problems/:problem_id

Phase 2: Medication Management (MEDIUM PRIORITY)

2.1 Get Medication Details ⭐ NEEDS IMPLEMENTATION

Endpoint: GET /clinics/:clinic_id/patients/:clinic_patient_id/medications/:medication_id

2.2 Update Medication Status ⭐ NEEDS IMPLEMENTATION

Endpoint: PUT /clinics/:clinic_id/patients/:clinic_patient_id/medications/:medication_id/status

Phase 3: Encounter Management (LOW PRIORITY)

3.1 Get Encounter Details ⭐ NEEDS IMPLEMENTATION

Endpoint: GET /clinics/:clinic_id/patients/:clinic_patient_id/encounters/:encounter_id

3.2 Schedule Follow-up ⭐ INTEGRATION NEEDED

Endpoint: POST /clinics/:clinic_id/patients/:clinic_patient_id/encounters/:encounter_id/follow_up

Database Schema Status

✅ PatientProblem (READY FOR CRUD)

-- ALL FIELDS ALREADY EXIST
clinic_id                 -- ✅ Clinic scoping
patient_id               -- ✅ Links to patient  
doctor_id                -- ✅ Who identified problem
problem_code             -- ✅ ICD codes
problem_name             -- ✅ Problem description
problem_category         -- ✅ Categorization
severity                 -- ✅ low/medium/high/critical
onset_date              -- ✅ When problem started
resolved_date           -- ✅ When resolved
status                  -- ✅ active/resolved/chronic/inactive
first_appointment_id    -- ✅ Links to encounters
last_appointment_id     -- ✅ Links to encounters  
source_prescription_id  -- ✅ Links to source
clinical_notes          -- ✅ Free text notes
metadata (jsonb)        -- ✅ For linked_medications
deleted                 -- ✅ Soft delete

✅ PatientMedicine (READY FOR STATUS UPDATES)

-- Core fields exist, may need status enhancement
patient_id              -- ✅ Patient scoped
prescription_id         -- ✅ Links to prescription
medicine_name           -- ✅ Drug name
active                  -- ✅ Current status (boolean)
start_date              -- ✅ When started
end_date                -- ✅ When ended/stopping
notes                   -- ✅ Clinical notes
-- MAY NEED: status enum, discontinuation_reason

✅ MedicationRequest (FHIR-READY)

-- FHIR-compliant medication ordering
clinic_id, patient_id, doctor_id  -- ✅ Proper scoping
medication_name                   -- ✅ Drug name
status                           -- ✅ active/on-hold/cancelled/completed
intent                           -- ✅ proposal/plan/order
prescription_id                  -- ✅ Links to prescription
appointment_id                   -- ✅ Links to encounter

Implementation Strategy

Phase 1: Problems CRUD (2-3 days)

1.1 Problems Controller Extensions

# Add to ClinicPatientsLhrController
def create_problem
  result = ClinicPatients::CreatePatientProblem.call(
    clinic_id: params[:clinic_id],
    clinic_patient_id: params[:clinic_patient_id],
    **problem_params,
    current_user: current_user
  )
  render_success_or_error(result, :problem)
end

def update_problem
  result = ClinicPatients::UpdatePatientProblem.call(
    clinic_id: params[:clinic_id],
    clinic_patient_id: params[:clinic_patient_id],
    problem_id: params[:problem_id],
    **problem_params,
    current_user: current_user
  )
  render_success_or_error(result, :problem)
end

def resolve_problem
  result = ClinicPatients::ResolvePatientProblem.call(
    clinic_id: params[:clinic_id],
    clinic_patient_id: params[:clinic_patient_id], 
    problem_id: params[:problem_id],
    resolution_notes: params[:resolution_notes],
    resolution_date: params[:resolution_date] || Date.current,
    current_user: current_user
  )
  render_success_or_error(result, :problem)
end

def delete_problem
  result = ClinicPatients::DeletePatientProblem.call(
    clinic_id: params[:clinic_id],
    clinic_patient_id: params[:clinic_patient_id],
    problem_id: params[:problem_id],
    current_user: current_user
  )
  render_success_or_error(result)
end

private

def problem_params
  params.permit(:problem_name, :problem_code, :problem_category, 
                :severity, :status, :onset_date, :clinical_notes)
end

def render_success_or_error(result, data_key = nil)
  if result.success?
    response_data = { success: true }
    response_data[data_key] = result.send(data_key) if data_key
    render json: response_data
  else
    render_result(result)
  end
end

1.2 Clinic-side Interactors (follow existing pattern)

# parts/clinic/interactors/clinic_patients/create_patient_problem.rb
module ClinicPatients
  class CreatePatientProblem < BaseInteractor
    def call
      validate_clinic_patient
      call_patient_problems_interactor
    end

    private

    def validate_clinic_patient
      context.clinic_patient = ClinicPatient.find_by!(
        id: context.clinic_patient_id,
        clinic_id: context.clinic_id
      )
      context.patient_id = context.clinic_patient.patient_id
    end

    def call_patient_problems_interactor
      # Call patient-side interactor
      result = PatientProblems::Create.call(
        patient_id: context.patient_id,
        clinic_id: context.clinic_id,
        doctor_id: current_user_doctor_id,
        problem_name: context.problem_name,
        problem_code: context.problem_code,
        problem_category: context.problem_category,
        severity: context.severity || 'medium',
        status: context.status || 'active',
        onset_date: context.onset_date || Date.current,
        clinical_notes: context.clinical_notes,
        current_user: context.current_user
      )

      if result.success?
        context.problem = result.problem
      else
        context.fail!(message: result.message)
      end
    end

    def current_user_doctor_id
      # Get doctor ID from current user context
      context.current_user.clinic_doctors
        .find_by(clinic_id: context.clinic_id)&.doctor_id ||
      context.current_user.doctor&.id ||
      context.current_user.id
    end
  end
end

1.3 Patient-side Interactors (NEW)

# parts/patient/interactors/patient_problems/create.rb
class PatientProblems::Create < BaseInteractor
  def call
    validate_access
    create_patient_problem
    format_response
  end

  private

  def validate_access
    context.patient = Patient.find(context.patient_id)
    authorize(context.patient, :show?)
  end

  def create_patient_problem
    context.patient_problem = PatientProblem.create!(
      clinic_id: context.clinic_id,
      patient_id: context.patient_id,
      doctor_id: context.doctor_id,
      problem_name: context.problem_name,
      problem_code: context.problem_code,
      problem_category: context.problem_category || 'General',
      severity: context.severity,
      status: context.status,
      onset_date: context.onset_date,
      clinical_notes: context.clinical_notes
    )
  end

  def format_response
    context.problem = {
      id: context.patient_problem.id.to_s,
      name: context.patient_problem.problem_name,
      icd_code: context.patient_problem.problem_code,
      category: context.patient_problem.problem_category,
      severity: context.patient_problem.severity,
      status: context.patient_problem.status,
      onset_date: context.patient_problem.onset_date&.strftime('%Y-%m-%d'),
      clinical_notes: context.patient_problem.clinical_notes,
      created_at: context.patient_problem.created_at.iso8601
    }
  end
end

Phase 2: Medication Management (1-2 days)

2.1 Medication Details API

  • Status: PatientMedicine model exists and is sufficient
  • Implementation: Create interactor to fetch and format medication details
  • Enhancement: May need to add status history tracking

2.2 Medication Status Updates

  • Current: active boolean field
  • Enhancement: Consider adding status enum and reason tracking
  • Implementation: Update status + create audit trail

Phase 3: Encounter Details (1 day)

3.1 Encounter Details API

  • Status: Appointment model has all data
  • Implementation: Format appointment data with linked prescriptions/vitals
  • Integration: Use existing appointment authorization

3.2 Follow-up Scheduling

  • Status: Appointment creation already exists
  • Implementation: Create appointment with follow-up context
  • Integration: Link to original encounter

Routes Configuration

# Add to parts/clinic/routes.rb
member do
  # Existing LHR read endpoints
  get :longitudinal_health_record, to: 'clinic_patients_lhr#show'
  get :problems, to: 'clinic_patients_lhr#problems'
  get :medications_timeline, to: 'clinic_patients_lhr#medications_timeline'
  get :clinical_assessments, to: 'clinic_patients_lhr#clinical_assessments'
  get :vitals_trends, to: 'clinic_patients_lhr#vitals_trends'
  
  # NEW: Problem management
  post 'problems', to: 'clinic_patients_lhr#create_problem'
  put 'problems/:problem_id', to: 'clinic_patients_lhr#update_problem'
  put 'problems/:problem_id/resolve', to: 'clinic_patients_lhr#resolve_problem'
  delete 'problems/:problem_id', to: 'clinic_patients_lhr#delete_problem'
  
  # NEW: Medication management  
  get 'medications/:medication_id', to: 'clinic_patients_lhr#show_medication'
  put 'medications/:medication_id/status', to: 'clinic_patients_lhr#update_medication_status'
  
  # NEW: Encounter management
  get 'encounters/:encounter_id', to: 'clinic_patients_lhr#show_encounter'
  post 'encounters/:encounter_id/follow_up', to: 'clinic_patients_lhr#schedule_follow_up'
end

Questions for You

1. Medication Status Management

The PatientMedicine model currently uses a boolean active field. Should we:

  • Option A: Keep boolean and add discontinuation_reason field
  • Option B: Change to status enum (active/discontinued/on-hold/completed) + reason
  • Option C: Create separate MedicationStatusHistory table for audit trail

2. Problem-Medication Linking

How should we store medication links to problems:

  • Option A: Use existing metadata JSONB field in PatientProblem
  • Option B: Create separate problem_medications join table
  • Option C: Add linked_problems JSONB field to PatientMedicine

3. Authorization Scope

Should doctors be able to:

  • Edit problems created by other doctors in the same clinic? (Current: Yes, clinic-scoped)
  • View/edit problems from other clinics for the same patient? (Current: No, clinic-scoped)

4. Implementation Priority

Which phase should I implement first:

  • Phase 1: Problems CRUD (most console.log placeholders)
  • Phase 2: Medication status updates
  • Phase 3: Encounter details

5. Database Migration Needed?

Based on analysis, minimal migrations needed:

  • Add discontinuation_reason to PatientMedicine?
  • Add any missing indexes?
  • Any other schema enhancements?

The existing schema is very comprehensive and most functionality can be implemented immediately. Should I proceed with Phase 1 (Problems CRUD) using the existing schema?

<function_calls> [{"content": "Deep dive into existing models and database schema", "status": "completed", "priority": "high", "id": "147"}, {"content": "Analyze PatientProblem and related models", "status": "completed", "priority": "high", "id": "148"}, {"content": "Check PatientMedicine and medication-related models", "status": "completed", "priority": "high", "id": "149"}, {"content": "Understand authorization patterns in LHR", "status": "completed", "priority": "medium", "id": "150"}, {"content": "Create updated LHR APIs implementation plan with actual schema", "status": "completed", "priority": "high", "id": "151"}]

On this page