@i4mi/fhir_r4
Version:
FHIR resource definitions + API methods + utils
278 lines (276 loc) • 12.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.writeI18N = writeI18N;
exports.readI18N = readI18N;
exports.getAllI18N = getAllI18N;
exports.hasCoding = hasCoding;
exports.getCode = getCode;
exports.isUUID = isUUID;
exports.getFullName = getFullName;
exports.selectName = selectName;
exports.isInPeriod = isInPeriod;
exports.getIdentifierString = getIdentifierString;
const definition_1 = require("../definition");
const TRANSLATION_URL = 'http://hl7.org/fhir/StructureDefinition/translation';
const LANG_URL = 'lang';
const CONTENT_URL = 'content';
const REG_EXP_UUID = new RegExp(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
/**
* Creates a internationalization extension object for assigning to text fields, according to
* specification in http://hl7.org/fhir/R4B/extension-translation.html.
* @param strings A key / value pair object with the translation strings.
* @returns A javascript object containing the extensions for an extensible property of a FHIR resource.
* @see http://hl7.org/fhir/R4B/extension-translation.html
*/
function writeI18N(strings) {
const i18nExtensions = new Array();
Object.keys(strings).forEach(language => {
i18nExtensions.push({
url: TRANSLATION_URL,
extension: [
{
url: LANG_URL,
valueCode: language
},
{
url: CONTENT_URL,
valueString: strings[language]
}
]
});
});
return {
extension: i18nExtensions
};
}
/**
* Reads the i18n string from a given extension element. The element needs to have
* the structure as defined in http://hl7.org/fhir/R4B/extension-translation.html
* @param element the element formed like defined in http://hl7.org/fhir/R4B/extension-translation.html
* @param lang the desired language string
* @returns the i18n string, or undefined if the given language is not available or the
* element is malformed.
* @see http://hl7.org/fhir/R4B/extension-translation.html
*/
function readI18N(element, lang) {
var _a, _b;
if (!element.extension)
return undefined;
const translationExtension = element.extension.find((extension) => {
var _a;
if (extension.url === TRANSLATION_URL) {
return (extension.extension &&
((_a = extension.extension) === null || _a === void 0 ? void 0 : _a.findIndex((extension) => {
return extension.url === LANG_URL && extension.valueCode === lang;
})) > -1);
}
else {
return false;
}
});
return (_b = (_a = translationExtension === null || translationExtension === void 0 ? void 0 : translationExtension.extension) === null || _a === void 0 ? void 0 : _a.find((extension) => {
return extension.url === CONTENT_URL;
})) === null || _b === void 0 ? void 0 : _b.valueString;
}
/**
* Reads all available i18n strings from a given extension element. The element needs to have
* the structure as defined in http://hl7.org/fhir/R4B/extension-translation.html
* @param element the element formed like defined in http://hl7.org/fhir/R4B/extension-translation.html
* @returns a key/value pair object with all available i18n strings as value and their respective
* language as key
* @see http://hl7.org/fhir/R4B/extension-translation.html
*/
function getAllI18N(element) {
var _a;
const i18n = {};
const translationExtensions = (_a = element.extension) === null || _a === void 0 ? void 0 : _a.filter(ext => ext.url === TRANSLATION_URL);
translationExtensions === null || translationExtensions === void 0 ? void 0 : translationExtensions.map(te => {
var _a, _b, _c, _d;
const lang = (_b = (_a = te.extension) === null || _a === void 0 ? void 0 : _a.find(ext => ext.url === LANG_URL)) === null || _b === void 0 ? void 0 : _b.valueCode;
const str = (_d = (_c = te.extension) === null || _c === void 0 ? void 0 : _c.find(ext => ext.url === CONTENT_URL)) === null || _d === void 0 ? void 0 : _d.valueString;
if (lang && str) {
i18n[lang] = str;
}
});
return i18n;
}
/**
* Helper function to detect if a CodeableConcept has a given coding
* @param codeableConcept a codeable concept
* @param coding the coding to search for
* @returns true if the codeable concept contains the given coding
* false if the codeable concept is undefined or does not
* contain the given coding
*/
function hasCoding(codeableConcept, coding) {
if (!codeableConcept)
return false;
const system = coding.system;
const code = coding.code;
if (!codeableConcept.coding)
return false;
return codeableConcept.coding.findIndex(c => (system === undefined || c.system === system) && (c.code === code)) > -1;
}
/**
* Helper function extract a code from a codeable concept
* @param codeableConcept a codeable concept
* @param coding the system of the wanted code
* @returns the code, if found, else undefined
*/
function getCode(codeableConcept, system) {
var _a;
if (!codeableConcept)
return undefined;
const coding = (_a = codeableConcept.coding) === null || _a === void 0 ? void 0 : _a.find((coding) => coding.system === system);
return coding
? coding.code
: undefined;
}
/**
* Checks if the given value is a valid UUID / GUID.
* @param id the value that will be tested.
* @returns true if the given ID is an UUID or GUID
* false if in every other case
*/
function isUUID(id) {
if (typeof id !== 'string')
return false;
return REG_EXP_UUID.test(id);
}
/**
* Generates the full name according the given HumanName
* @param name the HumanName that will be used to generate the full name
* @param excludeTitles set to true if you want to exclude the titles (default: false)
* @param shortenMiddleNames set to true if you want to shorten the second to nth middle names (default: false)
* @returns the name concatenated to a string
*/
function getFullName(name, excludeTitles = false, shortenMiddleNames = false) {
if (!name)
return '';
let text = '';
if (name.prefix && name.prefix.length > 0 && !excludeTitles)
text += name.prefix.reduce((a, b) => a + ' ' + b) + ' ';
if (name.given)
text += name.given.reduce((a, b) => a + ' ' + (shortenMiddleNames ? (b.substring(0, 1) + '.') : b)) + ' ';
if (name.family)
text += name.family;
if (name.suffix && name.suffix.length > 0 && !excludeTitles)
text += ', ' + name.suffix.reduce((a, b) => a + ' ' + b);
return text.trimEnd();
}
/**
* Chooses which supplied HumanName is the best suited. When no priorisation provided,
* the priority is:
* USUAL > OFFICIAL > TEMP (if period does match) > NICKNAME >
* ANONYMOUS > TEMP (if period does not match) > MAIDEN > OLD.
* @param names an array of HumanName
* @param preferred? an array of HumanNameNameUse, describing your priorisation (priorisation[0] is highest)
* @returns the best suited name
*/
function selectName(names, priorisation, alsoReturnOldNames) {
let name = undefined;
if (names.length === 0)
return undefined;
priorisation === null || priorisation === void 0 ? void 0 : priorisation.forEach(nameType => {
if (!name)
name = names.find((x) => x.use === nameType);
});
if (name)
return name;
name = names.find((x) => x.use === definition_1.HumanNameNameUse.USUAL);
if (name)
return name;
name = names.find((x) => x.use === definition_1.HumanNameNameUse.OFFICIAL);
if (name)
return name;
name = names.find((x) => x.use === definition_1.HumanNameNameUse.TEMP && x.period && isInPeriod(x.period));
if (name)
return name;
name = names.find((x) => x.use === definition_1.HumanNameNameUse.NICKNAME);
if (name)
return name;
name = names.find((x) => x.use === definition_1.HumanNameNameUse.ANONYMOUS);
if (name)
return name;
name = names.find((x) => x.use === definition_1.HumanNameNameUse.TEMP);
if (name)
return name;
name = names.find((x) => x.use === definition_1.HumanNameNameUse.MAIDEN);
if (name)
return name;
name = names.find((x) => x.use !== definition_1.HumanNameNameUse.OLD || alsoReturnOldNames);
return name;
}
/**
* Evaluates if a given time is in a given period.
* @param period the Period (can have a start and / or an end) to check against
* @param time? the time point to check against (can be Date, number or string, if it's a string
* it must be parseable by new Date()).
* if no time point is provided, the current time is taken as time point
* @returns - true if the given time or the current time is after the start and / or before
* the end of the period
* - false if the given time or the current time is outside the period or an invalid
* time point was given
*/
function isInPeriod(period, time = new Date()) {
let now;
switch (typeof time) {
case 'string':
case 'number':
now = new Date(time);
break;
default:
now = time;
}
let hasStarted = period.start
? new Date(period.start).getTime() <= now.getTime()
: true;
let hasEnded = false;
if (period.end) {
const parts = period.end.split('-');
if (period.end.length === 4) { // it's a year (inclusive for whole year!)
const endYear = Number(period.end);
hasEnded = endYear < now.getFullYear();
}
else if (parts.length === 2 && period.end.length < 8) { // it's a year with a month (inclusive for whole month!)
const endYear = Number(parts[0]);
const endMonth = Number(parts[1]);
hasEnded = endYear < now.getFullYear() || endYear === now.getFullYear() && endMonth < now.getMonth() - 1;
}
else if (parts.length === 3 && period.end.length < 11) { // it's a year with month and day (inclusive for whole day!)
const endYear = Number(parts[0]);
const endMonth = Number(parts[1]);
const endDay = Number(parts[2]);
hasEnded = (endYear < now.getFullYear() ||
(endYear === now.getFullYear() && endMonth < (now.getMonth() + 1)) ||
(endYear === now.getFullYear() && endMonth === (now.getMonth() + 1) && endDay < now.getDate()));
}
else {
hasEnded = new Date(period.end).getTime() < now.getTime();
}
}
return (hasStarted && !hasEnded);
}
/**
* Gets the identifier string for a given system (of the identifier) from an array of identifiers
* For backward compatibility, also a Patient resource can be passed as source
* @param source an array of Identifier (or a Patient resource)
* @param system the system the wanted identifier is in (e.g. as oid)
* @returns a string in the form of urn:oid:1.1.1.99.1|1e3796be
* @throws an Error if the source has no identifier whose system matches the given system
*/
function getIdentifierString(source, system) {
let identifiers;
if (source.resourceType === 'Patient') {
identifiers = source.identifier || [];
}
else {
identifiers = source;
}
const identifier = identifiers.find((id) => { var _a; return ((_a = id.system) === null || _a === void 0 ? void 0 : _a.includes(system)) && id.value != undefined; });
if (!identifier || !identifier.value) {
throw new Error('No identifier matches the system "' + system + '".');
}
return system + '|' + identifier.value;
}
;