fhirsmith 0.11.2 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +377 -2
- package/README.md +1 -1
- package/config-template.json +31 -1
- package/library/languages.js +191 -34
- package/library/path-safety.js +51 -0
- package/library/request-token.js +56 -0
- package/package.json +2 -2
- package/packages/packages.js +17 -118
- package/publisher/publisher.js +208 -30
- package/publisher/validation.js +210 -0
- package/server.js +24 -15
- package/stats-db.js +127 -0
- package/stats.js +172 -6
- package/translations/Messages.properties +5 -1
- package/tx/README.md +21 -0
- package/tx/cs/cs-api.js +30 -1
- package/tx/cs/cs-api.md +1 -1
- package/tx/cs/cs-cs.js +1 -1
- package/tx/cs/cs-icd11.js +805 -0
- package/tx/cs/cs-lang.js +331 -85
- package/tx/cs/cs-loinc.js +45 -4
- package/tx/cs/cs-mimetypes.js +492 -21
- package/tx/cs/cs-snomed.js +141 -58
- package/tx/cs/cs-ucum.js +33 -3
- package/tx/cs/icd11-expressions.js +243 -0
- package/tx/importers/export-icd11-codesystem.js +823 -0
- package/tx/importers/icd11-schema.md +285 -0
- package/tx/importers/import-icd11.js +861 -0
- package/tx/importers/import-loinc.module.js +14 -7
- package/tx/importers/import-sct.module.js +10 -1
- package/tx/library/codesystem.js +21 -27
- package/tx/library/conceptmap.js +39 -0
- package/tx/library/nesting-limit.js +79 -0
- package/tx/library/operation-outcome.js +97 -3
- package/tx/library/parameter-check.js +139 -0
- package/tx/library/valueset.js +16 -20
- package/tx/library.js +23 -1
- package/tx/ocl/cache/cache-utils.cjs +5 -3
- package/tx/ocl/cs-ocl.cjs +73 -17
- package/tx/ocl/jobs/background-queue.cjs +6 -3
- package/tx/ocl/mappers/concept-mapper.cjs +9 -1
- package/tx/ocl/shared/patches.cjs +48 -1
- package/tx/ocl/vs-ocl.cjs +15 -13
- package/tx/operation-context.js +220 -10
- package/tx/params.js +226 -175
- package/tx/sct/expressions.js +677 -15
- package/tx/tx.fhir.org.yml +3 -2
- package/tx/tx.js +72 -92
- package/tx/workers/batch-validate.js +11 -18
- package/tx/workers/batch.js +16 -28
- package/tx/workers/cache-control.js +87 -17
- package/tx/workers/closure.js +3 -8
- package/tx/workers/compare.js +6 -34
- package/tx/workers/expand.js +108 -60
- package/tx/workers/lookup.js +35 -40
- package/tx/workers/read.js +6 -40
- package/tx/workers/search.js +2 -8
- package/tx/workers/subsumes.js +58 -41
- package/tx/workers/translate.js +472 -47
- package/tx/workers/validate.js +83 -78
- package/tx/workers/worker.js +92 -11
- package/tx/xml/xml-base.js +264 -77
- package/tx/xversion/xv-conceptmap.js +81 -0
- package/tx/xversion/xv-parameters.js +8 -0
- package/tx/xversion/xv-valueset.js +39 -1
- package/utilities/merge-audit.py +178 -0
- package/utilities/why-merge-audit.md +39 -0
- package/tx/data/snomed-subset.txt +0 -84
package/tx/workers/validate.js
CHANGED
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
// POST /ValueSet/{id}/$validate-code
|
|
12
12
|
//
|
|
13
13
|
|
|
14
|
-
const { TerminologyWorker } = require('./worker');
|
|
14
|
+
const { TerminologyWorker, Unknown_Code_in_VersionSCT, SCTVersion } = require('./worker');
|
|
15
15
|
const {Languages, Language} = require("../../library/languages");
|
|
16
16
|
const {Extensions} = require("../library/extensions");
|
|
17
17
|
const {validateParameter, isAbsoluteUrl, validateOptionalParameter, getValuePrimitive} = require("../../library/utilities");
|
|
18
18
|
const {TxParameters} = require("../params");
|
|
19
|
-
const {OperationOutcome, Issue} = require(
|
|
19
|
+
const { OperationOutcome, Issue, buildOperationOutcome, outcomeFromError } = require('../library/operation-outcome');
|
|
20
20
|
const {Parameters} = require("../library/parameters");
|
|
21
21
|
const {Designations, DisplayCheckingStyle, DisplayDifference, SearchFilterText} = require("../library/designations");
|
|
22
22
|
const ValueSet = require("../library/valueset");
|
|
@@ -293,6 +293,8 @@ class ValueSetChecker {
|
|
|
293
293
|
}
|
|
294
294
|
}
|
|
295
295
|
}
|
|
296
|
+
// R6's ValueSet.compose.property
|
|
297
|
+
this.params.seeCompose(this.valueSet.jsonObj.compose);
|
|
296
298
|
if (!this.params.HTTPLanguages && this.valueSet.jsonObj.language) {
|
|
297
299
|
this.params.HTTPLanguages = Languages.fromAcceptLanguage(this.valueSet.jsonObj.language, this.worker.languages, false);
|
|
298
300
|
}
|
|
@@ -951,7 +953,7 @@ class ValueSetChecker {
|
|
|
951
953
|
let msg = this.worker.i18n.translate('INACTIVE_CONCEPT_FOUND', this.params.HTTPLanguages, [vstatus.value, coding.code]);
|
|
952
954
|
messages.push(msg);
|
|
953
955
|
op.addIssue(new Issue('warning', 'business-rule', path, 'INACTIVE_CONCEPT_FOUND', msg, 'code-comment'));
|
|
954
|
-
} else if (vstatus.value.toLowerCase() === 'deprecated') {
|
|
956
|
+
} else if (vstatus.value && vstatus.value.toLowerCase() === 'deprecated') {
|
|
955
957
|
result.addParamCode('status', vstatus.value);
|
|
956
958
|
let msg = this.worker.i18n.translate('DEPRECATED_CONCEPT_FOUND', this.params.HTTPLanguages, [vstatus.value, coding.code]);
|
|
957
959
|
messages.push(msg);
|
|
@@ -1049,6 +1051,7 @@ class ValueSetChecker {
|
|
|
1049
1051
|
this.checkCanonicalStatus(issuePath, op, this.valueSet.jsonObj, this.valueSet.jsonObj);
|
|
1050
1052
|
let list = new Designations(this.worker.languages);
|
|
1051
1053
|
let ok = false;
|
|
1054
|
+
let matchedCodings = new Set();
|
|
1052
1055
|
let codelist = '';
|
|
1053
1056
|
mt = [];
|
|
1054
1057
|
let i = 0;
|
|
@@ -1080,6 +1083,9 @@ class ValueSetChecker {
|
|
|
1080
1083
|
if (v === false) {
|
|
1081
1084
|
cause.value = 'code-invalid';
|
|
1082
1085
|
}
|
|
1086
|
+
if (v === true) {
|
|
1087
|
+
matchedCodings.add(i);
|
|
1088
|
+
}
|
|
1083
1089
|
let ws;
|
|
1084
1090
|
if (impliedSystem.value) {
|
|
1085
1091
|
ws = impliedSystem.value;
|
|
@@ -1247,6 +1253,17 @@ class ValueSetChecker {
|
|
|
1247
1253
|
c.version = prov.version();
|
|
1248
1254
|
}
|
|
1249
1255
|
await this.worker.listDisplaysFromCodeSystem(list, prov, ctxt.context);
|
|
1256
|
+
// the code resolved in the code system, so its status is known whether or not it
|
|
1257
|
+
// turns out to be in the value set. check() only picks the status up on the paths
|
|
1258
|
+
// where the code was found in the value set, so pick it up here for the rest
|
|
1259
|
+
if (!this.params.membershipOnly && !inactive.value && await prov.isInactive(ctxt.context)) {
|
|
1260
|
+
inactive.value = true;
|
|
1261
|
+
inactive.path = path;
|
|
1262
|
+
const st = await prov.getStatus(ctxt.context);
|
|
1263
|
+
if (st) {
|
|
1264
|
+
vstatus.value = st;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1250
1267
|
let pd = list.preferredDisplay(this.params.workingLanguages());
|
|
1251
1268
|
if (pd) {
|
|
1252
1269
|
pdisp = pd;
|
|
@@ -1288,6 +1305,36 @@ class ValueSetChecker {
|
|
|
1288
1305
|
}
|
|
1289
1306
|
i++;
|
|
1290
1307
|
}
|
|
1308
|
+
|
|
1309
|
+
// A required binding on a CodeableConcept is satisfied when ANY coding is in the value set, so
|
|
1310
|
+
// a coding whose code system could not be resolved must not veto a membership that another
|
|
1311
|
+
// coding has confirmed. It is still reported, but as a warning, and with a message that does
|
|
1312
|
+
// not read as though nothing could be validated (hapifhir/org.hl7.fhir.core#2272).
|
|
1313
|
+
if (issuePath === 'CodeableConcept' && matchedCodings.size > 0) {
|
|
1314
|
+
const notChecked = ['UNKNOWN_CODESYSTEM', 'UNKNOWN_CODESYSTEM_VERSION', 'UNKNOWN_CODESYSTEM_VERSION_NONE'];
|
|
1315
|
+
let j = 0;
|
|
1316
|
+
for (let c of code.coding || []) {
|
|
1317
|
+
if (!matchedCodings.has(j)) {
|
|
1318
|
+
let sysPath = issuePath + '.coding[' + j + '].system';
|
|
1319
|
+
for (let iss of op.jsonObj.issue || []) {
|
|
1320
|
+
let mid = (iss.extension || []).find(e => e.url === 'http://hl7.org/fhir/StructureDefinition/operationoutcome-message-id');
|
|
1321
|
+
if (iss.severity === 'error' && mid && notChecked.includes(mid.valueString) && (iss.expression || []).includes(sysPath)) {
|
|
1322
|
+
let was = iss.details.text;
|
|
1323
|
+
let now = this.worker.i18n.translate('UNKNOWN_CODESYSTEM_CODING_NOT_CHECKED', this.params.HTTPLanguages,
|
|
1324
|
+
[c.version ? c.system + '|' + c.version : c.system]);
|
|
1325
|
+
iss.severity = 'warning';
|
|
1326
|
+
iss.details.text = now;
|
|
1327
|
+
mid.valueString = 'UNKNOWN_CODESYSTEM_CODING_NOT_CHECKED';
|
|
1328
|
+
// the summary message is built from mt, so it has to say the same thing
|
|
1329
|
+
mt = mt.filter(t => t !== was);
|
|
1330
|
+
msg(now);
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
j++;
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1291
1338
|
// for an internally defined value set (CodeSystem validation), the code-system-level
|
|
1292
1339
|
// message is preferred - but if nothing produced an error, this is the only
|
|
1293
1340
|
// explanation there will be, so it can't be suppressed
|
|
@@ -1321,7 +1368,10 @@ class ValueSetChecker {
|
|
|
1321
1368
|
}
|
|
1322
1369
|
}
|
|
1323
1370
|
|
|
1324
|
-
|
|
1371
|
+
// the answer, which is not the same as ok: a coding can be in the value set and still carry an
|
|
1372
|
+
// error of its own (an unknown version, say), and then the CodeableConcept is not valid
|
|
1373
|
+
let valid = ok === true && !op.hasErrors();
|
|
1374
|
+
result.addParamBool('result', valid);
|
|
1325
1375
|
if (psys) {
|
|
1326
1376
|
result.addParamUri('system', psys);
|
|
1327
1377
|
} else if (ok === true && impliedSystem.value) {
|
|
@@ -1333,9 +1383,10 @@ class ValueSetChecker {
|
|
|
1333
1383
|
for (let us of unknownSystems) {
|
|
1334
1384
|
if (ok === false) {
|
|
1335
1385
|
result.addParamCanonical('x-unknown-system', us);
|
|
1336
|
-
} else {
|
|
1386
|
+
} else if (!valid) {
|
|
1337
1387
|
result.addParamCanonical('x-caused-by-unknown-system', us);
|
|
1338
1388
|
}
|
|
1389
|
+
// if the CodeableConcept is valid, the unknown system caused nothing: neither parameter applies
|
|
1339
1390
|
}
|
|
1340
1391
|
if (normalForm.value) {
|
|
1341
1392
|
result.addParamCode('normalized-code', normalForm.value);
|
|
@@ -1478,7 +1529,11 @@ class ValueSetChecker {
|
|
|
1478
1529
|
list.allowedDisplays(ts2, null, defLang.value);
|
|
1479
1530
|
let mid = 'INACTIVE_DISPLAY_FOUND';
|
|
1480
1531
|
let m = this.worker.i18n.translatePlural(ts2.length, mid, this.params.HTTPLanguages, [c.display, c.code, ts2.join(','), list.status(c.display)]);
|
|
1481
|
-
|
|
1532
|
+
// The display IS a designation of this concept, just not a current one,
|
|
1533
|
+
// so lenient-display-validation decides whether that fails the code:
|
|
1534
|
+
// a warning (and result true) when lenient, an error when not - the
|
|
1535
|
+
// same rule the other display checks use.
|
|
1536
|
+
op.addIssue(new Issue(this.dispWarning(), 'invalid', addToPath(path, 'display'), mid, m, 'display-comment'));
|
|
1482
1537
|
}
|
|
1483
1538
|
}
|
|
1484
1539
|
}
|
|
@@ -1529,7 +1584,7 @@ class ValueSetChecker {
|
|
|
1529
1584
|
let msg = this.worker.i18n.translate('INACTIVE_CONCEPT_FOUND', this.params.HTTPLanguages, [vstatus.value, code]);
|
|
1530
1585
|
messages.push(msg);
|
|
1531
1586
|
op.addIssue(new Issue('warning', 'business-rule', 'code', 'INACTIVE_CONCEPT_FOUND', msg, 'code-comment'));
|
|
1532
|
-
} else if (vstatus.value.toLowerCase() === 'deprecated') {
|
|
1587
|
+
} else if (vstatus.value && vstatus.value.toLowerCase() === 'deprecated') {
|
|
1533
1588
|
result.addParamCode('status', vstatus.value);
|
|
1534
1589
|
let msg = this.worker.i18n.translate('DEPRECATED_CONCEPT_FOUND', this.params.HTTPLanguages, [vstatus.value, code]);
|
|
1535
1590
|
messages.push(msg);
|
|
@@ -1563,7 +1618,7 @@ class ValueSetChecker {
|
|
|
1563
1618
|
let loc = await cs.locate(code);
|
|
1564
1619
|
result = false;
|
|
1565
1620
|
if (loc.context == null) {
|
|
1566
|
-
this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" not found in ' + this.worker.renderer.displayCoded(cs)+": "+loc.
|
|
1621
|
+
this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" not found in ' + this.worker.renderer.displayCoded(cs)+": "+loc.message, this.indentCount);
|
|
1567
1622
|
if (!this.params.membershipOnly && role !== 'not in') {
|
|
1568
1623
|
if (cs.contentMode() !== 'complete') {
|
|
1569
1624
|
op.addIssue(new Issue('warning', 'code-invalid', addToPath(path, 'code'), 'UNKNOWN_CODE_IN_FRAGMENT', this.worker.i18n.translate('UNKNOWN_CODE_IN_FRAGMENT', this.params.HTTPLanguages, [code, cs.system(), cs.version()]), 'invalid-code'));
|
|
@@ -1739,6 +1794,19 @@ class ValueSetChecker {
|
|
|
1739
1794
|
if (!vcc.coding) { vcc.coding = []}
|
|
1740
1795
|
vcc.coding.push( { system : cs.system(), version: cs.version(), code: await cs.code(loc), display: displays.preferredDisplay(this.params.workingLanguages())});
|
|
1741
1796
|
}
|
|
1797
|
+
if (role !== 'not in' && !inactive.value) {
|
|
1798
|
+
if (await cs.isInactive(loc)) {
|
|
1799
|
+
inactive.value = true;
|
|
1800
|
+
inactive.path = path;
|
|
1801
|
+
// only replace a status we already have with a real one - a filter context
|
|
1802
|
+
// doesn't always carry the status (SNOMED, LOINC), and the assembly reads
|
|
1803
|
+
// vstatus for the message wording as well as for the status parameter
|
|
1804
|
+
const st = await cs.getStatus(loc);
|
|
1805
|
+
if (st) {
|
|
1806
|
+
vstatus.value = st;
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1742
1810
|
result = true;
|
|
1743
1811
|
return result;
|
|
1744
1812
|
}
|
|
@@ -1869,56 +1937,6 @@ function addToPath(path, name) {
|
|
|
1869
1937
|
}
|
|
1870
1938
|
|
|
1871
1939
|
|
|
1872
|
-
function Unknown_Code_in_VersionSCT(url, version) {
|
|
1873
|
-
if (url === 'http://snomed.info/sct') {
|
|
1874
|
-
return 'Unknown_Code_in_Version_SCT';
|
|
1875
|
-
} else if (version) {
|
|
1876
|
-
return 'Unknown_Code_in_Version';
|
|
1877
|
-
} else {
|
|
1878
|
-
return 'Unknown_Code_in';
|
|
1879
|
-
}
|
|
1880
|
-
}
|
|
1881
|
-
|
|
1882
|
-
function SCTVersion(url, ver) {
|
|
1883
|
-
if (url !== 'http://snomed.info/sct' || !ver) {
|
|
1884
|
-
return '';
|
|
1885
|
-
} else {
|
|
1886
|
-
let result = 'unknown';
|
|
1887
|
-
let s = ver.split('/');
|
|
1888
|
-
if (s.length >= 5) {
|
|
1889
|
-
if (s[4] === '900000000000207008') result = 'International Edition';
|
|
1890
|
-
else if (s[4] === '449081005') result = 'International Spanish Edition';
|
|
1891
|
-
else if (s[4] === '11000221109') result = 'Argentinian Edition';
|
|
1892
|
-
else if (s[4] === '32506021000036107') result = 'Australian Edition (with drug extension)';
|
|
1893
|
-
else if (s[4] === '11000234105') result = 'Austrian Edition';
|
|
1894
|
-
else if (s[4] === '11000172109') result = 'Belgian Edition';
|
|
1895
|
-
else if (s[4] === '20621000087109') result = 'Canadian English Edition';
|
|
1896
|
-
else if (s[4] === '20611000087101') result = 'Canadian Canadian French Edition';
|
|
1897
|
-
else if (s[4] === '11000279109') result = 'Czech Edition';
|
|
1898
|
-
else if (s[4] === '554471000005108') result = 'Danish Edition';
|
|
1899
|
-
else if (s[4] === '11000181102') result = 'Estonian Edition';
|
|
1900
|
-
else if (s[4] === '11000229106') result = 'Finnish Edition';
|
|
1901
|
-
else if (s[4] === '11000274103') result = 'German Edition';
|
|
1902
|
-
else if (s[4] === '1121000189102') result = 'Indian Edition';
|
|
1903
|
-
else if (s[4] === '827022005') result = 'IPS Terminology';
|
|
1904
|
-
else if (s[4] === '11000220105') result = 'Irish Edition';
|
|
1905
|
-
else if (s[4] === '11000146104') result = 'Netherlands Edition';
|
|
1906
|
-
else if (s[4] === '21000210109') result = 'New Zealand Edition';
|
|
1907
|
-
else if (s[4] === '51000202101') result = 'Norwegian Edition';
|
|
1908
|
-
else if (s[4] === '11000267109') result = 'Republic of Korea Edition (South Korea)';
|
|
1909
|
-
else if (s[4] === '900000001000122104') result = 'Spanish National Edition';
|
|
1910
|
-
else if (s[4] === '45991000052106') result = 'Swedish Edition';
|
|
1911
|
-
else if (s[4] === '2011000195101') result = 'Swiss Edition';
|
|
1912
|
-
else if (s[4] === '83821000000107') result = 'UK Edition';
|
|
1913
|
-
else if (s[4] === '999000021000000109') result = 'UK Clinical Edition';
|
|
1914
|
-
else if (s[4] === '5631000179106') result = 'Uruguay Edition';
|
|
1915
|
-
else if (s[4] === '21000325107') result = 'Chilean Edition';
|
|
1916
|
-
else if (s[4] === '731000124108') result = 'US Edition';
|
|
1917
|
-
else if (s[4] === '5991000124107') result = 'US Edition (with ICD-10-CM maps)';
|
|
1918
|
-
}
|
|
1919
|
-
return result;
|
|
1920
|
-
}
|
|
1921
|
-
}
|
|
1922
1940
|
|
|
1923
1941
|
|
|
1924
1942
|
function toText(st, sep) {
|
|
@@ -1984,8 +2002,7 @@ class ValidateWorker extends TerminologyWorker {
|
|
|
1984
2002
|
// this is actually handled in the inner method
|
|
1985
2003
|
}
|
|
1986
2004
|
} else {
|
|
1987
|
-
return res.status(error.statusCode || 500).json(
|
|
1988
|
-
'error', error.issueCode || 'exception', error.message));
|
|
2005
|
+
return res.status(error.statusCode || 500).json(outcomeFromError(error));
|
|
1989
2006
|
}
|
|
1990
2007
|
|
|
1991
2008
|
}
|
|
@@ -2093,8 +2110,7 @@ class ValidateWorker extends TerminologyWorker {
|
|
|
2093
2110
|
} catch (error) {
|
|
2094
2111
|
this.log.error(error);
|
|
2095
2112
|
debugLog(error);
|
|
2096
|
-
return res.status(error.statusCode || 500).json(
|
|
2097
|
-
'error', error.issueCode || 'exception', error.message));
|
|
2113
|
+
return res.status(error.statusCode || 500).json(outcomeFromError(error));
|
|
2098
2114
|
}
|
|
2099
2115
|
}
|
|
2100
2116
|
|
|
@@ -2119,8 +2135,7 @@ class ValidateWorker extends TerminologyWorker {
|
|
|
2119
2135
|
op.addIssue(error);
|
|
2120
2136
|
return res.status(error.statusCode || 500).json(op.jsonObj);
|
|
2121
2137
|
} else {
|
|
2122
|
-
return res.status(error.statusCode || 500).json(
|
|
2123
|
-
'error', error.issueCode || 'exception', error.message));
|
|
2138
|
+
return res.status(error.statusCode || 500).json(outcomeFromError(error));
|
|
2124
2139
|
}
|
|
2125
2140
|
}
|
|
2126
2141
|
}
|
|
@@ -2194,8 +2209,7 @@ class ValidateWorker extends TerminologyWorker {
|
|
|
2194
2209
|
} catch (error) {
|
|
2195
2210
|
this.log.error(error);
|
|
2196
2211
|
debugLog(error);
|
|
2197
|
-
return res.status(error.statusCode || 500).json(
|
|
2198
|
-
'error', error.issueCode || 'exception', error.message));
|
|
2212
|
+
return res.status(error.statusCode || 500).json(outcomeFromError(error));
|
|
2199
2213
|
}
|
|
2200
2214
|
}
|
|
2201
2215
|
|
|
@@ -2535,18 +2549,9 @@ class ValidateWorker extends TerminologyWorker {
|
|
|
2535
2549
|
/**
|
|
2536
2550
|
* Build an OperationOutcome
|
|
2537
2551
|
*/
|
|
2538
|
-
operationOutcome(severity, code, message) {
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
issue: [{
|
|
2542
|
-
severity,
|
|
2543
|
-
code,
|
|
2544
|
-
details: {
|
|
2545
|
-
text: message
|
|
2546
|
-
},
|
|
2547
|
-
diagnostics: message
|
|
2548
|
-
}]
|
|
2549
|
-
};
|
|
2552
|
+
operationOutcome(severity, code, message, txIssueType = null) {
|
|
2553
|
+
// the shared builder, so that every outcome has details.text and a tx-issue-type coding
|
|
2554
|
+
return buildOperationOutcome(severity, code, message, txIssueType);
|
|
2550
2555
|
}
|
|
2551
2556
|
|
|
2552
2557
|
|
package/tx/workers/worker.js
CHANGED
|
@@ -137,9 +137,10 @@ class TerminologyWorker {
|
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
const found = matches[latest];
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
140
|
+
// enable for logging
|
|
141
|
+
// if (this.additionalResourcesCacheId && this.log) {
|
|
142
|
+
// this.log.info(`cache-id '${this.additionalResourcesCacheId}': using cached ${found.resourceType} ${found.url}${found.version ? '|' + found.version : ''} for lookup of ${url}${version ? '|' + version : ''}`);
|
|
143
|
+
// }
|
|
143
144
|
return found;
|
|
144
145
|
}
|
|
145
146
|
}
|
|
@@ -462,7 +463,9 @@ class TerminologyWorker {
|
|
|
462
463
|
/**
|
|
463
464
|
* Process a ValueSet, recording context and extracting embedded expansion parameters
|
|
464
465
|
* @param {Object} vs - ValueSet resource (raw JSON)
|
|
465
|
-
* @param {
|
|
466
|
+
* @param {TxParameters} params - the parameters to add what the ValueSet declares to.
|
|
467
|
+
* Note: this is the caller's parameters, not this.params - the worker's own
|
|
468
|
+
* params is null when expanding an imported ValueSet
|
|
466
469
|
*/
|
|
467
470
|
seeValueSet(vs, params) {
|
|
468
471
|
// Build canonical URL from url and version
|
|
@@ -482,12 +485,14 @@ class TerminologyWorker {
|
|
|
482
485
|
if (nameExt && valueExt) {
|
|
483
486
|
const name = nameExt.valueString || nameExt.valueCode;
|
|
484
487
|
if (name) {
|
|
485
|
-
|
|
488
|
+
params.seeParameter(name, valueExt, false);
|
|
486
489
|
}
|
|
487
490
|
}
|
|
488
491
|
}
|
|
489
492
|
}
|
|
490
493
|
}
|
|
494
|
+
// R6's ValueSet.compose.property
|
|
495
|
+
params.seeCompose(vs.jsonObj.compose);
|
|
491
496
|
if (!params.FHTTPLanguages && vs.jsonObj.language) {
|
|
492
497
|
params.HTTPLanguages = Languages.fromAcceptLanguage(vs.jsonObj.language, this.languages, !this.isValidating());
|
|
493
498
|
}
|
|
@@ -666,9 +671,12 @@ class TerminologyWorker {
|
|
|
666
671
|
// The cache must already exist: caches are created explicitly via
|
|
667
672
|
// $cache-control?mode=start, which is the only thing that mints a cache-id.
|
|
668
673
|
// A cache-id the server doesn't know is an unambiguous, server-authoritative
|
|
669
|
-
// error condition
|
|
670
|
-
//
|
|
671
|
-
//
|
|
674
|
+
// error condition - report it with a specific coded issue rather than silently
|
|
675
|
+
// auto-creating a fresh cache and then failing obscurely later when a
|
|
676
|
+
// by-reference resource can't be found. The cache keeps a tombstone for every
|
|
677
|
+
// id it has retired, so describeMissing() names which fate this id met
|
|
678
|
+
// (never issued here / closed by the client / idle-expired) instead of
|
|
679
|
+
// offering the client a list of possibilities to guess between.
|
|
672
680
|
if (!this.opContext.resourceCache.has(cacheId)) {
|
|
673
681
|
// A comma in the cache-id means the request carried multiple X-Cache-Id
|
|
674
682
|
// headers (Node joins duplicate headers with ", "): server-minted ids are
|
|
@@ -684,8 +692,12 @@ class TerminologyWorker {
|
|
|
684
692
|
'started a second cache on a connection that already had one, without removing the first header.',
|
|
685
693
|
'cache-id-duplicate', 400);
|
|
686
694
|
}
|
|
687
|
-
|
|
688
|
-
|
|
695
|
+
// The issue *code* stays cache-id-unknown for every fate: clients switch on
|
|
696
|
+
// the coding, and all three mean the same thing to them (this cache is gone,
|
|
697
|
+
// don't retry). Only the human-readable diagnostics differ.
|
|
698
|
+
const { messageId, params } = this.opContext.resourceCache.describeMissing(cacheId);
|
|
699
|
+
throw new Issue('error', 'not-found', null, messageId,
|
|
700
|
+
this.i18n.translate(messageId, this.opContext.langs, params),
|
|
689
701
|
'cache-id-unknown', 404);
|
|
690
702
|
}
|
|
691
703
|
|
|
@@ -1050,8 +1062,77 @@ class TerminologyWorker {
|
|
|
1050
1062
|
}
|
|
1051
1063
|
}
|
|
1052
1064
|
|
|
1065
|
+
|
|
1066
|
+
/**
|
|
1067
|
+
* Choose the message id to use when reporting an unknown code, based on the
|
|
1068
|
+
* code system and whether a version is known. SNOMED CT gets its own message
|
|
1069
|
+
* because the edition is named in the message (see SCTVersion below)
|
|
1070
|
+
* @param {string} url - code system url
|
|
1071
|
+
* @param {string} version - code system version (may be null)
|
|
1072
|
+
* @returns {string} message id
|
|
1073
|
+
*/
|
|
1074
|
+
function Unknown_Code_in_VersionSCT(url, version) {
|
|
1075
|
+
if (url === 'http://snomed.info/sct') {
|
|
1076
|
+
return 'Unknown_Code_in_Version_SCT';
|
|
1077
|
+
} else if (version) {
|
|
1078
|
+
return 'Unknown_Code_in_Version';
|
|
1079
|
+
} else {
|
|
1080
|
+
return 'Unknown_Code_in';
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
/**
|
|
1085
|
+
* Human readable name for the SNOMED CT edition in a SNOMED CT version URI.
|
|
1086
|
+
* Returns '' for anything that isn't a versioned SNOMED CT reference
|
|
1087
|
+
* @param {string} url - code system url
|
|
1088
|
+
* @param {string} ver - code system version
|
|
1089
|
+
* @returns {string} edition name, or ''
|
|
1090
|
+
*/
|
|
1091
|
+
function SCTVersion(url, ver) {
|
|
1092
|
+
if (url !== 'http://snomed.info/sct' || !ver) {
|
|
1093
|
+
return '';
|
|
1094
|
+
} else {
|
|
1095
|
+
let result = 'unknown';
|
|
1096
|
+
let s = ver.split('/');
|
|
1097
|
+
if (s.length >= 5) {
|
|
1098
|
+
if (s[4] === '900000000000207008') result = 'International Edition';
|
|
1099
|
+
else if (s[4] === '449081005') result = 'International Spanish Edition';
|
|
1100
|
+
else if (s[4] === '11000221109') result = 'Argentinian Edition';
|
|
1101
|
+
else if (s[4] === '32506021000036107') result = 'Australian Edition (with drug extension)';
|
|
1102
|
+
else if (s[4] === '11000234105') result = 'Austrian Edition';
|
|
1103
|
+
else if (s[4] === '11000172109') result = 'Belgian Edition';
|
|
1104
|
+
else if (s[4] === '20621000087109') result = 'Canadian English Edition';
|
|
1105
|
+
else if (s[4] === '20611000087101') result = 'Canadian Canadian French Edition';
|
|
1106
|
+
else if (s[4] === '11000279109') result = 'Czech Edition';
|
|
1107
|
+
else if (s[4] === '554471000005108') result = 'Danish Edition';
|
|
1108
|
+
else if (s[4] === '11000181102') result = 'Estonian Edition';
|
|
1109
|
+
else if (s[4] === '11000229106') result = 'Finnish Edition';
|
|
1110
|
+
else if (s[4] === '11000274103') result = 'German Edition';
|
|
1111
|
+
else if (s[4] === '1121000189102') result = 'Indian Edition';
|
|
1112
|
+
else if (s[4] === '827022005') result = 'IPS Terminology';
|
|
1113
|
+
else if (s[4] === '11000220105') result = 'Irish Edition';
|
|
1114
|
+
else if (s[4] === '11000146104') result = 'Netherlands Edition';
|
|
1115
|
+
else if (s[4] === '21000210109') result = 'New Zealand Edition';
|
|
1116
|
+
else if (s[4] === '51000202101') result = 'Norwegian Edition';
|
|
1117
|
+
else if (s[4] === '11000267109') result = 'Republic of Korea Edition (South Korea)';
|
|
1118
|
+
else if (s[4] === '900000001000122104') result = 'Spanish National Edition';
|
|
1119
|
+
else if (s[4] === '45991000052106') result = 'Swedish Edition';
|
|
1120
|
+
else if (s[4] === '2011000195101') result = 'Swiss Edition';
|
|
1121
|
+
else if (s[4] === '83821000000107') result = 'UK Edition';
|
|
1122
|
+
else if (s[4] === '999000021000000109') result = 'UK Clinical Edition';
|
|
1123
|
+
else if (s[4] === '5631000179106') result = 'Uruguay Edition';
|
|
1124
|
+
else if (s[4] === '21000325107') result = 'Chilean Edition';
|
|
1125
|
+
else if (s[4] === '731000124108') result = 'US Edition';
|
|
1126
|
+
else if (s[4] === '5991000124107') result = 'US Edition (with ICD-10-CM maps)';
|
|
1127
|
+
}
|
|
1128
|
+
return result;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1053
1132
|
module.exports = {
|
|
1054
1133
|
TerminologyWorker,
|
|
1055
1134
|
TerminologySetupError,
|
|
1056
|
-
CACHE_ID_HEADER
|
|
1135
|
+
CACHE_ID_HEADER,
|
|
1136
|
+
Unknown_Code_in_VersionSCT,
|
|
1137
|
+
SCTVersion
|
|
1057
1138
|
};
|