Skip to content

Commit

Permalink
Added String Value Type for FHIR Mapping transformation (#46)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
mmacagno and Maurizio Macagno authored May 27, 2020
1 parent 173145c commit 9f7cf74
Show file tree
Hide file tree
Showing 11 changed files with 317 additions and 68 deletions.
12 changes: 12 additions & 0 deletions docs/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <a name="valuetypes"></a>
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.

Expand All @@ -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.

Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ public R4FhirValueProcessor()
: base(
new SampledDataFhirValueProcessor(),
new CodeableConceptFhirValueProcessor(),
new QuantityFhirValueProcessor())
new QuantityFhirValueProcessor(),
new StringFhirValueProcessor())
{
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<StringFhirValueType, (DateTime start, DateTime end, IEnumerable<(DateTime, string)> 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@
<None Update="TestInput\data_CodeValueFhirTemplate_CodeableConceptData.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="TestInput\data_CodeValueFhirTemplate_CodeableConceptAndStringComponent.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="TestInput\data_CodeValueFhirTemplate_String.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="TestInput\data_CodeValueFhirTemplate_Quantity.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<TemplateContainer>(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<TemplateContainer>(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()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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"
}
}
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,5 @@
}
}
]

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"templateType": "CodeValueFhir",
"template": {
"periodInterval": 0,
"typeName": "stringDetail",
"value": {
"valueName": "reasonText",
"valueType": "String"
}
}
}
Original file line number Diff line number Diff line change
@@ -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<NotSupportedException>(() => 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<NotSupportedException>(() => 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);
}
}
}
Loading

0 comments on commit 9f7cf74

Please sign in to comment.