Kulcare Docs
DeveloperCare plansCare planMigration

Frontend Migration Guide - LHR Implementation

Overview

This guide outlines the frontend changes required to migrate from the current careplan system to the new FHIR-compliant Longitudinal Health Records architecture. The backend has been updated to rename careplans table to prescriptions and change all encounter_id references to appointment_id.

Backend Changes Summary

Database Changes Made:

  • careplans table → prescriptions table
  • All encounter_id columns → appointment_id columns
  • Polymorphic type references: 'Careplan' → 'Prescription'
  • Event tracking: 'careplan.create' → 'prescription.create'
  • Model associations: .careplans → .prescriptions

New FHIR API Endpoints Available:

All endpoints use RESTful routes nested under appointments:

  • /appointments/:appointment_id/clinical_observations/vitals - Vitals management
  • /appointments/:appointment_id/clinical_impressions - Clinical assessments
  • /appointments/:appointment_id/medication_requests - Medications
  • /appointments/:appointment_id/service_requests - Investigations
  • /appointments/:appointment_id/care_plans/advice - Clinical advice
  • /appointments/:appointment_id/care_plans/follow_up - Next visit planning

Phase 1: API Client Updates

Step 1: Update Type Definitions

Update Medical Types

// frontend/packages/api-client/src/types/medical.ts

// Updated Prescription interface (was Careplan)
export interface Prescription {
  id: number;
  patient_id: number;
  doctor_id: number;
  appointment_id: number; // CHANGED: was encounter_id
  clinic_id: number;
  prescription_date: string;
  diagnosis: string[];
  observations: string;
  notes: string;
  followup_date?: string;
  is_final: boolean;
  created_at: string;
  updated_at: string;
  
  // Related data
  prescription_medicines: PrescriptionMedicine[];
  prescription_test_lists: PrescriptionTestList[];
  medication_requests: MedicationRequest[];
  service_requests: ServiceRequest[];
}

// New FHIR resource interfaces
export interface MedicationRequest {
  id: number;
  clinic_id: number;
  patient_id: number;
  doctor_id: number;
  appointment_id: number; // CHANGED: was encounter_id
  prescription_id: number;
  
  status: 'active' | 'on-hold' | 'cancelled' | 'completed';
  intent: 'proposal' | 'plan' | 'order';
  medication_name: string;
  medication_type?: string;
  strength?: string;
  form?: string;
  
  dosage_instruction?: string;
  frequency?: string;
  quantity_value?: number;
  quantity_unit?: string;
  duration_value?: number;
  duration_unit?: string;
  instructions?: string;
  as_needed: boolean;
  
  document_generated: boolean;
  document_url?: string;
  
  created_at: string;
  updated_at: string;
  
  // Relations
  patient?: Patient;
  doctor?: Doctor;
  appointment?: Appointment; // CHANGED: was encounter
}

export interface ServiceRequest {
  id: number;
  clinic_id: number;
  patient_id: number;
  doctor_id: number;
  appointment_id: number; // CHANGED: was encounter_id
  prescription_id: number;
  
  status: 'active' | 'on-hold' | 'cancelled' | 'completed';
  intent: 'proposal' | 'plan' | 'order';
  service_type: 'lab_test' | 'imaging' | 'procedure';
  service_name: string;
  service_code?: string;
  instructions?: string;
  
  document_generated: boolean;
  document_url?: string;
  
  created_at: string;
  updated_at: string;
}

export interface PatientProblem {
  id: number;
  clinic_id: number;
  patient_id: number;
  doctor_id: number;
  
  problem_code?: string;
  problem_name: string;
  problem_category?: string;
  severity?: string;
  
  onset_date?: string;
  resolved_date?: string;
  status: 'active' | 'resolved' | 'chronic';
  
  first_appointment_id?: number; // CHANGED: was first_encounter_id
  last_appointment_id?: number; // CHANGED: was last_encounter_id
  clinical_notes?: string;
  
  created_at: string;
  updated_at: string;
}

export interface CarePlan {
  id: number;
  clinic_id: number;
  patient_id: number;
  doctor_id: number;
  
  status: 'active' | 'completed' | 'cancelled';
  intent: 'proposal' | 'plan' | 'order';
  title?: string;
  description?: string;
  
  period_start?: string;
  period_end?: string;
  
