Skip to main content
Glama
episodeOfCareUtils.ts12.7 kB
import { EpisodeOfCare, // EpisodeOfCareStatus, // This type does not exist as a named export CodeableConcept, Identifier, Reference, Period, Patient, Organization, Practitioner, Condition, OperationOutcome, } from '@medplum/fhirtypes'; import { medplum, MedplumClient, ensureAuthenticated } from '../config/medplumClient'; // Define EpisodeOfCareStatus based on FHIR R4 valueset export type EpisodeOfCareStatus = | 'planned' | 'waitlist' | 'active' | 'onhold' | 'finished' | 'cancelled' | 'entered-in-error'; export interface CreateEpisodeOfCareArgs { patientId: string; status: EpisodeOfCareStatus; // planned | waitlist | active | onhold | finished | cancelled | entered-in-error managingOrganizationId?: string; type?: CodeableConcept[]; // e.g., [{ coding: [{ system: 'http://terminology.hl7.org/CodeSystem/episodeofcare-type', code: 'hacc' }]}] (Home and Community Care) periodStart?: string; // ISO8601 DateTime periodEnd?: string; // ISO8601 DateTime careManagerId?: string; // ID of the Practitioner teamMemberIds?: string[]; // IDs of CareTeam resources (more complex, consider if needed for initial version) identifier?: Identifier[]; // diagnosis related fields are complex (condition, role, rank) - might simplify or add later } export interface UpdateEpisodeOfCareArgs { status?: EpisodeOfCareStatus; type?: CodeableConcept[]; periodStart?: string; periodEnd?: string; managingOrganizationId?: string; careManagerId?: string; // Consider how to handle diagnosis updates - potentially replacing all, or adding/removing specific ones. } export interface EpisodeOfCareSearchArgs { patient?: string; // Patient ID, e.g., "Patient/123" or just "123" status?: EpisodeOfCareStatus | string; // Can be a single status or comma-separated for multiple e.g. "active,onhold" type?: string; // Token search for type (e.g., system|code or just code) date?: string | string[]; // Date range for period (e.g., "ge2023-01-01&le2023-12-31" or ["ge2023-01-01", "le2023-12-31"]) identifier?: string; // Identifier for the episode of care organization?: string; // <<< RENAMED from 'managing-organization' 'care-manager'?: string; // Practitioner ID, e.g., "Practitioner/123" or just "123" } /** * Creates a new EpisodeOfCare resource. */ export async function createEpisodeOfCare( args: CreateEpisodeOfCareArgs, client?: MedplumClient, ): Promise<EpisodeOfCare | OperationOutcome> { const medplumClient = client || medplum; await ensureAuthenticated(); try { if (!args.patientId) { throw new Error('Patient ID is required to create an EpisodeOfCare.'); } if (!args.status) { throw new Error('Status is required to create an EpisodeOfCare.'); } const episode: EpisodeOfCare = { resourceType: 'EpisodeOfCare', status: args.status, patient: { reference: `Patient/${args.patientId}` }, type: args.type, identifier: args.identifier, period: args.periodStart || args.periodEnd ? { start: args.periodStart, end: args.periodEnd, } : undefined, managingOrganization: args.managingOrganizationId ? { reference: `Organization/${args.managingOrganizationId}` } : undefined, careManager: args.careManagerId ? { reference: `Practitioner/${args.careManagerId}` } : undefined, // team: args.teamMemberIds?.map(id => ({ reference: `CareTeam/${id}` })) // If using CareTeam IDs }; // Remove undefined top-level fields to keep the resource clean Object.keys(episode).forEach((key) => (episode as any)[key] === undefined && delete (episode as any)[key]); if (episode.period && !episode.period.start && !episode.period.end) { delete episode.period; } const result = (await medplumClient.createResource(episode)) as EpisodeOfCare; console.log('EpisodeOfCare created successfully:', result.id); return result; } catch (error: any) { const outcome: OperationOutcome = { resourceType: 'OperationOutcome', issue: [ { severity: 'error', code: 'exception', diagnostics: `Error creating EpisodeOfCare: ${error.message || 'Unknown error'}`, }, ], }; if (error.outcome) { return error.outcome as OperationOutcome; } return outcome; } } /** * Retrieves an EpisodeOfCare resource by its ID. */ export async function getEpisodeOfCareById( episodeOfCareId: string, client?: MedplumClient, ): Promise<EpisodeOfCare | null | OperationOutcome> { const medplumClient = client || medplum; await ensureAuthenticated(); try { if (!episodeOfCareId) { throw new Error('EpisodeOfCare ID is required.'); } const result = (await medplumClient.readResource( 'EpisodeOfCare', episodeOfCareId, )) as EpisodeOfCare | null; console.log(result ? 'EpisodeOfCare retrieved:' : 'EpisodeOfCare not found:', episodeOfCareId); return result; } catch (error: any) { if (error.outcome && error.outcome.issue && error.outcome.issue[0]?.code === 'not-found') { console.log(`EpisodeOfCare with ID "${episodeOfCareId}" not found.`); return null; } const outcome: OperationOutcome = { resourceType: 'OperationOutcome', issue: [ { severity: 'error', code: 'exception', diagnostics: `Error retrieving EpisodeOfCare: ${error.message || 'Unknown error'}`, }, ], }; if (error.outcome) { return error.outcome as OperationOutcome; } return outcome; } } /** * Updates an existing EpisodeOfCare resource. */ export async function updateEpisodeOfCare( episodeOfCareId: string, updates: UpdateEpisodeOfCareArgs, client?: MedplumClient, ): Promise<EpisodeOfCare | OperationOutcome> { const medplumClient = client || medplum; await ensureAuthenticated(); try { if (!episodeOfCareId) { throw new Error('EpisodeOfCare ID is required for update.'); } if (Object.keys(updates).length === 0) { throw new Error('No updates provided for EpisodeOfCare.'); } // Fetch the existing resource const existingEpisode = await medplumClient.readResource('EpisodeOfCare', episodeOfCareId); if (!existingEpisode) { // This case should ideally be handled by readResource throwing a not-found error // which would be caught and returned as OperationOutcome or null by getEpisodeOfCareById logic // For robustness, if it somehow returns null here: return { resourceType: 'OperationOutcome', issue: [ { severity: 'error', code: 'not-found', diagnostics: `EpisodeOfCare with ID "${episodeOfCareId}" not found for update.`, }, ], }; } // Construct the updated resource. Be careful with deep merges or partial updates. // A common strategy for utilities is to merge top-level fields or replace arrays. const resourceToUpdate: EpisodeOfCare = { ...existingEpisode, resourceType: 'EpisodeOfCare', // Ensure resourceType is maintained id: episodeOfCareId, // Ensure ID is maintained }; if (updates.status) resourceToUpdate.status = updates.status; if (updates.type) resourceToUpdate.type = updates.type; // Replace array if (updates.managingOrganizationId) { resourceToUpdate.managingOrganization = { reference: `Organization/${updates.managingOrganizationId}` }; } else if (updates.hasOwnProperty('managingOrganizationId') && updates.managingOrganizationId === null) { delete resourceToUpdate.managingOrganization; // Allow unsetting } if (updates.careManagerId) { resourceToUpdate.careManager = { reference: `Practitioner/${updates.careManagerId}` }; } else if (updates.hasOwnProperty('careManagerId') && updates.careManagerId === null) { delete resourceToUpdate.careManager; // Allow unsetting } // Handle period update let periodUpdated = false; const currentPeriod = resourceToUpdate.period || {}; const newPeriod: Period = { ...currentPeriod }; if (updates.hasOwnProperty('periodStart')) { newPeriod.start = updates.periodStart; // Allow null/undefined to clear periodUpdated = true; } if (updates.hasOwnProperty('periodEnd')) { newPeriod.end = updates.periodEnd; // Allow null/undefined to clear periodUpdated = true; } if(periodUpdated){ if(newPeriod.start || newPeriod.end){ resourceToUpdate.period = newPeriod; } else { delete resourceToUpdate.period; } } // Remove undefined top-level fields from the merged object that might have been introduced by spread Object.keys(resourceToUpdate).forEach( (key) => (resourceToUpdate as any)[key] === undefined && delete (resourceToUpdate as any)[key] ); const result = (await medplumClient.updateResource(resourceToUpdate)) as EpisodeOfCare; console.log('EpisodeOfCare updated successfully:', result.id); return result; } catch (error: any) { if (error.outcome && error.outcome.issue && error.outcome.issue[0]?.code === 'not-found') { return { resourceType: 'OperationOutcome', issue: [ { severity: 'error', code: 'not-found', diagnostics: `EpisodeOfCare with ID "${episodeOfCareId}" not found for update.`, }, ], }; } const outcome: OperationOutcome = { resourceType: 'OperationOutcome', issue: [ { severity: 'error', code: 'exception', diagnostics: `Error updating EpisodeOfCare: ${error.message || 'Unknown error'}`, }, ], }; if (error.outcome) { return error.outcome as OperationOutcome; } return outcome; } } /** * Searches for EpisodeOfCare resources based on specified criteria. */ export async function searchEpisodesOfCare( args: EpisodeOfCareSearchArgs, client?: MedplumClient, ): Promise<EpisodeOfCare[] | OperationOutcome> { const medplumClient = client || medplum; await ensureAuthenticated(); try { const searchCriteria: string[] = []; let hasCriteria = false; Object.entries(args).forEach(([key, value]) => { if (value !== undefined && value !== null && value !== '') { // Standard FHIR search parameters for EpisodeOfCare: // patient, status, type, date, identifier, organization, care-manager if (key === 'patient' || key === 'status' || key === 'type' || key === 'identifier' || key === 'organization' || key === 'care-manager') { searchCriteria.push(`${key}=${encodeURIComponent(String(value))}`); hasCriteria = true; } else if (key === 'date') { if (Array.isArray(value)) { value.forEach(d => { searchCriteria.push(`date=${encodeURIComponent(d)}`); }); } else { // Handle single date string, which might contain '&' for ranges not split by client // This logic splits a single string like 'ge2023-01-01&le2023-12-31' into two params const dateParams = String(value).split('&'); dateParams.forEach(dp => { searchCriteria.push(`date=${encodeURIComponent(dp)}`); }); } hasCriteria = true; } else { console.warn(`Unsupported search parameter for EpisodeOfCare: ${key}`); } } }); if (!hasCriteria) { return { resourceType: 'OperationOutcome', issue: [ { severity: 'error', code: 'invalid', diagnostics: 'At least one search criterion must be provided for searching EpisodeOfCare.', }, ], }; } const query = searchCriteria.join('&'); console.log('Searching EpisodesOfCare with query:', query); const result = (await medplumClient.searchResources('EpisodeOfCare', query)) as EpisodeOfCare[]; console.log(`Found ${result.length} EpisodesOfCare.`); return result; } catch (error: any) { console.error('Error searching EpisodesOfCare:', error); const outcome: OperationOutcome = { resourceType: 'OperationOutcome', issue: [ { severity: 'error', code: 'exception', diagnostics: `Error searching EpisodesOfCare: ${error.message || 'Unknown error'}`, }, ], }; if (error.outcome) { return error.outcome as OperationOutcome; } return outcome; } }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/rkirkendall/medplum-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server