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
- PatientProblem: ✅ FULLY IMPLEMENTED - Clinic-scoped with all required fields
- PatientMedicine: ✅ EXISTS - Patient-scoped but linked to prescriptions
- MedicationRequest: ✅ FHIR-COMPLIANT - Clinic-scoped with workflow states
- ClinicalImpression: ✅ FHIR-COMPLIANT - Clinic-scoped clinical assessments
- 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 encounterImplementation 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
end1.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
end1.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
endPhase 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:
activeboolean 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'
endQuestions for You
1. Medication Status Management
The PatientMedicine model currently uses a boolean active field. Should we:
- Option A: Keep boolean and add
discontinuation_reasonfield - 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
metadataJSONB field in PatientProblem - Option B: Create separate
problem_medicationsjoin table - Option C: Add
linked_problemsJSONB 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_reasonto 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"}]