  goals: Record<string, any>;
  activities: Record<string, any>;
  care_team: any[];
  addresses_problems: number[];
  
  document_generated: boolean;
  document_url?: string;
  
  created_at: string;
  updated_at: string;
}

Update Longitudinal Data Interface

// frontend/packages/api-client/src/types/longitudinal.ts

export interface PatientLongitudinalData {
  patient: Patient;
  current_medications: PatientMedicine[];
  current_problems: PatientProblem[];
  active_care_plans: CarePlan[];
  
  encounter_history: EncounterSummary[];
  medication_history: MedicationRequest[];
  service_history: ServiceRequest[];
  
  health_trends: {
    vitals_timeline: VitalsTrend[];
    weight_timeline: WeightTrend[];
    medication_adherence: MedicationAdherence[];
    problem_timeline: ProblemTrend[];
  };
}

export interface EncounterSummary {
  id: number;
  appointment_date: string;
  appointment_type: string;
  doctor: Doctor;
  diagnosis: string[];
  observations: string;
  prescription?: Prescription;
  vitals?: Record<string, any>;
}

Step 2: Update API Client Functions

Create New FHIR-Compliant API Clients

// frontend/packages/api-client/src/api/vitals.ts
export const vitalsApi = {
  getVitals: (appointmentId: number) =>
    apiClient.get<VitalSigns>(`/appointments/${appointmentId}/clinical_observations/vitals`),
    
  updateVitals: (appointmentId: number, data: VitalSignsData) =>
    apiClient.put<VitalSigns>(`/appointments/${appointmentId}/clinical_observations/vitals`, data),
};

// frontend/packages/api-client/src/api/clinical-impressions.ts
export const clinicalImpressionsApi = {
  getClinicalImpressions: (appointmentId: number) =>
    apiClient.get<ClinicalImpression[]>(`/appointments/${appointmentId}/clinical_impressions`),
    
  updateClinicalImpressions: (appointmentId: number, data: ClinicalImpressionData) =>
    apiClient.put<ClinicalImpression>(`/appointments/${appointmentId}/clinical_impressions`, data),
};

// frontend/packages/api-client/src/api/medication-requests.ts
export const medicationRequestsApi = {
  getMedications: (appointmentId: number) =>
    apiClient.get<MedicationRequest[]>(`/appointments/${appointmentId}/medication_requests`),
    
  addMedication: (appointmentId: number, data: MedicationRequestData) =>
    apiClient.post<MedicationRequest>(`/appointments/${appointmentId}/medication_requests`, data),
    
  searchMedications: (search: string) =>
    apiClient.get<Medication[]>(`/medications?search=${encodeURIComponent(search)}`),
};

// frontend/packages/api-client/src/api/service-requests.ts  
export const serviceRequestsApi = {
  getServiceRequests: (appointmentId: number) =>
    apiClient.get<ServiceRequest[]>(`/appointments/${appointmentId}/service_requests`),
    
  orderInvestigation: (appointmentId: number, data: ServiceRequestData) =>
    apiClient.post<ServiceRequest>(`/appointments/${appointmentId}/service_requests`, data),
    
  searchInvestigations: (params: { category?: string; search?: string }) =>
    apiClient.get<Investigation[]>('/investigations', { params }),
};

// frontend/packages/api-client/src/api/care-plans.ts
export const carePlansApi = {
  getAdvice: (appointmentId: number) =>
    apiClient.get<AdviceData>(`/appointments/${appointmentId}/care_plans/advice`),
    
  updateAdvice: (appointmentId: number, data: AdviceData) =>
    apiClient.put<AdviceData>(`/appointments/${appointmentId}/care_plans/advice`, data),
    
  getFollowUpPlan: (appointmentId: number) =>
    apiClient.get<FollowUpPlan>(`/appointments/${appointmentId}/care_plans/follow_up`),
    
  scheduleFollowUp: (appointmentId: number, data: FollowUpData) =>
    apiClient.post<FollowUpPlan>(`/appointments/${appointmentId}/care_plans/follow_up`, data),
};

