Kulcare Docs
DeveloperCare plansCare planBackend

LHR Backend Completion Plan

Goal: Complete all FHIR resource coverage in LHR backend before frontend work

Current State

Existing LHR Endpoints

GET  /clinics/:clinic_id/patients/:id/longitudinal_health_record
GET  /clinics/:clinic_id/patients/:id/problems
POST /clinics/:clinic_id/patients/:id/problems
PUT  /clinics/:clinic_id/patients/:id/problems/:problem_id
PUT  /clinics/:clinic_id/patients/:id/problems/:problem_id/resolve
DEL  /clinics/:clinic_id/patients/:id/problems/:problem_id
PUT  /clinics/:clinic_id/patients/:id/problems/:problem_id/medications
GET  /clinics/:clinic_id/patients/:id/medications_timeline
GET  /clinics/:clinic_id/patients/:id/medications/:medication_id
PUT  /clinics/:clinic_id/patients/:id/medications/:medication_id/status
GET  /clinics/:clinic_id/patients/:id/vitals_trends
GET  /clinics/:clinic_id/patients/:id/clinical_assessments
GET  /clinics/:clinic_id/patients/:id/encounters/:encounter_id
POST /clinics/:clinic_id/patients/:id/encounters/:encounter_id/follow_up
GET  /clinics/:clinic_id/patients/:id/export

Existing Models (Ready to Expose)

ModelFHIR ResourceHas DataNeeds API
AllergyIntoleranceAllergyIntoleranceYesYes
ProcedureProcedureYesYes
ImmunizationImmunizationYesYes
DiagnosticReportDiagnosticReportYesYes
CarePlanCarePlanYesYes
ServiceRequestServiceRequestYesYes

Models Needing Creation

ModelFHIR ResourcePriority
FamilyMemberHistoryFamilyMemberHistoryHigh
GoalGoalHigh
SocialHistoryObservation (social-history)High
ImagingStudyImagingStudyMedium

Phase 1: Expose Existing Models (No Migrations)

Add LHR endpoints for models that already exist.

1.1 Allergies Endpoint

Model: AllergyIntolerance (exists)

New Endpoint:

GET /clinics/:clinic_id/patients/:id/allergies

Implementation:

# backend/parts/clinic/interactors/clinic_patients/get_allergies.rb
class ClinicPatients::GetAllergies
  include Interactor

  def call
    patient = Patient.find(context.patient_id)
    context.allergies = patient.allergy_intolerances
                              .includes(:basic_allergy)
                              .order(created_at: :desc)
  end
end

Controller Addition:

# Add to clinic_patients_lhr_controller.rb
def allergies
  result = ClinicPatients::GetAllergies.call(params)
  render_result(result, result.success? ? AllergySerializer : nil)
end

Route:

get :allergies, to: 'clinic_patients_lhr#allergies'

1.2 Procedures Endpoint

Model: Procedure (exists)

New Endpoint:

GET /clinics/:clinic_id/patients/:id/procedures

Implementation:

# backend/parts/clinic/interactors/clinic_patients/get_procedures.rb
class ClinicPatients::GetProcedures
  include Interactor

  def call
    patient = Patient.find(context.patient_id)
    context.procedures = patient.procedures
                               .includes(:doctor, :clinic)
                               .order(performed_date: :desc)
  end
end

1.3 Immunizations Endpoint

Model: Immunization (exists)

New Endpoint:

GET /clinics/:clinic_id/patients/:id/immunizations

Implementation:

# backend/parts/clinic/interactors/clinic_patients/get_immunizations.rb
class ClinicPatients::GetImmunizations
  include Interactor

  def call
    patient = Patient.find(context.patient_id)
    context.immunizations = patient.immunizations
                                  .order(administered_date: :desc)
  end
end

1.4 Lab Results Endpoint

Model: DiagnosticReport (exists)

New Endpoints:

GET /clinics/:clinic_id/patients/:id/lab_results
GET /clinics/:clinic_id/patients/:id/lab_results/:id
GET /clinics/:clinic_id/patients/:id/lab_results/trends?test_code=HBA1C

Implementation:

