UNPKG

fume-fhir-converter

Version:

FHIR-Utilized Mapping Engine - Community

150 lines (131 loc) 6.79 kB
/** * © Copyright Outburn Ltd. 2022-2023 All Rights Reserved * Project name: FUME */ /* eslint-disable */ import { getSnapshot } from '../parser/getSnapshot'; import { initCap, initCapOnce, replaceColonsWithBrackets, startsWith, substringAfter, substringBefore } from '../stringFunctions'; import _ from 'lodash'; import { getElementDefinition } from '../parser/getElementDefinition'; import { getStructureDefinition } from '../jsonataFunctions'; import { getMandatoriesOfStructure } from './getMandatoriesOfStructure'; import config from '../../config'; import { getFhirPackageIndex } from '../conformance'; import { getLogger } from '../logger'; const dev = process.env.NODE_ENV === 'dev'; export const getMandatoriesOfElement = async (structId: string, relativePath: string): Promise<any> => { if (dev) console.log({ func: getMandatoriesOfElement, structId, relativePath }); /* for primitives, return the primitive if it has a fixed value */ /* for complex types, return an object containing all mandatory */ /* decendants that have fixed values down the chain */ /* the element is taken from the root structure definition 'snapshot.element' array */ const rootStruct = await getSnapshot(structId, config.getFhirVersion(), getFhirPackageIndex(), getLogger()); const rootStructSnapshot = rootStruct.snapshot?.element; /* take the element definition of the requested (parent) element */ if (dev) console.log('calling getElementDefinition', {structId, relativePath}); const parentElement = await getElementDefinition(structId, relativePath); const fromDefinition = parentElement?.__fromDefinition; if (dev) console.log({fromDefinition}); /* check if returned element is from the root definition */ if (rootStruct?.url === fromDefinition) { /* get the fhirtype name(s) of the parent, for use in polymorphic keys */ const parentElementType = parentElement.type.map(t => { if (t.code.startsWith('http://hl7.org/fhirpath/System.')) { return t?.extension.find(e => e?.url === 'http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type')?.valueUrl; } else { return t?.code; } }); /* get the profile, if set (e.g. slices on extension or il-core-patient.address) TODO */ const parentElementProfile = parentElement?.type[0]?.profile; let fixed; /* not poly - polies cannot have fixed */ if (parentElementType.length === 1) { const fixedKeys = ['fixed', 'pattern'].map(key => key + parentElementType[0].split(' ').map(w => initCapOnce(w)).join(' ')); /* get the fixed value if there is one TODO */ fixed = fixedKeys.map(k => _.get(parentElement, k)).filter(Boolean); fixed = fixed.length ? fixed[0] : undefined; } const typeOfFixed = typeof fixed; /* now if fixed is not object, no need to continue */ if (fixed && typeOfFixed !== 'object') { return fixed; } else { /* however if it is an object, it may also have fixed values from underneath */ /* this is obviously true also when there isn't a fixed value */ /* take all elements with min>0 that are "under" the requested element */ let childrenFromRootStruct = rootStructSnapshot.filter(e => { /* There is a fixed[x] or pattern[x] or it's just mandatory */ return e.id.startsWith(parentElement.id + '.') && !substringAfter(e.id, parentElement.id + '.')?.includes('.') && /* take only those that are a single step under */ (Object.keys(e).filter(k => startsWith(k, 'fixed') || startsWith(k, 'pattern')).length > 0 || e.min > 0); }); /* add the correct json key name to each element */ childrenFromRootStruct = childrenFromRootStruct.map(child => { const lastNode = child.id.split('.').slice(-1)[0]; child.__jsonKey = lastNode.endsWith('[x]') ? substringBefore(lastNode, '[x]') + initCap(child?.type[0].code) : lastNode; return child; }); let obj = []; childrenFromRootStruct = childrenFromRootStruct.map(child => { const code = child?.type[0]?.code; if (code) { if (startsWith(code, 'http://hl7.org/fhirpath/System.')) { child.__kind = 'primitive-type'; } else { child.__kind = getStructureDefinition(code, config.getFhirVersion(), getFhirPackageIndex(), getLogger()).kind; } } return child; }); childrenFromRootStruct = await Promise.all(childrenFromRootStruct.map(async child => { const val = await getMandatoriesOfElement(structId, substringAfter(child.id, rootStructSnapshot[0].id + '.')); return _.merge(child, { __val: val }); })); childrenFromRootStruct = childrenFromRootStruct.map(child => { if (typeof child.__val === 'object' && child.__kind === 'primitive-type') { child[`_${child.__jsonKey}`] = child[child.__jsonKey]; delete child[child.__jsonKey]; } return child; }); childrenFromRootStruct = childrenFromRootStruct.map(child => { const lastNode = child.id.split('.').slice(-1)[0]; const jsonKey = lastNode.endsWith('[x]') ? `${lastNode.split('[x]')[0]}${initCapOnce(child.type[0].code)}` : lastNode; return { ...child, __jsonKey: jsonKey }; }); obj = await childrenFromRootStruct.reduce((acc, curr) => { if (curr.__val) { acc[curr.__jsonKey] = (curr.base.max > '1' || curr.base.max === '*' || curr.max > '1' || curr.max === '*') ? [curr.__val] : curr.__val; } return acc; }, {}); let fromProfile; if (parentElementProfile?.length === 1) { fromProfile = await getMandatoriesOfStructure(parentElementProfile[0]); } const res = _.merge(fixed, fromProfile, obj); if (Object.keys(res).length !== 0 && Object.keys(res).length > 0) { const kv = { ...res }; const kvWithBrackets = {}; for (const key of Object.keys(kv)) { if (res[key]) { kvWithBrackets[replaceColonsWithBrackets(key)] = kv[key]; } } const mergedResult = Object.assign({}, kvWithBrackets); return mergedResult; } } } else { /* if returned element is from a different definition than the root, call this function again */ /* this time on the element's containing definition */ const fromDefinitionSnapshot = await getSnapshot(fromDefinition, config.getFhirVersion(), getFhirPackageIndex(), getLogger()); const baseType = fromDefinitionSnapshot.type; const relativeElementId = substringAfter(parentElement.id, baseType + '.'); return await getMandatoriesOfElement(fromDefinition, relativeElementId ?? ''); } };