// frontend/packages/api-client/src/api/longitudinal-health-records.ts
export const lhrApi = {
  getPatientLHR: (patientId: number) =>
    apiClient.get<PatientLongitudinalData>(`/patients/${patientId}/longitudinal_health_record`),
    
  getPatientProblems: (patientId: number) =>
    apiClient.get<PatientProblem[]>(`/patients/${patientId}/problems`),
    
  getMedicationTimeline: (patientId: number) =>
    apiClient.get<MedicationRequest[]>(`/patients/${patientId}/medications_timeline`),
};

Step 3: Update API Client Index

// frontend/packages/api-client/src/api/index.ts

// Remove old careplan exports
// export { careplansApi } from './careplans';

// Add new FHIR-compliant exports
export { vitalsApi } from './vitals';
export { clinicalImpressionsApi } from './clinical-impressions';
export { medicationRequestsApi } from './medication-requests';
export { serviceRequestsApi } from './service-requests';
export { carePlansApi } from './care-plans';
export { lhrApi } from './longitudinal-health-records';

Phase 2: CMS App Updates

Step 1: Create New FHIR Tab Components

Vitals Tab Component

// frontend/apps/cms/src/components/lhr-tabs/VitalsTab.tsx

import { vitalsApi } from '@kulcare/api-client';
import type { VitalSigns } from '@kulcare/api-client/types';

export function VitalsTab({ appointmentId }: { appointmentId: number }) {
  const [vitals, setVitals] = useState<VitalSigns | null>(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    loadVitals();
  }, [appointmentId]);
  
  const loadVitals = async () => {
    try {
      setLoading(true);
      const result = await vitalsApi.getVitals(appointmentId);
      setVitals(result.data);
    } catch (error) {
      console.error('Failed to load vitals:', error);
    } finally {
      setLoading(false);
    }
  };
  
  const updateVitals = async (data: VitalSignsData) => {
    try {
      await vitalsApi.updateVitals(appointmentId, data);
      loadVitals(); // Reload data
      toast.success('Vitals updated successfully');
    } catch (error) {
      toast.error('Failed to update vitals');
    }
  };
  
  if (loading) return <LoadingSpinner />;
  
  return (
    <div className="vitals-tab">
      <VitalSignsForm 
        initialData={vitals}
        onSubmit={updateVitals}
      />
    </div>
  );
}

Medications Tab Component

// frontend/apps/cms/src/components/lhr-tabs/MedicationsTab.tsx

import { medicationRequestsApi } from '@kulcare/api-client';
import type { MedicationRequest } from '@kulcare/api-client/types';

export function MedicationsTab({ appointmentId }: { appointmentId: number }) {
  const [medications, setMedications] = useState<MedicationRequest[]>([]);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    loadMedications();
  }, [appointmentId]);
  
  const loadMedications = async () => {
    try {
      setLoading(true);
      const result = await medicationRequestsApi.getMedications(appointmentId);
      setMedications(result.data);
    } catch (error) {
      console.error('Failed to load medications:', error);
    } finally {
      setLoading(false);
    }
  };
  
  const addMedication = async (data: MedicationRequestData) => {
    try {
      await medicationRequestsApi.addMedication(appointmentId, data);
      loadMedications(); // Reload data
      toast.success('Medication added successfully');
    } catch (error) {
      toast.error('Failed to add medication');
    }
  };
  
  const searchMedications = async (search: string) => {
    try {
      const result = await medicationRequestsApi.searchMedications(search);
      return result.data;
    } catch (error) {
      console.error('Failed to search medications:', error);
      return [];
    }
  };
  
  return (
    <div className="medications-tab">
      <MedicationList medications={medications} />
      <AddMedicationForm 
        onSubmit={addMedication}
        onSearch={searchMedications}
      />
    </div>
  );
}

Step 2: Create New FHIR Resource Components

MedicationRequestList Component

// frontend/apps/cms/src/components/medication-requests/MedicationRequestList.tsx

import { medicationRequestsApi } from '@kulcare/api-client';
import type { MedicationRequest } from '@kulcare/api-client/types';

