diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 456ef707d..e75a210fb 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -45,6 +45,10 @@ jobs:
with:
fetch-depth: 0
+ - uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: "6.0.x"
+
- name: Install GitVersion
run: dotnet tool install --global GitVersion.Tool
@@ -199,7 +203,7 @@ jobs:
needs: [build]
strategy:
matrix:
- feature: [AcrApi, DicomDimseScp, DicomDimseScu, DicomWebExport, DicomWebStow]
+ feature: [AcrApi, DicomDimseScp, DicomDimseScu, DicomWebExport, DicomWebStow, HealthLevel7]
fail-fast: false
env:
TAG: ${{ needs.build.outputs.TAG }}
diff --git a/.licenserc.yaml b/.licenserc.yaml
index 4f4475f5c..916a8d96b 100644
--- a/.licenserc.yaml
+++ b/.licenserc.yaml
@@ -28,6 +28,8 @@ header:
- '**/*.feature.cs'
- 'src/.sonarlint/**'
- 'src/coverlet.runsettings'
+ - 'src/Monai.Deploy.InformaticsGateway.sln'
+ - 'src/Database/Migrations/**'
- 'demos/**/.env/**'
- 'docs/templates/**'
- 'tests/Integration.Test/*.dev'
diff --git a/Dockerfile b/Dockerfile
index b544cce82..d719c5d58 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -45,6 +45,7 @@ COPY --from=build /app/out .
COPY --from=build /tools /opt/dotnetcore-tools
EXPOSE 104
+EXPOSE 2575
EXPOSE 5000
RUN ls -lR /opt/monai/ig
diff --git a/global.json b/global.json
index d6c2c37f7..447ef4324 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "6.0.100",
+ "version": "6.0.400",
"rollForward": "latestFeature"
}
}
\ No newline at end of file
diff --git a/src/Api/Storage/Hl7FileStorageMetadata.cs b/src/Api/Storage/Hl7FileStorageMetadata.cs
new file mode 100644
index 000000000..2ba1b69cf
--- /dev/null
+++ b/src/Api/Storage/Hl7FileStorageMetadata.cs
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2021-2022 MONAI Consortium
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Text.Json.Serialization;
+using Ardalis.GuardClauses;
+
+namespace Monai.Deploy.InformaticsGateway.Api.Storage
+{
+ ///
+ /// Provides basic information for a FHIR resource and storage hierarchy/path.
+ ///
+ public sealed record Hl7FileStorageMetadata : FileStorageMetadata
+ {
+ public const string Hl7SubDirectoryName = "ehr";
+ public const string FileExtension = ".txt";
+
+ ///
+ [JsonIgnore]
+ public override string DataTypeDirectoryName => Hl7SubDirectoryName;
+
+ ///
+ [JsonPropertyName("file")]
+ public override StorageObjectMetadata File { get; set; }
+
+ ///
+ /// DO NOT USE
+ /// This constructor is intended for JSON serializer.
+ /// Due to limitation in current version of .NET, the constructor must be public.
+ /// https://github.com/dotnet/runtime/issues/31511
+ ///
+ [JsonConstructor]
+ public Hl7FileStorageMetadata() { }
+
+ public Hl7FileStorageMetadata(string connectionId)
+ : base(connectionId, Guid.NewGuid().ToString())
+ {
+ Guard.Against.NullOrWhiteSpace(connectionId, nameof(connectionId));
+
+ Source = connectionId;
+
+ File = new StorageObjectMetadata(FileExtension)
+ {
+ TemporaryPath = string.Join(PathSeparator, connectionId, DataTypeDirectoryName, $"{base.Id}{FileExtension}"),
+ UploadPath = string.Join(PathSeparator, DataTypeDirectoryName, $"{base.Id}{FileExtension}"),
+ ContentType = System.Net.Mime.MediaTypeNames.Text.Plain,
+ };
+ }
+ }
+}
diff --git a/src/Api/Test/Storage/StorageObjectMetadataTest.cs b/src/Api/Test/Storage/StorageObjectMetadataTest.cs
index 0dc61e9aa..f7f7516c3 100644
--- a/src/Api/Test/Storage/StorageObjectMetadataTest.cs
+++ b/src/Api/Test/Storage/StorageObjectMetadataTest.cs
@@ -34,8 +34,10 @@ public void GivenAStorageObjectMetadata_InitializeWithFileExtensionMissingDot_Ex
[Fact]
public void GivenAStorageObjectMetadata_WhenSetUploadIsCalled_ExpectUplaodValuesToBeSetAndDataStreamDisposed()
{
- var metadata = new StorageObjectMetadata(".txt");
- metadata.Data = new MemoryStream();
+ var metadata = new StorageObjectMetadata(".txt")
+ {
+ Data = new MemoryStream()
+ };
metadata.SetUploaded("MYBUCKET");
Assert.Equal("MYBUCKET", metadata.TemporaryBucketName);
diff --git a/src/CLI/Services/ConfigurationOptionAccessor.cs b/src/CLI/Services/ConfigurationOptionAccessor.cs
index 40c35be42..fd134c93e 100644
--- a/src/CLI/Services/ConfigurationOptionAccessor.cs
+++ b/src/CLI/Services/ConfigurationOptionAccessor.cs
@@ -30,6 +30,11 @@ public interface IConfigurationOptionAccessor
///
int DicomListeningPort { get; set; }
+ ///
+ /// Gets or sets the HL7 listening port from appsettings.json.
+ ///
+ int Hl7ListeningPort { get; set; }
+
///
/// Gets or sets the Docker image prefix from appsettings.json.
/// This is used to query the Informatics Gateway Docker containers that are installed.
@@ -105,13 +110,28 @@ public int DicomListeningPort
}
set
{
- Guard.Against.OutOfRangePort(value, nameof(InformaticsGatewayServerEndpoint));
+ Guard.Against.OutOfRangePort(value, nameof(DicomListeningPort));
var jObject = ReadConfigurationFile();
jObject["InformaticsGateway"]["dicom"]["scp"]["port"] = value;
SaveConfigurationFile(jObject);
}
}
+ public int Hl7ListeningPort
+ {
+ get
+ {
+ return GetValueFromJsonPath("InformaticsGateway.hl7.port");
+ }
+ set
+ {
+ Guard.Against.OutOfRangePort(value, nameof(Hl7ListeningPort));
+ var jObject = ReadConfigurationFile();
+ jObject["InformaticsGateway"]["hl7"]["port"] = value;
+ SaveConfigurationFile(jObject);
+ }
+ }
+
public string DockerImagePrefix
{
get
diff --git a/src/CLI/Services/DockerRunner.cs b/src/CLI/Services/DockerRunner.cs
index afeb9af35..cc4b18f92 100644
--- a/src/CLI/Services/DockerRunner.cs
+++ b/src/CLI/Services/DockerRunner.cs
@@ -119,6 +119,10 @@ public async Task StartApplication(ImageVersion imageVersion, Cancellation
createContainerParams.ExposedPorts.Add($"{_configurationService.Configurations.DicomListeningPort}/tcp", new EmptyStruct());
createContainerParams.HostConfig.PortBindings.Add($"{_configurationService.Configurations.DicomListeningPort}/tcp", new List { new PortBinding { HostPort = $"{_configurationService.Configurations.DicomListeningPort}" } });
+ _logger.DockerPrtBinding(_configurationService.Configurations.Hl7ListeningPort);
+ createContainerParams.ExposedPorts.Add($"{_configurationService.Configurations.Hl7ListeningPort}/tcp", new EmptyStruct());
+ createContainerParams.HostConfig.PortBindings.Add($"{_configurationService.Configurations.Hl7ListeningPort}/tcp", new List { new PortBinding { HostPort = $"{_configurationService.Configurations.Hl7ListeningPort}" } });
+
_logger.DockerPrtBinding(_configurationService.Configurations.InformaticsGatewayServerPort);
createContainerParams.ExposedPorts.Add($"{_configurationService.Configurations.InformaticsGatewayServerPort}/tcp", new EmptyStruct());
createContainerParams.HostConfig.PortBindings.Add($"{_configurationService.Configurations.InformaticsGatewayServerPort}/tcp", new List { new PortBinding { HostPort = $"{_configurationService.Configurations.InformaticsGatewayServerPort}" } });
diff --git a/src/Client.Common/Monai.Deploy.InformaticsGateway.Client.Common.csproj b/src/Client.Common/Monai.Deploy.InformaticsGateway.Client.Common.csproj
index 1798269e1..8262cf20a 100644
--- a/src/Client.Common/Monai.Deploy.InformaticsGateway.Client.Common.csproj
+++ b/src/Client.Common/Monai.Deploy.InformaticsGateway.Client.Common.csproj
@@ -17,7 +17,7 @@
- netstandard2.1
+ net6.0
Monai.Deploy.InformaticsGateway.Client.Common
Apache-2.0
true
diff --git a/src/Common/Monai.Deploy.InformaticsGateway.Common.csproj b/src/Common/Monai.Deploy.InformaticsGateway.Common.csproj
index 7ac4689e0..71bdd1c89 100644
--- a/src/Common/Monai.Deploy.InformaticsGateway.Common.csproj
+++ b/src/Common/Monai.Deploy.InformaticsGateway.Common.csproj
@@ -19,7 +19,7 @@
Monai.Deploy.InformaticsGateway.Common
- netstandard2.1
+ net6.0
Apache-2.0
true
True
diff --git a/src/Configuration/Hl7Configuration.cs b/src/Configuration/Hl7Configuration.cs
index 9a6740d2a..4b3ab4bd1 100644
--- a/src/Configuration/Hl7Configuration.cs
+++ b/src/Configuration/Hl7Configuration.cs
@@ -1,5 +1,18 @@
-// SPDX-FileCopyrightText: © 2022 MONAI Consortium
-// SPDX-License-Identifier: Apache License 2.0
+/*
+ * Copyright 2022 MONAI Consortium
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
using Microsoft.Extensions.Configuration;
@@ -7,11 +20,12 @@ namespace Monai.Deploy.InformaticsGateway.Configuration
{
public class Hl7Configuration
{
- public static readonly int DefaultClientTimeout = 300000;
+ public static readonly int DefaultClientTimeout = 60000;
public const int DefaultMaximumNumberOfConnections = 10;
///
/// Gets or sets the client connection timeout in milliseconds.
+ /// Defaults to 60,000ms.
///
[ConfigurationKeyName("clientTimeout")]
public int ClientTimeoutMilliseconds { get; set; } = DefaultClientTimeout;
@@ -27,7 +41,7 @@ public class Hl7Configuration
/// Gets or sets the MLLP listening port.
/// Defaults to 2575.
///
- [ConfigurationKeyName("clientTimeout")]
+ [ConfigurationKeyName("port")]
public int Port { get; set; } = 2575;
///
diff --git a/src/Database/Migrations/20220802200605_R3_0.3.0.Designer.cs b/src/Database/Migrations/20220802200605_R3_0.3.0.Designer.cs
index e688b935a..01b3cb57b 100644
--- a/src/Database/Migrations/20220802200605_R3_0.3.0.Designer.cs
+++ b/src/Database/Migrations/20220802200605_R3_0.3.0.Designer.cs
@@ -1,4 +1,20 @@
-//
+/*
+ * Copyright 2022 MONAI Consortium
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
diff --git a/src/Database/Migrations/20220802200605_R3_0.3.0.cs b/src/Database/Migrations/20220802200605_R3_0.3.0.cs
index 38e6e6c4c..c64cbb6f7 100644
--- a/src/Database/Migrations/20220802200605_R3_0.3.0.cs
+++ b/src/Database/Migrations/20220802200605_R3_0.3.0.cs
@@ -1,4 +1,20 @@
-using Microsoft.EntityFrameworkCore.Migrations;
+/*
+ * Copyright 2022 MONAI Consortium
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
diff --git a/src/Database/Migrations/InformaticsGatewayContextModelSnapshot.cs b/src/Database/Migrations/InformaticsGatewayContextModelSnapshot.cs
index 6c8e9a124..6b9847296 100644
--- a/src/Database/Migrations/InformaticsGatewayContextModelSnapshot.cs
+++ b/src/Database/Migrations/InformaticsGatewayContextModelSnapshot.cs
@@ -1,4 +1,20 @@
-//
+/*
+ * Copyright 2022 MONAI Consortium
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
diff --git a/src/Database/StorageMetadataWrapperEntityConfiguration.cs b/src/Database/StorageMetadataWrapperEntityConfiguration.cs
index 4696217c7..dbeb65c7d 100644
--- a/src/Database/StorageMetadataWrapperEntityConfiguration.cs
+++ b/src/Database/StorageMetadataWrapperEntityConfiguration.cs
@@ -15,15 +15,8 @@
* limitations under the License.
*/
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text.Json;
-using System.Text.Json.Serialization;
using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
-using Monai.Deploy.InformaticsGateway.Api.Storage;
namespace Monai.Deploy.InformaticsGateway.Database
{
@@ -31,16 +24,6 @@ internal class StorageMetadataWrapperEntityConfiguration : IEntityTypeConfigurat
{
public void Configure(EntityTypeBuilder builder)
{
- var filesComparer = new ValueComparer>(
- (c1, c2) => c1.SequenceEqual(c2),
- c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
- c => c.ToList());
-
- var jsonSerializerSettings = new JsonSerializerOptions
- {
- DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
- };
-
builder.HasKey(j => new
{
j.CorrelationId,
diff --git a/src/Database/Test/Monai.Deploy.InformaticsGateway.Database.Test.csproj b/src/Database/Test/Monai.Deploy.InformaticsGateway.Database.Test.csproj
index 449ac0ec4..b6c21e7d8 100644
--- a/src/Database/Test/Monai.Deploy.InformaticsGateway.Database.Test.csproj
+++ b/src/Database/Test/Monai.Deploy.InformaticsGateway.Database.Test.csproj
@@ -1,3 +1,19 @@
+
+
diff --git a/src/Database/Test/Usings.cs b/src/Database/Test/Usings.cs
index 8c927eb74..ffb0fd08f 100644
--- a/src/Database/Test/Usings.cs
+++ b/src/Database/Test/Usings.cs
@@ -1 +1,17 @@
+/*
+ * Copyright 2022 MONAI Consortium
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
global using Xunit;
\ No newline at end of file
diff --git a/src/DicomWebClient/API/IDicomWebClient.cs b/src/DicomWebClient/API/IDicomWebClient.cs
index e2971af80..141bff521 100644
--- a/src/DicomWebClient/API/IDicomWebClient.cs
+++ b/src/DicomWebClient/API/IDicomWebClient.cs
@@ -74,8 +74,9 @@ public interface IDicomWebClient
///
/// ServiceType to be configured
/// Url prefix
+ #pragma warning disable CA1054
void ConfigureServicePrefix(DicomWebServiceType serviceType, string urlPrefix);
-
+ #pragma warning restore CA1054
///
/// Configures the authentication header for the DICOMweb client.
///
diff --git a/src/DicomWebClient/API/IServiceBase.cs b/src/DicomWebClient/API/IServiceBase.cs
index 86df382fa..95555ef1c 100644
--- a/src/DicomWebClient/API/IServiceBase.cs
+++ b/src/DicomWebClient/API/IServiceBase.cs
@@ -19,6 +19,8 @@ namespace Monai.Deploy.InformaticsGateway.DicomWeb.Client.API
{
public interface IServiceBase
{
+ #pragma warning disable CA1054
bool TryConfigureServiceUriPrefix(string uriPrefix);
+ #pragma warning restore CA1054
}
}
diff --git a/src/DicomWebClient/DicomWebClient.cs b/src/DicomWebClient/DicomWebClient.cs
index a1b4eafeb..31c2d9106 100644
--- a/src/DicomWebClient/DicomWebClient.cs
+++ b/src/DicomWebClient/DicomWebClient.cs
@@ -78,7 +78,9 @@ public void ConfigureServiceUris(Uri uriRoot)
}
///
+ #pragma warning disable CA1054
public void ConfigureServicePrefix(DicomWebServiceType serviceType, string urlPrefix)
+ #pragma warning restore CA1054
{
Guard.Against.NullOrWhiteSpace(urlPrefix, nameof(urlPrefix));
diff --git a/src/DicomWebClient/Monai.Deploy.InformaticsGateway.DicomWeb.Client.csproj b/src/DicomWebClient/Monai.Deploy.InformaticsGateway.DicomWeb.Client.csproj
index d24c86e55..99f8db114 100644
--- a/src/DicomWebClient/Monai.Deploy.InformaticsGateway.DicomWeb.Client.csproj
+++ b/src/DicomWebClient/Monai.Deploy.InformaticsGateway.DicomWeb.Client.csproj
@@ -18,7 +18,7 @@
- netstandard2.1
+ net6.0
9.0
Apache-2.0
true
diff --git a/src/InformaticsGateway/Common/FileStorageMetadataExtensions.cs b/src/InformaticsGateway/Common/FileStorageMetadataExtensions.cs
index 8f85967d8..dabb80437 100644
--- a/src/InformaticsGateway/Common/FileStorageMetadataExtensions.cs
+++ b/src/InformaticsGateway/Common/FileStorageMetadataExtensions.cs
@@ -34,19 +34,21 @@ public static async Task SetDataStreams(this DicomFileStorageMetadata dicomFileS
await dicomFile.SaveAsync(dicomFileStorageMetadata.File.Data).ConfigureAwait(false);
dicomFileStorageMetadata.File.Data.Seek(0, SeekOrigin.Begin);
- dicomFileStorageMetadata.JsonFile.Data = new MemoryStream(Encoding.UTF8.GetBytes(dicomJson));
- dicomFileStorageMetadata.JsonFile.Data.Seek(0, SeekOrigin.Begin);
+ SetTextStream(dicomFileStorageMetadata.JsonFile, dicomJson);
}
- public static async Task SetDataStream(this FhirFileStorageMetadata fhirFileStorageMetadata, string json)
+ public static void SetDataStream(this FhirFileStorageMetadata fhirFileStorageMetadata, string json)
+ => SetTextStream(fhirFileStorageMetadata.File, json);
+
+ public static void SetDataStream(this Hl7FileStorageMetadata hl7FileStorageMetadata, string message)
+ => SetTextStream(hl7FileStorageMetadata.File, message);
+
+ private static void SetTextStream(StorageObjectMetadata storageObjectMetadata, string message)
{
- Guard.Against.Null(json, nameof(json)); // allow empty here
+ Guard.Against.Null(message, nameof(message)); // allow empty here
- fhirFileStorageMetadata.File.Data = new MemoryStream();
- var sw = new StreamWriter(fhirFileStorageMetadata.File.Data, Encoding.UTF8);
- await sw.WriteAsync(json).ConfigureAwait(false);
- await sw.FlushAsync().ConfigureAwait(false);
- fhirFileStorageMetadata.File.Data.Seek(0, SeekOrigin.Begin);
+ storageObjectMetadata.Data = new MemoryStream(Encoding.UTF8.GetBytes(message));
+ storageObjectMetadata.Data.Seek(0, SeekOrigin.Begin);
}
}
}
diff --git a/src/InformaticsGateway/Logging/Log.800.Hl7Service.cs b/src/InformaticsGateway/Logging/Log.800.Hl7Service.cs
index 990b7e2a6..f3b2002b5 100644
--- a/src/InformaticsGateway/Logging/Log.800.Hl7Service.cs
+++ b/src/InformaticsGateway/Logging/Log.800.Hl7Service.cs
@@ -1,5 +1,18 @@
-// SPDX-FileCopyrightText: © 2022 MONAI Consortium
-// SPDX-License-Identifier: Apache License 2.0
+/*
+ * Copyright 2022 MONAI Consortium
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
using System;
using Microsoft.Extensions.Logging;
@@ -31,5 +44,23 @@ public static partial class Log
[LoggerMessage(EventId = 807, Level = LogLevel.Critical, Message = "Socket error: {error}")]
public static partial void Hl7SocketException(this ILogger logger, string error);
+
+ [LoggerMessage(EventId = 808, Level = LogLevel.Critical, Message = "Error handling HL7 results.")]
+ public static partial void ErrorHandlingHl7Results(this ILogger logger, Exception ex);
+
+ [LoggerMessage(EventId = 809, Level = LogLevel.Debug, Message = "Acknowledgment type={value}.")]
+ public static partial void AcknowledgmentType(this ILogger logger, string value);
+
+ [LoggerMessage(EventId = 810, Level = LogLevel.Information, Message = "Acknowledgment sent: length={length}.")]
+ public static partial void AcknowledgmentSent(this ILogger logger, int length);
+
+ [LoggerMessage(EventId = 811, Level = LogLevel.Debug, Message = "HL7 bytes received: {length}.")]
+ public static partial void Hl7MessageBytesRead(this ILogger logger, int length);
+
+ [LoggerMessage(EventId = 812, Level = LogLevel.Debug, Message = "Parsing message with {length} bytes.")]
+ public static partial void Hl7GenerateMessage(this ILogger logger, int length);
+
+ [LoggerMessage(EventId = 813, Level = LogLevel.Debug, Message = "Waiting for HL7 message.")]
+ public static partial void HL7ReadingMessage(this ILogger logger);
}
}
diff --git a/src/InformaticsGateway/Services/Common/INetworkStream.cs b/src/InformaticsGateway/Services/Common/INetworkStream.cs
index 37a1367b9..c36cfff5e 100644
--- a/src/InformaticsGateway/Services/Common/INetworkStream.cs
+++ b/src/InformaticsGateway/Services/Common/INetworkStream.cs
@@ -1,5 +1,18 @@
-// SPDX-FileCopyrightText: © 2022 MONAI Consortium
-// SPDX-License-Identifier: Apache License 2.0
+/*
+ * Copyright 2022 MONAI Consortium
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
using System;
using System.Threading;
diff --git a/src/InformaticsGateway/Services/Common/ITcpClientAdapter.cs b/src/InformaticsGateway/Services/Common/ITcpClientAdapter.cs
index 07e553fe3..a5df17207 100644
--- a/src/InformaticsGateway/Services/Common/ITcpClientAdapter.cs
+++ b/src/InformaticsGateway/Services/Common/ITcpClientAdapter.cs
@@ -1,5 +1,18 @@
-// SPDX-FileCopyrightText: © 2022 MONAI Consortium
-// SPDX-License-Identifier: Apache License 2.0
+/*
+ * Copyright 2022 MONAI Consortium
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
using System;
using System.Net;
diff --git a/src/InformaticsGateway/Services/Common/ITcpListener.cs b/src/InformaticsGateway/Services/Common/ITcpListener.cs
index 6df5e116c..d3e37bda3 100644
--- a/src/InformaticsGateway/Services/Common/ITcpListener.cs
+++ b/src/InformaticsGateway/Services/Common/ITcpListener.cs
@@ -1,5 +1,18 @@
-// SPDX-FileCopyrightText: © 2022 MONAI Consortium
-// SPDX-License-Identifier: Apache License 2.0
+/*
+ * Copyright 2022 MONAI Consortium
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
using System.Net;
using System.Threading;
diff --git a/src/InformaticsGateway/Services/Common/ITcpListenerFactory.cs b/src/InformaticsGateway/Services/Common/ITcpListenerFactory.cs
index fe676419b..efa6d06e7 100644
--- a/src/InformaticsGateway/Services/Common/ITcpListenerFactory.cs
+++ b/src/InformaticsGateway/Services/Common/ITcpListenerFactory.cs
@@ -1,5 +1,18 @@
-// SPDX-FileCopyrightText: © 2022 MONAI Consortium
-// SPDX-License-Identifier: Apache License 2.0
+/*
+ * Copyright 2022 MONAI Consortium
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
using System.Net;
diff --git a/src/InformaticsGateway/Services/Common/NetworkStreamAdapter.cs b/src/InformaticsGateway/Services/Common/NetworkStreamAdapter.cs
index 62ac62ebc..ab842abf2 100644
--- a/src/InformaticsGateway/Services/Common/NetworkStreamAdapter.cs
+++ b/src/InformaticsGateway/Services/Common/NetworkStreamAdapter.cs
@@ -1,5 +1,18 @@
-// SPDX-FileCopyrightText: © 2022 MONAI Consortium
-// SPDX-License-Identifier: Apache License 2.0
+/*
+ * Copyright 2022 MONAI Consortium
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
using System;
using System.Net.Sockets;
diff --git a/src/InformaticsGateway/Services/Common/TcpClientAdapter.cs b/src/InformaticsGateway/Services/Common/TcpClientAdapter.cs
index a7352c9b3..b56d93554 100644
--- a/src/InformaticsGateway/Services/Common/TcpClientAdapter.cs
+++ b/src/InformaticsGateway/Services/Common/TcpClientAdapter.cs
@@ -1,5 +1,18 @@
-// SPDX-FileCopyrightText: © 2022 MONAI Consortium
-// SPDX-License-Identifier: Apache License 2.0
+/*
+ * Copyright 2022 MONAI Consortium
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
using System;
using System.Net;
@@ -11,7 +24,7 @@ internal class TcpClientAdapter : ITcpClientAdapter
private readonly System.Net.Sockets.TcpClient _tcpClient;
private bool _disposedValue;
- public EndPoint? RemoteEndPoint
+ public EndPoint RemoteEndPoint
{
get
{
diff --git a/src/InformaticsGateway/Services/Connectors/DataRetrievalService.cs b/src/InformaticsGateway/Services/Connectors/DataRetrievalService.cs
index b6b163977..4b43f76aa 100644
--- a/src/InformaticsGateway/Services/Connectors/DataRetrievalService.cs
+++ b/src/InformaticsGateway/Services/Connectors/DataRetrievalService.cs
@@ -69,7 +69,6 @@ public DataRetrievalService(
_serviceScopeFactory = serviceScopeFactory ?? throw new ArgumentNullException(nameof(serviceScopeFactory));
_options = options ?? throw new ArgumentNullException(nameof(options));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
- _options = options ?? throw new ArgumentNullException(nameof(options));
_rootScope = _serviceScopeFactory.CreateScope();
@@ -205,6 +204,10 @@ private void RestoreExistingInstances(InferenceRequest inferenceRequest, Diction
foreach (var file in files)
{
+ if (cancellationToken.IsCancellationRequested)
+ {
+ break;
+ }
if (file is DicomFileStorageMetadata dicomFileInfo)
{
retrievedInstances.Add(dicomFileInfo.Id, dicomFileInfo);
@@ -327,7 +330,7 @@ private async Task RetrieveFhirResource(string transactionId, HttpClient h
}
var fhirFile = new FhirFileStorageMetadata(transactionId, resource.Type, resource.Id, fhirFormat);
- await fhirFile.SetDataStream(json).ConfigureAwait(false);
+ fhirFile.SetDataStream(json);
retrievedResources.Add(fhirFile.Id, fhirFile);
return true;
}
diff --git a/src/InformaticsGateway/Services/Connectors/PayloadMoveException.cs b/src/InformaticsGateway/Services/Connectors/PayloadMoveException.cs
index 346cb0b8b..bbacdd55f 100644
--- a/src/InformaticsGateway/Services/Connectors/PayloadMoveException.cs
+++ b/src/InformaticsGateway/Services/Connectors/PayloadMoveException.cs
@@ -34,5 +34,10 @@ public PayloadNotifyException(FailureReason reason)
{
Reason = reason;
}
+
+ protected PayloadNotifyException(System.Runtime.Serialization.SerializationInfo serializationInfo, System.Runtime.Serialization.StreamingContext streamingContext)
+ {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/src/InformaticsGateway/Services/Connectors/PayloadNotificationActionHandler.cs b/src/InformaticsGateway/Services/Connectors/PayloadNotificationActionHandler.cs
index 4dd21748b..c45c1cbc4 100644
--- a/src/InformaticsGateway/Services/Connectors/PayloadNotificationActionHandler.cs
+++ b/src/InformaticsGateway/Services/Connectors/PayloadNotificationActionHandler.cs
@@ -39,7 +39,7 @@ internal interface IPayloadNotificationActionHandler
Task NotifyAsync(Payload payload, ActionBlock notificationQueue, CancellationToken cancellationToken = default);
}
- internal class PayloadNotificationActionHandler : IPayloadNotificationActionHandler
+ internal class PayloadNotificationActionHandler : IPayloadNotificationActionHandler, IDisposable
{
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly ILogger _logger;
@@ -47,6 +47,7 @@ internal class PayloadNotificationActionHandler : IPayloadNotificationActionHand
private readonly IServiceScope _scope;
private readonly IMessageBrokerPublisherService _messageBrokerPublisherService;
+ private bool _disposedValue;
public PayloadNotificationActionHandler(IServiceScopeFactory serviceScopeFactory,
ILogger logger,
@@ -168,5 +169,25 @@ private async Task UpdatePayloadState(Payload payload)
return PayloadAction.Updated;
}
}
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposedValue)
+ {
+ if (disposing)
+ {
+ _scope.Dispose();
+ }
+
+ _disposedValue = true;
+ }
+ }
+
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
}
}
diff --git a/src/InformaticsGateway/Services/HealthLevel7/IMllpClient.cs b/src/InformaticsGateway/Services/HealthLevel7/IMllpClient.cs
index 964f4f5a9..bf1291706 100644
--- a/src/InformaticsGateway/Services/HealthLevel7/IMllpClient.cs
+++ b/src/InformaticsGateway/Services/HealthLevel7/IMllpClient.cs
@@ -1,5 +1,18 @@
-// SPDX-FileCopyrightText: © 2022 MONAI Consortium
-// SPDX-License-Identifier: Apache License 2.0
+/*
+ * Copyright 2022 MONAI Consortium
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
using System;
using System.Threading;
@@ -13,6 +26,6 @@ internal interface IMllpClient
void Dispose();
- Task Start(Action onDisconnect, CancellationToken cancellationToken);
+ Task Start(Func onDisconnect, CancellationToken cancellationToken);
}
}
diff --git a/src/InformaticsGateway/Services/HealthLevel7/IMllpClientFactory.cs b/src/InformaticsGateway/Services/HealthLevel7/IMllpClientFactory.cs
index c3c23015c..e2d145b6e 100644
--- a/src/InformaticsGateway/Services/HealthLevel7/IMllpClientFactory.cs
+++ b/src/InformaticsGateway/Services/HealthLevel7/IMllpClientFactory.cs
@@ -1,5 +1,18 @@
-// SPDX-FileCopyrightText: © 2022 MONAI Consortium
-// SPDX-License-Identifier: Apache License 2.0
+/*
+ * Copyright 2022 MONAI Consortium
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
using Microsoft.Extensions.Logging;
using Monai.Deploy.InformaticsGateway.Configuration;
diff --git a/src/InformaticsGateway/Services/HealthLevel7/MllpClient.cs b/src/InformaticsGateway/Services/HealthLevel7/MllpClient.cs
index 06257dc13..f42fdab36 100644
--- a/src/InformaticsGateway/Services/HealthLevel7/MllpClient.cs
+++ b/src/InformaticsGateway/Services/HealthLevel7/MllpClient.cs
@@ -1,5 +1,18 @@
-// SPDX-FileCopyrightText: © 2022 MONAI Consortium
-// SPDX-License-Identifier: Apache License 2.0
+/*
+ * Copyright 2022 MONAI Consortium
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
using System;
using System.Collections.Generic;
@@ -24,6 +37,7 @@ internal sealed class MllpClient : IDisposable, IMllpClient
private readonly ILogger _logger;
private readonly List _exceptions;
private readonly List _messages;
+ private readonly IDisposable _loggerScope;
private bool _disposedValue;
public Guid ClientId { get; }
@@ -38,10 +52,10 @@ public MllpClient(ITcpClientAdapter client, Hl7Configuration configurations, ILo
_exceptions = new List();
_messages = new List();
- _logger.BeginScope(new LoggingDataDictionary { { "End point", _client.RemoteEndPoint }, { "CorrelationId", ClientId } });
+ _loggerScope = _logger.BeginScope(new LoggingDataDictionary { { "End point", _client.RemoteEndPoint }, { "CorrelationId", ClientId } });
}
- public async Task Start(Action onDisconnect, CancellationToken cancellationToken)
+ public async Task Start(Func onDisconnect, CancellationToken cancellationToken)
{
using var clientStream = _client.GetStream();
clientStream.ReadTimeout = _configurations.ClientTimeoutMilliseconds;
@@ -52,7 +66,7 @@ public async Task Start(Action onDisconnect, Canc
if (onDisconnect is not null)
{
- onDisconnect(this, new MllpClientResult(_messages, _exceptions.Count > 0 ? new AggregateException(_exceptions) : null));
+ await onDisconnect(this, new MllpClientResult(_messages, _exceptions.Count > 0 ? new AggregateException(_exceptions) : null));
}
}
@@ -60,16 +74,24 @@ private async Task> ReceiveData(INetworkStream clientStream, Canc
{
Guard.Against.Null(clientStream, nameof(clientStream));
- var messageBuffer = new Memory(new byte[_configurations.BufferSize]);
- int bytesRead;
var data = string.Empty;
var messages = new List();
+ var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
while (true)
{
+ var messageBuffer = new Memory(new byte[_configurations.BufferSize]);
+ int bytesRead;
try
{
- bytesRead = await clientStream.ReadAsync(messageBuffer, cancellationToken).ConfigureAwait(false);
+ _logger.HL7ReadingMessage();
+ linkedCancellationTokenSource.CancelAfter(_configurations.ClientTimeoutMilliseconds);
+ bytesRead = await clientStream.ReadAsync(messageBuffer, linkedCancellationTokenSource.Token).ConfigureAwait(false);
+ _logger.Hl7MessageBytesRead(bytesRead);
+ if (!linkedCancellationTokenSource.TryReset())
+ {
+ linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
+ }
}
catch (Exception ex)
{
@@ -85,24 +107,36 @@ private async Task> ReceiveData(INetworkStream clientStream, Canc
data += Encoding.UTF8.GetString(messageBuffer.ToArray());
- var startIndex = data.IndexOf(Resources.AsciiVT);
- if (startIndex >= 0)
+ do
{
- var endIndex = data.IndexOf(Resources.AsciiFS);
-
- if (endIndex > startIndex)
+ var startIndex = data.IndexOf(Resources.AsciiVT);
+ if (startIndex >= 0)
{
- if (!CreateMessage(startIndex, endIndex, ref data, out var message))
+ var endIndex = data.IndexOf(Resources.AsciiFS);
+
+ if (endIndex > startIndex)
{
- break;
+ if (!CreateMessage(startIndex, endIndex, ref data, out var message))
+ {
+ break;
+ }
+ else
+ {
+ await SendAcknowledgment(clientStream, message, cancellationToken).ConfigureAwait(false);
+ messages.Add(message);
+ }
}
else
{
- await SendAcknowledgment(clientStream, message, cancellationToken).ConfigureAwait(false);
- messages.Add(message);
+ break;
}
}
- }
+ else
+ {
+ data = string.Empty;
+ break;
+ }
+ } while (true);
}
return messages;
}
@@ -124,7 +158,8 @@ private async Task SendAcknowledgment(INetworkStream clientStream, Message messa
try
{
await clientStream.WriteAsync(ackData, cancellationToken).ConfigureAwait(false);
- await clientStream.FlushAsync(cancellationToken);
+ await clientStream.FlushAsync(cancellationToken).ConfigureAwait(false);
+ _logger.AcknowledgmentSent(ackData.Length);
}
catch (Exception ex)
{
@@ -144,6 +179,9 @@ private bool ShouldSendAcknowledgment(Message message)
{
return true;
}
+
+ _logger.AcknowledgmentType(value.Value);
+
return value.Value switch
{
Resources.AcknowledgmentTypeNever => false,
@@ -166,7 +204,9 @@ private bool CreateMessage(int startIndex, int endIndex, ref string data, out Me
var messageEndIndex = endIndex + 1;
try
{
- message = new Message(data.Substring(messageStartIndex, endIndex - messageStartIndex));
+ var text = data.Substring(messageStartIndex, endIndex - messageStartIndex);
+ _logger.Hl7GenerateMessage(text.Length);
+ message = new Message(text);
message.ParseMessage();
data = data.Length > endIndex ? data.Substring(messageEndIndex) : string.Empty;
return true;
@@ -187,6 +227,7 @@ private void Dispose(bool disposing)
if (disposing)
{
_client.Dispose();
+ _loggerScope.Dispose();
}
_disposedValue = true;
diff --git a/src/InformaticsGateway/Services/HealthLevel7/MllpClientResult.cs b/src/InformaticsGateway/Services/HealthLevel7/MllpClientResult.cs
index bc256fb47..9abf611df 100644
--- a/src/InformaticsGateway/Services/HealthLevel7/MllpClientResult.cs
+++ b/src/InformaticsGateway/Services/HealthLevel7/MllpClientResult.cs
@@ -1,5 +1,18 @@
-// SPDX-FileCopyrightText: © 2022 MONAI Consortium
-// SPDX-License-Identifier: Apache License 2.0
+/*
+ * Copyright 2022 MONAI Consortium
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
using System;
using System.Collections.Generic;
@@ -14,7 +27,7 @@ internal class MllpClientResult
public MllpClientResult(IList messages, AggregateException aggregateException)
{
- Messages = messages;
+ Messages = messages ?? new List();
AggregateException = aggregateException;
}
}
diff --git a/src/InformaticsGateway/Services/HealthLevel7/MllpService.cs b/src/InformaticsGateway/Services/HealthLevel7/MllpService.cs
index 5ea9cd0cc..3ba770ffd 100644
--- a/src/InformaticsGateway/Services/HealthLevel7/MllpService.cs
+++ b/src/InformaticsGateway/Services/HealthLevel7/MllpService.cs
@@ -1,20 +1,37 @@
-// SPDX-FileCopyrightText: © 2022 MONAI Consortium
-// SPDX-License-Identifier: Apache License 2.0
+/*
+ * Copyright 2022 MONAI Consortium
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
+using Ardalis.GuardClauses;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Monai.Deploy.InformaticsGateway.Api.Rest;
+using Monai.Deploy.InformaticsGateway.Api.Storage;
using Monai.Deploy.InformaticsGateway.Common;
using Monai.Deploy.InformaticsGateway.Configuration;
using Monai.Deploy.InformaticsGateway.Logging;
using Monai.Deploy.InformaticsGateway.Services.Common;
+using Monai.Deploy.InformaticsGateway.Services.Connectors;
+using Monai.Deploy.InformaticsGateway.Services.Storage;
namespace Monai.Deploy.InformaticsGateway.Services.HealthLevel7
{
@@ -24,6 +41,8 @@ internal sealed class MllpService : IHostedService, IDisposable, IMonaiService
private bool _disposedValue;
private readonly ITcpListener _tcpListener;
private readonly IMllpClientFactory _mllpClientFactory;
+ private readonly IObjectUploadQueue _uploadQueue;
+ private readonly IPayloadAssembler _payloadAssembler;
private readonly IServiceScope _serviceScope;
private readonly ILoggerFactory _logginFactory;
private readonly ILogger _logger;
@@ -58,6 +77,8 @@ public MllpService(IServiceScopeFactory serviceScopeFactory,
var tcpListenerFactory = _serviceScope.ServiceProvider.GetService() ?? throw new ServiceNotFoundException(nameof(ITcpListenerFactory));
_tcpListener = tcpListenerFactory.CreateTcpListener(System.Net.IPAddress.Any, _configuration.Value.Hl7.Port);
_mllpClientFactory = _serviceScope.ServiceProvider.GetService() ?? throw new ServiceNotFoundException(nameof(IMllpClientFactory));
+ _uploadQueue = _serviceScope.ServiceProvider.GetService() ?? throw new ServiceNotFoundException(nameof(IObjectUploadQueue));
+ _payloadAssembler = _serviceScope.ServiceProvider.GetService() ?? throw new ServiceNotFoundException(nameof(IPayloadAssembler));
_activeTasks = new ConcurrentDictionary();
}
@@ -90,19 +111,27 @@ private async Task BackgroundProcessing(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
+ IMllpClient mllpClient = null;
try
{
WaitUntilAvailable(_configuration.Value.Hl7.MaximumNumberOfConnections);
var client = await _tcpListener.AcceptTcpClientAsync(cancellationToken).ConfigureAwait(false);
_logger.ClientConnected();
- var mllpClient = _mllpClientFactory.CreateClient(client, _configuration.Value.Hl7, _logginFactory.CreateLogger());
+ mllpClient = _mllpClientFactory.CreateClient(client, _configuration.Value.Hl7, _logginFactory.CreateLogger());
_ = mllpClient.Start(OnDisconnect, cancellationToken);
_activeTasks.TryAdd(mllpClient.ClientId, mllpClient);
}
catch (System.Net.Sockets.SocketException ex)
{
_logger.Hl7SocketException(ex.Message);
+
+ if (mllpClient is not null)
+ {
+ mllpClient.Dispose();
+ _activeTasks.Remove(mllpClient.ClientId, out _);
+ }
+
if (ex.ErrorCode == SOCKET_OPERATION_CANCELLED)
{
break;
@@ -117,9 +146,27 @@ private async Task BackgroundProcessing(CancellationToken cancellationToken)
_logger.ServiceCancelled(ServiceName);
}
- private void OnDisconnect(IMllpClient client, MllpClientResult result)
+ private async Task OnDisconnect(IMllpClient client, MllpClientResult result)
{
+ Guard.Against.Null(client, nameof(client));
+ Guard.Against.Null(result, nameof(result));
+
_activeTasks.Remove(client.ClientId, out _);
+
+ try
+ {
+ foreach (var message in result.Messages)
+ {
+ var hl7Fileetadata = new Hl7FileStorageMetadata(client.ClientId.ToString());
+ hl7Fileetadata.SetDataStream(message.HL7Message);
+ _uploadQueue.Queue(hl7Fileetadata);
+ await _payloadAssembler.Queue(client.ClientId.ToString(), hl7Fileetadata).ConfigureAwait(false);
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorHandlingHl7Results(ex);
+ }
}
private void WaitUntilAvailable(int maximumNumberOfConnections)
diff --git a/src/InformaticsGateway/Services/HealthLevel7/Resources.cs b/src/InformaticsGateway/Services/HealthLevel7/Resources.cs
index c15b34e56..62b0ed23a 100644
--- a/src/InformaticsGateway/Services/HealthLevel7/Resources.cs
+++ b/src/InformaticsGateway/Services/HealthLevel7/Resources.cs
@@ -1,5 +1,18 @@
-// SPDX-FileCopyrightText: © 2022 MONAI Consortium
-// SPDX-License-Identifier: Apache License 2.0
+/*
+ * Copyright 2022 MONAI Consortium
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
namespace Monai.Deploy.InformaticsGateway.Services.HealthLevel7
{
diff --git a/src/InformaticsGateway/Services/Http/InferenceController.cs b/src/InformaticsGateway/Services/Http/InferenceController.cs
index 7acd066ae..69300f256 100644
--- a/src/InformaticsGateway/Services/Http/InferenceController.cs
+++ b/src/InformaticsGateway/Services/Http/InferenceController.cs
@@ -22,10 +22,8 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Options;
using Monai.Deploy.InformaticsGateway.Api;
using Monai.Deploy.InformaticsGateway.Api.Rest;
-using Monai.Deploy.InformaticsGateway.Configuration;
using Monai.Deploy.InformaticsGateway.Logging;
using Monai.Deploy.InformaticsGateway.Repositories;
@@ -36,16 +34,13 @@ namespace Monai.Deploy.InformaticsGateway.Services.Http
public class InferenceController : ControllerBase
{
private readonly IInferenceRequestRepository _inferenceRequestRepository;
- private readonly IOptions _configuration;
private readonly ILogger _logger;
public InferenceController(
IInferenceRequestRepository inferenceRequestRepository,
- IOptions configuration,
ILogger logger)
{
_inferenceRequestRepository = inferenceRequestRepository ?? throw new ArgumentNullException(nameof(inferenceRequestRepository));
- _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
diff --git a/src/InformaticsGateway/Test/Services/HealthLevel7/MllpClientTest.cs b/src/InformaticsGateway/Test/Services/HealthLevel7/MllpClientTest.cs
index 31dffc8d0..f9900619c 100644
--- a/src/InformaticsGateway/Test/Services/HealthLevel7/MllpClientTest.cs
+++ b/src/InformaticsGateway/Test/Services/HealthLevel7/MllpClientTest.cs
@@ -1,5 +1,18 @@
-// SPDX-FileCopyrightText: © 2022 MONAI Consortium
-// SPDX-License-Identifier: Apache License 2.0
+/*
+ * Copyright 2022 MONAI Consortium
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
using System;
using System.Linq;
@@ -56,12 +69,15 @@ public async Task ReceiveData_ExceptionReadingStream()
_tcpClient.Setup(p => p.GetStream()).Returns(stream.Object);
var client = new MllpClient(_tcpClient.Object, _config, _logger.Object);
- var action = new Action((client, results) =>
+ var action = new Func(async (client, results) =>
{
- Assert.Empty(results.Messages);
- Assert.NotNull(results.AggregateException);
- Assert.Single(results.AggregateException.InnerExceptions);
- Assert.Equal("error", results.AggregateException.InnerExceptions.First().Message);
+ await Task.Run(() =>
+ {
+ Assert.Empty(results.Messages);
+ Assert.NotNull(results.AggregateException);
+ Assert.Single(results.AggregateException.InnerExceptions);
+ Assert.Equal("error", results.AggregateException.InnerExceptions.First().Message);
+ });
});
await client.Start(action, _cancellationTokenSource.Token);
}
@@ -76,10 +92,13 @@ public async Task ReceiveData_ZeroByte()
_tcpClient.Setup(p => p.GetStream()).Returns(stream.Object);
var client = new MllpClient(_tcpClient.Object, _config, _logger.Object);
- var action = new Action((client, results) =>
+ var action = new Func(async (client, results) =>
{
- Assert.Empty(results.Messages);
- Assert.Null(results.AggregateException);
+ await Task.Run(() =>
+ {
+ Assert.Empty(results.Messages);
+ Assert.Null(results.AggregateException);
+ });
});
await client.Start(action, _cancellationTokenSource.Token);
}
@@ -89,8 +108,6 @@ public async Task ReceiveData_InvalidMessage()
{
var message = @$"{Resources.AsciiVT}HELLO WORLD{Resources.AsciiFS}";
var messageBytes = Encoding.UTF8.GetBytes(message);
- var hl7Message = new Message(SampleMessage);
- hl7Message.ParseMessage();
var index = 0;
var size = 0;
@@ -110,12 +127,15 @@ public async Task ReceiveData_InvalidMessage()
_tcpClient.Setup(p => p.GetStream()).Returns(stream.Object);
var client = new MllpClient(_tcpClient.Object, _config, _logger.Object);
- var action = new Action((client, results) =>
+ var action = new Func(async (client, results) =>
{
- Assert.Empty(results.Messages);
- Assert.NotNull(results.AggregateException);
- Assert.Single(results.AggregateException.InnerExceptions);
- Assert.Contains("Failed to validate the message with error", results.AggregateException.InnerExceptions.First().Message);
+ await Task.Run(() =>
+ {
+ Assert.Empty(results.Messages);
+ Assert.NotNull(results.AggregateException);
+ Assert.Single(results.AggregateException.InnerExceptions);
+ Assert.Contains("Failed to validate the message with error", results.AggregateException.InnerExceptions.First().Message);
+ });
});
await client.Start(action, _cancellationTokenSource.Token);
}
@@ -126,10 +146,9 @@ public async Task ReceiveData_DisabledAck()
_config.SendAcknowledgment = false;
var originalMessage = SampleMessage.Replace("", string.Empty);
- var message = @$"{Resources.AsciiVT}{originalMessage}{Resources.AsciiFS}";
- var messageBytes = Encoding.UTF8.GetBytes(message);
var hl7Message = new Message(originalMessage);
hl7Message.ParseMessage();
+ var messageBytes = hl7Message.GetMLLP();
var index = 0;
var size = 0;
@@ -149,14 +168,17 @@ public async Task ReceiveData_DisabledAck()
_tcpClient.Setup(p => p.GetStream()).Returns(stream.Object);
var client = new MllpClient(_tcpClient.Object, _config, _logger.Object);
- var action = new Action((client, results) =>
+ var action = new Func(async (client, results) =>
{
- Assert.Single(results.Messages);
- Assert.Equal(originalMessage, results.Messages.First().HL7Message);
- Assert.Null(results.AggregateException);
+ await Task.Run(() =>
+ {
+ Assert.Single(results.Messages);
+ Assert.Equal(originalMessage, results.Messages.First().HL7Message);
+ Assert.Null(results.AggregateException);
- stream.Verify(p => p.FlushAsync(It.IsAny()), Times.Never());
- stream.Verify(p => p.WriteAsync(It.IsAny>(), It.IsAny()), Times.Never());
+ stream.Verify(p => p.FlushAsync(It.IsAny()), Times.Never());
+ stream.Verify(p => p.WriteAsync(It.IsAny>(), It.IsAny()), Times.Never());
+ });
});
await client.Start(action, _cancellationTokenSource.Token);
}
@@ -165,10 +187,9 @@ public async Task ReceiveData_DisabledAck()
public async Task ReceiveData_NeverSendAck()
{
var originalMessage = SampleMessage.Replace("", Resources.AcknowledgmentTypeNever);
- var message = @$"{Resources.AsciiVT}{originalMessage}{Resources.AsciiFS}";
- var messageBytes = Encoding.UTF8.GetBytes(message);
var hl7Message = new Message(originalMessage);
hl7Message.ParseMessage();
+ var messageBytes = hl7Message.GetMLLP();
var index = 0;
var size = 0;
@@ -188,14 +209,17 @@ public async Task ReceiveData_NeverSendAck()
_tcpClient.Setup(p => p.GetStream()).Returns(stream.Object);
var client = new MllpClient(_tcpClient.Object, _config, _logger.Object);
- var action = new Action((client, results) =>
+ var action = new Func(async (client, results) =>
{
- Assert.Single(results.Messages);
- Assert.Equal(originalMessage, results.Messages.First().HL7Message);
- Assert.Null(results.AggregateException);
+ await Task.Run(() =>
+ {
+ Assert.Single(results.Messages);
+ Assert.Equal(originalMessage, results.Messages.First().HL7Message);
+ Assert.Null(results.AggregateException);
- stream.Verify(p => p.FlushAsync(It.IsAny()), Times.Never());
- stream.Verify(p => p.WriteAsync(It.IsAny>(), It.IsAny()), Times.Never());
+ stream.Verify(p => p.FlushAsync(It.IsAny()), Times.Never());
+ stream.Verify(p => p.WriteAsync(It.IsAny>(), It.IsAny()), Times.Never());
+ });
});
await client.Start(action, _cancellationTokenSource.Token);
}
@@ -204,10 +228,9 @@ public async Task ReceiveData_NeverSendAck()
public async Task ReceiveData_ExceptionSendingAck()
{
var originalMessage = SampleMessage.Replace("", string.Empty);
- var message = @$"{Resources.AsciiVT}{originalMessage}{Resources.AsciiFS}";
- var messageBytes = Encoding.UTF8.GetBytes(message);
var hl7Message = new Message(originalMessage);
hl7Message.ParseMessage();
+ var messageBytes = hl7Message.GetMLLP();
var index = 0;
var size = 0;
@@ -227,13 +250,16 @@ public async Task ReceiveData_ExceptionSendingAck()
_tcpClient.Setup(p => p.GetStream()).Returns(stream.Object);
var client = new MllpClient(_tcpClient.Object, _config, _logger.Object);
- var action = new Action((client, results) =>
+ var action = new Func(async (client, results) =>
{
- Assert.Single(results.Messages);
- Assert.Equal(originalMessage, results.Messages.First().HL7Message);
- Assert.NotNull(results.AggregateException);
- Assert.Single(results.AggregateException.InnerExceptions);
- Assert.Equal("error", results.AggregateException.InnerExceptions.First().Message);
+ await Task.Run(() =>
+ {
+ Assert.Single(results.Messages);
+ Assert.Equal(originalMessage, results.Messages.First().HL7Message);
+ Assert.NotNull(results.AggregateException);
+ Assert.Single(results.AggregateException.InnerExceptions);
+ Assert.Equal("error", results.AggregateException.InnerExceptions.First().Message);
+ });
});
await client.Start(action, _cancellationTokenSource.Token);
}
@@ -242,10 +268,9 @@ public async Task ReceiveData_ExceptionSendingAck()
public async Task ReceiveData_CompleteWorkflow()
{
var originalMessage = SampleMessage.Replace("", string.Empty);
- var message = @$"{Resources.AsciiVT}{originalMessage}{Resources.AsciiFS}";
- var messageBytes = Encoding.UTF8.GetBytes(message);
var hl7Message = new Message(originalMessage);
hl7Message.ParseMessage();
+ var messageBytes = hl7Message.GetMLLP();
var index = 0;
var size = 0;
@@ -265,14 +290,64 @@ public async Task ReceiveData_CompleteWorkflow()
_tcpClient.Setup(p => p.GetStream()).Returns(stream.Object);
var client = new MllpClient(_tcpClient.Object, _config, _logger.Object);
- var action = new Action((client, results) =>
+ var action = new Func(async (client, results) =>
{
- Assert.Single(results.Messages);
- Assert.Equal(originalMessage, results.Messages.First().HL7Message);
- Assert.Null(results.AggregateException);
+ await Task.Run(() =>
+ {
+ Assert.Single(results.Messages);
+ Assert.Equal(originalMessage, results.Messages.First().HL7Message);
+ Assert.Null(results.AggregateException);
+
+ stream.Verify(p => p.FlushAsync(It.IsAny()), Times.Once());
+ stream.Verify(p => p.WriteAsync(It.IsAny>(), It.IsAny()), Times.Once());
+ });
+ });
+ await client.Start(action, _cancellationTokenSource.Token);
+ }
+
+ [Fact(DisplayName = "ReceiveData - complete workflow with multiple messages in one write")]
+ public async Task ReceiveData_CompleteWorkflow_WithMultipleMessages()
+ {
+ var originalMessage = SampleMessage.Replace("", string.Empty);
- stream.Verify(p => p.FlushAsync(It.IsAny()), Times.Once());
- stream.Verify(p => p.WriteAsync(It.IsAny>(), It.IsAny()), Times.Once());
+ var hl7Message = new Message(originalMessage);
+ hl7Message.ParseMessage();
+
+ var messageBytes = hl7Message.GetMLLP();
+ var multipleMessages = new byte[messageBytes.Length * 2];
+ messageBytes.CopyTo(multipleMessages, 0);
+ messageBytes.CopyTo(multipleMessages, messageBytes.Length);
+
+ var index = 0;
+ var size = 0;
+ var stream = new Mock();
+ stream.Setup(p => p.WriteAsync(It.IsAny>(), It.IsAny()));
+ stream.Setup(p => p.FlushAsync(It.IsAny()));
+ stream.Setup(p => p.ReadAsync(It.IsAny>(), It.IsAny()))
+ .Returns, CancellationToken>((data, cancellationToken) =>
+ {
+ var toBeCopied = multipleMessages.Skip(index).Take(data.Length).ToArray();
+ toBeCopied.CopyTo(data);
+ index += toBeCopied.Length;
+ size = toBeCopied.Length;
+ return ValueTask.FromResult(size);
+ });
+
+ _tcpClient.Setup(p => p.GetStream()).Returns(stream.Object);
+ var client = new MllpClient(_tcpClient.Object, _config, _logger.Object);
+
+ var action = new Func(async (client, results) =>
+ {
+ await Task.Run(() =>
+ {
+ Assert.Equal(2, results.Messages.Count);
+ Assert.Equal(originalMessage, results.Messages[0].HL7Message);
+ Assert.Equal(originalMessage, results.Messages[1].HL7Message);
+ Assert.Null(results.AggregateException);
+
+ stream.Verify(p => p.FlushAsync(It.IsAny()), Times.Exactly(2));
+ stream.Verify(p => p.WriteAsync(It.IsAny>(), It.IsAny()), Times.Exactly(2));
+ });
});
await client.Start(action, _cancellationTokenSource.Token);
}
diff --git a/src/InformaticsGateway/Test/Services/HealthLevel7/MllpServiceTest.cs b/src/InformaticsGateway/Test/Services/HealthLevel7/MllpServiceTest.cs
index a1ed415db..d4f374425 100644
--- a/src/InformaticsGateway/Test/Services/HealthLevel7/MllpServiceTest.cs
+++ b/src/InformaticsGateway/Test/Services/HealthLevel7/MllpServiceTest.cs
@@ -1,5 +1,18 @@
-// SPDX-FileCopyrightText: © 2022 MONAI Consortium
-// SPDX-License-Identifier: Apache License 2.0
+/*
+ * Copyright 2022 MONAI Consortium
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
using System;
using System.Collections.Generic;
@@ -10,9 +23,12 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Monai.Deploy.InformaticsGateway.Api.Rest;
+using Monai.Deploy.InformaticsGateway.Api.Storage;
using Monai.Deploy.InformaticsGateway.Configuration;
using Monai.Deploy.InformaticsGateway.Services.Common;
+using Monai.Deploy.InformaticsGateway.Services.Connectors;
using Monai.Deploy.InformaticsGateway.Services.HealthLevel7;
+using Monai.Deploy.InformaticsGateway.Services.Storage;
using Monai.Deploy.InformaticsGateway.SharedTest;
using Moq;
using xRetry;
@@ -22,52 +38,56 @@ namespace Monai.Deploy.InformaticsGateway.Test.Services.HealthLevel7
{
public class MllpServiceTest
{
- private readonly CancellationTokenSource _cancellationTokenSource;
+ private readonly Mock _serviceScopeFactory;
+ private readonly IOptions _options;
+
private readonly Mock _tcpListenerFactory;
+ private readonly Mock _loggerFactory;
private readonly Mock _mllpClientFactory;
- private readonly Mock _serviceScopeFactory;
+ private readonly Mock _uploadQueue;
+ private readonly Mock _payloadAssembler;
+ private readonly Mock _tcpListener;
+
+ private readonly CancellationTokenSource _cancellationTokenSource;
private readonly Mock _serviceScope;
- private readonly Mock _loggerFactory;
private readonly Mock> _logger;
- private readonly Mock _tcpListener;
- private readonly IOptions _options;
+ private readonly IServiceProvider _serviceProvider;
public MllpServiceTest()
{
- _cancellationTokenSource = new CancellationTokenSource();
+ _serviceScopeFactory = new Mock();
+ _options = Options.Create(new InformaticsGatewayConfiguration());
+
_tcpListenerFactory = new Mock();
+ _loggerFactory = new Mock();
_mllpClientFactory = new Mock();
- _serviceScopeFactory = new Mock();
+ _uploadQueue = new Mock();
+ _payloadAssembler = new Mock();
+ _tcpListener = new Mock();
+
+ _cancellationTokenSource = new CancellationTokenSource();
_serviceScope = new Mock();
- _loggerFactory = new Mock();
_logger = new Mock>();
- _tcpListener = new Mock();
_serviceScopeFactory.Setup(p => p.CreateScope()).Returns(_serviceScope.Object);
- var serviceProvider = new Mock();
- serviceProvider
- .Setup(x => x.GetService(typeof(ILoggerFactory)))
- .Returns(_loggerFactory.Object);
- serviceProvider
- .Setup(x => x.GetService(typeof(ITcpListenerFactory)))
- .Returns(_tcpListenerFactory.Object);
- serviceProvider
- .Setup(x => x.GetService(typeof(IMllpClientFactory)))
- .Returns(_mllpClientFactory.Object);
-
- _serviceScope.Setup(x => x.ServiceProvider).Returns(serviceProvider.Object);
+ var services = new ServiceCollection();
+ services.AddScoped(p => _loggerFactory.Object);
+ services.AddScoped(p => _tcpListenerFactory.Object);
+ services.AddScoped(p => _mllpClientFactory.Object);
+ services.AddScoped(p => _uploadQueue.Object);
+ services.AddScoped(p => _payloadAssembler.Object);
+ _serviceProvider = services.BuildServiceProvider();
+ _serviceScopeFactory.Setup(p => p.CreateScope()).Returns(_serviceScope.Object);
+ _serviceScope.Setup(p => p.ServiceProvider).Returns(_serviceProvider);
_loggerFactory.Setup(p => p.CreateLogger(It.IsAny())).Returns(_logger.Object);
-
- _options = Options.Create(new InformaticsGatewayConfiguration());
-
_tcpListenerFactory.Setup(p => p.CreateTcpListener(It.IsAny(), It.IsAny())).Returns(_tcpListener.Object);
_logger.Setup(p => p.IsEnabled(It.IsAny())).Returns(true);
}
- [RetryFact(DisplayName = "Constructor")]
- public void Constructor()
+ [RetryFact()]
+ public void GivenAMllpService_WhenInitialized_ExpectParametersToBeValidated()
{
Assert.Throws(() => new MllpService(null, null));
Assert.Throws(() => new MllpService(_serviceScopeFactory.Object, null));
@@ -75,8 +95,8 @@ public void Constructor()
new MllpService(_serviceScopeFactory.Object, _options);
}
- [RetryFact(DisplayName = "Can start service")]
- public void CanStart()
+ [RetryFact()]
+ public void GivenAMllpService_WhenStartAsyncIsCalled_ExpectServiceStartupNormally()
{
var service = new MllpService(_serviceScopeFactory.Object, _options);
var task = service.StartAsync(_cancellationTokenSource.Token);
@@ -85,8 +105,8 @@ public void CanStart()
Assert.Equal(ServiceStatus.Running, service.Status);
}
- [RetryFact(DisplayName = "Can stop service")]
- public void CanStop()
+ [RetryFact()]
+ public void GivenAMllpService_WhenStopAsyncIsCalled_ExpectServiceStopsNormally()
{
_tcpListener.Setup(p => p.Stop());
var service = new MllpService(_serviceScopeFactory.Object, _options);
@@ -97,18 +117,18 @@ public void CanStop()
_tcpListener.Verify(p => p.Stop(), Times.Once());
}
- [RetryFact(10, 100, DisplayName = "Tracks active connections")]
- public void TracksActiveConnections()
+ [RetryFact(10, 100)]
+ public void GivenTcpConnections_WhenConnectsAndDisconnectsFromMllpService_ExpectItToTrackActiveConnections()
{
- var actions = new Dictionary>();
+ var actions = new Dictionary>();
var mllpClients = new List>();
var checkEvent = new CountdownEvent(5);
_mllpClientFactory.Setup(p => p.CreateClient(It.IsAny(), It.IsAny(), It.IsAny>()))
.Returns(() =>
{
var client = new Mock();
- client.Setup(p => p.Start(It.IsAny>(), It.IsAny()))
- .Callback, CancellationToken>((action, cancellationToken) =>
+ client.Setup(p => p.Start(It.IsAny>(), It.IsAny()))
+ .Callback, CancellationToken>((action, cancellationToken) =>
{
actions.Add(client.Object, action);
checkEvent.Signal();
@@ -141,13 +161,13 @@ public void TracksActiveConnections()
foreach (var action in actions.Keys)
{
- actions[action](action, null);
+ actions[action](action, new MllpClientResult(null, null));
}
Assert.Equal(0, service.ActiveConnections);
}
- [RetryFact(DisplayName = "Abides by the maximum connection limit")]
- public void AbidesByMaximumConnectionLimit()
+ [RetryFact]
+ public void GivenAMllpService_WhenMaximumConnectionLimitIsConfigure_ExpectTheServiceToAbideByTheLimit()
{
var checkEvent = new CountdownEvent(_options.Value.Hl7.MaximumNumberOfConnections);
var mllpClients = new List>();
@@ -155,8 +175,8 @@ public void AbidesByMaximumConnectionLimit()
.Returns(() =>
{
var client = new Mock();
- client.Setup(p => p.Start(It.IsAny>(), It.IsAny()))
- .Callback, CancellationToken>((action, cancellationToken) =>
+ client.Setup(p => p.Start(It.IsAny>(), It.IsAny()))
+ .Callback, CancellationToken>((action, cancellationToken) =>
{
checkEvent.Signal();
});
@@ -179,8 +199,8 @@ public void AbidesByMaximumConnectionLimit()
_logger.VerifyLoggingMessageBeginsWith($"Maximum number {_options.Value.Hl7.MaximumNumberOfConnections} of clients reached.", LogLevel.Information, Times.AtLeastOnce());
}
- [RetryFact(DisplayName = "Dispose clients")]
- public async Task DisposeClients()
+ [RetryFact]
+ public async Task GivenConnectedTcpClients_WhenDisconnects_ExpectServiceToDisposeResources()
{
var checkEvent = new ManualResetEventSlim();
var client = new Mock();
@@ -188,8 +208,8 @@ public async Task DisposeClients()
_mllpClientFactory.Setup(p => p.CreateClient(It.IsAny(), It.IsAny(), It.IsAny>()))
.Returns(() =>
{
- client.Setup(p => p.Start(It.IsAny>(), It.IsAny()))
- .Callback, CancellationToken>((action, cancellationToken) =>
+ client.Setup(p => p.Start(It.IsAny>(), It.IsAny()))
+ .Callback, CancellationToken>((action, cancellationToken) =>
{
callCount++;
checkEvent.Set();
@@ -215,5 +235,45 @@ public async Task DisposeClients()
service.Dispose();
client.Verify(p => p.Dispose(), Times.Exactly(callCount));
}
+
+ [RetryFact]
+ public async Task GivenATcpClientWithHl7Messages_WhenDisconnected_ExpectMessageToBeQueued()
+ {
+ var checkEvent = new ManualResetEventSlim();
+ var client = new Mock();
+ _mllpClientFactory.Setup(p => p.CreateClient(It.IsAny(), It.IsAny(), It.IsAny>()))
+ .Returns(() =>
+ {
+ client.Setup(p => p.Start(It.IsAny>(), It.IsAny()))
+ .Callback, CancellationToken>((action, cancellationToken) =>
+ {
+ var results = new MllpClientResult(
+ new List
+ {
+ new HL7.Dotnetcore.Message(""),
+ new HL7.Dotnetcore.Message(""),
+ new HL7.Dotnetcore.Message(""),
+ }, null);
+ action(client.Object, results);
+ checkEvent.Set();
+ _cancellationTokenSource.Cancel();
+ });
+ client.Setup(p => p.Dispose());
+ client.SetupGet(p => p.ClientId).Returns(Guid.NewGuid());
+ return client.Object;
+ });
+
+ _tcpListener.Setup(p => p.AcceptTcpClientAsync(It.IsAny()))
+ .Returns(ValueTask.FromResult((new Mock()).Object));
+
+ var service = new MllpService(_serviceScopeFactory.Object, _options);
+ _ = service.StartAsync(_cancellationTokenSource.Token);
+
+ Assert.True(checkEvent.Wait(2000));
+ await Task.Delay(500).ConfigureAwait(false);
+
+ _uploadQueue.Verify(p => p.Queue(It.IsAny()), Times.Exactly(3));
+ _payloadAssembler.Verify(p => p.Queue(It.IsAny(), It.IsAny()), Times.Exactly(3));
+ }
}
}
diff --git a/src/InformaticsGateway/Test/Services/Http/InferenceControllerTest.cs b/src/InformaticsGateway/Test/Services/Http/InferenceControllerTest.cs
index c3e820317..68e6d5cb6 100644
--- a/src/InformaticsGateway/Test/Services/Http/InferenceControllerTest.cs
+++ b/src/InformaticsGateway/Test/Services/Http/InferenceControllerTest.cs
@@ -22,7 +22,6 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Options;
using Monai.Deploy.InformaticsGateway.Api.Rest;
using Monai.Deploy.InformaticsGateway.Configuration;
using Monai.Deploy.InformaticsGateway.Repositories;
@@ -37,7 +36,6 @@ public class InferenceControllerTest
{
private readonly Mock _inferenceRequestRepository;
private readonly InformaticsGatewayConfiguration _informaticsGatewayConfiguration;
- private readonly IOptions _configuration;
private readonly Mock> _logger;
private readonly Mock _fileSystem;
private readonly InferenceController _controller;
@@ -47,7 +45,6 @@ public InferenceControllerTest()
{
_inferenceRequestRepository = new Mock();
_informaticsGatewayConfiguration = new InformaticsGatewayConfiguration();
- _configuration = Options.Create(_informaticsGatewayConfiguration);
_logger = new Mock>();
_fileSystem = new Mock();
_problemDetailsFactory = new Mock();
@@ -70,7 +67,7 @@ public InferenceControllerTest()
Instance = instance
};
});
- _controller = new InferenceController(_inferenceRequestRepository.Object, _configuration, _logger.Object)
+ _controller = new InferenceController(_inferenceRequestRepository.Object, _logger.Object)
{
ProblemDetailsFactory = _problemDetailsFactory.Object
};
@@ -189,59 +186,6 @@ public void NewInferenceRequest_ShallReturnProblemIfSameTransactionIdExists()
Assert.Equal(409, problem.Status);
}
- //[RetryFact(5, 250, DisplayName = "NewInferenceRequest - shall return problem if failed to creaet working dir")]
- //public void NewInferenceRequest_ShallReturnProblemIfFailedToCreateWorkingDir()
- //{
- // _fileSystem.Setup(p => p.Directory.CreateDirectory(It.IsAny()))
- // .Throws(new IOException());
- // _fileSystem.Setup(p => p.Path.Combine(It.IsAny(), It.IsAny())).Returns((string path1, string path2) => System.IO.Path.Combine(path1, path2));
-
- // var input = new InferenceRequest
- // {
- // TransactionId = Guid.NewGuid().ToString(),
- // InputResources = new List()
- // {
- // new RequestInputDataResource
- // {
- // Interface = InputInterfaceType.Algorithm,
- // ConnectionDetails = new InputConnectionDetails()
- // },
- // new RequestInputDataResource
- // {
- // Interface = InputInterfaceType.DicomWeb,
- // ConnectionDetails = new InputConnectionDetails
- // {
- // Uri = "http://my.svc/api"
- // }
- // }
- // },
- // InputMetadata = new InferenceRequestMetadata
- // {
- // Details = new InferenceRequestDetails
- // {
- // Type = InferenceRequestType.DicomUid,
- // Studies = new List
- // {
- // new RequestedStudy
- // {
- // StudyInstanceUid = "1"
- // }
- // }
- // }
- // }
- // };
-
- // var result = _controller.NewInferenceRequest(input);
-
- // Assert.NotNull(result);
- // var objectResult = result.Result as ObjectResult;
- // Assert.NotNull(objectResult);
- // var problem = objectResult.Value as ProblemDetails;
- // Assert.NotNull(problem);
- // Assert.Equal("Failed to generate a temporary storage location for request.", problem.Title);
- // Assert.Equal(500, problem.Status);
- //}
-
[RetryFact(5, 250, DisplayName = "NewInferenceRequest - shall return problem if failed to add job")]
public void NewInferenceRequest_ShallReturnProblemIfFailedToAddJob()
{
diff --git a/src/InformaticsGateway/Test/Services/Storage/ObjectUploadServiceTest.cs b/src/InformaticsGateway/Test/Services/Storage/ObjectUploadServiceTest.cs
index 8ef50d71c..5a7e445c3 100644
--- a/src/InformaticsGateway/Test/Services/Storage/ObjectUploadServiceTest.cs
+++ b/src/InformaticsGateway/Test/Services/Storage/ObjectUploadServiceTest.cs
@@ -124,7 +124,7 @@ public async Task GivenADicomFileStorageMetadata_WhenQueuedForUpload_ExpectTwoFi
}
[Fact]
- public async Task GivenAFhirFileStorageMetadata_WhenQueuedForUpload_ExpectSingleFileToBeUploaded()
+ public void GivenAFhirFileStorageMetadata_WhenQueuedForUpload_ExpectSingleFileToBeUploaded()
{
var countdownEvent = new CountdownEvent(1);
_storageService.Setup(p => p.PutObjectAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny()))
@@ -137,7 +137,7 @@ public async Task GivenAFhirFileStorageMetadata_WhenQueuedForUpload_ExpectSingle
Assert.Equal(ServiceStatus.Running, svc.Status);
- var file = await GenerateFhirFileStorageMetadata();
+ var file = GenerateFhirFileStorageMetadata();
_uploadQueue.Queue(file);
Assert.True(countdownEvent.Wait(TimeSpan.FromSeconds(3)));
@@ -145,11 +145,11 @@ public async Task GivenAFhirFileStorageMetadata_WhenQueuedForUpload_ExpectSingle
_storageService.Verify(p => p.PutObjectAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny()), Times.Once());
}
- private async Task GenerateFhirFileStorageMetadata()
+ private FhirFileStorageMetadata GenerateFhirFileStorageMetadata()
{
var file = new FhirFileStorageMetadata(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), FhirStorageFormat.Json);
- await file.SetDataStream("[]");
+ file.SetDataStream("[]");
return file;
}
diff --git a/src/InformaticsGateway/appsettings.Test.json b/src/InformaticsGateway/appsettings.Test.json
index 330518a44..5bdcb25b7 100644
--- a/src/InformaticsGateway/appsettings.Test.json
+++ b/src/InformaticsGateway/appsettings.Test.json
@@ -11,17 +11,17 @@
},
"messaging": {
"publisherSettings": {
- "endpoint": "192.168.0.2",
+ "endpoint": "172.18.0.4",
"username": "rabbitmq",
- "password": "qmtibbar",
- "virtualHost": "/",
+ "password": "rabbitmq",
+ "virtualHost": "monaideploy",
"exchange": "monaideploy"
},
"subscriberSettings": {
- "endpoint": "192.168.0.2",
+ "endpoint": "172.18.0.4",
"username": "rabbitmq",
- "password": "qmtibbar",
- "virtualHost": "/",
+ "password": "rabbitmq",
+ "virtualHost": "monaideploy",
"exchange": "monaideploy",
"exportRequestQueue": "export_tasks"
}
@@ -31,7 +31,7 @@
"bucketName": "monai",
"temporaryBucketName": "monai",
"settings": {
- "endpoint": "192.168.0.4:9000",
+ "endpoint": "172.18.0.2:9000",
"accessKey": "minioadmin",
"accessToken": "minioadmin",
"securedConnection": false,
diff --git a/src/InformaticsGateway/appsettings.json b/src/InformaticsGateway/appsettings.json
index 6aef050d9..c5fce2636 100644
--- a/src/InformaticsGateway/appsettings.json
+++ b/src/InformaticsGateway/appsettings.json
@@ -53,6 +53,12 @@
"executableLocation": "/bin/mc",
"serviceName": "MinIO"
}
+ },
+ "hl7": {
+ "port": 2575,
+ "maximumNumberOfConnections": 10,
+ "clientTimeout": 60000,
+ "sendAck": true
}
},
"Logging": {
diff --git a/src/Monai.Deploy.InformaticsGateway.sln b/src/Monai.Deploy.InformaticsGateway.sln
index 753e7c5f6..38806dda6 100644
--- a/src/Monai.Deploy.InformaticsGateway.sln
+++ b/src/Monai.Deploy.InformaticsGateway.sln
@@ -1,5 +1,22 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
+
+
+
VisualStudioVersion = 17.0.32014.148
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Monai.Deploy.InformaticsGateway", "InformaticsGateway\Monai.Deploy.InformaticsGateway.csproj", "{5F7BF552-40CD-4B57-BD26-728BDC207AC6}"
diff --git a/tests/Integration.Test/Drivers/Configurations.cs b/tests/Integration.Test/Drivers/Configurations.cs
index a20f048aa..9aaa59cc5 100644
--- a/tests/Integration.Test/Drivers/Configurations.cs
+++ b/tests/Integration.Test/Drivers/Configurations.cs
@@ -194,6 +194,11 @@ public class InformaticsGatewaySettings
///
public int ApiPort { get; set; }
+ ///
+ /// Gets or sets the HL7 listening port on the Informatics Gateway.
+ ///
+ public int Hl7Port { get; set; }
+
///
/// Gets or sets the name of the bucket used by the storage service.
///
diff --git a/tests/Integration.Test/Features/HealthLevel7.feature b/tests/Integration.Test/Features/HealthLevel7.feature
new file mode 100644
index 000000000..cd4715f87
--- /dev/null
+++ b/tests/Integration.Test/Features/HealthLevel7.feature
@@ -0,0 +1,50 @@
+# Copyright 2022 MONAI Consortium
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# @ignored
+Feature: Health Level 7
+
+ @messaging_workflow_request @messaging
+ Scenario Outline: Ability to store different versions of HL7 messages
+ Given HL7 messages in version
+ When the message are sent to Informatics Gateway
+ Then acknowledgement are received
+ And a workflow requests sent to message broker
+ And messages are uploaded to storage service
+
+ Examples:
+ | version |
+ | 2.3 |
+ | 2.3.1 |
+ | 2.4 |
+ | 2.5.1 |
+ | 2.6 |
+ | 2.8 |
+
+ @messaging_workflow_request @messaging
+ Scenario Outline: Ability to receive and store multiple messages in a single batch
+ Given HL7 messages in version
+ When the message are sent to Informatics Gateway in one batch
+ Then acknowledgement are received
+ And a workflow requests sent to message broker
+ And messages are uploaded to storage service
+
+ Examples:
+ | version |
+ | 2.3 |
+ | 2.3.1 |
+ | 2.4 |
+ | 2.5.1 |
+ | 2.6 |
+ | 2.8 |
\ No newline at end of file
diff --git a/tests/Integration.Test/Monai.Deploy.InformaticsGateway.Integration.Test.csproj b/tests/Integration.Test/Monai.Deploy.InformaticsGateway.Integration.Test.csproj
index dc865720b..8dc3f2d74 100644
--- a/tests/Integration.Test/Monai.Deploy.InformaticsGateway.Integration.Test.csproj
+++ b/tests/Integration.Test/Monai.Deploy.InformaticsGateway.Integration.Test.csproj
@@ -24,6 +24,7 @@
+
@@ -53,6 +54,10 @@
+
+
+
+
@@ -69,4 +74,4 @@
-
+
\ No newline at end of file
diff --git a/tests/Integration.Test/StepDefinitions/HealthLevel7Definitions.cs b/tests/Integration.Test/StepDefinitions/HealthLevel7Definitions.cs
new file mode 100644
index 000000000..cac0f858a
--- /dev/null
+++ b/tests/Integration.Test/StepDefinitions/HealthLevel7Definitions.cs
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2022 MONAI Consortium
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System.Net.Sockets;
+using System.Text;
+using Ardalis.GuardClauses;
+using Minio;
+using Monai.Deploy.InformaticsGateway.Configuration;
+using Monai.Deploy.InformaticsGateway.Integration.Test.Drivers;
+using Monai.Deploy.InformaticsGateway.Integration.Test.Hooks;
+using Monai.Deploy.Messaging.Events;
+using Monai.Deploy.Messaging.Messages;
+using TechTalk.SpecFlow.Infrastructure;
+
+namespace Monai.Deploy.InformaticsGateway.Integration.Test.StepDefinitions
+{
+ [Binding]
+ [CollectionDefinition("SpecFlowNonParallelizableFeatures", DisableParallelization = true)]
+ public class HealthLevel7Definitions
+ {
+ internal static readonly TimeSpan WaitTimeSpan = TimeSpan.FromMinutes(2);
+ private readonly FeatureContext _featureContext;
+ private readonly ScenarioContext _scenarioContext;
+ private readonly ISpecFlowOutputHelper _outputHelper;
+ private readonly Configurations _configuration;
+ private readonly RabbitMqHooks _rabbitMqHooks;
+ private readonly Dictionary _input;
+ private readonly Dictionary _output;
+
+ public HealthLevel7Definitions(
+ FeatureContext featureContext,
+ ScenarioContext scenarioContext,
+ ISpecFlowOutputHelper outputHelper,
+ Configurations configuration,
+ RabbitMqHooks rabbitMqHooks)
+ {
+ _featureContext = featureContext ?? throw new ArgumentNullException(nameof(featureContext));
+ _scenarioContext = scenarioContext ?? throw new ArgumentNullException(nameof(scenarioContext));
+ _outputHelper = outputHelper ?? throw new ArgumentNullException(nameof(outputHelper));
+ _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
+ _rabbitMqHooks = rabbitMqHooks ?? throw new ArgumentNullException(nameof(rabbitMqHooks));
+
+ _input = new Dictionary();
+ _output = new Dictionary();
+ }
+
+ [Given(@"HL7 messages in version (.*)")]
+ public async Task GivenHl7MessagesInVersionX(string version)
+ {
+ Guard.Against.NullOrWhiteSpace(version, nameof(version));
+
+ var files = Directory.GetFiles($"data/hl7/{version}");
+
+ foreach (var file in files)
+ {
+ var text = await File.ReadAllTextAsync(file);
+ var message = new HL7.Dotnetcore.Message(text);
+ message.ParseMessage();
+ message.SetValue("MSH.10", file);
+ _input[file] = message;
+ }
+ _rabbitMqHooks.SetupMessageHandle(1);
+ }
+
+ [When(@"the message are sent to Informatics Gateway")]
+ public async Task WhenTheMessagesAreSentToInformaticsGateway()
+ {
+ using var tcpClient = new TcpClient();
+ await tcpClient.ConnectAsync(_configuration.InformaticsGatewayOptions.Host, _configuration.InformaticsGatewayOptions.Hl7Port);
+ var networkStream = tcpClient.GetStream();
+ foreach (var file in _input.Keys)
+ {
+ _outputHelper.WriteLine($"Sending file {file}...");
+ var data = _input[file].GetMLLP();
+ await networkStream.WriteAsync(data, 0, data.Length);
+ var buffer = new byte[1048576];
+ var responseData = string.Empty;
+ do
+ {
+ if (await networkStream.ReadAsync(buffer, 0, buffer.Length) == 0)
+ {
+ break;
+ }
+
+ responseData = Encoding.UTF8.GetString(buffer.ToArray());
+
+ var startIndex = responseData.IndexOf((char)0x0B);
+ if (startIndex >= 0)
+ {
+ var endIndex = responseData.IndexOf((char)0x1C);
+
+ if (endIndex > startIndex)
+ {
+ var messageStartIndex = startIndex + 1;
+ var messageEndIndex = endIndex + 1;
+ responseData = responseData.Substring(messageStartIndex, endIndex - messageStartIndex);
+ break;
+ }
+ }
+ } while (true);
+ _output[file] = responseData;
+ }
+ tcpClient.Close();
+ }
+
+ [When(@"the message are sent to Informatics Gateway in one batch")]
+ public async Task WhenTheMessagesAreSentToInformaticsGatewayInOneBatch()
+ {
+ var messages = new List();
+ foreach (var file in _input.Keys)
+ {
+ _outputHelper.WriteLine($"Sending file {file}...");
+ var data = _input[file].GetMLLP();
+ messages.AddRange(data);
+ }
+
+ using var tcpClient = new TcpClient();
+ await tcpClient.ConnectAsync(_configuration.InformaticsGatewayOptions.Host, _configuration.InformaticsGatewayOptions.Hl7Port);
+ var networkStream = tcpClient.GetStream();
+ await networkStream.WriteAsync(messages.ToArray(), 0, messages.Count);
+ var buffer = new byte[512];
+ var responseData = string.Empty;
+
+ do
+ {
+ if (await networkStream.ReadAsync(buffer, 0, buffer.Length) == 0)
+ {
+ break;
+ }
+
+ responseData = Encoding.UTF8.GetString(buffer.ToArray());
+ var rawHl7Messages = HL7.Dotnetcore.MessageHelper.ExtractMessages(responseData);
+
+ foreach(var message in rawHl7Messages)
+ {
+ var hl7Message = new HL7.Dotnetcore.Message(message);
+ hl7Message.ParseMessage();
+ var segment = hl7Message.DefaultSegment("MSH");
+ _output[segment.Fields(10).Value] = message;
+ }
+
+ if (_output.Count == _input.Count)
+ {
+ break;
+ }
+ } while (true);
+ tcpClient.Close();
+ }
+
+ [Then(@"acknowledgement are received")]
+ public void ThenAcknowledgementAreReceived()
+ {
+ foreach (var file in _output.Keys)
+ {
+ _outputHelper.WriteLine($"Verifying acknowledgement for {file}...");
+ var message = new HL7.Dotnetcore.Message(_output[file]);
+ message.ParseMessage();
+ var segment = message.DefaultSegment("MSH");
+ _outputHelper.WriteLine($"ACK Value= {segment.Value}...");
+ segment.Fields(9).Value.Should().Be("ACK");
+ }
+ }
+
+ [Then(@"a workflow requests sent to message broker")]
+ public void ThenAWorkflowRequestIsSentToMessageBroker()
+ {
+ _rabbitMqHooks.MessageWaitHandle.Wait(WaitTimeSpan).Should().BeTrue();
+ }
+
+ [Then(@"messages are uploaded to storage service")]
+ public async Task ThenMessageAreUploadedToStorageService()
+ {
+ var messages = _scenarioContext[RabbitMqHooks.ScenarioContextKey] as IList;
+ messages.Should().NotBeNullOrEmpty().And.HaveCount(1);
+ var message = messages.First();
+ message.ApplicationId.Should().Be(MessageBrokerConfiguration.InformaticsGatewayApplicationId);
+ var request = message.ConvertTo();
+ request.Should().NotBeNull();
+ request.FileCount.Should().Be(_input.Count);
+
+ var minioClient = new MinioClient()
+ .WithEndpoint(_configuration.StorageServiceOptions.Endpoint)
+ .WithCredentials(_configuration.StorageServiceOptions.AccessKey, _configuration.StorageServiceOptions.AccessToken);
+
+ foreach (var file in request.Payload)
+ {
+ var getObjectArgs = new GetObjectArgs()
+ .WithBucket(request.Bucket)
+ .WithObject($"{request.PayloadId}/{file.Path}")
+ .WithCallbackStream((stream) =>
+ {
+ using var memoryStream = new MemoryStream();
+ stream.CopyTo(memoryStream);
+ memoryStream.Position = 0;
+ var data = Encoding.UTF8.GetString(memoryStream.ToArray());
+
+ var hl7Message = new HL7.Dotnetcore.Message(data);
+ hl7Message.ParseMessage();
+
+ var matchFound = false;
+ foreach (var key in _input.Keys)
+ {
+ if (hl7Message.HL7Message.Equals(_input[key].SerializeMessage(true)))
+ {
+ matchFound = true;
+ break;
+ }
+ }
+ matchFound.Should().BeTrue();
+ });
+ await minioClient.GetObjectAsync(getObjectArgs);
+ }
+ }
+ }
+}
diff --git a/tests/Integration.Test/appsettings.json b/tests/Integration.Test/appsettings.json
index ad79e4389..349fecd07 100644
--- a/tests/Integration.Test/appsettings.json
+++ b/tests/Integration.Test/appsettings.json
@@ -6,7 +6,8 @@
"InformaticsGatewaySettings": {
"host": "$HOST_IP",
"dimsePort": 104,
- "apiPort": 5000
+ "apiPort": 5000,
+ "Hl7Port": 2575
},
"MessageBrokerSettings": {
"endpoint": "$HOST_IP",
diff --git a/tests/Integration.Test/data/hl7/2.3.1/001.txt b/tests/Integration.Test/data/hl7/2.3.1/001.txt
new file mode 100644
index 000000000..4fdf81dd3
--- /dev/null
+++ b/tests/Integration.Test/data/hl7/2.3.1/001.txt
@@ -0,0 +1 @@
+MSH|^~\&|||||20220808145711.766-0700||ADT^A09|301|T|2.3.1
diff --git a/tests/Integration.Test/data/hl7/2.3.1/002.txt b/tests/Integration.Test/data/hl7/2.3.1/002.txt
new file mode 100644
index 000000000..dd2a4b8c1
--- /dev/null
+++ b/tests/Integration.Test/data/hl7/2.3.1/002.txt
@@ -0,0 +1 @@
+MSH|^~\&|||||20220808145738.123-0700||OSQ^Q06|401|T|2.3.1
diff --git a/tests/Integration.Test/data/hl7/2.3/001.txt b/tests/Integration.Test/data/hl7/2.3/001.txt
new file mode 100644
index 000000000..6fd63f013
--- /dev/null
+++ b/tests/Integration.Test/data/hl7/2.3/001.txt
@@ -0,0 +1,21 @@
+MSH|^~\&|LAB|MYFAC|LAB||201411130917||ORU^R01|3216598|D|2.3|||AL||
+PID|1|ABC123DF|AND234DA_PID3|PID_4_ALTID|Patlast^Patfirst^Mid||19670202|F|||4505 21 st^^LAKE COUNTRY^BC^V4V 2S7||222-555-8484|||||MF0050356/15|
+PV1|1|O|MYFACSOMPL||||^Xavarie^Sonna^^^^^XAVS|||||||||||REF||SELF|||||||||||||||||||MYFAC||REG|||201411071440||||||||23390^PV1_52Surname^PV1_52Given^H^^Dr^^PV1_52Mnemonic|
+ORC|RE|PT103933301.0100|||CM|N|||201411130917|^Kyle^Andra^J.^^^^KYLA||^Xavarie^Sonna^^^^^XAVS|MYFAC|
+OBR|1|PT1311:H00001R301.0100|PT1311:H00001R|301.0100^Complete Blood Count (CBC)^00065227^57021-8^CBC \T\ Auto Differential^pCLOCD|R||201411130914|||KYLA||||201411130914||^Xavarie^Sonna^^^^^XAVS||00065227||||201411130915||LAB|F||^^^^^R|^Xavarie^Sonna^^^^^XAVS|
+OBX|1|NM|301.0500^White Blood Count (WBC)^00065227^6690-2^Leukocytes^pCLOCD|1|10.1|10\S\9/L|3.1-9.7|H||A~S|F|||201411130916|MYFAC^MyFake Hospital^L|
+OBX|2|NM|301.0600^Red Blood Count (RBC)^00065227^789-8^Erythrocytes^pCLOCD|1|3.2|10\S\12/L|3.7-5.0|L||A~S|F|||201411130916|MYFAC^MyFake Hospital^L|
+OBX|3|NM|301.0700^Hemoglobin (HGB)^00065227^718-7^Hemoglobin^pCLOCD|1|140|g/L|118-151|N||A~S|F|||201411130916|MYFAC^MyFake Hospital^L|
+OBX|4|NM|301.0900^Hematocrit (HCT)^00065227^4544-3^Hematocrit^pCLOCD|1|0.34|L/L|0.33-0.45|N||A~S|F|||201411130916|MYFAC^MyFake Hospital^L|
+OBX|5|NM|301.1100^MCV^00065227^787-2^Mean Corpuscular Volume^pCLOCD|1|98.0|fL|84.0-98.0|N||A~S|F|||201411130916|MYFAC^MyFake Hospital^L|
+OBX|6|NM|301.1300^MCH^00065227^785-6^Mean Corpuscular Hemoglobin^pCLOCD|1|27.0|pg|28.3-33.5|L||A~S|F|||201411130916|MYFAC^MyFake Hospital^L|
+OBX|7|NM|301.1500^MCHC^00065227^786-4^Mean Corpuscular Hemoglobin Concentration^pCLOCD|1|330|g/L|329-352|N||A~S|F|||201411130916|MYFAC^MyFake Hospital^L|
+OBX|8|NM|301.1700^RDW^00065227^788-0^Erythrocyte Distribution Width^pCLOCD|1|12.0|%|12.0-15.0|N||A~S|F|||201411130916|MYFAC^MyFake Hospital^L|
+OBX|9|NM|301.1900^Platelets^00065227^777-3^Platelets^pCLOCD|1|125|10\S\9/L|147-375|L||A~S|F|||201411130916|MYFAC^MyFake Hospital^L|
+OBX|10|NM|301.2100^Neutrophils^00065227^751-8^Neutrophils^pCLOCD|1|8.0|10\S\9/L|1.2-6.0|H||A~S|F|||201411130916|MYFAC^MyFake Hospital^L|
+OBX|11|NM|301.2300^Lymphocytes^00065227^731-0^Lymphocytes^pCLOCD|1|1.0|10\S\9/L|0.6-3.1|N||A~S|F|||201411130916|MYFAC^MyFake Hospital^L|
+OBX|12|NM|301.2500^Monocytes^00065227^742-7^Monocytes^pCLOCD|1|1.0|10\S\9/L|0.1-0.9|H||A~S|F|||201411130916|MYFAC^MyFake Hospital^L|
+OBX|13|NM|301.2700^Eosinophils^00065227^711-2^Eosinophils^pCLOCD|1|0.0|10\S\9/L|0.0-0.5|N||A~S|F|||201411130916|MYFAC^MyFake Hospital^L|
+OBX|14|NM|301.2900^Basophils^00065227^704-7^Basophils^pCLOCD|1|0.0|10\S\9/L|0.0-0.2|N||A~S|F|||201411130916|MYFAC^MyFake Hospital^L|
+ZDR||^Xavarie^Sonna^^^^^XAVS^^^^^XX^^ATP|
+ZPR||
diff --git a/tests/Integration.Test/data/hl7/2.3/002.txt b/tests/Integration.Test/data/hl7/2.3/002.txt
new file mode 100644
index 000000000..329ab126c
--- /dev/null
+++ b/tests/Integration.Test/data/hl7/2.3/002.txt
@@ -0,0 +1,19 @@
+MSH|^~\&|SendingApp|SendingFac|ReceivingApp|ReceivingFac|20120411070545||ORU^R01|59689|P|2.3|||AL||
+PID|1|12345|12345^^^MIE&1.2.840.114398.1.100&ISO^MR||MOUSE^MINNIE^S||19240101|F|||123 MOUSEHOLE LN^^FORT WAYNE^IN^46808|||||||||||||||||||
+PV1|1|O|||||71^DUCK^DONALD||||||||||||12376|||||||||||||||||||||||||20120410160227||||||
+ORC|RE||12376|||||||100^DUCK^DASIY||71^DUCK^DONALD|^^^||20120411070545|||||
+OBR|1||12376|cbc^CBC|R||20120410160227|||22^GOOF^GOOFY|||Fasting: No|201204101625||71^DUCK^DONALD||||||201204101630|||F||^^^^^R|||||||||||||||||85025|
+OBX|1|NM|wbc^Wbc^Local^6690-2^Wbc^LN||7.0|/nl|3.8-11.0||||F|||20120410160227|lab|12^XYZ LAB|
+OBX|2|NM|neutros^Neutros^Local^770-8^Neutros^LN||68|%|40-82||||F|||20120410160227|lab|12^XYZ LAB|
+OBX|3|NM|lymphs^Lymphs^Local^736-9^Lymphs^LN||20|%|11-47||||F|||20120410160227|lab|12^XYZ LAB|
+OBX|4|NM|monos^Monos^Local^5905-5^Monos^LN||16|%|4-15|H|||F|||20120410160227|lab|12^XYZ LAB|
+OBX|5|NM|eo^Eos^Local^713-8^Eos^LN||3|%|0-8||||F|||20120410160227|lab|12^XYZ LAB|
+OBX|6|NM|baso^Baso^Local^706-2^Baso^LN||0|%|0-1||||F|||20120410160227|lab|12^XYZ LAB|
+OBX|7|NM|ig^Imm Gran^Local^38518-7^Imm Gran^LN||0|%|0-2||||F|||20120410160227|lab|12^XYZ LAB|
+OBX|8|NM|rbc^Rbc^Local^789-8^Rbc^LN||4.02|/pl|4.07-4.92|L|||F|||20120410160227|lab|12^XYZ LAB|
+OBX|9|NM|hgb^Hgb^Local^718-7^Hgb^LN||13.7|g/dl|12.0-14.1||||F|||20120410160227|lab|12^XYZ LAB|
+OBX|10|NM|hct^Hct^Local^4544-3^Hct^LN||40|%|34-43||||F|||20120410160227|lab|12^XYZ LAB|
+OBX|11|NM|mcv^Mcv^Local^787-2^Mcv^LN||80|fl|77-98||||F|||20120410160227|lab|12^XYZ LAB|
+OBX|12|NM|mch^Mch||30|pg|27-35||||F|||20120410160227|lab|12^XYZ LAB|
+OBX|13|NM|mchc^Mchc||32|g/dl|32-35||||F|||20120410160227|lab|12^XYZ LAB|
+OBX|14|NM|plt^Platelets||221|/nl|140-400||||F|||20120410160227|lab|12^XYZ LAB|
diff --git a/tests/Integration.Test/data/hl7/2.3/003.txt b/tests/Integration.Test/data/hl7/2.3/003.txt
new file mode 100644
index 000000000..89f7080fc
--- /dev/null
+++ b/tests/Integration.Test/data/hl7/2.3/003.txt
@@ -0,0 +1,10 @@
+MSH|^~\&|AccMgr|1|||20050110045504||ADT^A01|599102|P|2.3|||
+EVN|A01|20050110045502|||||
+PID|1||10006579^^^1^MRN^1||DUCK^DONALD^D||19241010|M||1|111 DUCK ST^^FOWL^CA^999990000^^M|1|8885551212|8885551212|1|2||40007716^^^AccMgr^VN^1|123121234|||||||||||NO NK1|1|DUCK^HUEY|SO|3583 DUCK RD^^FOWL^CA^999990000|8885552222||Y||||||||||||||
+PV1|1|I|PREOP^101^1^1^^^S|3|||37^DISNEY^WALT^^^^^^AccMgr^^^^CI|||01||||1|||37^DISNEY^WALT^^^^^^AccMgr^^^^CI|2|40007716^^^AccMgr^VN|4|||||||||||||||||||1||G|||20050110045253||||||
+GT1|1|8291|DUCK^DONALD^D||111^DUCKST^^FOWL^CA^999990000|8885551212||19241010|M||1|123121234||||#Cartoon Ducks Inc|111^DUCK ST^^FOWL^CA^999990000|8885551212||PT|
+DG1|1|I9|71596^OSTEOARTHROS NOS-L/LEG ^I9|OSTEOARTHROS NOS-L/LEG ||A| IN1|1|MEDICARE|3|MEDICARE|||||||Cartoon Ducks Inc|19891001|||4|DUCK^DONALD^D|1|19241010|111^DUCK ST^^FOWL^CA^999990000|||||||||||||||||123121234A||||||PT|M|111 DUCK ST^^FOWL^CA^999990000|||||8291
+IN2|1||123121234|Cartoon Ducks Inc|||123121234A|||||||||||||||||||||||||||||||||||||||||||||||||||||||||8885551212
+IN1|2|NON-PRIMARY|9|MEDICAL MUTUAL CALIF.|PO BOX 94776^^HOLLYWOOD^CA^441414776||8003621279|PUBSUMB|||Cartoon Ducks Inc||||7|DUCK^DONALD^D|1|19241010|111 DUCK ST^^FOWL^CA^999990000|||||||||||||||||056269770||||||PT|M|111^DUCK ST^^FOWL^CA^999990000|||||8291
+IN2|2||123121234|Cartoon Ducks Inc||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||8885551212
+IN1|3|SELF PAY|1|SELF PAY|||||||||||5||1
\ No newline at end of file
diff --git a/tests/Integration.Test/data/hl7/2.3/004.txt b/tests/Integration.Test/data/hl7/2.3/004.txt
new file mode 100644
index 000000000..246f1fbfb
--- /dev/null
+++ b/tests/Integration.Test/data/hl7/2.3/004.txt
@@ -0,0 +1,4 @@
+MSH|^~\&|AccMgr|1|||20050110114442||ADT^A02|59910287|P|2.3|||
+EVN|A02|20050110114442|||||
+PID|1||10006579^^^1^MRN^1||DUCK^DONALD^D||19241010|M||1|111^DUCK ST^^FOWL^CA^999990000^^M|1|8885551212|8885551212|1|2||40007716^^^AccMgr^VN^1|123121234|||||||||||NO
+PV1|1|I|IN1^214^1^1^^^S|3||PREOP^101^|37^DISNEY^WALT^^^^^^AccMgr^^^^CI|||01||||1|||37^DISNEY^WALT^^^^^^AccMgr^^^^CI|2|40007716^^^AccMgr^VN|4|||||||||||||||||||1||I|||20050110045253||||||
\ No newline at end of file
diff --git a/tests/Integration.Test/data/hl7/2.3/005.txt b/tests/Integration.Test/data/hl7/2.3/005.txt
new file mode 100644
index 000000000..0834697f6
--- /dev/null
+++ b/tests/Integration.Test/data/hl7/2.3/005.txt
@@ -0,0 +1,12 @@
+MSH|^~\&|HL7|CG3_SICU|CE_CENTRAL|GH_CSF|20251014154001||ORU^R01|20251014154001-425|P|2.3||||||UNICODE UTF-8
+PID|||10002^^^A^MR||RAPID^^|^^|||||^^^^^^^||||||||||||||||||
+PV1||E|G52008|||||||||||||||||||||||||||||||||||||||||
+OBR|1||||||20251014154001||||||||||||||||||||^^^^|||||||||
+OBX|1|ST|HR||68|/min|||||R
+OBX|2|ST|PVC||0|#/min|||||R
+OBX|3|ST|RR||14|breaths/min|||||R
+OBX|4|ST|CO2EX||28|mm(hg)|||||R
+OBX|5|ST|CO2IN||3|mm(hg)|||||R
+OBX|6|ST|CO2RR||14|breaths/min|||||R
+OBX|7|ST|SPO2R||71|/min|||||R
+OBX|8|ST|SPO2P||100|%|||||R
diff --git a/tests/Integration.Test/data/hl7/2.3/006.txt b/tests/Integration.Test/data/hl7/2.3/006.txt
new file mode 100644
index 000000000..4d411858d
--- /dev/null
+++ b/tests/Integration.Test/data/hl7/2.3/006.txt
@@ -0,0 +1,19 @@
+MSH|^~\&|HL7|CG3_SICU|CE_CENTRAL|GH_CSF|20251014154101||ORU^R01|20251014154101-639|P|2.3||||||UNICODE UTF-8
+PID|||100002^^^A^MR||RAPID^^|^^|||||^^^^^^^||||||||||||||||||
+PV1||E|G52008|||||||||||||||||||||||||||||||||||||||||
+OBR|1||||||20251014154101||||||||||||||||||||^^^^|||||||||
+OBX|1|ST|HR||73|/min|||||R
+OBX|2|ST|PVC||15|#/min|||||R
+OBX|3|ST|STI||-0.5|mm|||||R
+OBX|4|ST|STII||0.0|mm|||||R
+OBX|5|ST|STIII||0.5|mm|||||R
+OBX|6|ST|STV1||0.0|mm|||||R
+OBX|7|ST|STAVR||0.2|mm|||||R
+OBX|8|ST|STAVL||-0.5|mm|||||R
+OBX|9|ST|STAVF||0.2|mm|||||R
+OBX|10|ST|RR||15|breaths/min|||||R
+OBX|11|ST|CO2EX||32|mm(hg)|||||R
+OBX|12|ST|CO2IN||0|mm(hg)|||||R
+OBX|13|ST|CO2RR||14|breaths/min|||||R
+OBX|14|ST|SPO2R||73|/min|||||R
+OBX|15|ST|SPO2P||99|%|||||R
diff --git a/tests/Integration.Test/data/hl7/2.4/001.txt b/tests/Integration.Test/data/hl7/2.4/001.txt
new file mode 100644
index 000000000..b31370cee
--- /dev/null
+++ b/tests/Integration.Test/data/hl7/2.4/001.txt
@@ -0,0 +1,15 @@
+MSH|^~\&|ADT1|MCM|LABADT|MCM|198808181126|SECURITY|ADT^A04|MSG00001|P|2.4
+EVN|A01-|198808181123
+PID|||PATID1234^5^M11||JONES^WILLIAM^A^III||19610615|M-||2106-3|1200 N ELM STREET^^GREENSBORO^NC^27401-1020|GL|(919)379-1212|(919)271-3434~(919)277-3114||S||PATID12345001^2^M10|123456789|9-87654^NC
+NK1|1|JONES^BARBARA^K|SPO|||||20011105
+NK1|1|JONES^MICHAEL^A|FTH
+PV1|1|I|2000^2012^01||||004777^LEBAUER^SIDNEY^J.|||SUR||-||1|A0-
+AL1|1||^PENICILLIN||PRODUCES HIVES~RASH
+AL1|2||^CAT DANDER
+DG1|001|I9|1550|MAL NEO LIVER, PRIMARY|19880501103005|F||
+PR1|2234|M11|111^CODE151|COMMON PROCEDURES|198809081123
+ROL|45^RECORDER^ROLE MASTER LIST|AD|CP|KATE^SMITH^ELLEN|199505011201
+GT1|1122|1519|BILL^GATES^A
+IN1|001|A357|1234|BCMD|||||132987
+IN2|ID1551001|SSN12345678
+ROL|45^RECORDER^ROLE MASTER LIST|AD|CP|KATE^ELLEN|199505011201
\ No newline at end of file
diff --git a/tests/Integration.Test/data/hl7/2.5.1-/002.txt b/tests/Integration.Test/data/hl7/2.5.1-/002.txt
new file mode 100644
index 000000000..cb267f5c3
--- /dev/null
+++ b/tests/Integration.Test/data/hl7/2.5.1-/002.txt
@@ -0,0 +1,6 @@
+MSH|^~\|NIST EHR|NIST EHR Facility|NIST Test Lab APP|NIST Lab Facility|20130211184101-0500||OML^O21^OML_O21|NIST-LOI_5.0_1.1-NG|T|2.5.1|||AL|AL|||||
+PID|1||PATID5421^^^NIST MPI^MR||Wilson^Patrice^Natasha^^^^L||19820304|F||2106-3^White^HL70005|144 East 12th Street^^Los Angeles^CA^90012^^H||^PRN^PH^^^203^2290210|||||||||N^Not Hispanic or Latino^HL70189
+NK1|1|Wilson^Phillip^Arthur^^^^L|SPO^Spouse^HL70063|144 East 12th Street^^Los Angeles^CA^90012^^H|||||||||
+ORC|NW|ORD448811^NIST EHR|||||||20120628070100|||5742200012^Radon^Nicholas^^^^^^NPI^L^^^NPI
+OBR|1|ORD448811^NIST EHR||1000^Hepatitis A B C Panel^99USL|||20120628070100|||||||||5742200012^Radon^Nicholas^^^^^^NPI^L^^^NPI
+DG1|1||F11.129^Opioid abuse with intoxication,unspecified^I10C|||W|||||||||1
\ No newline at end of file
diff --git a/tests/Integration.Test/data/hl7/2.5.1-/003.txt b/tests/Integration.Test/data/hl7/2.5.1-/003.txt
new file mode 100644
index 000000000..a034550f7
--- /dev/null
+++ b/tests/Integration.Test/data/hl7/2.5.1-/003.txt
@@ -0,0 +1,9 @@
+MSH|^~\|NIST EHR^2.16.840.1.113883.3.72.5.22^ISO|NIST EHR Facility^2.16.840.1.113883.3.72.5.23^ISO|NIST Test Lab APP^2.16.840.1.113883.3.72.5.20^ISO|NIST Lab Facility^2.16.840.1.113883.3.72.5.21^ISO|20130211184101-0500||OML^O21^OML_O21|NIST-LOI_3.0_1.1-GU|T|2.5.1|||AL|AL|||||LOI_Common_Component^LOI Base Profile^2.16.840.1.113883.9.66^ISO~LOI_GU_Component^LOI GU Profile^2.16.840.1.113883.9.78^ISO~LAB_PRU_Component^LOI PRU Profile^2.16.840.1.113883.9.82^ISO
+PID|1||PATID1234^^^NIST MPI&2.16.840.1.113883.3.72.5.30.2&ISO^MR||Jones^William^A^JR^^^L||19610615|M||2106-3^White^HL70005|2100 Kennwood Ave^Apt 41^Los Angeles^CA^90067^^H
+ORC|NW|ORD777888^^2.16.840.1.113883.3.72.5.24^ISO||GORD874244^^2.16.840.1.113883.3.72.5.24^ISO|||||20120628070100|||5742200012^Radon^Nicholas^^^^^^NPI&2.16.840.1.113883.4.6&ISO^L^^^NPI||||||||2^Patient has been informed of responsibility, and agrees to pay for service^HL70339
+OBR|1|ORD777888^^2.16.840.1.113883.3.72.5.24^ISO||400.1^Lipid Panel - direct LDL^99USL^57698-3^Lipid panel with direct LDL - Serum or Plasma^LN^20130421^^Lipid Panel - direct LDL|||20110531123551-0800|||||||||5742200012^Radon^Nicholas^^^^^^NPI&2.16.840.1.113883.4.6&ISO^L^^^NPI||||||||||||10092000194^Hamlin^Pafford^^^^^^NPI&2.16.840.1.113883.4.6&ISO^L^^^NPI
+PRT|1^NIST EHR^2.16.840.1.113883.3.72.5.22^ISO|AD||RCT^Result Copies To^HL70912^^^^^^Send blind carbon copies to|10092000194^Hamlin^Pafford^^^^^^NPI&2.16.840.1.113883.4.6&ISO^L^^^NPI||||||||||^^FX^^^323^5555555
+DG1|1||Z82.49^Family history of ischemic heart disease and other diseases of the circulatory system^I10C^^^^^^family history of heart diease NOS|||W|||||||||2
+DG1|2||R00.2^Palpitations^I10C^^^^^^Palpitations|||W|||||||||1
+OBX|1|CWE|49541-6^Fasting status^LN^1902^Fasting Status^99USL||Y^Yes^HL70136||||||O|||20110531|||||||||||||||SCI
+SPM|1|S-666555&NIST EHR&2.16.840.1.113883.3.72.5.24&ISO||119297000^Blood Specimen^SCT^^^^^^Blood|||||||||||||20110531123551-0800
\ No newline at end of file
diff --git a/tests/Integration.Test/data/hl7/2.5.1-/004.txt b/tests/Integration.Test/data/hl7/2.5.1-/004.txt
new file mode 100644
index 000000000..257e1edb5
--- /dev/null
+++ b/tests/Integration.Test/data/hl7/2.5.1-/004.txt
@@ -0,0 +1,13 @@
+MSH|^~\|NIST EHR^2.16.840.1.113883.3.72.5.22^ISO|NIST EHR Facility^2.16.840.1.113883.3.72.5.23^ISO|NIST Test Lab APP^2.16.840.1.113883.3.72.5.20^ISO|NIST Lab Facility^2.16.840.1.113883.3.72.5.21^ISO|20130211184101-0500||OML^O21^OML_O21|NIST-LOI_9.0_1.1-GU_PRU|T|2.5.1|||AL|AL|||||LOI_Common_Component^LOI BaseProfile^2.16.840.1.113883.9.66^ISO~LOI_GU_Component^LOI GU Profile^2.16.840.1.113883.9.78^ISO~LAB_PRU_Component^LOI PRU Profile^2.16.840.1.113883.9.82^ISO
+PID|1||PATID14567^^^NIST MPI&2.16.840.1.113883.3.72.5.30.2&ISO^MR||Hernandez^Maria^^^^^L||19880906|F||2054-5^Black or African American^HL70005|3248 E FlorenceAve^^Huntington Park^CA^90255^^H||^^PH^^^323^5825421|||||||||H^Hispanic or Latino^HL70189
+ORC|NW|ORD231-1^NIST EHR^2.16.840.1.113883.3.72.5.24^ISO|||||||20130116090021-0800|||134569827^Feller^Hans^^^^^^NPI&2.16.840.1.113883.4.6&ISO^L^^^NPI
+OBR|1|ORD231-1^NIST EHR^2.16.840.1.113883.3.72.5.24^ISO||34555-3^Creatinine 24H renal clearance panel^LN^^^^^^CreatinineClearance|||201301151130-0800|201301160912-0800||||||||134569827^Feller^Hans^^^^^^NPI&2.16.840.1.113883.4.6&ISO^L^^^NPI
+DG1|1||I10^Essential (primary) hypertension^I10C^^^^^^Hypertension, NOS|||F|||||||||2
+DG1|2||O10.93^Unspecified pre-existing hypertension complicating the puerperium^I10C^^^^^^Pregnancy with chronic hypertension|||W|||||||||1
+OBX|1|CWE|67471-3^Pregnancy status^LN^1903^Pregnancy status^99USL^2.44^^Isthe patient pregnant?||Y^Yes^HL70136^1^Yes, confirmed less than 12 weeks^99USL^2.5.1^^early pregnancy (pre 12 weeks)||||||O|||20130115|||||||||||||||SCI
+OBX|2|NM|3167-4^Volume of 24 hour Urine^LN^1904^Urine Volume of 24 hour collection^99USL^2.44^^Urine Volume 24hour collection||1250|mL^milliliter^UCUM^ml^mililiter^L^1.7^^ml|||||O|||20130116|||||||||||||||SCI
+OBX|3|NM|3141-9^Body weight Measured^LN^BWm^Body weight Measured^99USL^2.44^^patient weight measured in kg||59.5|kg^kilogram^UCUM|||||O|||20130116|||||||||||||||SCI
+SPM|1|S-2312987-1&NIST EHR&2.16.840.1.113883.3.72.5.24&ISO||276833005^24 hour urine sample (specimen)^SCT^UR24H^24hr Urine^99USL^^^24 hour urine|||||||||||||201301151130-0800^201301160912-0800
+SPM|2|S-2312987-2&NIST EHR&2.16.840.1.113883.3.72.5.24&ISO||119297000^Blood Specimen^SCT|||||||||||||201301160912-0800ORC|NW|ORD231-2^NIST EHR^2.16.840.1.113883.3.72.5.24^ISO|||||||20130115102146-0800|||134569827^Feller^Hans^^^^^^NPI&2.16.840.1.113883.4.6&ISO^L^^^NPI
+OBR|2|ORD231-2^NIST EHR^2.16.840.1.113883.3.72.5.24^ISO||21482-5^Protein [Mass/volume] in 24 hour Urine^LN^^^^^^24 hour Urine Protein|||201301151130-0800|201301160912-0800||||||||134569827^Feller^Hans^^^^^^NPI&2.16.840.1.113883.4.6&ISO^L^^^NPI
+DG1|1||I10^Essential (primary) hypertension^I10C^^^^^^Hypertension, NOS|||F|||||||||2
\ No newline at end of file
diff --git a/tests/Integration.Test/data/hl7/2.5.1-/005.txt b/tests/Integration.Test/data/hl7/2.5.1-/005.txt
new file mode 100644
index 000000000..c4231d74b
--- /dev/null
+++ b/tests/Integration.Test/data/hl7/2.5.1-/005.txt
@@ -0,0 +1,4 @@
+MSH|^~\&|NISTEHRAPP|NISTEHRFAC|NISTIISAPP|NISTIISFAC|20150625072816.601-0500||VXU^V04^VXU_V04|NIST-IZ-AD-10.1_Send_V04_Z22|P|2.5.1||||AL|||||Z22^CDCPHINVS|NISTEHRFAC|NISTIISFAC
+PID|1||21142^^^NIST-MPI-1^MR||Vasquez^Manuel^Diego^^^^L||19470215|M||2106-3^White^CDCREC|227 Park Ave^^Bozeman^MT^59715^USA^P||^PRN^PH^^^406^5555815~^NET^^Manuel.Vasquez@isp.com|||||||||2135-2^Hispanic or Latino^CDCREC||N|1|||||N
+PD1|||||||||||01^No reminder/recall^HL70215|N|20150625|||A|20150625|20150625ORC|RE||31165^NIST-AA-IZ-2|||||||7824^Jackson^Lily^Suzanne^^^^^NIST-PI-1^L^^^PRN|||||||NISTEHRFAC^NISTEHRFacility^HL70362
+RXA|0|1|20141021||152^Pneumococcal Conjugate, unspecified formulation^CVX|999|||01^Historical Administration^NIP001|||||||||||CP|A
\ No newline at end of file
diff --git a/tests/Integration.Test/data/hl7/2.5.1-/006.txt b/tests/Integration.Test/data/hl7/2.5.1-/006.txt
new file mode 100644
index 000000000..ec1cc1532
--- /dev/null
+++ b/tests/Integration.Test/data/hl7/2.5.1-/006.txt
@@ -0,0 +1,16 @@
+MSH|^~\&|NISTEHRAPP|NISTEHRFAC|NISTIISAPP|NISTIISFAC|20150624073733.994-0500||VXU^V04^VXU_V04|NIST-IZ-AD-1.1_Send_V04_Z22|P|2.5.1||||AL|||||Z22^CDCPHINVS|NISTEHRFAC|NISTIISFAC
+PID|1||3123^^^NIST-MPI-1^MR||Richardson^Russell^Clint^^^^L|Billington^^^^^^M|20150415|M||1002-5^American Indian or Alaska Native^CDCREC|543 Blount Drive^^Bozeman^MT^59715^USA^P||^PRN^PH^^^406^5557690|||||||||2186-5^Not Hispanic or Latino^CDCREC||N|1|||||N
+PD1|||||||||||02^Reminder/recall - any method^HL70215|N|20150624|||A|20150415|20150624
+NK1|1|Richardson^Maria^Elizabeth^^^^L|MTH^Mother^HL70063|543 Blount Drive^^Bozeman^MT^59715^USA^P|^PRN^PH^^^406^5557690
+NK1|2|Richardson^John^William^^^^L|FTH^Father^HL70063|543 Blount Drive^^Bozeman^MT^59715^USA^P|^PRN^CP^^^406^5558299
+ORC|RE|3140^NIST-AA-IZ-2|38766^NIST-AA-IZ-2|||||||7824^Jackson^Lily^Suzanne^^^^^NIST-PI-1^L^^^PRN||654^Thomas^Wilma^Elizabeth^^^^^NIST-PI-1^L^^^MD|||||NISTEHRFAC^NISTEHRFacility^HL70362|
+RXA|0|1|20150624||49281-0560-05^Pentacel^NDC|0.5|mL^mL^UCUM||00^New Record^NIP001|7824^Jackson^Lily^Suzanne^^^^^NIST-PI-1^L^^^PRN|^^^NIST-Clinic-1||||526434|20150722|PMC^Sanofi Pasteur^MVX|||CP|A
+RXR|C28161^Intramuscular^NCIT|RT^Right Thigh^HL70163
+OBX|1|CE|30963-3^Vaccine Funding Source^LN|1|VXC50^Public^CDCPHINVS||||||F|||20150624
+OBX|2|CE|64994-7^Vaccine Funding Program Eligibility^LN|2|V04^VFC Eligible - American Indian/Alaska Native^HL70064||||||F|||20150624|||VXC40^per immunization^CDCPHINVS
+OBX|3|CE|69764-9^Document Type^LN|3|253088698300017211160720^Polio VIS^cdcgs1vis||||||F|||20150624
+OBX|4|DT|29769-7^Date Vis Presented^LN|3|20150624||||||F|||20150624
+OBX|5|CE|69764-9^Document Type^LN|4|253088698300006611150402^Haemophilus Influenzae type b VIS^cdcgs1vis||||||F|||20150624
+OBX|6|DT|29769-7^Date Vis Presented^LN|4|20150624||||||F|||20150624
+OBX|7|CE|69764-9^Document Type^LN|5|253088698300003511180824^Diphtheria/Tetanus/Pertussis (DTaP) VIS^cdcgs1vis||||||F|||20150624
+OBX|8|DT|29769-7^Date Vis Presented^LN|5|20150624||||||F|||20150624
\ No newline at end of file
diff --git a/tests/Integration.Test/data/hl7/2.5.1/001.txt b/tests/Integration.Test/data/hl7/2.5.1/001.txt
new file mode 100644
index 000000000..368a11519
--- /dev/null
+++ b/tests/Integration.Test/data/hl7/2.5.1/001.txt
@@ -0,0 +1,183 @@
+MSH|^~\&|SISGDSP|SISGDSP|SISHIERECEIVER^11223344^L,M,N|^^L,M,N|20220808145543||ORU^R01^ORU_R01|220220550|T|2.5.1
+PID|1||^^^NPI^MR||Joe^^^^^^B||202205110000|||2106-3^White||||||||||||2186-5^Not Hispanic or Latino||N|1
+NK1|1|Jones^Maria|MTH^Mother|850 Marina Bay Parkway, APT 125^^Richmond^CA^94806-4000^USA|^^^^^916^3720117|^^^^^916^3729999||||||||||197708010000
+ORC|RE|^FormNumber||^HospOrdNumber||||||||^CORTEZ^ALAN^^^^^^^NPI||||||||||300 PASTEUR DR, RM H1524^^PALO ALTO^CA^94305-2200
+OBR|1|^FormNumber||54089-8^NB Screen Panel Patient AHIC|||202205130000|||||||||^James^Smith||||||20220808145543|||F
+OBR|2|^FormNumber||57128-1^Newborn Screening Report summary panel|||202205130000|||||||||^James^Smith||||||20220808145543|||C
+OBX|1|CE|57721-3^Reason for lab test in Dried blood spot^LN|1|LA12421-6^Initial screen^LN|||N|||F|||20220808145543
+OBX|2|CE|57718-9^Sample quality of Dried blood spot^LN|1|LA12432-3^Acceptable^LN|||N|||F|||20220808145543
+OBX|3|CE|57130-7^Newborn screening report - overall interpretation^LN|1|LA18944-1^Screen is out of range for at least one condition^LN|||N|||C|||20220808145543
+OBX|4|CE|57131-5^Newborn conditions with positive markers [Identifier] in Dried blood spot^LN|1|LA12531-2^VLCAD^LN|||N|||F|||20220808145543
+OBX|5|CE|57131-5^Newborn conditions with positive markers [Identifier] in Dried blood spot^LN|2|LA12537-9^CF^LN|||N|||F|||20220808145543
+OBX|6|CE|57131-5^Newborn conditions with positive markers [Identifier] in Dried blood spot^LN|3|LA12533-8^CAH^LN|||N|||F|||20220808145543
+OBX|7|CE|57720-5^Newborn conditions with equivocal markers [Identifier] in Dried blood spot^LN|1|LA137-2^None^LN|||N|||F|||20220808145543
+OBX|8|TX|57724-7^Newborn screening short narrative summary^LN|1|ACTION REQUIRED\.br\\.br\NBS Testing Lab - COMMUNITY REG MEDICAL CENTER LAB \R\2823 FRESNO ST, FRESNO, CA 93721-1324\.br\\.br\Genetic Disease Laboratory - GDL 850 MARINA BAY PKWY, # G265, RICHMOND, CA 94804-6403\.br\\.br\Lab Director - Rasoul Alikhani Koupaei, PhD., Genetic Disease Laboratory, (510) 231-1790\.br\\.br\Follow-up:\.br\\.br\Acyl Carnitine Panel: An immediate referral to a CCS Metabolic Center is strongly recommended. \.br\\.br\ Cystic Fibrosis (CF): An immediate referral to a CCS approved Cystic Fibrosis Center for evaluation and additional testing is strongly recommended. CAH: An immediate referral to a CCS endocrine center or pediatric endocrinologist, for evaluation and additional testing, is strongly recommended. \.br\\.br\If you have any questions regarding these screening outcomes, please contact the Newborn Screening Staff at Stanford University Medical Center at (510) 412-1562. \.br\\.br\Disclaimer:\.br\\.br\Testing for ALD Tier-1, ALD Tier-2, Pompe Tier-1 and MPS I Tier-1 was developed and its performance characteristics determined by the Genetic Disease Laboratory. It has not been cleared or approved by the U.S. Food and Drug Administration (FDA). The FDA has determined that such clearance or approval is not necessary. This test is used for clinical purposes. It should not be regarded as investigational or for research. This laboratory is certified under the Clinical Laboratory Improvement Amendments of 1988 (CLIA-88) as qualified to perform high complexity testing. The ALD Tier-1, ALD Tier-2, Pompe Tier-1 and MPS I Tier-1 testing was run at the California Department of Public Health Genetic Disease Screening Laboratory. \.br\Test interpretations are based on the Birth/Collection Information provided above and subject to disclaimer below. \.br\Screen for CF based on IRT and the California CFTR DNA mutation panel which consists of the following mutations: 1288insTA, 1717-1G>A, 1812 1G>A, 2055del9>A, 2105-2117del13insAGAAA, 2307insA, 3120+1G>A, 3272-26A>G, 3791delC, 3849+10kbC>T, 3876delA, 406-1G>A, 621+1G>T, 663delT, 711+1G>T, 935delA, A559T, CFTRdel2,3(21kb), delF311, delF508, delI507, G330X, G542X, G551D, G85E, H199Y, N1303K, P205S, Q98R, R1066C, R1162X, R334W, R553X, R75X, S492F, S549N, W1089X, W1204X(3743G>A), W1204X(3744G>A), W1282X. California CFTR Mutation Panel was run at Stanford Health Care, Molecular Pathology Laboratory, 3375 Hillview Avenue, Palo Alto, CA 94304. Directors: James Zehnder, MD; Christian Kunder, MD, PhD; Carlos J. Suarez, MD; (650) 723-6574\.br\Due to biological variability of newborns and differences in detection rates for the various disorders in the newborn period, the Newborn Screening Program will not identify all newborns with these conditions. While a positive screening result identifies newborns at an increased risk to justify a diagnostic work-up, a negative screening result does not rule out the possibility of a disorder. Health care providers should remain watchful for any sign or symptoms of these disorders in their patients. A newborn screening result should not be considered diagnostic, and cannot replace the individualized evaluation and diagnosis of an infant by a well-trained, knowledgeable health care provider. \.br\\.br\|||N|||F|||20220808145543
+OBX|9|TX|57129-9^Full newborn screening summary report for display or printing^LN|1||||N|||F|||20220808145543
+OBX|10|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|1|LA22279-6^SMA^LN|||N|||F|||20220808145543
+OBX|11|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|2|LA25796-6^X-ALD^LN|||N|||F|||20220808145543
+OBX|12|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|3|LA14037-8^GAA^LN|||N|||F|||20220808145543
+OBX|13|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|4|LA25797-4^MPS-I^LN|||N|||F|||20220808145543
+OBX|14|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|5|LA12466-1^3-MCC^LN|||N|||F|||20220808145543
+OBX|15|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|6|LA12468-7^3MGA^LN|||N|||F|||20220808145543
+OBX|16|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|7|LA12469-5^5-OXO^LN|||N|||F|||20220808145543
+OBX|17|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|8|LA12470-3^ARG^LN|||N|||F|||20220808145543
+OBX|18|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|9|LA12482-8^CIT-I^LN|||N|||F|||20220808145543
+OBX|19|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|10|LA12483-6^CIT-II^LN|||N|||F|||20220808145543
+OBX|20|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|11|LA12485-1^CPT-Ia^LN|||N|||F|||20220808145543
+OBX|21|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|12|LA12486-9^CPT-II^LN|||N|||F|||20220808145543
+OBX|22|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|13|LA12493-5^GA-1^LN|||N|||F|||20220808145543
+OBX|23|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|14|LA12495-0^GA-2^LN|||N|||F|||20220808145543
+OBX|24|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|15|LA12497-6^HHH^LN|||N|||F|||20220808145543
+OBX|25|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|16|LA12499-2^HMG^LN|||N|||F|||20220808145543
+OBX|26|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|17|LA12505-6^IVA^LN|||N|||F|||20220808145543
+OBX|27|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|18|LA12507-2^LCHAD^LN|||N|||F|||20220808145543
+OBX|28|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|19|LA12508-0^MAL^LN|||N|||F|||20220808145543
+OBX|29|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|20|LA12509-8^MCAD^LN|||N|||F|||20220808145543
+OBX|30|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|21|LA12510-6^MCD^LN|||N|||F|||20220808145543
+OBX|31|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|22|LA12512-2^MET^LN|||N|||F|||20220808145543
+OBX|32|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|23|LA12513-0^MSUD^LN|||N|||F|||20220808145543
+OBX|33|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|24|LA12516-3^NKHG^LN|||N|||F|||20220808145543
+OBX|34|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|25|LA12520-5^PKU^LN|||N|||F|||20220808145543
+OBX|35|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|26|LA12521-3^PRO I^LN|||N|||F|||20220808145543
+OBX|36|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|27|LA12528-8^TYR-1^LN|||N|||F|||20220808145543
+OBX|37|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|28|LA12529-6^TYR-II^LN|||N|||F|||20220808145543
+OBX|38|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|29|LA12531-2^VLCAD^LN|||N|||F|||20220808145543
+OBX|39|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|30|LA12532-0^BIO^LN|||N|||F|||20220808145543
+OBX|40|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|31|LA12533-8^CAH^LN|||N|||F|||20220808145543
+OBX|41|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|32|LA12537-9^CF^LN|||N|||F|||20220808145543
+OBX|42|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|33|LA12543-7^GALT^LN|||N|||F|||20220808145543
+OBX|43|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|34|LA12566-8^SCID^LN|||N|||F|||20220808145543
+OBX|44|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|35|LA12576-7^SCAD or EMA or IBG or GA-2 (MADD)^LN|||N|||F|||20220808145543
+OBX|45|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|36|LA16207-5^Hemoglobinopathies^LN|||N|||F|||20220808145543
+OBX|46|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|37|99717-3^Hypothyroidism^L|||N|||F|||20220808145543
+OBX|47|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|38|LA12487-7^CUD^LN|||N|||F|||20220808145543
+OBX|48|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|39|LA12474-5^BKT^LN|||N|||F|||20220808145543
+OBX|49|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|40|LA12523-9^PA^LN|||N|||F|||20220808145543
+OBX|50|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|41|LA12515-5^MMA^LN|||N|||F|||20220808145543
+OBX|51|CE|57719-7^Conditions tested for in this newborn screening study [Identifier] in Dried blood spot^LN|42|LA12464-6^2M3HBA^LN|||N|||F|||20220808145543
+OBR|3|^FormNumber||57717-1^Newborn screen card data panel|||202205130000|||||||||^James^Smith||||||20220808145543|||F
+OBX|1|ST|57716-3^State printed on filter paper card [Identifier] in NBS card^LN|1|CA|||N|||F|||20220808145543
+OBX|2|NM|8339-4^Birthweight^LN|1|3035|grams||N|||F|||20220808145543
+OBX|3|TM|57715-5^Time of birth^LN|1|0000|||N|||F|||20220808145543
+OBX|4|CE|57722-1^Birth plurality of Pregnancy^LN|1|LA12411-7^Singleton^LN|||N|||F|||20220808145543
+OBX|5|NM|73806-2^Newborn age in hours^LN|1|24|hour(s)||N|||F|||20220808145543
+OBX|6|CE|57713-0^Infant NICU factors that affect newborn screening interpretation^LN|1|LA137-2^None^LN|||N|||F|||20220808145543
+OBX|7|CE|67704-7^Feeding types^LN|1|LA16917-9^NPO^LN|||N|||F|||20220808145543
+OBX|8|CE|67704-7^Feeding types^LN|3|LA12418-2^TPN^LN|||N|||F|||20220808145543
+OBX|9|TX|^^^99717-5^Accession Number^L|1|091-83-561/21-2013-11|||N|||F|||20220808145543
+OBX|10|TX|62324-9^Post-discharge provider name^LN||Dr Sue Great||||||F|||20220808145543
+OBX|11|TX|62325-6^Post-discharge provider practice ID^LN||1234567890||||||F|||20220808145543
+OBX|12|TX|62327-2^Post-discharge provider practice address^LN||333 Best Care Place, Suite 3B, Los Angeles, California 90001||||||F|||20220808145543
+OBX|13|TN|62328-0^Post-discharge provider practice telephone number^LN||(323) 123-4567||||||F|||20220808145543
+OBR|4|^FormNumber||57794-0^Newborn screening test results panel in Dried blood spot|||202205130000|||||||||^James^Smith||||||20220808145543|||F
+OBR|5|^FormNumber||53261-4^Amino acid newborn screen panel|||202205130000|||||||||^James^Smith||||||20220808145543|||F
+OBX|1|NM|47633-3^Glycine [Moles/volume] in Dried blood spot^LN|1|0.5|µmol/L||N|||F|||20220808145543
+OBX|2|NM|53150-9^Alanine+Beta Alanine+Sarcosine [Moles/volume] in Dried blood spot^LN|1|500|µmol/L|<1000|N|||F|||20220808145543
+OBX|3|NM|47799-2^Valine [Moles/volume] in Dried blood spot^LN|1|0.5|µmol/L||N|||F|||20220808145543
+OBX|4|NM|53151-7^Valine/Phenylalanine [Molar ratio] in Dried blood spot^LN|1|0.00645|{Ratio}|<3.5|N|||F|||20220808145543
+OBX|5|NM|53152-5^Alloisoleucine+Isoleucine+Leucine+Hydroxyproline^LN|1|125|µmol/L|<250|N|||F|||20220808145543
+OBX|6|NM|53154-1^Alloisoleucine+Isoleucine+Leucine+Hydroxyproline/Alanine [Molar ratio] in Dried blood spot^LN|1|0.65|{Ratio}|<1.1|N|||F|||20220808145543
+OBX|7|NM|29573-3^Phenylalanine [Moles/volume] in Dried blood spot^LN|1|77.5|µmol/L|<165|N|||F|||20220808145543
+OBX|8|NM|35572-7^Phenylalanine/Tyrosine [Molar ratio] in Dried blood spot^LN|1|0.75|{Ratio}|<2.4|N|||F|||20220808145543
+OBX|9|NM|35571-9^Tyrosine [Moles/volume] in Dried blood spot^LN|1|425|µmol/L|<850|N|||F|||20220808145543
+OBX|10|NM|53231-7^Succinylacetone [Moles/volume] in Dried blood spot^LN|1|2.25|µmol/L|<4.5|N|||F|||20220808145543
+OBX|11|NM|47700-0^Methionine [Moles/volume] in Dried blood spot^LN|1|27|µmol/L|8-100|N|||F|||20220808145543
+OBX|12|NM|42892-0^Citrulline [Moles/volume] in Dried blood spot^LN|1|16.25|µmol/L|5-60|N|||F|||20220808145543
+OBX|13|NM|54092-2^Citrulline/Arginine [Molar ratio] in Dried blood spot^LN|1|3|{Ratio}|<6|N|||F|||20220808145543
+OBX|14|NM|53155-8^Asparagine+Ornithine [Moles/volume] in Dried blood spot^LN|1|400|µmol/L|<800|N|||F|||20220808145543
+OBX|15|NM|75215-4^Ornithine/Citrulline [Molar ratio] in Dried blood spot^LN|1|0.5|{Ratio}||N|||F|||20220808145543
+OBX|16|NM|47562-4^Arginine [Moles/volume] in Dried blood spot^LN|1|25|µmol/L|<50|N|||F|||20220808145543
+OBX|17|NM|75214-7^Arginine/Ornithine [Molar ratio] in Dried blood spot^LN|1|0.7|{Ratio}|<1.4|N|||F|||20220808145543
+OBX|18|NM|47732-3^Proline [Moles/volume] in Dried blood spot^LN|1|750|µmol/L|<1500|N|||F|||20220808145543
+OBX|19|NM|53232-5^5-Oxoproline+Pipecolate [Moles/volume] in Dried blood spot^LN|1|0.5|µmol/L||N|||F|||20220808145543
+OBX|20|TX|57710-6^Amino acidemias newborn screening comment/discussion^LN|1|Negative|||N|||F|||20220808145543
+OBR|6|^FormNumber||58092-8^Acylcarnitine newborn screen panel|||202205130000|||||||||^James^Smith||||||20220808145543|||F
+OBX|1|CE|58088-6^Acylcarnitine newborn screen interpretation^LN|1|LA18593-6^Out-of-Range^LN|||A|||F|||20220808145543
+OBX|2|TX|58093-6^Acylcarnitine newborn screening comment/discussion^LN|1|This pattern of elevations is consistent with VLCADD|||A|||F|||20220808145543
+OBR|7|4932840580^FormNumber||57084-6^Fatty acid oxidation newborn screen panel|||202205130000|||||||||^James^Smith||||||20220808145543|||F
+OBX|1|NM|38481-8^Carnitine.free (C0)^LN|1|22.6|µmol/L|6-125|N|||F|||20220808145543
+OBX|2|NM|53235-8^Carnitine.free (C0)/Palmitoylcarnitine (C16)+Stearoylcarnitine (C18)^LN|1|4.17|{Ratio}|<75|N|||F|||20220808145543
+OBX|3|NM|50157-7^Acetylcarnitine (C2)^LN|1|28.4|µmol/L|11-80|N|||F|||20220808145543
+OBX|4|NM|75212-1^Malonylcarnitine (C3-DC)/Decanoylcarnitine (C10) [Molar ratio] in Dried blood spot^LN|1|5.50000|{Ratio}|<5.2|H|||F|||20220808145543
+OBX|5|NM|45211-0^Hexanoylcarnitine (C6)^LN|1|0.030|µmol/L|<0.95|N|||F|||20220808145543
+OBX|6|NM|53175-6^Octanoylcarnitine (C8)^LN|1|0.050|µmol/L|<0.6|N|||F|||20220808145543
+OBX|7|NM|53177-2^Octanoylcarnitine (C8)/Decanoylcarnitine (C10)^LN|1|1.25|{Ratio}||N|||F|||20220808145543
+OBX|8|NM|53174-9^Octenoylcarnitine (C8:1)^LN|1|0.050|µmol/L|<0.65|N|||F|||20220808145543
+OBX|9|NM|45197-1^Decanoylcarnitine (C10)^LN|1|0.040|µmol/L|<0.65|N|||F|||20220808145543
+OBX|10|NM|45198-9^Decenoylcarnitine (C10:1)^LN|1|0.45|µmol/L|<0.45|H|||F|||20220808145543
+OBX|11|NM|45199-7^Dodecanoylcarnitine (C12)^LN|1|0.110|µmol/L|<2|N|||F|||20220808145543
+OBX|12|NM|45200-3^Dodecenoylcarnitine (C12:1)^LN|1|0.09|µmol/L||N|||F|||20220808145543
+OBX|13|NM|53192-1^Tetradecanoylcarnitine (C14)^LN|1|1.2|µmol/L|<1.2|H|||F|||20220808145543
+OBX|14|NM|53191-3^Tetradecenoylcarnitine (C14:1)^LN|1|0.8|µmol/L|<0.8|H|||F|||20220808145543
+OBX|15|NM|53194-7^Tetradecenoylcarnitine (C14:1)/Dodecenoylcarnitine (C12:1)^LN|1|8.89|{Ratio}||N|||F|||20220808145543
+OBX|16|NM|53190-5^Tetradecadienoylcarnitine (C14:2)^LN|1|0.09|µmol/L||N|||F|||20220808145543
+OBX|17|NM|50281-5^3-Hydroxytetradecanoylcarnitine (C14-OH)^LN|1|0.020|µmol/L|<0.2|N|||F|||20220808145543
+OBX|18|NM|53199-6^Palmitoylcarnitine (C16)^LN|1|3.850|µmol/L|<10|N|||F|||20220808145543
+OBX|19|NM|53198-8^Palmitoleylcarnitine (C16:1)^LN|1|0.250|µmol/L|<1.4|N|||F|||20220808145543
+OBX|20|NM|50125-4^3-Hydroxypalmitoylcarnitine (C16-OH)^LN|1|0.030|µmol/L|<0.1|N|||F|||20220808145543
+OBX|21|NM|53201-0^3-Hydroxypalmitoylcarnitine (C16-OH)/Palmitoylcarnitine (C16)^LN|1|0.00779|{Ratio}|<0.07|N|||F|||20220808145543
+OBX|22|NM|53241-6^Stearoylcarnitine (C18)^LN|1|1.100|µmol/L|<3.5|N|||F|||20220808145543
+OBX|23|NM|53202-8^Oleoylcarnitine (C18:1)^LN|1|1.570|µmol/L|<7|N|||F|||20220808145543
+OBX|24|NM|45217-7^Linoleoylcarnitine (C18:2)^LN|1|0.19|µmol/L||N|||F|||20220808145543
+OBX|25|NM|50132-0^3-Hydroxystearoylcarnitine (C18-OH)^LN|1|0.010|µmol/L|<0.1|N|||F|||20220808145543
+OBX|26|NM|50113-0^3-Hydroxyoleoylcarnitine (C18:1-OH)^LN|1|0.020|µmol/L|<0.1|N|||F|||20220808145543
+OBR|8|^FormNumber||57085-3^Organic acid newborn screen panel|||202205130000|||||||||^James^Smith||||||20220808145543|||F
+OBX|1|NM|53160-8^Propionylcarnitine (C3)^LN|1|1.3|µmol/L|<6.3|N|||F|||20220808145543
+OBX|2|NM|53163-2^Propionylcarnitine (C3)/Acetylcarnitine (C2)^LN|1|0.05|{Ratio}|<0.3|N|||F|||20220808145543
+OBX|3|NM|67708-8^Malonylcarnitine (C3-DC)+3-Hydroxybutyrylcarnitine (C4-OH)^LN|1|0.220|µmol/L|<0.38|N|||F|||20220808145543
+OBX|4|NM|53166-5^Butyrylcarnitine+Isobutyrylcarnitine (C4)^LN|1|0.300|µmol/L|<1.7|N|||F|||20220808145543
+OBX|5|NM|45216-9^Isovalerylcarnitine+Methylbutyrylcarnitine (C5)^LN|1|0.050|µmol/L|<1|N|||F|||20220808145543
+OBX|6|NM|53240-8^Isovalerylcarnitine+Methylbutyrylcarnitine (C5)/Propionylcarnitine (C3)^LN|1|0.03846|{Ratio}|<0.45|N|||F|||20220808145543
+OBX|7|NM|53170-7^Tiglylcarnitine (C5:1)^LN|1|0.000|µmol/L|<0.5|N|||F|||20220808145543
+OBX|8|NM|50106-4^3-Hydroxyisovalerylcarnitine (C5-OH)^LN|1|0.130|µmol/L|<0.85|N|||F|||20220808145543
+OBX|9|NM|67710-4^Glutarylcarnitine (C5-DC)+3-Hydroxyhexanoylcarnitine (C6-OH)^LN|1|0.140|µmol/L|<0.5|N|||F|||20220808145543
+OBX|10|NM|75216-2^Glutarylcarnitine (C5-DC)/Malonylcarnitine (C3-DC) [Molar ratio] in Dried blood spot^LN|1|0.63636|{Ratio}|>0.6|N|||F|||20220808145543
+OBR|9|^FormNumber||54078-1^Cystic fibrosis newborn screening panel|||202205130000|||||||||^James^Smith||||||20220808145543|||F
+OBX|1|NM|48633-2^Trypsinogen I.free^LN|1|67.00|ng/mL|<63|H|||F|||20220808145543
+OBX|2|CE|46769-6^Cystic fibrosis newborn screen interpretation^LN|1|LA11884-6^Indeterminate^LN|||A|||F|||20220808145543
+OBX|3|TX|57707-2^Cystic fibrosis newborn screening comment/discussion^LN|1|Consistent with Cystic Fibrosis (delF508 and delI507 mutations)|||A|||F|||20220808145543
+OBR|10|^FormNumber||57086-1^Congenital adrenal hyperplasia newborn screening panel|||202205130000|||||||||^James^Smith||||||20220808145543|||F
+OBX|1|NM|38473-5^17-Hydroxyprogesterone^LN|1|305|nmol/L|<70|H|||F|||20220808145543
+OBX|2|CE|46758-9^Congenital adrenal hyperplasia newborn screen interpretation^LN|1|LA18593-6^Out of range^LN|||A|||F|||20220808145543
+OBX|3|TX|57706-4^Congenital adrenal hyperplasia newborn screening comment-discussion^LN|1|Positive|||A|||F|||20220808145543
+OBR|11|^FormNumber||54090-6^Thyroid newborn screening panel|||202205130000|||||||||^James^Smith||||||20220808145543|||F
+OBX|1|NM|29575-8^Thyrotropin^LN|1|14.50|mIU/L|<29|N|||F|||20220808145543
+OBX|2|CE|46762-1^Congenital hypothyroidism newborn screen interpretation^LN|1|LA18592-8^In range^LN|||N|||F|||20220808145543
+OBX|3|TX|57705-6^Congenital hypothyroidism newborn screening comment-discussion^LN|1|Negative|||N|||F|||20220808145543
+OBR|12|^FormNumber||54079-9^Galactosemia newborn screening panel|||202205130000|||||||||^James^Smith||||||20220808145543|||F
+OBX|1|NM|42906-8^Galactose 1 phosphate uridyl transferase^LN|1|55.00|enzyme units|>50|N|||F|||20220808145543
+OBX|2|CE|46737-3^Galactosemias newborn screen interpretation^LN|1|LA18592-8^In range^LN|||N|||F|||20220808145543
+OBX|3|TX|57704-9^Galactosemias newborn screening comment-discussion^LN|1|Negative|||N|||F|||20220808145543
+OBR|13|^FormNumber||54081-5^Hemoglobinopathies newborn screening panel|||202205130000|||||||||^James^Smith||||||20220808145543|||F
+OBX|1|TX|54104-5^Hemoglobin pattern^LN|1|FA|||N|||F|||20220808145543
+OBX|2|TX|57703-1^Hemoglobin disorders newborn screening comment/discussion^LN|1|Usual hemoglobin pattern. These results assume no transfusion prior to testing and do not rule out the possibility of a thalassemia trait or rare hemoglobin variants.|||N|||F|||20220808145543
+OBR|14|^FormNumber||57087-9^Biotinidase newborn screening panel|||202205130000|||||||||^James^Smith||||||20220808145543|||F
+OBX|1|NM|75217-0^Biotinidase [Enzymatic activity/volume] in Dried blood spot^LN|1|15.00|ERU|>10|N|||F|||20220808145543
+OBX|2|CE|46761-3^Biotinidase deficiency newborn screen interpretation^LN|1|LA18592-8^In range^LN|||N|||F|||20220808145543
+OBX|3|TX|57699-1^Biotinidase deficiency newborn screening comment-discussion^LN|1|Negative|||N|||F|||20220808145543
+OBR|15|^FormNumber||62333-0^Severe combined immunodeficiency (SCID) newborn screening panel|||202205130000|||||||||^James^Smith||||||20220808145543|||F
+OBX|1|NM|62320-7^T-cell receptor excision circle [#/volume] in Dried blood spot by Probe and target amplification method^LN|1|33|copies/µL|>18|N|||F|||20220808145543
+OBX|2|CE|62321-5^Severe combined immunodeficiency newborn screen interpretation^LN|1|LA18592-8^In range^LN|||N|||F|||20220808145543
+OBX|3|TX|62322-3^Severe combined immunodeficiency newborn screening comment-discussion^LN|1|Negative|||N|||F|||20220808145543
+OBR|16|^FormNumber||63414-7^Pompe Disease newborn screening panel|||202205130000|||||||||^James^Smith||||||20220808145543|||C
+OBX|1|NM|55827-0^Acid alpha glucosidase [Enzymatic activity/volume] in DBS^LN|1|14.313|µmol/L/h|>=2.079|N|||C|||20220808145543
+OBX|2|CE|63415-4^Pompe Disease deficiency newborn screen interpretation^LN|1|LA18592-8^In range^LN|||N|||C|||20220808145543
+OBX|3|TX|63416-2^Pompe Disease deficiency newborn screening comments-discussion^LN|1|Negative|||N|||C|||20220808145543
+OBX|4|TX|63416-2^Pompe Disease deficiency newborn screening comments-discussion^LN|2|Interpretation Comments: The acid alpha-glucosidase (GAA) enzyme activity is above the cut-off of the daily patient median - suggestive of screen negative for Pompe disease.|||N|||C|||20220808145543
+OBR|17|^FormNumber||79563-3^Mucopolysaccharidosis type I newborn screening panel|||202205130000|||||||||^James^Smith||||||20220808145543|||F
+OBX|1|NM|55909-6^Alpha-L-iduronidase [Enzymatic activity/volume] in DBS^LN|1|7.64|µmol/L/h|>=1.2204|N|||F|||20220808145543
+OBX|2|CE|79564-1^Mucopolysaccharidosis type I newborn screen interpretation^LN|1|LA18592-8^In range^LN|||N|||F|||20220808145543
+OBX|3|TX|79565-8^Mucopolysaccharidosis type I newborn screening comment-discussion^LN|1|Negative |||N|||F|||20220808145543
+OBX|4|TX|79565-8^Mucopolysaccharidosis type I newborn screening comment-discussion^LN|2|Interpretation Comments: The alpha-L-iduronidase (IDUA)enzyme activity is above the cut-off of the daily patient median - suggestive of screen negative for Mucopolysaccharidosis I (MPS I) disorder|||N|||F|||20220808145543
+OBR|18|^FormNumber||92005-8^Spinal muscular atrophy newborn screening panel|||202205130000|||||||||^James^Smith||||||20220808145543|||F
+OBX|1|TX|^^^99717-60^SMN1 Homozygous Deletion Analysis^L|1|Exon 7 Present||Exon 7 Present|N|||F|||20220808145543
+OBX|2|CE|92004-1^Spinal muscular atrophy newborn screen interpretation^LN|1|LA18592-8^In range^LN|||N|||F|||20220808145543
+OBX|3|TX|92003-3^Spinal muscular atrophy newborn screening comment-discussion^LN|1|SMA Negative|||N|||F|||20220808145543
+OBX|4|TX|92003-3^Spinal muscular atrophy newborn screening comment-discussion^LN|2||||N|||F|||20220808145543
+OBR|19|^FormNumber||^^^99717-28^Adrenoleukodystrophy newborn screening panel^L|||202205130000|||||||||^James^Smith||||||20220808145543|||F
+OBX|1|CE|^^^99717-32^Adrenoleukodystrophy deficiency newborn screening interpretation^L|1|LA18592-8^In range^LN|||N|||F|||20220808145543
+OBX|2|TX|^^^99717-33^Adrenoleukodystrophy deficiency newborn screening comments-discussion^L|1|Negative|||N|||F|||20220808145543
+OBR|20|^FormNumber||^^^99717-29^Adrenoleukodystrophy Tier-1 newborn screening panel^L|||202205130000|||||||||^James^Smith||||||20220808145543|||F
+OBX|1|NM|79321-6^Lysophosphatidylcholine(26:0) [Moles/volume] in Dried blood spot^LN|1|0.2|µmol/L|<0.42|N|||F|||20220808145543
+OBR|21|^FormNumber||ClinicalPDFReport^Clinical PDF Report 226-95-827/21-2018-21^^ClinicalPDFReport^Clinical PDF Report 226-95-827/21-2018-21^|||202205130000|||||||||^ORDGLSTNAME^ORDGFRSTNAME^^^^^^||||||20220808145543|||F
+OBX|1|ED|ClinicalPDFReport^Clinical PDF Report 226-95-827/21-2018-21^^ClinicalPDFReport^Clinical PDF Report 226-95-827/21-2018-21^||SIS^Image^PDF^Base64^JVBERi0xLjQNCiWys7S1DQolR2VuZXJhdGVkIGJ5IEV4cGVydFBkZiB2OS40LjANCjEgMCBvYmoNCjw8DQovUGFnZXMgMiAwIFINCi9QYWdlTW9kZSAvVXNlTm9uZQ0KL1BhZ2VMYXlvdXQgL09uZUNvbHVtbg0KL1R5cGUgL0NhdGFsb2cNCj4+DQoNCmVuZG9iag0KMiAwIG9iag0KPDwNCi9Db3VudCAyDQovS2lkcyBbMyAwIFIgNCAwIFJdDQovVHlwZSAvUGFnZXMNCj4+DQoNCmVuZG9iag0KMyAwIG9iag0KPDwNCi9SZXNvdXJjZXMgPDwNCi9YT2JqZWN0IDw8DQovRXhwZXJ0UGRmX2ljZW1lZmhnY2dsaGtvZGVwamlsbWhmYnBuZmhuZXBiIDUgMCBSDQo+Pg0KDQovUHJvY1NldCBbL1BERiAvVGV4dCAvSW1hZ2VDXQ0KPj4NCg0KL1BhcmVudCAyIDAgUg0KL01lZGlhQm94IFswLjAwMDAwIDAuMDAwMDAgNjEyLjAwMDAwIDc5Mi4wMDAwMF0NCi9UeXBlIC9QYWdlDQovQ29udGVudHMgWzYgMCBSIDcgMCBSXQ0KPj4NCg0KZW5kb2JqDQo2IDAgb2JqDQo8PA0KL0ZpbHRlciAvRmxhdGVEZWNvZGUNCi9MZW5ndGggMTM5DQo+Pg0Kc3RyZWFtDQp4nF2MMQ7CMAxF90i5gy9AcENTww47TIxITZ2m0KShYuD4NIJIFV785P+fUWEeKLup9I/oUGhmuEKUAp5SlJ5GtdPa0FqgSjVEdRak+BqozB6b2pT/uIb/aEPaqHxbGGxYdNie3onn17lzt8FyYOd724/+MXWc7sMYvGtTdD5yauE4SXGR4gNvjzCODQplbmRzdHJlYW0NCg0KZW5kb2JqDQo3IDAgb2JqDQo8PA0KL0xlbmd0aCAwDQo+Pg0Kc3RyZWFtDQoNCmVuZHN0cmVhbQ0KDQplbmRvYmoNCjQgMCBvYmoNCjw8DQovUmVzb3VyY2VzIDw8DQovWE9iamVjdCA8PA0KL0V4cGVydFBkZl9pY2VtZWZoZ2NnbGhrb2RlcGppbG1oZmJwbmZobmVwYiA1IDAgUg0KPj4NCg0KL1Byb2NTZXQgWy9QREYgL1RleHQgL0ltYWdlQ10NCj4+DQoNCi9QYXJlbnQgMiAwIFINCi9NZWRpYUJveCBbMC4wMDAwMCAwLjAwMDAwIDYxMi4wMDAwMCA3OTIuMDAwMDBdDQovVHlwZSAvUGFnZQ0KL0NvbnRlbnRzIFs4IDAgUiA5IDAgUl0NCj4+DQoNCmVuZG9iag0KOCAwIG9iag0KPDwNCi9GaWx0ZXIgL0ZsYXRlRGVjb2RlDQovTGVuZ3RoIDEzNw0KPj4NCnN0cmVhbQ0KeJxdjDsOwjAQRHtLvsOewKwjf+IeeqgokeKs40DsmIiC4wMBSxHb7JNm3qDAz0H9RjY/sq7SQnCGzBncOas9pUVr0dmtoIyQjVsFzr4GCt2iUbru4xb+o3VTokMAn94y7A7PQsvj2IfL6ClRiIMfpnibeyrXcUoxdCWHmKl0sJ85O3H2AifWMDwNCmVuZHN0cmVhbQ0KDQplbmRvYmoNCjkgMCBvYmoNCjw8DQovTGVuZ3RoIDANCj4+DQpzdHJlYW0NCg0KZW5kc3RyZWFtDQoNCmVuZG9iag0KMTAgMCBvYmoNCjw8DQovRGlzcGxheURvY1RpdGxlIGZhbHNlDQovTm9uRnVsbFNjcmVlbkJlaGF2aW9yIC9Vc2VOb25lDQovRml0V2luZG93IGZhbHNlDQovQ2VudGVyV2luZG93IGZhbHNlDQovSGlkZVRvb2xiYXIgZmFsc2UNCi9IaWRlV2luZG93VUkgZmFsc2UNCi9IaWRlTWVudWJhciBmYWxzZQ0KPj4NCg0KZW5kb2JqDQo1IDAgb2JqDQo8PA0KL0ZpbHRlciAvRmxhdGVEZWNvZGUNCi9SZXNvdXJjZXMgPDwNCi9Gb250IDw8DQovRXhwZXJ0UGRmX2NmbW5mZmhwYmZoY2tvbmlwbm9sa2tpbmNobGNrbnBpIDExIDAgUg0KL0V4cGVydFBkZl9pamFsYW9rbWdua2JrZm1ib2NrYmNvbWRmaW9lbG5ibSAxMiAwIFINCi9QRUlUVEwrQXJpYWxNVCAxMyAwIFINCi9FeHBlcnRQZGZfZGVjZ3BwZmltYWxha2Vsb3BwZmtvaGJvaGNsY3Bta2IgMTQgMCBSDQovRXhwZXJ0UGRmX2dqaWlubWNuZHBnb2tramlhZmVnaGNjbGhmY2hvY25iIDE1IDAgUg0KPj4NCg0KL1Byb2NTZXQgWy9QREYgL1RleHQgL0ltYWdlQ10NCj4+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mNjN8P8Dh9hiiw0KZW5kc3RyZWFtDQoNCmVuZG9iag0KMTEgMCBvYmoNCjw8DQovV2lkdGhzIFs3NTAgNzUwIDI3OCAyNzggMzU1IDU1NiA1NTYgODg5IDY2NyAxOTEgMzMzIDMzMyAzODkgNTg0IDI3OCAzMzMgMjc4IDI3OCA1NTYgNTU2IDU1NiA1NTYgNTU2IDU1NiA1NTYgNTU2IDU1NiA1NTYgMjc4IDI3OCA1ODQgNTg0IDU4NCA1NTYgMTAxNSA2NjcgNjY3IDcyMiA3MjIgNjY3IDYxMSA3NzggNzIyIDI3OCA1MDAgNjY3IDU1NiA4MzMgNzIyIDc3OCA2NjcgNzc4IDcyMiA2NjcgNjExIDcyMiA2NjcgOTQ0IDY2NyA2NjcgNjExIDI3OCAyNzggMjc4IDQ2OSA1NTYgMzMzIDU1NiA1NTYgNTAwIDU1NiA1NTYgMjc4IDU1NiA1NTYgMjIyIDIyMiA1MDAgMjIyIDgzMyA1NTYgNTU2IDU1NiA1NTYgMzMzIDUwMCAyNzggNTU2IDUwMCA3MjIgNTAwIDUwMCA1MDAgMzM0IDI2MCAzMzQgNTg0IDc1MCA1NTYgNzUwIDIyMiA1NTYgMzMzIDEwMDAgNTU2IDU1NiAzMzMgMTAwMCA2NjcgMzMzIDEwMDAgNzUwIDYxMSA3NTAgNzUwIDIyMiAyMjIgMzMzIDMzMyAzNTAgNTU2IDEwMDAgMzMzIDEwMDAgNTAwIDMzMyA5NDQgNzUwIDUwMCA2NjcgMjc4IDMzMyA1NTYgNTU2IDU1NiA1NTYgMjYwIDU1NiAzMzMgNzM3IDM3MCA1NTYgNTg0IDMzMyA3MzcgNTUyIDQwMCA1NDkgMzMzIDMzMyAzMzMgNTc2IDUzNyAzMzMgMzMzIDMzMyAzNjUgNTU2IDgzNCA4MzQgODM0IDYxMSA2NjcgNjY3IDY2NyA2NjcgNjY3IDY2NyAxMDAwIDcyMiA2NjcgNjY3IDY2NyA2NjcgMjc4IDI3OCAyNzggMjc4IDcyMiA3MjIgNzc4IDc3OCA3NzggNzc4IDc3OCA1ODQgNzc4IDcyMiA3MjIgNzIyIDcyMiA2NjcgNjY3IDYxMSA1NTYgNTU2IDU1NiA1NTYgNTU2IDU1NiA4ODkgNTAwIDU1NiA1NTYgNTU2IDU1NiAyNzggMjc4IDI3OCAyNzggNTU2IDU1NiA1NTYgNTU2IDU1NiA1NTYgNTU2IDU0OSA2MTEgNTU2IDU1NiA1NTYgNTU2IDUwMCA1NTYgNTAwXQ0KL0ZvbnREZXNjcmlwdG9yIDE2IDAgUg0KL0Jhc2VGb250IC9BcmlhbA0KL1N1YnR5cGUgL1RydWVUeXBlDQovRW5jb2RpbmcgL1dpbkFuc2lFbmNvZGluZw0KL0xhc3RDaGFyIDI1NQ0KL1R5cGUgL0ZvbnQNCi9GaXJzdENoYXIgMzANCi9OYW1lIC9FeHBlcnRQZGZfY2ZtbmZmaHBiZmhja29uaXBub2xra2luY2hsY2tucGkNCj4+DQoNCmVuZG9iag0KMTIgMCBvYmoNCjw8DQovV2lkdGhzIFs3NzggNzc4IDI1MCAzMzMgNTU1IDUwMCA1MDAgMTAwMCA4MzMgMjc4IDMzMyAzMzMgNTAwIDU3MCAyNTAgMzMzIDI1MCAyNzggNTAwIDUwMCA1MDAgNTAwIDUwMCA1MDAgNTAwIDUwMCA1MDAgNTAwIDMzMyAzMzMgNTcwIDU3MCA1NzAgNTAwIDkzMCA3MjIgNjY3IDcyMiA3MjIgNjY3IDYxMSA3NzggNzc4IDM4OSA1MDAgNzc4IDY2NyA5NDQgNzIyIDc3OCA2MTEgNzc4IDcyMiA1NTYgNjY3IDcyMiA3MjIgMTAwMCA3MjIgNzIyIDY2NyAzMzMgMjc4IDMzMyA1ODEgNTAwIDMzMyA1MDAgNTU2IDQ0NCA1NTYgNDQ0IDMzMyA1MDAgNTU2IDI3OCAzMzMgNTU2IDI3OCA4MzMgNTU2IDUwMCA1NTYgNTU2IDQ0NCAzODkgMzMzIDU1NiA1MDAgNzIyIDUwMCA1MDAgNDQ0IDM5NCAyMjAgMzk0IDUyMCA3NzggNTAwIDc3OCAzMzMgNTAwIDUwMCAxMDAwIDUwMCA1MDAgMzMzIDEwMDAgNTU2IDMzMyAxMDAwIDc3OCA2NjcgNzc4IDc3OCAzMzMgMzMzIDUwMCA1MDAgMzUwIDUwMCAxMDAwIDMzMyAxMDAwIDM4OSAzMzMgNzIyIDc3OCA0NDQgNzIyIDI1MCAzMzMgNTAwIDUwMCA1MDAgNTAwIDIyMCA1MDAgMzMzIDc0NyAzMDAgNTAwIDU3MCAzMzMgNzQ3IDUwMCA0MDAgNTQ5IDMwMCAzMDAgMzMzIDU3NiA1NDAgMzMzIDMzMyAzMDAgMzMwIDUwMCA3NTAgNzUwIDc1MCA1MDAgNzIyIDcyMiA3MjIgNzIyIDcyMiA3MjIgMTAwMCA3MjIgNjY3IDY2NyA2NjcgNjY3IDM4OSAzODkgMzg5IDM4OSA3MjIgNzIyIDc3OCA3NzggNzc4IDc3OCA3NzggNTcwIDc3OCA3MjIgNzIyIDcyMiA3MjIgNzIyIDYxMSA1NTYgNTAwIDUwMCA1MDAgNTAwIDUwMCA1MDAgNzIyIDQ0NCA0NDQgNDQ0IDQ0NCA0NDQgMjc4IDI3OCAyNzggMjc4IDUwMCA1NTYgNTAwIDUwMCA1MDAgNTAwIDUwMCA1NDkgNTAwIDU1NiA1NTYgNTU2IDU1NiA1MDAgNTU2IDUwMF0NCi9Gb250RGVzY3JpcHRvciAxNyAwIFINCi9CYXNlRm9udCAvVGltZXMjMjBOZXcjMjBSb21hbixCb2xkDQovU3VidHlwZSAvVHJ1ZVR5cGUNCi9FbmNvZGluZyAvV2luQW5zaUVuY29kaW5nDQovTGFzdENoYXIgMjU1DQovVHlwZSAvRm9udA0KL0ZpcnN0Q2hhciAzMA0KL05hbWUgL0V4cGVydFBkZl9pamFsYW9rbWdua2JrZm1ib2NrYmNvbWRmaW9lbG5ibQ0KPj4NCg0KZW5kb2JqDQoxMyAwIG9iag0KPDwNCi9EZXNjZW5kYW50Rm9udHMgWzE4IDAgUl0NCi9Ub1VuaWNvZGUgMTkgMCBSDQovQmFzZUZvbnQgL1BFSVRUTCtBcmlhbE1UDQovU3VidHlwZSAvVHlwZTANCi9FbmNvZGluZyAvSWRlbnRpdHktSA0KL1R5cGUgL0ZvbnQNCi9OYW1lIC9QRUlUVEwrQXJpYWxNVA0KPj4NCg0KZW5kb2JqDQoxNCAwIG9iag0KPDwNCi9XaWR0aHMgWzc1MCA3NTAgMjc4IDMzMyA0NzQgNTU2IDU1NiA4ODkgNzIyIDIzOCAzMzMgMzMzIDM4OSA1ODQgMjc4IDMzMyAyNzggMjc4IDU1NiA1NTYgNTU2IDU1NiA1NTYgNTU2IDU1NiA1NTYgNTU2IDU1NiAzMzMgMzMzIDU4NCA1ODQgNTg0IDYxMSA5NzUgNzIyIDcyMiA3MjIgNzIyIDY2NyA2MTEgNzc4IDcyMiAyNzggNTU2IDcyMiA2MTEgODMzIDcyMiA3NzggNjY3IDc3OCA3MjIgNjY3IDYxMSA3MjIgNjY3IDk0NCA2NjcgNjY3IDYxMSAzMzMgMjc4IDMzMyA1ODQgNTU2IDMzMyA1NTYgNjExIDU1NiA2MTEgNTU2IDMzMyA2MTEgNjExIDI3OCAyNzggNTU2IDI3OCA4ODkgNjExIDYxMSA2MTEgNjExIDM4OSA1NTYgMzMzIDYxMSA1NTYgNzc4IDU1NiA1NTYgNTAwIDM4OSAyODAgMzg5IDU4NCA3NTAgNTU2IDc1MCAyNzggNTU2IDUwMCAxMDAwIDU1NiA1NTYgMzMzIDEwMDAgNjY3IDMzMyAxMDAwIDc1MCA2MTEgNzUwIDc1MCAyNzggMjc4IDUwMCA1MDAgMzUwIDU1NiAxMDAwIDMzMyAxMDAwIDU1NiAzMzMgOTQ0IDc1MCA1MDAgNjY3IDI3OCAzMzMgNTU2IDU1NiA1NTYgNTU2IDI4MCA1NTYgMzMzIDczNyAzNzAgNTU2IDU4NCAzMzMgNzM3IDU1MiA0MDAgNTQ5IDMzMyAzMzMgMzMzIDU3NiA1NTYgMzMzIDMzMyAzMzMgMzY1IDU1NiA4MzQgODM0IDgzNCA2MTEgNzIyIDcyMiA3MjIgNzIyIDcyMiA3MjIgMTAwMCA3MjIgNjY3IDY2NyA2NjcgNjY3IDI3OCAyNzggMjc4IDI3OCA3MjIgNzIyIDc3OCA3NzggNzc4IDc3OCA3NzggNTg0IDc3OCA3MjIgNzIyIDcyMiA3MjIgNjY3IDY2NyA2MTEgNTU2IDU1NiA1NTYgNTU2IDU1NiA1NTYgODg5IDU1NiA1NTYgNTU2IDU1NiA1NTYgMjc4IDI3OCAyNzggMjc4IDYxMSA2MTEgNjExIDYxMSA2MTEgNjExIDYxMSA1NDkgNjExIDYxMSA2MTEgNjExIDYxMSA1NTYgNjExIDU1Nl0NCi9Gb250RGVzY3JpcHRvciAyMCAwIFINCi9CYXNlRm9udCAvQXJpYWwsQm9sZEl0YWxpYw0KL1N1YnR5cGUgL1RydWVUeXBlDQovRW5jb2RpbmcgL1dpbkFuc2lFbmNvZGluZw0KL0xhc3RDaGFyIDI1NQ0KL1R5cGUgL0ZvbnQNCi9GaXJzdENoYXIgMzANCi9OYW1lIC9FeHBlcnRQZGZfZGVjZ3BwZmltYWxha2Vsb3BwZmtvaGJvaGNsY3Bta2INCj4+DQoNCmVuZG9iag0KMTUgMCBvYmoNCjw8DQovV2lkdGhzIFs3NTAgNzUwIDI3OCAzMzMgNDc0IDU1NiA1NTYgODg5IDcyMiAyMzggMzMzIDMzMyAzODkgNTg0IDI3OCAzMzMgMjc4IDI3OCA1NTYgNTU2IDU1NiA1NTYgNTU2IDU1NiA1NTYgNTU2IDU1NiA1NTYgMzMzIDMzMyA1ODQgNTg0IDU4NCA2MTEgOTc1IDcyMiA3MjIgNzIyIDcyMiA2NjcgNjExIDc3OCA3MjIgMjc4IDU1NiA3MjIgNjExIDgzMyA3MjIgNzc4IDY2NyA3NzggNzIyIDY2NyA2MTEgNzIyIDY2NyA5NDQgNjY3IDY2NyA2MTEgMzMzIDI3OCAzMzMgNTg0IDU1NiAzMzMgNTU2IDYxMSA1NTYgNjExIDU1NiAzMzMgNjExIDYxMSAyNzggMjc4IDU1NiAyNzggODg5IDYxMSA2MTEgNjExIDYxMSAzODkgNTU2IDMzMyA2MTEgNTU2IDc3OCA1NTYgNTU2IDUwMCAzODkgMjgwIDM4OSA1ODQgNzUwIDU1NiA3NTAgMjc4IDU1NiA1MDAgMTAwMCA1NTYgNTU2IDMzMyAxMDAwIDY2NyAzMzMgMTAwMCA3NTAgNjExIDc1MCA3NTAgMjc4IDI3OCA1MDAgNTAwIDM1MCA1NTYgMTAwMCAzMzMgMTAwMCA1NTYgMzMzIDk0NCA3NTAgNTAwIDY2NyAyNzggMzMzIDU1NiA1NTYgNTU2IDU1NiAyODAgNTU2IDMzMyA3MzcgMzcwIDU1NiA1ODQgMzMzIDczNyA1NTIgNDAwIDU0OSAzMzMgMzMzIDMzMyA1NzYgNTU2IDMzMyAzMzMgMzMzIDM2NSA1NTYgODM0IDgzNCA4MzQgNjExIDcyMiA3MjIgNzIyIDcyMiA3MjIgNzIyIDEwMDAgNzIyIDY2NyA2NjcgNjY3IDY2NyAyNzggMjc4IDI3OCAyNzggNzIyIDcyMiA3NzggNzc4IDc3OCA3NzggNzc4IDU4NCA3NzggNzIyIDcyMiA3MjIgNzIyIDY2NyA2NjcgNjExIDU1NiA1NTYgNTU2IDU1NiA1NTYgNTU2IDg4OSA1NTYgNTU2IDU1NiA1NTYgNTU2IDI3OCAyNzggMjc4IDI3OCA2MTEgNjExIDYxMSA2MTEgNjExIDYxMSA2MTEgNTQ5IDYxMSA2MTEgNjExIDYxMSA2MTEgNTU2IDYxMSA1NTZdDQovRm9udERlc2NyaXB0b3IgMjEgMCBSDQovQmFzZUZvbnQgL0FyaWFsLEJvbGQNCi9TdWJ0eXBlIC9UcnVlVHlwZQ0KL0VuY29kaW5nIC9XaW5BbnNpRW5jb2RpbmcNCi9MYXN0Q2hhciAyNTUNCi9UeXBlIC9Gb250DQovRmlyc3RDaGFyIDMwDQovTmFtZSAvRXhwZXJ0UGRmX2dqaWlubWNuZHBnb2tramlhZmVnaGNjbGhmY2hvY25iDQo+Pg0KDQplbmRvYmoNCjE2IDAgb2JqDQo8PA0KL0F2Z1dpZHRoIDQ0MQ0KL01pc3NpbmdXaWR0aCA0NDENCi9YSGVpZ2h0IDI1MA0KL0xlYWRpbmcgMTE1MA0KL0ZsYWdzIDMyDQovU3RlbUggNzINCi9EZXNjZW50IC0yMTANCi9TdGVtViA3Mg0KL1R5cGUgL0ZvbnREZXNjcmlwdG9yDQovRm9udEJCb3ggWy02NjUuMDAwMDAgLTIxMi4wMDAwMCAyMDAwLjAwMDAwIDkzOC4wMDAwMF0NCi9JdGFsaWNBbmdsZSAwDQovRm9udE5hbWUgL0FyaWFsDQovTWF4V2lkdGggMjY2NQ0KL0FzY2VudCA3MjgNCi9DYXBIZWlnaHQgNTAwDQo+Pg0KDQplbmRvYmoNCjE3IDAgb2JqDQo8PA0KL0F2Z1dpZHRoIDQyNw0KL01pc3NpbmdXaWR0aCA0MjcNCi9YSGVpZ2h0IDI1MA0KL0xlYWRpbmcgMTE0OQ0KL0ZsYWdzIDM0DQovU3RlbUggMTQ0DQovRGVzY2VudCAtMjE2DQovU3RlbVYgMTQ0DQovVHlwZSAvRm9udERlc2NyaXB0b3INCi9Gb250QkJveCBbLTU1OC4wMDAwMCAtMjE2LjAwMDAwIDIwMDAuMDAwMDAgOTMzLjAwMDAwXQ0KL0l0YWxpY0FuZ2xlIDANCi9Gb250TmFtZSAvVGltZXMjMjBOZXcjMjBSb21hbixCb2xkDQovTWF4V2lkdGggMjU1OA0KL0FzY2VudCA2NzcNCi9DYXBIZWlnaHQgNTAwDQo+Pg0KDQplbmRvYmoNCjE4IDAgb2JqDQo8PA0KL0ZvbnREZXNjcmlwdG9yIDIyIDAgUg0KL0Jhc2VGb250IC9QRUlUVEwrQXJpYWxNVA0KL1N1YnR5cGUgL0NJREZvbnRUeXBlMg0KL1cgWzMgWzI3N10gMTcgWzI3NyAyNzcgNTU2IDU1NiA1NTYgNTU2IDU1NiA1NTYgNTU2IDU1NiA1NTYgNTU2XSAzMSBbNTgzXSAzMyBbNTgzXSA0NyBbNTU2XSA3MCBbNTAwXSA3MiBbNTU2XSA3NSBbNTU2IDIyMl0gNzkgWzIyMiA4MzNdIDgyIFs1NTYgNTU2XSA4NiBbNTAwIDI3N10gMTUxIFs1NzZdIDU0MSBbNTc2XV0NCi9DSURUb0dJRE1hcCAvSWRlbnRpdHkNCi9UeXBlIC9Gb250DQovRFcgMTAwMA0KL0NJRFN5c3RlbUluZm8gPDwNCi9PcmRlcmluZyAoSWRlbnRpdHkpDQovUmVnaXN0cnkgKEFkb2JlKQ0KL1N1cHBsZW1lbnQgMA0KPj4NCg0KPj4NCg0KZW5kb2JqDQoxOSAwIG9iag0KPDwNCi9MZW5ndGggODYxDQo+Pg0Kc3RyZWFtDQovQ0lESW5pdCAvUHJvY1NldCBmaW5kcmVzb3VyY2UgYmVnaW4KMTIgZGljdCBiZWdpbgpiZWdpbmNtYXANCi9DSURTeXN0ZW1JbmZvIDw8IC9SZWdpc3RyeSAoQWRvYmUpL09yZGVyaW5nIChVQ1MpL1N1cHBsZW1lbnQgMD4+IGRlZgovQ01hcE5hbWUgL0Fkb2JlLUlkZW50aXR5LVVDUyBkZWYKL0NNYXBUeXBlIDIgZGVmCjEgYmVnaW5jb2Rlc3BhY2VyYW5nZQ0KPDAwMDM+PDAyMWQ+DQplbmRjb2Rlc3BhY2VyYW5nZQ0KMjggYmVnaW5iZnJhbmdlDQo8MDAwMz48MDAwMz48MDBBMD4KPDAwMTE+PDAwMTE+PDAwMkU+CjwwMDEyPjwwMDEyPjwwMDJGPgo8MDAxMz48MDAxMz48MDAzMD4KPDAwMTQ+PDAwMTQ+PDAwMzE+CjwwMDE1PjwwMDE1PjwwMDMyPgo8MDAxNj48MDAxNj48MDAzMz4KPDAwMTc+PDAwMTc+PDAwMzQ+CjwwMDE4PjwwMDE4PjwwMDM1Pgo8MDAxOT48MDAxOT48MDAzNj4KPDAwMUE+PDAwMUE+PDAwMzc+CjwwMDFCPjwwMDFCPjwwMDM4Pgo8MDAxQz48MDAxQz48MDAzOT4KPDAwMUY+PDAwMUY+PDAwM0M+CjwwMDIxPjwwMDIxPjwwMDNFPgo8MDAyRj48MDAyRj48MDA0Qz4KPDAwNDY+PDAwNDY+PDAwNjM+CjwwMDQ4PjwwMDQ4PjwwMDY1Pgo8MDA0Qj48MDA0Qj48MDA2OD4KPDAwNEM+PDAwNEM+PDAwNjk+CjwwMDRGPjwwMDRGPjwwMDZDPgo8MDA1MD48MDA1MD48MDA2RD4KPDAwNTI+PDAwNTI+PDAwNkY+CjwwMDUzPjwwMDUzPjwwMDcwPgo8MDA1Nj48MDA1Nj48MDA3Mz4KPDAwNTc+PDAwNTc+PDAwNzQ+CjwwMDk3PjwwMDk3PjwwMEI1Pgo8MDIxRD48MDIxRD48MDNCQz4KZW5kYmZyYW5nZQplbmRjbWFwCkNNYXBOYW1lIGN1cnJlbnRkaWN0IC9DTWFwIGRlZmluZXJlc291cmNlIHBvcAplbmQgZW5kDQoNCmVuZHN0cmVhbQ0KDQplbmRvYmoNCjIwIDAgb2JqDQo8PA0KL0F2Z1dpZHRoIDQ3OQ0KL01pc3NpbmdXaWR0aCA0NzkNCi9YSGVpZ2h0IDI1MA0KL0xlYWRpbmcgMTE1MA0KL0ZsYWdzIDk2DQovU3RlbUggMTQ0DQovRGVzY2VudCAtMjEwDQovU3RlbVYgMTQ0DQovVHlwZSAvRm9udERlc2NyaXB0b3INCi9Gb250QkJveCBbLTU2MC4wMDAwMCAtMjEyLjAwMDAwIDEzOTAuMDAwMDAgOTM4LjAwMDAwXQ0KL0l0YWxpY0FuZ2xlIC0xMg0KL0ZvbnROYW1lIC9BcmlhbCxCb2xkSXRhbGljDQovTWF4V2lkdGggMTk1MA0KL0FzY2VudCA3MjgNCi9DYXBIZWlnaHQgNTAwDQo+Pg0KDQplbmRvYmoNCjIxIDAgb2JqDQo8PA0KL0F2Z1dpZHRoIDQ3OQ0KL01pc3NpbmdXaWR0aCA0NzkNCi9YSGVpZ2h0IDI1MA0KL0xlYWRpbmcgMTE1MA0KL0ZsYWdzIDMyDQovU3RlbUggMTQ0DQovRGVzY2VudCAtMjEwDQovU3RlbVYgMTQ0DQovVHlwZSAvRm9udERlc2NyaXB0b3INCi9Gb250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1awIYr7XNtVftAcJtcH7vNduwxBo0rwBg/1eFUmNEijvtI0nT8wB3rNuKi8VX3mo3FHWlJvKoqIpC0pRmSN5kiMz5AqZIoRMkgkiEUTmBsMziyK4gHOTOW74V3kxmhDtnMRr8YlxuK8xkdBPiF2VPclr2NhjHzrI29XZp4YxwFOPWuwbw8Ys7yGvabO71Bsow8fMpB5T6j/7McZ/BqAy6fcBRk1/gIdcOiiy/LZ/jDCePXhR5Pa7gxdBgArzzzYLm/nq7L0fnEuqdlrTi1L4qr1gs5dew3+78ebNgh2wNdEeDqHtsb8aeugf44/4H9c5xufcBP6xXMUf3cdcl6tOEHgD/FRwSMfnwMHWORccgac055BOSgn3KuHK0B+4JW6Ay2RQWXDlTEZwE5hzcbTkOvHSkmCu6SgSTHRN/5I5LQNTLgtmfh+dCuZ0fp8zrCoQVQWkpAoEX0eqQFR8XSBPL5DVFDkcI4cik4wvGDVhps9GzPQZMPT/lq5NKT6qBJ3Q7Rpu23C7cLTZH8/2Cmx/V9fjTsAdOpOX27udPW53uiwwug7rGI4eV8JL3CF3VwwnRqHb9OPQ6jpvK1bFNXac4KhWXze/ynU4zrVevyRYnQdb57lq5iVuk7trPJfJc5k8V82qiVxIbPW6HxNkB9thYo+k7BRs23ZxMbDnc79VxR6uLBaeF0/g1eU1ytKAXTFsNg0Hd61srWxxF9xa3DUD8repq/C8slg8wa9TVw7kWcNGtNeP+qjg/uokvwgKSL0+X/CkptF/FfC5zNpxoh5CHrvR8Njmo5YfKwqobT4ldn+kZbPuYPghEW+BeJ+LsjwGufaAa5lMCv77+vdTu83vgn3p7yNslXAPRYHMSl5Tgn+EZgvmGrb8E3ix4s+KKIAJRpjiaBQjHTalKDlHfM6jo9dPW+la9FKb9IQu0WhJxoUvFh2vWE+EFctJQ39rRr4jr6IteHe+DXYF7ArYNbBr8qqVX9ZkydQyxNSyU46mTDraKGpA0WcU4nGoDQplbmRzdHJlYW0NCg0KZW5kb2JqDQp4cmVmDQowIDI0DQowMDAwMDAwMDAwIDY1NTM1IGYNCjAwMDAwMDAwNDkgMDAwMDAgbg0KMDAwMDAwMDE1MCAwMDAwMCBuDQowMDAwMDAwMjIyIDAwMDAwIG4NCjAwMDAwMDA3NTQgMDAwMDAgbg0KMDAwMDAwMTQ2OSAwMDAwMCBuDQowMDAwMDAwNDczIDAwMDAwIG4NCjAwMDAwMDA2OTUgMDAwMDAgbg0KMDAwMDAwMTAwNSAwMDAwMCBuDQowMDAwMDAxMjI1IDAwMDAwIG4NCjAwMDAwMDEyODQgMDAwMDAgbg0KMDAwMDAyMzQzMyAwMDAwMCBuDQowMDAwMDI0NTY3IDAwMDAwIG4NCjAwMDAwMjU3MjEgMDAwMDAgbg0KMDAwMDAyNTg5OCAwMDAwMCBuDQowMDAwMDI3MDQyIDAwMDAwIG4NCjAwMDAwMjgxODAgMDAwMDAgbg0KMDAwMDAyODQ3NyAwMDAwMCBuDQowMDAwMDI4Nzk1IDAwMDAwIG4NCjAwMDAwMjkyMTAgMDAwMDAgbg0KMDAwMDAzMDEzMyAwMDAwMCBuDQowMDAwMDMwNDQ1IDAwMDAwIG4NCjAwMDAwMzA3NDkgMDAwMDAgbg0KMDAwMDAzMTA2OCAwMDAwMCBuDQp0cmFpbGVyDQo8PA0KL1Jvb3QgMSAwIFINCi9TaXplIDI0DQo+Pg0KDQpzdGFydHhyZWYNCjUyMDg1DQolJUVPRg0K||||||F|||20220808145543
\ No newline at end of file
diff --git a/tests/Integration.Test/data/hl7/2.6/001.txt b/tests/Integration.Test/data/hl7/2.6/001.txt
new file mode 100644
index 000000000..5b791d49e
--- /dev/null
+++ b/tests/Integration.Test/data/hl7/2.6/001.txt
@@ -0,0 +1 @@
+MSH|^~\&|||||20220808145751.997-0700||VXR^V03^VXR_V03|501|T|2.6
diff --git a/tests/Integration.Test/data/hl7/2.8/001.txt b/tests/Integration.Test/data/hl7/2.8/001.txt
new file mode 100644
index 000000000..91d5492f2
--- /dev/null
+++ b/tests/Integration.Test/data/hl7/2.8/001.txt
@@ -0,0 +1,5 @@
+MSH|^~\&|ADT1|GOOD HEALTH HOSPITAL|GHH LAB, INC.|GOOD HEALTH HOSPITAL|198808181126|SECURITY|ADT^A01^ADT_A01|MSG00001|P|2.8||
+EVN|A01|200708181123||
+PID|1||PATID1234^5^M11^ADT1^MR^GOOD HEALTH HOSPITAL~123456789^^^USSSA^SS||EVERYMAN^ADAM^A^III||19610615|M||C|2222 HOME STREET^^GREENSBORO^NC^27401-1020|GL|(555) 555-2004|(555)555-2004||S||PATID12345001^2^M10^ADT1^AN^A|444333333|987654^NC|
+NK1|1|NUCLEAR^NELDA^W|SPO^SPOUSE||||NK^NEXT OF KIN
+PV1|1|I|2000^2012^01||||004777^ATTEND^AARON^A|||SUR||||ADM|A0|
\ No newline at end of file
diff --git a/tests/Integration.Test/debug.sh b/tests/Integration.Test/debug.sh
index ceae295f7..c0fe2d81c 100755
--- a/tests/Integration.Test/debug.sh
+++ b/tests/Integration.Test/debug.sh
@@ -15,7 +15,7 @@
# enable(1)/disable(0) VS code attach debuger
-export VSTEST_HOST_DEBUG=1
+export VSTEST_HOST_DEBUG=0
export SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
TEST_DIR="$SCRIPT_DIR/"
diff --git a/tests/Integration.Test/docker-compose.yml b/tests/Integration.Test/docker-compose.yml
index 018d8aad4..71dc8962d 100644
--- a/tests/Integration.Test/docker-compose.yml
+++ b/tests/Integration.Test/docker-compose.yml
@@ -77,6 +77,7 @@ services:
orthanc:
image: "jodogne/orthanc-plugins"
+ hostname: orthanc
volumes:
- ${PWD}/configs/orthanc.json:/etc/orthanc/orthanc.json
- ${PWD}/.run/orthanc:/var/lib/orthanc/db/
@@ -103,6 +104,7 @@ services:
- ./bin/Release/net6.0:/opt/monai/ig/plug-ins/
ports:
- "104:104"
+ - "2575:2575"
- "5000:5000"
networks:
- testrunner