Skip to main content
Glama
plandefinitionapply.ts11.4 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import type { ProfileResource, WithId } from '@medplum/core'; import { allOk, concatUrls, createReference, evalFhirPathTyped, getExtension, getReferenceString, Operator, toTypedValue, } from '@medplum/core'; import type { FhirRequest, FhirResponse } from '@medplum/fhir-router'; import type { ActivityDefinition, Bot, CarePlan, ClientApplication, CodeableConcept, Encounter, Organization, Patient, PlanDefinition, PlanDefinitionAction, Practitioner, Questionnaire, Reference, RequestGroup, RequestGroupAction, Resource, ServiceRequest, Task, TaskInput, } from '@medplum/fhirtypes'; import { getConfig } from '../../config/loader'; import { getAuthenticatedContext } from '../../context'; import type { Repository } from '../repo'; import { getOperationDefinition } from './definitions'; import { parseInputParameters } from './utils/parameters'; const TASK_ELEMENTS_URL = 'https://medplum.com/fhir/StructureDefinition/task-elements'; const operation = getOperationDefinition('PlanDefinition', 'apply'); interface PlanDefinitionApplyParameters { readonly subject: string[]; readonly encounter?: string; readonly practitioner?: string; readonly organization?: string; } /** * Handles a PlanDefinition $apply request. * * The operation converts a PlanDefinition to a RequestGroup. * * See: https://hl7.org/fhir/plandefinition-operation-apply.html * @param req - The FHIR request. * @returns The FHIR response. */ export async function planDefinitionApplyHandler(req: FhirRequest): Promise<FhirResponse> { const ctx = getAuthenticatedContext(); const { id } = req.params; const planDefinition = await ctx.repo.readResource<PlanDefinition>('PlanDefinition', id); const params = parseInputParameters<PlanDefinitionApplyParameters>(operation, req); const subject = await ctx.repo.readReference<Patient>({ reference: params.subject[0] }); const subjectRef = createReference(subject); let encounterRef: Reference<Encounter> | undefined = undefined; if (params.encounter) { const encounter = await ctx.repo.readReference<Encounter>({ reference: params.encounter }); encounterRef = createReference(encounter); } let practitionerRef: Reference<Practitioner> | undefined = undefined; if (params.practitioner) { const practitioner = await ctx.repo.readReference<Practitioner>({ reference: params.practitioner }); practitionerRef = createReference(practitioner); } let organizationRef: Reference<Organization> | undefined = undefined; if (params.organization) { const organization = await ctx.repo.readReference<Organization>({ reference: params.organization }); organizationRef = createReference(organization); } const actions: RequestGroupAction[] = []; if (planDefinition.action) { for (const action of planDefinition.action) { actions.push( await createAction( ctx.repo, planDefinition, ctx.profile, subjectRef, action, encounterRef, practitionerRef, organizationRef ) ); } } const requestGroup = await ctx.repo.createResource<RequestGroup>({ resourceType: 'RequestGroup', instantiatesCanonical: planDefinition.url ? [planDefinition.url] : undefined, instantiatesUri: !planDefinition.url ? [concatUrls(getConfig().baseUrl, getReferenceString(planDefinition))] : undefined, subject: subjectRef, status: 'active', intent: 'order', action: actions, encounter: encounterRef, }); const carePlan = await ctx.repo.createResource<CarePlan>({ resourceType: 'CarePlan', status: 'active', subject: subjectRef, intent: 'plan', instantiatesCanonical: planDefinition.url ? [planDefinition.url] : undefined, instantiatesUri: !planDefinition.url ? [concatUrls(getConfig().baseUrl, getReferenceString(planDefinition))] : undefined, activity: [{ reference: createReference(requestGroup) }], }); return [allOk, carePlan]; } /** * Creates a Task and RequestGroup action for the given PlanDefinition action. * @param repo - The repository configured for the current user. * @param planDefinition - The plan definition. * @param requester - The user who requested the plan definition. * @param subject - The subject of the plan definition. * @param action - The PlanDefinition action. * @param encounter - Optional encounter reference. * @param practitioner - Optional practitioner reference. * @param organization - Optional organization reference. * @returns The RequestGroup action. */ async function createAction( repo: Repository, planDefinition: PlanDefinition, requester: Reference<ProfileResource | Bot | ClientApplication>, subject: Reference<Patient>, action: PlanDefinitionAction, encounter?: Reference<Encounter>, practitioner?: Reference<Practitioner>, organization?: Reference<Organization> ): Promise<RequestGroupAction> { let definition: WithId<Resource> | undefined; if (action.definitionCanonical) { definition = await readCanonical(repo, action.definitionCanonical); } switch (definition?.resourceType) { case 'Questionnaire': return createTask(repo, planDefinition, requester, subject, action, encounter, undefined, undefined, [ { type: { text: 'Questionnaire' }, valueReference: { display: action.title, reference: getReferenceString(definition), }, }, ]); case 'ActivityDefinition': return createActivityDefinitionTask( repo, planDefinition, requester, subject, action, definition, encounter, practitioner, organization ); default: return createTask(repo, planDefinition, requester, subject, action, encounter); } } /** * Creates a Task and RequestGroup action to request a resource. * @param repo - The repository configured for the current user. * @param planDefinition - The plan definition. * @param requester - The user who requested the plan definition. * @param subject - The subject of the plan definition. * @param action - The PlanDefinition action. * @param activityDefinition - The resolved ActivityDefinition resource. * @param encounter - Optional encounter reference. * @param practitioner - Optional practitioner reference. * @param organization - Optional organization reference. * @returns The RequestGroup action. */ async function createActivityDefinitionTask( repo: Repository, planDefinition: PlanDefinition, requester: Reference<Bot | ClientApplication | ProfileResource>, subject: Reference<Patient>, action: PlanDefinitionAction, activityDefinition: WithId<ActivityDefinition>, encounter: Reference<Encounter> | undefined, practitioner?: Reference<Practitioner>, organization?: Reference<Organization> ): Promise<RequestGroupAction> { const parameters = { practitioner: practitioner, organization: organization, subject: subject, }; const taskElementsExtension = getExtension(activityDefinition, TASK_ELEMENTS_URL); const ownerExtension = getExtension(taskElementsExtension, 'owner'); let owner: Reference<Practitioner | Organization> | undefined = undefined; if (ownerExtension) { const expression = ownerExtension.valueExpression?.expression; if (expression) { const parametersValue = toTypedValue(parameters); const result = evalFhirPathTyped(expression, [parametersValue], { '%practitioner': toTypedValue(practitioner), '%organization': toTypedValue(organization), '%subject': toTypedValue(subject), }); if (result.length !== 0) { const resultValue = result[0].value; if (resultValue && typeof resultValue === 'object' && 'reference' in resultValue) { owner = resultValue as Reference<Practitioner | Organization>; } } } } const performerTypeExtension = getExtension(taskElementsExtension, 'performerType'); const performerType: CodeableConcept | undefined = performerTypeExtension ? performerTypeExtension.valueCodeableConcept : undefined; switch (activityDefinition.kind) { case 'ServiceRequest': { const serviceRequest = await repo.createResource({ resourceType: 'ServiceRequest', status: 'draft', intent: activityDefinition.intent as ServiceRequest['intent'], subject: subject, requester: requester as ServiceRequest['requester'], encounter: encounter, code: activityDefinition.code, extension: activityDefinition.extension, }); return createTask(repo, planDefinition, requester, subject, action, encounter, owner, performerType, [ { type: { text: 'ServiceRequest', }, valueReference: { display: action.title, reference: getReferenceString(serviceRequest), }, }, ]); } default: return createTask(repo, planDefinition, requester, subject, action, encounter, owner, performerType); } } /** * Creates a Task and RequestGroup action for a PlanDefinition action. * @param repo - The repository configured for the current user. * @param planDefinition - The plan definition. * @param requester - The requester profile. * @param subject - The subject of the plan definition. * @param action - The PlanDefinition action. * @param encounter - Optional encounter reference. * @param owner - Optional owner reference. * @param performerType - Optional performer type. * @param input - Optional input details. * @returns The RequestGroup action. */ async function createTask( repo: Repository, planDefinition: PlanDefinition, requester: Reference<ProfileResource | Bot | ClientApplication>, subject: Reference<Patient>, action: PlanDefinitionAction, encounter?: Reference<Encounter>, owner?: Reference<Practitioner | Organization>, performerType?: CodeableConcept, input?: TaskInput[] ): Promise<RequestGroupAction> { const task = await repo.createResource<Task>({ resourceType: 'Task', code: { text: action.title, }, performerType: performerType ? [performerType] : undefined, intent: 'order', status: 'requested', authoredOn: new Date().toISOString(), requester: requester as Task['requester'], for: subject, encounter: encounter, owner: owner, description: action.description, focus: input?.[0]?.valueReference, input, basedOn: [ { reference: getReferenceString(planDefinition), display: planDefinition.title, }, ], }); return { title: action.title, resource: createReference(task), }; } async function readCanonical<T extends Questionnaire | ActivityDefinition | PlanDefinition>( repo: Repository, canonical: string ): Promise<WithId<T> | undefined> { if (!canonical) { return undefined; } return repo.searchOne({ resourceType: 'ActivityDefinition', types: ['ActivityDefinition', 'PlanDefinition', 'Questionnaire'], filters: [{ code: 'url', operator: Operator.EQUALS, value: canonical }], }); }

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/medplum/medplum'

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