export function MedicationRequestList({ patientId }: { patientId: number }) {
  const [requests, setRequests] = useState<MedicationRequest[]>([]);
  
  useEffect(() => {
    loadMedicationRequests();
  }, [patientId]);
  
  const loadMedicationRequests = async () => {
    try {
      const result = await medicationRequestsApi.list({ patient_id: patientId });
      setRequests(result.data);
    } catch (error) {
      console.error('Failed to load medication requests:', error);
    }
  };
  
  const updateStatus = async (id: number, status: string) => {
    try {
      await medicationRequestsApi.updateStatus(id, status);
      loadMedicationRequests(); // Reload data
      toast.success('Status updated');
    } catch (error) {
      toast.error('Failed to update status');
    }
  };
  
  return (
    <div className="medication-requests-list">
      {requests.map(request => (
        <MedicationRequestCard 
          key={request.id}
          request={request}
          onStatusUpdate={updateStatus}
        />
      ))}
    </div>
  );
}

Step 3: Update LHR Components

Update PatientLHR Component

// frontend/apps/cms/src/components/patient/PatientLHR.tsx

import { lhrApi } from '@kulcare/api-client';
import type { PatientLongitudinalData } from '@kulcare/api-client/types';

export function PatientLHR({ patientId }: { patientId: number }) {
  const [lhrData, setLhrData] = useState<PatientLongitudinalData | null>(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    loadLHRData();
  }, [patientId]);
  
  const loadLHRData = async () => {
    try {
      setLoading(true);
      const result = await lhrApi.getPatientLHR(patientId);
      setLhrData(result.data);
    } catch (error) {
      console.error('Failed to load LHR data:', error);
      toast.error('Failed to load patient health record');
    } finally {
      setLoading(false);
    }
  };
  
  if (loading) return <LoadingSpinner />;
  if (!lhrData) return <div>No health record data available</div>;
  
  return (
    <div className="patient-lhr">
      <LHRSummary data={lhrData} />
      
      <div className="lhr-sections">
        <CurrentProblemsSection problems={lhrData.current_problems} />
        <CurrentMedicationsSection medications={lhrData.current_medications} />
        <EncounterHistorySection encounters={lhrData.encounter_history} />
        <HealthTrendsSection trends={lhrData.health_trends} />
      </div>
    </div>
  );
}

Phase 3: Routing Updates

Step 1: Update Route Definitions

New FHIR-Compliant Routes

// frontend/apps/cms/src/routes/index.tsx

const routes = [
  // Legacy route redirects (temporary backward compatibility)
  {
    path: '/patients/:patientId/careplans',
    element: <Navigate to="/cms/prescription/patient/:patientId" replace />,
  },
  
  // New FHIR LHR route structure
  {
    path: '/cms/prescription/patient/:patientId/:appointmentId',
    element: <LHRPrescriptionPage />,
    children: [
      { path: 'vitals', element: <VitalsTab /> },
      { path: 'observations', element: <ObservationsTab /> },
      { path: 'medications', element: <MedicationsTab /> },
      { path: 'investigations', element: <InvestigationsTab /> },
      { path: 'advice', element: <AdviceTab /> },
      { path: 'next-visit', element: <NextVisitTab /> },
    ],
  },
  
  // Patient overview with LHR integration
  {
    path: '/patients/:patientId/longitudinal-health-record',
    element: <PatientLHRPage />,
  },
];

LHR Prescription Page Component

// frontend/apps/cms/src/pages/LHRPrescriptionPage.tsx

import { Outlet, useParams } from 'react-router-dom';

export function LHRPrescriptionPage() {
  const { patientId, appointmentId } = useParams();
  
  return (
    <div className="lhr-prescription-page">
      <LHRHeader patientId={patientId} appointmentId={appointmentId} />
      
      <div className="lhr-tabs">
        <LHRTabNavigation />
        <div className="tab-content">
          <Outlet context={{ patientId, appointmentId }} />
        </div>
      </div>
    </div>
  );
}

Step 2: Update Navigation

Patient Menu Updates

// frontend/apps/cms/src/components/patient/PatientMenu.tsx

const menuItems = [
  {
    label: 'Overview',
    path: `/patients/${patientId}`,
    icon: <UserIcon />,
  },
  {
    label: 'Appointments', 
    path: `/patients/${patientId}/appointments`,
    icon: <CalendarIcon />,
  },
  // Update existing menu item to new LHR structure
  {
    label: 'Create Prescription',
    path: `/cms/prescription/patient/${patientId}/${appointmentId}`,
    icon: <PrescriptionIcon />,
  },
  // Add new menu items
  {
    label: 'Health Timeline',
    path: `/patients/${patientId}/longitudinal-health-record`,
    icon: <TimelineIcon />,
  },
];