# backend/parts/clinic/interactors/clinic_patients/get_lab_results.rb
class ClinicPatients::GetLabResults
  include Interactor

  def call
    patient = Patient.find(context.patient_id)

    results = patient.diagnostic_reports
                    .includes(:doctor, :appointment)
                    .order(report_date: :desc)

    # Filter by test code if provided
    if context.test_code.present?
      results = results.where(test_code: context.test_code)
    end

    # Filter by date range if provided
    if context.start_date.present?
      results = results.where('report_date >= ?', context.start_date)
    end

    context.lab_results = results
  end
end

# backend/parts/clinic/interactors/clinic_patients/get_lab_result_trends.rb
class ClinicPatients::GetLabResultTrends
  include Interactor

  def call
    patient = Patient.find(context.patient_id)
    test_code = context.test_code

    context.trends = patient.diagnostic_reports
                           .where(test_code: test_code)
                           .order(report_date: :asc)
                           .map do |report|
      {
        date: report.report_date,
        value: report.value,
        unit: report.unit,
        reference_range: report.reference_range,
        is_abnormal: report.abnormal?
      }
    end
  end
end

1.5 Care Plans Endpoint

Model: CarePlan (exists)

New Endpoint:

GET /clinics/:clinic_id/patients/:id/care_plans
GET /clinics/:clinic_id/patients/:id/care_plans/:id

Implementation:

# backend/parts/clinic/interactors/clinic_patients/get_care_plans.rb
class ClinicPatients::GetCarePlans
  include Interactor

  def call
    patient = Patient.find(context.patient_id)
    context.care_plans = patient.care_plans
                               .includes(:care_plan_problems, :patient_problems, :doctor)
                               .order(created_at: :desc)
  end
end

1.6 Referrals Endpoint

Model: ServiceRequest (exists)

New Endpoint:

GET /clinics/:clinic_id/patients/:id/referrals

Implementation:

# backend/parts/clinic/interactors/clinic_patients/get_referrals.rb
class ClinicPatients::GetReferrals
  include Interactor

  def call
    patient = Patient.find(context.patient_id)
    context.referrals = patient.service_requests
                              .includes(:doctor, :clinic, :appointment)
                              .order(created_at: :desc)
  end
end

Phase 1 Checklist

  • 1.1 Add GET allergies endpoint
  • 1.2 Add GET procedures endpoint
  • 1.3 Add GET immunizations endpoint
  • 1.4 Add GET lab_results + GET lab_results/trends endpoints
  • 1.5 Add GET care_plans endpoint
  • 1.6 Add GET referrals endpoint
  • Update GetLongitudinalHealthRecord to include these in summary
  • Add serializers for each resource (using LongitudinalHealthRecordSerializer)
  • Add tests for each endpoint

Status: COMPLETED (Dec 26, 2024) Actual Effort: ~1 day


Phase 2: Create New Models

2.1 FamilyMemberHistory Model

FHIR: FamilyMemberHistory

Migration:

# db/migrate/xxx_create_family_member_histories.rb
class CreateFamilyMemberHistories < ActiveRecord::Migration[7.0]
  def change
    create_table :family_member_histories do |t|
      t.references :patient, null: false, foreign_key: true
      t.references :clinic, foreign_key: true
      t.references :recorded_by, foreign_key: { to_table: :users }, optional: true

      # FHIR FamilyMemberHistory fields
      t.string :relationship, null: false  # mother, father, sibling, etc.
      t.string :sex                        # male, female, unknown
      t.boolean :deceased, default: false
      t.integer :age_at_death
      t.string :cause_of_death

      # Condition information
      t.string :condition_name
      t.string :condition_code            # ICD-10 code
      t.string :condition_outcome         # ongoing, resolved, deceased
      t.integer :onset_age                # age when condition started
      t.boolean :contributed_to_death, default: false

      t.text :notes
      t.timestamps
    end

    add_index :family_member_histories, [:patient_id, :relationship]
  end
end

Model:

# backend/parts/patient/models/family_member_history.rb
class FamilyMemberHistory < ApplicationRecord
  belongs_to :patient
  belongs_to :clinic, optional: true
  belongs_to :recorded_by, class_name: 'User', optional: true

  RELATIONSHIPS = %w[
    mother father
    maternal_grandmother maternal_grandfather
    paternal_grandmother paternal_grandfather
    sibling brother sister
    son daughter child
    maternal_aunt maternal_uncle
    paternal_aunt paternal_uncle
    cousin
    other
  ].freeze

  validates :relationship, presence: true, inclusion: { in: RELATIONSHIPS }
  validates :condition_name, presence: true, if: -> { condition_code.present? }

  scope :immediate_family, -> { where(relationship: %w[mother father sibling brother sister]) }
  scope :with_conditions, -> { where.not(condition_name: nil) }
