fhirsmith 0.11.1 → 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.
@@ -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
- this.defineProperty(expansion, n, this.getPropUrl(cs, pn, cp), pn, vn, v);
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
- defineProperty(expansion, contains, url, code, valueName, value) {
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
- if (!expansion.property) {
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 = contains.property.find(t2 => t2.code == code);
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);
@@ -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) {