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/exportExisting Models (Ready to Expose)
| Model | FHIR Resource | Has Data | Needs API |
|---|---|---|---|
AllergyIntolerance | AllergyIntolerance | Yes | Yes |
Procedure | Procedure | Yes | Yes |
Immunization | Immunization | Yes | Yes |
DiagnosticReport | DiagnosticReport | Yes | Yes |
CarePlan | CarePlan | Yes | Yes |
ServiceRequest | ServiceRequest | Yes | Yes |
Models Needing Creation
| Model | FHIR Resource | Priority |
|---|---|---|
FamilyMemberHistory | FamilyMemberHistory | High |
Goal | Goal | High |
SocialHistory | Observation (social-history) | High |
ImagingStudy | ImagingStudy | Medium |
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/allergiesImplementation:
# 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
endController Addition:
# Add to clinic_patients_lhr_controller.rb
def allergies
result = ClinicPatients::GetAllergies.call(params)
render_result(result, result.success? ? AllergySerializer : nil)
endRoute:
get :allergies, to: 'clinic_patients_lhr#allergies'1.2 Procedures Endpoint
Model: Procedure (exists)
New Endpoint:
GET /clinics/:clinic_id/patients/:id/proceduresImplementation:
# 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
end1.3 Immunizations Endpoint
Model: Immunization (exists)
New Endpoint:
GET /clinics/:clinic_id/patients/:id/immunizationsImplementation:
# 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
end1.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=HBA1CImplementation:
# 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
end1.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/:idImplementation:
# 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
end1.6 Referrals Endpoint
Model: ServiceRequest (exists)
New Endpoint:
GET /clinics/:clinic_id/patients/:id/referralsImplementation:
# 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
endPhase 1 Checklist
- 1.1 Add
GET allergiesendpoint - 1.2 Add
GET proceduresendpoint - 1.3 Add
GET immunizationsendpoint - 1.4 Add
GET lab_results+GET lab_results/trendsendpoints - 1.5 Add
GET care_plansendpoint - 1.6 Add
GET referralsendpoint - Update
GetLongitudinalHealthRecordto 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
endModel:
# 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) }
endEndpoints:
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_id2.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
endModel:
# 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) }
endEndpoints:
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_id2.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
endOption 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
endRecommendation: 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/:idModel (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) }
endInteractor:
# 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
end2.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
endModel:
# 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
endEndpoints:
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/:idInteractor:
# 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
endPhase 2 Checklist
- 2.1 Create
FamilyMemberHistorymigration - 2.1 Create
FamilyMemberHistorymodel - 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
Goalmigration - 2.2 Create
Goalmodel - 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
SocialHistorymigration - 2.3 Create
SocialHistorymodel - 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
ImagingStudymigration - 2.4 Create
ImagingStudymodel - 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, scopesgoal_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 methodimaging_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 validationget_goals_spec.rb- Valid params, status/category filtering, summary data, clinic validationget_social_history_spec.rb- Valid params, category/current filtering, summary data, clinic validationget_imaging_studies_spec.rb- Valid params, modality/body_site filtering, summary data, error handlingcreate_family_history_spec.rb- Valid params, record creation, attribute validation, error handlingcreate_goal_spec.rb- Valid params, record creation, doctor association, error handlingcreate_social_history_spec.rb- Valid params, creation with traits, error handlingcreate_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, :deletedsocial_history_factory.rb- With traits: :smoking, :former_smoker, :alcohol, :occupation, :exercise, :diet, :past, :deletedimaging_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
endUpdated 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
endImplementation Order
| Order | Task | Dependencies | Effort |
|---|---|---|---|
| 1 | Add allergies endpoint | None | 2 hrs |
| 2 | Add procedures endpoint | None | 2 hrs |
| 3 | Add immunizations endpoint | None | 2 hrs |
| 4 | Add lab_results + trends endpoints | None | 4 hrs |
| 5 | Add care_plans endpoint | None | 2 hrs |
| 6 | Add referrals endpoint | None | 2 hrs |
| 7 | Update main LHR response with above | Steps 1-6 | 2 hrs |
| 8 | Create FamilyMemberHistory migration + model | None | 3 hrs |
| 9 | Add family_history CRUD endpoints | Step 8 | 4 hrs |
| 10 | Create Goal migration + model | None | 3 hrs |
| 11 | Add goals CRUD endpoints | Step 10 | 4 hrs |
| 12 | Create SocialHistory migration + model | None | 3 hrs |
| 13 | Add social_history CRUD endpoints | Step 12 | 4 hrs |
| 14 | Create ImagingStudy migration + model | None | 3 hrs |
| 15 | Add imaging_studies CRUD endpoints | Step 14 | 4 hrs |
| 16 | Update main LHR with Phase 2 data | Steps 8-15 | 2 hrs |
| 17 | Add serializers for all new resources | All | 6 hrs |
| 18 | Add RSpec tests | All | 10 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
endRequest 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
endRelated Documentation
- LHR Components Audit - Gap analysis
- LHR Completion Plan - Overall plan
- Backend Endpoints - Existing API documentation