end

Endpoints:

GET    /clinics/:clinic_id/patients/:id/family_history
POST   /clinics/:clinic_id/patients/:id/family_history
PUT    /clinics/:clinic_id/patients/:id/family_history/:history_id
DELETE /clinics/:clinic_id/patients/:id/family_history/:history_id

2.2 Goal Model

FHIR: Goal

Migration:

# db/migrate/xxx_create_goals.rb
class CreateGoals < ActiveRecord::Migration[7.0]
  def change
    create_table :goals do |t|
      t.references :patient, null: false, foreign_key: true
      t.references :clinic, foreign_key: true
      t.references :doctor, foreign_key: { to_table: :users }, optional: true
      t.references :patient_problem, foreign_key: true, optional: true
      t.references :care_plan, foreign_key: true, optional: true

      # FHIR Goal fields
      t.string :lifecycle_status, default: 'active'  # proposed, planned, accepted, active, on-hold, completed, cancelled
      t.string :achievement_status                    # in-progress, improving, worsening, no-change, achieved, not-achieved
      t.string :category                              # clinical, behavioral, functional, dietary
      t.string :priority                              # high, medium, low

      t.text :description, null: false
      t.date :start_date
      t.date :target_date
      t.date :status_date                             # when status was last updated

      # Target/measurement
      t.string :target_measure                        # e.g., "HbA1c", "Weight", "Blood Pressure"
      t.string :target_value                          # e.g., "< 7.0%", "75 kg", "< 130/80"
      t.string :current_value

      t.text :notes
      t.timestamps
    end

    add_index :goals, [:patient_id, :lifecycle_status]
    add_index :goals, [:patient_problem_id]
    add_index :goals, [:care_plan_id]
  end
end

Model:

# backend/parts/patient/models/goal.rb
class Goal < ApplicationRecord
  belongs_to :patient
  belongs_to :clinic, optional: true
  belongs_to :doctor, class_name: 'User', optional: true
  belongs_to :patient_problem, optional: true
  belongs_to :care_plan, optional: true

  LIFECYCLE_STATUSES = %w[proposed planned accepted active on-hold completed cancelled].freeze
  ACHIEVEMENT_STATUSES = %w[in-progress improving worsening no-change achieved not-achieved not-attainable].freeze
  CATEGORIES = %w[clinical behavioral functional dietary safety].freeze
  PRIORITIES = %w[high medium low].freeze

  validates :description, presence: true
  validates :lifecycle_status, inclusion: { in: LIFECYCLE_STATUSES }
  validates :achievement_status, inclusion: { in: ACHIEVEMENT_STATUSES }, allow_nil: true
  validates :category, inclusion: { in: CATEGORIES }, allow_nil: true
  validates :priority, inclusion: { in: PRIORITIES }, allow_nil: true

  scope :active, -> { where(lifecycle_status: 'active') }
  scope :by_problem, ->(problem_id) { where(patient_problem_id: problem_id) }
end

Endpoints:

GET    /clinics/:clinic_id/patients/:id/goals
POST   /clinics/:clinic_id/patients/:id/goals
PUT    /clinics/:clinic_id/patients/:id/goals/:goal_id
PUT    /clinics/:clinic_id/patients/:id/goals/:goal_id/status
DELETE /clinics/:clinic_id/patients/:id/goals/:goal_id

2.3 Social History (Extend Patient or New Model)

Option A: Extend Patient Model (simpler, less history tracking)

# db/migrate/xxx_add_social_history_to_patients.rb
class AddSocialHistoryToPatients < ActiveRecord::Migration[7.0]
  def change
    add_column :patients, :smoking_status, :string    # never, former, current
    add_column :patients, :smoking_packs_per_day, :decimal, precision: 3, scale: 1
    add_column :patients, :smoking_years, :integer
    add_column :patients, :smoking_quit_date, :date

    add_column :patients, :alcohol_use, :string       # none, occasional, moderate, heavy
    add_column :patients, :alcohol_drinks_per_week, :integer

    add_column :patients, :occupation, :string
    add_column :patients, :employer, :string

    add_column :patients, :exercise_frequency, :string  # sedentary, light, moderate, active
    add_column :patients, :diet_type, :string           # regular, vegetarian, vegan, etc.

    add_column :patients, :marital_status, :string
    add_column :patients, :living_situation, :string    # alone, with_family, assisted_living, etc.

    add_column :patients, :social_history_notes, :text
    add_column :patients, :social_history_updated_at, :datetime
  end
