Repository: FHIR/sushi (ts)

Issue:
Slicing anomaly in instance of US Core Organization
There something wrong with the attached example. 

[Organization-mCODEOrganizationExample01.json.txt](https://github.com/FHIR/sushi/files/4098514/Organization-mCODEOrganizationExample01.json.txt)

The IG Publisher complains that:

`Practitioner | error | Unrecognised property '@identifier:NPI'`

Here's the FSH - it is pretty straightforward:

```
Instance: mCODEOrganizationExample01
InstanceOf: USCoreOrganization
* id = "mCODEOrganizationExample01"
* identifier[NPI].value = "1265714091"
* active = true
* name = "Foundation Medicine"
* contact.telecom.system = #phone
* contact.telecom.value = "617-418-2200"
* address.line = "150 Second Street"
* address.city = "Cambridge"
* address.state = "MA"
* address.postalCode = "02141"
* address.country = "USA"
```




Pull request description:
Set values from FHIR on slices
Fixes #121, which is also tracked by https://standardhealthrecord.atlassian.net/browse/CIMPL-245.

We were not correctly handling the case that a Structure Definition from which we are constructing an Instance would have existing values fixed to a slice element. This PR adds capability to handle when a value is fixed directly on a slice, and not on children of that slice.

To further test this, first build fsh-mcode on master using SUSHI on master. You will see a number of errors that look like: `error: Cannot resolve element from path: category:Laboratory`. Now build fsh-mcode using SUSHI on this branch. First, you should notice those errors go away. Second, you should notice that the `identifier` field in Organization-mCODEOrganizationExample01.json file is generated correctly as this:
```
"identifier": [
    {
      "system": "http://hl7.org/fhir/sid/us-npi",
      "value": "1265714091"
    },
    {
      "system": "urn:oid:2.16.840.1.113883.4.7"
    }
  ]
```
instead of this:
```
"identifier": [
    {
      "value": "1265714091",
      "system": "http://hl7.org/fhir/sid/us-npi"
    }
  ]
```
Finally, you should be able to go into CancerGenomicsExamples.fsh and remove all the lines that have been marked as a workaround for issue 148 (which was a duplicate of the issue addressed here), and you should still get the same output as when those lines were present (down to the ordering of elements in the generated SDs).

Patch:
diff --git a/src/export/InstanceExporter.ts b/src/export/InstanceExporter.ts
index 70130d6..41aca33 100644
--- a/src/export/InstanceExporter.ts
+++ b/src/export/InstanceExporter.ts
@@ -102,16 +102,23 @@ export class InstanceExporter {
       const fixedValueKey = Object.keys(child).find(
         k => k.startsWith('fixed') || k.startsWith('pattern')
       );
-      if (fixedValueKey) {
+      if (fixedValueKey && child.min > 0) {
         // Get the end of the child path, this is the part that differs from existingPath
-        const childPathPart = child
+        const childPath = child
           .diffId()
           .split('.')
           .slice(-1)[0];
-
+        // Turn FHIR slicing (element:slicName/resliceName) into FSH slicing (element[sliceName][resliceName])
+        const colonSplitPath = childPath.split(':');
+        let fshChildPath = colonSplitPath[0];
+        const slicePathSection = colonSplitPath[1];
+        const sliceNames = slicePathSection?.split('/');
+        sliceNames?.forEach(s => {
+          fshChildPath += `[${s}]`;
+        });
         try {
           const { fixedValue, pathParts } = instanceOfStructureDefinition.validateValueAtPath(
-            existingPath + childPathPart,
+            existingPath + fshChildPath,
             child[fixedValueKey as keyof ElementDefinition],
             this.fisher
           );
@@ -119,6 +126,10 @@ export class InstanceExporter {
         } catch (e) {
           logger.error(e.message);
         }
+      } else if (fixedValueKey) {
+        logger.debug(
+          `Element ${child.id} is optional with min cardinality 0, so fixed value for optional element is not set on instance ${instanceDef.instanceName}`
+        );
       }
     }
   }
diff --git a/src/fhirtypes/common.ts b/src/fhirtypes/common.ts
index 3cba033..7951e5b 100644
--- a/src/fhirtypes/common.ts
+++ b/src/fhirtypes/common.ts
@@ -71,9 +71,14 @@ export function setPropertyOnInstance(
           if (j < current[key].length && j === index && current[key][index] == null) {
             current[key][index] = {};
           } else if (j >= current[key].length) {
-            // _sliceName is used to later differentiate which slice an element represents
-            const arrayFiller = sliceName ? { _sliceName: sliceName } : null;
-            current[key].push(j === index ? {} : arrayFiller);
+            if (sliceName) {
+              // _sliceName is used to later differentiate which slice an element represents
+              current[key].push({ _sliceName: sliceName });
+            } else if (j === index) {
+              current[key].push({});
+            } else {
+              current[key].push(null);
+            }
           }
         }
         // If it isn't the last element, move on, if it is, set the value
@@ -83,7 +88,11 @@ export function setPropertyOnInstance(
             current._sliceName = sliceName;
           }
         } else {
-          current[key][index] = fixedValue;
+          if (typeof fixedValue === 'object') {
+            Object.assign(current[key][index], fixedValue);
+          } else {
+            current[key][index] = fixedValue;
+          }
         }
       } else {
         // If it isn't the last element, move on, if it is, set the value


Test patch:
diff --git a/test/export/InstanceExporter.test.ts b/test/export/InstanceExporter.test.ts
index 145c955..bdffa30 100644
--- a/test/export/InstanceExporter.test.ts
+++ b/test/export/InstanceExporter.test.ts
@@ -196,6 +196,9 @@ describe('InstanceExporter', () => {
 
     // Fixing top level elements
     it('should fix top level elements that are fixed on the Structure Definition', () => {
+      const cardRule = new CardRule('active');
+      cardRule.min = 1;
+      patient.rules.push(cardRule);
       const fixedValRule = new FixedValueRule('active');
       fixedValRule.fixedValue = true;
       patient.rules.push(fixedValRule);
@@ -203,6 +206,17 @@ describe('InstanceExporter', () => {
       expect(exported.active).toEqual(true);
     });
 
+    it('should not fix optional elements that are fixed on the Structure Definition', () => {
+      const fixedValRule = new FixedValueRule('active');
+      fixedValRule.fixedValue = true;
+      patient.rules.push(fixedValRule);
+      const exported = exportInstance(instance);
+      expect(exported.active).toBeUndefined();
+      expect(loggerSpy.getLastMessage()).toMatch(
+        'Element Patient.active is optional with min cardinality 0, so fixed value for optional element is not set on instance Bar'
+      );
+    });
+
     it('should fix top level elements to an array even if constrained on the Structure Definition', () => {
       const condition = new Profile('TestCondition');
       condition.parent = 'Condition';
@@ -232,6 +246,9 @@ describe('InstanceExporter', () => {
     });
 
     it('should fix top level elements that are fixed by a pattern on the Structure Definition', () => {
+      const cardRule = new CardRule('maritalStatus');
+      cardRule.min = 1;
+      patient.rules.push(cardRule);
       const fixedValRule = new FixedValueRule('maritalStatus');
       const fixedFshCode = new FshCode('foo', 'http://foo.com');
       fixedValRule.fixedValue = fixedFshCode;
@@ -242,7 +259,7 @@ describe('InstanceExporter', () => {
       });
     });
 
-    it('should fix a value onto a elements that are fixed by a pattern on the Structure Definition', () => {
+    it('should fix a value onto an element that are fixed by a pattern on the Structure Definition', () => {
       const observation = new Profile('TestObservation');
       observation.parent = 'Observation';
       doc.profiles.set(observation.name, observation);
@@ -253,6 +270,9 @@ describe('InstanceExporter', () => {
       const fixedFshCode = new FshCode('foo', 'http://foo.com');
       fixedValRule.fixedValue = fixedFshCode;
       observation.rules.push(fixedValRule); // * valueQuantity = foo.com#foo
+      const cardRule = new CardRule('valueQuantity');
+      cardRule.min = 1;
+      observation.rules.push(cardRule); // * valueQuantity 1..1
       const observationInstance = new Instance('MyObservation');
       observationInstance.instanceOf = 'TestObservation';
       const fixedQuantityValue = new FixedValueRule('valueQuantity.value');
@@ -268,10 +288,44 @@ describe('InstanceExporter', () => {
       });
     });
 
+    it('should fix a value onto slice elements that are fixed by a pattern on the Structure Definition', () => {
+      const resprate = new Profile('TestResprate');
+      resprate.parent = 'resprate';
+      doc.profiles.set(resprate.name, resprate);
+      const containsRule = new ContainsRule('category');
+      containsRule.items = ['niceSlice'];
+      resprate.rules.push(containsRule); // * identifier contains niceSlice
+      const cardRule = new CardRule('category[niceSlice]');
+      cardRule.min = 1;
+      cardRule.max = '*';
+      resprate.rules.push(cardRule); // * category[niceSlice] 1..*
+      const fixedValRule = new FixedValueRule('category[niceSlice]');
+      const fixedFshCode = new FshCode('rice', 'http://spice.com');
+      fixedValRule.fixedValue = fixedFshCode;
+      resprate.rules.push(fixedValRule); // * category[niceSlice] = http://spice.com#rice
+      const resprateInstance = new Instance('myResprate');
+      resprateInstance.instanceOf = 'TestResprate';
+      doc.instances.set(resprateInstance.name, resprateInstance);
+      const exported = exportInstance(resprateInstance);
+      expect(exported.category).toEqual([
+        {
+          coding: [
+            {
+              code: 'rice',
+              system: 'http://spice.com'
+            }
+          ]
+        }
+      ]);
+    });
+
     it('should fix top level choice elements that are fixed on the Structure Definition', () => {
       const fixedValRule = new FixedValueRule('deceasedBoolean');
       fixedValRule.fixedValue = true;
       patient.rules.push(fixedValRule);
+      const cardRule = new CardRule('deceasedBoolean');
+      cardRule.min = 1;
+      patient.rules.push(cardRule);
       const exported = exportInstance(instance);
       expect(exported.deceasedBoolean).toBe(true);
     });
@@ -306,6 +360,9 @@ describe('InstanceExporter', () => {
       const fixedValRule = new FixedValueRule('active');
       fixedValRule.fixedValue = true;
       patient.rules.push(fixedValRule);
+      const cardRule = new CardRule('active');
+      cardRule.min = 1;
+      patient.rules.push(cardRule);
       const instanceFixedValRule = new FixedValueRule('active');
       instanceFixedValRule.fixedValue = false;
       instance.rules.push(instanceFixedValRule);
@@ -321,6 +378,9 @@ describe('InstanceExporter', () => {
       const fixedFshCode = new FshCode('foo', 'http://foo.com');
       fixedValRule.fixedValue = fixedFshCode;
       patient.rules.push(fixedValRule);
+      const cardRule = new CardRule('maritalStatus');
+      cardRule.min = 1;
+      patient.rules.push(cardRule);
       const instanceFixedValRule = new FixedValueRule('maritalStatus');
       const instanceFixedFshCode = new FshCode('bar', 'http://bar.com');
       instanceFixedValRule.fixedValue = instanceFixedFshCode;
@@ -337,6 +397,9 @@ describe('InstanceExporter', () => {
 
     // Nested elements
     it('should fix a nested element that has parents defined in the instance and is fixed on the Structure Definition', () => {
+      const cardRule = new CardRule('communication.preferred');
+      cardRule.min = 1;
+      patient.rules.push(cardRule);
       const fixedValRule = new FixedValueRule('communication.preferred');
       fixedValRule.fixedValue = true;
       patient.rules.push(fixedValRule);
@@ -351,6 +414,9 @@ describe('InstanceExporter', () => {
     });
 
     it('should fix a nested element that has parents and children defined in the instance and is fixed on the Structure Definition', () => {
+      const cardRule = new CardRule('communication.language.text');
+      cardRule.min = 1;
+      patient.rules.push(cardRule);
       const fixedValRule = new FixedValueRule('communication.language.text');
       fixedValRule.fixedValue = 'foo';
       patient.rules.push(fixedValRule);
@@ -378,6 +444,10 @@ describe('InstanceExporter', () => {
       cardRule.min = 1;
       cardRule.max = '1';
       patient.rules.push(cardRule);
+      const cardRuleRelationship = new CardRule('contact.relationship');
+      cardRuleRelationship.min = 1;
+      cardRuleRelationship.max = '*';
+      patient.rules.push(cardRuleRelationship);
       const fixedValRule = new FixedValueRule('contact.relationship');
       fixedValRule.fixedValue = new FshCode('mother');
       patient.rules.push(fixedValRule);