Skip to main content
Glama
details.test.ts16 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { SEARCH_PARAMETER_BUNDLE_FILES, readJson } from '@medplum/definitions'; import type { Bundle, BundleEntry, ResourceType, SearchParameter } from '@medplum/fhirtypes'; import { globalSchema, indexSearchParameterBundle } from '../types'; import { indexStructureDefinitionBundle } from '../typeschema/types'; import { deriveIdentifierSearchParameter } from './derived'; import { SearchParameterType, getSearchParameterDetails } from './details'; const searchParams: SearchParameter[] = []; const searchParameterBundles: Bundle<SearchParameter>[] = []; for (const filename of SEARCH_PARAMETER_BUNDLE_FILES) { const bundle = readJson(filename) as Bundle<SearchParameter>; searchParameterBundles.push(bundle); for (const entry of bundle.entry as BundleEntry[]) { searchParams.push(entry.resource as SearchParameter); } } describe('SearchParameterDetails', () => { beforeAll(() => { indexStructureDefinitionBundle(readJson('fhir/r4/profiles-types.json') as Bundle); indexStructureDefinitionBundle(readJson('fhir/r4/profiles-resources.json') as Bundle); indexStructureDefinitionBundle(readJson('fhir/r4/profiles-medplum.json') as Bundle); for (const bundle of searchParameterBundles) { indexSearchParameterBundle(bundle); } }); test('Get details', () => { // expression: 'Patient.name | Person.name | Practitioner.name | RelatedPerson.name' const individualPhoneticParam = searchParams.find((e) => e.id === 'individual-phonetic') as SearchParameter; const details = getSearchParameterDetails('Patient', individualPhoneticParam); expect(details).toBeDefined(); expect(details.array).toStrictEqual(true); }); test('Boolean param', () => { // expression: 'Patient.active' const activeParam = searchParams.find((e) => e.id === 'Patient-active') as SearchParameter; const details = getSearchParameterDetails('Patient', activeParam); expect(details).toBeDefined(); expect(details.type).toStrictEqual(SearchParameterType.BOOLEAN); expect(details.array).toStrictEqual(false); }); test('Date param', () => { // expression: 'Patient.birthDate' const birthDateParam = searchParams.find((e) => e.id === 'individual-birthdate') as SearchParameter; const details = getSearchParameterDetails('Patient', birthDateParam); expect(details).toBeDefined(); expect(details.type).toStrictEqual(SearchParameterType.DATE); expect(details.array).toStrictEqual(false); }); test('Date/Time param', () => { // expression: 'ServiceRequest.authoredOn' const authoredParam = searchParams.find((e) => e.id === 'ServiceRequest-authored') as SearchParameter; const details = getSearchParameterDetails('ServiceRequest', authoredParam); expect(details).toBeDefined(); expect(details.type).toStrictEqual(SearchParameterType.DATETIME); expect(details.array).toStrictEqual(false); }); test('Get nested details', () => { // expression: 'Patient.link.other' const missingExpressionParam = searchParams.find((e) => e.id === 'Patient-link') as SearchParameter; const details = getSearchParameterDetails('Patient', missingExpressionParam); expect(details).toBeDefined(); expect(details.type).toStrictEqual(SearchParameterType.REFERENCE); }); test('Missing expression for resource type', () => { const missingExpressionParam: SearchParameter = { resourceType: 'SearchParameter', code: 'test', type: 'string', expression: 'OtherType.test', } as SearchParameter; const details = getSearchParameterDetails('Patient', missingExpressionParam); expect(details).toBeDefined(); }); test('Property not found', () => { const missingExpressionParam: SearchParameter = { resourceType: 'SearchParameter', code: 'unknown', type: 'string', expression: 'Patient.unknown', } as SearchParameter; expect(() => getSearchParameterDetails('Patient', missingExpressionParam)).toThrow(); }); test('Subtype not found', () => { const missingExpressionParam: SearchParameter = { resourceType: 'SearchParameter', code: 'name-unknown', type: 'string', expression: 'Patient.name.select()', } as SearchParameter; expect(() => getSearchParameterDetails('Patient', missingExpressionParam)).toThrow(); }); test('Unhandled function', () => { const missingExpressionParam: SearchParameter = { resourceType: 'SearchParameter', code: 'unhandled-function', type: 'string', expression: 'Patient.name.unknown', } as SearchParameter; expect(() => getSearchParameterDetails('Patient', missingExpressionParam)).toThrow(); }); describe('clinical-code', () => { // "AllergyIntolerance.code | AllergyIntolerance.reaction.substance | Condition.code | (DeviceRequest.code as CodeableConcept) | DiagnosticReport.code | FamilyMemberHistory.condition.code | List.code | Medication.code | (MedicationAdministration.medication as CodeableConcept) | (MedicationDispense.medication as CodeableConcept) | (MedicationRequest.medication as CodeableConcept) | (MedicationStatement.medication as CodeableConcept) | Observation.code | Procedure.code | ServiceRequest.code" const clinicalCodeParam = searchParams.find((e) => e.id === 'clinical-code') as SearchParameter; test('AllergyIntolerance', () => { const details = getSearchParameterDetails('AllergyIntolerance', clinicalCodeParam); expect(details).toBeDefined(); expect(details.type).toStrictEqual(SearchParameterType.TEXT); expect(details.elementDefinitions).toBeDefined(); expect(details.parsedExpression.toString()).toStrictEqual( '(AllergyIntolerance.code | AllergyIntolerance.reaction.substance)' ); }); test('Observation', () => { const details = getSearchParameterDetails('Observation', clinicalCodeParam); expect(details).toBeDefined(); expect(details.type).toStrictEqual(SearchParameterType.TEXT); expect(details.elementDefinitions).toBeDefined(); expect(details.parsedExpression.toString()).toStrictEqual('Observation.code'); }); }); describe('individual-phone', () => { // "Patient.telecom.where(system='phone') | Person.telecom.where(system='phone') | Practitioner.telecom.where(system='phone') | PractitionerRole.telecom.where(system='phone') | RelatedPerson.telecom.where(system='phone')" const individualPhoneParam = searchParams.find((e) => e.id === 'individual-phone') as SearchParameter; test('Patient', () => { const details = getSearchParameterDetails('Patient', individualPhoneParam); expect(details).toBeDefined(); expect(details.type).toStrictEqual(SearchParameterType.TEXT); expect(details.elementDefinitions).toBeDefined(); expect(details.parsedExpression.toString()).toStrictEqual("Patient.telecom.where((system = 'phone'))"); }); test('RelatedPerson', () => { const details = getSearchParameterDetails('RelatedPerson', individualPhoneParam); expect(details).toBeDefined(); expect(details.type).toStrictEqual(SearchParameterType.TEXT); expect(details.elementDefinitions).toBeDefined(); expect(details.parsedExpression.toString()).toStrictEqual("RelatedPerson.telecom.where((system = 'phone'))"); }); }); test('Observation-value-date', () => { // "(Observation.value as dateTime) | (Observation.value as Period)" const valueDateParam = searchParams.find((e) => e.id === 'Observation-value-date') as SearchParameter; const details = getSearchParameterDetails('Observation', valueDateParam); expect(details).toBeDefined(); expect(details.type).toStrictEqual(SearchParameterType.DATETIME); expect(details.elementDefinitions).toBeDefined(); expect(details.parsedExpression.toString()).toStrictEqual( '((Observation.value as dateTime) | (Observation.value as Period))' ); }); test('Observation-value-quantity', () => { // expression: '(Observation.value as Quantity) | (Observation.value as SampledData)', const valueQuantityParam = searchParams.find((e) => e.id === 'Observation-value-quantity') as SearchParameter; const details = getSearchParameterDetails('Observation', valueQuantityParam); expect(details).toBeDefined(); expect(details.type).toStrictEqual(SearchParameterType.QUANTITY); expect(details.elementDefinitions).toBeDefined(); expect(details.parsedExpression.toString()).toStrictEqual( '((Observation.value as Quantity) | (Observation.value as SampledData))' ); }); test('Encounter-date', () => { // expression: 'AllergyIntolerance.recordedDate | CarePlan.period | CareTeam.period | ClinicalImpression.date | Composition.date | Consent.dateTime | DiagnosticReport.effective | Encounter.period | EpisodeOfCare.period | FamilyMemberHistory.date | Flag.period | Immunization.occurrence | List.date | Observation.effective | Procedure.performed | (RiskAssessment.occurrence as dateTime) | SupplyRequest.authoredOn', const clinicalDateParam = searchParams.find((e) => e.id === 'clinical-date') as SearchParameter; const details = getSearchParameterDetails('Encounter', clinicalDateParam); expect(details).toBeDefined(); expect(details.type).toStrictEqual(SearchParameterType.DATETIME); expect(details.elementDefinitions).toBeDefined(); expect(details.parsedExpression.toString()).toStrictEqual('Encounter.period'); }); test('Bundle-composition', () => { // expression: 'Bundle.entry[0].resource', const searchParam = searchParams.find((e) => e.id === 'Bundle-composition') as SearchParameter; const details = getSearchParameterDetails('Bundle', searchParam); expect(details).toBeDefined(); expect(details.array).toBe(false); }); test('ProjectMembership-profile-type', () => { // expression: 'ProjectMembership.profile.resolve().resourceType', const searchParam = searchParams.find((e) => e.id === 'ProjectMembership-profile-type') as SearchParameter; const details = getSearchParameterDetails('ProjectMembership', searchParam); expect(details).toBeDefined(); expect(details.array).toBe(false); }); test('ProjectMembership-access-policy', () => { const searchParam: SearchParameter = { resourceType: 'SearchParameter', id: 'ProjectMembership-access-policy', url: 'https://medplum.com/fhir/SearchParameter/ProjectMembership-access-policy', version: '4.0.1', name: 'access-policy', status: 'draft', publisher: 'Medplum', description: 'The access policy of the user', code: 'access-policy', base: ['ProjectMembership'], type: 'reference', expression: 'ProjectMembership.accessPolicy | ProjectMembership.access.policy', target: ['AccessPolicy'], }; const details = getSearchParameterDetails('ProjectMembership', searchParam); expect(details).toBeDefined(); expect(details.array).toBe(true); }); test('Account-patient', () => { // expression: 'Account.subject.where(resolve() is Patient)', const searchParam = searchParams.find((e) => e.id === 'Account-patient') as SearchParameter; const details = getSearchParameterDetails('Account', searchParam); expect(details).toBeDefined(); expect(details.array).toBe(true); }); test('ActivityDefinition-composed-of', () => { // expression: 'Account.subject.where(resolve() is Patient)', const searchParam = searchParams.find((e) => e.id === 'ActivityDefinition-composed-of') as SearchParameter; const details = getSearchParameterDetails('ActivityDefinition', searchParam); expect(details).toBeDefined(); expect(details.array).toBe(true); }); test('Patient-deceased', () => { // expression: 'Patient.deceased.exists() and Patient.deceased != false', const searchParam = searchParams.find((e) => e.id === 'Patient-deceased') as SearchParameter; const details = getSearchParameterDetails('Patient', searchParam); expect(details).toBeDefined(); expect(details.array).toBe(false); }); test('us-core-condition-asserted-date', () => { const searchParam = searchParams.find((e) => e.id === 'us-core-condition-asserted-date') as SearchParameter; const details = getSearchParameterDetails('Condition', searchParam); expect(details).toBeDefined(); expect(details.array).toBe(false); expect(details.elementDefinitions).toHaveLength(1); expect(details.elementDefinitions?.[0]?.type).toHaveLength(1); expect(details.elementDefinitions?.[0]?.type?.[0]?.code).toBe('dateTime'); }); test('us-core-ethnicity', () => { const searchParam = searchParams.find((e) => e.id === 'us-core-ethnicity') as SearchParameter; const details = getSearchParameterDetails('Patient', searchParam); expect(details).toBeDefined(); expect(details.array).toBe(true); expect(details.type).toStrictEqual(SearchParameterType.TEXT); expect(details.elementDefinitions).toStrictEqual([]); }); test('us-core-patient-gender-identity', () => { const searchParam = searchParams.find((e) => e.id === 'us-core-patient-gender-identity') as SearchParameter; const details = getSearchParameterDetails('Patient', searchParam); expect(details).toBeDefined(); expect(details.array).toBe(true); expect(details.type).toStrictEqual(SearchParameterType.TEXT); expect(details.elementDefinitions).toStrictEqual([]); }); test('EvidenceVariable-characteristic-type', () => { const searchParam = searchParams.find((e) => e.id === 'EvidenceVariable-characteristic-type') as SearchParameter; const details = getSearchParameterDetails('EvidenceVariable', searchParam); expect(details).toBeDefined(); expect(details.array).toBe(true); expect(details.type).toStrictEqual(SearchParameterType.TEXT); }); test('CodeSystem-context', () => { const searchParam = searchParams.find((e) => e.id === 'conformance-context') as SearchParameter; const details = getSearchParameterDetails('CodeSystem', searchParam); expect(details).toBeDefined(); expect(details.elementDefinitions).toBeDefined(); expect(details.elementDefinitions?.length).toBe(1); }); test('Optimized derived reference identifier search parameter', () => { const patientRefParam = searchParams.find((e) => e.id === 'clinical-patient') as SearchParameter; expect(patientRefParam).toBeDefined(); expect( patientRefParam.expression?.startsWith( 'AllergyIntolerance.patient | CarePlan.subject.where(resolve() is Patient)' ) ).toBe(true); // search parameter details should strip down to the expression only relevant to Observation const derivedParam = deriveIdentifierSearchParameter(patientRefParam); expect( derivedParam.expression?.startsWith('(AllergyIntolerance.patient | CarePlan.subject.where(resolve() is Patient)') ).toBe(true); const details = getSearchParameterDetails('Observation', derivedParam); expect(details).toBeDefined(); expect(details.type).toStrictEqual(SearchParameterType.TEXT); expect(details.elementDefinitions).toBeDefined(); expect(details.parsedExpression.toString()).toStrictEqual( 'Observation.subject.where((resolve() is Patient)).identifier' ); }); test('Everything', () => { // Make sure that getSearchParameterDetails returns successfully for all known parameters. for (const resourceType of Object.keys(globalSchema.types)) { if (resourceType === 'Resource' || resourceType === 'DomainResource') { continue; } for (const searchParam of searchParams) { if (searchParam.base?.includes(resourceType as ResourceType)) { const details = getSearchParameterDetails(resourceType, searchParam); expect(details).toBeDefined(); } } } }); });

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