Repository: FHIR/sushi (ts)

Issue:
Duplicate meta.profile values when profiles assign meta.profile value
When a profile assigns a meta.profile value (meaning all instances must have it), SUSHI will generate instances that have the profile value twice.  This is likely because the instance exporter generates it based on the fixed value rule, but also generates it automatically based on the `InstanceOf:` metadata.

This was seen in the carin-bb IG, but can be replicated with [this FSH code](https://fshschool.org/FSHOnline/#/share/2LQcQms):
```
Profile: MyPatientAbstract
Parent: Patient
Id: MyPatientAbstract
Title: "My Patient Abstract"
Description: "Simple example test Abstract"
* meta 1..1 MS
* meta.profile 1..* MS

* meta.profile ^slicing.discriminator.type = #pattern
* meta.profile ^slicing.discriminator.path = "$this"
* meta.profile contains supportedPatientProfile 1..1 MS



Profile: MyPatient
Parent: MyPatientAbstract
Id: MyPatient
* meta.profile[supportedPatientProfile] = Canonical(MyPatient)


Instance: MyPatientInstance
InstanceOf: MyPatient
Usage: #example
* name[0].family = "LastName"
```

Resulting in this instance:
```json
{
  "resourceType": "Patient",
  "id": "MyPatientInstance",
  "meta": {
    "profile": [
      "http://example.org/StructureDefinition/MyPatient",
      "http://example.org/StructureDefinition/MyPatient"
    ]
  },
  "name": [
    {
      "family": "LastName"
    }
  ]
}
```

Pull request:
Fix Duplicate meta.profile Values
This PR fixes #737 and [CIMPL-652](https://standardhealthrecord.atlassian.net/browse/CIMPL-652).

The issue has an example of how to replicate this bug on `master`. The test that I added also replicates this example.

There were duplicate values in `meta.profile` because the `InstanceExporter` set the `meta.profile` value based on the `InstanceOf` profile at the start of the extractor. Then later in the extractor, it also sets values based on implied values on the parent. In the example in the issue, the `InstanceOf` profile has `meta.profile` set to be `1..1` and it assigns the value. The InstanceExporter sees this as a rule we should imply and set on the Instance, and so it does.

I considered a few different ways to solve this, but settled on de-duplicating `meta.profile` values in the InstanceExporter after we finish assigning all the values. This problem is pretty specific to `meta.profile` on instances, so I thought it made sense to keep it within the exporter, and I don't think there should ever be a case when we want a duplicate.

Patch:
diff --git a/src/export/InstanceExporter.ts b/src/export/InstanceExporter.ts
index 70dabe2..846b2da 100644
--- a/src/export/InstanceExporter.ts
+++ b/src/export/InstanceExporter.ts
@@ -13,7 +13,7 @@ import {
 } from '../fhirtypes/common';
 import { InstanceOfNotDefinedError } from '../errors/InstanceOfNotDefinedError';
 import { Package } from '.';
-import { cloneDeep } from 'lodash';
+import { cloneDeep, uniq } from 'lodash';
 import { AssignmentRule } from '../fshtypes/rules';
 
 export class InstanceExporter implements Fishable {
@@ -300,6 +300,9 @@ export class InstanceExporter implements Fishable {
     );
     cleanResource(instanceDef);
 
+    // Once all rules are set, we should ensure that we did not add a duplicate profile URL anywhere
+    if (instanceDef.meta?.profile) instanceDef.meta.profile = uniq(instanceDef.meta.profile);
+
     // check for another instance of the same type with the same id
     // see https://www.hl7.org/fhir/resource.html#id
     // instanceDef has already been added to the package, so it's fine if it matches itself


Test patch:
diff --git a/test/export/InstanceExporter.test.ts b/test/export/InstanceExporter.test.ts
index 11dafb8..ca3f8ad 100644
--- a/test/export/InstanceExporter.test.ts
+++ b/test/export/InstanceExporter.test.ts
@@ -14,11 +14,12 @@ import {
 } from '../../src/fshtypes';
 import {
   AssignmentRule,
-  ContainsRule,
   CardRule,
-  OnlyRule,
   CaretValueRule,
-  InsertRule
+  ContainsRule,
+  FlagRule,
+  InsertRule,
+  OnlyRule
 } from '../../src/fshtypes/rules';
 import { loggerSpy, TestFisher } from '../testhelpers';
 import { InstanceDefinition } from '../../src/fhirtypes';
@@ -193,6 +194,71 @@ describe('InstanceExporter', () => {
       expect(exported.meta).toBeUndefined();
     });
 
+    it('should only set meta.profile with one profile when profile is set on the InstanceOf profile', () => {
+      const patientAbstractProfile = new Profile('MyPatientAbstract');
+      patientAbstractProfile.parent = 'Patient';
+      /*
+       * meta 1..1 MS
+       * meta.profile 1..* MS
+       * meta.profile ^slicing.discriminator.type = #pattern
+       * meta.profile ^slicing.discriminator.path = "$this"
+       * meta.profile contains supportedPatientProfile 1..1
+       */
+      const metaMS = new FlagRule('meta');
+      metaMS.mustSupport = true;
+      const metaCard = new CardRule('meta');
+      metaCard.min = 1;
+      metaCard.max = '1';
+      const metaProfileMS = new FlagRule('meta.profile');
+      metaProfileMS.mustSupport = true;
+      const metaProfileCard = new CardRule('meta.profile');
+      metaProfileCard.min = 1;
+      metaProfileCard.max = '1';
+      const typeCaretRule = new CaretValueRule('meta.profile');
+      typeCaretRule.caretPath = 'slicing.discriminator.type';
+      typeCaretRule.value = new FshCode('pattern');
+      const pathCaretRule = new CaretValueRule('item');
+      pathCaretRule.caretPath = 'slicing.discriminator.path';
+      pathCaretRule.value = 'type';
+      const containsRule = new ContainsRule('meta.profile');
+      containsRule.items = [{ name: 'supportedPatientProfile' }];
+      const cardRule = new CardRule('meta.profile[supportedPatientProfile]');
+      cardRule.min = 1;
+      cardRule.max = '1';
+      patientAbstractProfile.rules.push(
+        metaMS,
+        metaCard,
+        metaProfileMS,
+        metaProfileCard,
+        typeCaretRule,
+        pathCaretRule,
+        containsRule,
+        cardRule
+      );
+
+      const patientProfile = new Profile('MyPatient');
+      patientProfile.parent = 'MyPatientAbstract';
+      // * meta.profile[supportedPatientProfile] = Canonical(MyPatient)
+      const metaProfileAssignment = new AssignmentRule('meta.profile[supportedPatientProfile]');
+      metaProfileAssignment.value = new FshCanonical('MyPatient');
+      patientProfile.rules.push(metaProfileAssignment);
+      doc.profiles.set(patientAbstractProfile.name, patientAbstractProfile);
+      doc.profiles.set(patientProfile.name, patientProfile);
+
+      const patientInstance = new Instance('MyPatientInstance');
+      patientInstance.instanceOf = 'MyPatient';
+      // * name[0].family = "LastName"
+      const patientAssignmentRule = new AssignmentRule('name[0].family');
+      patientAssignmentRule.value = 'LastName';
+      patientInstance.rules.push(patientAssignmentRule);
+
+      const exported = exportInstance(patientInstance);
+      expect(exported.meta.profile).toHaveLength(1);
+      expect(exported.meta.profile).toEqual([
+        'http://hl7.org/fhir/us/minimal/StructureDefinition/MyPatient'
+      ]);
+    });
+
     // Setting instance id
     it('should set id to instance name by default', () => {
       const myExamplePatient = new Instance('MyExample');