LHR Tab Navigation

// frontend/apps/cms/src/components/lhr/LHRTabNavigation.tsx

const tabItems = [
  { key: 'vitals', label: 'Vitals', icon: <HeartIcon /> },
  { key: 'observations', label: 'Observations', icon: <ClipboardIcon /> },
  { key: 'medications', label: 'Medications', icon: <PillIcon /> },
  { key: 'investigations', label: 'Investigations', icon: <TestTubeIcon /> },
  { key: 'advice', label: 'Advice', icon: <MessageCircleIcon /> },
  { key: 'next-visit', label: 'Next Visit', icon: <CalendarPlusIcon /> },
];

export function LHRTabNavigation() {
  const { patientId, appointmentId } = useParams();
  const location = useLocation();
  
  return (
    <nav className="lhr-tab-navigation">
      {tabItems.map((tab) => (
        <Link
          key={tab.key}
          to={`/cms/prescription/patient/${patientId}/${appointmentId}/${tab.key}`}
          className={cn(
            'tab-link',
            location.pathname.endsWith(tab.key) && 'active'
          )}
        >
          {tab.icon}
          {tab.label}
        </Link>
      ))}
    </nav>
  );
}

Phase 4: State Management Updates

Step 1: Update Store Slices

Rename Careplan Store

// frontend/apps/cms/src/store/combined-prescriptions.ts (renamed from careplans.ts)

import { create } from 'zustand';
import { combinedPrescriptionsApi } from '@kulcare/api-client';
import type { Prescription } from '@kulcare/api-client/types';

interface PrescriptionStore {
  prescriptions: Prescription[];
  loading: boolean;
  error: string | null;
  
  // Actions
  loadPrescriptions: (patientId: number) => Promise<void>;
  createPrescription: (data: any) => Promise<void>;
  updatePrescription: (id: number, data: any) => Promise<void>;
  deletePrescription: (id: number) => Promise<void>;
}

export const usePrescriptionStore = create<PrescriptionStore>((set, get) => ({
  prescriptions: [],
  loading: false,
  error: null,
  
  loadPrescriptions: async (patientId: number) => {
    set({ loading: true, error: null });
    try {
      const result = await combinedPrescriptionsApi.list({ patient_id: patientId });
      set({ prescriptions: result.data, loading: false });
    } catch (error) {
      set({ error: 'Failed to load prescriptions', loading: false });
    }
  },
  
  createPrescription: async (data: any) => {
    try {
      const result = await combinedPrescriptionsApi.create(data);
      set(state => ({ 
        prescriptions: [result.data, ...state.prescriptions] 
      }));
    } catch (error) {
      set({ error: 'Failed to create prescription' });
    }
  },
}));

Create New FHIR Store

// frontend/apps/cms/src/store/medication-requests.ts

import { create } from 'zustand';
import { medicationRequestsApi } from '@kulcare/api-client';
import type { MedicationRequest } from '@kulcare/api-client/types';

interface MedicationRequestStore {
  requests: MedicationRequest[];
  loading: boolean;
  error: string | null;
  
  loadRequests: (patientId: number) => Promise<void>;
  updateStatus: (id: number, status: string) => Promise<void>;
  createRequest: (data: any) => Promise<void>;
}

export const useMedicationRequestStore = create<MedicationRequestStore>((set) => ({
  requests: [],
  loading: false,
  error: null,
  
  loadRequests: async (patientId: number) => {
    set({ loading: true, error: null });
    try {
      const result = await medicationRequestsApi.list({ patient_id: patientId });
      set({ requests: result.data, loading: false });
    } catch (error) {
      set({ error: 'Failed to load medication requests', loading: false });
    }
  },
  
  updateStatus: async (id: number, status: string) => {
    try {
      await medicationRequestsApi.updateStatus(id, status);
      set(state => ({
        requests: state.requests.map(req => 
          req.id === id ? { ...req, status } : req
        )
      }));
    } catch (error) {
      set({ error: 'Failed to update status' });
    }
  },
}));

Phase 5: Component Migration

Step 1: Update Existing Components

Files to Update:

  1. All components importing from @kulcare/api-client/careplans
  2. Components using Careplan type - change to Prescription
  3. Route links pointing to /careplans/ - change to /combined-prescriptions/