end

Option B: SocialHistory Model (better for history tracking)

# db/migrate/xxx_create_social_histories.rb
class CreateSocialHistories < ActiveRecord::Migration[7.0]
  def change
    create_table :social_histories do |t|
      t.references :patient, null: false, foreign_key: true
      t.references :recorded_by, foreign_key: { to_table: :users }, optional: true

      t.string :category, null: false   # smoking, alcohol, occupation, exercise, diet, housing
      t.string :status                  # current status value
      t.string :value                   # additional value (e.g., packs per day)
      t.date :start_date
      t.date :end_date
      t.text :notes

      t.timestamps
    end

    add_index :social_histories, [:patient_id, :category]
  end
end

Recommendation: Use Option B (SocialHistory model) for these reasons:

  • Tracks changes over time (e.g., when patient quit smoking, job changes)
  • Better audit trail for clinical decision-making
  • Aligns with FHIR Observation pattern for social-history category
  • Supports multiple entries per category if needed

Endpoints:

GET  /clinics/:clinic_id/patients/:id/social_history
POST /clinics/:clinic_id/patients/:id/social_history
PUT  /clinics/:clinic_id/patients/:id/social_history/:id
DELETE /clinics/:clinic_id/patients/:id/social_history/:id

Model (Recommended Implementation):

# backend/parts/patient/models/social_history.rb
class SocialHistory < ApplicationRecord
  belongs_to :patient
  belongs_to :recorded_by, class_name: 'User', optional: true

  CATEGORIES = %w[
    smoking alcohol drug_use
    occupation employment
    exercise diet
    housing living_situation
    education marital_status
    sexual_activity stress
  ].freeze

  SMOKING_STATUSES = %w[never former current unknown].freeze
  ALCOHOL_STATUSES = %w[none occasional moderate heavy unknown].freeze
  EXERCISE_LEVELS = %w[sedentary light moderate active].freeze

  validates :category, presence: true, inclusion: { in: CATEGORIES }

  scope :current, -> { where(end_date: nil) }
  scope :by_category, ->(cat) { where(category: cat) }
end

Interactor:

# backend/parts/clinic/interactors/clinic_patients/get_social_history.rb
class ClinicPatients::GetSocialHistory
  include Interactor

  def call
    patient = Patient.find(context.patient_id)

    # Group by category, showing current status for each
    context.social_history = {
      smoking: current_status(patient, 'smoking'),
      alcohol: current_status(patient, 'alcohol'),
      occupation: current_status(patient, 'occupation'),
      exercise: current_status(patient, 'exercise'),
      diet: current_status(patient, 'diet'),
      living_situation: current_status(patient, 'living_situation'),
      history: patient.social_histories.order(created_at: :desc)
    }
  end

  private

  def current_status(patient, category)
    patient.social_histories.by_category(category).current.order(created_at: :desc).first
  end
end

2.4 ImagingStudy Model

FHIR: ImagingStudy

Stores radiology and imaging study references (X-rays, MRIs, CT scans, ultrasounds).

Migration:

# db/migrate/xxx_create_imaging_studies.rb
class CreateImagingStudies < ActiveRecord::Migration[7.0]
  def change
    create_table :imaging_studies do |t|
      t.references :patient, null: false, foreign_key: true
      t.references :clinic, foreign_key: true
      t.references :doctor, foreign_key: { to_table: :users }, optional: true
      t.references :appointment, foreign_key: true, optional: true
      t.references :patient_problem, foreign_key: true, optional: true

      # FHIR ImagingStudy fields
      t.string :status, null: false, default: 'available'  # registered, available, cancelled, entered-in-error
      t.string :modality, null: false                       # CT, MR, US, XR, NM, PT, etc.
      t.string :body_site                                   # anatomical location
      t.string :laterality                                  # left, right, bilateral

      # Study details
      t.string :study_uid                                   # DICOM Study Instance UID
      t.string :accession_number                            # Hospital/clinic tracking number
      t.string :description
      t.datetime :started_at
      t.integer :number_of_series
      t.integer :number_of_instances                        # total images

      # Interpretation
      t.text :findings                                      # radiologist findings
      t.text :impression                                    # clinical impression/conclusion
      t.string :interpreting_radiologist
      t.datetime :interpreted_at

      # File references (if storing images/reports)
      t.string :report_url                                  # link to PDF report
      t.string :images_url                                  # link to DICOM viewer or images

      t.text :notes
      t.timestamps
    end

    add_index :imaging_studies, [:patient_id, :modality]
    add_index :imaging_studies, [:patient_id, :started_at]
    add_index :imaging_studies, :study_uid, unique: true, where: "study_uid IS NOT NULL"
  end
