From 9f7cf74463158c87796833052cef85b96ee308df Mon Sep 17 00:00:00 2001 From: mmacagno Date: Tue, 26 May 2020 20:48:40 -0700 Subject: [PATCH] Added String Value Type for FHIR Mapping transformation (#46) * Add string value type for CodeValueFhir templates * Updated documentation for new string value type. * Fix unit test to reflect real and valid FHIR component format Co-authored-by: Maurizio Macagno --- docs/Configuration.md | 12 ++ .../Template/StringFhirValueType.cs | 11 ++ .../Template/R4FhirValueProcessor.cs | 3 +- .../Template/StringFhirValueProcessor.cs | 34 +++++ ...rosoft.Health.Fhir.Ingest.UnitTests.csproj | 6 + .../CodeValueFhirTemplateFactoryTests.cs | 84 +++++++++++ ...ate_CodeableConceptAndStringComponent.json | 41 ++++++ ...data_CodeValueFhirTemplate_Components.json | 1 - .../data_CodeValueFhirTemplate_String.json | 11 ++ ....cs => QuantityFhirValueProcessorTests.cs} | 132 +++++++++--------- .../Template/StringFhirValueProcessorTests.cs | 50 +++++++ 11 files changed, 317 insertions(+), 68 deletions(-) create mode 100644 src/lib/Microsoft.Health.Fhir.Ingest/Template/StringFhirValueType.cs create mode 100644 src/lib/Microsoft.Health.Fhir.R4.Ingest/Template/StringFhirValueProcessor.cs create mode 100644 test/Microsoft.Health.Fhir.Ingest.UnitTests/TestInput/data_CodeValueFhirTemplate_CodeableConceptAndStringComponent.json create mode 100644 test/Microsoft.Health.Fhir.Ingest.UnitTests/TestInput/data_CodeValueFhirTemplate_String.json rename test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Template/{QuanityFhirValueProcessorTests.cs => QuantityFhirValueProcessorTests.cs} (95%) create mode 100644 test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Template/StringFhirValueProcessorTests.cs diff --git a/docs/Configuration.md b/docs/Configuration.md index a1a7c9c0..ba907a99 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -333,7 +333,14 @@ The CodeValueFhirTemplate is currently the only template supported in FHIR mappi |**Components[].Value**|The value to extract and represent in the component. See [Value Type Templates](#valuetypes) for more information. ## Value Type Templates +Each Value type defines at least the following properties: +| Property | Description +| --- | --- +|**ValueType**|The type of this value. One of the value type templates below. +|**ValueName**|The name of the attribute projected by the normalization phase. + Below are the currently supported value type templates. In the future further templates may be added. + ### SampledData Represents the [SampledData](http://hl7.org/fhir/datatypes.html#SampledData) FHIR data type. Measurements are written to value stream starting with start of the observations and incrementing forward using the period defined. If no value is present an `E` will be written into the data stream. If the period is such that two more values occupy the same position in the data stream the latest value is used. The same logic is applied when an observation using the SampledData is updated. @@ -351,6 +358,11 @@ Represents the [Quantity](http://hl7.org/fhir/datatypes.html#Quantity) FHIR data |**Code**| Coded form of the unit. |**System**| System that defines the coded unit form. +### String +Represent the [string](https://www.hl7.org/fhir/datatypes.html#string) FHIR data type. If more than one value is present in the grouping only the first value is used. If new value arrives that maps to the same observation it will overwrite the old value. + +No additional properties are defined. + ### CodeableConcept Represents the [CodeableConcept](http://hl7.org/fhir/datatypes.html#CodeableConcept) FHIR data type. The actual value isn't used. diff --git a/src/lib/Microsoft.Health.Fhir.Ingest/Template/StringFhirValueType.cs b/src/lib/Microsoft.Health.Fhir.Ingest/Template/StringFhirValueType.cs new file mode 100644 index 00000000..c43761f8 --- /dev/null +++ b/src/lib/Microsoft.Health.Fhir.Ingest/Template/StringFhirValueType.cs @@ -0,0 +1,11 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +namespace Microsoft.Health.Fhir.Ingest.Template +{ + public class StringFhirValueType : FhirValueType + { + } +} diff --git a/src/lib/Microsoft.Health.Fhir.R4.Ingest/Template/R4FhirValueProcessor.cs b/src/lib/Microsoft.Health.Fhir.R4.Ingest/Template/R4FhirValueProcessor.cs index afe6751f..62887600 100644 --- a/src/lib/Microsoft.Health.Fhir.R4.Ingest/Template/R4FhirValueProcessor.cs +++ b/src/lib/Microsoft.Health.Fhir.R4.Ingest/Template/R4FhirValueProcessor.cs @@ -15,7 +15,8 @@ public R4FhirValueProcessor() : base( new SampledDataFhirValueProcessor(), new CodeableConceptFhirValueProcessor(), - new QuantityFhirValueProcessor()) + new QuantityFhirValueProcessor(), + new StringFhirValueProcessor()) { } } diff --git a/src/lib/Microsoft.Health.Fhir.R4.Ingest/Template/StringFhirValueProcessor.cs b/src/lib/Microsoft.Health.Fhir.R4.Ingest/Template/StringFhirValueProcessor.cs new file mode 100644 index 00000000..bf9c09e0 --- /dev/null +++ b/src/lib/Microsoft.Health.Fhir.R4.Ingest/Template/StringFhirValueProcessor.cs @@ -0,0 +1,34 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using EnsureThat; +using Hl7.Fhir.Model; + +namespace Microsoft.Health.Fhir.Ingest.Template +{ + public class StringFhirValueProcessor : FhirValueProcessor values), Element> + { + protected override Element CreateValueImpl(StringFhirValueType template, (DateTime start, DateTime end, IEnumerable<(DateTime, string)> values) inValue) + { + EnsureArg.IsNotNull(template, nameof(template)); + + return new FhirString(inValue.values.Single().Item2); + } + + protected override Element MergeValueImpl(StringFhirValueType template, (DateTime start, DateTime end, IEnumerable<(DateTime, string)> values) inValue, Element existingValue) + { + if (!(existingValue is FhirString)) + { + throw new NotSupportedException($"Element {nameof(existingValue)} expected to be of type {typeof(FhirString)}."); + } + + // Only a single value, just replace. + return CreateValueImpl(template, inValue); + } + } +} diff --git a/test/Microsoft.Health.Fhir.Ingest.UnitTests/Microsoft.Health.Fhir.Ingest.UnitTests.csproj b/test/Microsoft.Health.Fhir.Ingest.UnitTests/Microsoft.Health.Fhir.Ingest.UnitTests.csproj index 772394f3..aea7a3c8 100644 --- a/test/Microsoft.Health.Fhir.Ingest.UnitTests/Microsoft.Health.Fhir.Ingest.UnitTests.csproj +++ b/test/Microsoft.Health.Fhir.Ingest.UnitTests/Microsoft.Health.Fhir.Ingest.UnitTests.csproj @@ -45,6 +45,12 @@ Always + + Always + + + Always + Always diff --git a/test/Microsoft.Health.Fhir.Ingest.UnitTests/Template/CodeValueFhirTemplateFactoryTests.cs b/test/Microsoft.Health.Fhir.Ingest.UnitTests/Template/CodeValueFhirTemplateFactoryTests.cs index 007ee609..01e2a957 100644 --- a/test/Microsoft.Health.Fhir.Ingest.UnitTests/Template/CodeValueFhirTemplateFactoryTests.cs +++ b/test/Microsoft.Health.Fhir.Ingest.UnitTests/Template/CodeValueFhirTemplateFactoryTests.cs @@ -196,6 +196,90 @@ public void GivenValidTemplateJsonWithComponentValueQuantityType_WhenFactoryCrea }); } + [Theory] + [FileData(@"TestInput/data_CodeValueFhirTemplate_String.json")] + public void GivenValidTemplateJsonWithValueStringType_WhenFactoryCreate_ThenTemplateCreated_Test(string json) + { + var templateContainer = JsonConvert.DeserializeObject(json); + + var factory = new CodeValueFhirTemplateFactory(); + + var template = factory.Create(templateContainer); + Assert.NotNull(template); + + var codeValueTemplate = template as CodeValueFhirTemplate; + Assert.NotNull(codeValueTemplate); + + Assert.Equal("stringDetail", codeValueTemplate.TypeName); + Assert.Equal(ObservationPeriodInterval.Single, codeValueTemplate.PeriodInterval); + Assert.NotNull(codeValueTemplate.Value); + + var value = codeValueTemplate.Value as StringFhirValueType; + Assert.NotNull(value); + Assert.Equal("reasonText", value.ValueName); + + Assert.Null(codeValueTemplate.Codes); + } + + [Theory] + [FileData(@"TestInput/data_CodeValueFhirTemplate_CodeableConceptAndStringComponent.json")] + public void GivenValidTemplateJsonWithMixedMainAndComponentValues_WhenFactoryCreate_ThenTemplateCreated_Test(string json) + { + var templateContainer = JsonConvert.DeserializeObject(json); + + var factory = new CodeValueFhirTemplateFactory(); + + var template = factory.Create(templateContainer); + Assert.NotNull(template); + + var codeValueTemplate = template as CodeValueFhirTemplate; + Assert.NotNull(codeValueTemplate); + + Assert.Equal("alarmEvent", codeValueTemplate.TypeName); + Assert.Equal(ObservationPeriodInterval.Single, codeValueTemplate.PeriodInterval); + Assert.NotNull(codeValueTemplate.Value); + + var value = codeValueTemplate.Value as CodeableConceptFhirValueType; + Assert.NotNull(value); + Assert.Equal("alarm", value.ValueName); + Assert.Equal("Alarm!", value.Text); + Assert.Collection( + value.Codes, + c => + { + Assert.Equal("alarmEvent", c.Code); + Assert.Equal("https://www.contoso.com/events/v1", c.System); + Assert.Equal("Alarm Event", c.Display); + }); + + Assert.Collection( + codeValueTemplate.Codes, + c => + { + Assert.Equal("deviceEvent", c.Code); + Assert.Equal("https://www.contoso.com/events/v1", c.System); + Assert.Equal("Device Event", c.Display); + }); + + Assert.Collection( + codeValueTemplate.Components, + c => + { + var stringValue = c.Value as StringFhirValueType; + Assert.NotNull(stringValue); + Assert.Equal("reason", stringValue.ValueName); + + Assert.Collection( + c.Codes, + cd => + { + Assert.Equal("reasonText", cd.Code); + Assert.Equal("https://www.contoso.com/events/v1", cd.System); + Assert.Equal("Reason Text", cd.Display); + }); + }); + } + [Fact] public void GivenInvalidTemplateTargetType_WhenFactoryCreate_ThenInvalidTemplateExceptionThrown_Test() { diff --git a/test/Microsoft.Health.Fhir.Ingest.UnitTests/TestInput/data_CodeValueFhirTemplate_CodeableConceptAndStringComponent.json b/test/Microsoft.Health.Fhir.Ingest.UnitTests/TestInput/data_CodeValueFhirTemplate_CodeableConceptAndStringComponent.json new file mode 100644 index 00000000..f58b305f --- /dev/null +++ b/test/Microsoft.Health.Fhir.Ingest.UnitTests/TestInput/data_CodeValueFhirTemplate_CodeableConceptAndStringComponent.json @@ -0,0 +1,41 @@ +{ + "templateType": "CodeValueFhir", + "template": { + "codes": [ + { + "code": "deviceEvent", + "system": "https://www.contoso.com/events/v1", + "display": "Device Event" + } + ], + "periodInterval": 0, + "typeName": "alarmEvent", + "value": { + "text": "Alarm!", + "codes": [ + { + "code": "alarmEvent", + "system": "https://www.contoso.com/events/v1", + "display": "Alarm Event" + } + ], + "valueName": "alarm", + "valueType": "CodeableConcept" + }, + "components": [ + { + "codes": [ + { + "code": "reasonText", + "display": "Reason Text", + "system": "https://www.contoso.com/events/v1" + } + ], + "value": { + "valueName": "reason", + "valueType": "String" + } + } + ] + } +} \ No newline at end of file diff --git a/test/Microsoft.Health.Fhir.Ingest.UnitTests/TestInput/data_CodeValueFhirTemplate_Components.json b/test/Microsoft.Health.Fhir.Ingest.UnitTests/TestInput/data_CodeValueFhirTemplate_Components.json index ed67ebbc..1311c444 100644 --- a/test/Microsoft.Health.Fhir.Ingest.UnitTests/TestInput/data_CodeValueFhirTemplate_Components.json +++ b/test/Microsoft.Health.Fhir.Ingest.UnitTests/TestInput/data_CodeValueFhirTemplate_Components.json @@ -42,6 +42,5 @@ } } ] - } } \ No newline at end of file diff --git a/test/Microsoft.Health.Fhir.Ingest.UnitTests/TestInput/data_CodeValueFhirTemplate_String.json b/test/Microsoft.Health.Fhir.Ingest.UnitTests/TestInput/data_CodeValueFhirTemplate_String.json new file mode 100644 index 00000000..a293236c --- /dev/null +++ b/test/Microsoft.Health.Fhir.Ingest.UnitTests/TestInput/data_CodeValueFhirTemplate_String.json @@ -0,0 +1,11 @@ +{ + "templateType": "CodeValueFhir", + "template": { + "periodInterval": 0, + "typeName": "stringDetail", + "value": { + "valueName": "reasonText", + "valueType": "String" + } + } +} \ No newline at end of file diff --git a/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Template/QuanityFhirValueProcessorTests.cs b/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Template/QuantityFhirValueProcessorTests.cs similarity index 95% rename from test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Template/QuanityFhirValueProcessorTests.cs rename to test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Template/QuantityFhirValueProcessorTests.cs index 1062f268..e9f0ea1a 100644 --- a/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Template/QuanityFhirValueProcessorTests.cs +++ b/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Template/QuantityFhirValueProcessorTests.cs @@ -1,66 +1,66 @@ -// ------------------------------------------------------------------------------------------------- -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. -// ------------------------------------------------------------------------------------------------- - -using System; -using Hl7.Fhir.Model; -using Xunit; - -namespace Microsoft.Health.Fhir.Ingest.Template -{ - public class QuanityFhirValueProcessorTests - { - [Fact] - public void GivenValidTemplate_WhenCreateValue_ThenSampledDataProperlyConfigured_Test() - { - var processor = new QuantityFhirValueProcessor(); - var template = new QuantityFhirValueType - { - Unit = "myUnit", - System = "mySystem", - Code = "myCode", - }; - - var data = (DateTime.Now, DateTime.UtcNow, new (DateTime, string)[] { (DateTime.UtcNow, "22.4") }); - var result = processor.CreateValue(template, data) as Quantity; - Assert.NotNull(result); - Assert.Equal("myUnit", result.Unit); - Assert.Equal("mySystem", result.System); - Assert.Equal("myCode", result.Code); - Assert.Equal(22.4m, result.Value); - } - - [Fact] - public void GivenInvalidElementType_WhenMergeValue_ThenNotSupportedExceptionThrown_Test() - { - var processor = new QuantityFhirValueProcessor(); - var template = new QuantityFhirValueType(); - var data = (DateTime.Now, DateTime.UtcNow, new (DateTime, string)[] { (DateTime.UtcNow, "value") }); - - Assert.Throws(() => processor.MergeValue(template, data, new FhirDateTime())); - } - - [Fact] - public void GivenValidTemplate_WhenMergeValue_ThenMergeValueReturned_Test() - { - var processor = new QuantityFhirValueProcessor(); - var template = new QuantityFhirValueType - { - Unit = "myUnit", - System = "mySystem", - Code = "myCode", - }; - - var oldQuantity = new Quantity { Value = 1, System = "s", Code = "c", Unit = "u" }; - - var data = (DateTime.Now, DateTime.UtcNow, new (DateTime, string)[] { (DateTime.UtcNow, "22.4") }); - var result = processor.MergeValue(template, data, oldQuantity) as Quantity; - Assert.NotNull(result); - Assert.Equal("myUnit", result.Unit); - Assert.Equal("mySystem", result.System); - Assert.Equal("myCode", result.Code); - Assert.Equal(22.4m, result.Value); - } - } -} +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using System; +using Hl7.Fhir.Model; +using Xunit; + +namespace Microsoft.Health.Fhir.Ingest.Template +{ + public class QuantityFhirValueProcessorTests + { + [Fact] + public void GivenValidTemplate_WhenCreateValue_ThenSampledDataProperlyConfigured_Test() + { + var processor = new QuantityFhirValueProcessor(); + var template = new QuantityFhirValueType + { + Unit = "myUnit", + System = "mySystem", + Code = "myCode", + }; + + var data = (DateTime.Now, DateTime.UtcNow, new (DateTime, string)[] { (DateTime.UtcNow, "22.4") }); + var result = processor.CreateValue(template, data) as Quantity; + Assert.NotNull(result); + Assert.Equal("myUnit", result.Unit); + Assert.Equal("mySystem", result.System); + Assert.Equal("myCode", result.Code); + Assert.Equal(22.4m, result.Value); + } + + [Fact] + public void GivenInvalidElementType_WhenMergeValue_ThenNotSupportedExceptionThrown_Test() + { + var processor = new QuantityFhirValueProcessor(); + var template = new QuantityFhirValueType(); + var data = (DateTime.Now, DateTime.UtcNow, new (DateTime, string)[] { (DateTime.UtcNow, "value") }); + + Assert.Throws(() => processor.MergeValue(template, data, new FhirDateTime())); + } + + [Fact] + public void GivenValidTemplate_WhenMergeValue_ThenMergeValueReturned_Test() + { + var processor = new QuantityFhirValueProcessor(); + var template = new QuantityFhirValueType + { + Unit = "myUnit", + System = "mySystem", + Code = "myCode", + }; + + var oldQuantity = new Quantity { Value = 1, System = "s", Code = "c", Unit = "u" }; + + var data = (DateTime.Now, DateTime.UtcNow, new (DateTime, string)[] { (DateTime.UtcNow, "22.4") }); + var result = processor.MergeValue(template, data, oldQuantity) as Quantity; + Assert.NotNull(result); + Assert.Equal("myUnit", result.Unit); + Assert.Equal("mySystem", result.System); + Assert.Equal("myCode", result.Code); + Assert.Equal(22.4m, result.Value); + } + } +} diff --git a/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Template/StringFhirValueProcessorTests.cs b/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Template/StringFhirValueProcessorTests.cs new file mode 100644 index 00000000..2fa3c780 --- /dev/null +++ b/test/Microsoft.Health.Fhir.R4.Ingest.UnitTests/Template/StringFhirValueProcessorTests.cs @@ -0,0 +1,50 @@ +// ------------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information. +// ------------------------------------------------------------------------------------------------- + +using System; +using Hl7.Fhir.Model; +using Xunit; + +namespace Microsoft.Health.Fhir.Ingest.Template +{ + public class StringFhirValueProcessorTests + { + [Fact] + public void GivenValidTemplate_WhenCreateValue_ThenStringProperlyConfigured_Test() + { + var processor = new StringFhirValueProcessor(); + var template = new StringFhirValueType() { }; + + var data = (DateTime.Now, DateTime.UtcNow, new (DateTime, string)[] { (DateTime.UtcNow, "my string value") }); + var result = processor.CreateValue(template, data) as FhirString; + Assert.NotNull(result); + Assert.Equal("my string value", result.Value); + } + + [Fact] + public void GivenInvalidElementType_WhenMergeValue_ThenNotSupportedExceptionThrown_Test() + { + var processor = new StringFhirValueProcessor(); + var template = new StringFhirValueType(); + var data = (DateTime.Now, DateTime.UtcNow, new (DateTime, string)[] { (DateTime.UtcNow, "a string") }); + + Assert.Throws(() => processor.MergeValue(template, data, new FhirDateTime())); + } + + [Fact] + public void GivenValidTemplate_WhenMergeValue_ThenMergeValueReturned_Test() + { + var processor = new StringFhirValueProcessor(); + var template = new StringFhirValueType(); + + FhirString oldString = new FhirString("old string"); + + var data = (DateTime.Now, DateTime.UtcNow, new (DateTime, string)[] { (DateTime.UtcNow, "new string") }); + var result = processor.MergeValue(template, data, oldString) as FhirString; + Assert.NotNull(result); + Assert.Equal("new string", result.Value); + } + } +}