diff --git a/tx/cs/cs-snomed.js b/tx/cs/cs-snomed.js index bf2f313..fd98901 100644 --- a/tx/cs/cs-snomed.js +++ b/tx/cs/cs-snomed.js @@ -659,6 +659,19 @@ class SnomedProvider extends BaseCSServices { } } + async incompleteValidationMessage(context) { + + const ctxt = await this.#ensureContext(context); + + if (!ctxt) return null; + + if (ctxt.isComplex()) { + return "The expression is grammatically correct and the concepts are valid, but the expression has not been checked against the SNOMED CT concept model (MRCM)"; + } else { + return null; + } + } + async locateIsA(code, parent, disallowParent = false) { diff --git a/tx/workers/validate.js b/tx/workers/validate.js index 570e7da..36ba555 100644 --- a/tx/workers/validate.js +++ b/tx/workers/validate.js @@ -1005,7 +1005,7 @@ class ValueSetChecker { let tsys = ''; let tcode = ''; let tver = ''; - let vcc = {}; + let vcc = {}; // todo: VCC is an appendage, and useless. remove it if (code.text) { vcc.text = code.text; } @@ -1066,6 +1066,9 @@ class ValueSetChecker { tcode = c.code; tver = c.version; } + if (!tver && ver.value) { + tver = ver.value; + } let cc; if (!ws) { ws = ""; @@ -1209,6 +1212,9 @@ class ValueSetChecker { } } } else { + if (!c.version && mode == 'codeableConcept' && prov.version()) { + c.version = prov.version(); + } await this.worker.listDisplaysFromCodeSystem(list, prov, ctxt.context); let pd = list.preferredDisplay(this.params.workingLanguages()); if (pd) { @@ -1243,7 +1249,7 @@ class ValueSetChecker { msg(m); op.addIssue(new Issue(severity, 'invalid', addToPath(path, 'display'), baseMsg, m, 'invalid-display')); } - if (prov.version() && code.coding.length < 2) { + if (prov.version() && mode != 'codeableConcept') { result.addParamStr('version', prov.version()); } } @@ -1518,36 +1524,8 @@ class ValueSetChecker { if (loc.message && op) { op.addIssue(new Issue('information', 'code-invalid', addToPath(path, 'code'), null, loc.message, 'invalid-code')); } - } else if (!(this.params.abstractOk || !(await cs.isAbstract(loc.context)))) { - this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is abstract', this.indentCount); - if (!this.params.membershipOnly) { - op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'ABSTRACT_CODE_NOT_ALLOWED', this.worker.i18n.translate('ABSTRACT_CODE_NOT_ALLOWED', this.params.HTTPLanguages, [cs.system(), code]), 'code-rule')); - } - } else if (this.excludeInactives() && await cs.isInactive(loc.context)) { - this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is inactive', this.indentCount); - let msg = this.worker.i18n.translate('STATUS_CODE_WARNING_CODE', this.params.HTTPLanguages, ['not active', code]); - op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'STATUS_CODE_WARNING_CODE', msg, 'code-rule')); - result = false; - messages.push(msg); - if (!this.params.membershipOnly) { - inactive.value = true; - inactive.path = path; - if (inactive.value) { - vstatus.value = await cs.getStatus(loc.context); - } - } - } else if (this.params.activeOnly && await cs.isInactive(loc.context)) { - this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is inactive', this.indentCount); - result = false; - inactive.value = true; - inactive.path = path; - vstatus.value = await cs.getStatus(loc.context); - let msg = this.worker.i18n.translate('STATUS_CODE_WARNING_CODE', this.params.HTTPLanguages, ['not active', code]); - messages.push(msg); - op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'STATUS_CODE_WARNING_CODE', msg, 'code-rule')); } else { this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs), this.indentCount); - result = true; if (await cs.code(loc.context) != code) { let msg; if (cs.version()) { @@ -1563,22 +1541,53 @@ class ValueSetChecker { op.addIssue(new Issue('information', 'informational', addToPath(path, 'code'), null, msg, 'process-note')); } await this.worker.listDisplaysFromCodeSystem(displays, cs, loc.context); - inactive.value = await cs.isInactive(loc.context); - inactive.path = path; - vstatus.value = await cs.getStatus(loc.context); - if (vcc !== null) { - if (!vcc.coding) { - vcc.coding = []; + if (!(this.params.abstractOk || !(await cs.isAbstract(loc.context)))) { + this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is abstract', this.indentCount); + if (!this.params.membershipOnly) { + op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'ABSTRACT_CODE_NOT_ALLOWED', this.worker.i18n.translate('ABSTRACT_CODE_NOT_ALLOWED', this.params.HTTPLanguages, [cs.system(), code]), 'code-rule')); } - vcc.coding.push({ - system: cs.system(), - version: cs.version(), - code: await cs.code(loc.context), - display: displays.preferredDisplay(this.params.workingLanguages()) - }); + } else if (this.excludeInactives() && await cs.isInactive(loc.context)) { + this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is inactive', this.indentCount); + let msg = this.worker.i18n.translate('STATUS_CODE_WARNING_CODE', this.params.HTTPLanguages, ['not active', code]); + op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'STATUS_CODE_WARNING_CODE', msg, 'code-rule')); + result = false; + messages.push(msg); + if (!this.params.membershipOnly) { + inactive.value = true; + inactive.path = path; + if (inactive.value) { + vstatus.value = await cs.getStatus(loc.context); + } + } + } else if (this.params.activeOnly && await cs.isInactive(loc.context)) { + this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is inactive', this.indentCount); + result = false; + inactive.value = true; + inactive.path = path; + vstatus.value = await cs.getStatus(loc.context); + let msg = this.worker.i18n.translate('STATUS_CODE_WARNING_CODE', this.params.HTTPLanguages, ['not active', code]); + messages.push(msg); + op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'STATUS_CODE_WARNING_CODE', msg, 'code-rule')); + } else { + result = true; + inactive.value = await cs.isInactive(loc.context); + inactive.path = path; + vstatus.value = await cs.getStatus(loc.context); + + if (vcc !== null) { + if (!vcc.coding) { + vcc.coding = []; + } + vcc.coding.push({ + system: cs.system(), + version: cs.version(), + code: await cs.code(loc.context), + display: displays.preferredDisplay(this.params.workingLanguages()) + }); + } + return result; } - return result; } } @@ -1863,13 +1872,14 @@ class ValidateWorker extends TerminologyWorker { let coded; let mode; - try { - // Handle tx-resource and cache-id parameters - this.setupAdditionalResources(params); + // Handle tx-resource and cache-id parameters + this.setupAdditionalResources(params); - let txp = new TxParameters(this.languages, this.i18n, true); - txp.readParams(params); - for (const item of txp.supplements) this.requiredSupplements.add(item); + let txp = new TxParameters(this.languages, this.i18n, true); + txp.readParams(params); + for (const item of txp.supplements) this.requiredSupplements.add(item); + + try { // Extract coded value mode = {mode: null}; @@ -1902,7 +1912,7 @@ class ValidateWorker extends TerminologyWorker { this.log.error(error); this.debugLog(error); if (error instanceof Issue && !error.isHandleAsOO()) { - return this.handlePrepareError(error, coded, mode.mode); + return await this.handlePrepareError(error, coded, mode.mode, txp); } else { throw error; } @@ -2304,7 +2314,7 @@ class ValidateWorker extends TerminologyWorker { if (!(error instanceof Issue) || error.isHandleAsOO()) { throw error; } else { - return this.handlePrepareError(error, coded, mode); + return await this.handlePrepareError(error, coded, mode, params); } } @@ -2417,7 +2427,7 @@ class ValidateWorker extends TerminologyWorker { } - handlePrepareError(error, coded, mode) { + async handlePrepareError(error, coded, mode, txp) { let op = new OperationOutcome(); op.addIssue(error); let p = new Parameters(); @@ -2434,6 +2444,15 @@ class ValidateWorker extends TerminologyWorker { } if (coded.coding[0].version) { p.addParamStr('version', coded.coding[0].version) + } else if (coded.coding[0].system) { + try { + let cs = await this.findCodeSystem(coded.coding[0].system, null, txp); + if (cs && cs.version()) { + p.addParamStr('version', cs.version()); + } + } catch (e) { + // nothing. not interested. + } } if (coded.coding[0].code) { p.addParamCode('code', coded.coding[0].code)