end

Model:

# backend/parts/patient/models/imaging_study.rb
class ImagingStudy < ApplicationRecord
  belongs_to :patient
  belongs_to :clinic, optional: true
  belongs_to :doctor, class_name: 'User', optional: true
  belongs_to :appointment, optional: true
  belongs_to :patient_problem, optional: true

  STATUSES = %w[registered available cancelled entered-in-error].freeze

  # DICOM modality codes
  MODALITIES = %w[
    AR AU BDUS BMD CR CT CTPROTOCOL
    DG DOC DX ECG EPS ES GM HC HD
    IO IVOCT IVUS KER KO LEN LS MG
    MR NM OAM OCT OP OPM OPR OPT
    OPV OSS OT PLAN PR PT PX REG
    RESP RF RG RTDOSE RTIMAGE RTPLAN
    RTRECORD RTSTRUCT RWV SEG SM SMR
    SR SRF TG US VA XA XC
  ].freeze

  # Common modalities for display
  COMMON_MODALITIES = {
    'XR' => 'X-Ray',
    'CT' => 'CT Scan',
    'MR' => 'MRI',
    'US' => 'Ultrasound',
    'NM' => 'Nuclear Medicine',
    'PT' => 'PET Scan',
    'MG' => 'Mammography',
    'DX' => 'Digital Radiography',
    'CR' => 'Computed Radiography',
    'RF' => 'Fluoroscopy'
  }.freeze

  validates :status, presence: true, inclusion: { in: STATUSES }
  validates :modality, presence: true, inclusion: { in: MODALITIES }

  scope :available, -> { where(status: 'available') }
  scope :by_modality, ->(mod) { where(modality: mod) }
  scope :recent, ->(limit = 10) { order(started_at: :desc).limit(limit) }
  scope :with_findings, -> { where.not(findings: nil) }

  def modality_display_name
    COMMON_MODALITIES[modality] || modality
  end

  def interpreted?
    interpreted_at.present?
  end
end

Endpoints:

GET    /clinics/:clinic_id/patients/:id/imaging_studies
GET    /clinics/:clinic_id/patients/:id/imaging_studies/:id
POST   /clinics/:clinic_id/patients/:id/imaging_studies
PUT    /clinics/:clinic_id/patients/:id/imaging_studies/:id
DELETE /clinics/:clinic_id/patients/:id/imaging_studies/:id

Interactor:

# backend/parts/clinic/interactors/clinic_patients/get_imaging_studies.rb
class ClinicPatients::GetImagingStudies
  include Interactor

  def call
    patient = Patient.find(context.patient_id)

    studies = patient.imaging_studies
                    .includes(:doctor, :patient_problem)
                    .order(started_at: :desc)

    # Filter by modality if provided
    studies = studies.by_modality(context.modality) if context.modality.present?

    # Filter by date range if provided
    if context.start_date.present?
      studies = studies.where('started_at >= ?', context.start_date)
    end
    if context.end_date.present?
      studies = studies.where('started_at <= ?', context.end_date)
    end

    context.imaging_studies = studies
  end
end

Phase 2 Checklist

  • 2.1 Create FamilyMemberHistory migration
  • 2.1 Create FamilyMemberHistory model
  • 2.1 Create family history interactors (CRUD)
  • 2.1 Add family history endpoints to LHR controller
  • 2.1 Add family history serializer (using LongitudinalHealthRecordSerializer)
  • 2.1 Add tests
  • 2.2 Create Goal migration
  • 2.2 Create Goal model
  • 2.2 Create goal interactors (CRUD + status update)
  • 2.2 Add goal endpoints to LHR controller
  • 2.2 Add goal serializer (using LongitudinalHealthRecordSerializer)
  • 2.2 Add tests
  • 2.3 Create SocialHistory migration
  • 2.3 Create SocialHistory model
  • 2.3 Create social history interactors (CRUD)
  • 2.3 Add social history endpoints to LHR controller
  • 2.3 Add social history serializer (using LongitudinalHealthRecordSerializer)
  • 2.3 Add tests
  • 2.4 Create ImagingStudy migration
  • 2.4 Create ImagingStudy model
  • 2.4 Create imaging study interactors (CRUD)
  • 2.4 Add imaging study endpoints to LHR controller
  • 2.4 Add imaging study serializer (using LongitudinalHealthRecordSerializer)
  • 2.4 Add tests

