Backend Endpoints for Longitudinal Health Records
Overview
This document defines the backend API endpoints required to support the existing frontend LHR UI at /cms/careplan/patient/:patient_id.
Primary LHR Endpoint
GET /api/clinic_patients/:clinic_patient_id/longitudinal_health_record
Purpose: Returns complete longitudinal health data for the frontend LHR dashboard
Controller: PatientsLhrController#show
Note: The frontend route uses clinic_patient_id (21) which needs to be converted to patient_id (23) in the controller
Expected Response Structure:
{
"patient_id": "23318",
"patient_info": {
"id": "23318",
"name": "John Smith",
"age": 45,
"gender": "Male",
"phone": "+1-555-0234",
"email": "john.smith@example.com",
"medical_id": "MR-001234",
"allergies": ["Penicillin", "Peanuts"],
"blood_group": "O+",
"chronic_conditions": ["Hypertension", "Type 2 Diabetes"]
},
"encounters": [
{
"id": "enc-001",
"date": "2024-01-15T10:00:00Z",
"type": "follow-up",
"provider": "Dr. Sarah Johnson",
"chief_complaint": "Routine diabetes follow-up",
"diagnosis": ["Type 2 Diabetes", "Hypertension"],
"status": "completed",
"has_notes": true,
"has_prescriptions": true,
"has_lab_orders": true,
"vitals": [
{
"type": "blood_pressure",
"value": "120/80",
"unit": "mmHg",
"date": "2024-01-15T10:00:00Z",
"is_abnormal": false
}
],
"medications": [...],
"problems": [...],
"lab_results": [...]
}
],
"clinical_assessments": [
{
"id": "ci-001",
"assessment_date": "2024-01-15T10:00:00Z",
"summary": "Chief Complaints: Fatigue, increased thirst\\n\\nClinical Findings: Blood glucose elevated\\n\\nAssessment: Type 2 Diabetes, uncontrolled",
"finding_codes": ["E11.9", "I10"],
"finding_display": ["Type 2 Diabetes", "Hypertension"],
"clinical_notes": "Patient reports poor medication adherence",
"status": "completed",
"doctor": "Dr. Sarah Johnson",
"prescription_id": "presc-001"
}
],
"current_medications": [
{
"id": "med-001",
"drug_name": "Metformin",
"generic_name": "Metformin",
"dosage": "500mg",
"frequency": "BID",
"duration": "Ongoing",
"route": "PO",
"instructions": "Take with meals",
"start_date": "2023-06-01T00:00:00Z",
"prescribed_by": "Dr. Sarah Johnson",
"is_controlled": false,
"interactions": [],
"prescription_status": "active",
"adherence_rate": 95,
"refills_remaining": 2,
"next_refill_date": "2024-02-15T00:00:00Z"
}
],
"problem_list": [
{
"id": "prob-001",
"name": "Type 2 Diabetes Mellitus",
"icd_code": "E11.9",
"status": "chronic",
"severity": "medium",
"onset_date": "2020-03-15T00:00:00Z",
"last_review_date": "2024-01-15T00:00:00Z",
"linked_medications": ["med-001"],
"linked_encounters": ["enc-001"],
"notes": "Well controlled with medication"
}
],
"vitals_trends": [
{
"metric": "blood_pressure",
"unit": "mmHg",
"data_points": [
{
"date": "2024-01-15T10:00:00Z",
"value": { "systolic": 120, "diastolic": 80 },
"encounter_id": "enc-001",
"is_abnormal": false,
"notes": "Well controlled"
}
],
"normal_range": { "min": 90, "max": 140 },
"latest_value": { "systolic": 120, "diastolic": 80 },
"trend": "stable"
}
],
"medication_history": [
{
"id": "medhist-001",
"medication_id": "med-001",
"date": "2024-01-15T10:00:00Z",
"type": "started",
"drug_name": "Metformin",
"dosage": "500mg",
"frequency": "BID",
"reason": "Diabetes management",
"prescribed_by": "Dr. Sarah Johnson",
"encounter_id": "enc-001"
}
],
"lab_results": [
{
"id": "lab-001",
"test_name": "HbA1c",
"test_code": "4548-4",
"date": "2024-01-10T00:00:00Z",
"value": "7.2",
"unit": "%",
"normal_range": "< 7.0",
"status": "abnormal",
"trend": "improving",
"encounter_id": "enc-001",
"ordered_by": "Dr. Sarah Johnson"
}
],
"clinical_assessments": [
{
"id": "assess-001",
"encounter_id": "enc-001",
"assessment_date": "2024-01-15T10:00:00Z",
"assessment_type": "routine_followup",
"provider": "Dr. Sarah Johnson",
"subjective": "Patient reports feeling well, no new symptoms",
"objective": "Vital signs stable, no acute distress",
"assessment": "Diabetes well controlled, continue current regimen",
"plan": "Continue metformin, follow up in 3 months",
"priority": "routine",
"follow_up_required": true,
"follow_up_date": "2024-04-15T10:00:00Z"
}
],
"last_visit_date": "2024-01-15T10:00:00Z",
"next_appointment_date": "2024-04-15T10:00:00Z",
"active_problems_count": 3,
"current_medications_count": 2,
"total_encounters": 12
}Implementation Strategy
Controller Implementation
# parts/patient/controllers/patients_lhr_controller.rb
class PatientsLhrController < ApiController
before_action :set_clinic_patient
def show
result = Patients::GetLongitudinalHealthRecord.call(
patient_id: params[:patient_id],
clinic_id: current_user.clinic_id
)
render_result(result, result.success? ? :longitudinal_data : nil)
end
def problems
result = PatientProblems::Index.call(
patient_id: params[:patient_id],
clinic_id: current_user.clinic_id
)
render_result(result, result.success? ? PatientProblemSerializer : nil)
end
def medications_timeline
result = MedicationRequests::Timeline.call(
patient_id: params[:patient_id],
clinic_id: current_user.clinic_id
)
render_result(result, result.success? ? MedicationEventSerializer : nil)
end
def vitals_trends
result = Patients::GetVitalsTrends.call(
patient_id: params[:patient_id],
clinic_id: current_user.clinic_id,
date_range: params[:date_range]
)
render_result(result, result.success? ? VitalsTrendSerializer : nil)
end
private
def set_clinic_patient
@clinic_patient = ClinicPatient.find_by!(
clinic_id: current_user.clinic_id,
patient_id: params[:patient_id]
)
rescue ActiveRecord::RecordNotFound
render json: { error: 'Patient not found in clinic' }, status: :not_found
end
endMain Interactor Implementation
# parts/patient/interactors/patients/get_longitudinal_health_record.rb
class Patients::GetLongitudinalHealthRecord < BaseInteractor
def call
validate_access
gather_patient_data
build_encounters_history
aggregate_current_state
aggregate_historical_trends
calculate_summary_metrics
build_response
end
private
def validate_access
context.clinic_patient = ClinicPatient.find_by!(
clinic_id: current_user.clinic_id,
patient_id: context.patient_id
)
context.patient = context.clinic_patient.patient
authorize(context.patient, :show?)
end
def gather_patient_data
context.patient_info = {
id: context.patient.id.to_s,
name: "#{context.patient.first_name} #{context.patient.last_name}".strip,
age: context.patient.age || calculate_age(context.patient.date_of_birth),
gender: context.patient.gender&.capitalize || 'Unknown',
phone: context.patient.phone_number,
email: context.patient.email,
medical_id: context.clinic_patient.patient_code || "MR-#{context.patient.id}",
allergies: context.patient.allergies || [],
blood_group: context.patient.blood_group,
chronic_conditions: extract_chronic_conditions
}
end
def build_encounters_history
appointments = Appointment.includes(
:doctor, :clinic_doctor, :prescription => [
:prescription_complaints,
:prescription_medicines,
:prescription_test_lists
]
).where(clinic_patient_id: context.clinic_patient.id)
.order(appointment_date: :desc)
.limit(50)
context.encounters = appointments.map do |appointment|
prescription = appointment.prescription
doctor_name = "Dr. #{appointment.doctor.first_name} #{appointment.doctor.last_name}"
{
id: appointment.id.to_s,
date: appointment.appointment_date.in_time_zone,
type: map_appointment_type(appointment.appointment_type),
provider: doctor_name,
chief_complaint: prescription&.prescription_complaints&.first&.name || appointment.chief_complaint || '',
diagnosis: prescription&.diagnosis || [],
status: appointment.status == 'completed' ? 'completed' : 'in-progress',
has_notes: prescription&.notes.present? || appointment.notes.present?,
has_prescriptions: prescription&.prescription_medicines&.any?,
has_lab_orders: prescription&.prescription_test_lists&.any?,
vitals: extract_vitals_from_appointment(appointment),
medications: extract_medications_from_prescription(prescription),
problems: extract_problems_from_prescription(prescription),
lab_results: extract_lab_results_from_prescription(prescription)
}
end
end
def aggregate_current_state
# Current medications from patient_medicines
current_meds = PatientMedicine.where(
patient_id: context.patient_id,
active: true
).includes(:prescription)
context.current_medications = current_meds.map do |med|
prescription = med.prescription
doctor_name = "Dr. #{prescription.doctor.first_name} #{prescription.doctor.last_name}"
{
id: med.id.to_s,
drug_name: med.medicine_name,
generic_name: med.medicine_name, # Could be enhanced with drug database
dosage: med.dosage,
frequency: med.frequency,
duration: med.duration || 'Ongoing',
route: 'PO', # Could be enhanced with route tracking
instructions: med.instructions,
start_date: med.created_at,
last_modified: med.updated_at,
prescribed_by: doctor_name,
is_controlled: false, # Could be enhanced with controlled substance tracking
interactions: [], # Could be enhanced with drug interaction checking
prescription_status: med.active? ? 'active' : 'completed',
adherence_rate: calculate_adherence_rate(med),
refills_remaining: med.refills_remaining,
next_refill_date: calculate_next_refill_date(med)
}
end
# Current problems (to be created from historical diagnoses)
context.problem_list = extract_patient_problems
end
def aggregate_historical_trends
# Vitals trends from appointment history
context.vitals_trends = build_vitals_trends
# Medication history from prescription changes
context.medication_history = build_medication_history
# Lab results from all encounters
context.lab_results = build_lab_results_summary
# Clinical assessments from encounters
context.clinical_assessments = build_clinical_assessments
end
def calculate_summary_metrics
context.last_visit_date = context.encounters.first&.dig(:date)
context.next_appointment_date = get_next_appointment
context.active_problems_count = context.problem_list.count { |p| p[:status].in?(['active', 'chronic']) }
context.current_medications_count = context.current_medications.count
context.total_encounters = context.encounters.count
end
def build_response
context.longitudinal_data = {
patient_id: context.patient_id.to_s,
patient_info: context.patient_info,
encounters: context.encounters,
current_medications: context.current_medications,
problem_list: context.problem_list,
vitals_trends: context.vitals_trends,
medication_history: context.medication_history,
lab_results: context.lab_results,
clinical_assessments: context.clinical_assessments,
last_visit_date: context.last_visit_date,
next_appointment_date: context.next_appointment_date,
active_problems_count: context.active_problems_count,
current_medications_count: context.current_medications_count,
total_encounters: context.total_encounters
}
end
# Helper methods
def extract_chronic_conditions
# Extract from patient medical history or aggregate from diagnoses
conditions = []
# From patient medical history
if context.patient.medical_history.present?
chronic_conditions = context.patient.medical_history.dig('chronic_conditions')
conditions.concat(chronic_conditions) if chronic_conditions.is_a?(Array)
end
# From recent diagnoses (last 2 years)
recent_diagnoses = Prescription.joins(:clinic_patient)
.where(clinic_patients: { patient_id: context.patient_id })
.where('prescriptions.created_at > ?', 2.years.ago)
.where.not(diagnosis: [nil, []])
.pluck(:diagnosis)
.flatten
.uniq
conditions.concat(recent_diagnoses)
conditions.uniq.compact
end
def extract_vitals_from_appointment(appointment)
return [] unless appointment.vitals.present?
vitals_data = appointment.vitals.is_a?(String) ? JSON.parse(appointment.vitals) : appointment.vitals
vitals_data.map do |key, value|
next unless value.present?
{
type: map_vital_type(key),
value: value,
unit: get_vital_unit(key),
date: appointment.appointment_date,
is_abnormal: is_vital_abnormal?(key, value)
}
end.compact
end
def extract_problems_from_prescription(prescription)
return [] unless prescription&.prescription_complaints&.any?
prescription.prescription_complaints.map do |complaint|
{
id: "comp-#{complaint.id}",
name: complaint.name,
status: 'active',
severity: 'medium', # Could be enhanced with severity tracking
onset_date: prescription.created_at,
last_review_date: prescription.updated_at,
linked_medications: [],
linked_encounters: [prescription.event_id.to_s],
notes: "Chief complaint from encounter"
}
end
end
def build_vitals_trends
appointments = Appointment.where(clinic_patient_id: context.clinic_patient.id)
.where.not(vitals: [nil, '{}', ''])
.order(:appointment_date)
trends_map = {}
appointments.each do |appointment|
vitals_data = appointment.vitals.is_a?(String) ? JSON.parse(appointment.vitals) : appointment.vitals
vitals_data.each do |key, value|
next unless value.present?
vital_type = map_vital_type(key)
next unless vital_type
trends_map[vital_type] ||= {
metric: vital_type,
unit: get_vital_unit(key),
data_points: []
}
trends_map[vital_type][:data_points] << {
date: appointment.appointment_date,
value: parse_vital_value(key, value),
encounter_id: appointment.id.to_s,
is_abnormal: is_vital_abnormal?(key, value)
}
end
end
# Add normal ranges and trends
trends_map.values.map do |trend|
trend[:normal_range] = get_normal_range(trend[:metric])
trend[:latest_value] = trend[:data_points].last&.dig(:value)
trend[:trend] = calculate_vital_trend(trend[:data_points])
trend
end
end
def build_medication_history
# Build from prescription_medicines across all prescriptions
prescriptions = Prescription.joins(:clinic_patient)
.includes(:prescription_medicines, :doctor)
.where(clinic_patients: { patient_id: context.patient_id })
.where(is_final: true)
.order(:created_at)
events = []
medication_tracker = {}
prescriptions.each do |prescription|
doctor_name = "Dr. #{prescription.doctor.first_name} #{prescription.doctor.last_name}"
prescription.prescription_medicines.each do |medicine|
med_key = medicine.medicine_name.downcase
previous_med = medication_tracker[med_key]
event_type = if previous_med.nil?
'started'
elsif previous_med[:dosage] != medicine.dosage
'dosageChanged'
elsif previous_med[:frequency] != medicine.frequency
'frequencyChanged'
else
'continued'
end
next if event_type == 'continued' # Don't log if no changes
events << {
id: "medhist-#{prescription.id}-#{medicine.id}",
medication_id: medicine.id.to_s,
date: prescription.created_at,
type: event_type,
drug_name: medicine.medicine_name,
dosage: medicine.dosage,
previous_dosage: previous_med&.dig(:dosage),
frequency: medicine.frequency,
previous_frequency: previous_med&.dig(:frequency),
reason: extract_medication_reason(medicine, prescription),
prescribed_by: doctor_name,
encounter_id: prescription.event_id&.to_s
}
medication_tracker[med_key] = {
dosage: medicine.dosage,
frequency: medicine.frequency
}
end
end
events.reverse # Newest first
end
def build_lab_results_summary
# Build from prescription_test_lists and actual lab results if available
prescriptions = Prescription.joins(:clinic_patient)
.includes(:prescription_test_lists => :prescription_tests, :doctor)
.where(clinic_patients: { patient_id: context.patient_id })
.where(is_final: true)
.order(created_at: :desc)
results = []
prescriptions.each do |prescription|
doctor_name = "Dr. #{prescription.doctor.first_name} #{prescription.doctor.last_name}"
prescription.prescription_test_lists.each do |test_list|
test_list.prescription_tests.each do |test|
results << {
id: "lab-#{prescription.id}-#{test.id}",
test_name: test.test_name,
test_code: test.test_code,
date: prescription.created_at,
value: "Ordered", # Could be enhanced with actual results
unit: "",
normal_range: "",
status: "normal", # Could be enhanced with actual results
trend: "stable",
encounter_id: prescription.event_id&.to_s,
ordered_by: doctor_name
}
end
end
end
results
end
# Helper methods for mapping data
def map_appointment_type(type)
case type&.downcase
when 'consultation' then 'consultation'
when 'followup', 'follow_up' then 'follow-up'
when 'emergency' then 'emergency'
when 'routine' then 'routine'
else 'consultation'
end
end
def map_vital_type(key)
case key.to_s.downcase
when 'bp', 'blood_pressure', 'bloodpressure' then 'blood_pressure'
when 'hr', 'heart_rate', 'heartrate', 'pulse' then 'heart_rate'
when 'temp', 'temperature' then 'temperature'
when 'weight', 'wt' then 'weight'
when 'spo2', 'oxygen_saturation', 'o2sat' then 'oxygen_saturation'
when 'rr', 'respiratory_rate', 'resp_rate' then 'respiratory_rate'
else nil
end
end
def get_vital_unit(key)
case map_vital_type(key)
when 'blood_pressure' then 'mmHg'
when 'heart_rate' then 'bpm'
when 'temperature' then '°C'
when 'weight' then 'kg'
when 'oxygen_saturation' then '%'
when 'respiratory_rate' then 'breaths/min'
else ''
end
end
def parse_vital_value(key, value)
if map_vital_type(key) == 'blood_pressure' && value.to_s.include?('/')
systolic, diastolic = value.to_s.split('/').map(&:to_i)
{ systolic: systolic, diastolic: diastolic }
else
value.to_f
end
end
def is_vital_abnormal?(key, value)
# Basic abnormal ranges - could be enhanced with age/gender specific ranges
case map_vital_type(key)
when 'blood_pressure'
if value.to_s.include?('/')
sys, dia = value.to_s.split('/').map(&:to_i)
sys > 140 || sys < 90 || dia > 90 || dia < 60
else
false
end
when 'heart_rate'
val = value.to_f
val > 100 || val < 60
when 'temperature'
val = value.to_f
val > 37.5 || val < 36
when 'oxygen_saturation'
value.to_f < 95
else
false
end
end
def extract_patient_problems
# Extract from historical diagnoses and create patient-level problems
diagnoses_map = {}
prescriptions = Prescription.joins(:clinic_patient)
.where(clinic_patients: { patient_id: context.patient_id })
.where.not(diagnosis: [nil, []])
.order(:created_at)
prescriptions.each do |prescription|
prescription.diagnosis.each do |diagnosis|
diagnosis_key = diagnosis.downcase.strip
if diagnoses_map[diagnosis_key]
# Update existing problem
diagnoses_map[diagnosis_key][:last_review_date] = prescription.created_at
diagnoses_map[diagnosis_key][:linked_encounters] << prescription.event_id.to_s
else
# Create new problem
diagnoses_map[diagnosis_key] = {
id: "prob-#{Digest::MD5.hexdigest(diagnosis_key)}",
name: diagnosis,
status: determine_problem_status(diagnosis),
severity: determine_problem_severity(diagnosis),
onset_date: prescription.created_at,
last_review_date: prescription.created_at,
linked_medications: [],
linked_encounters: [prescription.event_id.to_s],
notes: "Identified from clinical encounters"
}
end
end
end
# Link medications to problems
diagnoses_map.values.each do |problem|
linked_meds = context.current_medications.select do |med|
problem[:name].downcase.include?(extract_condition_from_medication(med[:drug_name]))
end
problem[:linked_medications] = linked_meds.map { |med| med[:id] }
end
diagnoses_map.values
end
def determine_problem_status(diagnosis)
chronic_keywords = ['diabetes', 'hypertension', 'copd', 'asthma', 'arthritis']
if chronic_keywords.any? { |keyword| diagnosis.downcase.include?(keyword) }
'chronic'
else
'active'
end
end
def determine_problem_severity(diagnosis)
critical_keywords = ['heart attack', 'stroke', 'cancer', 'emergency']
high_keywords = ['diabetes', 'hypertension', 'copd']
if critical_keywords.any? { |keyword| diagnosis.downcase.include?(keyword) }
'critical'
elsif high_keywords.any? { |keyword| diagnosis.downcase.include?(keyword) }
'high'
else
'medium'
end
end
endRoutes Configuration
# parts/patient/routes.rb
Rails.application.routes.draw do
namespace :api do
resources :patients do
member do
get :longitudinal_health_record, to: 'patients_lhr#show'
get :problems, to: 'patients_lhr#problems'
get :medications_timeline, to: 'patients_lhr#medications_timeline'
get :vitals_trends, to: 'patients_lhr#vitals_trends'
end
end
end
endData Migration Rake Tasks
# lib/tasks/lhr_migration.rake
namespace :lhr do
desc "Migrate existing prescription data to support LHR"
task migrate_for_lhr: :environment do
puts "Starting LHR migration..."
# 1. Create patient problems from historical diagnoses
migrate_patient_problems
# 2. Enhance vitals data structure
standardize_vitals_data
# 3. Create medication history events
create_medication_history
puts "LHR migration completed!"
end
def migrate_patient_problems
puts "Creating patient problems from historical diagnoses..."
problems_created = 0
ClinicPatient.includes(:patient, prescriptions: :doctor).find_each do |clinic_patient|
patient = clinic_patient.patient
clinic_id = clinic_patient.clinic_id
diagnoses_map = {}
clinic_patient.prescriptions.where.not(diagnosis: [nil, []]).order(:created_at).each do |prescription|
prescription.diagnosis.each do |diagnosis|
diagnosis_key = diagnosis.downcase.strip
unless diagnoses_map[diagnosis_key]
diagnoses_map[diagnosis_key] = {
clinic_id: clinic_id,
patient_id: patient.id,
doctor_id: prescription.doctor_id,
problem_name: diagnosis,
problem_category: categorize_diagnosis(diagnosis),
status: determine_problem_status(diagnosis),
severity: determine_problem_severity(diagnosis),
onset_date: prescription.prescription_date || prescription.created_at.to_date,
first_encounter_id: prescription.event_id,
last_encounter_id: prescription.event_id,
clinical_notes: "Migrated from prescription diagnoses"
}
else
# Update last encounter
diagnoses_map[diagnosis_key][:last_encounter_id] = prescription.event_id
end
end
end
# Create patient problems
diagnoses_map.values.each do |problem_data|
begin
PatientProblem.create!(problem_data)
problems_created += 1
rescue ActiveRecord::RecordInvalid => e
puts "Failed to create problem: #{problem_data[:problem_name]} - #{e.message}"
end
end
end
puts "Created #{problems_created} patient problems"
end
def standardize_vitals_data
puts "Standardizing vitals data format..."
updated_count = 0
Appointment.where.not(vitals: [nil, '{}', '']).find_each do |appointment|
begin
vitals_data = appointment.vitals.is_a?(String) ? JSON.parse(appointment.vitals) : appointment.vitals
standardized = {}
vitals_data.each do |key, value|
standardized_key = standardize_vital_key(key)
standardized[standardized_key] = value if standardized_key
end
if standardized.any?
appointment.update!(vitals: standardized)
updated_count += 1
end
rescue JSON::ParserError => e
puts "Failed to parse vitals for appointment #{appointment.id}: #{e.message}"
end
end
puts "Standardized vitals for #{updated_count} appointments"
end
def create_medication_history
puts "Creating medication history from prescriptions..."
# This will be handled by background jobs when prescriptions are finalized
# For now, create history for existing finalized prescriptions
count = 0
Prescription.where(is_final: true).includes(:prescription_medicines).find_each do |prescription|
prescription.prescription_medicines.each do |medicine|
# Create medication request if not exists
unless MedicationRequest.exists?(
prescription_id: prescription.id,
medication_name: medicine.medicine_name
)
MedicationRequest.create!(
clinic_id: prescription.clinic_id,
patient_id: prescription.patient_id,
doctor_id: prescription.doctor_id,
encounter_id: prescription.event_id,
prescription_id: prescription.id,
medication_name: medicine.medicine_name,
dosage_instruction: "#{medicine.dosage} #{medicine.frequency}",
frequency: medicine.frequency,
instructions: medicine.instructions,
status: 'completed'
)
count += 1
end
end
end
puts "Created #{count} medication requests from historical prescriptions"
end
# Helper methods
def categorize_diagnosis(diagnosis)
case diagnosis.downcase
when /diabetes/ then 'Endocrine'
when /hypertension|blood pressure/ then 'Cardiovascular'
when /infection|fever/ then 'Infectious'
when /pain|arthritis/ then 'Musculoskeletal'
else 'General'
end
end
def standardize_vital_key(key)
case key.to_s.downcase
when 'bp', 'blood_pressure', 'bloodpressure' then 'blood_pressure'
when 'hr', 'heart_rate', 'heartrate', 'pulse' then 'heart_rate'
when 'temp', 'temperature' then 'temperature'
when 'weight', 'wt' then 'weight'
when 'spo2', 'oxygen_saturation', 'o2sat' then 'oxygen_saturation'
when 'rr', 'respiratory_rate', 'resp_rate' then 'respiratory_rate'
else nil
end
end
endThis creates the exact backend structure needed to support your existing LHR frontend at /cms/careplan/patient/:patient_id.
<function_calls> [{"content": "Analyze existing LHR frontend URL structure and components", "status": "completed", "priority": "high", "id": "1"}, {"content": "Create backend endpoints for LHR UI integration", "status": "completed", "priority": "high", "id": "2"}, {"content": "Update documentation with endpoint specifications", "status": "completed", "priority": "high", "id": "3"}]