Repository: FHIR/sushi (ts)

Issue:
SUSHI should allow extensions on primitives to satisfy cardinality
Reported by Julian Sass on zulip [here](https://chat.fhir.org/#narrow/stream/215610-shorthand/topic/Error.20extension.20on.20element.20w.2F.20min.201.20cardinality):

SUSHI compiler throws an error for instances that use an extension like data-absent-reason on elements that have a min 1 cardinality defined in a profile. E.g.:

```
Profile: MyProcedure
Parent: Procedure
* performed[x] 1..1

Instance: my-procedure
InstanceOf: MyProcedure
* status = #not-done
* subject = Reference(Patient/example)
* performedDateTime.extension.url = "http://hl7.org/fhir/StructureDefinition/data-absent-reason"
* performedDateTime.extension.valueCode = #not-performed
```

produces
> error Element Procedure.performed[x] has minimum cardinality 1 but occurs 0 time(s).

I think the error should not be triggered.

https://fshschool.org/FSHOnline/#/share/34xsyMF

Pull request:
Allow underscore-prefixed primitives to satisfy cardinality requirements
Fixes #1021 and completes task [CIMPL-902](https://standardhealthrecord.atlassian.net/browse/CIMPL-902).

This issue turns out to have been specific to primitive-type choice element slices. When checking choice elements for a required element, use the underscore-prefixed version if it exists. This is consistent with the existing logic for cases that are not choice elements.

Patch:
diff --git a/src/export/InstanceExporter.ts b/src/export/InstanceExporter.ts
index d34276f..f763c93 100644
--- a/src/export/InstanceExporter.ts
+++ b/src/export/InstanceExporter.ts
@@ -219,7 +219,8 @@ export class InstanceExporter implements Fishable {
           .forEach(ce => possibleChoiceSlices.push(...ce.children(true)));
         const choiceSlices = possibleChoiceSlices.filter(c => c.path === child.path && c.sliceName);
         for (const choiceSlice of choiceSlices) {
-          instanceChild = instance[choiceSlice.sliceName];
+          // as above, we use the _ prefixed element if it exists
+          instanceChild = instance[`_${choiceSlice.sliceName}`] ?? instance[choiceSlice.sliceName];
           if (instanceChild != null) {
             // Once we find the the choiceSlice that matches, use it as the child
             child = choiceSlice;


Test patch:
diff --git a/test/export/InstanceExporter.test.ts b/test/export/InstanceExporter.test.ts
index c17cecd..ae50528 100644
--- a/test/export/InstanceExporter.test.ts
+++ b/test/export/InstanceExporter.test.ts
@@ -3151,6 +3151,53 @@ describe('InstanceExporter', () => {
       );
     });
 
+    it('should not log an error when a required choice element has an extension on a complex type choice', () => {
+      // Profile: TestObservation
+      // Parent: Observation
+      // * effective[x] 1..1
+      const observationProfile = new Profile('TestObservation');
+      observationProfile.parent = 'Observation';
+      const cardRule = new CardRule('effective[x]');
+      cardRule.min = 1;
+      cardRule.max = '1';
+      observationProfile.rules.push(cardRule);
+      doc.profiles.set(observationProfile.name, observationProfile);
+      // Instance: HasExtension
+      // InstanceOf: TestObservation
+      // * status = #final
+      // * code = #1234
+      // * effectiveQuantity.extension.url = "http://example.org/SomeExtension"
+      // * effectiveQuantity.extension.valueInteger = 7
+      const observationInstance = new Instance('HasExtension');
+      observationInstance.instanceOf = 'TestObservation';
+      const statusRule = new AssignmentRule('status');
+      statusRule.value = new FshCode('final');
+      const codeRule = new AssignmentRule('code');
+      codeRule.value = new FshCode('1234');
+      const extensionUrlRule = new AssignmentRule('effectivePeriod.extension.url');
+      extensionUrlRule.value = 'http://example.org/SomeExtension';
+      const extensionValueRule = new AssignmentRule('effectivePeriod.extension.valueInteger');
+      extensionValueRule.value = 7;
+      observationInstance.rules.push(statusRule, codeRule, extensionUrlRule, extensionValueRule);
+      doc.instances.set(observationInstance.name, observationInstance);
+      exportInstance(observationInstance);
+      expect(loggerSpy.getAllMessages('error')).toHaveLength(0);
+    });
+
+    it('should not log an error when a required choice element has an extension on a primitive type choice', () => {
+      const cardRule = new CardRule('deceased[x]');
+      cardRule.min = 1;
+      cardRule.max = '1';
+      patient.rules.push(cardRule);
+      const extensionUrl = new AssignmentRule('deceasedBoolean.extension.url');
+      extensionUrl.value = 'http://example.org/StrutureDefinition/SomeExtension';
+      const extensionValue = new AssignmentRule('deceasedBoolean.extension.valueCode');
+      extensionValue.value = new FshCode('complicated');
+      patientInstance.rules.push(extensionUrl, extensionValue);
+      exportInstance(patientInstance);
+      expect(loggerSpy.getAllMessages('error')).toHaveLength(0);
+    });
+
     it('should log an error when a required primitive child element is not present', () => {
       const cardRule1 = new CardRule('active.id');
       cardRule1.min = 1;