Status: COMPLETED (Dec 26, 2024) Actual Effort: ~1 day

Test Coverage Summary

Model Specs (73 examples, 0 failures):

  • family_member_history_spec.rb - Associations, validations, scopes
  • goal_spec.rb - Associations, validations, scopes (active, completed, by_priority), instance methods (active?, achieved?, update_achievement_status!)
  • social_history_spec.rb - Associations, validations, scopes (current, by_category, practice_viewable), instance methods (current?, display_category), current_summary class method
  • imaging_study_spec.rb - Associations, validations, scopes (available, by_modality, interpreted, pending_interpretation, practice_viewable), instance methods (modality_display_name, interpreted?, has_report?)

Interactor Specs (45 examples, 0 failures):

  • get_family_history_spec.rb - Valid params, relationship/conditions filtering, summary data, clinic/patient validation
  • get_goals_spec.rb - Valid params, status/category filtering, summary data, clinic validation
  • get_social_history_spec.rb - Valid params, category/current filtering, summary data, clinic validation
  • get_imaging_studies_spec.rb - Valid params, modality/body_site filtering, summary data, error handling
  • create_family_history_spec.rb - Valid params, record creation, attribute validation, error handling
  • create_goal_spec.rb - Valid params, record creation, doctor association, error handling
  • create_social_history_spec.rb - Valid params, creation with traits, error handling
  • create_imaging_study_spec.rb - Valid params, DICOM info, record creation, error handling

Factories Created:

  • goal_factory.rb - With traits: :with_target, :achieved, :on_hold, :cancelled, :with_problem, :with_care_plan, :deleted
  • social_history_factory.rb - With traits: :smoking, :former_smoker, :alcohol, :occupation, :exercise, :diet, :past, :deleted
  • imaging_study_factory.rb - With traits: :xray, :ct_scan, :mri, :ultrasound, :with_dicom, :pending_interpretation, :cancelled, :with_appointment, :deleted

Estimated Effort: 5-6 days


Phase 3: Update Main LHR Response

Status: COMPLETED (Dec 26, 2024)

Update GetLongitudinalHealthRecord to include all new data in summary.

# backend/parts/clinic/interactors/clinic_patients/get_longitudinal_health_record.rb
class ClinicPatients::GetLongitudinalHealthRecord
  include Interactor

  def call
    patient = find_patient

    context.lhr = {
      patient: patient_summary(patient),
      health_score: calculate_health_score(patient),

      # Existing
      problems: active_problems(patient),
      medications: current_medications(patient),
      vitals: recent_vitals(patient),
      encounters: recent_encounters(patient),

      # NEW - Phase 1
      allergies: patient.allergy_intolerances.active,
      procedures: patient.procedures.recent(5),
      immunizations: patient.immunizations.recent(10),
      lab_results: patient.diagnostic_reports.recent(10),
      care_plans: patient.care_plans.active,
      referrals: patient.service_requests.pending,

      # NEW - Phase 2
      family_history: patient.family_member_histories.with_conditions,
      goals: patient.goals.active,
      social_history: social_history_summary(patient),
      imaging_studies: patient.imaging_studies.available.recent(10),

      # Counts for dashboard
      counts: {
        active_problems: patient.patient_problems.active.count,
        current_medications: patient.patient_medicines.active.count,
        active_allergies: patient.allergy_intolerances.active.count,
        active_goals: patient.goals.active.count,
        pending_referrals: patient.service_requests.pending.count,
        imaging_studies: patient.imaging_studies.available.count
      }
    }
  end
end

Updated Route Structure

# backend/parts/clinic/routes.rb