Search and Replace Operations:

# In frontend/apps/cms/src/
find . -name "*.tsx" -o -name "*.ts" | xargs sed -i '' 's/careplansApi/combinedPrescriptionsApi/g'
find . -name "*.tsx" -o -name "*.ts" | xargs sed -i '' 's/Careplan\b/Prescription/g'
find . -name "*.tsx" -o -name "*.ts" | xargs sed -i '' 's/careplan\b/combinedPrescription/g'
find . -name "*.tsx" -o -name "*.ts" | xargs sed -i '' 's/\/careplans\//\/combined-prescriptions\//g'

Step 2: Create New Component Files

Patient Problems Component

// frontend/apps/cms/src/components/patient-problems/PatientProblemsList.tsx

import { useEffect, useState } from 'react';
import { patientProblemsApi } from '@kulcare/api-client';
import type { PatientProblem } from '@kulcare/api-client/types';

export function PatientProblemsList({ patientId }: { patientId: number }) {
  const [problems, setProblems] = useState<PatientProblem[]>([]);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    loadProblems();
  }, [patientId]);
  
  const loadProblems = async () => {
    try {
      setLoading(true);
      const result = await patientProblemsApi.list({ patient_id: patientId });
      setProblems(result.data);
    } catch (error) {
      console.error('Failed to load patient problems:', error);
    } finally {
      setLoading(false);
    }
  };
  
  const updateProblemStatus = async (id: number, status: string) => {
    try {
      await patientProblemsApi.updateStatus(id, status);
      loadProblems(); // Reload
    } catch (error) {
      console.error('Failed to update problem status:', error);
    }
  };
  
  if (loading) return <div>Loading problems...</div>;
  
  return (
    <div className="patient-problems-list">
      <div className="problems-header">
        <h3>Active Problems</h3>
        <button onClick={() => setShowCreateForm(true)}>Add Problem</button>
      </div>
      
      <div className="problems-grid">
        {problems.filter(p => p.status === 'active').map(problem => (
          <PatientProblemCard 
            key={problem.id}
            problem={problem}
            onStatusUpdate={updateProblemStatus}
          />
        ))}
      </div>
    </div>
  );
}

Phase 6: Mock Data Updates

Step 1: Update Mock Services

Update Mock Data Structure

// frontend/packages/mock-data/src/longitudinal-mock.ts

import type { PatientLongitudinalData } from '@kulcare/api-client/types';

export const mockPatientLHR: PatientLongitudinalData = {
  patient: mockPatient,
  
  current_problems: [
    {
      id: 1,
      problem_name: 'Hypertension',
      status: 'active',
      severity: 'moderate',
      onset_date: '2024-01-15',
      // ... other fields
    },
  ],
  
  current_medications: [
    {
      id: 1,
      medicine_name: 'Amlodipine',
      dosage: '5mg',
      frequency: 'Once daily',
      active: true,
      // ... other fields  
    },
  ],
  
  encounter_history: [
    {
      id: 1,
      appointment_date: '2024-12-01',
      diagnosis: ['Hypertension follow-up'],
      observations: 'Blood pressure well controlled',
      // ... other fields
    },
  ],
  
  medication_history: [
    {
      id: 1,
      medication_name: 'Amlodipine',
      status: 'active',
      dosage_instruction: '5mg once daily',
      created_at: '2024-12-01T10:00:00Z',
      // ... other fields
    },
  ],
  
  health_trends: {
    vitals_timeline: mockVitalsTrends,
    weight_timeline: mockWeightTrends,
    medication_adherence: mockMedicationAdherence,
    problem_timeline: mockProblemTrends,
  },
};

Phase 7: Testing Updates

Step 1: Update Component Tests

Update Test Imports

// frontend/apps/cms/src/components/combined-prescriptions/__tests__/PrescriptionForm.test.tsx

// Update imports
import { combinedPrescriptionsApi } from '@kulcare/api-client';
import type { Prescription } from '@kulcare/api-client/types';

// Update mock API calls
jest.mock('@kulcare/api-client', () => ({
  combinedPrescriptionsApi: {
    create: jest.fn(),
    list: jest.fn(),
    update: jest.fn(),
  },
}));

