fhirsmith 0.11.2 → 0.12.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 +72 -2
- package/config-template.json +18 -1
- package/package.json +1 -1
- package/publisher/publisher.js +153 -5
- package/publisher/validation.js +198 -0
- package/server.js +12 -12
- package/stats-db.js +127 -0
- package/stats.js +172 -6
- package/translations/Messages.properties +4 -1
- package/tx/cs/cs-api.md +1 -1
- package/tx/cs/cs-snomed.js +103 -54
- package/tx/library/conceptmap.js +39 -0
- package/tx/library/parameter-check.js +139 -0
- package/tx/operation-context.js +220 -10
- package/tx/params.js +226 -175
- package/tx/sct/expressions.js +485 -0
- package/tx/tx.fhir.org.yml +1 -1
- package/tx/tx.js +57 -50
- package/tx/workers/batch-validate.js +5 -2
- package/tx/workers/cache-control.js +82 -6
- package/tx/workers/expand.js +80 -23
- package/tx/workers/lookup.js +14 -1
- package/tx/workers/translate.js +461 -30
- package/tx/workers/validate.js +7 -1
- package/tx/workers/worker.js +22 -10
- package/tx/xml/xml-base.js +21 -1
- package/tx/xversion/xv-conceptmap.js +81 -0
- package/tx/xversion/xv-parameters.js +8 -0
- 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/expand.js
CHANGED
|
@@ -460,7 +460,9 @@ class ValueSetExpander {
|
|
|
460
460
|
if (cp.code === pn) {
|
|
461
461
|
let vn = getValueName(cp);
|
|
462
462
|
let v = cp[vn];
|
|
463
|
-
|
|
463
|
+
// a concept may carry the same property more than once (the
|
|
464
|
+
// "dup" property in the test code system has two values)
|
|
465
|
+
this.defineProperty(expansion, n, this.getPropUrl(cs, pn, cp), pn, vn, v, true);
|
|
464
466
|
}
|
|
465
467
|
}
|
|
466
468
|
}
|
|
@@ -571,19 +573,24 @@ class ValueSetExpander {
|
|
|
571
573
|
}
|
|
572
574
|
this.checkResourceCanonicalStatus(expansion, vs, this.valueSet);
|
|
573
575
|
|
|
576
|
+
const imported = [];
|
|
574
577
|
for (const c of vs.expansion.contains || []) {
|
|
575
578
|
await this.worker.checkAndYield('importValueSet');
|
|
576
|
-
count += await this.importValueSetItem(null, c, imports, offset);
|
|
579
|
+
count += await this.importValueSetItem(null, c, imports, offset, imported);
|
|
577
580
|
}
|
|
581
|
+
this.importPropertyDefinitions(expansion, vs.expansion.property, imported);
|
|
578
582
|
return count;
|
|
579
583
|
}
|
|
580
584
|
|
|
581
|
-
async importValueSetItem(p, c, imports, offset) {
|
|
585
|
+
async importValueSetItem(p, c, imports, offset, imported) {
|
|
582
586
|
let count = 0;
|
|
583
587
|
await this.worker.checkAndYield('importValueSetItem');
|
|
584
588
|
const s = this.keyC(c);
|
|
585
589
|
if (this.passesImports(imports, c.system, c.code, offset) && !this.map.has(s) && !this.isExcluded(c.system, c.version, c.code)) {
|
|
586
590
|
count++;
|
|
591
|
+
if (imported) {
|
|
592
|
+
imported.push(c);
|
|
593
|
+
}
|
|
587
594
|
this.fullList.push(c);
|
|
588
595
|
if (p != null) {
|
|
589
596
|
if (!p.contains) {p.contains = [] }
|
|
@@ -595,7 +602,7 @@ class ValueSetExpander {
|
|
|
595
602
|
}
|
|
596
603
|
for (const cc of c.contains || []) {
|
|
597
604
|
await this.worker.checkAndYield('importValueSetItem');
|
|
598
|
-
count += await this.importValueSetItem(c, cc, imports, offset);
|
|
605
|
+
count += await this.importValueSetItem(c, cc, imports, offset, imported);
|
|
599
606
|
}
|
|
600
607
|
return count;
|
|
601
608
|
}
|
|
@@ -1679,35 +1686,85 @@ class ValueSetExpander {
|
|
|
1679
1686
|
return this.keyS(contains.system, contains.version, contains.code);
|
|
1680
1687
|
}
|
|
1681
1688
|
|
|
1682
|
-
|
|
1689
|
+
/**
|
|
1690
|
+
* Record a property definition in expansion.property, and return the code to
|
|
1691
|
+
* use for the per-concept value (an existing definition's code wins).
|
|
1692
|
+
*
|
|
1693
|
+
* @param {Object} expansion
|
|
1694
|
+
* @param {string} url
|
|
1695
|
+
* @param {string} code
|
|
1696
|
+
* @returns {string}
|
|
1697
|
+
*/
|
|
1698
|
+
declareProperty(expansion, url, code) {
|
|
1699
|
+
if (!expansion.property) {
|
|
1700
|
+
expansion.property = [];
|
|
1701
|
+
}
|
|
1702
|
+
let pd = expansion.property.find(t1 => t1.uri == url || t1.code == code);
|
|
1703
|
+
if (!pd) {
|
|
1704
|
+
pd = {};
|
|
1705
|
+
expansion.property.push(pd);
|
|
1706
|
+
pd.uri = url;
|
|
1707
|
+
pd.code = code;
|
|
1708
|
+
} else if (!pd.uri) {
|
|
1709
|
+
pd.uri = url;
|
|
1710
|
+
}
|
|
1711
|
+
if (pd.uri != url) {
|
|
1712
|
+
throw new Error('URL mismatch on expansion: ' + pd.uri + ' vs ' + url + ' for code ' + code);
|
|
1713
|
+
}
|
|
1714
|
+
return pd.code;
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
/**
|
|
1718
|
+
* Carry the property definitions of an imported expansion across to this one.
|
|
1719
|
+
*
|
|
1720
|
+
* Imported concepts are copied whole (see importValueSetItem), properties and
|
|
1721
|
+
* all, but the definitions that give those properties a uri live on the
|
|
1722
|
+
* imported expansion, so without this the outer expansion has concepts
|
|
1723
|
+
* carrying a property that it never declares. Only definitions for properties
|
|
1724
|
+
* that survived the import are copied.
|
|
1725
|
+
*
|
|
1726
|
+
* @param {Object} expansion - the expansion being built
|
|
1727
|
+
* @param {Array} definitions - the imported expansion's `property`
|
|
1728
|
+
* @param {Array} items - the concepts actually taken from the import
|
|
1729
|
+
*/
|
|
1730
|
+
importPropertyDefinitions(expansion, definitions, items) {
|
|
1731
|
+
if (!expansion || !definitions || definitions.length === 0 || items.length === 0) {
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1734
|
+
const used = new Set();
|
|
1735
|
+
for (const item of items) {
|
|
1736
|
+
for (const property of item.property || []) {
|
|
1737
|
+
used.add(property.code);
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
for (const definition of definitions) {
|
|
1741
|
+
if (definition.uri && used.has(definition.code)) {
|
|
1742
|
+
this.declareProperty(expansion, definition.uri, definition.code);
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
/**
|
|
1748
|
+
* @param {boolean} [repeatable] - true when the code system may carry more than
|
|
1749
|
+
* one value for this property on one concept (only the code system property
|
|
1750
|
+
* loop passes this). Single-valued properties keep last-one-wins, so a status
|
|
1751
|
+
* derived from two sources still yields one entry.
|
|
1752
|
+
*/
|
|
1753
|
+
defineProperty(expansion, contains, url, code, valueName, value, repeatable) {
|
|
1683
1754
|
if (value === undefined || value == null) {
|
|
1684
1755
|
return;
|
|
1685
1756
|
}
|
|
1686
1757
|
// we only define it if the code system has a definition
|
|
1687
1758
|
if (url) {
|
|
1688
|
-
|
|
1689
|
-
expansion.property = [];
|
|
1690
|
-
}
|
|
1691
|
-
let pd = expansion.property.find(t1 => t1.uri == url || t1.code == code);
|
|
1692
|
-
if (!pd) {
|
|
1693
|
-
pd = {};
|
|
1694
|
-
expansion.property.push(pd);
|
|
1695
|
-
pd.uri = url;
|
|
1696
|
-
pd.code = code;
|
|
1697
|
-
} else if (!pd.uri) {
|
|
1698
|
-
pd.uri = url
|
|
1699
|
-
}
|
|
1700
|
-
if (pd.uri != url) {
|
|
1701
|
-
throw new Error('URL mismatch on expansion: ' + pd.uri + ' vs ' + url + ' for code ' + code);
|
|
1702
|
-
} else {
|
|
1703
|
-
code = pd.code;
|
|
1704
|
-
}
|
|
1759
|
+
code = this.declareProperty(expansion, url, code);
|
|
1705
1760
|
}
|
|
1706
1761
|
|
|
1707
1762
|
if (!contains.property) {
|
|
1708
1763
|
contains.property = [];
|
|
1709
1764
|
}
|
|
1710
|
-
let pdv =
|
|
1765
|
+
let pdv = repeatable
|
|
1766
|
+
? contains.property.find(t2 => t2.code == code && t2[valueName] === value)
|
|
1767
|
+
: contains.property.find(t2 => t2.code == code);
|
|
1711
1768
|
if (!pdv) {
|
|
1712
1769
|
pdv = {};
|
|
1713
1770
|
contains.property.push(pdv);
|
package/tx/workers/lookup.js
CHANGED
|
@@ -14,6 +14,7 @@ const {TxParameters} = require("../params");
|
|
|
14
14
|
const {Parameters} = require("../library/parameters");
|
|
15
15
|
const {Issue, OperationOutcome} = require("../library/operation-outcome");
|
|
16
16
|
const {debugLog} = require("../operation-context");
|
|
17
|
+
const {checkAddedParameters} = require("../library/parameter-check");
|
|
17
18
|
|
|
18
19
|
class LookupWorker extends TerminologyWorker {
|
|
19
20
|
/**
|
|
@@ -393,8 +394,20 @@ class LookupWorker extends TerminologyWorker {
|
|
|
393
394
|
}
|
|
394
395
|
}
|
|
395
396
|
|
|
396
|
-
// Let the provider add additional properties
|
|
397
|
+
// Let the provider add additional properties. Some providers pass content
|
|
398
|
+
// through from a remote API, where a value can be any JSON at all, so what
|
|
399
|
+
// comes back is checked before it goes out: a structure where a FHIR
|
|
400
|
+
// primitive belongs is a provider bug, and shipping it would produce a
|
|
401
|
+
// response that isn't valid FHIR (and, in XML, isn't even parseable).
|
|
402
|
+
const providerParamsFrom = responseParams.length;
|
|
397
403
|
await csProvider.extendLookup(ctxt, params.properties || [], responseParams);
|
|
404
|
+
let providerName;
|
|
405
|
+
try {
|
|
406
|
+
providerName = csProvider.system();
|
|
407
|
+
} catch (e) {
|
|
408
|
+
providerName = 'unknown';
|
|
409
|
+
}
|
|
410
|
+
checkAddedParameters(responseParams, providerParamsFrom, providerName);
|
|
398
411
|
|
|
399
412
|
if (reportedSupplements) {
|
|
400
413
|
for (const supplement of reportedSupplements) {
|