Migrating to Medict
A comprehensive guide to migrating your existing healthcare application to Medict.
Migration Overview
Migrating to Medict involves moving your healthcare data and workflows to a FHIR-compliant platform. This guide covers common migration scenarios and best practices.
Pre-Migration Planning
1. Data Assessment
Before migrating, assess your current data:
- Data Volume: Count records by type (patients, encounters, observations)
- Data Quality: Identify missing or invalid data
- Data Relationships: Map how different data types relate to each other
- Custom Fields: Document any non-standard fields
2. FHIR Mapping
Map your existing data model to FHIR resources:
// Example mapping from legacy system to FHIR
const legacyToFhirMapping = {
'patient_id': 'Patient.id',
'first_name': 'Patient.name[0].given[0]',
'last_name': 'Patient.name[0].family',
'date_of_birth': 'Patient.birthDate',
'email': 'Patient.telecom[0].value',
'phone': 'Patient.telecom[1].value'
};
3. Migration Strategy
Choose your migration approach:
- Big Bang: Migrate everything at once
- Phased: Migrate in stages by module or data type
- Parallel: Run both systems simultaneously
- Gradual: Migrate users incrementally
Data Migration
Patient Data Migration
import { MedictClient } from '@medplum/core';
async function migratePatients(legacyPatients) {
const client = new MedictClient({
baseUrl: 'https://api.medplum.com',
clientId: 'your-client-id'
});
const results = [];
for (const legacyPatient of legacyPatients) {
try {
const fhirPatient = {
resourceType: 'Patient',
identifier: [{
use: 'usual',
value: legacyPatient.patient_id
}],
name: [{
use: 'official',
family: legacyPatient.last_name,
given: [legacyPatient.first_name]
}],
birthDate: legacyPatient.date_of_birth,
gender: legacyPatient.gender,
telecom: [
{
system: 'email',
value: legacyPatient.email,
use: 'home'
},
{
system: 'phone',
value: legacyPatient.phone,
use: 'home'
}
]
};
const createdPatient = await client.createResource(fhirPatient);
results.push({
legacyId: legacyPatient.patient_id,
fhirId: createdPatient.id,
status: 'success'
});
} catch (error) {
results.push({
legacyId: legacyPatient.patient_id,
status: 'error',
error: error.message
});
}
}
return results;
}
Clinical Data Migration
async function migrateObservations(legacyObservations, patientMapping) {
const results = [];
for (const obs of legacyObservations) {
const fhirObservation = {
resourceType: 'Observation',
status: 'final',
category: [{
coding: [{
system: 'http://terminology.hl7.org/CodeSystem/observation-category',
code: obs.category_code
}]
}],
code: {
coding: [{
system: obs.code_system,
code: obs.code,
display: obs.display_name
}]
},
subject: {
reference: `Patient/${patientMapping[obs.patient_id]}`
},
effectiveDateTime: obs.observation_date,
valueQuantity: {
value: obs.value,
unit: obs.unit
}
};
try {
const created = await client.createResource(fhirObservation);
results.push({ legacyId: obs.id, fhirId: created.id, status: 'success' });
} catch (error) {
results.push({ legacyId: obs.id, status: 'error', error: error.message });
}
}
return results;
}
Application Migration
API Integration
Replace your existing API calls with Medict SDK:
// Before: Custom API
const response = await fetch('/api/patients', {
method: 'GET',
headers: { 'Authorization': `Bearer ${token}` }
});
const patients = await response.json();
// After: Medict SDK
const patients = await medplum.searchResources('Patient', {
name: 'Smith'
});
Authentication Migration
// Before: Custom auth
const token = localStorage.getItem('authToken');
const isAuthenticated = !!token;
// After: Medict auth
import { useMedict } from '@medplum/react';
function App() {
const medplum = useMedict();
const isAuthenticated = medplum.getActiveLogin() !== undefined;
return (
<div>
{isAuthenticated ? <Dashboard /> : <Login />}
</div>
);
}
UI Component Migration
// Before: Custom patient card
function PatientCard({ patient }) {
return (
<div className="patient-card">
<h3>{patient.firstName} {patient.lastName}</h3>
<p>DOB: {patient.dateOfBirth}</p>
</div>
);
}
// After: Medict patient card
import { PatientCard } from '@medplum/react';
function PatientCard({ patientId }) {
return <PatientCard patientId={patientId} />;
}
Testing Your Migration
Data Validation
async function validateMigration(patientMapping) {
const validationResults = [];
for (const [legacyId, fhirId] of Object.entries(patientMapping)) {
try {
const fhirPatient = await client.readResource('Patient', fhirId);
const legacyPatient = await getLegacyPatient(legacyId);
const validation = {
legacyId,
fhirId,
nameMatch: fhirPatient.name?.[0]?.family === legacyPatient.last_name,
dobMatch: fhirPatient.birthDate === legacyPatient.date_of_birth,
genderMatch: fhirPatient.gender === legacyPatient.gender
};
validationResults.push(validation);
} catch (error) {
validationResults.push({
legacyId,
fhirId,
error: error.message
});
}
}
return validationResults;
}
Performance Testing
async function performanceTest() {
const startTime = Date.now();
// Test search performance
const searchResults = await client.searchResources('Patient', {
name: 'Smith'
});
const searchTime = Date.now() - startTime;
// Test create performance
const createStart = Date.now();
await client.createResource({
resourceType: 'Patient',
name: [{ family: 'Test', given: ['Performance'] }]
});
const createTime = Date.now() - createStart;
return {
searchTime,
createTime,
searchCount: searchResults.length
};
}
Common Migration Challenges
1. Data Format Differences
Problem: Legacy system uses different date formats Solution: Convert dates to ISO 8601 format
function convertDate(legacyDate) {
// Convert MM/DD/YYYY to YYYY-MM-DD
const [month, day, year] = legacyDate.split('/');
return `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`;
}
2. Missing Required Fields
Problem: Legacy data missing FHIR required fields Solution: Provide default values
function ensureRequiredFields(patient) {
return {
...patient,
resourceType: 'Patient',
name: patient.name || [{ use: 'official', family: 'Unknown' }],
gender: patient.gender || 'unknown'
};
}
3. Large Dataset Migration
Problem: Migrating millions of records Solution: Use batch processing and pagination
async function migrateLargeDataset(records, batchSize = 100) {
const results = [];
for (let i = 0; i < records.length; i += batchSize) {
const batch = records.slice(i, i + batchSize);
const batchResults = await Promise.allSettled(
batch.map(record => migrateRecord(record))
);
results.push(...batchResults);
// Add delay to avoid rate limiting
await new Promise(resolve => setTimeout(resolve, 1000));
}
return results;
}
Post-Migration Checklist
- [ ] Verify all data migrated successfully
- [ ] Test all application functionality
- [ ] Update user documentation
- [ ] Train staff on new system
- [ ] Set up monitoring and alerts
- [ ] Plan legacy system decommissioning
- [ ] Update integrations and APIs
- [ ] Test backup and recovery procedures
Migration Support
Need help with your migration? Our team can assist with:
- Migration planning and strategy
- Data mapping and transformation
- Custom migration scripts
- Performance optimization
- Testing and validation
Contact our migration team for personalized assistance.
Next Steps
- Learn about FHIR Basics
- Explore Authentication
- Understand Data Management
- Review Best Practices