describe('PrescriptionForm', () => {
  it('creates combined prescription successfully', async () => {
    const mockCreate = combinedPrescriptionsApi.create as jest.Mock;
    mockCreate.mockResolvedValue({ data: mockPrescription });
    
    // ... test implementation
  });
});

Step 2: Create New Component Tests

Test New LHR Components

// frontend/apps/cms/src/components/patient/__tests__/PatientLHR.test.tsx

import { render, screen, waitFor } from '@testing-library/react';
import { PatientLHR } from '../PatientLHR';
import { lhrApi } from '@kulcare/api-client';

jest.mock('@kulcare/api-client');

describe('PatientLHR', () => {
  it('loads and displays longitudinal health record', async () => {
    const mockGetLHR = lhrApi.getPatientLHR as jest.Mock;
    mockGetLHR.mockResolvedValue({ data: mockPatientLHR });
    
    render(<PatientLHR patientId={123} />);
    
    await waitFor(() => {
      expect(screen.getByText('Current Problems')).toBeInTheDocument();
      expect(screen.getByText('Current Medications')).toBeInTheDocument();
    });
  });
});

Migration Checklist

Backend Verification (Completed ✅)

  • Database table renamed: careplans → prescriptions
  • Column renamed: encounter_id → appointment_id across all FHIR tables
  • Polymorphic types updated: 'Careplan' → 'Prescription'
  • Event tracking updated: 'careplan.create' → 'prescription.create'
  • Model associations updated: .careplans → .prescriptions
  • API endpoints created for all 6 LHR tabs

API Layer

  • Create new API client files for each tab:
    • vitals.ts - For Vitals tab
    • clinical-impressions.ts - For Observations tab
    • medication-requests.ts - For Medications tab
    • service-requests.ts - For Investigations tab
    • care-plans.ts - For Advice and Next Visit tabs
  • Update type definitions to use appointment_id instead of encounter_id
  • Test all new API endpoints with Postman collection
  • Update API client index exports

Component Layer

  • Create new LHR tab components:
    • VitalsTab.tsx
    • ObservationsTab.tsx (Clinical Impressions)
    • MedicationsTab.tsx
    • InvestigationsTab.tsx (Service Requests)
    • AdviceTab.tsx
    • NextVisitTab.tsx
  • Create LHRPrescriptionPage.tsx parent component
  • Create LHRTabNavigation.tsx component
  • Update existing careplan components to use new prescription APIs

Routing

  • Implement new route structure: /cms/prescription/patient/:patientId/:appointmentId
  • Add child routes for each tab (vitals, observations, medications, etc.)
  • Add redirect routes for backward compatibility
  • Update navigation links throughout the app
  • Test route transitions and deep linking

State Management

  • Create new Zustand stores for each LHR resource:
    • vitals-store.ts
    • clinical-impressions-store.ts
    • medication-requests-store.ts
    • service-requests-store.ts
    • care-plans-store.ts
  • Update React Query keys for new endpoints
  • Implement real-time updates with Ably for LHR changes
  • Test state management flows between tabs

Mock Data & Development

  • Update mock data to use appointment_id instead of encounter_id
  • Create mock responses for all new FHIR endpoints
  • Update development seed data
  • Create development environment variables for LHR feature flags

Testing

  • Update existing careplan-related tests
  • Create tests for new LHR tab components
  • Create integration tests for tab navigation
  • Update API mock configurations
  • Test error handling and loading states
  • Run full test suite and ensure no regressions

Documentation & Training

  • Update component documentation and Storybook stories
  • Create usage examples for new LHR components
  • Update developer onboarding documentation
  • Create user guide for new LHR interface
  • Document API endpoints and payload structures

Deployment Strategy

Development Environment

  1. Deploy backend changes first
  2. Test legacy API endpoints still work
  3. Deploy frontend changes with feature flags
  4. Gradually enable new features per user group

Production Environment

  1. Run database migrations during maintenance window
  2. Deploy backend with backward compatibility
  3. Deploy frontend with legacy route redirects
  4. Monitor error rates and rollback if needed
  5. Remove legacy support after successful transition

Monitoring

  • Track API usage patterns for old vs new endpoints
  • Monitor error rates during transition
  • Set up alerts for FHIR resource creation failures
  • Track document generation success rates

On this page