resources :patients, controller: 'clinic_patients' do
  member do
    # LHR main endpoint
    get :longitudinal_health_record, to: 'clinic_patients_lhr#show'

    # Existing endpoints
    get :problems, to: 'clinic_patients_lhr#problems'
    get :medications_timeline, to: 'clinic_patients_lhr#medications_timeline'
    get :vitals_trends, to: 'clinic_patients_lhr#vitals_trends'
    get :clinical_assessments, to: 'clinic_patients_lhr#clinical_assessments'

    # NEW Phase 1 endpoints
    get :allergies, to: 'clinic_patients_lhr#allergies'
    get :procedures, to: 'clinic_patients_lhr#procedures'
    get :immunizations, to: 'clinic_patients_lhr#immunizations'
    get :lab_results, to: 'clinic_patients_lhr#lab_results'
    get 'lab_results/trends', to: 'clinic_patients_lhr#lab_result_trends'
    get :care_plans, to: 'clinic_patients_lhr#care_plans'
    get :referrals, to: 'clinic_patients_lhr#referrals'

    # NEW Phase 2 endpoints
    get :family_history, to: 'clinic_patients_lhr#family_history'
    post :family_history, to: 'clinic_patients_lhr#create_family_history'
    put 'family_history/:history_id', to: 'clinic_patients_lhr#update_family_history'
    delete 'family_history/:history_id', to: 'clinic_patients_lhr#delete_family_history'

    get :goals, to: 'clinic_patients_lhr#goals'
    post :goals, to: 'clinic_patients_lhr#create_goal'
    put 'goals/:goal_id', to: 'clinic_patients_lhr#update_goal'
    put 'goals/:goal_id/status', to: 'clinic_patients_lhr#update_goal_status'
    delete 'goals/:goal_id', to: 'clinic_patients_lhr#delete_goal'

    get :social_history, to: 'clinic_patients_lhr#social_history'
    post :social_history, to: 'clinic_patients_lhr#create_social_history'
    put 'social_history/:history_id', to: 'clinic_patients_lhr#update_social_history'
    delete 'social_history/:history_id', to: 'clinic_patients_lhr#delete_social_history'

    get :imaging_studies, to: 'clinic_patients_lhr#imaging_studies'
    get 'imaging_studies/:study_id', to: 'clinic_patients_lhr#show_imaging_study'
    post :imaging_studies, to: 'clinic_patients_lhr#create_imaging_study'
    put 'imaging_studies/:study_id', to: 'clinic_patients_lhr#update_imaging_study'
    delete 'imaging_studies/:study_id', to: 'clinic_patients_lhr#delete_imaging_study'

    # Existing CRUD endpoints...
  end
end

Implementation Order

OrderTaskDependenciesEffort
1Add allergies endpointNone2 hrs
2Add procedures endpointNone2 hrs
3Add immunizations endpointNone2 hrs
4Add lab_results + trends endpointsNone4 hrs
5Add care_plans endpointNone2 hrs
6Add referrals endpointNone2 hrs
7Update main LHR response with aboveSteps 1-62 hrs
8Create FamilyMemberHistory migration + modelNone3 hrs
9Add family_history CRUD endpointsStep 84 hrs
10Create Goal migration + modelNone3 hrs
11Add goals CRUD endpointsStep 104 hrs
12Create SocialHistory migration + modelNone3 hrs
13Add social_history CRUD endpointsStep 124 hrs
14Create ImagingStudy migration + modelNone3 hrs
15Add imaging_studies CRUD endpointsStep 144 hrs
16Update main LHR with Phase 2 dataSteps 8-152 hrs
17Add serializers for all new resourcesAll6 hrs
18Add RSpec testsAll10 hrs

Total Estimated Effort: ~60 hours (7-8 days)


Testing Plan

Unit Tests (Interactors)

# spec/interactors/clinic_patients/get_allergies_spec.rb
RSpec.describe ClinicPatients::GetAllergies do
  let(:patient) { create(:patient) }
  let!(:allergy) { create(:allergy_intolerance, patient: patient) }

  it 'returns patient allergies' do
    result = described_class.call(patient_id: patient.id)
    expect(result.allergies).to include(allergy)
  end
end

Request Tests (API)

# spec/requests/clinic_patients_lhr_spec.rb
RSpec.describe 'LHR API' do
  describe 'GET /clinics/:clinic_id/patients/:id/allergies' do
    it 'returns allergies for the patient' do
      get allergies_clinic_patient_path(clinic, patient), headers: auth_headers
      expect(response).to have_http_status(:ok)
      expect(json_response['allergies']).to be_an(Array)
    end
  end
end

On this page