UNPKG

@phema/cql-execution

Version:

An execution framework for the Clinical Quality Language (CQL)

573 lines (488 loc) 22.2 kB
"use strict"; function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } var _require = require('../datatypes/exception'), Exception = _require.Exception; var _require2 = require('../util/util'), typeIsArray = _require2.typeIsArray; var dt = require('../datatypes/datatypes'); var Context = /*#__PURE__*/function () { function Context(parent) { var _codeService = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; var _parameters = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; _classCallCheck(this, Context); this.parent = parent; this._codeService = _codeService; this.context_values = {}; this.library_context = {}; this.localId_context = {}; this.evaluatedRecords = []; // TODO: If there is an issue with number of parameters look into cql4browsers fix: 387ea77538182833283af65e6341e7a05192304c this.checkParameters(_parameters); // not crazy about possibly throwing an error in a constructor, but... this._parameters = _parameters; } _createClass(Context, [{ key: "parameters", get: function get() { return this._parameters || this.parent && this.parent.parameters; }, set: function set(params) { this.checkParameters(params); this._parameters = params; } }, { key: "codeService", get: function get() { return this._codeService || this.parent && this.parent.codeService; }, set: function set(cs) { this._codeService = cs; } }, { key: "withParameters", value: function withParameters(params) { this.parameters = params || {}; return this; } }, { key: "withCodeService", value: function withCodeService(cs) { this.codeService = cs; return this; } }, { key: "rootContext", value: function rootContext() { if (this.parent) { return this.parent.rootContext(); } else { return this; } } }, { key: "findRecords", value: function findRecords(profile) { return this.parent && this.parent.findRecords(profile); } }, { key: "childContext", value: function childContext() { var context_values = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var ctx = new Context(this); ctx.context_values = context_values; return ctx; } }, { key: "getLibraryContext", value: function getLibraryContext(library) { return this.parent && this.parent.getLibraryContext(library); } }, { key: "getLocalIdContext", value: function getLocalIdContext(localId) { return this.parent && this.parent.getLocalIdContext(localId); } }, { key: "getParameter", value: function getParameter(name) { return this.parent && this.parent.getParameter(name); } }, { key: "getParentParameter", value: function getParentParameter(name) { if (this.parent) { if (this.parent.parameters[name] != null) { return this.parent.parameters[name]; } else { return this.parent.getParentParameter(name); } } } }, { key: "getTimezoneOffset", value: function getTimezoneOffset() { if (this.executionDateTime != null) { return this.executionDateTime.timezoneOffset; } else if (this.parent && this.parent.getTimezoneOffset != null) { return this.parent.getTimezoneOffset(); } else { throw new Exception('No Timezone Offset has been set'); } } }, { key: "getExecutionDateTime", value: function getExecutionDateTime() { if (this.executionDateTime != null) { return this.executionDateTime; } else if (this.parent && this.parent.getExecutionDateTime != null) { return this.parent.getExecutionDateTime(); } else { throw new Exception('No Execution DateTime has been set'); } } }, { key: "getValueSet", value: function getValueSet(name, library) { return this.parent && this.parent.getValueSet(name, library); } }, { key: "getCodeSystem", value: function getCodeSystem(name) { return this.parent && this.parent.getCodeSystem(name); } }, { key: "getCode", value: function getCode(name) { return this.parent && this.parent.getCode(name); } }, { key: "getConcept", value: function getConcept(name) { return this.parent && this.parent.getConcept(name); } }, { key: "get", value: function get(identifier) { // Check for undefined because if its null, we actually *do* want to return null (rather than // looking at parent), but if it's really undefined, *then* look at the parent if (typeof this.context_values[identifier] !== 'undefined') { return this.context_values[identifier]; } else if (identifier === '$this') { return this.context_values; } else { return this.parent != null && this.parent.get(identifier); } } }, { key: "set", value: function set(identifier, value) { this.context_values[identifier] = value; } }, { key: "setLocalIdWithResult", value: function setLocalIdWithResult(localId, value) { // Temporary fix. Real fix will be to return a list of all result values for a given localId. var ctx = this.localId_context[localId]; if (ctx === false || ctx === null || ctx === undefined || ctx.length === 0) { this.localId_context[localId] = value; } } }, { key: "getLocalIdResult", value: function getLocalIdResult(localId) { return this.localId_context[localId]; } // Returns an object of objects containing each library name // with the localIds and result values }, { key: "getAllLocalIds", value: function getAllLocalIds() { var localIdResults = {}; // Add the localIds and result values from the main library localIdResults[this.parent.source.library.identifier.id] = {}; localIdResults[this.parent.source.library.identifier.id] = this.localId_context; // Iterate over support libraries and store localIds for (var libName in this.library_context) { var lib = this.library_context[libName]; this.supportLibraryLocalIds(lib, localIdResults); } return localIdResults; } // Recursive function that will grab nested support library localId results }, { key: "supportLibraryLocalIds", value: function supportLibraryLocalIds(lib, localIdResults) { var _this = this; // Set library identifier name as the key and the object of localIds with their results as the value // if it already exists then we need to merge the results instead of overwriting if (localIdResults[lib.library.source.library.identifier.id] != null) { this.mergeLibraryLocalIdResults(localIdResults, lib.library.source.library.identifier.id, lib.localId_context); } else { localIdResults[lib.library.source.library.identifier.id] = lib.localId_context; } // Iterate over any support libraries in the current support library Object.values(lib.library_context).forEach(function (supportLib) { _this.supportLibraryLocalIds(supportLib, localIdResults); }); } // Merges the localId results for a library into the already collected results. The logic used for which result // to keep is the same as the logic used above in setLocalIdWithResult, "falsey" results are always replaced. }, { key: "mergeLibraryLocalIdResults", value: function mergeLibraryLocalIdResults(localIdResults, libraryId, libraryResults) { for (var localId in libraryResults) { var localIdResult = libraryResults[localId]; var existingResult = localIdResults[libraryId][localId]; // overwite this localid result if the existing result is "falsey". future work could track all results for each localid if (existingResult === false || existingResult === null || existingResult === undefined || existingResult.length === 0) { localIdResults[libraryId][localId] = localIdResult; } } } }, { key: "checkParameters", value: function checkParameters(params) { for (var pName in params) { var pVal = params[pName]; var pDef = this.getParameter(pName); if (pVal == null) { return; // Null can theoretically be any type } if (typeof pDef === 'undefined') { return; // This will happen if the parameter is declared in a different (included) library } else if (pDef.parameterTypeSpecifier != null && !this.matchesTypeSpecifier(pVal, pDef.parameterTypeSpecifier)) { throw new Error("Passed in parameter '".concat(pName, "' is wrong type")); } else if (pDef['default'] != null && !this.matchesInstanceType(pVal, pDef['default'])) { throw new Error("Passed in parameter '".concat(pName, "' is wrong type")); } } return true; } }, { key: "matchesTypeSpecifier", value: function matchesTypeSpecifier(val, spec) { switch (spec.type) { case 'NamedTypeSpecifier': return this.matchesNamedTypeSpecifier(val, spec); case 'ListTypeSpecifier': return this.matchesListTypeSpecifier(val, spec); case 'TupleTypeSpecifier': return this.matchesTupleTypeSpecifier(val, spec); case 'IntervalTypeSpecifier': return this.matchesIntervalTypeSpecifier(val, spec); case 'ChoiceTypeSpecifier': return this.matchesChoiceTypeSpecifier(val, spec); default: return true; // default to true when we don't know } } }, { key: "matchesListTypeSpecifier", value: function matchesListTypeSpecifier(val, spec) { var _this2 = this; return typeIsArray(val) && val.every(function (x) { return _this2.matchesTypeSpecifier(x, spec.elementType); }); } }, { key: "matchesTupleTypeSpecifier", value: function matchesTupleTypeSpecifier(val, spec) { var _this3 = this; // TODO: Spec is not clear about exactly how tuples should be matched return val != null && _typeof(val) === 'object' && !typeIsArray(val) && !val.isInterval && !val.isConcept && !val.isCode && !val.isDateTime && !val.isDate && !val.isQuantity && spec.element.every(function (x) { return typeof val[x.name] === 'undefined' || _this3.matchesTypeSpecifier(val[x.name], x.elementType); }); } }, { key: "matchesIntervalTypeSpecifier", value: function matchesIntervalTypeSpecifier(val, spec) { return val.isInterval && (val.low == null || this.matchesTypeSpecifier(val.low, spec.pointType)) && (val.high == null || this.matchesTypeSpecifier(val.high, spec.pointType)); } }, { key: "matchesChoiceTypeSpecifier", value: function matchesChoiceTypeSpecifier(val, spec) { var _this4 = this; return spec.choice.some(function (c) { return _this4.matchesTypeSpecifier(val, c); }); } }, { key: "matchesNamedTypeSpecifier", value: function matchesNamedTypeSpecifier(val, spec) { if (val == null) { return true; } switch (spec.name) { case '{urn:hl7-org:elm-types:r1}Boolean': return typeof val === 'boolean'; case '{urn:hl7-org:elm-types:r1}Decimal': return typeof val === 'number'; case '{urn:hl7-org:elm-types:r1}Integer': return typeof val === 'number' && Math.floor(val) === val; case '{urn:hl7-org:elm-types:r1}String': return typeof val === 'string'; case '{urn:hl7-org:elm-types:r1}Concept': return val && val.isConcept; case '{urn:hl7-org:elm-types:r1}Code': return val && val.isCode; case '{urn:hl7-org:elm-types:r1}DateTime': return val && val.isDateTime; case '{urn:hl7-org:elm-types:r1}Date': return val && val.isDate; case '{urn:hl7-org:elm-types:r1}Quantity': return val && val.isQuantity; case '{urn:hl7-org:elm-types:r1}Time': return val && val.isTime && val.isTime(); default: // Use the data model's implementation of _is, if it is available if (typeof val._is === 'function') { return val._is(spec); } // If the value is an array or interval, then we assume it cannot be cast to a // named type. Technically, this is not 100% true because a modelinfo can define // a named type whose base type is a list or interval. But none of our models // (FHIR, QDM, QICore) do that, so for those models, this approach will always be // correct. if (Array.isArray(val) || val.isInterval) { return false; } // Otherwise just default to true to match legacy behavior. // // NOTE: This is also where arbitrary tuples land because they will not have // an "is" function and we don't encode the type information into the runtime // objects so we can't easily determine their type. We can't reject them, // else things like `Encounter{ id: "1" } is Encounter` would return false. // So for now we allow false positives in order to avoid false negatives. return true; } } }, { key: "matchesInstanceType", value: function matchesInstanceType(val, inst) { if (inst.isBooleanLiteral) { return typeof val === 'boolean'; } else if (inst.isDecimalLiteral) { return typeof val === 'number'; } else if (inst.isIntegerLiteral) { return typeof val === 'number' && Math.floor(val) === val; } else if (inst.isStringLiteral) { return typeof val === 'string'; } else if (inst.isCode) { return val && val.isCode; } else if (inst.isConcept) { return val && val.isConcept; } else if (inst.isTime && inst.isTime()) { return val && val.isTime && val.isTime(); } else if (inst.isDate) { return val && val.isDate; } else if (inst.isDateTime) { return val && val.isDateTime; } else if (inst.isQuantity) { return val && val.isQuantity; } else if (inst.isList) { return this.matchesListInstanceType(val, inst); } else if (inst.isTuple) { return this.matchesTupleInstanceType(val, inst); } else if (inst.isInterval) { return this.matchesIntervalInstanceType(val, inst); } return true; // default to true when we don't know for sure } }, { key: "matchesListInstanceType", value: function matchesListInstanceType(val, list) { var _this5 = this; return typeIsArray(val) && val.every(function (x) { return _this5.matchesInstanceType(x, list.elements[0]); }); } }, { key: "matchesTupleInstanceType", value: function matchesTupleInstanceType(val, tpl) { var _this6 = this; return _typeof(val) === 'object' && !typeIsArray(val) && tpl.elements.every(function (x) { return typeof val[x.name] === 'undefined' || _this6.matchesInstanceType(val[x.name], x.value); }); } }, { key: "matchesIntervalInstanceType", value: function matchesIntervalInstanceType(val, ivl) { var pointType = ivl.low != null ? ivl.low : ivl.high; return val.isInterval && (val.low == null || this.matchesInstanceType(val.low, pointType)) && (val.high == null || this.matchesInstanceType(val.high, pointType)); } }]); return Context; }(); var PatientContext = /*#__PURE__*/function (_Context) { _inherits(PatientContext, _Context); var _super = _createSuper(PatientContext); function PatientContext(library, patient, codeService, parameters) { var _this7; var executionDateTime = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : dt.DateTime.fromJSDate(new Date()); _classCallCheck(this, PatientContext); _this7 = _super.call(this, library, codeService, parameters); _this7.library = library; _this7.patient = patient; _this7.executionDateTime = executionDateTime; return _this7; } _createClass(PatientContext, [{ key: "rootContext", value: function rootContext() { return this; } }, { key: "getLibraryContext", value: function getLibraryContext(library) { if (this.library_context[library] == null) { this.library_context[library] = new PatientContext(this.get(library), this.patient, this.codeService, this.parameters, this.executionDateTime); } return this.library_context[library]; } }, { key: "getLocalIdContext", value: function getLocalIdContext(localId) { if (this.localId_context[localId] == null) { this.localId_context[localId] = new PatientContext(this.get(localId), this.patient, this.codeService, this.parameters, this.executionDateTime); } return this.localId_context[localId]; } }, { key: "findRecords", value: function findRecords(profile) { return this.patient && this.patient.findRecords(profile); } }]); return PatientContext; }(Context); var UnfilteredContext = /*#__PURE__*/function (_Context2) { _inherits(UnfilteredContext, _Context2); var _super2 = _createSuper(UnfilteredContext); function UnfilteredContext(library, results, codeService, parameters) { var _this8; var executionDateTime = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : dt.DateTime.fromJSDate(new Date()); _classCallCheck(this, UnfilteredContext); _this8 = _super2.call(this, library, codeService, parameters); _this8.library = library; _this8.results = results; _this8.executionDateTime = executionDateTime; return _this8; } _createClass(UnfilteredContext, [{ key: "rootContext", value: function rootContext() { return this; } }, { key: "findRecords", value: function findRecords(template) { throw new Exception('Retreives are not currently supported in Unfiltered Context'); } }, { key: "getLibraryContext", value: function getLibraryContext(library) { throw new Exception('Library expressions are not currently supported in Unfiltered Context'); } }, { key: "get", value: function get(identifier) { //First check to see if the identifier is a unfiltered context expression that has already been cached if (this.context_values[identifier]) { return this.context_values[identifier]; } //if not look to see if the library has a unfiltered expression of that identifier if (this.library[identifier] && this.library[identifier].context === 'Unfiltered') { return this.library.expressions[identifier]; } //lastley attempt to gather all patient level results that have that identifier // should this compact null values before return ? return Object.values(this.results.patientResults).map(function (pr) { return pr[identifier]; }); } }]); return UnfilteredContext; }(Context); module.exports = { Context: Context, PatientContext: PatientContext, UnfilteredContext: UnfilteredContext };