Repository: FHIR/sushi (ts)

Issue:
Handle "Age" when assigned FSHCode values
The line:
```
* condition.onsetAge = UCUM#a "years"
```
fails to set these values on `onsetAge` with the error:
```
error Cannot bind value set to Age; must be coded (code, Coding, CodeableConcept, Quantity), or the data types (string, uri).
  File: C:\Users\mkramer\Documents\GitHub\fhir-mCODE-ig\fsh\ExtendedExample1.fsh
  Line: 522
```
This is because `Age` is a specialization of `Quantity`, and we do not correctly handle that when a Coding type value is being assigned. We do have code to handle these specializations, since we correctly handle `Age` for the `64.0 'a'` syntax that can be used on a `Quantity`.

Pull request:
Added support for coded values on Age types
fixes #617

Patch:
diff --git a/src/fhirtypes/ElementDefinition.ts b/src/fhirtypes/ElementDefinition.ts
index e6d1f10..0128ade 100644
--- a/src/fhirtypes/ElementDefinition.ts
+++ b/src/fhirtypes/ElementDefinition.ts
@@ -1176,7 +1176,7 @@ export class ElementDefinition {
         this.assignString(value as string, exactly);
         break;
       case 'Code':
-        this.assignFshCode(value as FshCode, exactly);
+        this.assignFshCode(value as FshCode, exactly, fisher);
         break;
       case 'Quantity':
         value = value as FshQuantity;
@@ -1520,17 +1520,19 @@ export class ElementDefinition {
    * @see {@link http://hl7.org/fhir/R4/elementdefinition-definitions.html#ElementDefinition.fixed_x_}
    * @see {@link http://hl7.org/fhir/R4/elementdefinition-definitions.html#ElementDefinition.pattern_x_}
    * @param {FshCode} code - the code to assign
-   * @param {exactly} boolean - True if if fixed[x] should be used, otherwise pattern[x] is used
+   * @param {boolean} exactly - True if if fixed[x] should be used, otherwise pattern[x] is used
+   * @param {Fishable} fisher - A fishable object used for finding structure definitions
    * @throws {CodedTypeNotFoundError} when there is no coded type on this element
    * @throws {ValueAlreadyAssignedError} when the code is already assigned to a different code
    * @throws {InvalidUriError} when the system being assigned is not a valid uri
    */
-  private assignFshCode(code: FshCode, exactly = false): void {
+  private assignFshCode(code: FshCode, exactly = false, fisher?: Fishable): void {
     if (code.system && !isUri(code.system.split('|')[0])) {
       throw new InvalidUriError(code.system);
     }
 
     const type = this.type[0].code;
+
     if (type === 'code' || type === 'string' || type === 'uri') {
       this.assignFHIRValue(code.toString(), code.code, exactly, type);
     } else if (type === 'CodeableConcept') {
@@ -1542,7 +1544,7 @@ export class ElementDefinition {
       );
     } else if (type === 'Coding') {
       this.assignFHIRValue(code.toString(), code.toFHIRCoding(), exactly, 'Coding');
-    } else if (type === 'Quantity') {
+    } else if (this.isQuantityType(type, fisher)) {
       // Since code only maps to part of Quantity, we want to ensure that if there are other (non-code) parts
       // already assigned, we take them on too -- as we don't want to overwrite them with blanks.
       const existing = this.fixedQuantity ?? this.patternQuantity ?? this.assignedByAnyParent();
@@ -1553,12 +1555,25 @@ export class ElementDefinition {
       if (existing?.comparator != null) {
         quantity.comparator = existing.comparator;
       }
-      this.assignFHIRValue(code.toString(), quantity, exactly, 'Quantity');
+      this.assignFHIRValue(code.toString(), quantity, exactly, type);
     } else {
       throw new CodedTypeNotFoundError([type]);
     }
   }
 
+  /**
+   * Checks if a provided type is a specialization of Quantity
+   * @param {string} type - The type being checked
+   * @param {Fishable} fisher - A fishable object used for finding the Base Definition of the provided type
+   */
+  private isQuantityType(type: string, fisher?: Fishable): boolean {
+    if (type === 'Quantity') {
+      return true;
+    }
+    const actualTypeSD = fisher?.fishForFHIR(type, Type.Type);
+    return actualTypeSD?.baseDefinition === 'http://hl7.org/fhir/StructureDefinition/Quantity';
+  }
+
   /**
    * Checks if a the ElementDefinition has exactly one type
    * @returns {boolean} - true if there is exactly one type


Test patch:
diff --git a/test/fhirtypes/ElementDefinition.assignFshCode.test.ts b/test/fhirtypes/ElementDefinition.assignFshCode.test.ts
index 6ff3917..06d146a 100644
--- a/test/fhirtypes/ElementDefinition.assignFshCode.test.ts
+++ b/test/fhirtypes/ElementDefinition.assignFshCode.test.ts
@@ -5,10 +5,12 @@ import { loadFromPath } from '../../src/fhirdefs/load';
 import { FHIRDefinitions } from '../../src/fhirdefs/FHIRDefinitions';
 import { StructureDefinition } from '../../src/fhirtypes/StructureDefinition';
 import { FshCode } from '../../src/fshtypes/FshCode';
+import { ElementDefinitionType } from '../../src/fhirtypes';
 
 describe('ElementDefinition', () => {
   let defs: FHIRDefinitions;
   let observation: StructureDefinition;
+  let condition: StructureDefinition;
   let fooBarCode: FshCode;
   let barFooCode: FshCode;
   let versionedCode: FshCode;
@@ -25,6 +27,7 @@ describe('ElementDefinition', () => {
   });
   beforeEach(() => {
     observation = fisher.fishForStructureDefinition('Observation');
+    condition = fisher.fishForStructureDefinition('Condition');
     fooBarCode = new FshCode('bar', 'http://foo.com');
     barFooCode = new FshCode('foo', 'http://bar.com');
     versionedCode = new FshCode('versioned', 'http://versioned.com|7.6.5');
@@ -433,6 +436,46 @@ describe('ElementDefinition', () => {
       expect(quantity.patternQuantity).toBeUndefined();
     });
 
+    it('should assign a code to an Age', () => {
+      const onsetX = condition.elements.find(e => e.id === 'Condition.onset[x]');
+      onsetX.type = [new ElementDefinitionType('Age')];
+      onsetX.assignValue(fooBarCode, false, fisher);
+      expect(onsetX.patternAge).toEqual({ code: 'bar', system: 'http://foo.com' });
+      expect(onsetX.fixedAge).toBeUndefined();
+    });
+
+    it('should assign a code to an Age (exactly)', () => {
+      const onsetX = condition.elements.find(e => e.id === 'Condition.onset[x]');
+      onsetX.type = [new ElementDefinitionType('Age')];
+      onsetX.assignValue(fooBarCode, true, fisher);
+      expect(onsetX.patternAge).toBeUndefined();
+      expect(onsetX.fixedAge).toEqual({ code: 'bar', system: 'http://foo.com' });
+    });
+
+    it('should assign a code with a display to an Age', () => {
+      const onsetX = condition.elements.find(e => e.id === 'Condition.onset[x]');
+      onsetX.type = [new ElementDefinitionType('Age')];
+      onsetX.assignValue(codeWithDisplay, false, fisher);
+      expect(onsetX.patternAge).toEqual({
+        code: 'bar',
+        system: 'http://foo.com',
+        unit: 'Foo Bar'
+      });
+      expect(onsetX.fixedAge).toBeUndefined();
+    });
+
+    it('should assign a code with a display to an Age (exactly)', () => {
+      const onsetX = condition.elements.find(e => e.id === 'Condition.onset[x]');
+      onsetX.type = [new ElementDefinitionType('Age')];
+      onsetX.assignValue(codeWithDisplay, true, fisher);
+      expect(onsetX.patternAge).toBeUndefined();
+      expect(onsetX.fixedAge).toEqual({
+        code: 'bar',
+        system: 'http://foo.com',
+        unit: 'Foo Bar'
+      });
+    });
+
     it('should assign a code with a display to a Quantity', () => {
       const quantity = observation.elements.find(e => e.id === 'Observation.referenceRange.low');
       quantity.assignValue(codeWithDisplay);
diff --git a/test/testhelpers/testdefs/package/StructureDefinition-code.json b/test/testhelpers/testdefs/package/StructureDefinition-code.json
new file mode 100644
index 0000000..e526d5f
--- /dev/null
+++ b/test/testhelpers/testdefs/package/StructureDefinition-code.json
@@ -0,0 +1,212 @@
+{
+  "resourceType": "StructureDefinition",
+  "id": "code",
+  "meta": {
+    "lastUpdated": "2019-11-01T09:29:23.356+11:00"
+  },
+  "extension": [
+    {
+      "url": "http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status",
+      "valueCode": "normative"
+    },
+    {
+      "url": "http://hl7.org/fhir/StructureDefinition/structuredefinition-normative-version",
+      "valueCode": "4.0.0"
+    }
+  ],
+  "url": "http://hl7.org/fhir/StructureDefinition/code",
+  "version": "4.0.1",
+  "name": "code",
+  "status": "active",
+  "date": "2019-11-01T09:29:23+11:00",
+  "publisher": "HL7 FHIR Standard",
+  "contact": [
+    {
+      "telecom": [
+        {
+          "system": "url",
+          "value": "http://hl7.org/fhir"
+        }
+      ]
+    }
+  ],
+  "description": "Base StructureDefinition for code type: A string which has at least one character and no leading or trailing whitespace and where there is no whitespace other than single spaces in the contents",
+  "fhirVersion": "4.0.1",
+  "kind": "primitive-type",
+  "abstract": false,
+  "type": "code",
+  "baseDefinition": "http://hl7.org/fhir/StructureDefinition/string",
+  "derivation": "specialization",
+  "snapshot": {
+    "element": [
+      {
+        "id": "code",
+        "path": "code",
+        "short": "Primitive Type code",
+        "definition": "A string which has at least one character and no leading or trailing whitespace and where there is no whitespace other than single spaces in the contents",
+        "min": 0,
+        "max": "*",
+        "base": {
+          "path": "code",
+          "min": 0,
+          "max": "*"
+        },
+        "constraint": [
+          {
+            "key": "ele-1",
+            "severity": "error",
+            "human": "All FHIR elements must have a @value or children",
+            "expression": "hasValue() or (children().count() > id.count())",
+            "xpath": "@value|f:*|h:div",
+            "source": "http://hl7.org/fhir/StructureDefinition/Element"
+          }
+        ],
+        "isModifier": false,
+        "isSummary": false
+      },
+      {
+        "id": "code.id",
+        "path": "code.id",
+        "representation": [
+          "xmlAttr"
+        ],
+        "short": "xml:id (or equivalent in JSON)",
+        "definition": "unique id for the element within a resource (for internal references)",
+        "min": 0,
+        "max": "1",
+        "base": {
+          "path": "Element.id",
+          "min": 0,
+          "max": "1"
+        },
+        "type": [
+          {
+            "extension": [
+              {
+                "url": "http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type",
+                "valueUrl": "string"
+              }
+            ],
+            "code": "http://hl7.org/fhirpath/System.String"
+          }
+        ],
+        "isModifier": false,
+        "isSummary": false
+      },
+      {
+        "id": "code.extension",
+        "path": "code.extension",
+        "short": "Additional content defined by implementations",
+        "definition": "May be used to represent additional information that is not part of the basic definition of the resource. To make the use of extensions safe and manageable, there is a strict set of governance  applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.",
+        "comment": "There can be no stigma associated with the use of extensions by any application, project, or standard - regardless of the institution or jurisdiction that uses or defines the extensions.  The use of extensions is what allows the FHIR specification to retain a core level of simplicity for everyone.",
+        "alias": [
+          "extensions",
+          "user content"
+        ],
+        "min": 0,
+        "max": "*",
+        "base": {
+          "path": "Element.extension",
+          "min": 0,
+          "max": "*"
+        },
+        "type": [
+          {
+            "code": "Extension"
+          }
+        ],
+        "constraint": [
+          {
+            "key": "ele-1",
+            "severity": "error",
+            "human": "All FHIR elements must have a @value or children",
+            "expression": "hasValue() or (children().count() > id.count())",
+            "xpath": "@value|f:*|h:div",
+            "source": "http://hl7.org/fhir/StructureDefinition/Element"
+          },
+          {
+            "key": "ext-1",
+            "severity": "error",
+            "human": "Must have either extensions or value[x], not both",
+            "expression": "extension.exists() != value.exists()",
+            "xpath": "exists(f:extension)!=exists(f:*[starts-with(local-name(.), 'value')])",
+            "source": "http://hl7.org/fhir/StructureDefinition/Extension"
+          }
+        ],
+        "isModifier": false,
+        "isSummary": false
+      },
+      {
+        "id": "code.value",
+        "path": "code.value",
+        "representation": [
+          "xmlAttr"
+        ],
+        "short": "Primitive value for code",
+        "definition": "Primitive value for code",
+        "min": 0,
+        "max": "1",
+        "base": {
+          "path": "string.value",
+          "min": 0,
+          "max": "1"
+        },
+        "type": [
+          {
+            "extension": [
+              {
+                "url": "http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type",
+                "valueUrl": "string"
+              },
+              {
+                "url": "http://hl7.org/fhir/StructureDefinition/regex",
+                "valueString": "[^\\s]+(\\s[^\\s]+)*"
+              }
+            ],
+            "code": "http://hl7.org/fhirpath/System.String"
+          }
+        ],
+        "isModifier": false,
+        "isSummary": false
+      }
+    ]
+  },
+  "differential": {
+    "element": [
+      {
+        "id": "code",
+        "path": "code",
+        "short": "Primitive Type code",
+        "definition": "A string which has at least one character and no leading or trailing whitespace and where there is no whitespace other than single spaces in the contents",
+        "min": 0,
+        "max": "*"
+      },
+      {
+        "id": "code.value",
+        "path": "code.value",
+        "representation": [
+          "xmlAttr"
+        ],
+        "short": "Primitive value for code",
+        "definition": "Primitive value for code",
+        "min": 0,
+        "max": "1",
+        "type": [
+          {
+            "extension": [
+              {
+                "url": "http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type",
+                "valueUrl": "code"
+              },
+              {
+                "url": "http://hl7.org/fhir/StructureDefinition/regex",
+                "valueString": "[^\\s]+(\\s[^\\s]+)*"
+              }
+            ],
+            "code": "http://hl7.org/fhirpath/System.String"
+          }
+        ]
+      }
+    ]
+  }
+}
\ No newline at end of file