Skip to main content
Glama
valuesets.ts8.23 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { readJson } from '@medplum/definitions'; import type { Bundle, BundleEntry, CodeSystem, CodeSystemConcept, Resource, ValueSet, ValueSetCompose, } from '@medplum/fhirtypes'; import csv from 'csv-parser'; import { createReadStream, writeFileSync } from 'node:fs'; import { resolve } from 'node:path'; const valueSets = new Map<string, CodeSystem | ValueSet>(); export function getValueSetValues(url: string): string[] { if (valueSets.size === 0) { const files = [ 'valuesets.json', 'v2-tables.json', 'v3-codesystems.json', 'valuesets-medplum.json', 'valuesets-medplum-generated.json', ]; for (const file of files) { loadValueSets('fhir/r4/' + file); } } const result: string[] = []; buildValueSetValues(url, result); return result; } function loadValueSets(fileName: string): void { const valueSetBundle = readJson(fileName) as Bundle; for (const entry of valueSetBundle.entry as BundleEntry[]) { const resource = entry.resource as Resource; if (resource.resourceType === 'CodeSystem' || resource.resourceType === 'ValueSet') { valueSets.set(resource.url as string, resource as CodeSystem | ValueSet); } } } function buildValueSetValues(url: string, result: string[]): void { // If the url includes a version, remove it if (url.includes('|')) { url = url.split('|')[0]; } const resource = valueSets.get(url); if (!resource) { return; } if (resource.resourceType === 'ValueSet') { buildValueSetComposeValues(resource.compose, result); } if (resource.resourceType === 'CodeSystem') { buildCodeSystemConceptValues(resource.concept, result); } } function buildValueSetComposeValues(compose: ValueSetCompose | undefined, result: string[]): void { if (compose?.include) { for (const include of compose.include) { if (include.concept) { for (const concept of include.concept) { if (concept.code) { result.push(concept.code); } } } else if (include.system) { const includedValues = getValueSetValues(include.system); if (includedValues) { result.push(...includedValues); } } } } } function buildCodeSystemConceptValues(concepts: CodeSystemConcept[] | undefined, result: string[]): void { if (!concepts) { return; } for (const concept of concepts) { if (concept.code) { result.push(concept.code); } buildCodeSystemConceptValues(concept.concept, result); } } export async function generateCodeSystems(): Promise<(CodeSystem | ValueSet)[]> { const valueSets: (CodeSystem | ValueSet)[] = []; valueSets.push(...(await generateCountryCodes())); valueSets.push(await generateCurrencyCodes()); return valueSets; } async function generateCountryCodes(): Promise<CodeSystem[]> { const m49Codes: Record<string, CodeSystemConcept> = Object.create(null); const isoCodes: Record<string, CodeSystemConcept> = Object.create(null); const path = resolve(import.meta.dirname, 'data/unsd-methodology.csv'); return new Promise((resolve, reject) => { createReadStream(path) .pipe(csv({ separator: ';' })) .on('data', (row) => parseCountryCodeRow(row, m49Codes, isoCodes)) .on('end', () => resolve([ { resourceType: 'CodeSystem', status: 'active', url: 'http://unstats.un.org/unsd/methods/m49/m49.htm', content: 'complete', concept: Object.values(m49Codes), property: [ { code: 'class', type: 'code', description: 'Size category of region described by the code', }, ], }, { resourceType: 'CodeSystem', status: 'active', url: 'urn:iso:std:iso:3166', content: 'complete', concept: Object.values(isoCodes), property: [ { code: 'synonym', type: 'code', uri: 'http://hl7.org/fhir/concept-properties#synonym', description: 'Equivalent code', }, { code: 'numeric', type: 'code', uri: 'http://hl7.org/fhir/concept-properties#synonym', description: 'Equivalent numeric code', }, ], }, ]) ) .on('error', reject); }); } function parseCountryCodeRow( row: any, m49Codes: Record<string, CodeSystemConcept>, isoCodes: Record<string, CodeSystemConcept> ): void { const world = row['Global Code'] as string; const region = row['Region Code'] as string; const subRegion = row['Sub-region Code'] as string; const intRegion = row['Intermediate Region Code'] as string; const country = row['Country or Area'] as string; const m49 = row['M49 Code'] as string; const iso2 = row['ISO-alpha2 Code'] as string; const iso3 = row['ISO-alpha3 Code'] as string; if (world) { m49Codes[world] = { code: world, display: row['Global Name'], property: [{ code: 'class', valueCode: 'world' }], }; } if (region) { m49Codes[region] = { code: region, display: row['Region Name'], property: [{ code: 'class', valueCode: 'region' }], }; } if (subRegion) { m49Codes[subRegion] = { code: subRegion, display: row['Sub-region Name'], property: [{ code: 'class', valueCode: 'sub-region' }], }; } if (intRegion) { m49Codes[intRegion] = { code: intRegion, display: row['Intermediate Region Name'], property: [{ code: 'class', valueCode: 'intermediate-region' }], }; } m49Codes[m49] = { code: m49, display: country, property: [{ code: 'class', valueCode: 'country' }] }; isoCodes[iso2] = { code: iso2, display: country, property: [ { code: 'synonym', valueCode: iso3 }, { code: 'numeric', valueCode: m49 }, ], }; isoCodes[iso3] = { code: iso3, display: country, property: [ { code: 'synonym', valueCode: iso2 }, { code: 'numeric', valueCode: m49 }, ], }; } async function generateCurrencyCodes(): Promise<CodeSystem> { const isoCodes: Record<string, CodeSystemConcept> = Object.create(null); const path = resolve(import.meta.dirname, 'data/iso-4217-list-one.csv'); return new Promise((resolve, reject) => { createReadStream(path) .pipe(csv()) .on('data', (row) => parseCurrencyCodeRow(row, isoCodes)) .on('end', () => { resolve({ resourceType: 'CodeSystem', status: 'active', url: 'urn:iso:std:iso:4217', content: 'complete', concept: Object.values(isoCodes), property: [ { code: 'numeric', type: 'code', uri: 'http://hl7.org/fhir/concept-properties#synonym', description: 'Equivalent numeric code', }, ], }); }) .on('error', reject); }); } function parseCurrencyCodeRow(row: any, isoCodes: Record<string, CodeSystemConcept>): void { const currency = row['Currency']; const alpha = row['Alphabetic Code']; const num = row['Numeric Code']; if (alpha) { isoCodes[alpha] = { code: alpha, display: currency, property: [{ code: 'numeric', valueCode: num }] }; } } async function main(): Promise<void> { const codeSystems = await generateCodeSystems(); const bundle: Bundle<CodeSystem | ValueSet> = { resourceType: 'Bundle', type: 'collection', entry: codeSystems.map((resource) => ({ fullUrl: resource.url, resource })), }; const json = JSON.stringify(bundle, undefined, 2) .replaceAll("'", '\\u0027') .replaceAll('<', '\\u003c') .replaceAll('=', '\\u003d') .replaceAll('>', '\\u003e'); writeFileSync( resolve(import.meta.dirname, '../../definitions/dist/fhir/r4/valuesets-medplum-generated.json'), json, 'utf8' ); } if (import.meta.main) { main().catch(console.error); }

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