fume-fhir-converter
Version:
FHIR-Utilized Mapping Engine - Community
150 lines (131 loc) • 6.79 kB
text/typescript
/**
* © 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 ?? '');
}
};