Repository: FHIR/sushi
Pull request #653 (2020-10-27 20:22:36)

Must Support does not automatically transfer from sliced elements to slices
Completes task [CIMPL-570](https://standardhealthrecord.atlassian.net/browse/CIMPL-570).

When setting `mustSupport = true` on an element with slices, do not automatically set `mustSupport = true` on the slices.
When `mustSupport = true` is set on a slice, include this in the differential even if the sliced element also has `mustSupport = true`.


Patch:
diff --git a/src/fhirtypes/ElementDefinition.ts b/src/fhirtypes/ElementDefinition.ts
index 7093c95..e6d1f10 100644
--- a/src/fhirtypes/ElementDefinition.ts
+++ b/src/fhirtypes/ElementDefinition.ts
@@ -1025,7 +1025,12 @@ export class ElementDefinition {
     const connectedElements = this.findConnectedElements();
     if (mustSupport === true) {
       this.mustSupport = mustSupport;
-      connectedElements.forEach(ce => (ce.mustSupport = mustSupport || ce.mustSupport));
+      // MS only gets applied to connected elements that are not themselves slices
+      // For example, Observation.component.interpretation MS implies Observation.component:Lab.interpretation MS
+      // But Observation.component MS does not imply Observation.component:Lab MS
+      connectedElements
+        .filter(ce => ce.sliceName == null)
+        .forEach(ce => (ce.mustSupport = mustSupport || ce.mustSupport));
     }
     if (summary === true) {
       this.isSummary = summary;
@@ -1867,10 +1872,11 @@ export class ElementDefinition {
       throw new DuplicateSliceError(this.structDef.name, this.id, name);
     }
 
-    // On a new slice, delete slice.min and slice.max and then reset them
+    // On a new slice, delete slice.min, slice.max, and slice.mustSupport. Then, reset slice.min and slice.max
     // so that they are always captured in diff
     delete slice.min;
     delete slice.max;
+    delete slice.mustSupport;
 
     // Capture the original so that the differential only contains changes from this point on.
     slice.captureOriginal();


Test patch:
diff --git a/test/export/StructureDefinitionExporter.test.ts b/test/export/StructureDefinitionExporter.test.ts
index 272b81c..d459e12 100644
--- a/test/export/StructureDefinitionExporter.test.ts
+++ b/test/export/StructureDefinitionExporter.test.ts
@@ -3772,9 +3772,12 @@ describe('StructureDefinitionExporter', () => {
     });
 
     it('should apply a FlagRule on a sliced element that updates the flags on its slices', () => {
-      // * component MS
+      // * component MS ?!
+      // modifier should be applied to the slices, but must support is not applied to slices.
+      // see https://confluence.hl7.org/display/FHIR/Guide+to+Designing+Resources
       const rootFlag = new FlagRule('component');
       rootFlag.mustSupport = true;
+      rootFlag.modifier = true;
 
       observationWithSlice.rules.push(rootFlag);
       doc.profiles.set(observationWithSlice.name, observationWithSlice);
@@ -3783,12 +3786,16 @@ describe('StructureDefinitionExporter', () => {
       const rootComponent = sd.findElement('Observation.component');
       const labComponent = sd.findElement('Observation.component:Lab');
       expect(rootComponent.mustSupport).toBe(true);
-      expect(labComponent.mustSupport).toBe(true);
+      expect(labComponent.mustSupport).toBeFalsy();
+      expect(rootComponent.isModifier).toBe(true);
+      expect(labComponent.isModifier).toBe(true);
     });
 
     it('should apply a FlagRule on the child of a sliced element that updates the flags on the child of a slice', () => {
       // * component[Lab].interpretation 0..1 // this forces the creation of the unfolded slice
       // * component.interpretation MS
+      // must support is applied to children of slices, in contrast to previous test case
+      // again, see https://confluence.hl7.org/display/FHIR/Guide+to+Designing+Resources
       const labCard = new CardRule('component[Lab].interpretation');
       labCard.min = 0;
       labCard.max = '1';
@@ -4540,6 +4547,29 @@ describe('StructureDefinitionExporter', () => {
     ]);
   });
 
+  it('should include mustSupport in the differential of a new slice, even if the base element is also mustSupport', () => {
+    const profile = new Profile('MustSlice');
+    profile.parent = 'resprate';
+    const mustCode = new FlagRule('code.coding');
+    mustCode.mustSupport = true;
+    const codeSlice = new ContainsRule('code.coding');
+    codeSlice.items = [{ name: 'OxygenCode' }];
+    const mustSlice = new FlagRule('code.coding[OxygenCode]');
+    mustSlice.mustSupport = true;
+    profile.rules.push(mustCode, codeSlice, mustSlice);
+    exporter.exportStructDef(profile);
+    const sd = pkg.profiles[0];
+    const json = sd.toJSON();
+    expect(json.differential.element).toContainEqual({
+      id: 'Observation.code.coding:OxygenCode',
+      path: 'Observation.code.coding',
+      sliceName: 'OxygenCode',
+      min: 0,
+      max: '*',
+      mustSupport: true
+    });
+  });
+
   it('should include the children of primitive elements when serializing to JSON', () => {
     const profile = new Profile('SpecialUrlId');
     profile.parent = 'Observation';